fix: 分页器一直显示和资产信息使用单卡
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m15s

This commit is contained in:
sexygoat
2026-05-21 14:39:21 +08:00
parent 893921007a
commit dfb78006ac
8 changed files with 152 additions and 12 deletions

View File

@@ -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本次仅调整后台设备详情字段取值口径

View File

@@ -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`

View File

@@ -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` 或任意非当前卡值。

View File

@@ -184,7 +184,7 @@
total: 0,
currentPage: 1,
pageSize: 20,
hideOnSinglePage: true,
hideOnSinglePage: false,
pageSizes: () => [10, 20, 30, 50],
paginationAlign: 'center',
paginationSize: 'default',

View File

@@ -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 // 是否为设备当前使用的卡

View File

@@ -107,9 +107,14 @@
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="实名状态">
<ElTag :type="getRealNameStatusType(currentCard?.real_name_status)" size="small">
{{ getRealNameStatusName(currentCard?.real_name_status) }}
<ElTag
v-if="selectedDeviceStatusCard"
:type="getRealNameStatusType(selectedDeviceStatusCard.real_name_status)"
size="small"
>
{{ getRealNameStatusName(selectedDeviceStatusCard.real_name_status) }}
</ElTag>
<span v-else>-</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="激活状态">
{{ cardInfo?.activation_status_name || '-' }}
@@ -119,12 +124,19 @@
cardInfo?.device_model || '-'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="实名时间">{{
cardInfo?.real_name_at ? formatDateTime(cardInfo.real_name_at) : '-'
selectedDeviceStatusCard?.real_name_at
? formatDateTime(selectedDeviceStatusCard.real_name_at)
: '-'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="在线状态">
<ElTag :type="getOnlineStatusType(cardInfo?.online_status)" size="small">
{{ getOnlineStatusName(cardInfo?.online_status) }}
<ElTag
v-if="selectedDeviceStatusCard?.network_status !== undefined"
:type="selectedDeviceStatusCard.network_status === 1 ? 'success' : 'danger'"
size="small"
>
{{ selectedDeviceStatusCard.network_status === 1 ? '正常' : '停机' }}
</ElTag>
<span v-else>-</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="虚拟号">{{ cardInfo?.virtual_no || '-' }}</ElDescriptionsItem>
<ElDescriptionsItem label="切卡模式">{{
@@ -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
})

View File

@@ -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) {

View File

@@ -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