From d3b5f55c64b774fe994519c06cbbd3f138426dda Mon Sep 17 00:00:00 2001 From: luo Date: Mon, 22 Jun 2026 10:36:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=9B=B4=E6=8D=A2=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E6=8E=88=E6=9D=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../design.md | 34 + .../proposal.md | 38 + .../specs/device-management/spec.md | 83 + .../enterprise-customer-management/spec.md | 22 + .../specs/iot-card-management/spec.md | 88 ++ .../tasks.md | 46 + src/api/modules/enterprise.ts | 20 +- .../business/CreateRefundDialog.vue | 152 +- .../business/PaymentVoucherDialog.vue | 182 ++- src/components/business/VoucherUpload.vue | 404 +++++ src/router/routes/asyncRoutes.ts | 22 - src/router/routesAlias.ts | 2 - src/types/api/card.ts | 5 + src/types/api/commission.ts | 1 + src/types/api/device.ts | 4 + src/types/api/enterpriseCard.ts | 36 +- src/types/api/enterpriseDevice.ts | 19 +- src/types/api/refund.ts | 1 + src/types/components.d.ts | 1 + .../enterprise-customer/index.vue | 64 +- .../components/PackageListCard.vue | 8 +- .../components/WalletTransactionCard.vue | 17 +- .../dialogs/PackageRechargeDialog.vue | 154 +- .../asset-management/device-list/index.vue | 543 ++++++- .../enterprise-cards/index.vue | 1351 ----------------- .../enterprise-devices/index.vue | 1079 ------------- .../iot-card-management/index.vue | 793 +++++++++- .../agent-fund-overview/index.vue | 137 +- src/views/finance/agent-recharge/detail.vue | 23 +- src/views/finance/agent-recharge/index.vue | 163 +- src/views/finance/refund/detail.vue | 84 +- src/views/finance/refund/index.vue | 18 +- .../order-management/order-list/detail.vue | 73 +- .../order-management/order-list/index.vue | 146 +- 34 files changed, 2478 insertions(+), 3335 deletions(-) create mode 100644 openspec/changes/update-enterprise-asset-authorization-entry/design.md create mode 100644 openspec/changes/update-enterprise-asset-authorization-entry/proposal.md create mode 100644 openspec/changes/update-enterprise-asset-authorization-entry/specs/device-management/spec.md create mode 100644 openspec/changes/update-enterprise-asset-authorization-entry/specs/enterprise-customer-management/spec.md create mode 100644 openspec/changes/update-enterprise-asset-authorization-entry/specs/iot-card-management/spec.md create mode 100644 openspec/changes/update-enterprise-asset-authorization-entry/tasks.md create mode 100644 src/components/business/VoucherUpload.vue delete mode 100644 src/views/asset-management/enterprise-cards/index.vue delete mode 100644 src/views/asset-management/enterprise-devices/index.vue diff --git a/openspec/changes/update-enterprise-asset-authorization-entry/design.md b/openspec/changes/update-enterprise-asset-authorization-entry/design.md new file mode 100644 index 0000000..8838c1f --- /dev/null +++ b/openspec/changes/update-enterprise-asset-authorization-entry/design.md @@ -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 dialog’s “上级店铺” 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. diff --git a/openspec/changes/update-enterprise-asset-authorization-entry/proposal.md b/openspec/changes/update-enterprise-asset-authorization-entry/proposal.md new file mode 100644 index 0000000..0b476db --- /dev/null +++ b/openspec/changes/update-enterprise-asset-authorization-entry/proposal.md @@ -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. diff --git a/openspec/changes/update-enterprise-asset-authorization-entry/specs/device-management/spec.md b/openspec/changes/update-enterprise-asset-authorization-entry/specs/device-management/spec.md new file mode 100644 index 0000000..3b71d36 --- /dev/null +++ b/openspec/changes/update-enterprise-asset-authorization-entry/specs/device-management/spec.md @@ -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 diff --git a/openspec/changes/update-enterprise-asset-authorization-entry/specs/enterprise-customer-management/spec.md b/openspec/changes/update-enterprise-asset-authorization-entry/specs/enterprise-customer-management/spec.md new file mode 100644 index 0000000..cb78057 --- /dev/null +++ b/openspec/changes/update-enterprise-asset-authorization-entry/specs/enterprise-customer-management/spec.md @@ -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 diff --git a/openspec/changes/update-enterprise-asset-authorization-entry/specs/iot-card-management/spec.md b/openspec/changes/update-enterprise-asset-authorization-entry/specs/iot-card-management/spec.md new file mode 100644 index 0000000..7f86f1f --- /dev/null +++ b/openspec/changes/update-enterprise-asset-authorization-entry/specs/iot-card-management/spec.md @@ -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 diff --git a/openspec/changes/update-enterprise-asset-authorization-entry/tasks.md b/openspec/changes/update-enterprise-asset-authorization-entry/tasks.md new file mode 100644 index 0000000..7087b9b --- /dev/null +++ b/openspec/changes/update-enterprise-asset-authorization-entry/tasks.md @@ -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. diff --git a/src/api/modules/enterprise.ts b/src/api/modules/enterprise.ts index ab59b98..688d68a 100644 --- a/src/api/modules/enterprise.ts +++ b/src/api/modules/enterprise.ts @@ -27,10 +27,10 @@ import type { import type { EnterpriseDeviceListParams, EnterpriseDevicePageResult, - AllocateDevicesRequest, - AllocateDevicesResponse, - RecallDevicesRequest, - RecallDevicesResponse + EnterpriseAllocateDevicesRequest, + EnterpriseAllocateDevicesResponse, + EnterpriseRecallDevicesRequest, + EnterpriseRecallDevicesResponse } from '@/types/api/enterpriseDevice' export class EnterpriseService extends BaseService { @@ -171,9 +171,9 @@ export class EnterpriseService extends BaseService { */ static allocateDevices( enterpriseId: number, - data: AllocateDevicesRequest - ): Promise> { - return this.post>( + data: EnterpriseAllocateDevicesRequest + ): Promise> { + return this.post>( `/api/admin/enterprises/${enterpriseId}/allocate-devices`, data ) @@ -201,9 +201,9 @@ export class EnterpriseService extends BaseService { */ static recallDevices( enterpriseId: number, - data: RecallDevicesRequest - ): Promise> { - return this.post>( + data: EnterpriseRecallDevicesRequest + ): Promise> { + return this.post>( `/api/admin/enterprises/${enterpriseId}/recall-devices`, data ) diff --git a/src/components/business/CreateRefundDialog.vue b/src/components/business/CreateRefundDialog.vue index e676daa..1f4acfb 100644 --- a/src/components/business/CreateRefundDialog.vue +++ b/src/components/business/CreateRefundDialog.vue @@ -47,23 +47,13 @@ /> - - -
将退款凭证拖到此处,或点击上传
- -
+ v-model="formData.refund_voucher_key" + voucher-name="退款凭证" + @uploading-change="voucherUploading = $event" + @change="formRef?.validateField('refund_voucher_key')" + />
@@ -83,13 +73,13 @@ - - diff --git a/src/components/business/PaymentVoucherDialog.vue b/src/components/business/PaymentVoucherDialog.vue index d20c9ef..7340b39 100644 --- a/src/components/business/PaymentVoucherDialog.vue +++ b/src/components/business/PaymentVoucherDialog.vue @@ -1,22 +1,47 @@ + + diff --git a/src/components/business/VoucherUpload.vue b/src/components/business/VoucherUpload.vue new file mode 100644 index 0000000..ed7b882 --- /dev/null +++ b/src/components/business/VoucherUpload.vue @@ -0,0 +1,404 @@ + + + + + diff --git a/src/router/routes/asyncRoutes.ts b/src/router/routes/asyncRoutes.ts index 439b27a..b461af4 100644 --- a/src/router/routes/asyncRoutes.ts +++ b/src/router/routes/asyncRoutes.ts @@ -301,28 +301,6 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, - // 企业设备管理(设备授权) - { - path: 'enterprise-devices', - name: 'EnterpriseDevices', - component: RoutesAlias.EnterpriseDevices, - meta: { - title: 'menus.assetManagement.enterpriseDevices', - isHide: true, - keepAlive: false - } - }, - // 企业卡授权 - { - path: 'enterprise-cards', - name: 'EnterpriseCards', - component: RoutesAlias.EnterpriseCards, - meta: { - title: 'menus.accountManagement.enterpriseCards', - isHide: true, - keepAlive: false - } - }, // 换货管理 { path: 'exchange-management', diff --git a/src/router/routesAlias.ts b/src/router/routesAlias.ts index 48ff440..c78af17 100644 --- a/src/router/routesAlias.ts +++ b/src/router/routesAlias.ts @@ -40,8 +40,6 @@ export enum RoutesAlias { // 账号管理 Account = '/account-management/account', // 账号列表 EnterpriseCustomer = '/account-management/enterprise-customer', // 企业客户管理 - EnterpriseCards = '/asset-management/enterprise-cards', // 企业卡授权 - EnterpriseDevices = '/asset-management/enterprise-devices', // 企业设备授权 // 资产管理 AssetInformation = '/asset-management/asset-information', // 资产信息 diff --git a/src/types/api/card.ts b/src/types/api/card.ts index eb4581e..ca2ec03 100644 --- a/src/types/api/card.ts +++ b/src/types/api/card.ts @@ -347,6 +347,9 @@ export interface StandaloneCardQueryParams extends PaginationParams { carrier_name?: string // carrier name filter is_standalone?: boolean // standalone filter status?: StandaloneCardStatus // 状态 + network_status?: number | null // 网络状态 (0:停机, 1:开机) + authorized_enterprise_id?: number // 有效授权企业ID + is_authorized_to_enterprise?: boolean | null // 是否已授权给企业 carrier_id?: number // 运营商ID shop_id?: number // 分销商ID iccid?: string // ICCID(模糊查询) @@ -404,6 +407,8 @@ export interface StandaloneIotCard { series_id: number | null // 套餐系列ID series_name: string // 套餐系列名称 realname_policy: string // 实名制策略 + authorized_enterprise_id?: number | null // 当前有效授权企业ID + authorized_enterprise_name?: string | null // 当前有效授权企业名称 } // ========== 单卡批量分配和回收相关 ========== diff --git a/src/types/api/commission.ts b/src/types/api/commission.ts index a66db87..478bdf8 100644 --- a/src/types/api/commission.ts +++ b/src/types/api/commission.ts @@ -310,6 +310,7 @@ export interface MainWalletTransactionQueryParams extends PaginationParams { transaction_type?: MainWalletTransactionType // 交易类型过滤 start_date?: string // 开始日期 YYYY-MM-DD end_date?: string // 结束日期 YYYY-MM-DD + asset_identifier?: string // 资产标识精确检索(ICCID 或设备虚拟号) } /** diff --git a/src/types/api/device.ts b/src/types/api/device.ts index 084f3eb..09b6607 100644 --- a/src/types/api/device.ts +++ b/src/types/api/device.ts @@ -65,6 +65,8 @@ export interface Device { imei?: string // IMEI(设备国际移动设备识别码) sn?: string // 设备序列号 realname_policy?: string // 实名认证策略 + authorized_enterprise_id?: number | null // 当前有效授权企业ID + authorized_enterprise_name?: string | null // 当前有效授权企业名称 } // 设备查询参数 @@ -81,6 +83,8 @@ export interface DeviceQueryParams extends PaginationParams { created_at_end?: string // 创建时间结束 series_id?: number // 套餐系列ID keyword?: string // 关键词搜索(虚拟号或IMEI) + authorized_enterprise_id?: number // 有效授权企业ID + is_authorized_to_enterprise?: boolean | null // 是否已授权给企业 } // 设备列表响应 diff --git a/src/types/api/enterpriseCard.ts b/src/types/api/enterpriseCard.ts index e11124b..4fffc10 100644 --- a/src/types/api/enterpriseCard.ts +++ b/src/types/api/enterpriseCard.ts @@ -30,8 +30,26 @@ export interface AllocatedDevice { * 授权卡给企业请求 */ export interface AllocateCardsRequest { + /** 卡片选取方式 */ + selection_type?: 'list' | 'range' | 'filter' /** 需要授权的 ICCID 列表 */ - iccids: string[] + iccids?: string[] + /** 起始 ICCID */ + iccid_start?: string + /** 结束 ICCID */ + iccid_end?: string + /** 运营商ID */ + carrier_id?: number + /** 单个店铺ID */ + shop_id?: number + /** 多个店铺ID */ + shop_ids?: number[] + /** 批次号 */ + batch_no?: string + /** ICCID模糊匹配 */ + iccid?: string + /** 授权备注 */ + remark?: string /** 确认整体授权设备下所有卡 */ confirm_device_bundles?: boolean } @@ -212,8 +230,22 @@ export interface RecalledDevice { * 回收卡授权请求 */ export interface RecallCardsRequest { + /** 卡片选取方式 */ + selection_type?: 'list' | 'range' | 'filter' /** 需要回收授权的 ICCID 列表 */ - iccids: string[] + iccids?: string[] + /** 起始 ICCID */ + iccid_start?: string + /** 结束 ICCID */ + iccid_end?: string + /** 运营商ID */ + carrier_id?: number + /** 批次号 */ + batch_no?: string + /** ICCID模糊匹配 */ + iccid?: string + /** 回收备注 */ + remark?: string } /** diff --git a/src/types/api/enterpriseDevice.ts b/src/types/api/enterpriseDevice.ts index 42c5953..892e4c8 100644 --- a/src/types/api/enterpriseDevice.ts +++ b/src/types/api/enterpriseDevice.ts @@ -38,8 +38,12 @@ export interface EnterpriseDevicePageResult { /** * 授权设备请求参数 */ -export interface AllocateDevicesRequest { - device_nos: string[] | null // 设备号列表(最多100个) +export interface EnterpriseAllocateDevicesRequest { + selection_type?: 'list' | 'filter' // 设备选取方式 + device_nos?: string[] | null // 设备号列表(最多100个) + batch_no?: string // 批次号 + shop_id?: number // 店铺ID + virtual_no?: string // 设备虚拟号模糊匹配 remark?: string // 授权备注 } @@ -63,7 +67,7 @@ export interface FailedDeviceItem { /** * 授权设备响应 */ -export interface AllocateDevicesResponse { +export interface EnterpriseAllocateDevicesResponse { success_count: number // 成功数量 fail_count: number // 失败数量 authorized_devices: AuthorizedDeviceItem[] | null // 已授权设备列表 @@ -75,14 +79,17 @@ export interface AllocateDevicesResponse { /** * 撤销设备授权请求参数 */ -export interface RecallDevicesRequest { - device_nos: string[] | null // 设备号列表(最多100个) +export interface EnterpriseRecallDevicesRequest { + selection_type?: 'list' | 'filter' // 设备选取方式 + device_nos?: string[] | null // 设备号列表(最多100个) + batch_no?: string // 批次号 + virtual_no?: string // 设备虚拟号模糊匹配 } /** * 撤销设备授权响应 */ -export interface RecallDevicesResponse { +export interface EnterpriseRecallDevicesResponse { success_count: number // 成功数量 fail_count: number // 失败数量 failed_items: FailedDeviceItem[] | null // 失败项列表 diff --git a/src/types/api/refund.ts b/src/types/api/refund.ts index a91d44a..1a1e37d 100644 --- a/src/types/api/refund.ts +++ b/src/types/api/refund.ts @@ -47,6 +47,7 @@ export interface RefundQueryParams { order_id?: number shop_id?: number shop_name?: string // 店铺名称(模糊查询) + asset_identifier?: string // 资产标识(ICCID 或 设备虚拟号,精确匹配) dateRange?: string[] } diff --git a/src/types/components.d.ts b/src/types/components.d.ts index a68621e..bcc764c 100644 --- a/src/types/components.d.ts +++ b/src/types/components.d.ts @@ -155,6 +155,7 @@ declare module 'vue' { TableContextMenuHint: typeof import('./../components/core/others/TableContextMenuHint.vue')['default'] ThemeSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/ThemeSettings.vue')['default'] UpdateRealnameStatusDialog: typeof import('./../components/business/UpdateRealnameStatusDialog.vue')['default'] + VoucherUpload: typeof import('./../components/business/VoucherUpload.vue')['default'] } export interface ComponentCustomProperties { vLoading: typeof import('element-plus/es')['ElLoadingDirective'] diff --git a/src/views/account-management/enterprise-customer/index.vue b/src/views/account-management/enterprise-customer/index.vue index 8ac4af5..37e4864 100644 --- a/src/views/account-management/enterprise-customer/index.vue +++ b/src/views/account-management/enterprise-customer/index.vue @@ -206,9 +206,9 @@ import { useRouter } from 'vue-router' import { RoutesAlias } from '@/router/routesAlias' import { EnterpriseService, ShopService } from '@/api/modules' - import { ElMessage, ElSwitch, ElCascader, ElTreeSelect } from 'element-plus' + import { ElMessage, ElSwitch, ElCascader } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus' - import type { EnterpriseItem, ShopResponse } from '@/types/api' + import type { EnterpriseItem } from '@/types/api' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' import { useAuth } from '@/composables/useAuth' @@ -236,8 +236,6 @@ const shopLoading = ref(false) const tableRef = ref() const currentEnterpriseId = ref(0) - const shopTreeData = ref([]) - // 店铺级联选择相关 const shopCascadeOptions = ref([]) const shopCascadeProps = { @@ -499,22 +497,6 @@ }) } - if (hasAuth('enterprise_customer:card_authorization')) { - actions.push({ - label: '卡授权', - handler: () => manageCards(row), - type: 'primary' - }) - } - - if (hasAuth('enterprise_customer:device_authorization')) { - actions.push({ - label: '设备授权', - handler: () => manageDevices(row), - type: 'primary' - }) - } - if (hasAuth('enterprise_customer:edit')) { actions.push({ label: '编辑', @@ -582,33 +564,6 @@ } } - // 构建树形数据 - const buildTreeData = (items: ShopResponse[]) => { - const map = new Map() - const tree: ShopResponse[] = [] - - // 先将所有项放入 map - items.forEach((item) => { - map.set(item.id, { ...item, children: [] }) - }) - - // 构建树形结构 - items.forEach((item) => { - const node = map.get(item.id)! - if (item.parent_id && map.has(item.parent_id)) { - // 有父节点,添加到父节点的 children 中 - const parent = map.get(item.parent_id)! - if (!parent.children) parent.children = [] - parent.children.push(node) - } else { - // 没有父节点或父节点不存在,作为根节点 - tree.push(node) - } - }) - - return tree - } - // 获取企业客户列表 const getTableData = async () => { loading.value = true @@ -855,21 +810,6 @@ }) } - // 卡管理 - const manageCards = (row: EnterpriseItem) => { - router.push({ - path: RoutesAlias.EnterpriseCards, - query: { id: row.id } - }) - } - - const manageDevices = (row: EnterpriseItem) => { - router.push({ - path: RoutesAlias.EnterpriseDevices, - query: { id: row.id } - }) - } - // 企业客户操作菜单项配置 diff --git a/src/views/asset-management/asset-information/components/PackageListCard.vue b/src/views/asset-management/asset-information/components/PackageListCard.vue index a273eab..accf674 100644 --- a/src/views/asset-management/asset-information/components/PackageListCard.vue +++ b/src/views/asset-management/asset-information/components/PackageListCard.vue @@ -297,8 +297,8 @@ v-model="usedDataForm.data_usage" :min="0" :max="usedDataMaxValue" - :precision="usedDataForm.unit === 'MB' ? 0 : 3" - :step="usedDataForm.unit === 'MB' ? 1 : 0.1" + :precision="3" + :step="0.1" class="used-data-input__number" placeholder="请输入新的套餐真实已用量" /> @@ -447,8 +447,8 @@ return } - if (dataUsageMb === undefined || dataUsageMb < 0 || !Number.isInteger(dataUsageMb)) { - callback(new Error('请输入可换算为非负整数 MB 的已用量')) + if (dataUsageMb === undefined || dataUsageMb < 0) { + callback(new Error('请输入可换算为非负数字 MB 的已用量')) return } diff --git a/src/views/asset-management/asset-information/components/WalletTransactionCard.vue b/src/views/asset-management/asset-information/components/WalletTransactionCard.vue index 078c7bc..c513476 100644 --- a/src/views/asset-management/asset-information/components/WalletTransactionCard.vue +++ b/src/views/asset-management/asset-information/components/WalletTransactionCard.vue @@ -44,7 +44,6 @@ /> 查询 重置 - 共 {{ total }} 条 @@ -66,6 +65,14 @@
+ + + - - - @@ -460,34 +374,4 @@ gap: 12px; justify-content: flex-end; } - - .payment-voucher-upload { - width: 100%; - - :deep(.el-upload) { - width: 100%; - } - - :deep(.el-upload-dragger) { - width: 100%; - padding: 24px 16px; - border-radius: 8px; - } - - &__icon { - margin-bottom: 12px; - font-size: 28px; - color: var(--el-color-primary); - } - - &__text { - font-size: 14px; - color: var(--el-text-color-regular); - - em { - font-style: normal; - color: var(--el-color-primary); - } - } - } diff --git a/src/views/asset-management/device-list/index.vue b/src/views/asset-management/device-list/index.vue index 6fed81a..33d9df3 100644 --- a/src/views/asset-management/device-list/index.vue +++ b/src/views/asset-management/device-list/index.vue @@ -5,7 +5,6 @@ @@ -41,6 +40,22 @@ > 批量设置套餐系列 + + 授权设备给企业 + + + 撤销设备授权 + 导出 @@ -677,6 +692,250 @@ :identifier="operationLogsIdentifier" download-permission="device:download_log_file" /> + + + + + + + + + + 设备号列表 + 筛选条件 + + + +
已选择 {{ selectedDevices.length }} 台设备
+
+ + + + +
+ +
+ + + + + + + + + + 设备号列表 + 筛选条件 + + + +
已选择 {{ selectedDevices.length }} 台设备
+
+ +
+ +
+ + + + {{ enterpriseDeviceOperationResult.success_count }} + + + {{ enterpriseDeviceOperationResult.fail_count }} + + +
+ 失败项详情 + + + + +
+
+ 已授权设备 + + + + +
+ +
{ + try { + const res = await ShopService.getShopsCascade({ parent_id: undefined }) + if (res.code === 0) { + shopCascadeOptions.value = (res.data || []).map((item: any) => ({ + value: item.id, + label: item.shop_name, + leaf: !item.has_children + })) + } + } catch (error) { + console.error('加载顶级店铺失败:', error) + } + } const seriesBindingShopCascadeOptions = ref([]) const seriesBindingShopCascadeProps = { lazy: true, @@ -815,6 +1093,27 @@ } const allocateResult = ref(null) const recallResult = ref(null) + const enterpriseDeviceAuthorizeDialogVisible = ref(false) + const enterpriseDeviceRecallDialogVisible = ref(false) + const enterpriseDeviceResultDialogVisible = ref(false) + const enterpriseDeviceAuthorizeLoading = ref(false) + const enterpriseDeviceRecallLoading = ref(false) + const enterpriseDeviceResultTitle = ref('') + const enterpriseDeviceAuthorizeFormRef = ref() + const enterpriseDeviceRecallFormRef = ref() + const enterpriseOptions = ref([]) + const enterpriseLoading = ref(false) + const enterpriseDeviceOperationResult = ref< + Partial & { + success_count: number + fail_count: number + } + >({ + success_count: 0, + fail_count: 0, + failed_items: null, + authorized_devices: null + }) // 套餐系列绑定相关 const seriesBindingDialogVisible = ref(false) @@ -840,6 +1139,22 @@ created_at_end: '' }) const seriesBindingForm = reactive(createInitialSeriesBindingState()) + const createInitialEnterpriseDeviceAuthorizeState = () => ({ + enterprise_id: undefined as number | undefined, + selection_type: DeviceSelectionType.LIST, + virtual_no: '', + batch_no: '', + shop_id_path: undefined as number[] | undefined, + remark: '' + }) + const createInitialEnterpriseDeviceRecallState = () => ({ + enterprise_id: undefined as number | undefined, + selection_type: DeviceSelectionType.LIST, + virtual_no: '', + batch_no: '' + }) + const enterpriseDeviceAuthorizeForm = reactive(createInitialEnterpriseDeviceAuthorizeState()) + const enterpriseDeviceRecallForm = reactive(createInitialEnterpriseDeviceRecallState()) const parseNumericStringToBigInt = (value: string): bigint | null => { const normalizedValue = value.trim() if (!normalizedValue || !/^\d+$/.test(normalizedValue)) { @@ -931,6 +1246,13 @@ } ] }) + const createEnterpriseDeviceRules = () => + reactive({ + enterprise_id: [{ required: true, message: '请选择企业', trigger: 'change' }], + selection_type: [{ required: true, message: '请选择选设备方式', trigger: 'change' }] + }) + const enterpriseDeviceAuthorizeRules = createEnterpriseDeviceRules() + const enterpriseDeviceRecallRules = createEnterpriseDeviceRules() const seriesBindingResult = ref(null) // 搜索表单店铺/系列选项 @@ -999,13 +1321,15 @@ shop_id: undefined as number | undefined, series_id: undefined as number | undefined, has_active_package: undefined as boolean | undefined, + authorized_enterprise_id: undefined as number | undefined, + is_authorized_to_enterprise: undefined as boolean | undefined, dateRange: [] as string[], created_at_start: '', created_at_end: '' } // 搜索表单 - const searchForm = reactive({ ...initialSearchState }) + const searchForm = reactive>({ ...initialSearchState }) // 搜索批次号(用于搜索表单) const loadSearchBatchNoOptions = async (batchNo?: string) => { @@ -1174,7 +1498,7 @@ })) }, { - label: '起始至结束', + label: '起止时间', prop: 'dateRange', type: 'date', config: { @@ -1189,7 +1513,7 @@ // 分页 searchFormItems.splice(searchFormItems.length - 1, 0, { - label: '是否有生效套餐', + label: '有效套餐', prop: 'has_active_package', type: 'select', config: { @@ -1201,6 +1525,37 @@ { label: '否', value: false } ] }) + searchFormItems.splice(searchFormItems.length - 1, 0, { + label: '授权企业', + prop: 'authorized_enterprise_id', + type: 'select', + config: { + clearable: true, + filterable: true, + remote: true, + remoteMethod: (query: string) => handleSearchEnterprise(query), + loading: enterpriseLoading.value, + placeholder: '请选择或搜索企业' + }, + options: () => + enterpriseOptions.value.map((enterprise) => ({ + label: enterprise.enterprise_name, + value: enterprise.id + })) + }) + searchFormItems.splice(searchFormItems.length - 1, 0, { + label: '授权状态', + prop: 'is_authorized_to_enterprise', + type: 'select', + config: { + clearable: true, + placeholder: '全部' + }, + options: () => [ + { label: '已授权', value: true }, + { label: '未授权', value: false } + ] + }) const pagination = reactive({ page: 1, @@ -1704,6 +2059,26 @@ } } + const handleSearchEnterprise = async (query?: string) => { + try { + enterpriseLoading.value = true + const params = { + page: 1, + page_size: 20, + enterprise_name: query || undefined, + status: 1 + } + const res = await EnterpriseService.getEnterprises(params) + if (res.code === 0) { + enterpriseOptions.value = res.data.items || [] + } + } catch (error) { + console.error('搜索企业失败:', error) + } finally { + enterpriseLoading.value = false + } + } + let isFirstActivation = true onMounted(() => { getTableData() @@ -1735,6 +2110,8 @@ shop_id: searchForm.shop_id || undefined, series_id: searchForm.series_id || undefined, has_active_package: searchForm.has_active_package ?? undefined, + authorized_enterprise_id: searchForm.authorized_enterprise_id || undefined, + is_authorized_to_enterprise: searchForm.is_authorized_to_enterprise ?? undefined, created_at_start: searchForm.created_at_start || undefined, created_at_end: searchForm.created_at_end || undefined } @@ -1763,6 +2140,8 @@ shop_id: searchForm.shop_id || undefined, series_id: searchForm.series_id || undefined, has_active_package: searchForm.has_active_package ?? undefined, + authorized_enterprise_id: searchForm.authorized_enterprise_id || undefined, + is_authorized_to_enterprise: searchForm.is_authorized_to_enterprise ?? undefined, created_at_start: searchForm.created_at_start || undefined, created_at_end: searchForm.created_at_end || undefined } @@ -1993,6 +2372,160 @@ recallForm.remark = '' } + const getInferredDeviceEnterpriseId = () => { + const filterEnterpriseId = searchForm.authorized_enterprise_id + if (filterEnterpriseId) return filterEnterpriseId + + const selectedEnterpriseIds = Array.from( + new Set( + selectedDevices.value + .map((device) => device.authorized_enterprise_id) + .filter((id): id is number => typeof id === 'number') + ) + ) + + return selectedEnterpriseIds.length === 1 ? selectedEnterpriseIds[0] : undefined + } + + const resetEnterpriseDeviceAuthorizeForm = () => { + Object.assign(enterpriseDeviceAuthorizeForm, createInitialEnterpriseDeviceAuthorizeState(), { + selection_type: + selectedDevices.value.length > 0 ? DeviceSelectionType.LIST : DeviceSelectionType.FILTER, + virtual_no: searchForm.virtual_no || '', + batch_no: searchForm.batch_no || '', + shop_id_path: undefined + }) + } + + const resetEnterpriseDeviceRecallForm = () => { + Object.assign(enterpriseDeviceRecallForm, createInitialEnterpriseDeviceRecallState(), { + enterprise_id: getInferredDeviceEnterpriseId(), + selection_type: + selectedDevices.value.length > 0 ? DeviceSelectionType.LIST : DeviceSelectionType.FILTER, + virtual_no: searchForm.virtual_no || '', + batch_no: searchForm.batch_no || '' + }) + } + + const showEnterpriseDeviceAuthorizeDialog = async () => { + resetEnterpriseDeviceAuthorizeForm() + await Promise.all([ + handleSearchEnterprise(), + loadSearchBatchNoOptions(), + loadTopLevelShopCascadeOptions() + ]) + enterpriseDeviceAuthorizeDialogVisible.value = true + enterpriseDeviceAuthorizeFormRef.value?.clearValidate() + } + + const showEnterpriseDeviceRecallDialog = async () => { + resetEnterpriseDeviceRecallForm() + await Promise.all([handleSearchEnterprise(), loadSearchBatchNoOptions()]) + enterpriseDeviceRecallDialogVisible.value = true + enterpriseDeviceRecallFormRef.value?.clearValidate() + } + + const handleEnterpriseDeviceAuthorizeDialogClose = () => { + Object.assign(enterpriseDeviceAuthorizeForm, createInitialEnterpriseDeviceAuthorizeState()) + enterpriseDeviceAuthorizeFormRef.value?.resetFields() + } + + const handleEnterpriseDeviceRecallDialogClose = () => { + Object.assign(enterpriseDeviceRecallForm, createInitialEnterpriseDeviceRecallState()) + enterpriseDeviceRecallFormRef.value?.resetFields() + } + + const buildEnterpriseDeviceParams = (form: any) => { + const params: Record = { + selection_type: form.selection_type, + remark: form.remark || undefined + } + + if (form.selection_type === DeviceSelectionType.LIST) { + const deviceNos = selectedDevices.value.map((device) => device.virtual_no).filter(Boolean) + if (!deviceNos.length) { + ElMessage.warning('列表模式下请先选择设备') + return null + } + params.device_nos = deviceNos + } else { + if (form.batch_no) params.batch_no = form.batch_no + if (Array.isArray(form.shop_id_path) && form.shop_id_path.length > 0) { + params.shop_id = form.shop_id_path[form.shop_id_path.length - 1] + } + if (form.virtual_no) params.virtual_no = form.virtual_no + } + + Object.keys(params).forEach((key) => { + if (params[key] === undefined || params[key] === '') delete params[key] + }) + + return params + } + + const applyEnterpriseDeviceOperationSuccess = async ( + title: string, + result: EnterpriseAllocateDevicesResponse | EnterpriseRecallDevicesResponse + ) => { + enterpriseDeviceOperationResult.value = result + enterpriseDeviceResultTitle.value = title + enterpriseDeviceResultDialogVisible.value = true + selectedDevices.value = [] + await getTableData() + } + + const handleEnterpriseDeviceAuthorize = async () => { + if (!enterpriseDeviceAuthorizeFormRef.value) return + + await enterpriseDeviceAuthorizeFormRef.value.validate(async (valid) => { + if (!valid) return + const params = buildEnterpriseDeviceParams(enterpriseDeviceAuthorizeForm) + if (!params) return + + enterpriseDeviceAuthorizeLoading.value = true + try { + const res = await EnterpriseService.allocateDevices( + enterpriseDeviceAuthorizeForm.enterprise_id!, + params as any + ) + if (res.code === 0) { + enterpriseDeviceAuthorizeDialogVisible.value = false + await applyEnterpriseDeviceOperationSuccess('授权设备给企业结果', res.data) + } + } catch (error) { + console.error('授权设备给企业失败:', error) + } finally { + enterpriseDeviceAuthorizeLoading.value = false + } + }) + } + + const handleEnterpriseDeviceRecall = async () => { + if (!enterpriseDeviceRecallFormRef.value) return + + await enterpriseDeviceRecallFormRef.value.validate(async (valid) => { + if (!valid) return + const params = buildEnterpriseDeviceParams(enterpriseDeviceRecallForm) + if (!params) return + + enterpriseDeviceRecallLoading.value = true + try { + const res = await EnterpriseService.recallDevices( + enterpriseDeviceRecallForm.enterprise_id!, + params as any + ) + if (res.code === 0) { + enterpriseDeviceRecallDialogVisible.value = false + await applyEnterpriseDeviceOperationSuccess('撤销设备授权结果', res.data) + } + } catch (error) { + console.error('撤销设备授权失败:', error) + } finally { + enterpriseDeviceRecallLoading.value = false + } + }) + } + // 批量设置套餐系列 const handleBatchSetSeries = async () => { seriesBindingResult.value = null diff --git a/src/views/asset-management/enterprise-cards/index.vue b/src/views/asset-management/enterprise-cards/index.vue deleted file mode 100644 index 1a933fe..0000000 --- a/src/views/asset-management/enterprise-cards/index.vue +++ /dev/null @@ -1,1351 +0,0 @@ - - - - - diff --git a/src/views/asset-management/enterprise-devices/index.vue b/src/views/asset-management/enterprise-devices/index.vue deleted file mode 100644 index eb8f400..0000000 --- a/src/views/asset-management/enterprise-devices/index.vue +++ /dev/null @@ -1,1079 +0,0 @@ - - - - - diff --git a/src/views/asset-management/iot-card-management/index.vue b/src/views/asset-management/iot-card-management/index.vue index 86b0f6e..0fcf643 100644 --- a/src/views/asset-management/iot-card-management/index.vue +++ b/src/views/asset-management/iot-card-management/index.vue @@ -5,7 +5,7 @@ @@ -42,6 +42,22 @@ > 批量设置套餐系列 + + 授权卡给企业 + + + 回收卡授权 + 导出 @@ -357,6 +373,350 @@ + + + + + + + + + + + ICCID列表 + 号段范围 + 筛选条件 + + + +
已选择 {{ selectedCards.length }} 张卡
+
+ + + + + + +
+ 共授权{{ enterpriseCardAuthorizeRangeCountText }}张卡 +
+ + + + +
+ +
+ + + + + + + + + + + + ICCID列表 + 号段范围 + 筛选条件 + + + +
已选择 {{ selectedCards.length }} 张卡
+
+ + + + + + +
+ 共回收{{ enterpriseCardRecallRangeCountText }}张卡 +
+ + + + +
+ +
+ + + + + + {{ enterpriseCardOperationResult.success_count }} + + + {{ enterpriseCardOperationResult.fail_count }} + + +
+ 失败项详情 + + + + +
+
+ 连带回收设备 + + + + + + + +
+ +
+ () + const enterpriseCardRecallFormRef = ref() + const enterpriseOptions = ref([]) + const enterpriseLoading = ref(false) + const enterpriseCardOperationResult = ref< + Partial & { + success_count: number + fail_count: number + } + >({ + success_count: 0, + fail_count: 0, + failed_items: null, + recalled_devices: null + }) // 操作审计日志弹窗 const operationLogsDialogVisible = ref(false) @@ -727,6 +1111,29 @@ batch_no: '' }) const seriesBindingForm = reactive(createInitialSeriesBindingState()) + const createInitialEnterpriseCardAuthorizeState = () => ({ + enterprise_id: undefined as number | undefined, + selection_type: CardSelectionType.LIST, + iccid_start: '', + iccid_end: '', + carrier_id: undefined as number | undefined, + shop_id_path: undefined as number[] | undefined, + batch_no: '', + iccid: '', + remark: '' + }) + const createInitialEnterpriseCardRecallState = () => ({ + enterprise_id: undefined as number | undefined, + selection_type: CardSelectionType.LIST, + iccid_start: '', + iccid_end: '', + carrier_id: undefined as number | undefined, + batch_no: '', + iccid: '', + remark: '' + }) + const enterpriseCardAuthorizeForm = reactive(createInitialEnterpriseCardAuthorizeState()) + const enterpriseCardRecallForm = reactive(createInitialEnterpriseCardRecallState()) const parseNumericStringToBigInt = (value: string): bigint | null => { const normalizedValue = value.trim() if (!normalizedValue || !/^\d+$/.test(normalizedValue)) { @@ -761,9 +1168,54 @@ const recallRangeCountText = computed(() => getFormattedRangeCountText(recallForm.iccid_start, recallForm.iccid_end) ) + const enterpriseCardAuthorizeRangeCountText = computed(() => + getFormattedRangeCountText( + enterpriseCardAuthorizeForm.iccid_start, + enterpriseCardAuthorizeForm.iccid_end + ) + ) + const enterpriseCardRecallRangeCountText = computed(() => + getFormattedRangeCountText( + enterpriseCardRecallForm.iccid_start, + enterpriseCardRecallForm.iccid_end + ) + ) const seriesBindingRangeCountText = computed(() => getFormattedRangeCountText(seriesBindingForm.iccid_start, seriesBindingForm.iccid_end) ) + const createEnterpriseCardRules = (form: { selection_type: CardSelectionType }) => + reactive({ + enterprise_id: [{ required: true, message: '请选择企业', trigger: 'change' }], + selection_type: [{ required: true, message: '请选择选卡方式', trigger: 'change' }], + iccid_start: [ + { + required: true, + validator: (_rule, value, callback) => { + if (form.selection_type === CardSelectionType.RANGE && !value) { + callback(new Error('请输入起始ICCID')) + } else { + callback() + } + }, + trigger: 'blur' + } + ], + iccid_end: [ + { + required: true, + validator: (_rule, value, callback) => { + if (form.selection_type === CardSelectionType.RANGE && !value) { + callback(new Error('请输入结束ICCID')) + } else { + callback() + } + }, + trigger: 'blur' + } + ] + }) + const enterpriseCardAuthorizeRules = createEnterpriseCardRules(enterpriseCardAuthorizeForm) + const enterpriseCardRecallRules = createEnterpriseCardRules(enterpriseCardRecallForm) const seriesBindingRules = reactive({ selection_type: [{ required: true, message: '请选择选卡方式', trigger: 'change' }], series_id: [{ required: true, message: '请选择套餐系列', trigger: 'change' }], @@ -921,6 +1373,26 @@ } } + const handleSearchEnterprise = async (query?: string) => { + try { + enterpriseLoading.value = true + const params = { + page: 1, + page_size: 20, + enterprise_name: query || undefined, + status: 1 + } + const res = await EnterpriseService.getEnterprises(params) + if (res.code === 0) { + enterpriseOptions.value = res.data.items || [] + } + } catch (error) { + console.error('搜索企业失败:', error) + } finally { + enterpriseLoading.value = false + } + } + // 搜索套餐系列(用于搜索栏) const handleSearchSeries = async (query: string) => { try { @@ -1043,10 +1515,25 @@ label: 'label', children: 'children' } + const loadTopLevelShopCascadeOptions = async () => { + try { + const res = await ShopService.getShopsCascade({ parent_id: undefined }) + if (res.code === 0) { + shopCascadeOptions.value = (res.data || []).map((item: any) => ({ + value: item.id, + label: item.shop_name, + leaf: !item.has_children + })) + } + } catch (error) { + console.error('加载顶级店铺失败:', error) + } + } // 搜索表单初始值 const initialSearchState: { status: undefined | number + network_status: undefined | number iccid: string carrier_name: string msisdn: string @@ -1057,9 +1544,12 @@ has_active_package: undefined | boolean is_distributed: undefined | number is_standalone: undefined | boolean + authorized_enterprise_id: undefined | number + is_authorized_to_enterprise: undefined | boolean [key: string]: any } = { status: undefined, + network_status: undefined, iccid: '', carrier_name: '', msisdn: '', @@ -1070,6 +1560,8 @@ has_active_package: undefined, is_distributed: undefined, is_standalone: undefined, + authorized_enterprise_id: undefined, + is_authorized_to_enterprise: undefined, is_replaced: undefined, iccid_start: '', iccid_end: '' @@ -1184,7 +1676,7 @@ } }, { - label: '运营商名称', + label: '运营商', prop: 'carrier_name', type: 'select', config: { @@ -1216,6 +1708,19 @@ { label: '已停用', value: 4 } ] }, + { + label: '网络状态', + prop: 'network_status', + type: 'select', + config: { + clearable: true, + placeholder: '全部' + }, + options: () => [ + { label: '停机', value: 0 }, + { label: '正常', value: 1 } + ] + }, { label: '卡虚拟号', prop: 'virtual_no', @@ -1235,7 +1740,7 @@ } }, { - label: '绑定设备虚拟号', + label: '设备虚拟号', prop: 'device_virtual_no', type: 'input', config: { @@ -1279,7 +1784,7 @@ })) }, { - label: '是否已分销', + label: '分销状态', prop: 'is_distributed', type: 'select', config: { @@ -1292,7 +1797,7 @@ ] }, { - label: '是否绑定设备', + label: '是否绑设备', prop: 'is_standalone', type: 'select', config: { @@ -1305,7 +1810,7 @@ ] }, { - label: '是否有换卡记录', + label: '有无换卡', prop: 'is_replaced', type: 'select', config: { @@ -1318,7 +1823,7 @@ ] }, { - label: 'ICCID起始号', + label: '起始号', prop: 'iccid_start', type: 'input', config: { @@ -1327,7 +1832,7 @@ } }, { - label: 'ICCID结束号', + label: '结束号', prop: 'iccid_end', type: 'input', config: { @@ -1339,7 +1844,7 @@ // 搜索表单配置 baseFormItems.splice(8, 0, { - label: '是否有生效套餐', + label: '有效套餐', prop: 'has_active_package', type: 'select', config: { @@ -1351,6 +1856,37 @@ { label: '否', value: false } ] }) + baseFormItems.splice(9, 0, { + label: '授权企业', + prop: 'authorized_enterprise_id', + type: 'select', + config: { + clearable: true, + filterable: true, + remote: true, + remoteMethod: handleSearchEnterprise, + loading: enterpriseLoading.value, + placeholder: '请选择或搜索企业' + }, + options: () => + enterpriseOptions.value.map((enterprise) => ({ + label: enterprise.enterprise_name, + value: enterprise.id + })) + }) + baseFormItems.splice(10, 0, { + label: '授权状态', + prop: 'is_authorized_to_enterprise', + type: 'select', + config: { + clearable: true, + placeholder: '全部' + }, + options: () => [ + { label: '已授权', value: true }, + { label: '未授权', value: false } + ] + }) const formItems: SearchFormItem[] = baseFormItems @@ -1999,6 +2535,175 @@ }) } + const getInferredCardEnterpriseId = () => { + const filterEnterpriseId = formFilters.authorized_enterprise_id + if (filterEnterpriseId) return filterEnterpriseId + + const selectedEnterpriseIds = Array.from( + new Set( + selectedCards.value + .map((card) => card.authorized_enterprise_id) + .filter((id): id is number => typeof id === 'number') + ) + ) + + return selectedEnterpriseIds.length === 1 ? selectedEnterpriseIds[0] : undefined + } + + const resetEnterpriseCardAuthorizeForm = () => { + Object.assign(enterpriseCardAuthorizeForm, createInitialEnterpriseCardAuthorizeState(), { + selection_type: + selectedCards.value.length > 0 ? CardSelectionType.LIST : CardSelectionType.FILTER, + carrier_id: undefined, + shop_id_path: undefined, + batch_no: formFilters.batch_no || '', + iccid: formFilters.iccid || '' + }) + } + + const resetEnterpriseCardRecallForm = () => { + Object.assign(enterpriseCardRecallForm, createInitialEnterpriseCardRecallState(), { + enterprise_id: getInferredCardEnterpriseId(), + selection_type: + selectedCards.value.length > 0 ? CardSelectionType.LIST : CardSelectionType.FILTER, + carrier_id: undefined, + batch_no: formFilters.batch_no || '', + iccid: formFilters.iccid || '' + }) + } + + const showEnterpriseCardAuthorizeDialog = async () => { + resetEnterpriseCardAuthorizeForm() + await Promise.all([ + handleSearchEnterprise(), + loadAllocateCarrierOptions(), + loadAllocateBatchNoOptions(), + loadTopLevelShopCascadeOptions() + ]) + enterpriseCardAuthorizeDialogVisible.value = true + enterpriseCardAuthorizeFormRef.value?.clearValidate() + } + + const showEnterpriseCardRecallDialog = async () => { + resetEnterpriseCardRecallForm() + await Promise.all([ + handleSearchEnterprise(), + loadAllocateCarrierOptions(), + loadAllocateBatchNoOptions() + ]) + enterpriseCardRecallDialogVisible.value = true + enterpriseCardRecallFormRef.value?.clearValidate() + } + + const handleEnterpriseCardAuthorizeDialogClose = () => { + Object.assign(enterpriseCardAuthorizeForm, createInitialEnterpriseCardAuthorizeState()) + enterpriseCardAuthorizeFormRef.value?.resetFields() + } + + const handleEnterpriseCardRecallDialogClose = () => { + Object.assign(enterpriseCardRecallForm, createInitialEnterpriseCardRecallState()) + enterpriseCardRecallFormRef.value?.resetFields() + } + + const appendEnterpriseCardFilterParams = (params: Record, form: any) => { + if (form.carrier_id) params.carrier_id = form.carrier_id + if (Array.isArray(form.shop_id_path) && form.shop_id_path.length > 0) { + params.shop_id = form.shop_id_path[form.shop_id_path.length - 1] + } + if (form.batch_no) params.batch_no = form.batch_no + if (form.iccid) params.iccid = form.iccid + } + + const buildEnterpriseCardParams = (form: any) => { + const params: Record = { + selection_type: form.selection_type, + remark: form.remark || undefined + } + + if (form.selection_type === CardSelectionType.LIST) { + const iccids = selectedCards.value.map((card) => card.iccid) + if (!iccids.length) { + ElMessage.warning('列表模式下请先选择卡') + return null + } + params.iccids = iccids + } else if (form.selection_type === CardSelectionType.RANGE) { + params.iccid_start = form.iccid_start + params.iccid_end = form.iccid_end + } else { + appendEnterpriseCardFilterParams(params, form) + } + + Object.keys(params).forEach((key) => { + if (params[key] === undefined || params[key] === '') delete params[key] + }) + + return params + } + + const applyEnterpriseCardOperationSuccess = async ( + title: string, + result: AllocateCardsResponse | RecallCardsResponse + ) => { + enterpriseCardOperationResult.value = result + enterpriseCardResultTitle.value = title + enterpriseCardResultDialogVisible.value = true + selectedCards.value = [] + await getTableData() + } + + const handleEnterpriseCardAuthorize = async () => { + if (!enterpriseCardAuthorizeFormRef.value) return + + await enterpriseCardAuthorizeFormRef.value.validate(async (valid) => { + if (!valid) return + const params = buildEnterpriseCardParams(enterpriseCardAuthorizeForm) + if (!params) return + + enterpriseCardAuthorizeLoading.value = true + try { + const res = await EnterpriseService.allocateCards( + enterpriseCardAuthorizeForm.enterprise_id!, + params as any + ) + if (res.code === 0) { + enterpriseCardAuthorizeDialogVisible.value = false + await applyEnterpriseCardOperationSuccess('授权卡给企业结果', res.data) + } + } catch (error) { + console.error('授权卡给企业失败:', error) + } finally { + enterpriseCardAuthorizeLoading.value = false + } + }) + } + + const handleEnterpriseCardRecall = async () => { + if (!enterpriseCardRecallFormRef.value) return + + await enterpriseCardRecallFormRef.value.validate(async (valid) => { + if (!valid) return + const params = buildEnterpriseCardParams(enterpriseCardRecallForm) + if (!params) return + + enterpriseCardRecallLoading.value = true + try { + const res = await EnterpriseService.recallCards( + enterpriseCardRecallForm.enterprise_id!, + params as any + ) + if (res.code === 0) { + enterpriseCardRecallDialogVisible.value = false + await applyEnterpriseCardOperationSuccess('回收卡授权结果', res.data) + } + } catch (error) { + console.error('回收卡授权失败:', error) + } finally { + enterpriseCardRecallLoading.value = false + } + }) + } + // 显示套餐系列绑定对话框 const showSeriesBindingDialog = async () => { Object.assign(seriesBindingForm, createInitialSeriesBindingState(), { @@ -2118,47 +2823,6 @@ }) } - // 清除卡套餐系列绑定 - const handleClearCardSeries = async () => { - if (selectedCards.value.length === 0) { - ElMessage.warning('请先选择要清除关联的卡') - return - } - - ElMessageBox.confirm( - `确定要清除所选 ${selectedCards.value.length} 张卡的套餐系列关联吗?`, - '确认清除', - { - confirmButtonText: '确定', - cancelButtonText: '取消', - type: 'warning' - } - ) - .then(async () => { - try { - const res = await CardService.batchSetCardSeriesBinding({ - selection_type: CardSelectionType.LIST, - iccids: selectedCards.value.map((card) => card.iccid), - series_id: 0 - }) - if (res.code === 0) { - if (res.data.fail_count === 0) { - ElMessage.success('清除关联成功') - } else { - ElMessage.warning(`部分失败:${res.data.failed_items?.[0]?.reason || '未知原因'}`) - } - selectedCards.value = [] - await getTableData() - } - } catch (error) { - console.error('清除关联失败:', error) - } - }) - .catch(() => { - // 用户取消 - }) - } - // IoT卡操作处理函数 const handleCardOperation = (command: string, iccid: string, row?: any) => { switch (command) { @@ -2258,33 +2922,6 @@ // 用户取消 }) } - - // 手动停用 IoT 卡(资产层面的停用) - const handleManualDeactivate = (iccid: string) => { - ElMessageBox.confirm( - `确定要手动停用卡片 ${iccid} 吗?此操作将在资产管理层面停用该卡。`, - '确认手动停用', - { - confirmButtonText: '确定', - cancelButtonText: '取消', - type: 'warning' - } - ) - .then(async () => { - try { - const res = await AssetService.deactivateAsset(iccid) - if (res.code === 0) { - ElMessage.success('手动停用成功') - await getTableData() - } - } catch (error: any) { - console.error('手动停用失败:', error) - } - }) - .catch(() => { - // 用户取消 - }) - } diff --git a/src/views/finance/refund/detail.vue b/src/views/finance/refund/detail.vue index 6ce36e1..d6f98fe 100644 --- a/src/views/finance/refund/detail.vue +++ b/src/views/finance/refund/detail.vue @@ -22,13 +22,7 @@
- + import { ref, onMounted, computed, h, reactive } from 'vue' import { useRoute, useRouter } from 'vue-router' - import { ElCard, ElImageViewer, ElIcon, ElMessage, ElTag } from 'element-plus' + import { ElButton, ElCard, ElIcon, ElMessage, ElTag } from 'element-plus' import { ArrowLeft, Loading } from '@element-plus/icons-vue' import DetailPage from '@/components/common/DetailPage.vue' import type { DetailSection } from '@/components/common/DetailPage.vue' - import { RefundService, StorageService } from '@/api/modules' + import { RefundService } from '@/api/modules' import type { Refund, RefundStatus, @@ -185,6 +179,7 @@ ResubmitRefundRequest } from '@/types/api' import { formatDateTime, yuanToFen } from '@/utils/business/format' + import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue' defineOptions({ name: 'RefundDetail' }) @@ -193,9 +188,7 @@ const loading = ref(false) const refund = ref(null) - const refundVoucherUrls = ref([]) - const voucherPreviewVisible = ref(false) - const voucherPreviewIndex = ref(0) + const refundVoucherFileKey = ref('') const pageTitle = computed(() => `退款详情`) @@ -262,14 +255,6 @@ return statusMap[status] || '-' } - const getVoucherKeys = (data: Refund): string[] => { - const voucherKey = data.refund_voucher_key || '' - return voucherKey - .split(',') - .map((key) => key.trim()) - .filter(Boolean) - } - const detailSections = computed((): DetailSection[] => [ { title: '退款信息', @@ -309,18 +294,18 @@ { label: '退款凭证', fullWidth: true, - render: () => - refundVoucherUrls.value.length + render: (data) => + data.refund_voucher_key ? h( - 'div', - { class: 'refund-voucher-list' }, - refundVoucherUrls.value.map((url, index) => - h('img', { - class: 'refund-voucher-image', - src: url, - onClick: () => handlePreviewRefundVoucher(index) - }) - ) + ElButton, + { + type: 'primary', + link: true, + onClick: () => { + refundVoucherFileKey.value = data.refund_voucher_key + } + }, + () => '查看退款凭证' ) : h('span', '-') }, @@ -391,7 +376,6 @@ const res = await RefundService.getRefundById(id) if (res.code === 0) { refund.value = res.data - await loadRefundVoucherUrls(res.data) } } catch (error) { console.error(error) @@ -405,26 +389,6 @@ router.back() } - const loadRefundVoucherUrls = async (data: Refund) => { - const fileKeys = getVoucherKeys(data) - refundVoucherUrls.value = [] - if (!fileKeys.length) return - - try { - const res = await StorageService.batchDownloadUrls({ file_keys: fileKeys }) - if (res.code === 0) { - refundVoucherUrls.value = fileKeys.map((key) => res.data.urls[key]).filter(Boolean) - } - } catch (error) { - console.error('获取退款凭证失败:', error) - } - } - - const handlePreviewRefundVoucher = (index: number) => { - voucherPreviewIndex.value = index - voucherPreviewVisible.value = true - } - onMounted(() => { const id = route.params.id as string if (id) { @@ -556,21 +520,5 @@ font-size: 32px; } } - - :deep(.refund-voucher-list) { - display: flex; - flex-wrap: wrap; - gap: 12px; - line-height: 1; - } - - :deep(.refund-voucher-image) { - width: 96px; - height: 96px; - cursor: zoom-in; - object-fit: cover; - border: 1px solid var(--el-border-color); - border-radius: 6px; - } } diff --git a/src/views/finance/refund/index.vue b/src/views/finance/refund/index.vue index 12023b5..a9cb62f 100644 --- a/src/views/finance/refund/index.vue +++ b/src/views/finance/refund/index.vue @@ -5,7 +5,7 @@ @@ -279,7 +279,8 @@ const initialSearchState: RefundQueryParams = { status: undefined, order_id: undefined, - shop_id: undefined + shop_id: undefined, + asset_identifier: '' } // 搜索表单 @@ -325,6 +326,15 @@ remoteMethod: (query: string) => searchOrdersForFilter(query) } }, + { + label: '资产标识', + prop: 'asset_identifier', + type: 'input', + placeholder: '请输入ICCID或设备虚拟号', + config: { + clearable: true + } + }, { label: '状态', prop: 'status', @@ -622,12 +632,14 @@ const getTableData = async () => { loading.value = true try { + const assetIdentifier = searchForm.asset_identifier?.trim() const params: RefundQueryParams = { page: pagination.page, page_size: pagination.page_size, status: searchForm.status, order_id: searchForm.order_id, - shop_id: searchForm.shop_id + shop_id: searchForm.shop_id, + asset_identifier: assetIdentifier || undefined } const res = await RefundService.getRefunds(params) if (res.code === 0) { diff --git a/src/views/order-management/order-list/detail.vue b/src/views/order-management/order-list/detail.vue index 142e1ec..2a80f2d 100644 --- a/src/views/order-management/order-list/detail.vue +++ b/src/views/order-management/order-list/detail.vue @@ -18,9 +18,7 @@
-
+

订单列表

@@ -57,35 +55,29 @@ 加载中...
+ +