From dfb78006acf14b71eb268ac2e0ed86ce084d894c Mon Sep 17 00:00:00 2001
From: sexygoat <1538832180@qq.com>
Date: Thu, 21 May 2026 14:39:21 +0800
Subject: [PATCH] =?UTF-8?q?fix:=20=E5=88=86=E9=A1=B5=E5=99=A8=E4=B8=80?=
=?UTF-8?q?=E7=9B=B4=E6=98=BE=E7=A4=BA=E5=92=8C=E8=B5=84=E4=BA=A7=E4=BF=A1?=
=?UTF-8?q?=E6=81=AF=E4=BD=BF=E7=94=A8=E5=8D=95=E5=8D=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../proposal.md | 43 ++++++++++++++
.../specs/asset-information/spec.md | 56 +++++++++++++++++++
.../tasks.md | 21 +++++++
src/components/core/tables/ArtTable.vue | 2 +-
src/types/api/asset.ts | 1 +
.../components/BasicInfoCard.vue | 36 +++++++++---
.../composables/useAssetInfo.ts | 4 +-
.../asset-information/types.ts | 1 +
8 files changed, 152 insertions(+), 12 deletions(-)
create mode 100644 openspec/changes/update-asset-device-detail-bound-card-status-source/proposal.md
create mode 100644 openspec/changes/update-asset-device-detail-bound-card-status-source/specs/asset-information/spec.md
create mode 100644 openspec/changes/update-asset-device-detail-bound-card-status-source/tasks.md
diff --git a/openspec/changes/update-asset-device-detail-bound-card-status-source/proposal.md b/openspec/changes/update-asset-device-detail-bound-card-status-source/proposal.md
new file mode 100644
index 0000000..b27006f
--- /dev/null
+++ b/openspec/changes/update-asset-device-detail-bound-card-status-source/proposal.md
@@ -0,0 +1,43 @@
+# Change: 更新资产信息设备详情绑定卡状态取值来源
+
+## Why
+
+当前后台“资产信息”的设备详情里,`实名状态`、`在线状态`、`实名时间` 这三个字段的取值来源不一致:
+
+- `实名状态` 当前优先读取设备绑定卡中的当前卡状态
+- `在线状态` 仍读取设备顶层 `online_status`
+- `实名时间` 仍读取设备顶层 `real_name_at`
+
+当设备只绑定一张卡,或设备绑定多张卡并发生切卡时,这三个字段可能分别来自不同对象,导致页面展示口径不一致,运营人员无法准确判断“当前使用卡”的实名与联网状态。
+
+后端已经在设备资产详情的 `cards` 列表中返回每张绑定卡的 `real_name_status`、`network_status`、`real_name_at` 和 `is_current`。前端需要明确统一取值规则,避免继续混用设备顶层字段和绑定卡字段。
+
+## What Changes
+
+- 更新后台“资产信息”的设备详情字段取值规则:
+ - 当 `cards` 只有一张绑定卡时,`实名状态`、`在线状态`、`实名时间` 直接取该卡的 `real_name_status`、`network_status`、`real_name_at`
+ - 当 `cards` 有多张绑定卡时,以上三个字段统一取 `is_current=true` 的绑定卡
+- 设备详情这三个字段不再在存在可选绑定卡时混用设备顶层 `real_name_status`、`online_status`、`real_name_at`
+- 当前端无法选出符合规则的绑定卡时:
+ - 保留字段标签
+ - 显示稳定占位内容
+ - 不得猜测性回退到任意非当前卡
+- 更新资产详情绑定卡契约与状态合并规范:
+ - 绑定卡记录需要支持 `real_name_at`
+ - 刷新设备实时状态后,不得无故丢失已解析出的绑定卡 `real_name_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:
+ - 依赖后端继续在设备资产详情 `cards` 列表中返回 `real_name_status`、`network_status`、`real_name_at`、`is_current`
+ - 与活跃变更 `update-device-activation-status-filter-and-display` 共享 `asset-information` 的设备基础信息展示区域
+ - 与活跃变更 `update-asset-realtime-sync-timestamps-display` 共享设备绑定卡状态合并逻辑
+- Breaking changes:
+ - 无外部接口 breaking change;本次仅调整后台设备详情字段取值口径
diff --git a/openspec/changes/update-asset-device-detail-bound-card-status-source/specs/asset-information/spec.md b/openspec/changes/update-asset-device-detail-bound-card-status-source/specs/asset-information/spec.md
new file mode 100644
index 0000000..187115f
--- /dev/null
+++ b/openspec/changes/update-asset-device-detail-bound-card-status-source/specs/asset-information/spec.md
@@ -0,0 +1,56 @@
+## ADDED Requirements
+
+### Requirement: Admin Device Detail Status Fields Use A Selected Bound Card
+
+The admin asset information view SHALL source the device detail `实名状态`, `在线状态`, and `实名时间` fields from a selected bound card in the device `cards` list instead of mixing device top-level status fields.
+
+#### Scenario: Device detail uses the only bound card when exactly one card is bound
+
+- **GIVEN** 用户正在查看后台“资产信息”中的设备资产详情
+- **AND** 设备详情响应中的 `cards` 列表仅包含一条绑定卡记录
+- **WHEN** 页面渲染设备基础信息区域
+- **THEN** 页面 MUST 将 `实名状态` 映射为这条绑定卡的 `real_name_status`
+- **AND** 页面 MUST 将 `在线状态` 映射为这条绑定卡的 `network_status`
+- **AND** 页面 MUST 将 `实名时间` 映射为这条绑定卡的 `real_name_at`
+- **AND** 页面 MUST NOT 要求这条唯一绑定卡必须带有 `is_current=true` 才可被选中
+
+#### Scenario: Device detail uses the current bound card when multiple cards are bound
+
+- **GIVEN** 用户正在查看后台“资产信息”中的设备资产详情
+- **AND** 设备详情响应中的 `cards` 列表包含多条绑定卡记录
+- **AND** 其中一条绑定卡记录的 `is_current` 为 `true`
+- **WHEN** 页面渲染设备基础信息区域
+- **THEN** 页面 MUST 将 `实名状态` 映射为该当前绑定卡的 `real_name_status`
+- **AND** 页面 MUST 将 `在线状态` 映射为该当前绑定卡的 `network_status`
+- **AND** 页面 MUST 将 `实名时间` 映射为该当前绑定卡的 `real_name_at`
+- **AND** 页面 MUST NOT 使用任意非当前绑定卡的对应字段填充这三个展示项
+- **AND** 页面 MUST NOT 在已选出当前绑定卡后继续混用设备顶层 `real_name_status`、`online_status`、`real_name_at`
+
+#### Scenario: Device detail keeps stable placeholders when no eligible bound card can be selected
+
+- **GIVEN** 用户正在查看后台“资产信息”中的设备资产详情
+- **AND** `cards` 列表为空,或 `cards` 列表包含多条记录但不存在 `is_current=true` 的绑定卡
+- **WHEN** 页面渲染 `实名状态`、`在线状态`、`实名时间` 这三个展示项
+- **THEN** 页面 MUST 保留这三个字段标签
+- **AND** 页面 MUST 显示稳定占位内容
+- **AND** 页面 MUST NOT 猜测性地回退到任意非当前绑定卡
+
+### Requirement: Admin Device Bound Card Status Contract Compatibility
+
+The admin asset information integration SHALL accept and preserve the bound-card fields needed by the device detail status source selection rule.
+
+#### Scenario: Device asset detail contract accepts bound-card real-name time
+
+- **GIVEN** `GET /api/admin/assets/resolve/:identifier` 返回设备资产详情
+- **AND** 响应中的 `cards` 列表包含每张绑定卡的 `real_name_status`、`network_status`、`real_name_at`、`is_current`
+- **WHEN** 前端解析设备资产详情响应
+- **THEN** 前端绑定卡类型契约 MUST 支持 `real_name_at`
+- **AND** 前端状态模型 MUST 保留每张绑定卡的 `real_name_status`、`network_status`、`real_name_at`、`is_current`
+
+#### Scenario: Realtime merge preserves selected-card real-name time for device detail display
+
+- **GIVEN** 前端已解析设备资产详情,并持有绑定卡的 `real_name_at`
+- **AND** `GET /api/admin/assets/:identifier/realtime-status` 或 `POST /api/admin/assets/:identifier/refresh` 返回了设备实时状态
+- **WHEN** 前端将实时返回的 `cards` 数据与已有绑定卡状态合并
+- **THEN** 绑定卡已有的 `real_name_at` MUST NOT 因实时合并而无故丢失
+- **AND** 后续设备详情展示 MUST 继续按选中的绑定卡读取 `real_name_at`
diff --git a/openspec/changes/update-asset-device-detail-bound-card-status-source/tasks.md b/openspec/changes/update-asset-device-detail-bound-card-status-source/tasks.md
new file mode 100644
index 0000000..6f92df5
--- /dev/null
+++ b/openspec/changes/update-asset-device-detail-bound-card-status-source/tasks.md
@@ -0,0 +1,21 @@
+# Tasks: Update Asset Device Detail Bound Card Status Source
+
+## 1. Contract And State
+
+- [x] 1.1 扩展设备资产绑定卡相关类型,支持 `real_name_at` 字段,并与 `real_name_status`、`network_status`、`is_current` 一起作为设备详情取值依据。
+- [x] 1.2 调整设备资产详情的绑定卡选择逻辑:仅一张卡时直接选中该卡,多张卡时选中 `is_current=true` 的卡。
+- [x] 1.3 调整设备实时状态合并逻辑,确保刷新后不会丢失绑定卡已有的 `real_name_at` 等静态字段。
+
+## 2. UI Display
+
+- [x] 2.1 在资产信息页设备详情中,将 `实名状态` 映射为所选绑定卡的 `real_name_status`。
+- [x] 2.2 在资产信息页设备详情中,将 `在线状态` 映射为所选绑定卡的 `network_status`。
+- [x] 2.3 在资产信息页设备详情中,将 `实名时间` 映射为所选绑定卡的 `real_name_at`。
+- [x] 2.4 当不存在符合规则的绑定卡时,保留这三个字段标签并显示稳定占位内容,不回退到任意非当前卡。
+
+## 3. Verification
+
+- [ ] 3.1 验证设备资产详情 `cards` 仅有一条记录时,这三个字段均展示该卡的 `real_name_status`、`network_status`、`real_name_at`。
+- [ ] 3.2 验证设备资产详情 `cards` 存在多条记录且其中一条 `is_current=true` 时,这三个字段均展示当前卡的对应值。
+- [ ] 3.3 验证设备实时刷新后,绑定卡 `real_name_at` 未被丢失,且页面仍按同一张选中卡展示三项字段。
+- [ ] 3.4 验证不存在符合规则的绑定卡时,页面显示稳定占位内容,且不误用设备顶层 `online_status` 或任意非当前卡值。
diff --git a/src/components/core/tables/ArtTable.vue b/src/components/core/tables/ArtTable.vue
index 800c1aa..756ed3a 100644
--- a/src/components/core/tables/ArtTable.vue
+++ b/src/components/core/tables/ArtTable.vue
@@ -184,7 +184,7 @@
total: 0,
currentPage: 1,
pageSize: 20,
- hideOnSinglePage: true,
+ hideOnSinglePage: false,
pageSizes: () => [10, 20, 30, 50],
paginationAlign: 'center',
paginationSize: 'default',
diff --git a/src/types/api/asset.ts b/src/types/api/asset.ts
index e6ed1b9..8014be3 100644
--- a/src/types/api/asset.ts
+++ b/src/types/api/asset.ts
@@ -118,6 +118,7 @@ export interface AssetBoundCard {
msisdn: string // 手机号
network_status: NetworkStatus // 网络状态
real_name_status: RealNameStatus // 实名状态
+ real_name_at?: string | null // 实名时间
realname_policy?: string // 实名认证策略
slot_position: number // 插槽位置
is_current?: boolean // 是否为设备当前使用的卡
diff --git a/src/views/asset-management/asset-information/components/BasicInfoCard.vue b/src/views/asset-management/asset-information/components/BasicInfoCard.vue
index 63fee91..9ee8e47 100644
--- a/src/views/asset-management/asset-information/components/BasicInfoCard.vue
+++ b/src/views/asset-management/asset-information/components/BasicInfoCard.vue
@@ -107,9 +107,14 @@
-
- {{ getRealNameStatusName(currentCard?.real_name_status) }}
+
+ {{ getRealNameStatusName(selectedDeviceStatusCard.real_name_status) }}
+ -
{{ cardInfo?.activation_status_name || '-' }}
@@ -119,12 +124,19 @@
cardInfo?.device_model || '-'
}}
{{
- cardInfo?.real_name_at ? formatDateTime(cardInfo.real_name_at) : '-'
+ selectedDeviceStatusCard?.real_name_at
+ ? formatDateTime(selectedDeviceStatusCard.real_name_at)
+ : '-'
}}
-
- {{ getOnlineStatusName(cardInfo?.online_status) }}
+
+ {{ selectedDeviceStatusCard.network_status === 1 ? '正常' : '停机' }}
+ -
{{ cardInfo?.virtual_no || '-' }}
{{
@@ -478,7 +490,7 @@
is_current: boolean
network_status?: number
real_name_status?: number
- real_name_at?: string
+ real_name_at?: string | null
realname_policy?: string
gateway_extend?: string
last_data_check_at?: string | null
@@ -507,7 +519,7 @@
status?: number
shop_name?: string
series_name?: string
- real_name_at?: string
+ real_name_at?: string | null
supplier?: string
gateway_extend?: string
last_sync_time?: string | null
@@ -639,11 +651,17 @@
})
})
- // 获取当前使用的卡
- const currentCard = computed(() => {
+ // 设备详情状态字段统一从选中的绑定卡取值:
+ // 只有一张卡时直接取该卡,多张卡时仅取 is_current=true 的卡。
+ const selectedDeviceStatusCard = computed(() => {
if (!props.cardInfo?.cards || props.cardInfo.cards.length === 0) {
return null
}
+
+ if (props.cardInfo.cards.length === 1) {
+ return props.cardInfo.cards[0]
+ }
+
return props.cardInfo.cards.find((card: BindingCard) => card.is_current) || null
})
diff --git a/src/views/asset-management/asset-information/composables/useAssetInfo.ts b/src/views/asset-management/asset-information/composables/useAssetInfo.ts
index 4f9164f..c8ae6dd 100644
--- a/src/views/asset-management/asset-information/composables/useAssetInfo.ts
+++ b/src/views/asset-management/asset-information/composables/useAssetInfo.ts
@@ -331,8 +331,8 @@ export function useAssetInfo() {
cardInfo.value.device_protect_status = data.device_protect_status
}
if (data.cards && data.cards.length > 0) {
- // 合并实时卡数据与原始卡数据,保留 real_name_at 等静态字段
- // 仅用实时接口中非 null/undefined 的字段覆盖,避免清掉 resolve 接口返回的静态数据
+ // 合并实时卡数据与原始卡数据,保留 real_name_at 等静态字段。
+ // 设备详情的实名状态、在线状态、实名时间统一依赖选中的绑定卡,因此不能在实时刷新时丢掉这些字段。
cardInfo.value.cards = mergeAssetBoundCards(cardInfo.value.cards || [], data.cards)
}
if (data.last_data_check_at !== undefined) {
diff --git a/src/views/asset-management/asset-information/types.ts b/src/views/asset-management/asset-information/types.ts
index d674621..2223046 100644
--- a/src/views/asset-management/asset-information/types.ts
+++ b/src/views/asset-management/asset-information/types.ts
@@ -47,6 +47,7 @@ export interface BindingCard {
slot_position?: number
network_status?: number
real_name_status?: number
+ real_name_at?: string | null
realname_policy?: string
gateway_extend?: string
last_data_check_at?: string | null