Browse Source

feat:SIM卡详情弹窗支持切换月份查看历史数据

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Jerry Liang 6 ngày trước cách đây
mục cha
commit
fcafe20373

+ 64 - 10
src/views/sim-card-manage/component/simCardDataView.vue

@@ -15,18 +15,36 @@
     @after-enter="modalAfterEnter"
     @before-leave="modalBeforeLeave"
   >
-    <template #header-extra> {{ data.updateTime }} </template>
+    <template #header-extra> {{ viewData.updateTime }} </template>
     <div ref="simCardMonthlyChart" class="flex-1" />
-    <template #footer> {{ data.month }} </template>
+    <template #footer>
+      <div class="flex gap-2 items-center justify-center">
+        <NButton text :focusable="false" :disabled="monthLoading" @click="switchMonth(-1)">
+          <span class="i-mdi:chevron-left text-lg" />
+        </NButton>
+        <span>{{ viewData.month }}</span>
+        <NButton
+          text
+          :focusable="false"
+          :disabled="monthLoading || viewData.month === latestMonth"
+          @click="switchMonth(1)"
+        >
+          <span class="i-mdi:chevron-right text-lg" />
+        </NButton>
+        <NSpin v-if="monthLoading" :size="14" />
+        <span v-else-if="noData" class="text-gray-400">无数据</span>
+      </div>
+    </template>
   </NModal>
 </template>
 
 <script setup lang="ts">
 import type { VNodeChild, CSSProperties } from 'vue'
-import { NModal, NSwitch } from 'naive-ui'
+import { NButton, NModal, NSpin, NSwitch } from 'naive-ui'
 import { Chart, type TooltipTitle } from '@antv/g2'
 import type { SimCardMonthlyData } from '@/service/simCardManage/interface'
-import { getDaysInMonth } from '../month'
+import { getDaysInMonth, shiftMonth } from '../month'
+import { useSimCardMonthlyCache } from '../useSimCardMonthlyCache'
 
 import { usePersonalization } from '@/composable/usePersonalization'
 
@@ -37,9 +55,42 @@ const props = defineProps<{
 const show = defineModel<boolean>('show', { required: true })
 
 const { isDark } = usePersonalization()
+const { getWithPriority } = useSimCardMonthlyCache()
 
 const simCardMonthlyChart = ref<HTMLDivElement | null>(null)
 
+// 当前展示的月数据;打开弹窗时取 props.data,切换月份后替换
+const viewData = ref<SimCardMonthlyData>(props.data)
+// 打开时的月份(即最新月份),不允许切换到更晚的月份
+const latestMonth = ref('')
+const monthLoading = ref(false)
+const noData = ref(false)
+// 总值/增量开关状态,切换月份重建图表后保持
+const showIncrement = ref(false)
+
+function switchMonth(offset: number) {
+  const month = shiftMonth(viewData.value.month, offset)
+  const simId = viewData.value.simId
+  monthLoading.value = true
+  getWithPriority(simId, month)
+    .then((resp) => {
+      viewData.value = resp
+      noData.value = false
+    })
+    .catch(() => {
+      // 该月没有数据时后端返回错误,展示空图表,仍可继续往前切换
+      viewData.value = { simId, updateTime: '', month, allDataUsage: 0, dailyUsage: {} }
+      noData.value = true
+    })
+    .finally(() => {
+      monthLoading.value = false
+      // 请求期间弹窗可能已关闭
+      if (!show.value) return
+      destroySimCardMonthlyChart()
+      initSimCardMonthlyChart()
+    })
+}
+
 interface SimCardMonthlyChartData {
   day: number
   value: null | number // 总值
@@ -59,15 +110,13 @@ function initSimCardMonthlyChart() {
     container: simCardMonthlyChart.value,
   })
 
-  const data = props.data
-
   chart.options({
     type: 'interval',
     autoFit: true,
     theme: { type: isDark.value ? 'dark' : 'light' },
     data: {
       type: 'inline',
-      value: data,
+      value: viewData.value,
       transform: [
         {
           type: 'custom',
@@ -105,10 +154,10 @@ function initSimCardMonthlyChart() {
     },
     encode: {
       x: 'day',
-      y: 'value',
+      y: showIncrement.value ? 'increment' : 'value',
     },
     style: {
-      fill: '#2080f0',
+      fill: showIncrement.value ? '#18a058' : '#2080f0',
     },
     axis: {
       x: { title: false },
@@ -133,6 +182,9 @@ function destroySimCardMonthlyChart() {
 }
 
 const modalAfterEnter = () => {
+  viewData.value = props.data
+  latestMonth.value = props.data.month
+  noData.value = false
   initSimCardMonthlyChart()
 }
 
@@ -162,10 +214,11 @@ function customTitle(): VNodeChild {
       class: 'flex gap-4 items-center',
     },
     [
-      props.data.simId,
+      viewData.value.simId,
       h(
         NSwitch,
         {
+          value: showIncrement.value,
           railStyle: ({ focused, checked }: { focused: boolean; checked: boolean }) => {
             const style: CSSProperties = {}
             if (checked) {
@@ -184,6 +237,7 @@ function customTitle(): VNodeChild {
             return style
           },
           onUpdateValue: (value: boolean) => {
+            showIncrement.value = value
             if (simCardMonthlyChartInstance != null) {
               if (value) {
                 simCardMonthlyChartInstance.options({

+ 10 - 1
src/views/sim-card-manage/month.test.ts

@@ -1,5 +1,5 @@
 import { describe, expect, it } from 'vitest'
-import { getDaysInMonth, getPrevMonth } from './month'
+import { getDaysInMonth, getPrevMonth, shiftMonth } from './month'
 
 describe('sim card month formats', () => {
   it.each([
@@ -13,4 +13,13 @@ describe('sim card month formats', () => {
     expect(getDaysInMonth(month)).toBe(days)
     expect(getPrevMonth(month)).toBe(previous)
   })
+
+  it.each([
+    ['2026-12', 1, '2027-01'],
+    ['202612', 1, '202701'],
+    ['2026-03', -14, '2025-01'],
+    ['2026-03', 0, '2026-03'],
+  ])('shifts %s by %i', (month, offset, expected) => {
+    expect(shiftMonth(month, offset)).toBe(expected)
+  })
 })

+ 8 - 3
src/views/sim-card-manage/month.ts

@@ -17,9 +17,14 @@ export function getDaysInMonth(month: string): number {
   return new Date(year, monthNumber, 0).getDate()
 }
 
-export function getPrevMonth(month: string): string {
+/** 按原格式偏移月份,offset 为负表示往前 */
+export function shiftMonth(month: string, offset: number): string {
   const { year, monthNumber, dashed } = parseMonth(month)
-  const previous = new Date(year, monthNumber - 2, 1)
+  const target = new Date(year, monthNumber - 1 + offset, 1)
   const separator = dashed ? '-' : ''
-  return `${previous.getFullYear()}${separator}${String(previous.getMonth() + 1).padStart(2, '0')}`
+  return `${target.getFullYear()}${separator}${String(target.getMonth() + 1).padStart(2, '0')}`
+}
+
+export function getPrevMonth(month: string): string {
+  return shiftMonth(month, -1)
 }