From 7d3352038a5725a31cc9b134170b70a4e569002c Mon Sep 17 00:00:00 2001 From: luo Date: Wed, 22 Jul 2026 16:57:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8D=A2=E8=B4=A7=E6=96=B0=E6=97=A7?= =?UTF-8?q?=E8=B5=84=E4=BA=A7=E5=B1=95=E7=A4=BA=E4=B8=8E=E7=8B=AC=E7=AB=8B?= =?UTF-8?q?=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../proposal.md | 29 ++++++ .../specs/exchange-management/spec.md | 96 +++++++++++++++++++ .../tasks.md | 26 +++++ src/api/modules/exchange.ts | 10 +- .../exchange-management/index.vue | 49 ++++++++-- 5 files changed, 197 insertions(+), 13 deletions(-) create mode 100644 openspec/changes/update-exchange-separate-asset-search-display/proposal.md create mode 100644 openspec/changes/update-exchange-separate-asset-search-display/specs/exchange-management/spec.md create mode 100644 openspec/changes/update-exchange-separate-asset-search-display/tasks.md diff --git a/openspec/changes/update-exchange-separate-asset-search-display/proposal.md b/openspec/changes/update-exchange-separate-asset-search-display/proposal.md new file mode 100644 index 0000000..1a452dd --- /dev/null +++ b/openspec/changes/update-exchange-separate-asset-search-display/proposal.md @@ -0,0 +1,29 @@ +# Change: 更新换货列表的新旧资产展示与独立搜索 + +## Why + +换货管理列表当前使用一个资产标识符条件同时匹配旧资产和新资产,且表格未完整区分两侧资产类型与标识。运营人员无法准确按换出资产或换入资产检索、识别换货单。 + +## What Changes + +- 将换货列表筛选区的单一“资产标识符”替换为“旧资产”和“新资产”两个独立输入框。 +- 列表查询使用 `old_asset_keyword` 和 `new_asset_keyword`;两个条件可独立或组合传递,空条件不传。 +- 停止在换货列表查询中使用旧的通用 `identifier` 参数。 +- 扩展换货列表项契约,支持旧资产和新资产各自的 `type`、`id`、`identifier`,以及 `status`、`status_name`。 +- 换货列表分别展示旧资产类型、旧资产标识、新资产类型、新资产标识四列,不能混列。 +- 物联网卡使用后端返回的 ICCID,设备使用后端返回的设备号;前端不转换接入号、虚拟号或其他输入标识。 + +## Impact + +- Affected specs: + - `exchange-management` +- Related active changes: + - `update-exchange-flow-direct-type`,本提案替换其换货列表通用资产标识符筛选口径,不改变流程类型筛选与换货操作。 + - `update-asset-exchange-generation-links`,仅共享换货领域概念,不修改资产详情换货链路展示。 +- Affected code: + - `src/api/modules/exchange.ts` + - `src/views/asset-management/exchange-management/index.vue` +- API contract: + - `GET /api/admin/exchanges?old_asset_keyword=&new_asset_keyword=` +- Dependencies: + - 后端列表接口支持分别按旧资产和新资产关键词查询,并返回新旧资产字段。 diff --git a/openspec/changes/update-exchange-separate-asset-search-display/specs/exchange-management/spec.md b/openspec/changes/update-exchange-separate-asset-search-display/specs/exchange-management/spec.md new file mode 100644 index 0000000..96b58a5 --- /dev/null +++ b/openspec/changes/update-exchange-separate-asset-search-display/specs/exchange-management/spec.md @@ -0,0 +1,96 @@ +## ADDED Requirements + +### Requirement: Exchange List Separate Asset Query Contract + +The exchange management list integration SHALL query `GET /api/admin/exchanges` with optional `old_asset_keyword` and `new_asset_keyword` parameters instead of the deprecated generic `identifier` parameter. + +#### Scenario: Query by old asset only + +- **GIVEN** 用户在换货管理列表的“旧资产”输入框中填写关键词 +- **AND** “新资产”输入框为空 +- **WHEN** 用户执行搜索 +- **THEN** 请求 MUST include `old_asset_keyword` with the entered value +- **AND** 请求 MUST NOT include `new_asset_keyword` +- **AND** 请求 MUST NOT include `identifier` + +#### Scenario: Query by new asset only + +- **GIVEN** 用户在换货管理列表的“新资产”输入框中填写关键词 +- **AND** “旧资产”输入框为空 +- **WHEN** 用户执行搜索 +- **THEN** 请求 MUST include `new_asset_keyword` with the entered value +- **AND** 请求 MUST NOT include `old_asset_keyword` +- **AND** 请求 MUST NOT include `identifier` + +#### Scenario: Combine old and new asset keywords + +- **GIVEN** 用户在“旧资产”和“新资产”输入框中均填写关键词 +- **WHEN** 用户执行搜索 +- **THEN** 请求 MUST include both `old_asset_keyword` and `new_asset_keyword` +- **AND** 后端返回同时匹配两项条件的换货单 + +#### Scenario: Omit empty asset keywords + +- **GIVEN** “旧资产”和“新资产”输入框均为空 +- **WHEN** 用户查询、刷新、切换分页或重置搜索条件 +- **THEN** 请求 MUST NOT include `old_asset_keyword` +- **AND** 请求 MUST NOT include `new_asset_keyword` +- **AND** 请求 MUST NOT include `identifier` + +#### Scenario: Preserve raw asset keyword input + +- **GIVEN** 用户输入 ICCID、接入号、虚拟号、IMEI 或 SN 作为旧资产或新资产关键词 +- **WHEN** 页面发送列表查询请求 +- **THEN** 页面 MUST pass the entered text unchanged in its corresponding keyword parameter +- **AND** 页面 MUST NOT convert an access number, virtual number, or other identifier into another asset identifier + +### Requirement: Exchange List Separate Asset Display Contract + +The exchange list-item contract SHALL preserve `old_asset_type`, `old_asset_id`, `old_asset_identifier`, `new_asset_type`, `new_asset_id`, `new_asset_identifier`, `status`, and `status_name` from each `GET /api/admin/exchanges` item. + +#### Scenario: Parse old and new asset fields + +- **GIVEN** 换货列表接口返回一条换货单 +- **WHEN** 前端解析该列表项 +- **THEN** 前端类型 MUST support the old asset type, ID, and identifier fields independently from the new asset fields +- **AND** 前端类型 MUST support `status` and `status_name` + +### Requirement: Exchange List Separate Asset Columns + +The exchange management list SHALL display distinct columns for `旧资产类型`, `旧资产标识`, `新资产类型`, and `新资产标识`. + +#### Scenario: Display separate old and new IoT card assets + +- **GIVEN** 换货列表项的 `old_asset_type` 和 `new_asset_type` 均为 `iot_card` +- **WHEN** 页面渲染换货列表行 +- **THEN** 旧资产类型和新资产类型 MUST each display `物联网卡` +- **AND** 旧资产标识 MUST display `old_asset_identifier` as the backend-returned ICCID +- **AND** 新资产标识 MUST display `new_asset_identifier` as the backend-returned ICCID + +#### Scenario: Display separate old and new device assets + +- **GIVEN** 换货列表项的旧资产或新资产类型为 `device` +- **WHEN** 页面渲染换货列表行 +- **THEN** 对应资产类型 MUST display `设备` +- **AND** 对应资产标识 MUST display its backend-returned device number + +#### Scenario: Keep old and new asset values separated + +- **GIVEN** 换货列表接口返回新旧资产字段 +- **WHEN** 页面渲染换货列表行 +- **THEN** 旧资产列 MUST only read `old_asset_type` and `old_asset_identifier` +- **AND** 新资产列 MUST only read `new_asset_type` and `new_asset_identifier` +- **AND** 页面 MUST NOT substitute a missing new asset value with an old asset value + +#### Scenario: Display unavailable new asset placeholder + +- **GIVEN** 换货列表项尚未关联新资产,或新资产类型或标识为空 +- **WHEN** 页面渲染新资产列 +- **THEN** 页面 MUST display a stable placeholder for each unavailable value + +#### Scenario: Display backend exchange status name + +- **GIVEN** 换货列表项返回 `status_name` +- **WHEN** 页面渲染状态列 +- **THEN** 页面 MUST display `status_name` +- **AND** 当 `status_name` 缺失时页面 MUST display a stable placeholder diff --git a/openspec/changes/update-exchange-separate-asset-search-display/tasks.md b/openspec/changes/update-exchange-separate-asset-search-display/tasks.md new file mode 100644 index 0000000..596cf26 --- /dev/null +++ b/openspec/changes/update-exchange-separate-asset-search-display/tasks.md @@ -0,0 +1,26 @@ +## 1. API Contract And Types + +- [ ] 1.1 将换货列表查询类型中的通用 `identifier` 替换为可选的 `old_asset_keyword` 与 `new_asset_keyword`。 +- [ ] 1.2 扩展换货列表项类型,支持 `old_asset_type`、`old_asset_id`、`old_asset_identifier`、`new_asset_type`、`new_asset_id`、`new_asset_identifier`、`status` 和 `status_name`。 + +## 2. Exchange List Search + +- [ ] 2.1 将换货管理列表筛选区的“资产标识符”替换为“旧资产”和“新资产”输入框。 +- [ ] 2.2 查询、刷新和分页时保留并传递非空的 `old_asset_keyword`、`new_asset_keyword`,不再发送 `identifier`。 +- [ ] 2.3 重置搜索条件时清空新旧资产关键词,后续请求不发送这两个参数。 +- [ ] 2.4 保留用户输入的 ICCID、接入号、虚拟号、IMEI 或 SN 原始值,不在前端转换后再传递给后端。 + +## 3. Exchange List Display + +- [ ] 3.1 增加旧资产类型、旧资产标识、新资产类型、新资产标识四个独立列表列。 +- [ ] 3.2 根据后端资产类型显示“物联网卡”或“设备”,并原样显示对应后端资产标识。 +- [ ] 3.3 状态列优先展示后端 `status_name`,缺失时显示稳定占位内容。 +- [ ] 3.4 新资产字段在未分配时显示稳定占位内容,不与旧资产字段混用。 + +## 4. Verification + +- [ ] 4.1 验证仅输入旧资产、仅输入新资产、同时输入新旧资产时,请求分别传递正确的关键词参数。 +- [ ] 4.2 验证两个资产关键词均为空时不传筛选参数,重置后保持相同行为。 +- [ ] 4.3 验证旧资产和新资产类型、标识符分别显示,IoT 卡显示 ICCID、设备显示设备号。 +- [ ] 4.4 验证卡的 ICCID、接入号或虚拟号关键词均由后端按输入所在的旧/新资产条件命中,前端不转换输入值。 +- [ ] 4.5 运行相关类型检查、lint 或构建验证。 diff --git a/src/api/modules/exchange.ts b/src/api/modules/exchange.ts index fb3c822..d6aceb8 100644 --- a/src/api/modules/exchange.ts +++ b/src/api/modules/exchange.ts @@ -11,7 +11,8 @@ export interface ExchangeQueryParams { page_size?: number status?: number // 换货状态 flow_type?: string // 流程类型(shipping/direct) - identifier?: string // 资产标识符(模糊匹配,同时匹配旧资产、新资产) + old_asset_keyword?: string // 旧资产关键词(ICCID、接入号、虚拟号、IMEI、SN) + new_asset_keyword?: string // 新资产关键词(ICCID、接入号、虚拟号、IMEI、SN) created_at_start?: string // 创建时间起始 created_at_end?: string // 创建时间结束 } @@ -33,10 +34,13 @@ export interface ExchangeResponse { exchange_no: string exchange_reason: string old_asset_type: string + old_asset_id: number old_asset_identifier: string - new_asset_type: string - new_asset_identifier: string + new_asset_type?: string | null + new_asset_id?: number | null + new_asset_identifier?: string | null status: number // 换货状态(1:待填写信息, 2:待发货, 3:已发货待确认, 4:已完成, 5:已取消) + status_name?: string status_text: string flow_type: string // 流程类型(shipping/direct) flow_type_name: string // 流程类型名称 diff --git a/src/views/asset-management/exchange-management/index.vue b/src/views/asset-management/exchange-management/index.vue index 5b657aa..faabbf5 100644 --- a/src/views/asset-management/exchange-management/index.vue +++ b/src/views/asset-management/exchange-management/index.vue @@ -423,7 +423,8 @@ const searchForm = reactive({ status: undefined, flow_type: undefined, // 流程类型筛选 - identifier: '', + old_asset_keyword: '', + new_asset_keyword: '', created_at_range: [], created_at_start: '', created_at_end: '' @@ -626,11 +627,20 @@ ] }, { - label: '资产标识符', - prop: 'identifier', + label: '旧资产', + prop: 'old_asset_keyword', type: 'input', config: { - placeholder: '请输入资产标识符', + placeholder: '请输入旧资产标识', + clearable: true + } + }, + { + label: '新资产', + prop: 'new_asset_keyword', + type: 'input', + config: { + placeholder: '请输入新资产标识', clearable: true } }, @@ -682,18 +692,25 @@ prop: 'old_asset_type', label: '旧资产类型', width: 120, - formatter: (row: any) => (row.old_asset_type === 'iot_card' ? 'IoT卡' : '设备') + formatter: (row: ExchangeResponse) => getAssetTypeName(row.old_asset_type) }, { prop: 'old_asset_identifier', label: '旧资产标识符', - width: 220 + width: 220, + formatter: (row: ExchangeResponse) => row.old_asset_identifier || '--' + }, + { + prop: 'new_asset_type', + label: '新资产类型', + width: 120, + formatter: (row: ExchangeResponse) => getAssetTypeName(row.new_asset_type) }, { prop: 'new_asset_identifier', label: '新资产标识符', width: 220, - formatter: (row: any) => row.new_asset_identifier || '--' + formatter: (row: ExchangeResponse) => row.new_asset_identifier || '--' }, { prop: 'recipient_name', @@ -734,7 +751,8 @@ prop: 'status', label: '状态', width: 130, - formatter: (row: any) => h(ElTag, { type: getStatusType(row.status) }, () => row.status_text) + formatter: (row: ExchangeResponse) => + h(ElTag, { type: getStatusType(row.status) }, () => row.status_name || '--') }, { prop: 'created_at', @@ -755,6 +773,12 @@ return types[status] || 'info' } + const getAssetTypeName = (assetType?: string | null) => { + if (assetType === 'iot_card') return '物联网卡' + if (assetType === 'device') return '设备' + return '--' + } + // 加载换货单列表 const loadExchangeList = async () => { loading.value = true @@ -771,8 +795,11 @@ if (searchForm.flow_type) { params.flow_type = searchForm.flow_type } - if (searchForm.identifier) { - params.identifier = searchForm.identifier + if (searchForm.old_asset_keyword) { + params.old_asset_keyword = searchForm.old_asset_keyword + } + if (searchForm.new_asset_keyword) { + params.new_asset_keyword = searchForm.new_asset_keyword } if (searchForm.created_at_start) { params.created_at_start = searchForm.created_at_start @@ -809,6 +836,8 @@ // 重置 const handleReset = () => { + searchForm.old_asset_keyword = '' + searchForm.new_asset_keyword = '' searchForm.created_at_range = [] searchForm.created_at_start = '' searchForm.created_at_end = ''