diff --git a/api/modules/asset.js b/api/modules/asset.js
index 0dbe581..3ee3ad1 100644
--- a/api/modules/asset.js
+++ b/api/modules/asset.js
@@ -19,6 +19,24 @@ export const normalizeAssetInfo = (data = {}) => ({
: data.is_expiring === true || data.is_expiring === 1
});
+const normalizePackageHistoryNode = (node = {}) => ({
+ ...node,
+ children: Array.isArray(node.children)
+ ? node.children.map(normalizePackageHistoryNode)
+ : [],
+ expand_by_default: node.expand_by_default === true || node.expand_by_default === 1
+});
+
+export const normalizeAssetPackageHistory = (data = {}) => ({
+ ...data,
+ items: Array.isArray(data.items)
+ ? data.items.map(normalizePackageHistoryNode)
+ : [],
+ page: Number(data.page),
+ size: Number(data.size),
+ total: Number(data.total)
+});
+
export const isAssetRealNameCompleted = (assetInfo = {}) => {
if (assetInfo.asset_type === 'device') {
return (assetInfo.cards || []).some((card) => Number(card?.real_name_status) === 1);
@@ -50,7 +68,7 @@ export const assetApi = {
url: '/api/c/v1/asset/package-history',
method: 'GET',
data: { identifier, page, page_size, ...params }
- });
+ }).then(normalizeAssetPackageHistory);
},
getPackages(identifier, packageType = '') {
diff --git a/components/PackageHistoryNode.vue b/components/PackageHistoryNode.vue
new file mode 100644
index 0000000..2de3dfe
--- /dev/null
+++ b/components/PackageHistoryNode.vue
@@ -0,0 +1,286 @@
+
+
+
+
+
+ {{ node.relationship_status_name || node.relationship_status }}
+
+
+ 关联主套餐使用记录:{{ node.master_usage_id }}
+
+
+
+
+ 购买时间
+ {{ formatDate(node.created_at) }}
+
+
+ 激活时间
+ {{ formatDate(node.activated_at) }}
+
+
+ 到期时间
+ {{ formatDate(node.expires_at) }}
+
+
+
+
+
+
+ 流量信息
+
+
+ 已使用
+ {{ getUsedFlow(node) }}
+
+
+ 总流量
+ {{ getTotalFlow(node) }}
+
+
+ 剩余
+ {{ getRemainFlow(node) }}
+
+
+
+
+
+
+
+
+
+
+ {{ expanded ? '收起关联加油包' : '展开关联加油包' }}
+ ({{ children.length }})
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/API.md b/docs/API.md
index 7e8f8a1..b36e3ec 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -376,7 +376,102 @@ data:
- wallet_balance:钱包余额(分)
-## 3.2 资产套餐历史
+## 3.2 资产套餐历史(当前契约)
+
+URL:
+GET /api/c/v1/asset/package-history
+
+更新说明(2026-09-08):
+* 套餐历史已改为主套餐—加油包关系组。`items` 只包含顶层关系组;关联加油包位于所属主项的 `children` 中,子项绝不会跨页返回。
+* `total` 为筛选后的顶层关系组数量,分页壳使用 `page`、`size`、`total`;其中 `size` 表示每页顶层关系组数量。
+* 传入 `status` 和 `package_type` 时,两项必须由同一条使用记录联合命中;主项或任一子项命中时,接口均返回完整关系组,客户端不得对组内节点二次过滤。
+* 加油包通过 `master_usage_id` 标识关联主套餐使用记录。关联主套餐物理缺失时,节点以独立顶层项返回并设置 `relationship_status: "master_missing"`;关联主套餐存在但读取或展示失败时,接口按统一错误响应返回失败。
+
+Query 参数:
+- identifier(string,必填)- 资产标识符(SN/IMEI/虚拟号/ICCID/MSISDN),长度 1–50
+- package_type(string,可选)- 套餐类型(formal:正式套餐;addon:加油包)
+- status(integer,可选)- 套餐状态(0:待生效;1:生效中;2:已用完;3:已过期;4:已失效)
+- page(integer,必填)- 页码,最小为 1
+- page_size(integer,必填)- 每页顶层关系组数量,范围 1–100
+
+成功响应:
+```json
+{
+ "code": 0,
+ "msg": "success",
+ "timestamp": "2026-09-08T00:00:00Z",
+ "data": {
+ "items": [
+ {
+ "activated_at": "2026-09-01T00:00:00Z",
+ "children": [
+ {
+ "children": [],
+ "expand_by_default": false,
+ "master_usage_id": 5001,
+ "package_id": 1002,
+ "package_name": "5GB加油包",
+ "package_type": "addon",
+ "package_usage_id": 5002,
+ "status": 1,
+ "status_name": "生效中"
+ }
+ ],
+ "created_at": "2026-09-01T00:00:00Z",
+ "enable_virtual_data": false,
+ "expand_by_default": true,
+ "expires_at": "2026-09-30T23:59:59Z",
+ "master_usage_id": null,
+ "order_id": 0,
+ "package_id": 1001,
+ "package_name": "10GB月套餐",
+ "package_type": "formal",
+ "package_usage_id": 5001,
+ "priority": 1,
+ "real_total_mb": 10240,
+ "real_used_mb": 2048,
+ "reduction_pct": 0,
+ "status": 1,
+ "status_name": "生效中",
+ "usage_type": "single_card",
+ "virtual_total_mb": 10240,
+ "virtual_used_mb": 2048
+ }
+ ],
+ "page": 1,
+ "size": 10,
+ "total": 1
+ }
+}
+```
+
+`data.items[]` 为递归节点,所有节点均可包含以下字段:
+- activated_at(date-time,可空):激活时间
+- children(array):关联加油包或下级关联节点
+- created_at(date-time):购买创建时间
+- enable_virtual_data(boolean):是否启用虚流量
+- expand_by_default(boolean):是否默认展开 `children`
+- expires_at(date-time,可空):到期时间
+- master_usage_id(integer,可空):关联主套餐使用记录 ID;普通主项为 `null`
+- order_id(integer):历史兼容字段,始终输出零值 `0`,不填充真实订单 ID
+- package_id、package_name、package_type、package_usage_id:套餐及使用记录标识;`package_type` 为 `formal` 或 `addon`
+- priority:优先级
+- real_total_mb、real_used_mb:真实总量和真实已用量(MB)
+- virtual_total_mb、virtual_used_mb:业务停机阈值和展示已用量(MB)
+- reduction_pct:展示增幅比例
+- status、status_name:套餐状态及名称
+- usage_type:使用类型(single_card/device)
+- relationship_status、relationship_status_name:关系异常状态及名称;仅在主套餐物理缺失时返回 `master_missing`
+
+错误响应:
+- HTTP 400:请求参数错误
+- HTTP 401:未认证或认证已过期
+- HTTP 403:无权访问
+- HTTP 500:服务器内部错误,包括关联主套餐存在但读取或展示失败
+
+以上失败场景均使用统一 `ErrorResponse`:`code`、`msg`、`timestamp`,可选 `data`。H5 通过共享请求层展示后端返回的 `msg`;401 同时清除登录状态并跳转登录页。
+
+## 3.2 资产套餐历史(旧版,已废弃)
URL:
GET /api/c/v1/asset/package-history
diff --git a/pages/asset-package-history/asset-package-history.vue b/pages/asset-package-history/asset-package-history.vue
index 4647b4f..cd0b73c 100644
--- a/pages/asset-package-history/asset-package-history.vue
+++ b/pages/asset-package-history/asset-package-history.vue
@@ -1,6 +1,5 @@
-
@@ -10,169 +9,81 @@
当前账号下暂无套餐历史信息
-
-
-
-
-
-
- 激活时间
- {{ item.activated_at || '-' }}
-
-
- 到期时间
- {{ item.expires_at || '-' }}
-
-
-
-
-
-
- 流量信息
-
-
- 已使用
- {{ getUsedFlow(item) }}
-
-
- 总流量
- {{ getTotalFlow(item) }}
-
-
- 剩余
- {{ getRemainFlow(item) }}
-
-
-
-
-
-
-
-
+
+
-
-
+
加载中...
- 没有更多了
- 点击加载更多
+ 没有更多套餐
+ 点击加载更多