Просмотр исходного кода

✨ feat:
广告组合: 通过查询按钮控制趋势图和列表查询

WanGxC 1 год назад
Родитель
Сommit
3bb819d219

+ 7 - 2
src/views/adManage/portfolios/api.ts

@@ -2,17 +2,22 @@ import { request } from '/@/utils/service'
 import { AddReq, DelReq, EditReq, InfoReq, UserPageQuery } from '@fast-crud/fast-crud'
 import {storeToRefs} from 'pinia'
 import {useShopInfo} from '/@/stores/shopInfo'
+import { usePublicData } from '/@/stores/publicData'
 
+
+const publicData = usePublicData()
+const { dateRange } = storeToRefs(publicData)
 const shopInfo = useShopInfo()
 const {profile} = storeToRefs(shopInfo)
 
-
 export const apiPrefix = '/api/ad_manage/portfolios/'
 export function GetList(query: UserPageQuery) {
+	const updatedQuery = { ...query, startDate: dateRange.value[0], endDate: dateRange.value[1] };
+
 	return request({
 		url: apiPrefix,
 		method: 'get',
-		params: query,
+		params: updatedQuery,
 	})
 }
 export function GetAllPortfolios() {

+ 328 - 0
src/views/adManage/portfolios/chartComponents/dataTendency.vue

@@ -0,0 +1,328 @@
+<template>
+  <div v-loading="loading">
+    <MetricsCards v-model="metrics" :metric-items="metricsItems" @change="changeMetric"></MetricsCards>
+    <el-radio-group v-model="statDim" class="chart-button-group" @change="changeStatDim">
+      <el-radio-button label="day">日</el-radio-button>
+      <el-radio-button label="week" :disabled="!props.fetchLineWeek">周</el-radio-button>
+      <el-radio-button label="month" :disabled="!props.fetchLineWeek">月</el-radio-button>
+    </el-radio-group>
+    <div style="height: 350px" ref="chartRef"></div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { computed, onBeforeUnmount, onMounted, Ref, ref, watch } from 'vue'
+import * as echarts from 'echarts'
+import { spCampaignMetricsEnum } from '/@/views/adManage/utils/enum.js'
+import MetricsCards from '/@/components/MetricsCards/index.vue'
+import XEUtils from 'xe-utils'
+import { buildChartOpt, parseQueryParams } from '/@/views/adManage/utils/tools.js'
+import emitter from '/@/utils/emitter'
+
+// import { useShopInfo } from '/@/stores/shopInfo'
+// import { usePublicData } from '/@/stores/publicData'
+// import { storeToRefs } from 'pinia'
+
+defineOptions({
+  name: 'DataTendencyChart',
+})
+
+interface Props {
+  fetchCard: Function
+  fetchLine: Function
+  fetchLineMonth?: Function
+  fetchLineWeek?: Function
+  query: { [key: string]: any }
+  initMetric?: ShowMetric[]
+  metricEnum?: { [key: string]: string }[]
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  initMetric: () => [
+    { metric: 'Impression', color: '#0085ff', label: '曝光量' },
+    { metric: 'Click', color: '#3fd4cf', label: '点击量' },
+    { metric: 'Spend', color: '#ff9500', label: '花费' },
+  ],
+  metricEnum: () => spCampaignMetricsEnum,
+})
+
+const metrics = ref(props.initMetric)
+// const shopInfo = useShopInfo()
+// const publicData = usePublicData()
+// const { dateRange } = storeToRefs(publicData)
+const metricsItems: Ref<MetricData[]> = ref([])
+let chartObj: any
+const chartRef = ref()
+const statDim = ref('day')
+const option: any = {
+  dataset: {
+    source: [],
+  },
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      label: {
+        backgroundColor: '#6a7985',
+      },
+    },
+  },
+  legend: {
+    selected: {}, // 控制显隐
+    show: false,
+  },
+  grid: {
+    top: 50,
+    right: 150,
+    bottom: 30,
+    left: 65,
+  },
+  xAxis: {
+    type: 'category',
+  },
+  yAxis: [
+    {
+      id: 0,
+      type: 'value',
+      name: '',
+      splitLine: {
+        show: true, // 设置显示分割线
+      },
+      axisLine: {
+        show: true,
+        lineStyle: { color: '' },
+      },
+      show: true,
+    },
+    {
+      id: 1,
+      type: 'value',
+      name: '',
+      position: 'right',
+      splitLine: {
+        show: false,
+      },
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '',
+        },
+      },
+      show: true,
+    },
+    {
+      id: 2,
+      type: 'value',
+      position: 'right',
+      offset: 90,
+      name: '',
+      splitLine: {
+        show: false,
+      },
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '',
+        },
+      },
+      show: true,
+    },
+  ],
+  series: [
+    {
+      id: 0,
+      name: '',
+      type: 'bar',
+      encode: {
+        x: 'Name',
+        y: '',
+      },
+      barWidth: '18px',
+      yAxisIndex: 0,
+      itemStyle: {
+        color: '',
+        borderRadius: [4, 4, 4, 4],
+      },
+    },
+    {
+      id: 1,
+      name: '',
+      type: 'line',
+      encode: {
+        x: 'Name',
+        y: '',
+      },
+      symbolSize: 6,
+      symbol: 'circle',
+      smooth: true,
+      yAxisIndex: 1,
+      itemStyle: {
+        // color: '#ff9500',
+        // borderColor: '#ff9500'
+      },
+      areaStyle: {
+        // color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+        //   { offset: 0, color: '#3fd4cf53' },
+        //   { offset: 1, color: '#3fd4cf03' },
+        // ]),
+      },
+      emphasis: {
+        focus: 'series',
+      },
+    },
+    {
+      id: 2,
+      name: '',
+      type: 'line',
+      encode: {
+        x: 'Name',
+        y: '',
+      },
+      symbolSize: 6,
+      symbol: 'circle',
+      smooth: true,
+      yAxisIndex: 2,
+      itemStyle: {},
+      areaStyle: {},
+      emphasis: {
+        focus: 'series',
+      },
+    },
+  ],
+}
+const loading = ref(true)
+const queryParams = computed(() => parseQueryParams(props.query))
+
+onMounted(() => {
+  getMetricsItems()
+  addResize()
+  // initLine()
+  setTimeout(() => {
+    initLine()
+  }, 0)
+})
+onBeforeUnmount(() => {
+  if (chartObj) {
+    chartObj.dispose()
+    chartObj = null
+  }
+  removeResize()
+})
+
+const initLine = async () => {
+  chartObj = echarts.init(chartRef.value)
+  const items = await getDataset()
+  option.dataset.source = items
+
+  XEUtils.arrayEach(option.series, (info: any, index) => {
+    const color = metrics.value[index].color
+    info.name = metrics.value[index].label
+    info.encode.y = metrics.value[index].metric
+    if (info.type === 'bar') {
+      info.itemStyle.color = color
+    } else {
+      info.itemStyle = { color: color, borderColor: color }
+      info.areaStyle = {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: color + '53' },
+          { offset: 1, color: color + '03' },
+        ]),
+      }
+    }
+  })
+  XEUtils.arrayEach(option.yAxis, (info: any, index) => {
+    info.name = metrics.value[index].label
+    info.axisLine.lineStyle.color = metrics.value[index].color
+  })
+
+  XEUtils.arrayEach(props.metricEnum, (info) => {
+    option.legend.selected[info.label] = false
+  })
+  for (const info of metrics.value) {
+    option.legend.selected[info.label] = true
+  }
+  // console.log(option)
+  chartObj.setOption(option)
+  loading.value = false
+}
+const getDataset = async () => {
+  if (statDim.value === 'week') {
+    if (props.fetchLineWeek) {
+      const resp = await props.fetchLineWeek(queryParams.value)
+      return resp.data
+    }
+  } else if (statDim.value === 'month') {
+    if (props.fetchLineMonth) {
+      const resp = await props.fetchLineMonth(queryParams.value)
+      return resp.data
+    }
+  } else {
+    const resp = await props.fetchLine(queryParams.value)
+    return resp.data
+  }
+}
+const getMetricsItems = async () => {
+  const resp = await props.fetchCard(queryParams.value)
+  const data = resp.data
+  metricsItems.value.length = 0
+  XEUtils.arrayEach(props.metricEnum, (info) => {
+    const tmp: MetricData = {
+      label: info.label,
+      value: info.value,
+      metricVal: data[info.value],
+      gapVal: data[`gap${info.value}`],
+      preVal: data[`prev${info.value}`],
+    }
+    metricsItems.value.push(tmp)
+  })
+}
+
+const changeMetric = () => {
+  const opt = buildChartOpt(option, metrics.value)
+  chartObj.setOption(opt)
+}
+
+const changeStatDim = async () => {
+  loading.value = true
+  let source = await getDataset()
+  if (source.length > 0) {
+    chartObj.setOption({ dataset: { source: source } })
+  }
+  loading.value = false
+}
+
+watch(props.query, async () => {
+  // console.log("------watch-----queryParams", props.query)
+  loading.value = true
+  await getMetricsItems()
+  const items = await getDataset()
+  const opt = { dataset: { source: items } }
+  chartObj.setOption(opt)
+  loading.value = false
+})
+
+const resizeChart = () => {
+  chartObj.resize()
+}
+const addResize = () => {
+  window.addEventListener('resize', resizeChart)
+}
+const removeResize = () => {
+  window.removeEventListener('resize', resizeChart)
+}
+</script>
+
+<style scoped>
+.metrics-cards {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  gap: 12px;
+  width: 100%;
+}
+
+.chart-button-group {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 5px;
+}
+</style>

+ 199 - 187
src/views/adManage/portfolios/crud.tsx

@@ -1,195 +1,207 @@
 import * as api from './api'
 import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
-import { inject, nextTick, ref } from 'vue'
+import { inject, nextTick, ref, watch } from 'vue'
 import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+import emitter from '/@/utils/emitter'
+import {storeToRefs} from 'pinia'
+import { usePublicData } from '/@/stores/publicData'
 
+
+const publicData = usePublicData()
+const { dateRange } = storeToRefs(publicData)
 export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
-	const pageRequest = async (query: UserPageQuery) => {
-		return await api.GetList(query)
-	}
-	const editRequest = async ({ form, row }: EditReq) => {
-		form.id = row.id
-		return await api.UpdateObj(form)
-	}
-	const delRequest = async ({ row }: DelReq) => {
-		return await api.DelObj(row.id)
-	}
-	const addRequest = async ({ form }: AddReq) => {
-		return await api.AddObj(form)
-	}
 
-	//权限判定
-	const hasPermissions = inject('$hasPermissions')
+	// 通过 update 控制趋势图刷新
+	let update = 0
+  const pageRequest = async (query: UserPageQuery) => {
+		update++
+		console.log('update', update)
+		emitter.emit('protfolios-update', update)
+    return await api.GetList(query)
+  }
+  const editRequest = async ({ form, row }: EditReq) => {
+    form.id = row.id
+    return await api.UpdateObj(form)
+  }
+  const delRequest = async ({ row }: DelReq) => {
+    return await api.DelObj(row.id)
+  }
+  const addRequest = async ({ form }: AddReq) => {
+    return await api.AddObj(form)
+  }
+
+  //权限判定
+  const hasPermissions = inject('$hasPermissions')
 
-	return {
-		crudOptions: {
-			table: {
-				height: 800,
-			},
-			container: {
-				fixedHeight: false,
-			},
-			request: {
-				pageRequest,
-				addRequest,
-				editRequest,
-				delRequest,
-			},
-			rowHandle: {
-				fixed: 'right',
-				width: 80,
-				buttons: {
-					view: {
-						show: false,
-					},
-					edit: {
-						iconRight: 'Edit',
-						type: 'text',
-						text: null,
-						// show: hasPermissions('dictionary:Update'),
-					},
-					remove: {
-						iconRight: 'Delete',
-						type: 'text',
-						text: null,
-						// show: hasPermissions('dictionary:Delete'),
-					},
-					// custom: {
-					// 	text: '字典配置',
-					// 	type: 'text',
-					// 	// show: hasPermissions('dictionary:Update'),
-					// 	tooltip: {
-					// 		placement: 'top',
-					// 		content: '字典配置',
-					// 	},
-					// 	//@ts-ignore
-					// 	click: (ctx: any) => {
-					// 		const { row } = ctx;
-					// 		context!.subDictRef.value.drawer = true;
-					// 		nextTick(() => {
-					// 			context!.subDictRef.value.setSearchFormData({ form: { parent: row.id } });
-					// 			context!.subDictRef.value.doRefresh();
-					// 		});
-					// 	},
-					// },
-				},
-			},
-			actionbar: {
-				buttons: {
-					add: {
-						text: '新建广告组合'
-					}
-				}
-			},
-			toolbar: {
-				buttons: {
-					search: {
-						show: true,
-					},
-					compact: {
-						show: false,
-					},
-				},
-			},
-			columns: {
-				// _index: {
-				// 	title: '序号',
-				// 	form: { show: false },
-				// 	column: {
-				// 		//type: 'index',
-				// 		align: 'center',
-				// 		width: '70px',
-				// 		columnSetDisabled: true, //禁止在列设置中选择
-				// 		formatter: (context) => {
-				// 			//计算序号,你可以自定义计算规则,此处为翻页累加
-				// 			let index = context.index ?? 1;
-				// 			let pagination = crudExpose!.crudBinding.value.pagination;
-				// 			// @ts-ignore
-				// 			return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
-				// 		},
-				// 	},
-				// },
-				name: {
-					title: '广告组合',
-					column: {
-						width: '150px',
-					},
-					search: {
-						show: true,
-						component: {
-							props: {
-								clearable: true,
-							},
-						},
-					},
-					form: {
-						rules: [{ required: true, message: '必填项' }],
-					},
-				},
-				state: {
-					title: '状态',
-					type: 'dict-select',
-					dict: dict({
-						data: [
-							{ value: 'enabled', label: '投放中' },
-							{ value: 'disable', label: '禁用' },
-						],
-					}),
-					form: {
-						show: false,
-					},
-				},
-				budget_policy: {
-					title: '预算类型',
-					type: 'dict-select',
-					dict: dict({
-						data: [
-							{ value: '', label: '无预算上限' },
-							{ value: 'dateRange', label: '日期范围' },
-							{ value: 'monthlyRecurring', label: '按月' },
-						],
-					}),
-					form: {
-						value: '',
-					},
-				},
-				budget_startDate: {
-					title: '开始日期',
-					type: 'date',
-					form: {
-						show: compute((context) => context.form.budget_policy === 'dateRange'),
-						rules: [{ required: true, message: '必填项' }],
-					},
-				},
-				budget_endDate: {
-					title: '结束日期',
-					type: 'date',
-					form: {
-						show: compute((context) => context.form.budget_policy !== ''),
-					},
-				},
-				budget_amount: {
-					title: '预算',
-					type: 'number',
-					form: {
-						value: 0,
-						show: compute((context) => context.form.budget_policy !== ''),
-						rules: [{ required: true, message: '必填项' }],
-						component: {
-							min: 0,
-							precision: 2,
-							controlsPosition: 'right',
-						},
-					},
-				},
-				inBudget: {
-					title: '是否预算内',
-					form: {
-						show: false,
-					},
-				},
-				...BaseColumn,
-			},
-		},
-	}
+  return {
+    crudOptions: {
+      table: {
+        height: 800,
+      },
+      container: {
+        fixedHeight: false,
+      },
+      request: {
+        pageRequest,
+        addRequest,
+        editRequest,
+        delRequest,
+      },
+      rowHandle: {
+        fixed: 'right',
+        width: 80,
+        buttons: {
+          view: {
+            show: false,
+          },
+          edit: {
+            iconRight: 'Edit',
+            type: 'text',
+            text: null,
+            // show: hasPermissions('dictionary:Update'),
+          },
+          remove: {
+            iconRight: 'Delete',
+            type: 'text',
+            text: null,
+            // show: hasPermissions('dictionary:Delete'),
+          },
+          // custom: {
+          // 	text: '字典配置',
+          // 	type: 'text',
+          // 	// show: hasPermissions('dictionary:Update'),
+          // 	tooltip: {
+          // 		placement: 'top',
+          // 		content: '字典配置',
+          // 	},
+          // 	//@ts-ignore
+          // 	click: (ctx: any) => {
+          // 		const { row } = ctx;
+          // 		context!.subDictRef.value.drawer = true;
+          // 		nextTick(() => {
+          // 			context!.subDictRef.value.setSearchFormData({ form: { parent: row.id } });
+          // 			context!.subDictRef.value.doRefresh();
+          // 		});
+          // 	},
+          // },
+        },
+      },
+      actionbar: {
+        buttons: {
+          add: {
+            text: '新建广告组合',
+          },
+        },
+      },
+      toolbar: {
+        buttons: {
+          search: {
+            show: true,
+          },
+          compact: {
+            show: false,
+          },
+        },
+      },
+      columns: {
+        // _index: {
+        // 	title: '序号',
+        // 	form: { show: false },
+        // 	column: {
+        // 		//type: 'index',
+        // 		align: 'center',
+        // 		width: '70px',
+        // 		columnSetDisabled: true, //禁止在列设置中选择
+        // 		formatter: (context) => {
+        // 			//计算序号,你可以自定义计算规则,此处为翻页累加
+        // 			let index = context.index ?? 1;
+        // 			let pagination = crudExpose!.crudBinding.value.pagination;
+        // 			// @ts-ignore
+        // 			return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
+        // 		},
+        // 	},
+        // },
+        name: {
+          title: '广告组合',
+          column: {
+            width: '150px',
+          },
+          search: {
+            show: true,
+            component: {
+              props: {
+                clearable: true,
+              },
+            },
+          },
+          form: {
+            rules: [{ required: true, message: '必填项' }],
+          },
+        },
+        state: {
+          title: '状态',
+          type: 'dict-select',
+          dict: dict({
+            data: [
+              { value: 'enabled', label: '投放中' },
+              { value: 'disable', label: '禁用' },
+            ],
+          }),
+          form: {
+            show: false,
+          },
+        },
+        budget_policy: {
+          title: '预算类型',
+          type: 'dict-select',
+          dict: dict({
+            data: [
+              { value: '', label: '无预算上限' },
+              { value: 'dateRange', label: '日期范围' },
+              { value: 'monthlyRecurring', label: '按月' },
+            ],
+          }),
+          form: {
+            value: '',
+          },
+        },
+        budget_startDate: {
+          title: '开始日期',
+          type: 'date',
+          form: {
+            show: compute((context) => context.form.budget_policy === 'dateRange'),
+            rules: [{ required: true, message: '必填项' }],
+          },
+        },
+        budget_endDate: {
+          title: '结束日期',
+          type: 'date',
+          form: {
+            show: compute((context) => context.form.budget_policy !== ''),
+          },
+        },
+        budget_amount: {
+          title: '预算',
+          type: 'number',
+          form: {
+            value: 0,
+            show: compute((context) => context.form.budget_policy !== ''),
+            rules: [{ required: true, message: '必填项' }],
+            component: {
+              min: 0,
+              precision: 2,
+              controlsPosition: 'right',
+            },
+          },
+        },
+        inBudget: {
+          title: '是否预算内',
+          form: {
+            show: false,
+          },
+        },
+        ...BaseColumn,
+      },
+    },
+  }
 }

+ 56 - 41
src/views/adManage/portfolios/index.vue

@@ -1,62 +1,78 @@
 <template>
-	<div class="asj-container">
-		<fs-page class="fs-page-custom">
-			<fs-crud ref="crudRef" v-bind="crudBinding">
-				<template #search-left>
-					<DateRangePicker v-model="dateRange" timezone="America/Los_Angeles"></DateRangePicker>
-				</template>
-				<template #header-middle>
-					<el-tabs v-model="tabActiveName" class="chart-tabs" type="border-card">
-						<el-tab-pane label="数据趋势" name="dataTendency">
-							<DataTendencyChart 
-								v-if="tabActiveName === 'dataTendency'" 
-								:fetch-card="getCardData" 
-								:fetch-line="getLineData"
-								:fetch-line-month="getLineMonthData"
-								:fetch-line-week="getLineWeekData">
-							</DataTendencyChart>
-						</el-tab-pane>
-						<el-tab-pane label="广告结构" name="adStruct" >
-							<!-- <AdStructChart v-if="tabActiveName === 'adStruct'"/> -->
-						</el-tab-pane>
-						<el-tab-pane label="散点视图" name="scatterView">
-							<div v-if="tabActiveName === 'scatterView'">散点视图</div>
-						</el-tab-pane>
-					</el-tabs>
-				</template>
-			</fs-crud>
-		</fs-page>
-	</div>
+  <div class="asj-container">
+    <fs-page class="fs-page-custom">
+      <fs-crud ref="crudRef" v-bind="crudBinding">
+        <template #search-left>
+          <DateRangePicker v-model="dateRange" timezone="America/Los_Angeles"></DateRangePicker>
+        </template>
+        <template #header-middle>
+          <el-tabs v-model="tabActiveName" class="chart-tabs" type="border-card">
+            <el-tab-pane label="数据趋势" name="dataTendency">
+              <DataTendencyChart
+                v-if="tabActiveName === 'dataTendency'"
+                :query="queryParams"
+                :fetch-card="getCardData"
+                :fetch-line="getLineData"
+                :fetch-line-month="getLineMonthData"
+                :fetch-line-week="getLineWeekData">
+              </DataTendencyChart>
+            </el-tab-pane>
+            <el-tab-pane label="广告结构" name="adStruct">
+              <!-- <AdStructChart v-if="tabActiveName === 'adStruct'"/> -->
+            </el-tab-pane>
+            <el-tab-pane label="散点视图" name="scatterView">
+              <div v-if="tabActiveName === 'scatterView'">散点视图</div>
+            </el-tab-pane>
+          </el-tabs>
+        </template>
+      </fs-crud>
+    </fs-page>
+  </div>
 </template>
 
 <script lang="ts" setup name="Portfolios">
-import { ref, onMounted } from 'vue';
-import { useFs } from '@fast-crud/fast-crud';
-import { createCrudOptions } from './crud';
+import { ref, onMounted, onBeforeUnmount } from 'vue'
+import { useFs } from '@fast-crud/fast-crud'
+import { createCrudOptions } from './crud'
 import DateRangePicker from '/@/components/DateRangePicker/index.vue'
-import DataTendencyChart from '/@/views/adManage/sp/chartComponents/dataTendency.vue'
+import DataTendencyChart from './chartComponents/dataTendency.vue'
 import { getCardData, getLineData, getLineMonthData, getLineWeekData } from './api'
 import { useShopInfo } from '/@/stores/shopInfo'
 import { usePublicData } from '/@/stores/publicData'
 import { storeToRefs } from 'pinia'
+import emitter from '/@/utils/emitter'
 
 const shopInfo = useShopInfo()
 const publicData = usePublicData()
 const { dateRange } = storeToRefs(publicData)
-const tabActiveName = ref("dataTendency")
-const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
+const tabActiveName = ref('dataTendency')
+const { profile } = storeToRefs(shopInfo)
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} })
 
-// 页面打开后获取列表数据
-onMounted(() => {
-	crudExpose.doRefresh();
-});
+// 避免时间更改后立马刷新趋势图, 通过监听事件触发
+const updateDateRange = ref([])
+emitter.on('protfolios-update', (value: any) => {
+  if (value !== 1) {
+    updateDateRange.value = dateRange.value
+  }
+})
 
+const queryParams = ref({
+  profileId: profile.value.profile_id,
+  updateDateRange,
+})
 
+// 页面打开后获取列表数据
+onMounted(() => {
+  crudExpose.doRefresh()
+})
 
+onBeforeUnmount(() => {
+  emitter.all.clear()
+})
 </script>
 
 <style scoped>
-
 .public-search {
   display: flex;
   gap: 3px;
@@ -66,7 +82,6 @@ onMounted(() => {
   z-index: 10;
   width: 100%;
   background-color: #f8f8f8;
-  box-shadow: 0px 0px 0px rgba(51,89,181,0.16);
+  box-shadow: 0px 0px 0px rgba(51, 89, 181, 0.16);
 }
-
 </style>