vue2server 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +6 -0
  2. package/eslint.config.js +20 -0
  3. package/frontEnd/package-lock.json +18392 -0
  4. package/frontEnd/package.json +47 -0
  5. package/frontEnd/public/.gitkeep +0 -0
  6. package/frontEnd/public/index.html +17 -0
  7. package/frontEnd/src/App.vue +69 -0
  8. package/frontEnd/src/api/mock.js +5 -0
  9. package/frontEnd/src/assets/.gitkeep +0 -0
  10. package/frontEnd/src/components/AppMenu.vue +86 -0
  11. package/frontEnd/src/main.js +14 -0
  12. package/frontEnd/src/pages/LineChartPage.vue +573 -0
  13. package/frontEnd/src/pages/MedalIssueDialog.vue +558 -0
  14. package/frontEnd/src/pages/MedalSetting.vue +571 -0
  15. package/frontEnd/src/pages/MeetingListPage.vue +542 -0
  16. package/frontEnd/src/router/index.js +12 -0
  17. package/frontEnd/src/router/routes.js +53 -0
  18. package/frontEnd/src/utils/request.js +28 -0
  19. package/frontEnd/vue.config.js +8 -0
  20. package/package.json +54 -0
  21. package/src/app.ts +88 -0
  22. package/src/controllers/health.controller.ts +14 -0
  23. package/src/controllers/medal.controller.ts +46 -0
  24. package/src/controllers/mock.controller.ts +41 -0
  25. package/src/middlewares/response.middleware.ts +71 -0
  26. package/src/routes/api.route.ts +13 -0
  27. package/src/routes/index.route.ts +5 -0
  28. package/src/routes/modules/health.route.ts +8 -0
  29. package/src/routes/modules/medal.route.ts +10 -0
  30. package/src/routes/modules/mock.route.ts +9 -0
  31. package/src/services/medal.service.ts +159 -0
  32. package/src/services/mock.service.ts +46 -0
  33. package/src/utils/httpError.ts +13 -0
  34. package/test/health.test.js +12 -0
  35. package/test/trend.test.js +30 -0
  36. package/tsconfig.json +15 -0
@@ -0,0 +1,573 @@
1
+ <template>
2
+ <div class="page">
3
+ <!-- 年份选择工具栏,切换不同年份的趋势数据 -->
4
+ <div class="toolbar">
5
+ <el-select v-model="selectedYear" size="small" @change="onYearChange('hand')">
6
+ <el-option v-for="y in yearOptions" :key="y" :label="y + '年'" :value="y" />
7
+ </el-select>
8
+ <el-select v-model="selectedDate" size="small" style="margin-left:8px" @change="onDateChange('hand')">
9
+ <el-option v-for="d in xAxis" :key="d" :label="d" :value="d" />
10
+ </el-select>
11
+ <!-- 当前视图最后一条数据展示区 -->
12
+ <div class="info">
13
+ <span>最后数据:</span>
14
+ <strong>{{ lastLabel || '-' }}</strong>
15
+ <span>值:</span>
16
+ <strong>{{ lastValue === null ? '-' : lastValue }}</strong>
17
+ </div>
18
+ </div>
19
+
20
+ <!-- 折线图容器 -->
21
+ <div ref="chartRef" class="chart"></div>
22
+ </div>
23
+ </template>
24
+
25
+ <script>
26
+ // 使用 ECharts 绘制折线图
27
+ import * as echarts from 'echarts'
28
+ // 调用后端接口获取指定年份的每日趋势数据
29
+ import { getTrend } from '../api/mock'
30
+
31
+ export default {
32
+ name: 'LineChartPage',
33
+ data() {
34
+ return {
35
+ /**
36
+ * ECharts 图表实例对象
37
+ * 用于执行 setOption、resize、dispose 等操作
38
+ */
39
+ chartInstance: null,
40
+
41
+ /**
42
+ * 当前选中的年份
43
+ * 初始化默认为当前系统年份
44
+ */
45
+ selectedYear: new Date().getFullYear(),
46
+
47
+ /**
48
+ * 可选年份列表
49
+ * 用于下拉框筛选数据,目前静态配置了 2020-2026 年
50
+ */
51
+ yearOptions: [ 2025, 2026],
52
+
53
+ /**
54
+ * 固定视口宽度(单位:天)
55
+ * 核心配置项:决定了图表中一次性展示多少天的数据
56
+ * 配合 dataZoom 事件逻辑,强制保持视窗大小不变,仅允许左右平移
57
+ */
58
+ windowSize: 31,
59
+
60
+ /**
61
+ * 数据总点数
62
+ * 即 xAxis/series 数组的长度,用于计算 dataZoom 的百分比与索引映射
63
+ * 在 loadData/extendLeft/extendRight 时会更新此值
64
+ */
65
+ totalPoints: 0,
66
+
67
+ /**
68
+ * 当前视窗的数据区间状态
69
+ * @property {number} startValue - 视窗起始数据索引(包含)
70
+ * @property {number} endValue - 视窗结束数据索引(包含)
71
+ * 始终满足:endValue = startValue + windowSize - 1
72
+ * 用于在 slider(底部滑块)和 inside(鼠标拖拽)两种交互模式间同步状态
73
+ */
74
+ dataZoomState: { startValue: 0, endValue: 30 },
75
+
76
+ /**
77
+ * x轴数据缓存(日期字符串数组)
78
+ * 格式示例:['2023-01-01', '2023-01-02', ...]
79
+ * 会随着左右滑动触发的数据动态加载(拼接)而增长
80
+ */
81
+ xAxis: [],
82
+
83
+ /**
84
+ * y轴数据缓存(数值数组)
85
+ * 与 xAxis 一一对应
86
+ */
87
+ series: [],
88
+
89
+ /**
90
+ * 当前选中的具体日期
91
+ * 对应顶部日期下拉框的值,与图表视窗联动
92
+ */
93
+ selectedDate: '',
94
+ isExtendingRight: false,
95
+
96
+ /**
97
+ * 视窗最右侧(最新)数据的日期标签
98
+ * 用于顶部“最后数据”展示区
99
+ */
100
+ lastLabel: '',
101
+
102
+ /**
103
+ * 视窗最右侧(最新)数据的数值
104
+ * 用于顶部“最后数据”展示区
105
+ */
106
+ lastValue: null
107
+ }
108
+ },
109
+ watch: {
110
+ lastLabel(newVal) {
111
+ if (newVal) {
112
+ this.selectedYear = parseInt(newVal.slice(0, 4))
113
+ this.selectedDate = newVal
114
+ }
115
+ }
116
+ },
117
+ mounted() {
118
+ // 初始化时拉取默认年份的数据
119
+ // 判断当前年份的天数是否已经满足31天。如果不满足需要请求前一年的数据和当前年份数据合并
120
+ this.loadData()
121
+ window.addEventListener('resize', this.handleResize)
122
+ },
123
+ beforeDestroy() {
124
+ window.removeEventListener('resize', this.handleResize)
125
+ if (this.chartInstance) {
126
+ this.chartInstance.dispose()
127
+ this.chartInstance = null
128
+ }
129
+ },
130
+ methods: {
131
+ /**
132
+ * 拉取指定年份的趋势数据,并初始化图表
133
+ * 含初次拼接相邻年份的数据
134
+ */
135
+ async loadData(year = this.selectedYear) {
136
+ const merged = await this.assembleInitialData(year)
137
+ const x = merged.map(i => i.label)
138
+ const y = merged.map(i => i.value)
139
+ this.xAxis = x
140
+ this.series = y
141
+ this.initChart(x, y, year)
142
+ this.selectedDate = x.length ? x[x.length - 1] : ''
143
+ },
144
+ /**
145
+ * 年份变化时重新加载数据
146
+ */
147
+ onYearChange(type) {
148
+ if (type === 'hand') {
149
+ if (this.chartInstance) {
150
+ this.chartInstance.dispose()
151
+ this.chartInstance = null
152
+ }
153
+ this.xAxis = []
154
+ this.series = []
155
+ this.totalPoints = 0
156
+ this.dataZoomState = { startValue: 0, endValue: this.windowSize - 1 }
157
+ this.lastLabel = ''
158
+ this.lastValue = null
159
+ this.loadData(this.selectedYear)
160
+ }
161
+
162
+ },
163
+ /**
164
+ * 初始化图表并应用固定 31 天视口窗口
165
+ *
166
+ * 参数说明:
167
+ * @param {string[]} xAxisData - 全年每日的类目轴标签数组,按天排列。
168
+ * - 期望格式:YYYY-MM-DD(显示时可能裁剪为 MM-DD 以提升密度)
169
+ * - 长度:与 seriesData 等长
170
+ * @param {number[]} seriesData - 与 xAxisData 一一对应的数值数据序列。
171
+ * - 每个元素代表对应日期的数值
172
+ * - 长度:与 xAxisData 等长
173
+ * @param {number} year - 当前选择的年份,用于设置图表标题与语义。
174
+ *
175
+ * 行为与步骤:
176
+ * 1) 销毁已有 ECharts 实例(若存在),避免内存泄漏与配置残留
177
+ * 2) 初始化新实例,并记录数据总长度 totalPoints
178
+ * 3) 设置初始 dataZoomState 区间为 [0, windowSize-1],窗口宽度固定为 31 天
179
+ * 4) 构建并设置 option:
180
+ * - title/tooltip:基本信息与提示
181
+ * - grid:为密集标签与滑块预留空间
182
+ * - dataZoom:包含 slider 与 inside,开启 zoomLock,禁止缩放,仅允许平移
183
+ * - xAxis/yAxis:类目轴与数值轴,xAxis 的标签密度与格式已优化
184
+ * - series:折线图数据(平滑)
185
+ * 5) 绑定 dataZoom 事件:根据用户拖动自动维持窗口固定宽度,并做边界约束
186
+ * 6) 调用 updateLastViewPoint:更新顶部“最后数据”展示(日期与值)
187
+ *
188
+ * 前置条件:
189
+ * - xAxisData.length === seriesData.length
190
+ * - windowSize <= xAxisData.length
191
+ *
192
+ * 副作用:
193
+ * - 更新 this.chartInstance / this.totalPoints / this.dataZoomState
194
+ * - 更新 lastLabel / lastValue(顶部展示区)
195
+ *
196
+ * 返回值:无(void)
197
+ */
198
+ // 初始化图表:根据所选年份定位初始视口结束位置,并绑定固定窗口 dataZoom 行为
199
+ initChart(xAxisData, seriesData, year) {
200
+ if (this.chartInstance) {
201
+ this.chartInstance.dispose()
202
+ }
203
+
204
+ this.chartInstance = echarts.init(this.$refs.chartRef)
205
+ this.totalPoints = xAxisData.length
206
+ let initialEnd = this.computeInitialWindowEnd(xAxisData, year)
207
+ const initialStart = Math.max(0, initialEnd - (this.windowSize - 1))
208
+ this.dataZoomState = { startValue: initialStart, endValue: initialEnd }
209
+
210
+ const option = {
211
+ // title: {
212
+ // text: year + ' 年每日趋势'
213
+ // },
214
+ tooltip: {
215
+ trigger: 'axis'
216
+ },
217
+ grid: { left: 60, right: 60, bottom: 80, top: 60, containLabel: true },
218
+ // 仅在视口内展示 31 天数据,可通过底部滑动条左右滑动查看全年
219
+ // slider:底部可视化拖动条;inside:鼠标滚轮/手势缩放
220
+ // 通过 startValue/endValue 控制初始显示区间(0-30 共 31 天)
221
+ dataZoom: [
222
+ {
223
+ type: 'slider', // 底部滑块式数据缩放控件
224
+ zoomLock: true, // 锁定缩放,仅允许平移窗口
225
+ startValue: this.dataZoomState.startValue, // 初始窗口起始索引
226
+ endValue: this.dataZoomState.endValue // 初始窗口结束索引
227
+ },
228
+ {
229
+ type: 'inside', // 内置交互式 dataZoom(滚轮/触摸)
230
+ zoomLock: true, // 锁定缩放,仅允许平移
231
+ zoomOnMouseWheel: false, // 禁止滚轮缩放
232
+ moveOnMouseWheel: true, // 允许滚轮左右平移视口
233
+ zoomOnPinch: false, // 禁止触摸捏合缩放
234
+ startValue: this.dataZoomState.startValue, // 初始窗口起始索引(与 slider 同步)
235
+ endValue: this.dataZoomState.endValue // 初始窗口结束索引(与 slider 同步)
236
+ }
237
+ ],
238
+ xAxis: { // 横轴:类目轴(日期)
239
+ type: 'category', // 使用类目轴
240
+ data: xAxisData, // 横轴数据(日期列表)
241
+ axisLabel: { // 坐标轴标签样式
242
+ interval: 0, // 强制显示所有标签
243
+ rotate: 45, // 标签旋转 45 度,避免重叠
244
+ fontSize: 10, // 标签字体大小
245
+ margin: 6, // 标签与轴线间距
246
+ showMinLabel: true, // 显示首个标签
247
+ showMaxLabel: true, // 显示最后一个标签
248
+ formatter: function (value) { // 标签文本格式化
249
+ return typeof value === 'string' && value.length >= 10 ? value.slice(5) : value // 长日期仅保留“MM-DD”
250
+ }
251
+ }
252
+ },
253
+ yAxis: { // 纵轴:数值轴
254
+ type: 'value' // 数值类型
255
+ },
256
+ series: [
257
+ {
258
+ name: '值', // 序列名称
259
+ type: 'line', // 折线图
260
+ smooth: true, // 使用平滑曲线
261
+ showSymbol: true,
262
+ lineStyle: {
263
+ color: '#ff0000'
264
+ },
265
+ itemStyle: {
266
+ color: '#ff0000'
267
+ },
268
+ label: {
269
+ show: true,
270
+ position: 'top',
271
+ formatter: function (params) {
272
+ return params.value
273
+ }
274
+ },
275
+ data: seriesData // 序列数据
276
+ }
277
+ ]
278
+ }
279
+
280
+ this.chartInstance.setOption(option)
281
+
282
+ this.chartInstance.off('dataZoom')
283
+ this.chartInstance.on('dataZoom', async (params) => {
284
+ // 固定窗口宽度的核心逻辑:
285
+ // 1. 将 dataZoom 回调中的百分比或显式索引统一转换为 start/end 索引
286
+ // 2. 判断用户是“移动 start”还是“移动 end”,相应地调整另一端保持 windowSize 不变
287
+ // 3. 做边界约束,确保 0 <= start <= end <= totalPoints - 1
288
+ // 4. 若区间有变化,使用 dispatchAction 同步两个 dataZoom(slider 与 inside),silent 避免死循环
289
+ const raw = Array.isArray(params.batch) && params.batch.length ? params.batch[0] : params
290
+ const startPercent = typeof raw.start === 'number' ? raw.start : 0
291
+ const endPercentDefault = (this.dataZoomState.endValue / (this.totalPoints - 1)) * 100
292
+ const endPercent = typeof raw.end === 'number' ? raw.end : endPercentDefault
293
+ let start = Number.isFinite(raw.startValue)
294
+ ? raw.startValue
295
+ : Math.round((startPercent / 100) * (this.totalPoints - 1))
296
+ let end = Number.isFinite(raw.endValue)
297
+ ? raw.endValue
298
+ : Math.round((endPercent / 100) * (this.totalPoints - 1))
299
+
300
+ const prev = this.dataZoomState
301
+ const movedStart = Math.abs(start - prev.startValue)
302
+ const movedEnd = Math.abs(end - prev.endValue)
303
+
304
+ if (movedStart >= movedEnd) {
305
+ end = start + (this.windowSize - 1)
306
+ } else {
307
+ start = end - (this.windowSize - 1)
308
+ }
309
+
310
+ if (end > this.totalPoints - 1) {
311
+ end = this.totalPoints - 1
312
+ start = end - (this.windowSize - 1)
313
+ }
314
+ if (start < 0) {
315
+ start = 0
316
+ end = start + (this.windowSize - 1)
317
+ }
318
+
319
+ // 触达左边界时尝试向左扩展(不小于 yearOptions[0])
320
+ const leftRes = await this.extendLeftIfNeeded(start, end)
321
+ start = leftRes.start
322
+ end = leftRes.end
323
+
324
+ // 触达右边界时尝试向右扩展(不超过“今天”)
325
+ const rightRes = await this.extendRightIfNeeded(start, end)
326
+ start = rightRes.start
327
+ end = rightRes.end
328
+
329
+ if (start !== prev.startValue || end !== prev.endValue) {
330
+ this.dataZoomState = { startValue: start, endValue: end }
331
+ this.chartInstance.dispatchAction({
332
+ type: 'dataZoom',
333
+ dataZoomIndex: 0,
334
+ startValue: start,
335
+ endValue: end,
336
+ silent: true
337
+ })
338
+ this.chartInstance.dispatchAction({
339
+ type: 'dataZoom',
340
+ dataZoomIndex: 1,
341
+ startValue: start,
342
+ endValue: end,
343
+ silent: true
344
+ })
345
+ }
346
+ this.updateLastViewPoint()
347
+ })
348
+ this.updateLastViewPoint()
349
+ },
350
+ /**
351
+ * 计算 YYYY-MM-DD 的“今天”标签(UTC)
352
+ */
353
+ todayLabel() {
354
+ const now = new Date()
355
+ const y = now.getUTCFullYear()
356
+ const m = String(now.getUTCMonth() + 1).padStart(2, '0')
357
+ const d = String(now.getUTCDate()).padStart(2, '0')
358
+ return `${y}-${m}-${d}`
359
+ },
360
+ /**
361
+ * 初次加载时的合并策略:
362
+ * - 选择小于当前年的年份:合并下一年全年,支持右滑
363
+ * - 选择当前年:无条件拼接上一年数据,便于左滑
364
+ */
365
+ async assembleInitialData(year) {
366
+ const nowYear = new Date().getFullYear()
367
+ const list = await getTrend(year)
368
+ if (year < nowYear) {
369
+ const next = await getTrend(year + 1)
370
+ const nextAll = Array.isArray(next) ? next : []
371
+ return [...list, ...nextAll]
372
+ }
373
+ if (year === nowYear) {
374
+ const prev = await getTrend(year - 1)
375
+ const prevAll = Array.isArray(prev) ? prev : []
376
+ return [...prevAll, ...list]
377
+ }
378
+ console.log(list)
379
+ return list
380
+ },
381
+ /**
382
+ * 计算初始视口结束索引:
383
+ * 若选择非当前年,则落在该年的最后一天或最后一个该年数据点
384
+ */
385
+ computeInitialWindowEnd(xAxisData, year) {
386
+ let end = Math.max(0, xAxisData.length - 1)
387
+ const nowYear = new Date().getFullYear()
388
+ if (year < nowYear) {
389
+ const target = `${year}-12-31`
390
+ let idx = xAxisData.lastIndexOf(target)
391
+ if (idx === -1) {
392
+ for (let i = xAxisData.length - 1; i >= 0; i--) {
393
+ const v = xAxisData[i]
394
+ if (typeof v === 'string' && v.slice(0, 4) === String(year)) {
395
+ idx = i
396
+ break
397
+ }
398
+ }
399
+ }
400
+ if (idx !== -1) end = idx
401
+ }
402
+ return end
403
+ },
404
+ /**
405
+ * 扩展左侧:
406
+ * 在起点触达时,将更早年份数据前置拼接,并保持窗口位置不变
407
+ */
408
+ async extendLeftIfNeeded(start, end) {
409
+ const minYear = Array.isArray(this.yearOptions) && this.yearOptions.length ? this.yearOptions[0] : 1970
410
+ if (start > 0 || !Array.isArray(this.xAxis) || this.xAxis.length === 0) return { start, end }
411
+ const firstLabel = this.xAxis[0]
412
+ const firstYear = this.parseYear(firstLabel)
413
+ if (!Number.isFinite(firstYear) || firstYear <= minYear) return { start, end }
414
+ const prevData = await getTrend(firstYear - 1)
415
+ const prevLen = Array.isArray(prevData) ? prevData.length : 0
416
+ if (prevLen <= 0) return { start, end }
417
+ const prevX = prevData.map(i => i.label)
418
+ const prevY = prevData.map(i => i.value)
419
+ this.appendData(prevX, prevY, 'left')
420
+ return { start: start + prevLen, end: end + prevLen }
421
+ },
422
+ /**
423
+ * 扩展右侧:
424
+ * 在末尾触达时,按需追加下一年或当前年剩余(不超过“今天”)
425
+ */
426
+ async extendRightIfNeeded(start, end) {
427
+ if (end < this.totalPoints - 1 || !Array.isArray(this.xAxis) || this.xAxis.length === 0) return { start, end }
428
+ if (this.isExtendingRight) return { start, end }
429
+ this.isExtendingRight = true
430
+ const nowYear = new Date().getFullYear()
431
+ const lastLabel = this.xAxis[this.xAxis.length - 1]
432
+ const lastYear = this.parseYear(lastLabel)
433
+ const today = this.todayLabel()
434
+ if (!this.canGrowRight(lastYear, lastLabel, nowYear, today)) { this.isExtendingRight = false; return { start, end } }
435
+ try {
436
+ const targetYear = Number.isFinite(lastYear) && lastYear < nowYear ? lastYear + 1 : nowYear
437
+ const nextData = await getTrend(targetYear)
438
+ const filtered = this.filterNewer(nextData, lastLabel, today)
439
+ if (filtered.length <= 0) return { start, end }
440
+ const nextX = filtered.map(i => i.label)
441
+ const nextY = filtered.map(i => i.value)
442
+ this.appendData(nextX, nextY, 'right')
443
+ return { start, end }
444
+ } finally {
445
+ this.isExtendingRight = false
446
+ }
447
+ },
448
+ /**
449
+ * 解析日期字符串中的年份
450
+ */
451
+ parseYear(label) {
452
+ return typeof label === 'string' ? parseInt(label.slice(0, 4), 10) : NaN
453
+ },
454
+ /**
455
+ * 判断是否还可以向右加载更多数据
456
+ * 条件:最后一条数据的年份小于当前年,
457
+ * 或者同年但日期小于今天
458
+ */
459
+ canGrowRight(lastYear, lastLabel, nowYear, today) {
460
+ return (Number.isFinite(lastYear) && lastYear < nowYear) ||
461
+ (Number.isFinite(lastYear) && lastYear === nowYear && typeof lastLabel === 'string' && lastLabel < today)
462
+ },
463
+ /**
464
+ * 过滤出新加载数据中,
465
+ * 晚于当前视图最后一条且早于等于今天的数据
466
+ */
467
+ filterNewer(data, lastLabel, today) {
468
+ return Array.isArray(data) ? data.filter(i => i.label > lastLabel && i.label <= today) : []
469
+ },
470
+ /**
471
+ * 将新数据拼接到现有数据序列中(左侧或右侧),并更新图表
472
+ */
473
+ appendData(nextX, nextY, direction) {
474
+ if (direction === 'left') {
475
+ this.xAxis = [...nextX, ...this.xAxis]
476
+ this.series = [...nextY, ...this.series]
477
+ } else {
478
+ this.xAxis = [...this.xAxis, ...nextX]
479
+ this.series = [...this.series, ...nextY]
480
+ }
481
+ this.totalPoints = this.xAxis.length
482
+ this.chartInstance.setOption({
483
+ xAxis: { data: this.xAxis },
484
+ series: [{ data: this.series }]
485
+ })
486
+ },
487
+ /**
488
+ * 日期下拉框变更时的处理逻辑:
489
+ * 定位视窗到选定日期
490
+ */
491
+ onDateChange(type) {
492
+ if (!type || type !== 'hand') {
493
+ return
494
+ }
495
+ const idx = this.xAxis.indexOf(this.selectedDate)
496
+ if (idx === -1) return
497
+ let end = idx
498
+ let start = end - (this.windowSize - 1)
499
+ if (start < 0) {
500
+ start = 0
501
+ end = start + (this.windowSize - 1)
502
+ }
503
+ if (end > this.totalPoints - 1) {
504
+ end = this.totalPoints - 1
505
+ start = end - (this.windowSize - 1)
506
+ }
507
+ this.dataZoomState = { startValue: start, endValue: end }
508
+ this.chartInstance.dispatchAction({
509
+ type: 'dataZoom',
510
+ dataZoomIndex: 0,
511
+ startValue: start,
512
+ endValue: end,
513
+ silent: true
514
+ })
515
+ this.chartInstance.dispatchAction({
516
+ type: 'dataZoom',
517
+ dataZoomIndex: 1,
518
+ startValue: start,
519
+ endValue: end,
520
+ silent: true
521
+ })
522
+ this.updateLastViewPoint()
523
+ },
524
+ /**
525
+ * 根据当前窗口区间更新顶部最后一条数据展示
526
+ */
527
+ updateLastViewPoint() {
528
+ const end = this.dataZoomState.endValue
529
+ if (Array.isArray(this.xAxis) && Array.isArray(this.series) && end >= 0 && end < this.series.length) {
530
+ this.lastLabel = this.xAxis[end]
531
+ this.lastValue = this.series[end]
532
+ } else {
533
+ this.lastLabel = ''
534
+ this.lastValue = null
535
+ }
536
+ },
537
+ /**
538
+ * 窗口尺寸变更时自适应
539
+ */
540
+ handleResize() {
541
+ if (this.chartInstance) {
542
+ this.chartInstance.resize()
543
+ }
544
+ }
545
+ }
546
+ }
547
+ </script>
548
+
549
+ <style>
550
+ .page {
551
+ padding: 24px;
552
+ }
553
+
554
+ .toolbar {
555
+ margin-bottom: 12px;
556
+ display: flex;
557
+ align-items: center;
558
+ }
559
+
560
+ .title {
561
+ margin-bottom: 16px;
562
+ }
563
+
564
+ .info {
565
+ font-size: 14px;
566
+ margin-left: 12px;
567
+ }
568
+
569
+ .chart {
570
+ width: 100%;
571
+ height: 400px;
572
+ }
573
+ </style>