需求,我的 vue3 中的 data=ref([])
更新之后,我希望 echarts 的图标也跟着一起更新
但是目前不会,我该怎么修改
<template> <top-bar></top-bar> <div class="container"> <a-page-header style="border: 1px solid rgb(235, 237, 240)" title="匹配结果入库" sub-title="查看匹配结果入库情况" @back="navigateToRoot"> <a-row class="content"> <div style="flex: 1"> <br /> <div> 在该页面,可以查看以下信息: <br /> <br /> <ul> <li>指定公司维度的产出</li> <li>指定平台维度的产出</li> <li>指定母本的产出</li> </ul> </div> </div> </a-row> </a-page-header> </div> <div class="container"> <div class="container-item"> <a-form :model="formState" :label-col="labelCol" :wrapper-col="wrapperCol"> <a-form-item label="meta_uuid"> <a-input v-model:value="formState.meta_uuid" placeholder="根据 meta_uuid 筛选,可以不填" /> </a-form-item> <a-form-item label="company_id"> <a-form-item name="input-number" no-style> <a-input-number v-model:value="formState['company_id']" placeholder="根据 company_id 筛选,可以不填" /> </a-form-item> <span class="ant-form-text">根据 company_id 筛选,可以不填</span><a href="/company_list" rel="external nofollow" target="_blank">查看公司列表</a> </a-form-item> <a-form-item label="track_source_id"> <a-form-item name="input-number" no-style> <a-input-number v-model:value="formState['track_source_id']" placeholder="请输入 track_source_id" /> </a-form-item> <span class="ant-form-text">指定 track_source_id <a href="/search_engine_health_check" rel="external nofollow" target="_blank">[详情]</a></span> </a-form-item> <a-form-item label="last_day"> <a-form-item name="input-number" no-style> <a-input-number v-model:value="formState['last_day']" placeholder="查看最近多少天" /> </a-form-item> <span class="ant-form-text"> 查看最近多少天</span> </a-form-item> <a-form-item label="选择资源类型"> <a-form-item name="input-number" no-style> <!-- <a-input-number v-model:value="formState['source_type']" placeholder="根据 source_type 筛选,可以不填" /> --> <a-radio-group v-model:value="formState.source_type"> <a-radio value="image">image</a-radio> <a-radio value="text">text</a-radio> </a-radio-group> </a-form-item> <!-- <span class="ant-form-text">选择资源类型:image 或者 text</span> --> </a-form-item> <a-form-item label="match 状态"> <a-form-item name="input-number" no-style> <!-- <a-input-number v-model:value="formState['classify']" placeholder="根据 source_type 筛选,可以不填" /> --> <a-radio-group v-model:value="formState.classify"> <a-radio value="all">所有</a-radio> <a-radio value="unclassify">疑似侵权</a-radio> <a-radio value="infringement">确认侵权</a-radio> </a-radio-group> </a-form-item> <!-- <span class="ant-form-text">选择全部还是疑似侵权</span> --> </a-form-item> <a-form-item :wrapper-col="{ span: 14, offset: 4 }"> <a-button type="primary" :loading="searchLoading" @click="sendRequest"> 提交 </a-button> <span style="margin-left: 25px"></span> <a-button @click="clearForm">清空</a-button> </a-form-item> </a-form> </div> </div> <div class="container"> <div class="container-item"> <a-typography-title :level="5"> 每个入库情况: </a-typography-title> <div ref="chartRefText" class="chart"></div> <button @click="toggleLegend">一键反选图例</button> </div> </div> </template> <script setup> import axios from "axios"; import { ref, onMounted, reactive, watch } from "vue"; import * as echarts from "echarts"; import { useRouter, useRoute } from "vue-router"; const chartRefText = ref(null); let myChartText; const data = ref([]); const searchLoading = ref(false); const labelCol = { style: { width: "150px", }, }; const wrapperCol = { span: 14, }; const route = useRoute(); const formState = reactive({ keyword_task_id: route.query.keyword_task_id || null, meta_uuid: route.query.meta_uuid || null, company_id: route.query.company_id || null, last_day: route.query.last_day || 7, track_source_id: route.query.track_source_id || null, classify: route.query.classify || 'all', source_type: route.query.source_type || 'image', }); onMounted(async () => { myChartText = echarts.init(chartRefText.value); try { const xAxisData = data.value.map(item => item.source_date); const yAxisData = data.value.map(item => item.source_count); const option = { xAxis: { type: 'category', data: xAxisData }, yAxis: { type: 'value' }, series: [{ type: 'line', data: yAxisData }] }; myChartText.setOption(option); } catch (error) { console.error(error); } }); watch(async () => { myChartText = echarts.init(chartRefText.value); try { const xAxisData = data.value.map(item => item.source_date); const yAxisData = data.value.map(item => item.source_count); const option = { xAxis: { type: 'category', data: xAxisData }, yAxis: { type: 'value' }, series: [{ type: 'line', data: yAxisData }] }; myChartText.setOption(option); } catch (error) { console.error(error); } }); const clearForm = () => { for (const key in formState) { if (Reflect.has(formState, key)) { formState[key] = null; } } formState["limit"] = 20; const urlParams = new URLSearchParams(); for (const key in formState) { if (Reflect.has(formState, key)) { if (formState[key] === "") { formState[key] = null; } if (formState[key] !== null) { urlParams.set(key, formState[key]); } } } window.history.replaceState(null, null, `?${urlParams.toString()}`); }; const sendRequest = () => { const urlParams = new URLSearchParams(); for (const key in formState) { if (Reflect.has(formState, key)) { if (formState[key] === "") { formState[key] = null; } if (formState[key] !== null) { urlParams.set(key, formState[key]); } } } window.history.replaceState(null, null, `?${urlParams.toString()}`); searchLoading.value = true; const queryParams = {}; for (const key in formState) { if (Reflect.has(formState, key)) { if (formState[key] !== null) { queryParams[key] = formState[key]; } } } const url = "http://xxxxx.cn/match/count"; axios .get(url, { params: queryParams, }) .then((response) => { data.value = response.data; }) .catch((error) => { console.error(error); }) .finally(() => { searchLoading.value = false; }); }; const toggleLegend = () => { const { legend } = myChartText.getOption() || {}; // Ensure legend is defined const { selected } = legend || {}; const newSelected = {}; Object.keys(selected || {}).forEach((key) => { newSelected[key] = !selected[key]; }); myChartText.setOption({ legend: { selected: newSelected, }, }); }; </script> <style scoped> .chart-container { width: 100%; max-width: 1440px; margin: 0 auto; } .chart { width: 100%; height: 400px; } </style>
接口返回的数据结构类似:
[ { "source_count": 2986, "source_date": "2023-07-27" }, { "source_count": 1947, "source_date": "2023-07-28" }, { "source_count": 8124, "source_date": "2023-07-29" }, { "source_count": 4600, "source_date": "2023-07-30" }, { "source_count": 262235, "source_date": "2023-07-31" }, { "source_count": 890030, "source_date": "2023-08-01" }, { "source_count": 178823, "source_date": "2023-08-02" }, { "source_count": 3574, "source_date": "2023-08-03" } ]
我重新描述一下我的需求,因为上面的那段代码,本来是跑不起来的
但是突然可以跑起来了?
所以是可以运行了
但是我觉得实现方式非常的不优雅
而且会有警告
我的需求是,在这个 form 组件里面,选择好了一堆参数之后,点击「提交」
然后 axios 请求后端接口,然后接口返回数据
然后会数据结构是这样的:
[ { "source_count": 2986, "source_date": "2023-07-27" }, { "source_count": 1947, "source_date": "2023-07-28" }, { "source_count": 8124, "source_date": "2023-07-29" }, { "source_count": 4600, "source_date": "2023-07-30" }, { "source_count": 262235, "source_date": "2023-07-31" }, { "source_count": 890030, "source_date": "2023-08-01" }, { "source_count": 178823, "source_date": "2023-08-02" }, { "source_count": 3574, "source_date": "2023-08-03" } ]
然后我要用 echarts 把这个 list[dict]
的数据渲染成折线图
但是落实到具体编码的时候,我遇到了问题:
- 但我在 onMounted 里面定义
myChartText = echarts.init(chartRefText.value);
的时候,点击「提交」之后,echarts 是不会刷新的 - 然后我又在 watch 里面重新定义了
myChartText = echarts.init(chartRefText.value);
,这样功能实现是没有问题,但是会有警告[ECharts] There is a chart instance already initialized on the dom.
, 我感觉不优雅
所以,我应该怎么办?
是定义一个全局变量 myChartText = echarts.init(chartRefText.value);
,然后在 onMounted 或者 watch 里面使用全局变量 myChartText 吗?
watch 用法有误,需要在第一个参数手动指定被侦听的数据,或者可以直接改为使用 watchEffect,可以无需手动指定被侦听数据。示例如下:
// 使用watch时指定侦听data watch(data, () => { }); // 使用watchEffect时无需指定侦听data watchEffect(() => { // 直接访问即可,watchEffect会自动跟踪变化 const xAxisData = data.value.map(item => item.source_date); });
watch
vs.watchEffect
watch
和watchEffect
都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:watch
只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch
会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。watchEffect
,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。
至于你补充的警告,在 watch
中不要每次都调用 echarts.init
方法,可以判断如果未初始化的情况再去调用。另外,在 watch
中执行初始化及更新的操作即可,无需在 onMounted
中提前执行一次。示例如下:
const element = ref(null); const instance = shallowRef(null); // 这里改用 watchPostEffect 以防止 element.value 为 null watchPostEffect(() => { if(instance.value == null) { instance.value = echarts.init(element.value); } const option = { // ... }; instance.value.setOption(option); });
关于 watchPostEffect
的用法可查看文档 Vue watchPostEffect()