fix: bug
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m4s

This commit is contained in:
sexygoat
2026-05-11 15:51:50 +08:00
parent 766497dc31
commit 1990a3ccba
40 changed files with 832 additions and 127 deletions

View File

@@ -9,12 +9,14 @@
### 1. 套餐管理模块 (Package Management) ✅
- [x] **套餐系列页面** (`/package-management/package-series`)
- ✅ 引入 `useAuth` composable
- ✅ 为"新增套餐系列"按钮添加 `v-permission="'package_series:add'"`
- ✅ 为状态切换添加 `disabled: !hasAuth('package_series:update_status')`
- ✅ 在操作列中使用 `hasAuth()` 判断编辑权限 (`package_series:edit`)和删除权限 (`package_series:delete`)
- [x] **套餐列表页面** (`/package-management/package-list`)
- ✅ 引入 `useAuth` composable
- ✅ 为"新增套餐"按钮添加 `v-permission="'package:add'"`
- ✅ 为上/下架切换添加 `disabled: !hasAuth('package:update_away')`
@@ -22,6 +24,7 @@
- ✅ 在操作列中使用 `hasAuth()` 判断编辑 (`package:edit`) 和删除 (`package:delete`) 权限
- [x] **单套餐分配页面** (`/package-management/package-assign`)
- ✅ 引入 `useAuth` composable
- ✅ 为"新增分配"按钮添加 `v-permission="'package_assign:add'"`
- ✅ 为状态切换添加 `disabled: !hasAuth('package_assign:update_status')`
@@ -60,6 +63,7 @@
### 4. 资产管理模块 (Asset Management) ✅
- [x] **IoT卡管理页面** (`/asset-management/iot-card-management`)
- ✅ 引入 `useAuth` composable
- ✅ 为表格头部操作按钮添加权限控制:
- 批量分配: `v-permission="'iot_card:batch_allocation'"`
@@ -73,16 +77,19 @@
- ✅ 验证权限控制正确工作
- [x] **IoT卡任务页面** (`/asset-management/iot-card-task`)
- ✅ 引入 `useAuth` composable
- ✅ 为"批量导入IoT卡"按钮添加 `v-permission="''"`
- ✅ 验证权限控制正确工作
- [x] **设备任务页面** (`/asset-management/device-task`)
- ✅ 引入 `useAuth` composable
- ✅ 为"批量导入设备"按钮添加 `v-permission="'device_task:bulk_import'"`
- ✅ 验证权限控制正确工作
- [x] **设备管理页面** (`/asset-management/device-list`)
- ✅ 引入 `useAuth` composable
- ✅ 为表格头部操作按钮添加权限控制:
- 批量分配: `v-permission="'devices:batch_allocation'"`
@@ -101,6 +108,7 @@
### 5. 账户管理模块 (Account Module) ✅
- [x] **企业客户页面** (`/account/enterprise-customer`)
- ✅ 引入 `useAuth` composable
- ✅ 为"新增企业客户"按钮添加 `v-permission="'enterprise_customer:add'"`
- ✅ 在操作列中使用 `hasAuth()` 判断以下权限:
@@ -111,6 +119,7 @@
- ✅ 验证权限控制正确工作
- [x] **运营商管理页面** (`/account/carrier-management`)
- ✅ 引入 `useAuth` composable
- ✅ 为"新增运营商"按钮添加 `v-permission="'carrier:add'"`
- ✅ 为状态切换添加 `disabled: !hasAuth('carrier:update_status')`
@@ -118,16 +127,19 @@
- ✅ 验证权限控制正确工作
- [x] **订单管理页面** (`/account/orders`)
- ✅ 引入 `useAuth` composable
- ✅ 为"创建订单"按钮添加 `v-permission="'orders:add'"`
- ✅ 验证权限控制正确工作
- [x] **提现配置页面** (`/account/commission/withdrawal-settings`)
- ✅ 引入 `useAuth` composable
- ✅ 为"新增配置"按钮添加 `v-permission="'withdrawal_settings:add'"`
- ✅ 验证权限控制正确工作
- [x] **我的佣金页面** (`/account/commission/my-commission`)
- ✅ 引入 `useAuth` composable
- ✅ 为"发起提现"按钮添加 `v-permission="'my_commission:add'"`
- ✅ 验证权限控制正确工作
@@ -148,6 +160,7 @@
### 7. 测试与文档
- [ ] **权限控制测试**
- 创建不同权限组合的测试用户
- 验证每个页面的按钮显示/隐藏符合预期
- 验证状态切换的启用/禁用符合预期

View File

@@ -0,0 +1,45 @@
# Change: 更新批量套餐系列绑定接口契约
## Why
当前批量设置套餐系列的两个接口仍然主要按“显式已选列表”思路使用:
- `PATCH /api/admin/devices/series-binding`
- `PATCH /api/admin/iot-cards/series-binding`
但最新业务要求这两个接口的使用方式需要尽量对齐现有“批量分配”流程,支持按列表、范围、筛选条件三种方式选择目标资产,而不是只能依赖表格勾选后的 ID/ICCID 列表。
如果接口契约不统一,前端无法复用批量分配/批量回收的选择模型,设备管理和单卡管理也会继续保留两套不同的批量操作交互与参数拼装逻辑。
## What Changes
- 为设备批量套餐系列绑定接口补充与批量操作一致的目标选择模型:
- 支持 `selection_type=list|range|filter`
- 支持 `device_ids``virtual_no_start` / `virtual_no_end`、以及设备筛选条件三类入参
- 在设备筛选条件中的“店铺名称”选择器复用 `GET /api/admin/shops/cascade?exclude_self=true` 级联店铺查询接口,并按最后一级节点提交 `shop_id`
- 保留 `series_id=0` 作为清除套餐系列绑定的语义
- 保留旧版兼容:未传 `selection_type` 时按 `device_ids` 处理
- 为单卡批量套餐系列绑定接口补充与单卡批量分配/回收一致的目标选择模型:
- 支持 `selection_type=list|range|filter`
- 支持 `iccids``iccid_start` / `iccid_end`、以及单卡筛选条件三类入参
- 保留 `series_id=0` 作为清除套餐系列绑定的语义
- 保留旧版兼容:未传 `selection_type` 时按 `iccids` 处理
- 两个接口均保持现有 PATCH 路径不变
- 两个接口继续返回批处理结果摘要与失败明细,不在本提案中引入新的异步任务机制或新的资源路径
## Impact
- Affected specs:
- `device-series-binding`
- `iot-card-series-binding`
- Affected code:
- `src/api/modules/device.ts`
- `src/api/modules/card.ts`
- `src/api/modules/shop.ts`
- `src/types/api/device.ts`
- `src/types/api/card.ts`
- `src/views/asset-management/device-list/index.vue`
- `src/views/asset-management/iot-card-management/index.vue`
- Reference docs:
- `docs/mvp/批量分配套餐系列.md`
- `docs/1-24下午-新功能.md`

View File

@@ -0,0 +1,81 @@
## ADDED Requirements
### Requirement: Device Batch Series Binding Selection Contract
The system SHALL expose `PATCH /api/admin/devices/series-binding` using a batch target-selection contract consistent with other batch asset operations.
The request MUST include `series_id`. The request MAY omit `selection_type` only for backward compatibility, and in that case the system MUST treat the request as `selection_type=list`.
When `selection_type=list`, the request MUST use `device_ids` to identify the target devices and MUST support up to 1000 device IDs.
When `selection_type=range`, the request MUST use `virtual_no_start` and `virtual_no_end` to identify the target device range.
When `selection_type=filter`, the request MUST support selecting the full filtered result set by the same device query dimensions used for batch operations, including `virtual_no`, `device_name`, `device_type`, `manufacturer`, `batch_no`, `status`, `shop_id`, `shop_ids`, `filter_series_id`, `created_at_start`, and `created_at_end`.
`series_id=0` MUST mean clearing the current series binding instead of binding a new series.
#### Scenario: Bind device series by explicit device ID list
- **GIVEN** 用户已经明确选中了多台设备
- **WHEN** 系统调用 `PATCH /api/admin/devices/series-binding`
- **AND** 请求体包含 `selection_type="list"``series_id=12``device_ids=[1001,1002,1003]`
- **THEN** 系统 MUST 按 `device_ids` 对这批设备设置目标套餐系列绑定
#### Scenario: Bind device series by virtual number range
- **GIVEN** 用户希望按设备虚拟号区间批量设置套餐系列
- **WHEN** 系统调用 `PATCH /api/admin/devices/series-binding`
- **AND** 请求体包含 `selection_type="range"``series_id=12``virtual_no_start="V000001"``virtual_no_end="V000100"`
- **THEN** 系统 MUST 按虚拟号范围选出目标设备并设置套餐系列绑定
#### Scenario: Clear device series binding by filter result set
- **GIVEN** 用户希望对满足筛选条件的全部设备清除当前套餐系列关联
- **WHEN** 系统调用 `PATCH /api/admin/devices/series-binding`
- **AND** 请求体包含 `selection_type="filter"``series_id=0``status=1``shop_ids=[1,2]`
- **THEN** 系统 MUST 对完整筛选结果集执行清除操作
- **AND** 系统 MUST NOT 只处理当前分页内已渲染的设备
#### Scenario: Omitted selection type remains compatible with legacy callers
- **GIVEN** 旧版调用方只提交 `device_ids``series_id`
- **WHEN** 系统调用 `PATCH /api/admin/devices/series-binding`
- **AND** 请求体包含 `device_ids=[1001,1002]``series_id=8`
- **THEN** 系统 MUST 将该请求视为 `selection_type="list"`
- **AND** 系统 MUST 继续按 `device_ids` 处理目标设备
### Requirement: Device Batch Series Binding Shop Filter Selector
The system SHALL use the cascade shop query endpoint for the shop selector in device batch series binding filter mode.
When the user filters devices by shop in the batch series binding dialog, the UI MUST load shop options from `GET /api/admin/shops/cascade` with `exclude_self=true`, present them as a cascade selector, and submit the last selected node as `shop_id`.
#### Scenario: Shop filter uses cascade endpoint with self excluded
- **GIVEN** 用户打开“批量设置设备套餐系列绑定”弹窗
- **AND** 用户选择 `selection_type="filter"`
- **WHEN** 用户展开“店铺名称”选择器
- **THEN** 系统 MUST 调用 `GET /api/admin/shops/cascade`
- **AND** 请求参数 MUST 包含 `exclude_self=true`
- **AND** 系统 MUST 按级联树结构展示可选店铺
#### Scenario: Shop cascade selection submits the last selected node
- **GIVEN** 用户在“店铺名称”级联选择器中依次选择某条店铺路径
- **WHEN** 用户确认提交设备批量套餐系列绑定
- **THEN** 系统 MUST 取该路径最后一级节点的 ID 作为请求中的 `shop_id`
### Requirement: Device Batch Series Binding Result Summary
The system SHALL return a batch processing summary for device series binding requests.
The result data MUST include `success_count`, `fail_count`, and `failed_items`. Each entry in `failed_items` MUST include `device_id`, `virtual_no`, and `reason`.
#### Scenario: Partial device binding failure returns itemized reasons
- **GIVEN** 某次设备批量套餐系列绑定同时存在成功项和失败项
- **WHEN** 接口返回处理结果
- **THEN** 响应数据 MUST 包含成功数量和失败数量
- **AND** 每条失败明细 MUST 返回对应的 `device_id`
- **AND** 每条失败明细 MUST 返回对应的 `virtual_no`
- **AND** 每条失败明细 MUST 返回可展示的失败原因 `reason`

View File

@@ -0,0 +1,59 @@
## ADDED Requirements
### Requirement: IoT Card Batch Series Binding Selection Contract
The system SHALL expose `PATCH /api/admin/iot-cards/series-binding` using the same batch target-selection pattern as standalone card batch allocation and recall.
The request MUST include `series_id`. The request MAY omit `selection_type` only for backward compatibility, and in that case the system MUST treat the request as `selection_type=list`.
When `selection_type=list`, the request MUST use `iccids` to identify the target cards and MUST support up to 1000 ICCIDs.
When `selection_type=range`, the request MUST use `iccid_start` and `iccid_end` to identify the target ICCID range.
When `selection_type=filter`, the request MUST support the same primary filter dimensions used by standalone card batch allocation and recall, including `carrier_id`, `status`, and `batch_no`.
`series_id=0` MUST mean clearing the current series binding instead of binding a new series.
#### Scenario: Bind card series by ICCID list
- **GIVEN** 用户已经明确选中了多张单卡
- **WHEN** 系统调用 `PATCH /api/admin/iot-cards/series-binding`
- **AND** 请求体包含 `selection_type="list"``series_id=12``iccids=["8986000000000000001","8986000000000000002"]`
- **THEN** 系统 MUST 按 `iccids` 对这批卡设置目标套餐系列绑定
#### Scenario: Bind card series by ICCID range
- **GIVEN** 用户希望按 ICCID 号段批量设置套餐系列
- **WHEN** 系统调用 `PATCH /api/admin/iot-cards/series-binding`
- **AND** 请求体包含 `selection_type="range"``series_id=12``iccid_start="8986000000000000001"``iccid_end="8986000000000000100"`
- **THEN** 系统 MUST 按 ICCID 范围选出目标卡并设置套餐系列绑定
#### Scenario: Clear card series binding by standalone-card filters
- **GIVEN** 用户希望对满足单卡筛选条件的全部卡清除当前套餐系列关联
- **WHEN** 系统调用 `PATCH /api/admin/iot-cards/series-binding`
- **AND** 请求体包含 `selection_type="filter"``series_id=0``carrier_id=1``status=1`
- **THEN** 系统 MUST 对完整筛选结果集执行清除操作
- **AND** 系统 MUST NOT 只处理当前分页内已渲染的卡
#### Scenario: Omitted selection type remains compatible with legacy callers
- **GIVEN** 旧版调用方只提交 `iccids``series_id`
- **WHEN** 系统调用 `PATCH /api/admin/iot-cards/series-binding`
- **AND** 请求体包含 `iccids=["8986000000000000001"]``series_id=8`
- **THEN** 系统 MUST 将该请求视为 `selection_type="list"`
- **AND** 系统 MUST 继续按 `iccids` 处理目标卡
### Requirement: IoT Card Batch Series Binding Result Summary
The system SHALL return a batch processing summary for IoT card series binding requests.
The result data MUST include `success_count`, `fail_count`, and `failed_items`. Each entry in `failed_items` MUST include `iccid` and `reason`.
#### Scenario: Partial card binding failure returns itemized reasons
- **GIVEN** 某次单卡批量套餐系列绑定同时存在成功项和失败项
- **WHEN** 接口返回处理结果
- **THEN** 响应数据 MUST 包含成功数量和失败数量
- **AND** 每条失败明细 MUST 返回对应的 `iccid`
- **AND** 每条失败明细 MUST 返回可展示的失败原因 `reason`

View File

@@ -0,0 +1,8 @@
## 1. Implementation
- [x] 1.1 更新设备批量套餐系列绑定的请求类型与 API 方法签名,使其支持 `list` / `range` / `filter` 三种选择方式。
- [x] 1.2 更新单卡批量套餐系列绑定的请求类型与 API 方法签名,使其对齐单卡批量分配/回收的选择方式。
- [x] 1.3 调整设备管理页面的“批量设置套餐系列”交互,使其支持与批量操作一致的目标选择模型,并让店铺筛选复用 `shops/cascade?exclude_self=true` 级联接口。
- [x] 1.4 调整单卡管理页面的“批量设置套餐系列”交互,使其支持与批量分配/回收一致的目标选择模型。
- [ ] 1.5 验证 `series_id=0` 的批量清除和单条清除入口仍然可用。
- [ ] 1.6 验证未传 `selection_type` 的旧调用仍按列表模式兼容处理。

View File

@@ -25,12 +25,15 @@
## Decisions
- Decision: 在前端类型层面显式补齐新增字段,而不是通过 `any` 或临时属性兼容
- Rationale: 这次变化的核心是语义纠偏。如果类型层继续缺字段,页面很容易回退到旧假设。
- Decision: 列表页必须同时呈现佣金流程状态和佣金业务结果,但不强制唯一 UI 形式
- Rationale: `commission_status_name` 仅代表流程状态,`commission_result_name` 代表业务结果。无论是两列展示,还是“主状态 + 副标签”,都必须让用户能明确区分这两个维度。
- Decision: 详情页将“操作者信息”和“业务归属信息”拆成两组展示项
- Rationale: 同一信息块内混放会继续强化旧认知,不利于用户理解订单责任归属。
- Decision: 所有“是否有佣金”的判断必须同时参考 `commission_status``commission_result`
@@ -39,6 +42,7 @@
## Risks / Trade-offs
- 风险: 订单模块以外可能也有复用订单类型或字段语义的代码。
- Mitigation: 实施前使用全局搜索盘点 `operator_name``commission_status``commission_result` 的相关用法。
- 风险: 列表新增列后表格宽度会增加。

View File

@@ -22,15 +22,19 @@
## Decisions
- Decision: 为订单创建弹窗引入独立的“凭证上传中”状态,而不是复用 `createLoading`
- Rationale: `createLoading` 表示“正在创建订单”,`voucherUploading` 表示“正在上传图片”,两者语义不同,混用会导致按钮文案和禁用逻辑失真。
- Decision: 切卡模式弹窗改为同时收集 `iot_card_id``switch_mode`
- Rationale: 最新接口契约要求请求体必须包含这两个字段,前端需要在提交前把选择结果显式建模。
- Decision: 设备未配置 IMEI 或没有任何绑定卡时,前端直接阻止提交
- Rationale: 这是当前确认保留的前置条件,应该在界面层尽早反馈,而不是把失败留到接口返回后再处理。
- Decision: 设备列表页和资产信息页共用同一套切卡模式弹窗提交契约
- Rationale: 两个入口都在执行相同的业务动作,统一参数结构和校验逻辑可以避免行为漂移。
- Decision: 只移除设备实时信息里的 `IP 地址` 字段,不影响 `WAN IP``LAN IP`

View File

@@ -42,5 +42,5 @@
- **AND** 当前套餐零售价上限为 `40.00`
- **WHEN** 用户输入大于 `40.00` 的零售价并提交
- **THEN** 系统阻止提交
- **AND** 系统显示“零售价不能大于建议售价 * 2”对应的校验提示
- **AND** 系统显示“零售价不能大于建议售价 \* 2”对应的校验提示
- **AND** 系统不得调用修改零售价接口