feat:更换企业授权
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 3m41s

This commit is contained in:
luo
2026-06-22 10:36:33 +08:00
parent 866a2587b3
commit d3b5f55c64
34 changed files with 2478 additions and 3335 deletions

View File

@@ -0,0 +1,34 @@
## Context
Enterprise asset authorization currently has two hidden pages launched from enterprise customer rows. The requested workflow moves those operations to the asset lists so users can filter assets by enterprise authorization state and act on the same list they export from.
## Goals
- Add enterprise authorization filters to IoT card management and device management.
- Add direct card/device enterprise authorization and recall actions beside existing export actions.
- Protect each new action with an independent button permission code.
- Remove enterprise customer row authorization shortcuts and the replaced “企业卡管理” / “企业设备列表” hidden pages.
## Non-Goals
- Do not change backend endpoint paths.
- Do not add new route pages for this workflow.
- Do not preserve compatibility routes for the old “企业卡管理” or “企业设备列表” pages unless a product requirement is added later.
## Decisions
- New permission codes:
- `iot_card:authorize_enterprise` for “授权卡给企业”.
- `iot_card:recall_enterprise_authorization` for “回收卡授权”.
- `devices:authorize_enterprise` for “授权设备给企业”.
- `devices:recall_enterprise_authorization` for “撤销设备授权”.
- Authorization and recall dialogs will require a target/source enterprise ID, except recall may prefill or lock it when `authorized_enterprise_id` is present in current filters or a single selected-row enterprise ID can be safely inferred from list response data.
- List-mode operations use selected rows. Card operations submit `iccids`; device operations submit `device_nos` derived from available list response identifiers such as `virtual_no`.
- Filter-mode operations submit only non-empty filter fields supported by `docs/授权、回收.md`.
- The `shop_id` filter in “授权卡给企业” and “授权设备给企业” dialogs uses the same lazy `ElCascader` tree pattern as the add-shop dialogs “上级店铺” field: `ShopService.getShopsCascade({ parent_id })`, `checkStrictly: true`, and submit the last selected path node as `shop_id`.
- Old hidden pages are deleted rather than kept as redirects because the list-page authorization workflow replaces “企业卡管理” and “企业设备列表”.
## Risks
- The list response field names for enterprise authorization metadata may vary. Implementation should read current API response types and use returned fields when available; if a required source enterprise cannot be inferred, require explicit enterprise selection in the recall dialog.
- Existing `add-enterprise-device-authorization` OpenSpec change overlaps conceptually but proposes a separate enterprise-device page. This change supersedes that approach for the current product direction.

View File

@@ -0,0 +1,38 @@
# Change: Move Enterprise Asset Authorization To Asset Lists
## Why
企业卡授权和设备授权当前从企业客户详情入口进入独立页面操作路径长且与资产实际筛选、导出、批量操作割裂。IoT 卡管理和设备管理列表需要直接支持按企业授权状态过滤,并在列表页完成企业授权和授权回收。
## What Changes
- IoT 卡管理新增 `authorized_enterprise_id``is_authorized_to_enterprise` 查询过滤。
- IoT 卡管理在导出按钮左侧新增“授权卡给企业”和“回收卡授权”按钮,并为两个按钮添加独立权限编码。
- 设备管理新增 `authorized_enterprise_id``is_authorized_to_enterprise` 查询过滤。
- 设备管理在导出按钮左侧新增“授权设备给企业”和“撤销设备授权”按钮,并为两个按钮添加独立权限编码。
- 新列表按钮复用 `docs/授权、回收.md` 中的授权/回收接口,支持列表选择和筛选条件批量操作。
- 企业客户管理移除“卡授权”和“设备授权”操作入口。
- 删除已被替代的“企业卡管理”和“企业设备列表”两个旧页面,以及对应隐藏路由和路由别名。
## Impact
- Affected specs: `iot-card-management`, `device-management`, `enterprise-customer-management`
- Affected code:
- `src/views/asset-management/iot-card-management/index.vue`
- `src/views/asset-management/device-list/index.vue`
- `src/views/account-management/enterprise-customer/index.vue`
- `src/api/modules/enterprise.ts`
- `src/types/api/card.ts`
- `src/types/api/device.ts`
- `src/types/api/enterpriseCard.ts`
- `src/types/api/enterpriseDevice.ts`
- `src/router/routesAlias.ts`
- `src/router/routes/asyncRoutes.ts`
- `src/views/asset-management/enterprise-cards/index.vue`(删除)
- `src/views/asset-management/enterprise-devices/index.vue`(删除)
## Dependencies
- Backend list APIs accept `authorized_enterprise_id` and `is_authorized_to_enterprise` as query parameters.
- Backend authorization APIs are described in `docs/授权、回收.md`.
- Existing enterprise list/search API can provide target enterprise options for the new dialogs.

View File

@@ -0,0 +1,83 @@
## ADDED Requirements
### Requirement: Device Enterprise Authorization Filters
The device management page SHALL support filtering devices by effective enterprise authorization.
#### Scenario: Filter devices by authorized enterprise ID
- **GIVEN** 用户在设备管理搜索栏选择或输入企业 ID
- **WHEN** 用户执行搜索
- **THEN** 系统 MUST send `authorized_enterprise_id` to the device list API
- **AND** the list MUST only show devices currently authorized to that enterprise according to the backend response
#### Scenario: Filter devices by enterprise authorization status
- **GIVEN** 用户在设备管理搜索栏选择企业授权状态
- **WHEN** 用户选择“已授权”并搜索
- **THEN** 系统 MUST send `is_authorized_to_enterprise=true`
- **WHEN** 用户选择“未授权”并搜索
- **THEN** 系统 MUST send `is_authorized_to_enterprise=false`
- **AND** clearing the filter MUST omit `is_authorized_to_enterprise` from the request
### Requirement: Device Enterprise Authorization Actions
The device management page SHALL provide enterprise device authorization actions directly in the table header before the export button.
#### Scenario: Show permission-controlled device authorization buttons
- **GIVEN** 用户访问设备管理页面
- **WHEN** the table header renders
- **THEN** the “授权设备给企业” button MUST appear to the left of the “导出” button only when the user has permission `devices:authorize_enterprise`
- **AND** the “撤销设备授权” button MUST appear to the left of the “导出” button only when the user has permission `devices:recall_enterprise_authorization`
#### Scenario: Authorize selected devices to an enterprise
- **GIVEN** 用户在设备管理列表选中了设备
- **WHEN** 用户点击“授权设备给企业”并选择目标企业
- **AND** 用户选择 `list` 选取方式
- **THEN** 系统 MUST call `POST /api/admin/enterprises/{id}/allocate-devices`
- **AND** the request body MUST include `selection_type: 'list'`
- **AND** the request body MUST include selected row device identifiers as `device_nos`
- **AND** identifiers MUST come from list response fields such as `virtual_no` when available
- **AND** the UI MUST display success count, fail count, authorized devices, and failed item reasons from the response
- **AND** the device list MUST refresh after any successful authorization
#### Scenario: Authorize devices by filters
- **GIVEN** 用户打开“授权设备给企业” dialog
- **WHEN** 用户选择 `filter` 选取方式并确认
- **THEN** 系统 MUST call `POST /api/admin/enterprises/{id}/allocate-devices`
- **AND** the request body MUST include `selection_type: 'filter'`
- **AND** filter fields MUST be populated from the dialog filter form or current list filters when applicable, including available values such as `batch_no`, `shop_id`, and `virtual_no`
- **AND** empty filter fields MUST be omitted
#### Scenario: Authorize devices filter selects shop from cascade tree
- **GIVEN** 用户在“授权设备给企业” dialog 中选择 `filter` 选取方式
- **WHEN** the shop filter field renders
- **THEN** the field MUST use the same lazy cascade tree pattern as the “上级店铺” field in the add-shop dialog
- **AND** it MUST load children through `ShopService.getShopsCascade({ parent_id })`
- **AND** it MUST allow selecting any level with `checkStrictly: true`
- **AND** when submitted, the request MUST use the last selected cascade node ID as `shop_id`
- **AND** clearing the cascade field MUST omit `shop_id`
#### Scenario: Recall selected device authorization
- **GIVEN** 用户在设备管理列表选中了 currently enterprise-authorized devices
- **WHEN** 用户点击“撤销设备授权”
- **THEN** the UI MUST require a source enterprise ID unless it can safely infer one from `authorized_enterprise_id` or selected row data returned by the list API
- **WHEN** 用户 confirms `list` recall
- **THEN** 系统 MUST call `POST /api/admin/enterprises/{id}/recall-devices`
- **AND** the request body MUST include `selection_type: 'list'` and selected row device identifiers as `device_nos`
- **AND** the UI MUST display success count, fail count, and failed item reasons
- **AND** the device list MUST refresh after any successful recall
#### Scenario: Recall device authorization by filters
- **GIVEN** 用户打开“撤销设备授权” dialog
- **WHEN** 用户选择 `filter` selection mode and confirms
- **THEN** 系统 MUST call `POST /api/admin/enterprises/{id}/recall-devices`
- **AND** the request body MUST include `selection_type: 'filter'`
- **AND** filter fields MUST follow the contract in `docs/授权、回收.md`, including available values such as `batch_no` and `virtual_no`
- **AND** empty filter fields MUST be omitted

View File

@@ -0,0 +1,22 @@
## ADDED Requirements
### Requirement: Enterprise Customer Asset Authorization Entry Removal
The enterprise customer management page SHALL NOT provide enterprise card or device authorization entry points, and the replaced “企业卡管理” and “企业设备列表” pages SHALL be removed because these operations move to the IoT card management and device management list pages.
#### Scenario: Enterprise customer action column no longer shows asset authorization
- **GIVEN** 用户访问企业客户管理页面
- **WHEN** the action column renders for an enterprise customer row
- **THEN** the UI MUST NOT show “卡授权”
- **AND** the UI MUST NOT show “设备授权”
#### Scenario: Replaced enterprise asset authorization pages are removed
- **GIVEN** the application routes are registered
- **WHEN** route configuration is loaded
- **THEN** `/asset-management/enterprise-cards` MUST NOT be registered
- **AND** `/asset-management/enterprise-devices` MUST NOT be registered
- **AND** route aliases for `EnterpriseCards` and `EnterpriseDevices` MUST NOT remain
- **AND** `src/views/asset-management/enterprise-cards/index.vue` MUST be deleted
- **AND** `src/views/asset-management/enterprise-devices/index.vue` MUST be deleted

View File

@@ -0,0 +1,88 @@
## ADDED Requirements
### Requirement: IoT Card Enterprise Authorization Filters
The IoT card management page SHALL support filtering standalone cards by effective enterprise authorization.
#### Scenario: Filter cards by authorized enterprise ID
- **GIVEN** 用户在 IoT 卡管理搜索栏选择或输入企业 ID
- **WHEN** 用户执行搜索
- **THEN** 系统 MUST send `authorized_enterprise_id` to the IoT card list API
- **AND** the list MUST only show cards currently authorized to that enterprise according to the backend response
#### Scenario: Filter cards by enterprise authorization status
- **GIVEN** 用户在 IoT 卡管理搜索栏选择企业授权状态
- **WHEN** 用户选择“已授权”并搜索
- **THEN** 系统 MUST send `is_authorized_to_enterprise=true`
- **WHEN** 用户选择“未授权”并搜索
- **THEN** 系统 MUST send `is_authorized_to_enterprise=false`
- **AND** clearing the filter MUST omit `is_authorized_to_enterprise` from the request
### Requirement: IoT Card Enterprise Authorization Actions
The IoT card management page SHALL provide enterprise card authorization actions directly in the table header before the export button.
#### Scenario: Show permission-controlled card authorization buttons
- **GIVEN** 用户访问 IoT 卡管理页面
- **WHEN** the table header renders
- **THEN** the “授权卡给企业” button MUST appear to the left of the “导出” button only when the user has permission `iot_card:authorize_enterprise`
- **AND** the “回收卡授权” button MUST appear to the left of the “导出” button only when the user has permission `iot_card:recall_enterprise_authorization`
#### Scenario: Authorize selected cards to an enterprise
- **GIVEN** 用户在 IoT 卡管理列表选中了卡
- **WHEN** 用户点击“授权卡给企业”并选择目标企业
- **AND** 用户选择 `list` 选取方式
- **THEN** 系统 MUST call `POST /api/admin/enterprises/{id}/allocate-cards`
- **AND** the request body MUST include `selection_type: 'list'`
- **AND** the request body MUST include selected row ICCIDs as `iccids`
- **AND** the UI MUST display success count, fail count, and failed item reasons from the response
- **AND** the card list MUST refresh after any successful authorization
#### Scenario: Authorize cards by range
- **GIVEN** 用户打开“授权卡给企业” dialog
- **WHEN** 用户选择 `range` 选取方式并填写起始和结束 ICCID
- **THEN** 系统 MUST call `POST /api/admin/enterprises/{id}/allocate-cards`
- **AND** the request body MUST include `selection_type: 'range'`, `iccid_start`, `iccid_end`, and optional `remark`
#### Scenario: Authorize cards by filters
- **GIVEN** 用户打开“授权卡给企业” dialog
- **WHEN** 用户选择 `filter` 选取方式并确认
- **THEN** 系统 MUST call `POST /api/admin/enterprises/{id}/allocate-cards`
- **AND** the request body MUST include `selection_type: 'filter'`
- **AND** filter fields MUST be populated from the dialog filter form or current list filters when applicable, including available values such as `carrier_id`, `shop_id`, `shop_ids`, `batch_no`, and `iccid`
- **AND** empty filter fields MUST be omitted
#### Scenario: Authorize cards filter selects shop from cascade tree
- **GIVEN** 用户在“授权卡给企业” dialog 中选择 `filter` 选取方式
- **WHEN** the shop filter field renders
- **THEN** the field MUST use the same lazy cascade tree pattern as the “上级店铺” field in the add-shop dialog
- **AND** it MUST load children through `ShopService.getShopsCascade({ parent_id })`
- **AND** it MUST allow selecting any level with `checkStrictly: true`
- **AND** when submitted, the request MUST use the last selected cascade node ID as `shop_id`
- **AND** clearing the cascade field MUST omit `shop_id`
#### Scenario: Recall selected card authorization
- **GIVEN** 用户在 IoT 卡管理列表选中了 currently enterprise-authorized cards
- **WHEN** 用户点击“回收卡授权”
- **THEN** the UI MUST require a source enterprise ID unless it can safely infer one from `authorized_enterprise_id` or selected row data returned by the list API
- **WHEN** 用户 confirms `list` recall
- **THEN** 系统 MUST call `POST /api/admin/enterprises/{id}/recall-cards`
- **AND** the request body MUST include `selection_type: 'list'` and selected row ICCIDs as `iccids`
- **AND** the UI MUST display success count, fail count, failed item reasons, and any `recalled_devices`
- **AND** the card list MUST refresh after any successful recall
#### Scenario: Recall card authorization by range or filters
- **GIVEN** 用户打开“回收卡授权” dialog
- **WHEN** 用户选择 `range` or `filter` selection mode and confirms
- **THEN** 系统 MUST call `POST /api/admin/enterprises/{id}/recall-cards`
- **AND** the request body MUST match the selected mode using the contract in `docs/授权、回收.md`
- **AND** empty filter fields MUST be omitted

View File

@@ -0,0 +1,46 @@
## 1. API And Types
- [x] 1.1 Extend IoT card list query params with `authorized_enterprise_id?: number` and `is_authorized_to_enterprise?: boolean | null`.
- [x] 1.2 Extend device list query params with `authorized_enterprise_id?: number` and `is_authorized_to_enterprise?: boolean | null`.
- [x] 1.3 Align enterprise card authorization request types with `selection_type: 'list' | 'range' | 'filter'`, list/range/filter fields, and optional `remark`.
- [x] 1.4 Align enterprise device authorization request types with `selection_type: 'list' | 'filter'`, list/filter fields, and optional `remark`.
- [x] 1.5 Keep using `EnterpriseService.allocateCards`, `recallCards`, `allocateDevices`, and `recallDevices`; update method types only if needed.
## 2. IoT Card Management
- [x] 2.1 Add enterprise authorization filters to the IoT card search form and request payload.
- [x] 2.2 Add “授权卡给企业” before the export button with permission `iot_card:authorize_enterprise`.
- [x] 2.3 Add “回收卡授权” before the export button with permission `iot_card:recall_enterprise_authorization`.
- [x] 2.4 Implement card authorization dialog with target enterprise selection, selection mode, list/range/filter inputs, optional remark, validation, submit, loading state, and result display.
- [x] 2.4.1 Update the card authorization dialog shop filter to use the same lazy shop cascade tree as the add-shop dialog “上级店铺” field, and submit the last selected node as `shop_id`.
- [x] 2.5 Implement card recall dialog with target/source enterprise resolution, selection mode, list/range/filter inputs, optional remark, validation, submit, loading state, and result display.
- [x] 2.6 Refresh the IoT card list after successful authorization or recall.
## 3. Device Management
- [x] 3.1 Add enterprise authorization filters to the device search form and request payload.
- [x] 3.2 Add “授权设备给企业” before the export button with permission `devices:authorize_enterprise`.
- [x] 3.3 Add “撤销设备授权” before the export button with permission `devices:recall_enterprise_authorization`.
- [x] 3.4 Implement device authorization dialog with target enterprise selection, selection mode, list/filter inputs, optional remark, validation, submit, loading state, and result display.
- [x] 3.4.1 Update the device authorization dialog shop filter to use the same lazy shop cascade tree as the add-shop dialog “上级店铺” field, and submit the last selected node as `shop_id`.
- [x] 3.5 Implement device recall dialog with target/source enterprise resolution, selection mode, list/filter inputs, validation, submit, loading state, and result display.
- [x] 3.6 Refresh the device list after successful authorization or recall.
## 4. Remove Replaced Enterprise Authorization Pages
- [x] 4.1 Remove “卡授权” and “设备授权” operations from the enterprise customer action column.
- [x] 4.2 Remove navigation logic to enterprise card/device authorization pages.
- [x] 4.3 Remove hidden routes for `/asset-management/enterprise-cards` and `/asset-management/enterprise-devices`.
- [x] 4.4 Remove `RoutesAlias.EnterpriseCards` and `RoutesAlias.EnterpriseDevices`.
- [x] 4.5 Delete the replaced “企业卡管理” page file `src/views/asset-management/enterprise-cards/index.vue`.
- [x] 4.6 Delete the replaced “企业设备列表” page file `src/views/asset-management/enterprise-devices/index.vue`.
- [x] 4.7 Verify no imports, route aliases, menu entries, or navigation calls reference the deleted pages.
## 5. Validation
- [x] 5.1 Verify IoT card list filters send correct query parameters and reset correctly.
- [x] 5.2 Verify device list filters send correct query parameters and reset correctly.
- [x] 5.3 Verify all four new buttons obey their permission codes.
- [x] 5.4 Verify list/range/filter authorization and recall flows against API contracts from `docs/授权、回收.md`.
- [x] 5.5 Run targeted ESLint for modified files.
- [ ] 5.6 Run `openspec validate update-enterprise-asset-authorization-entry --strict` when the OpenSpec CLI is available.