diff --git a/openspec/changes/update-asset-carrier-status-and-range-allocation-hints/proposal.md b/openspec/changes/update-asset-carrier-status-and-range-allocation-hints/proposal.md new file mode 100644 index 0000000..e38ab49 --- /dev/null +++ b/openspec/changes/update-asset-carrier-status-and-range-allocation-hints/proposal.md @@ -0,0 +1,34 @@ +# Change: 更新资产运营商状态文案与区间分配数量提示 + +## Why + +当前后台“资产信息”页的“卡信息”区域会把 `gateway_extend` 展示为“运营商停机原因”,但最新业务要求需要统一改为更宽泛的“运营商状态”文案。 + +同时,IoT 卡管理和设备管理里的“批量设置套餐系列绑定”弹窗已经支持区间选择,但在输入起始/结束标识后没有给出本次将覆盖多少张卡或多少台设备的直观提示,运营人员需要手工心算范围,容易误判分配数量。 + +## What Changes + +- 调整资产信息卡资产详情中“卡信息”区域的 `gateway_extend` 展示标签,将“运营商停机原因”改为 `运营商状态` +- 为 IoT 卡“批量设置套餐系列绑定”弹窗的区间模式新增提示文案:`共分配xxx张卡` + - 计算方式为 `结束ICCID - 起始ICCID + 1` + - 仅在起止值可计算且结果为正整数时显示 +- 为设备管理“批量设置设备套餐系列绑定”弹窗的区间模式新增提示文案:`共分配xxx台设备` + - 计算方式为 `结束设备号 - 起始设备号 + 1` + - 仅在起止值可计算且结果为正整数时显示 +- 不调整后端接口字段,仅补充前端展示与交互约束 + +## Impact + +- Affected specs: + - `asset-information` + - `iot-card-series-binding` + - `device-series-binding` +- Affected code: + - `src/views/asset-management/asset-information/components/BasicInfoCard.vue` + - `src/views/asset-management/iot-card-management/index.vue` + - `src/views/asset-management/device-list/index.vue` +- Dependencies: + - `add-asset-gateway-extend-display` + - `update-batch-series-binding-contract` +- Breaking changes: + - 无外部接口破坏性变更;本次仅调整文案和区间模式提示 diff --git a/openspec/changes/update-asset-carrier-status-and-range-allocation-hints/specs/asset-information/spec.md b/openspec/changes/update-asset-carrier-status-and-range-allocation-hints/specs/asset-information/spec.md new file mode 100644 index 0000000..1357e81 --- /dev/null +++ b/openspec/changes/update-asset-carrier-status-and-range-allocation-hints/specs/asset-information/spec.md @@ -0,0 +1,13 @@ +## ADDED Requirements + +### Requirement: Admin Asset Card Carrier Status Label + +The admin asset information view SHALL label `gateway_extend` as `运营商状态` within the card information section. + +#### Scenario: Show carrier status label in card information + +- **GIVEN** 用户正在查看后台“资产信息”中的卡资产详情 +- **WHEN** 页面渲染“卡信息”区域中的 `gateway_extend` +- **THEN** 字段标签 MUST 显示为 `运营商状态` +- **AND** 字段值 MUST 继续直接展示上游返回的 `gateway_extend` +- **AND** 当 `gateway_extend` 为空时,页面 MUST 显示稳定的占位内容 diff --git a/openspec/changes/update-asset-carrier-status-and-range-allocation-hints/specs/device-series-binding/spec.md b/openspec/changes/update-asset-carrier-status-and-range-allocation-hints/specs/device-series-binding/spec.md new file mode 100644 index 0000000..44c790c --- /dev/null +++ b/openspec/changes/update-asset-carrier-status-and-range-allocation-hints/specs/device-series-binding/spec.md @@ -0,0 +1,24 @@ +## ADDED Requirements + +### Requirement: Device Batch Series Binding Range Allocation Hint + +The device batch series binding dialog SHALL provide a derived allocation-count hint for device-number range selection. + +The hint MUST appear immediately below the `virtual_no_start` and `virtual_no_end` inputs. + +The count MUST be computed as `结束设备号 - 起始设备号 + 1` using the same numeric range values accepted by the dialog. + +#### Scenario: Show allocation count for a valid device-number range + +- **GIVEN** 用户打开“批量设置设备套餐系列绑定”弹窗 +- **AND** 用户选择 `selection_type="range"` +- **WHEN** 用户输入 `virtual_no_start` 和 `virtual_no_end`,且计算结果为正整数 +- **THEN** 弹窗 MUST 显示 `共分配xxx台设备` +- **AND** `xxx` MUST 等于 `结束设备号 - 起始设备号 + 1` +- **AND** 当任一端点变化时,提示 MUST 立即更新 + +#### Scenario: Hide count hint for incomplete or invalid device-number ranges + +- **GIVEN** 用户选择 `selection_type="range"` +- **WHEN** 任一起止设备号为空、无法按有效数值范围计算,或计算结果小于 `1` +- **THEN** 弹窗 MUST NOT 显示误导性的分配数量提示 diff --git a/openspec/changes/update-asset-carrier-status-and-range-allocation-hints/specs/iot-card-series-binding/spec.md b/openspec/changes/update-asset-carrier-status-and-range-allocation-hints/specs/iot-card-series-binding/spec.md new file mode 100644 index 0000000..949a66d --- /dev/null +++ b/openspec/changes/update-asset-carrier-status-and-range-allocation-hints/specs/iot-card-series-binding/spec.md @@ -0,0 +1,24 @@ +## ADDED Requirements + +### Requirement: IoT Card Batch Series Binding Range Allocation Hint + +The IoT card batch series binding dialog SHALL provide a derived allocation-count hint for ICCID range selection. + +The hint MUST appear immediately below the `iccid_start` and `iccid_end` inputs. + +The count MUST be computed with exact integer arithmetic as `iccid_end - iccid_start + 1` so that long ICCID values do not lose precision. + +#### Scenario: Show allocation count for a valid ICCID range + +- **GIVEN** 用户打开“批量设置套餐系列绑定”弹窗 +- **AND** 用户选择 `selection_type="range"` +- **WHEN** 用户输入 `iccid_start` 和 `iccid_end`,且计算结果为正整数 +- **THEN** 弹窗 MUST 显示 `共分配xxx张卡` +- **AND** `xxx` MUST 等于 `iccid_end - iccid_start + 1` +- **AND** 当任一端点变化时,提示 MUST 立即更新 + +#### Scenario: Hide count hint for incomplete or invalid ICCID ranges + +- **GIVEN** 用户选择 `selection_type="range"` +- **WHEN** 任一起止 ICCID 为空、无法按整数计算,或计算结果小于 `1` +- **THEN** 弹窗 MUST NOT 显示误导性的分配数量提示 diff --git a/openspec/changes/update-asset-carrier-status-and-range-allocation-hints/tasks.md b/openspec/changes/update-asset-carrier-status-and-range-allocation-hints/tasks.md new file mode 100644 index 0000000..6319880 --- /dev/null +++ b/openspec/changes/update-asset-carrier-status-and-range-allocation-hints/tasks.md @@ -0,0 +1,16 @@ +## 1. Asset Information + +- [x] 1.1 将资产信息“卡信息”区域的 `gateway_extend` 标签从“运营商停机原因”改为 `运营商状态` +- [x] 1.2 确认该字段仍直接展示 `gateway_extend` 原值,并保留空值占位行为 + +## 2. Range Allocation Hints + +- [x] 2.1 在 IoT 卡“批量设置套餐系列绑定”弹窗的区间模式下,于起始/结束 ICCID 输入框下方新增 `共分配xxx张卡` 提示 +- [x] 2.2 在设备管理“批量设置设备套餐系列绑定”弹窗的区间模式下,于起始/结束设备号输入框下方新增 `共分配xxx台设备` 提示 +- [x] 2.3 让两个提示都随起止值变化实时更新,并在范围不完整、不可计算或结果小于 1 时隐藏 + +## 3. Verification + +- [x] 3.1 验证卡资产详情“卡信息”区域展示标签为 `运营商状态` +- [x] 3.2 验证 ICCID 区间提示按 `结束ICCID - 起始ICCID + 1` 计算且不出现精度丢失 +- [x] 3.3 验证设备号区间提示按 `结束设备号 - 起始设备号 + 1` 计算,并在无效范围时不显示误导性数量 diff --git a/openspec/changes/update-asset-realtime-sync-timestamps-display/proposal.md b/openspec/changes/update-asset-realtime-sync-timestamps-display/proposal.md new file mode 100644 index 0000000..5bf312e --- /dev/null +++ b/openspec/changes/update-asset-realtime-sync-timestamps-display/proposal.md @@ -0,0 +1,40 @@ +# Change: 更新后台资产信息同步时间展示 + +## Why + +当前后台“资产信息”页虽然已经展示卡状态、实名状态和部分实时信息,但还缺少运营判断数据新鲜度所需的三个时间字段:实名同步时间、卡状态同步时间、流量同步时间。 + +- 卡资产的 `GET /api/admin/assets/:identifier/realtime-status` 已在顶层返回 `last_real_name_check_at`、`last_card_status_check_at`、`last_data_check_at`,但前端未展示。 +- 设备资产的同一接口会在 `cards` 列表中按绑定卡返回这三个字段;当前“设备实时信息”区块只消费 `device_realtime`,无法展示当前使用卡的同步时间。 + +如果不补齐这三个字段,后台在卡页和设备页都无法快速确认上游同步是否及时,也不利于定位实名、停复机和流量状态的延迟问题。 + +## What Changes + +- 更新后台“资产信息”的卡资产“卡信息”区域: + - 新增 `实名同步时间`,映射 `last_real_name_check_at` + - 新增 `卡状态同步时间`,映射 `last_card_status_check_at` + - 新增 `流量同步时间`,映射 `last_data_check_at` +- 更新后台“资产信息”的设备资产“设备实时信息”区域: + - 直接读取设备实时接口 `data` 顶层的 `last_real_name_check_at`、`last_card_status_check_at`、`last_data_check_at` + - 当对应字段为空时,显示稳定占位内容 +- 更新前端资产实时状态契约和数据合并逻辑: + - 支持卡资产顶层这三个同步时间字段 + - 支持设备绑定卡记录中的这三个同步时间字段 + - 刷新后合并绑定卡实时数据时,不得丢失这三个字段 + +## Impact + +- Affected specs: + - `asset-information` +- Affected code: + - `src/types/api/asset.ts` + - `src/views/asset-management/asset-information/types.ts` + - `src/views/asset-management/asset-information/composables/useAssetInfo.ts` + - `src/views/asset-management/asset-information/components/BasicInfoCard.vue` +- Dependencies: + - 与活跃变更 `update-admin-asset-device-signal-and-audit-logs` 共享 `asset-information` 能力和 `BasicInfoCard.vue` / `useAssetInfo.ts` 文件范围 + - 与活跃变更 `update-order-upload-and-device-switch-mode` 共享设备实时信息区块 + - 依赖后端继续按当前接口返回这三个时间字段,无需新增接口 +- Breaking changes: + - 无外部接口 breaking change;本次仅为前端展示契约和页面展示的增量调整 diff --git a/openspec/changes/update-asset-realtime-sync-timestamps-display/specs/asset-information/spec.md b/openspec/changes/update-asset-realtime-sync-timestamps-display/specs/asset-information/spec.md new file mode 100644 index 0000000..16f1f42 --- /dev/null +++ b/openspec/changes/update-asset-realtime-sync-timestamps-display/specs/asset-information/spec.md @@ -0,0 +1,64 @@ +## ADDED Requirements + +### Requirement: Admin Asset Card Sync Timestamp Display + +The admin asset information view SHALL display realtime sync timestamps for card assets within the card information section. + +#### Scenario: Show card sync timestamps on card asset page + +- **GIVEN** 用户正在查看后台“资产信息”中的卡资产详情 +- **AND** `GET /api/admin/assets/:identifier/realtime-status` 或 `POST /api/admin/assets/:identifier/refresh` 返回了卡资产实时数据 +- **WHEN** 页面渲染“卡信息”区域或完成实时刷新后的状态更新 +- **THEN** 页面 MUST 显示 `实名同步时间`,并映射 `last_real_name_check_at` +- **AND** 页面 MUST 显示 `卡状态同步时间`,并映射 `last_card_status_check_at` +- **AND** 页面 MUST 显示 `流量同步时间`,并映射 `last_data_check_at` +- **AND** 这三个字段的时间格式 MUST 与页面其他时间字段保持一致 + +#### Scenario: Card sync timestamps show placeholders when unavailable + +- **GIVEN** 用户正在查看后台“资产信息”中的卡资产详情 +- **WHEN** 任一同步时间字段为 `null`、空字符串或接口未返回对应值 +- **THEN** 页面 MUST 保留对应字段标签 +- **AND** 页面 MUST 为该字段显示稳定的占位内容 + +### Requirement: Admin Device Realtime Sync Timestamp Display + +The admin asset information view SHALL display device realtime sync timestamps from top-level realtime response fields within the device realtime information section for device assets. + +#### Scenario: Show device realtime sync timestamps in device realtime information + +- **GIVEN** 用户正在查看后台“资产信息”中的设备资产详情 +- **AND** `GET /api/admin/assets/:identifier/realtime-status` 或 `POST /api/admin/assets/:identifier/refresh` 返回了设备资产实时数据 +- **WHEN** 页面渲染“设备实时信息”区域或完成实时刷新后的状态更新 +- **THEN** 页面 MUST 显示 `实名同步时间`,并映射顶层 `last_real_name_check_at` +- **AND** 页面 MUST 显示 `卡状态同步时间`,并映射顶层 `last_card_status_check_at` +- **AND** 页面 MUST 显示 `流量同步时间`,并映射顶层 `last_data_check_at` +- **AND** 页面 MUST NOT 要求这三个字段必须存在于 `device_realtime` 中才可展示 + +#### Scenario: Device realtime section keeps stable placeholders when top-level timestamps are empty + +- **GIVEN** 用户正在查看后台“资产信息”中的设备资产详情 +- **WHEN** 顶层 `last_real_name_check_at`、`last_card_status_check_at`、`last_data_check_at` 中任一字段为空 +- **THEN** 页面 MUST 保留 `实名同步时间`、`卡状态同步时间`、`流量同步时间` 这三个字段标签 +- **AND** 页面 MUST 为缺失值显示稳定的占位内容 + +### Requirement: Admin Asset Realtime Sync Timestamp Contract Compatibility + +The admin asset information integration SHALL accept and preserve additive sync timestamp fields from realtime responses for card assets and device bound cards. + +#### Scenario: Card realtime contract accepts additive sync timestamp fields + +- **GIVEN** `GET /api/admin/assets/:identifier/realtime-status` 或 `POST /api/admin/assets/:identifier/refresh` 返回卡资产实时数据 +- **WHEN** 前端解析实时状态响应 +- **THEN** 前端类型契约 MUST 支持顶层 `last_data_check_at` +- **AND** 前端类型契约 MUST 支持顶层 `last_real_name_check_at` +- **AND** 前端类型契约 MUST 支持顶层 `last_card_status_check_at` + +#### Scenario: Device bound-card merge preserves sync timestamp fields + +- **GIVEN** `GET /api/admin/assets/:identifier/realtime-status` 或 `POST /api/admin/assets/:identifier/refresh` 返回设备资产实时数据 +- **AND** `cards` 列表中的绑定卡记录包含 `last_data_check_at`、`last_real_name_check_at`、`last_card_status_check_at` +- **WHEN** 前端将实时绑定卡数据与已有设备绑定卡状态合并 +- **THEN** 每条绑定卡记录 MUST 保留各自的三个同步时间字段 +- **AND** 绑定卡同步时间字段 MAY 被其他设备卡展示或后续状态使用 +- **AND** 合并过程 MUST NOT 因刷新而无故丢失已返回的同步时间值 diff --git a/openspec/changes/update-asset-realtime-sync-timestamps-display/tasks.md b/openspec/changes/update-asset-realtime-sync-timestamps-display/tasks.md new file mode 100644 index 0000000..3fe4726 --- /dev/null +++ b/openspec/changes/update-asset-realtime-sync-timestamps-display/tasks.md @@ -0,0 +1,20 @@ +# Tasks: Update Asset Realtime Sync Timestamps Display + +## 1. Contract And State + +- [x] 1.1 扩展资产实时状态相关类型,支持卡资产顶层和设备绑定卡记录中的 `last_data_check_at`、`last_real_name_check_at`、`last_card_status_check_at`。 +- [x] 1.2 调整资产实时状态合并逻辑,确保设备绑定卡刷新后保留上述三个同步时间字段。 +- [x] 1.3 为设备页补充设备实时状态顶层同步时间的读取逻辑,避免“设备实时信息”仅依赖 `device_realtime`。 + +## 2. UI Display + +- [x] 2.1 在卡资产“卡信息”区域新增 `实名同步时间`、`卡状态同步时间`、`流量同步时间` 三个展示项。 +- [x] 2.2 在设备资产“设备实时信息”区域新增同名三个展示项,并展示设备实时状态顶层返回的对应时间。 +- [x] 2.3 当字段为空或实时接口未返回对应值时,统一显示稳定占位内容。 + +## 3. Verification + +- [ ] 3.1 验证卡资产 `GET /api/admin/assets/:identifier/realtime-status` 返回的三个同步时间可正确展示在“卡信息”区域。 +- [ ] 3.2 验证设备资产 `GET /api/admin/assets/:identifier/realtime-status` 返回的顶层三个同步时间可正确展示在“设备实时信息”区域。 +- [ ] 3.3 验证 `POST /api/admin/assets/:identifier/refresh` 后卡页和设备页的三个同步时间展示保持一致。 +- [x] 3.4 运行 `openspec validate update-asset-realtime-sync-timestamps-display --strict`。 diff --git a/src/types/api/agentRecharge.ts b/src/types/api/agentRecharge.ts index b49ea9f..e7821f9 100644 --- a/src/types/api/agentRecharge.ts +++ b/src/types/api/agentRecharge.ts @@ -60,7 +60,7 @@ export interface AgentRechargeListResponse { // 创建代理充值订单请求 export interface CreateAgentRechargeRequest { - amount: number // 充值金额(单位:分),范围 10000 ~ 100000000 + amount: number // 充值金额(单位:分),最小 1(即 ¥0.01) payment_method: AgentRechargePaymentMethod shop_id: number payment_voucher_key?: string // 线下支付凭证(payment_method=offline 时必填) diff --git a/src/types/api/asset.ts b/src/types/api/asset.ts index c7e69ec..e6ed1b9 100644 --- a/src/types/api/asset.ts +++ b/src/types/api/asset.ts @@ -122,6 +122,9 @@ export interface AssetBoundCard { slot_position: number // 插槽位置 is_current?: boolean // 是否为设备当前使用的卡 gateway_extend?: string // Gateway 卡状态扩展字段,原样返回上游 extend + last_data_check_at?: string | null // 流量同步时间 + last_real_name_check_at?: string | null // 实名同步时间 + last_card_status_check_at?: string | null // 卡状态同步时间 } // ========== 资产实时状态响应 ========== @@ -179,7 +182,10 @@ export interface AssetRealtimeStatusResponse { realname_policy?: string // 实名认证策略(仅 card) current_month_usage_mb?: number // 本月已用流量 MB(仅 card) last_gateway_reading_mb?: number // 运营商周期月内已用流量 MB(仅 card) - last_sync_time?: string // 最后同步时间(仅 card) + last_sync_time?: string | null // 最后同步时间(仅 card) + last_data_check_at?: string | null // 流量同步时间 + last_real_name_check_at?: string | null // 实名同步时间 + last_card_status_check_at?: string | null // 卡状态同步时间 // ===== 设备专属字段 ===== device_protect_status?: DeviceProtectStatus // 保护期(仅 device) diff --git a/src/views/asset-management/asset-information/components/BasicInfoCard.vue b/src/views/asset-management/asset-information/components/BasicInfoCard.vue index f2204a2..37a8d36 100644 --- a/src/views/asset-management/asset-information/components/BasicInfoCard.vue +++ b/src/views/asset-management/asset-information/components/BasicInfoCard.vue @@ -59,6 +59,9 @@ {{ cardInfo?.real_name_at ? formatDateTime(cardInfo.real_name_at) : '-' }} + {{ + formatDateTime(cardInfo?.last_real_name_check_at) || '-' + }} {{ cardInfo?.msisdn || '-' }} @@ -66,8 +69,14 @@ {{ getAssetStatusName(cardInfo?.status) }} + {{ + formatDateTime(cardInfo?.last_card_status_check_at) || '-' + }} + {{ + formatDateTime(cardInfo?.last_data_check_at) || '-' + }} - + {{ cardInfo?.gateway_extend || '-' }} @@ -246,96 +255,117 @@ 设备实时信息 -
+
- - {{ deviceRealtime.online_status === 1 ? '在线' : '离线' }} + + {{ getOnlineStatusName(deviceRealtime?.online_status) }} {{ - deviceRealtime.battery_level !== null && deviceRealtime.battery_level !== undefined - ? `${deviceRealtime.battery_level}%` + deviceRealtime?.battery_level !== null && deviceRealtime?.battery_level !== undefined + ? `${deviceRealtime?.battery_level}%` : '-' }} - {{ formatDuration(deviceRealtime.run_time) }} + {{ formatDuration(deviceRealtime?.run_time) }} - {{ formatDuration(deviceRealtime.connect_time) }} + {{ formatDuration(deviceRealtime?.connect_time) }} - {{ formatDateTime(deviceRealtime.last_online_time) || '-' }} + {{ formatDateTime(deviceRealtime?.last_online_time) || '-' }} + + + {{ formatDateTime(cardInfo?.last_real_name_check_at) || '-' }} + + + {{ formatDateTime(cardInfo?.last_card_status_check_at) || '-' }} + + + {{ formatDateTime(cardInfo?.last_data_check_at) || '-' }} - - {{ getSignalQualityText(deviceRealtime.signal_quality) }} + + {{ getSignalQualityText(deviceRealtime?.signal_quality) }} - {{ getSignalBadReasonText(deviceRealtime.signal_bad_reason) }} + {{ getSignalBadReasonText(deviceRealtime?.signal_bad_reason) }} - {{ deviceRealtime.client_number ?? '-' }} / {{ deviceRealtime.max_clients ?? '-' }} + {{ deviceRealtime?.client_number ?? '-' }} / {{ deviceRealtime?.max_clients ?? '-' }} - - {{ deviceRealtime.wifi_enabled ? '已开启' : '已关闭' }} + + {{ + deviceRealtime?.wifi_enabled === undefined + ? '-' + : deviceRealtime.wifi_enabled + ? '已开启' + : '已关闭' + }} - {{ deviceRealtime.ssid || '-' }} + {{ deviceRealtime?.ssid || '-' }} - {{ deviceRealtime.wifi_password || '-' }} + {{ deviceRealtime?.wifi_password || '-' }} - {{ deviceRealtime.wan_ip || '-' }} + {{ deviceRealtime?.wan_ip || '-' }} - {{ deviceRealtime.lan_ip || '-' }} + {{ deviceRealtime?.lan_ip || '-' }} - {{ deviceRealtime.mac_address || '-' }} + {{ deviceRealtime?.mac_address || '-' }} - {{ deviceRealtime.imsi || '-' }} + {{ deviceRealtime?.imsi || '-' }} - {{ deviceRealtime.limit_speed ? `${deviceRealtime.limit_speed} KB/s` : '不限速' }} + {{ + deviceRealtime?.limit_speed === undefined + ? '-' + : deviceRealtime.limit_speed + ? `${deviceRealtime.limit_speed} KB/s` + : '不限速' + }} - {{ deviceRealtime.software_version || '-' }} + {{ deviceRealtime?.software_version || '-' }} {{ - deviceRealtime.switch_mode === '0' + deviceRealtime?.switch_mode === '0' ? '自动' - : deviceRealtime.switch_mode === '1' + : deviceRealtime?.switch_mode === '1' ? '手动' : '-' }} - {{ deviceRealtime.sync_interval ? `${deviceRealtime.sync_interval} 秒` : '-' }} + {{ deviceRealtime?.sync_interval ? `${deviceRealtime.sync_interval} 秒` : '-' }} - {{ formatDateTime(deviceRealtime.last_update_time) || '-' }} + {{ formatDateTime(deviceRealtime?.last_update_time) || '-' }}
@@ -451,6 +481,9 @@ real_name_at?: string realname_policy?: string gateway_extend?: string + last_data_check_at?: string | null + last_real_name_check_at?: string | null + last_card_status_check_at?: string | null } interface AssetInfo { @@ -477,6 +510,10 @@ real_name_at?: string supplier?: string gateway_extend?: string + last_sync_time?: string | null + last_data_check_at?: string | null + last_real_name_check_at?: string | null + last_card_status_check_at?: string | null bound_device_id?: number bound_device_no?: string bound_device_name?: string @@ -496,13 +533,13 @@ battery_level?: number | null run_time?: string connect_time?: string - last_online_time?: string + last_online_time?: string | null client_number?: number max_clients?: number current_iccid?: string signal_quality?: string signal_bad_reason?: string - wifi_enabled?: boolean + wifi_enabled?: boolean | number ssid?: string wifi_password?: string ip_address?: string @@ -516,8 +553,8 @@ switch_mode?: string sync_interval?: number device_id?: string - last_sync_time?: string - last_update_time?: string + last_sync_time?: string | null + last_update_time?: string | null } interface Props { @@ -610,6 +647,14 @@ return props.cardInfo.cards.find((card: BindingCard) => card.is_current) || null }) + const hasDeviceRealtimeSection = computed( + () => + Boolean(props.deviceRealtime) || + props.cardInfo?.last_data_check_at !== undefined || + props.cardInfo?.last_real_name_check_at !== undefined || + props.cardInfo?.last_card_status_check_at !== undefined + ) + // 处理轮询开关变化 const handlePollingChange = (value: string | number | boolean) => { emit('update:pollingEnabled', Boolean(value)) diff --git a/src/views/asset-management/asset-information/components/dialogs/DailyRecordsDialog.vue b/src/views/asset-management/asset-information/components/dialogs/DailyRecordsDialog.vue index 034c747..41f60da 100644 --- a/src/views/asset-management/asset-information/components/dialogs/DailyRecordsDialog.vue +++ b/src/views/asset-management/asset-information/components/dialogs/DailyRecordsDialog.vue @@ -7,7 +7,7 @@ {{ data.package_name }} - {{ formatDataSize(data.total_usage_mb) }} + {{ formatDataSize(displayedTotalUsageMb) }} @@ -59,9 +59,9 @@ @@ -86,14 +86,28 @@ ElTable, ElTableColumn, ElProgress, - ElEmpty, - ElMessage + ElEmpty } from 'element-plus' import { CardService } from '@/api/modules' + import type { PackageInfo } from '../../types' interface Props { modelValue: boolean packageUsageId?: number + packageRow?: PackageInfo + } + + interface DailyRecordItem { + date: string + daily_usage_mb: number + cumulative_usage_mb: number + } + + interface DailyRecordsResponseData { + package_name: string + package_usage_id: number + total_usage_mb: number + records: DailyRecordItem[] } interface Emits { @@ -104,16 +118,19 @@ const emit = defineEmits() const loading = ref(false) - const data = ref(null) + const data = ref(null) const filterType = ref<'all' | 'day' | 'month'>('all') const selectedDate = ref('') const selectedMonth = ref('') - const filteredRecords = ref([]) + const filteredRecords = ref([]) const visible = computed({ get: () => props.modelValue, set: (value) => emit('update:modelValue', value) }) + const displayedTotalUsageMb = computed( + () => props.packageRow?.real_total_mb ?? data.value?.total_usage_mb ?? 0 + ) // 监听对话框打开,加载数据 watch(visible, async (newVal) => { @@ -168,7 +185,7 @@ return } - filteredRecords.value = data.value.records.filter((record: any) => { + filteredRecords.value = data.value.records.filter((record) => { return record.date === selectedDate.value }) } @@ -181,7 +198,7 @@ return } - filteredRecords.value = data.value.records.filter((record: any) => { + filteredRecords.value = data.value.records.filter((record) => { return record.date.startsWith(selectedMonth.value) }) } diff --git a/src/views/asset-management/asset-information/composables/useAssetInfo.ts b/src/views/asset-management/asset-information/composables/useAssetInfo.ts index af2983a..4f9164f 100644 --- a/src/views/asset-management/asset-information/composables/useAssetInfo.ts +++ b/src/views/asset-management/asset-information/composables/useAssetInfo.ts @@ -20,6 +20,12 @@ type TrafficDisplaySource = { enable_virtual_data?: boolean | null } +const NULLABLE_REALTIME_CARD_FIELDS = new Set([ + 'last_data_check_at', + 'last_real_name_check_at', + 'last_card_status_check_at' +]) + /** * 格式化数据大小(MB -> GB/MB) */ @@ -54,7 +60,7 @@ const mergeAssetBoundCards = ( const mergedCard = { ...existingCard } as unknown as Record for (const [key, value] of Object.entries(realtimeCard)) { - if (value !== null && value !== undefined) { + if (value !== undefined && (value !== null || NULLABLE_REALTIME_CARD_FIELDS.has(key))) { mergedCard[key] = value } } @@ -304,6 +310,15 @@ export function useAssetInfo() { if (data.last_sync_time !== undefined) { cardInfo.value.last_sync_time = data.last_sync_time } + if (data.last_data_check_at !== undefined) { + cardInfo.value.last_data_check_at = data.last_data_check_at + } + if (data.last_real_name_check_at !== undefined) { + cardInfo.value.last_real_name_check_at = data.last_real_name_check_at + } + if (data.last_card_status_check_at !== undefined) { + cardInfo.value.last_card_status_check_at = data.last_card_status_check_at + } if (data.current_month_usage_mb !== undefined) { cardInfo.value.current_month_usage_mb = data.current_month_usage_mb } @@ -320,6 +335,15 @@ export function useAssetInfo() { // 仅用实时接口中非 null/undefined 的字段覆盖,避免清掉 resolve 接口返回的静态数据 cardInfo.value.cards = mergeAssetBoundCards(cardInfo.value.cards || [], data.cards) } + if (data.last_data_check_at !== undefined) { + cardInfo.value.last_data_check_at = data.last_data_check_at + } + if (data.last_real_name_check_at !== undefined) { + cardInfo.value.last_real_name_check_at = data.last_real_name_check_at + } + if (data.last_card_status_check_at !== undefined) { + cardInfo.value.last_card_status_check_at = data.last_card_status_check_at + } if (data.online_status !== undefined) { cardInfo.value.online_status = data.online_status } diff --git a/src/views/asset-management/asset-information/index.vue b/src/views/asset-management/asset-information/index.vue index 89b4e90..1c5c169 100644 --- a/src/views/asset-management/asset-information/index.vue +++ b/src/views/asset-management/asset-information/index.vue @@ -137,6 +137,7 @@ (null) + const selectedPackageRow = ref(null) // 绑定卡实名状态更新 const bindingCardRealnameStatusDialogVisible = ref(false) @@ -610,9 +612,10 @@ /** * 显示流量详单 */ - const handleShowDailyRecords = (payload: { packageRow: any }) => { + const handleShowDailyRecords = (payload: { packageRow: PackageInfo }) => { const { packageRow } = payload - selectedPackageUsageId.value = packageRow.package_usage_id + selectedPackageUsageId.value = packageRow.package_usage_id ?? packageRow.id + selectedPackageRow.value = packageRow dailyRecordsDialogVisible.value = true } diff --git a/src/views/asset-management/asset-information/types.ts b/src/views/asset-management/asset-information/types.ts index 1750ed0..d674621 100644 --- a/src/views/asset-management/asset-information/types.ts +++ b/src/views/asset-management/asset-information/types.ts @@ -29,6 +29,10 @@ export interface AssetInfo { cards?: BindingCard[] status?: number gateway_extend?: string + last_sync_time?: string | null + last_data_check_at?: string | null + last_real_name_check_at?: string | null + last_card_status_check_at?: string | null } // 设备绑定卡信息 @@ -45,6 +49,9 @@ export interface BindingCard { real_name_status?: number realname_policy?: string gateway_extend?: string + last_data_check_at?: string | null + last_real_name_check_at?: string | null + last_card_status_check_at?: string | null } // 设备实时信息接口 @@ -99,7 +106,9 @@ export interface DeviceRealtimeInfo { // 套餐信息接口 export interface PackageInfo { id: number + package_usage_id?: number package_id: number + order_id?: number package_name: string package_price: number paid_amount?: number @@ -114,6 +123,7 @@ export interface PackageInfo { status: number status_name?: string duration_days?: number + data_usage_mb?: number reduction_pct?: number // 展示增幅比例 package_type?: string // 套餐类型: formal(正式套餐)/ addon(加油包) enable_virtual_data?: boolean // 是否启用虚流量 diff --git a/src/views/asset-management/device-list/index.vue b/src/views/asset-management/device-list/index.vue index 71427ab..a46a213 100644 --- a/src/views/asset-management/device-list/index.vue +++ b/src/views/asset-management/device-list/index.vue @@ -243,6 +243,15 @@ +
+ 共分配{{ seriesBindingRangeCountText }}台设备 +