From 471d1196b3062b18d5dd05dd67735f04b566c04f Mon Sep 17 00:00:00 2001
From: sexygoat <1538832180@qq.com>
Date: Tue, 12 May 2026 12:11:53 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E8=BF=90=E8=90=A5?=
=?UTF-8?q?=E5=95=86=E5=81=9C=E6=9C=BA=E5=8E=9F=E5=9B=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../proposal.md | 40 ++++++++++++++++++
.../specs/asset-information/spec.md | 41 +++++++++++++++++++
.../add-asset-gateway-extend-display/tasks.md | 15 +++++++
src/types/api/asset.ts | 6 +++
.../components/BasicInfoCard.vue | 20 ++++++++-
.../composables/useAssetInfo.ts | 38 +++++++++++------
.../asset-information/types.ts | 2 +
7 files changed, 147 insertions(+), 15 deletions(-)
create mode 100644 openspec/changes/add-asset-gateway-extend-display/proposal.md
create mode 100644 openspec/changes/add-asset-gateway-extend-display/specs/asset-information/spec.md
create mode 100644 openspec/changes/add-asset-gateway-extend-display/tasks.md
diff --git a/openspec/changes/add-asset-gateway-extend-display/proposal.md b/openspec/changes/add-asset-gateway-extend-display/proposal.md
new file mode 100644
index 0000000..1df5cd1
--- /dev/null
+++ b/openspec/changes/add-asset-gateway-extend-display/proposal.md
@@ -0,0 +1,40 @@
+# Change: 补充资产详情 gateway_extend 卡状态扩展展示
+
+## Why
+
+当前后台“资产信息”页还没有消费接口新增的 `gateway_extend` 字段,因此运营人员无法在资产详情中直接看到运营商侧实际停机原因。
+
+最新接口契约已经补充两类返回:
+- 设备资产的绑定卡列表项新增 `gateway_extend`
+- 卡资产详情新增 `gateway_extend`
+
+如果前端不补齐这部分契约和展示规范,资产详情页会继续丢失上游已经返回的停机原因信息,影响停机排查和运营判断。
+
+## What Changes
+
+- 为后台“资产信息”页新增 `gateway_extend` 消费规范:
+ - 卡资产详情需接收并保留 `gateway_extend`
+ - 设备资产绑定卡列表项需接收并保留 `gateway_extend`
+ - 页面展示时需按上游原样展示该字段,不做二次翻译或改写
+- 为后台“资产信息”页新增 `gateway_extend` 展示规范:
+ - 卡资产在“卡信息”区域展示运营商侧实际停机原因
+ - 设备资产在“绑定卡列表”中展示每张卡的运营商侧实际停机原因
+ - 字段为空时展示稳定占位内容
+- 范围约束:
+ - 仅覆盖后台管理端资产详情页
+ - 不要求本次同步修改卡列表、设备列表或 C 端页面
+ - 不改变后端接口路径、方法和字段命名
+
+## Impact
+
+- Affected specs:
+ - `asset-information`
+- Affected code:
+ - `src/types/api/asset.ts`
+ - `src/views/asset-management/asset-information/composables/useAssetInfo.ts`
+ - `src/views/asset-management/asset-information/components/BasicInfoCard.vue`
+ - `src/views/asset-management/asset-information/types.ts`
+- Dependencies:
+ - 依赖后端资产详情相关响应已返回 `gateway_extend`
+- Breaking changes:
+ - 无
diff --git a/openspec/changes/add-asset-gateway-extend-display/specs/asset-information/spec.md b/openspec/changes/add-asset-gateway-extend-display/specs/asset-information/spec.md
new file mode 100644
index 0000000..ecd6ff4
--- /dev/null
+++ b/openspec/changes/add-asset-gateway-extend-display/specs/asset-information/spec.md
@@ -0,0 +1,41 @@
+## ADDED Requirements
+
+### Requirement: Admin Asset Gateway Extend Contract
+
+The admin asset information integration SHALL accept and preserve `gateway_extend` for card asset details and device bound-card entries.
+
+#### Scenario: Card asset detail preserves gateway_extend
+
+- **GIVEN** 用户正在查看后台“资产信息”中的卡资产详情
+- **AND** 资产详情响应返回 `gateway_extend`
+- **WHEN** 前端解析并映射卡资产详情数据
+- **THEN** 页面状态 MUST 保留 `gateway_extend` 原始字符串
+- **AND** 后续页面渲染 MUST 可以直接消费该字段
+
+#### Scenario: Device bound-card entries preserve gateway_extend
+
+- **GIVEN** 用户正在查看后台“资产信息”中的设备资产详情
+- **AND** 绑定卡列表数据中的某条记录返回 `gateway_extend`
+- **WHEN** 前端解析初次详情响应或后续刷新返回的绑定卡数据
+- **THEN** 对应卡记录 MUST 保留各自的 `gateway_extend` 原始字符串
+- **AND** 绑定卡列表的刷新合并过程 MUST NOT 无故丢失已有的 `gateway_extend`
+
+### Requirement: Admin Asset Gateway Extend Display
+
+The admin asset information view SHALL display `gateway_extend` as the carrier-side actual stop reason for card assets and device bound cards.
+
+#### Scenario: Show gateway_extend on card asset page
+
+- **GIVEN** 用户正在查看后台“资产信息”中的卡资产详情
+- **WHEN** 页面渲染“卡信息”区域
+- **THEN** 页面 MUST 提供“运营商停机原因”或等效标签来展示 `gateway_extend`
+- **AND** 展示内容 MUST 与上游返回的 `gateway_extend` 保持一致
+- **AND** 当 `gateway_extend` 为空时,页面 MUST 显示稳定占位内容
+
+#### Scenario: Show gateway_extend in device bound-card list
+
+- **GIVEN** 用户正在查看后台“资产信息”中的设备资产详情
+- **WHEN** 页面渲染“绑定卡列表”
+- **THEN** 每条绑定卡记录 MUST 提供“运营商停机原因”或等效展示位来展示本卡的 `gateway_extend`
+- **AND** 展示内容 MUST 与该卡返回的 `gateway_extend` 保持一致
+- **AND** 当某条卡记录的 `gateway_extend` 为空时,该行 MUST 显示稳定占位内容
diff --git a/openspec/changes/add-asset-gateway-extend-display/tasks.md b/openspec/changes/add-asset-gateway-extend-display/tasks.md
new file mode 100644
index 0000000..80bf627
--- /dev/null
+++ b/openspec/changes/add-asset-gateway-extend-display/tasks.md
@@ -0,0 +1,15 @@
+## 1. Contract Alignment
+
+- [x] 1.1 扩展后台资产详情相关类型,支持卡资产详情与设备绑定卡列表中的 `gateway_extend`。
+- [x] 1.2 调整资产详情数据映射与设备绑定卡刷新合并逻辑,确保 `gateway_extend` 在初次加载和后续刷新后都不会丢失。
+
+## 2. UI Display
+
+- [x] 2.1 在卡资产“卡信息”区域新增 `gateway_extend` 展示,作为运营商侧实际停机原因。
+- [x] 2.2 在设备资产“绑定卡列表”中新增 `gateway_extend` 展示,并为缺失值提供稳定占位内容。
+
+## 3. Verification
+
+- [ ] 3.1 验证卡资产详情返回 `gateway_extend` 时页面可按原样展示。
+- [ ] 3.2 验证设备资产绑定卡列表返回 `gateway_extend` 时每条卡记录展示正确。
+- [ ] 3.3 验证资产刷新或实时状态更新后,已展示的 `gateway_extend` 不会被错误清空。
diff --git a/src/types/api/asset.ts b/src/types/api/asset.ts
index 1007622..8783909 100644
--- a/src/types/api/asset.ts
+++ b/src/types/api/asset.ts
@@ -57,6 +57,7 @@ export interface AssetResolveResponse {
series_name: string // 套餐系列名称
real_name_status: RealNameStatus // 实名状态:0 未实名 / 1 已实名
realname_policy?: string // 实名认证策略 (none:无需实名, before_order:先实名后充值/购买, after_order:先充值/购买后实名)
+ real_name_at?: string | null // 实名时间
network_status?: NetworkStatus // 网络状态:0 停机 / 1 开机(仅 card)
current_package: string // 当前套餐名称(无则空)
current_package_usage_id: number | null // 当前主套餐对应的套餐使用记录 ID,无主套餐时为 null
@@ -86,6 +87,7 @@ export interface AssetResolveResponse {
supplier?: string // 供应商
activation_status?: number // 激活状态
enable_polling?: boolean // 是否参与轮询
+ gateway_extend?: string // Gateway 卡状态扩展字段,原样返回上游 extend
// ===== 设备专属字段 (asset_type === 'device' 时) =====
bound_card_count?: number // 绑定卡数量
@@ -117,6 +119,7 @@ export interface AssetBoundCard {
realname_policy?: string // 实名认证策略
slot_position: number // 插槽位置
is_current?: boolean // 是否为设备当前使用的卡
+ gateway_extend?: string // Gateway 卡状态扩展字段,原样返回上游 extend
}
// ========== 资产实时状态响应 ==========
@@ -199,6 +202,7 @@ export type AssetRefreshResponse = AssetRealtimeStatusResponse
* 对应接口:GET /api/admin/assets/:asset_type/:id/packages
*/
export interface AssetPackageUsageRecord {
+ id?: number // 兼容前端当前映射字段
package_usage_id?: number // 套餐使用记录 ID
package_id?: number // 套餐 ID
package_name?: string // 套餐名称
@@ -209,8 +213,10 @@ export interface AssetPackageUsageRecord {
status_name?: string // 状态中文名
real_total_mb?: number // 真流量总量(百分比以此为基准)
real_used_mb?: number // 真流量已用
+ real_remaining_mb?: number // 兼容前端当前映射字段
virtual_total_mb?: number // 业务停机阈值
virtual_used_mb?: number // 展示已用量
+ virtual_remaining_mb?: number // 兼容前端当前映射字段
virtual_ratio?: number // 虚流量比例(real/virtual)
reduction_pct?: number // 展示增幅比例(小数,如 0.428571)
enable_virtual_data?: boolean // 是否启用虚流量
diff --git a/src/views/asset-management/asset-information/components/BasicInfoCard.vue b/src/views/asset-management/asset-information/components/BasicInfoCard.vue
index e5a14ee..9e34b39 100644
--- a/src/views/asset-management/asset-information/components/BasicInfoCard.vue
+++ b/src/views/asset-management/asset-information/components/BasicInfoCard.vue
@@ -67,6 +67,10 @@
+
+ {{ cardInfo?.gateway_extend || '-' }}
+
+
-
+
+
@@ -194,7 +199,12 @@
{{ scope.row.real_name_at ? formatDateTime(scope.row.real_name_at) : '-' }}
-
+
+
+ {{ scope.row.gateway_extend || '-' }}
+
+
+
.info-card {
&.basic-info {
+ .gateway-extend-text {
+ word-break: break-all;
+ }
+
.card-header {
display: flex;
align-items: center;
diff --git a/src/views/asset-management/asset-information/composables/useAssetInfo.ts b/src/views/asset-management/asset-information/composables/useAssetInfo.ts
index 822ec6f..98fc96e 100644
--- a/src/views/asset-management/asset-information/composables/useAssetInfo.ts
+++ b/src/views/asset-management/asset-information/composables/useAssetInfo.ts
@@ -6,7 +6,12 @@
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { AssetService } from '@/api/modules'
-import type { AssetWalletResponse, DeviceGatewayInfo, AssetPackageUsageRecord } from '@/types/api'
+import type {
+ AssetBoundCard,
+ AssetWalletResponse,
+ DeviceGatewayInfo,
+ AssetPackageUsageRecord
+} from '@/types/api'
type TrafficDisplaySource = {
real_total_mb?: number | null
@@ -39,6 +44,23 @@ const getDisplayedUsagePercentage = (traffic?: TrafficDisplaySource | null) => {
return totalMb > 0 ? `${((usedMb / totalMb) * 100).toFixed(2)}%` : '0.00%'
}
+const mergeAssetBoundCards = (
+ existingCards: AssetBoundCard[] = [],
+ realtimeCards: AssetBoundCard[] = []
+): AssetBoundCard[] =>
+ realtimeCards.map((realtimeCard) => {
+ const existingCard = existingCards.find((card) => card.iccid === realtimeCard.iccid)
+ if (!existingCard) return realtimeCard
+
+ const mergedCard = { ...existingCard } as unknown as Record
+ for (const [key, value] of Object.entries(realtimeCard)) {
+ if (value !== null && value !== undefined) {
+ mergedCard[key] = value
+ }
+ }
+ return mergedCard as unknown as AssetBoundCard
+ })
+
export function useAssetInfo() {
// 状态管理
const loading = ref(false)
@@ -99,6 +121,7 @@ export function useAssetInfo() {
carrier_name: data.carrier_name || '',
supplier: data.supplier || '',
network_status: data.network_status,
+ gateway_extend: data.gateway_extend ?? '',
bound_device_id: data.bound_device_id,
bound_device_no: data.bound_device_no || '',
bound_device_name: data.bound_device_name || '',
@@ -293,18 +316,7 @@ export function useAssetInfo() {
if (data.cards && data.cards.length > 0) {
// 合并实时卡数据与原始卡数据,保留 real_name_at 等静态字段
// 仅用实时接口中非 null/undefined 的字段覆盖,避免清掉 resolve 接口返回的静态数据
- const existingCards: any[] = cardInfo.value.cards || []
- cardInfo.value.cards = data.cards.map((realtimeCard: any) => {
- const existing = existingCards.find((c: any) => c.iccid === realtimeCard.iccid)
- if (!existing) return realtimeCard
- const merged = { ...existing }
- for (const [key, value] of Object.entries(realtimeCard)) {
- if (value !== null && value !== undefined) {
- merged[key] = value
- }
- }
- return merged
- })
+ cardInfo.value.cards = mergeAssetBoundCards(cardInfo.value.cards || [], data.cards)
}
if (data.online_status !== undefined) {
cardInfo.value.online_status = data.online_status
diff --git a/src/views/asset-management/asset-information/types.ts b/src/views/asset-management/asset-information/types.ts
index 0ad5c2b..6dae43b 100644
--- a/src/views/asset-management/asset-information/types.ts
+++ b/src/views/asset-management/asset-information/types.ts
@@ -27,6 +27,7 @@ export interface AssetInfo {
sn?: string
cards?: BindingCard[]
status?: number
+ gateway_extend?: string
}
// 设备绑定卡信息
@@ -42,6 +43,7 @@ export interface BindingCard {
network_status?: number
real_name_status?: number
realname_policy?: string
+ gateway_extend?: string
}
// 设备实时信息接口