inspect.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. <template>
  2. <!-- 第一行:统计卡片行 -->
  3. <el-row :gutter="16" class="mb-4">
  4. <el-col :span="6">
  5. <el-card class="stat-card" shadow="never">
  6. <div class="flex flex-col">
  7. <div class="flex justify-between items-center text-gray-400">
  8. <span>昨日工单数量</span>
  9. <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
  10. </div>
  11. <el-divider />
  12. <div class="flex justify-between items-center mb-1">
  13. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  14. >总数量</span
  15. >
  16. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  17. >未完成</span
  18. >
  19. </div>
  20. <div class="flex justify-between items-center mb-1">
  21. <span class="text-3xl font-bold text-gray-700">
  22. {{ day.total }}
  23. </span>
  24. <span class="text-3xl font-bold text-gray-700">
  25. {{ day.todo }}
  26. </span>
  27. </div>
  28. <!-- <el-divider class="my-2" />-->
  29. <!-- <div class="flex justify-between items-center text-gray-400 text-sm">-->
  30. <!-- <span>今日新增</span>-->
  31. <!-- <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
  32. <!-- </div>-->
  33. </div>
  34. </el-card>
  35. </el-col>
  36. <el-col :span="6">
  37. <el-card class="stat-card" shadow="never">
  38. <div class="flex flex-col">
  39. <div class="flex justify-between items-center text-gray-400">
  40. <span>近一周工单数量</span>
  41. <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
  42. </div>
  43. <el-divider />
  44. <div class="flex justify-between items-center mb-1">
  45. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  46. >总数量</span
  47. >
  48. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  49. >未完成</span
  50. >
  51. </div>
  52. <div class="flex justify-between items-center mb-1">
  53. <span class="text-3xl font-bold text-gray-700">
  54. {{ week.total }}
  55. </span>
  56. <span class="text-3xl font-bold text-gray-700">
  57. {{ week.todo }}
  58. </span>
  59. </div>
  60. <!-- <el-divider class="my-2" />-->
  61. <!-- <div class="flex justify-between items-center text-gray-400 text-sm">-->
  62. <!-- <span>今日新增</span>-->
  63. <!-- <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
  64. <!-- </div>-->
  65. </div>
  66. </el-card>
  67. </el-col>
  68. <el-col :span="6">
  69. <el-card class="stat-card" shadow="never">
  70. <div class="flex flex-col">
  71. <div class="flex justify-between items-center text-gray-400">
  72. <span>近一月工单数量</span>
  73. <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
  74. </div>
  75. <el-divider />
  76. <div class="flex justify-between items-center mb-1">
  77. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  78. >总数量</span
  79. >
  80. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  81. >未完成</span
  82. >
  83. </div>
  84. <div class="flex justify-between items-center mb-1">
  85. <span class="text-3xl font-bold text-gray-700">
  86. {{ month.total }}
  87. </span>
  88. <span class="text-3xl font-bold text-gray-700">
  89. {{ month.todo }}
  90. </span>
  91. </div>
  92. <!-- <el-divider class="my-2" />-->
  93. <!-- <div class="flex justify-between items-center text-gray-400 text-sm">-->
  94. <!-- <span>今日新增</span>-->
  95. <!-- <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
  96. <!-- </div>-->
  97. </div>
  98. </el-card>
  99. </el-col>
  100. <el-col :span="6">
  101. <el-card class="stat-card" shadow="never">
  102. <div class="flex flex-col">
  103. <div class="flex justify-between items-center text-gray-400">
  104. <span>工单数量</span>
  105. <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
  106. </div>
  107. <el-divider />
  108. <div class="flex justify-between items-center mb-1">
  109. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  110. >总数量</span
  111. >
  112. <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
  113. >未完成</span
  114. >
  115. </div>
  116. <div class="flex justify-between items-center mb-1">
  117. <span class="text-3xl font-bold text-gray-700">
  118. {{ total.total }}
  119. </span>
  120. <span class="text-3xl font-bold text-gray-700">
  121. {{ total.todo }}
  122. </span>
  123. </div>
  124. <!-- <el-divider class="my-2" />-->
  125. <!-- <div class="flex justify-between items-center text-gray-400 text-sm">-->
  126. <!-- <span>今日新增</span>-->
  127. <!-- <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
  128. <!-- </div>-->
  129. </div>
  130. </el-card>
  131. </el-col>
  132. </el-row>
  133. <!-- 第二行:图表行 -->
  134. <el-row :gutter="16" class="mb-4">
  135. <el-col :span="12">
  136. <el-card class="chart-card" shadow="never">
  137. <template #header>
  138. <div class="flex items-center">
  139. <span class="text-base font-medium text-gray-600">今日工单状态统计</span>
  140. </div>
  141. </template>
  142. <el-row class="h-[220px]">
  143. <el-col :span="12" class="flex flex-col items-center">
  144. <div ref="writeTodayChartRef" class="h-[160px] w-full"></div>
  145. <div class="text-center mt-2">
  146. <span class="text-sm text-gray-600">待执行</span>
  147. </div>
  148. </el-col>
  149. <el-col :span="12" class="flex flex-col items-center">
  150. <div ref="finishedTodayChartRef" class="h-[160px] w-full"></div>
  151. <div class="text-center mt-2">
  152. <span class="text-sm text-gray-600">已执行</span>
  153. </div>
  154. </el-col>
  155. </el-row>
  156. </el-card>
  157. </el-col>
  158. <el-col :span="12">
  159. <el-card class="chart-card" shadow="never">
  160. <template #header>
  161. <div class="flex items-center">
  162. <span class="text-base font-medium text-gray-600">巡检工单状态统计</span>
  163. </div>
  164. </template>
  165. <el-row class="h-[220px]">
  166. <el-col :span="12" class="flex flex-col items-center">
  167. <div ref="writeChartRef" class="h-[160px] w-full"></div>
  168. <div class="text-center mt-2">
  169. <span class="text-sm text-gray-600">待执行</span>
  170. </div>
  171. </el-col>
  172. <el-col :span="12" class="flex flex-col items-center">
  173. <div ref="finishedChartRef" class="h-[160px] w-full"></div>
  174. <div class="text-center mt-2">
  175. <span class="text-sm text-gray-600">已执行</span>
  176. </div>
  177. </el-col>
  178. </el-row>
  179. </el-card>
  180. </el-col>
  181. </el-row>
  182. <!-- 第三行:消息统计行 -->
  183. <el-row>
  184. <el-col :span="24">
  185. <el-card class="chart-card" shadow="never">
  186. <template #header>
  187. <div class="flex items-center justify-between">
  188. <span class="text-base font-medium text-gray-600">近一年数量统计</span>
  189. </div>
  190. </template>
  191. <div ref="chartContainer" class="h-[300px]"></div>
  192. </el-card>
  193. </el-col>
  194. </el-row>
  195. <!-- TODO 第四行:地图 -->
  196. </template>
  197. <script setup lang="ts" name="Index">
  198. import * as echarts from 'echarts/core'
  199. import { BarChart } from 'echarts/charts'; // 显式导入柱状图模块
  200. import {
  201. GridComponent,
  202. LegendComponent,
  203. TitleComponent,
  204. ToolboxComponent,
  205. TooltipComponent
  206. } from 'echarts/components'
  207. import { GaugeChart, LineChart, PieChart } from 'echarts/charts'
  208. import { LabelLayout, UniversalTransition } from 'echarts/features'
  209. import { CanvasRenderer } from 'echarts/renderers'
  210. import {
  211. IotStatisticsDeviceMessageSummaryRespVO,
  212. IotStatisticsSummaryRespVO
  213. } from '@/api/iot/statistics'
  214. import { formatDate } from '@/utils/formatTime'
  215. import { IotStatApi } from '@/api/pms/stat'
  216. // TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
  217. /** IoT 首页 */
  218. defineOptions({ name: 'IoTHome' })
  219. // TODO @super:使用下 Echart 组件,参考 yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue 等
  220. echarts.use([
  221. TooltipComponent,
  222. LegendComponent,
  223. PieChart,
  224. CanvasRenderer,
  225. LabelLayout,
  226. TitleComponent,
  227. ToolboxComponent,
  228. GridComponent,
  229. LineChart,
  230. UniversalTransition,
  231. GaugeChart,
  232. BarChart
  233. ])
  234. const timeRange = ref('7d') // 修改默认选择为近一周
  235. const dateRange = ref<[Date, Date] | null>(null)
  236. const queryParams = reactive({
  237. startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
  238. endTime: Date.now() // 设置默认结束时间为当前时间
  239. })
  240. // const deviceCountChartRef = ref() // 设备数量统计的图表
  241. const reportingChartRef = ref() // 在线设备统计的图表
  242. const dealFinishedChartRef = ref() // 离线设备统计的图表
  243. const transOrderChartRef = ref() // 待激活设备统计的图表
  244. const orderFinishChartRef = ref()
  245. const deviceMessageCountChartRef = ref() // 上下行消息量统计的图表
  246. const writeChartRef = ref() // 上下行消息量统计的图表
  247. const finishedChartRef = ref() // 上下行消息量统计的图表
  248. const writeTodayChartRef = ref() // 上下行消息量统计的图表
  249. const finishedTodayChartRef = ref() // 上下行消息量统计的图表
  250. // 基础统计数据
  251. // TODO @super:初始为 -1,然后界面展示先是加载中?试试用 cursor 改哈
  252. const statsData = ref<IotStatisticsSummaryRespVO>({
  253. productCategoryCount: 0,
  254. productCount: 0,
  255. deviceCount: 0,
  256. deviceMessageCount: 0,
  257. productCategoryTodayCount: 0,
  258. productTodayCount: 0,
  259. deviceTodayCount: 0,
  260. deviceMessageTodayCount: 0,
  261. deviceOnlineCount: 0,
  262. deviceOfflineCount: 0,
  263. deviceInactiveCount: 0,
  264. productCategoryDeviceCounts: {}
  265. })
  266. const day = ref({
  267. total: undefined,
  268. todo: undefined
  269. })
  270. const week = ref({
  271. total: undefined,
  272. todo: undefined
  273. })
  274. const month = ref({
  275. total: undefined,
  276. todo: undefined
  277. })
  278. const total = ref({
  279. total: undefined,
  280. todo: undefined
  281. })
  282. const status = ref({
  283. finished: 0,
  284. todo: 0
  285. })
  286. const todayStatus = ref({
  287. finished: 0,
  288. todo: 0
  289. })
  290. // 消息统计数据
  291. const messageStats = ref<IotStatisticsDeviceMessageSummaryRespVO>({
  292. upstreamCounts: {},
  293. downstreamCounts: {}
  294. })
  295. /** 处理快捷时间范围选择 */
  296. const handleTimeRangeChange = (timeRange: string) => {
  297. const now = Date.now()
  298. let startTime: number
  299. // TODO @super:这个的计算,看看能不能结合 dayjs 简化。因为 1h、24h、7d 感觉是比较标准的。如果没有,抽到 utils/formatTime.ts 作为一个工具方法
  300. switch (timeRange) {
  301. case '1h':
  302. startTime = now - 60 * 60 * 1000
  303. break
  304. case '24h':
  305. startTime = now - 24 * 60 * 60 * 1000
  306. break
  307. case '7d':
  308. startTime = now - 7 * 24 * 60 * 60 * 1000
  309. break
  310. default:
  311. return
  312. }
  313. // 清空日期选择器
  314. dateRange.value = null
  315. // 更新查询参数
  316. queryParams.startTime = startTime
  317. queryParams.endTime = now
  318. // 重新获取数据
  319. getStats()
  320. }
  321. /** 处理自定义日期范围选择 */
  322. const handleDateRangeChange = (value: [Date, Date] | null) => {
  323. if (value) {
  324. // 清空快捷选项
  325. timeRange.value = ''
  326. // 更新查询参数
  327. queryParams.startTime = value[0].getTime()
  328. queryParams.endTime = value[1].getTime()
  329. // 重新获取数据
  330. getStats()
  331. }
  332. }
  333. /** 获取统计数据 */
  334. const getStats = async () => {
  335. // 获取基础统计数据
  336. IotStatApi.getInspectDay().then((res) => {
  337. day.value = res
  338. })
  339. IotStatApi.getInspectWeek().then((res) => {
  340. week.value = res
  341. })
  342. IotStatApi.getInspectMonth().then((res) => {
  343. month.value = res
  344. })
  345. IotStatApi.getInspectTotal().then((res) => {
  346. total.value = res
  347. })
  348. IotStatApi.getInspectStatus().then((res) => {
  349. status.value = res
  350. initCharts()
  351. })
  352. IotStatApi.getInspectTodayStatus().then((res) => {
  353. todayStatus.value = res
  354. debugger
  355. initCharts()
  356. })
  357. // statsData.value = await ProductCategoryApi.getIotStatisticsSummary()
  358. //
  359. // // 获取消息统计数据
  360. // messageStats.value = await ProductCategoryApi.getIotStatisticsDeviceMessageSummary(queryParams)
  361. // 初始化图表
  362. }
  363. /** 初始化图表 */
  364. const initCharts = () => {
  365. //待执行
  366. initGaugeChart(
  367. writeTodayChartRef.value,
  368. todayStatus.value.todo === undefined ? 0 : todayStatus.value.todo,
  369. '#05b'
  370. )
  371. //已执行
  372. initGaugeChart(
  373. finishedTodayChartRef.value,
  374. todayStatus.value.finished === undefined ? 0 : todayStatus.value.finished,
  375. '#f50'
  376. )
  377. // 待执行
  378. initGaugeChart(
  379. writeChartRef.value,
  380. status.value.todo === undefined ? 0 : status.value.todo,
  381. '#05b'
  382. )
  383. //已执行
  384. initGaugeChart(
  385. finishedChartRef.value,
  386. status.value.finished === undefined ? 0 : status.value.finished,
  387. '#f50'
  388. )
  389. // 消息量统计
  390. //initMessageChart()
  391. }
  392. /** 初始化仪表盘图表 */
  393. const initGaugeChart = (el: any, value: number, color: string) => {
  394. echarts.init(el).setOption({
  395. series: [
  396. {
  397. type: 'gauge',
  398. startAngle: 360,
  399. endAngle: 0,
  400. min: 0,
  401. max: statsData.value.deviceCount || 100, // 使用设备总数作为最大值
  402. progress: {
  403. show: true,
  404. width: 12,
  405. itemStyle: {
  406. color: color
  407. }
  408. },
  409. axisLine: {
  410. lineStyle: {
  411. width: 12,
  412. color: [[1, '#E5E7EB']]
  413. }
  414. },
  415. axisTick: { show: false },
  416. splitLine: { show: false },
  417. axisLabel: { show: false },
  418. pointer: { show: false },
  419. anchor: { show: false },
  420. title: { show: false },
  421. detail: {
  422. valueAnimation: true,
  423. fontSize: 24,
  424. fontWeight: 'bold',
  425. fontFamily: 'Inter, sans-serif',
  426. color: color,
  427. offsetCenter: [0, '0'],
  428. formatter: (value: number) => {
  429. return `${value} `
  430. }
  431. },
  432. data: [{ value: value }]
  433. }
  434. ]
  435. })
  436. }
  437. /** 初始化消息统计图表 */
  438. const initMessageChart = () => {
  439. // 获取所有时间戳并排序
  440. // TODO @super:一些 idea 里的红色报错,要去处理掉噢。
  441. const timestamps = Array.from(
  442. new Set([
  443. ...messageStats.value.upstreamCounts.map((item) => Number(Object.keys(item)[0])),
  444. ...messageStats.value.downstreamCounts.map((item) => Number(Object.keys(item)[0]))
  445. ])
  446. ).sort((a, b) => a - b) // 确保时间戳从小到大排序
  447. // 准备数据
  448. const xdata = timestamps.map((ts) => formatDate(ts, 'YYYY-MM-DD HH:mm'))
  449. const upData = timestamps.map((ts) => {
  450. const item = messageStats.value.upstreamCounts.find(
  451. (count) => Number(Object.keys(count)[0]) === ts
  452. )
  453. return item ? Object.values(item)[0] : 0
  454. })
  455. const downData = timestamps.map((ts) => {
  456. const item = messageStats.value.downstreamCounts.find(
  457. (count) => Number(Object.keys(count)[0]) === ts
  458. )
  459. return item ? Object.values(item)[0] : 0
  460. })
  461. // 配置图表
  462. echarts.init(deviceMessageCountChartRef.value).setOption({
  463. tooltip: {
  464. trigger: 'axis',
  465. backgroundColor: 'rgba(255, 255, 255, 0.9)',
  466. borderColor: '#E5E7EB',
  467. textStyle: {
  468. color: '#374151'
  469. }
  470. },
  471. legend: {
  472. data: ['上行消息量', '下行消息量'],
  473. textStyle: {
  474. color: '#374151',
  475. fontWeight: 500
  476. }
  477. },
  478. grid: {
  479. left: '3%',
  480. right: '4%',
  481. bottom: '3%',
  482. containLabel: true
  483. },
  484. xAxis: {
  485. type: 'category',
  486. boundaryGap: false,
  487. data: xdata,
  488. axisLine: {
  489. lineStyle: {
  490. color: '#E5E7EB'
  491. }
  492. },
  493. axisLabel: {
  494. color: '#6B7280'
  495. }
  496. },
  497. yAxis: {
  498. type: 'value',
  499. axisLine: {
  500. lineStyle: {
  501. color: '#E5E7EB'
  502. }
  503. },
  504. axisLabel: {
  505. color: '#6B7280'
  506. },
  507. splitLine: {
  508. lineStyle: {
  509. color: '#F3F4F6'
  510. }
  511. }
  512. },
  513. series: [
  514. {
  515. name: '上行消息量',
  516. type: 'line',
  517. smooth: true, // 添加平滑曲线
  518. data: upData,
  519. itemStyle: {
  520. color: '#3B82F6'
  521. },
  522. lineStyle: {
  523. width: 2
  524. },
  525. areaStyle: {
  526. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  527. { offset: 0, color: 'rgba(59, 130, 246, 0.2)' },
  528. { offset: 1, color: 'rgba(59, 130, 246, 0)' }
  529. ])
  530. }
  531. },
  532. {
  533. name: '下行消息量',
  534. type: 'line',
  535. smooth: true, // 添加平滑曲线
  536. data: downData,
  537. itemStyle: {
  538. color: '#10B981'
  539. },
  540. lineStyle: {
  541. width: 2
  542. },
  543. areaStyle: {
  544. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  545. { offset: 0, color: 'rgba(16, 185, 129, 0.2)' },
  546. { offset: 1, color: 'rgba(16, 185, 129, 0)' }
  547. ])
  548. }
  549. }
  550. ]
  551. })
  552. }
  553. const chartContainer = ref(null)
  554. let chartInstance = null
  555. // 生成过去12个月份的标签 (格式: YYYY-MM)
  556. const generateMonthLabels = () => {
  557. const months = []
  558. const date = new Date()
  559. for (let i = 11; i >= 0; i--) {
  560. const tempDate = new Date(date.getFullYear(), date.getMonth() - i, 1)
  561. const year = tempDate.getFullYear()
  562. const month = String(tempDate.getMonth() + 1).padStart(2, '0')
  563. months.push(`${year}-${month}`)
  564. }
  565. return months
  566. }
  567. // 模拟数据获取
  568. const fetchChartData = async () => {
  569. // 模拟异步请求
  570. return new Promise((resolve) => {
  571. setTimeout(() => {
  572. resolve({
  573. months: generateMonthLabels(),
  574. faults: [20,30,100,40,20,50,70,80,60,90,100,100],
  575. repairs: [10,30,90,30,10,20,60,50,22,34,70,85],
  576. })
  577. }, 300)
  578. })
  579. }
  580. // 初始化图表配置
  581. const initChart = async () => {
  582. if (!chartContainer.value) return
  583. // 获取数据
  584. const { months, faults, repairs } = await fetchChartData()
  585. // ECharts配置
  586. const option = {
  587. tooltip: {
  588. trigger: 'axis',
  589. axisPointer: {
  590. type: 'shadow'
  591. },
  592. formatter: (params) => {
  593. return `${params[0].axisValue}<br/>
  594. ${params[0].marker} ${params[0].seriesName}: ${params[0].value}`
  595. }
  596. },
  597. legend: {
  598. data: ['巡检工单数量'],
  599. top: 25
  600. },
  601. grid: {
  602. left: '3%',
  603. right: '4%',
  604. bottom: '3%',
  605. containLabel: true
  606. },
  607. xAxis: {
  608. type: 'category',
  609. data: months,
  610. axisLabel: {
  611. rotate: 45,
  612. margin: 15
  613. }
  614. },
  615. yAxis: {
  616. type: 'value',
  617. axisLabel: {
  618. formatter: (value) => Math.floor(value).toString()
  619. }
  620. },
  621. series: [
  622. {
  623. name: '巡检工单数量',
  624. type: 'bar',
  625. itemStyle: {
  626. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  627. { offset: 0, color: '#2196df' },
  628. { offset: 1, color: '#2196df' }
  629. ])
  630. },
  631. emphasis: {
  632. focus: 'series'
  633. },
  634. data: repairs
  635. }
  636. ]
  637. }
  638. // 初始化图表
  639. chartInstance = echarts.init(chartContainer.value)
  640. chartInstance.setOption(option)
  641. // 窗口缩放监听
  642. window.addEventListener('resize', handleResize)
  643. handleResize()
  644. }
  645. // 自适应调整
  646. const handleResize = () => {
  647. chartInstance?.resize()
  648. }
  649. /** 初始化 */
  650. onMounted(() => {
  651. getStats()
  652. initChart()
  653. })
  654. onUnmounted(() => {
  655. chartInstance?.dispose()
  656. window.removeEventListener('resize', handleResize)
  657. })
  658. </script>
  659. <style lang="scss" scoped>
  660. </style>