From 06cde977ad8382e44a2a884778af098e94fb4969 Mon Sep 17 00:00:00 2001 From: sexygoat <1538832180@qq.com> Date: Mon, 2 Feb 2026 17:08:49 +0800 Subject: [PATCH] =?UTF-8?q?fetch(modify):=E4=BF=AE=E5=A4=8D=E8=A7=92?= =?UTF-8?q?=E8=89=B2=E5=88=86=E9=85=8D=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../proposal.md | 32 + .../specs/device-api/spec.md | 100 +++ .../specs/iot-card-api/spec.md | 100 +++ .../tasks.md | 34 + src/api/modules/card.ts | 2 +- src/api/modules/device.ts | 2 +- src/router/utils/registerRoutes.ts | 11 +- src/types/api/card.ts | 4 +- src/types/api/device.ts | 4 +- src/types/components.d.ts | 1 + .../asset-management/device-list/index.vue | 2 +- .../iot-card-management/index.vue | 2 +- .../order-management/order-list/index.vue | 94 ++- src/views/system/role/index.vue | 668 +++++++++++------- 14 files changed, 789 insertions(+), 267 deletions(-) create mode 100644 openspec/changes/rename-series-allocation-id-to-series-id/proposal.md create mode 100644 openspec/changes/rename-series-allocation-id-to-series-id/specs/device-api/spec.md create mode 100644 openspec/changes/rename-series-allocation-id-to-series-id/specs/iot-card-api/spec.md create mode 100644 openspec/changes/rename-series-allocation-id-to-series-id/tasks.md diff --git a/openspec/changes/rename-series-allocation-id-to-series-id/proposal.md b/openspec/changes/rename-series-allocation-id-to-series-id/proposal.md new file mode 100644 index 0000000..748d6f1 --- /dev/null +++ b/openspec/changes/rename-series-allocation-id-to-series-id/proposal.md @@ -0,0 +1,32 @@ +# Change: 重命名 API 字段 series_allocation_id 为 series_id + +## Why + +后端 API 字段命名不一致。前端表单使用 `series_id`(套餐系列ID),但 API 参数仍使用 `series_allocation_id`(套餐系列分配ID)。这导致命名混淆且不符合实际业务含义(现在直接指向套餐系列 ID,而非分配记录 ID)。 + +## What Changes + +- **BREAKING**: 重命名 4 个 API 端点的请求/响应字段 + - `PATCH /api/admin/iot-cards/series-binding` - 请求参数 `series_allocation_id` → `series_id` + - `PATCH /api/admin/devices/series-binding` - 请求参数 `series_allocation_id` → `series_id` + - `GET /api/admin/iot-cards/standalone` - 查询参数和响应字段 `series_allocation_id` → `series_id` + - `GET /api/admin/devices` - 查询参数和响应字段 `series_allocation_id` → `series_id` + +- 前端同步修改: + - TypeScript 类型定义 + - API 方法参数 + - 页面组件调用代码 + - 移除临时注释 + +## Impact + +- **Affected specs**: iot-card-api, device-api +- **Affected code**: + - `src/types/api/device.ts` (BatchSetDeviceSeriesBindingRequest) + - `src/types/api/card.ts` (BatchSetCardSeriesBindingRequest) + - `src/api/modules/device.ts` (batchSetDeviceSeriesBinding) + - `src/api/modules/card.ts` (batchSetCardSeriesBinding) + - `src/views/asset-management/device-list/index.vue` + - `src/views/asset-management/iot-card-management/index.vue` +- **Breaking change**: 需要后端先部署更新,前端再部署 +- **Migration**: 更新所有使用这些字段的 API 调用 diff --git a/openspec/changes/rename-series-allocation-id-to-series-id/specs/device-api/spec.md b/openspec/changes/rename-series-allocation-id-to-series-id/specs/device-api/spec.md new file mode 100644 index 0000000..accc2f3 --- /dev/null +++ b/openspec/changes/rename-series-allocation-id-to-series-id/specs/device-api/spec.md @@ -0,0 +1,100 @@ +# Device API Specification Delta + +## ADDED Requirements + +### Requirement: Device Series ID Query Parameter + +The system SHALL support filtering devices by package series ID in query parameters. + +**Query Parameter**: `series_id` (number, optional) + +#### Scenario: Filter devices by series + +- **WHEN** admin queries devices with series_id parameter +- **THEN** system returns only devices associated with that package series +- **AND** returns empty list if no matches found + +#### Scenario: Query without series filter + +- **WHEN** admin queries devices without series_id parameter +- **THEN** system returns all devices matching other filter criteria +- **AND** series association is not considered + +### Requirement: Device Series ID Response Field + +The system SHALL include package series ID in device response data. + +**Response Field**: `series_id` (number | null) +- Value is package series ID if device is bound to a series +- Value is null if device has no series binding + +#### Scenario: Display series association + +- **WHEN** system returns device in list response +- **THEN** each device includes series_id field +- **AND** field shows current series binding or null + +#### Scenario: Null series binding + +- **WHEN** device has no series binding +- **THEN** series_id field is null +- **AND** device can still be displayed in results + +## RENAMED Requirements + +### API Field Renaming + +- FROM: `series_allocation_id` (in batch series binding endpoints) +- TO: `series_id` + +**Reason**: Field name `series_allocation_id` implied a reference to an allocation record ID, but it actually stores the package series ID directly. Renaming to `series_id` clarifies this relationship and aligns with frontend naming conventions. + +## MODIFIED Requirements + +### Requirement: Batch Set Device Series Binding + +The system SHALL provide an endpoint to batch set package series binding for devices. + +**Endpoint**: `PATCH /api/admin/devices/series-binding` + +**Request Body**: +```json +{ + "device_ids": [1, 2, 3], + "series_id": 123 // Changed from series_allocation_id +} +``` + +**Field Definitions**: +- `device_ids`: Array of device IDs (max 500 items) +- `series_id`: Package series ID (tb_package_series.id), 0 means clear association + +**Response**: +```json +{ + "code": 0, + "data": { + "success_count": 3, + "fail_count": 0, + "failed_items": null + } +} +``` + +#### Scenario: Successful batch series binding + +- **WHEN** admin calls batch series binding API with valid device IDs and series_id +- **THEN** system updates series association for all valid devices +- **AND** returns success count and failure details + +#### Scenario: Clear series association + +- **WHEN** admin calls batch series binding API with series_id = 0 +- **THEN** system clears series association for specified devices +- **AND** returns success confirmation + +#### Scenario: Partial failure handling + +- **WHEN** some devices in the batch cannot be updated +- **THEN** system processes valid devices successfully +- **AND** returns failed_items list with reasons for failures diff --git a/openspec/changes/rename-series-allocation-id-to-series-id/specs/iot-card-api/spec.md b/openspec/changes/rename-series-allocation-id-to-series-id/specs/iot-card-api/spec.md new file mode 100644 index 0000000..ebe381a --- /dev/null +++ b/openspec/changes/rename-series-allocation-id-to-series-id/specs/iot-card-api/spec.md @@ -0,0 +1,100 @@ +# IoT Card API Specification Delta + +## ADDED Requirements + +### Requirement: IoT Card Series ID Query Parameter + +The system SHALL support filtering standalone IoT cards by package series ID in query parameters. + +**Query Parameter**: `series_id` (number, optional) + +#### Scenario: Filter cards by series + +- **WHEN** admin queries standalone cards with series_id parameter +- **THEN** system returns only cards associated with that package series +- **AND** returns empty list if no matches found + +#### Scenario: Query without series filter + +- **WHEN** admin queries cards without series_id parameter +- **THEN** system returns all cards matching other filter criteria +- **AND** series association is not considered + +### Requirement: IoT Card Series ID Response Field + +The system SHALL include package series ID in standalone IoT card response data. + +**Response Field**: `series_id` (number | null) +- Value is package series ID if card is bound to a series +- Value is null if card has no series binding + +#### Scenario: Display series association + +- **WHEN** system returns IoT card in list response +- **THEN** each card includes series_id field +- **AND** field shows current series binding or null + +#### Scenario: Null series binding + +- **WHEN** card has no series binding +- **THEN** series_id field is null +- **AND** card can still be displayed in results + +## RENAMED Requirements + +### API Field Renaming + +- FROM: `series_allocation_id` (in batch series binding endpoints) +- TO: `series_id` + +**Reason**: Field name `series_allocation_id` implied a reference to an allocation record ID, but it actually stores the package series ID directly. Renaming to `series_id` clarifies this relationship and aligns with frontend naming conventions. + +## MODIFIED Requirements + +### Requirement: Batch Set Card Series Binding + +The system SHALL provide an endpoint to batch set package series binding for IoT cards. + +**Endpoint**: `PATCH /api/admin/iot-cards/series-binding` + +**Request Body**: +```json +{ + "iccids": ["898600..."], + "series_id": 123 // Changed from series_allocation_id +} +``` + +**Field Definitions**: +- `iccids`: Array of ICCIDs (max 500 items) +- `series_id`: Package series ID (tb_package_series.id), 0 means clear association + +**Response**: +```json +{ + "code": 0, + "data": { + "success_count": 100, + "fail_count": 0, + "failed_items": null + } +} +``` + +#### Scenario: Successful batch series binding + +- **WHEN** admin calls batch series binding API with valid ICCIDs and series_id +- **THEN** system updates series association for all valid cards +- **AND** returns success count and failure details + +#### Scenario: Clear series association + +- **WHEN** admin calls batch series binding API with series_id = 0 +- **THEN** system clears series association for specified cards +- **AND** returns success confirmation + +#### Scenario: Partial failure handling + +- **WHEN** some cards in the batch cannot be updated +- **THEN** system processes valid cards successfully +- **AND** returns failed_items list with reasons for failures diff --git a/openspec/changes/rename-series-allocation-id-to-series-id/tasks.md b/openspec/changes/rename-series-allocation-id-to-series-id/tasks.md new file mode 100644 index 0000000..94f47f4 --- /dev/null +++ b/openspec/changes/rename-series-allocation-id-to-series-id/tasks.md @@ -0,0 +1,34 @@ +# Implementation Tasks + +## 1. 类型定义更新 + +### 1.1 批量设置接口参数重命名 +- [x] 1.1.1 更新 `src/types/api/device.ts` 中 BatchSetDeviceSeriesBindingRequest 接口,将 `series_allocation_id` 改为 `series_id` +- [x] 1.1.2 更新 `src/types/api/card.ts` 中 BatchSetCardSeriesBindingRequest 接口,将 `series_allocation_id` 改为 `series_id` + +### 1.2 查询参数新增字段 +- [x] 1.2.1 在 `src/types/api/device.ts` 的 DeviceQueryParams 接口中添加 `series_id?: number` 查询参数 +- [x] 1.2.2 在 `src/types/api/card.ts` 的 StandaloneCardQueryParams 接口中添加 `series_id?: number` 查询参数 + +### 1.3 响应类型新增字段 +- [x] 1.3.1 在 `src/types/api/device.ts` 的 Device 接口中添加 `series_id?: number | null` 响应字段(在 updated_at 后面) +- [x] 1.3.2 在 `src/types/api/card.ts` 的 StandaloneIotCard 接口中添加 `series_id?: number | null` 响应字段(在 updated_at 后面) + +## 2. API 方法更新 + +- [x] 2.1 更新 `src/api/modules/device.ts` 中 batchSetDeviceSeriesBinding 方法参数,将 `series_allocation_id` 改为 `series_id` +- [x] 2.2 更新 `src/api/modules/card.ts` 中 batchSetCardSeriesBinding 方法参数,将 `series_allocation_id` 改为 `series_id` + +## 3. 页面组件更新 + +- [x] 3.1 更新 `src/views/asset-management/device-list/index.vue` 中调用 batchSetDeviceSeriesBinding 的代码,将参数名从 `series_allocation_id` 改为 `series_id` +- [x] 3.2 移除 device-list/index.vue 中第 1191 行的临时注释 +- [x] 3.3 更新 `src/views/asset-management/iot-card-management/index.vue` 中调用 batchSetCardSeriesBinding 的代码,将参数名从 `series_allocation_id` 改为 `series_id` +- [x] 3.4 移除 iot-card-management/index.vue 中第 1326 行的临时注释 + +## 4. 验证 + +- [x] 4.1 运行 TypeScript 类型检查确认无类型错误 +- [ ] 4.2 本地测试批量设置 IoT 卡系列绑定功能 +- [ ] 4.3 本地测试批量设置设备系列绑定功能 +- [ ] 4.4 确认所有相关功能正常工作 diff --git a/src/api/modules/card.ts b/src/api/modules/card.ts index 12e2b63..4c8aea9 100644 --- a/src/api/modules/card.ts +++ b/src/api/modules/card.ts @@ -363,7 +363,7 @@ export class CardService extends BaseService { */ static batchSetCardSeriesBinding(data: { iccids: string[] - series_allocation_id: number + series_id: number }): Promise> { return this.patch('/api/admin/iot-cards/series-binding', data) } diff --git a/src/api/modules/device.ts b/src/api/modules/device.ts index af6818f..8b95f84 100644 --- a/src/api/modules/device.ts +++ b/src/api/modules/device.ts @@ -153,7 +153,7 @@ export class DeviceService extends BaseService { */ static batchSetDeviceSeriesBinding(data: { device_ids: number[] - series_allocation_id: number + series_id: number }): Promise> { return this.patch('/api/admin/devices/series-binding', data) } diff --git a/src/router/utils/registerRoutes.ts b/src/router/utils/registerRoutes.ts index a6c9195..c5b07b6 100644 --- a/src/router/utils/registerRoutes.ts +++ b/src/router/utils/registerRoutes.ts @@ -173,7 +173,11 @@ function convertRouteComponent( // 基础路由配置 const converted: ConvertedRoute = { ...routeConfig, - component: undefined + component: undefined, + meta: { + ...routeConfig.meta, + dynamic: true // 标记为动态路由,用于退出时清理 + } } // 是否为一级菜单 @@ -229,7 +233,10 @@ function handleLayoutRoute( path: route.path, name: route.name, component: loadComponent(component as string, String(route.name)), - meta: route.meta + meta: { + ...route.meta, + dynamic: true // 标记为动态路由,用于退出时清理 + } } ] } diff --git a/src/types/api/card.ts b/src/types/api/card.ts index e4fa7a8..0546eaa 100644 --- a/src/types/api/card.ts +++ b/src/types/api/card.ts @@ -318,6 +318,7 @@ export interface StandaloneCardQueryParams extends PaginationParams { is_replaced?: boolean // 是否有换卡记录 iccid_start?: string // ICCID起始号 iccid_end?: string // ICCID结束号 + series_id?: number // 套餐系列ID } // 单卡信息 @@ -347,6 +348,7 @@ export interface StandaloneIotCard { activated_at?: string | null // 激活时间 (可选) created_at: string // 创建时间 updated_at: string // 更新时间 + series_id?: number | null // 套餐系列ID } // ========== 单卡批量分配和回收相关 ========== @@ -488,7 +490,7 @@ export interface IotCardDetailResponse { // 批量设置卡的套餐系列绑定请求参数 export interface BatchSetCardSeriesBindingRequest { iccids: string[] // ICCID列表(最多500个) - series_allocation_id: number // 套餐系列分配ID(0表示清除关联) + series_id: number // 套餐系列ID(0表示清除关联) } // 卡套餐系列绑定失败项 diff --git a/src/types/api/device.ts b/src/types/api/device.ts index 0fc31e2..40d20fa 100644 --- a/src/types/api/device.ts +++ b/src/types/api/device.ts @@ -34,6 +34,7 @@ export interface Device { activated_at: string | null // 激活时间 created_at: string // 创建时间 updated_at: string // 更新时间 + series_id?: number | null // 套餐系列ID } // 设备查询参数 @@ -47,6 +48,7 @@ export interface DeviceQueryParams extends PaginationParams { manufacturer?: string // 制造商(模糊查询) created_at_start?: string // 创建时间起始 created_at_end?: string // 创建时间结束 + series_id?: number // 套餐系列ID } // 设备列表响应 @@ -207,7 +209,7 @@ export interface DeviceImportTaskDetail extends DeviceImportTask { // 批量设置设备的套餐系列绑定请求参数 export interface BatchSetDeviceSeriesBindingRequest { device_ids: number[] // 设备ID列表(最多500个) - series_allocation_id: number // 套餐系列分配ID(0表示清除关联) + series_id: number // 套餐系列ID(0表示清除关联) } // 设备套餐系列绑定失败项 diff --git a/src/types/components.d.ts b/src/types/components.d.ts index d16a15d..5e84da7 100644 --- a/src/types/components.d.ts +++ b/src/types/components.d.ts @@ -131,6 +131,7 @@ declare module 'vue' { ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTimePicker: typeof import('element-plus/es')['ElTimePicker'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] + ElTransfer: typeof import('element-plus/es')['ElTransfer'] ElTree: typeof import('element-plus/es')['ElTree'] ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] diff --git a/src/views/asset-management/device-list/index.vue b/src/views/asset-management/device-list/index.vue index 60835cf..9546fc7 100644 --- a/src/views/asset-management/device-list/index.vue +++ b/src/views/asset-management/device-list/index.vue @@ -1188,7 +1188,7 @@ try { const data = { device_ids: selectedDevices.value.map((d) => d.id), - series_allocation_id: seriesBindingForm.series_id! // 注意:API参数名仍是series_allocation_id,但前端使用series_id + series_id: seriesBindingForm.series_id! } const res = await DeviceService.batchSetDeviceSeriesBinding(data) if (res.code === 0) { diff --git a/src/views/asset-management/iot-card-management/index.vue b/src/views/asset-management/iot-card-management/index.vue index eaf1434..9a915b4 100644 --- a/src/views/asset-management/iot-card-management/index.vue +++ b/src/views/asset-management/iot-card-management/index.vue @@ -1323,7 +1323,7 @@ try { const res = await CardService.batchSetCardSeriesBinding({ iccids, - series_allocation_id: seriesBindingForm.series_id! // 注意:API参数名仍是series_allocation_id,但前端使用series_id + series_id: seriesBindingForm.series_id! }) if (res.code === 0) { diff --git a/src/views/order-management/order-list/index.vue b/src/views/order-management/order-list/index.vue index 4a2854d..19efcb5 100644 --- a/src/views/order-management/order-list/index.vue +++ b/src/views/order-management/order-list/index.vue @@ -63,10 +63,27 @@ v-model="createForm.package_ids" :placeholder="t('orderManagement.createForm.packageIdsPlaceholder')" multiple + filterable + remote + reserve-keyword + :remote-method="searchPackages" + :loading="packageSearchLoading" + clearable style="width: 100%" > - - + +
+ {{ pkg.package_name }} + + ¥{{ (pkg.price / 100).toFixed(2) }} + +
+
import { h } from 'vue' import { useI18n } from 'vue-i18n' - import { OrderService, CardService, DeviceService } from '@/api/modules' + import { OrderService, CardService, DeviceService, PackageManageService } from '@/api/modules' import { ElMessage, ElMessageBox, ElTag } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus' import type { @@ -253,7 +270,8 @@ OrderPaymentMethod, OrderCommissionStatus, StandaloneIotCard, - Device + Device, + PackageResponse } from '@/types/api' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' @@ -384,6 +402,10 @@ const orderList = ref([]) + // 套餐搜索相关 + const packageOptions = ref([]) + const packageSearchLoading = ref(false) + // IoT卡搜索相关 const iotCardOptions = ref([]) const cardSearchLoading = ref(false) @@ -392,17 +414,34 @@ const deviceOptions = ref([]) const deviceSearchLoading = ref(false) + // 搜索套餐(根据套餐名称) + const searchPackages = async (query: string) => { + packageSearchLoading.value = true + try { + const res = await PackageManageService.getPackages({ + package_name: query || undefined, + page: 1, + page_size: 20, + status: 1, // 只获取启用的套餐 + shelf_status: 1 // 只获取已上架的套餐 + }) + if (res.code === 0) { + packageOptions.value = res.data.items || [] + } + } catch (error) { + console.error('Search packages failed:', error) + packageOptions.value = [] + } finally { + packageSearchLoading.value = false + } + } + // 搜索IoT卡(根据ICCID) const searchIotCards = async (query: string) => { - if (!query) { - iotCardOptions.value = [] - return - } - cardSearchLoading.value = true try { const res = await CardService.getStandaloneIotCards({ - iccid: query, + iccid: query || undefined, page: 1, page_size: 20 }) @@ -419,15 +458,10 @@ // 搜索设备(根据设备号device_no) const searchDevices = async (query: string) => { - if (!query) { - deviceOptions.value = [] - return - } - deviceSearchLoading.value = true try { const res = await DeviceService.getDevices({ - device_no: query, + device_no: query || undefined, page: 1, page_size: 20 }) @@ -656,8 +690,29 @@ // 显示创建订单对话框 const showCreateDialog = async () => { createDialogVisible.value = true - // 默认加载20条IoT卡和设备数据 - await Promise.all([loadDefaultIotCards(), loadDefaultDevices()]) + // 默认加载20条套餐、IoT卡和设备数据 + await Promise.all([loadDefaultPackages(), loadDefaultIotCards(), loadDefaultDevices()]) + } + + // 加载默认套餐列表 + const loadDefaultPackages = async () => { + packageSearchLoading.value = true + try { + const res = await PackageManageService.getPackages({ + page: 1, + page_size: 20, + status: 1, // 只获取启用的套餐 + shelf_status: 1 // 只获取已上架的套餐 + }) + if (res.code === 0) { + packageOptions.value = res.data.items || [] + } + } catch (error) { + console.error('Load default packages failed:', error) + packageOptions.value = [] + } finally { + packageSearchLoading.value = false + } } // 加载默认IoT卡列表 @@ -709,7 +764,8 @@ createForm.iot_card_id = null createForm.device_id = null - // 清空IoT卡和设备搜索结果 + // 清空套餐、IoT卡和设备搜索结果 + packageOptions.value = [] iotCardOptions.value = [] deviceOptions.value = [] } diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue index 61f3dd5..d2dc77c 100644 --- a/src/views/system/role/index.vue +++ b/src/views/system/role/index.vue @@ -88,66 +88,114 @@ - -
- -
-
可分配权限
- - - + + +
+ +
+
+ 可分配权限 + +
+
+ + + +
- -
-
已分配权限
-
-
- - {{ perm.perm_type === 1 ? '菜单' : '按钮' }} - - {{ perm.perm_name }} - - 移除 - -
- +
+ + 添加 + +
+ + +
+
+ 已分配权限 +
+
+ + + +