From 330affe0c92deead239753407d56cc4b0820b4a4 Mon Sep 17 00:00:00 2001 From: luo Date: Tue, 29 Sep 2026 10:16:33 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=8F=90=E7=8E=B0=E5=AE=A1=E6=89=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../specs/commission-management/spec.md | 9 +- .../add-commission-management/tasks.md | 1 + src/api/modules/commission.ts | 8 +- src/types/api/commission.ts | 16 ++++ src/types/components.d.ts | 7 +- .../account-management/account/index.vue | 4 +- .../composables/useAssetInfo.ts | 57 +++++++++++- .../asset-information/index.vue | 5 +- .../withdrawal-approval/index.vue | 89 +++++++++++++------ 9 files changed, 158 insertions(+), 38 deletions(-) diff --git a/openspec/changes/add-commission-management/specs/commission-management/spec.md b/openspec/changes/add-commission-management/specs/commission-management/spec.md index 3590b0d..00b3897 100644 --- a/openspec/changes/add-commission-management/specs/commission-management/spec.md +++ b/openspec/changes/add-commission-management/specs/commission-management/spec.md @@ -24,7 +24,7 @@ The system SHALL provide a paginated list of all commission withdrawal requests ### Requirement: Withdrawal Request Approval -The system SHALL allow administrators to approve withdrawal requests. +The system SHALL allow administrators to approve withdrawal requests using the approval API contract. The administrator SHALL enter the remark manually, and the system SHALL submit the request's account name, account number, amount, and withdrawal method from the selected list item together with the manual payment type. #### Scenario: Approve withdrawal request @@ -32,6 +32,13 @@ The system SHALL allow administrators to approve withdrawal requests. - **THEN** the system marks the request as approved - **AND** updates the processor information and timestamp +#### Scenario: Submit approval details + +- **WHEN** administrator confirms approval after optionally entering a remark +- **THEN** the system submits the selected request's account name, account number, amount, and withdrawal method with `payment_type` set to `manual` +- **AND** submits the manually entered remark +- **AND** refreshes the list after a successful response + ### Requirement: Withdrawal Request Rejection The system SHALL allow administrators to reject withdrawal requests with a reason. diff --git a/openspec/changes/add-commission-management/tasks.md b/openspec/changes/add-commission-management/tasks.md index 7b0d701..801853d 100644 --- a/openspec/changes/add-commission-management/tasks.md +++ b/openspec/changes/add-commission-management/tasks.md @@ -41,6 +41,7 @@ - [x] 5.6 Implement approve action with confirmation dialog - [x] 5.7 Implement reject action with reason input dialog - [x] 5.8 Add status badges using unified status components +- [x] 5.9 Submit approval request details from the selected list item and collect a manual remark ## 6. Withdrawal Settings Module diff --git a/src/api/modules/commission.ts b/src/api/modules/commission.ts index 49e8500..5dca4d8 100644 --- a/src/api/modules/commission.ts +++ b/src/api/modules/commission.ts @@ -8,6 +8,7 @@ import type { WithdrawalRequestQueryParams, WithdrawalRequestPageResult, ApproveWithdrawalParams, + WithdrawalApprovalResponse, RejectWithdrawalParams, WithdrawalSettingListResult, WithdrawalSettingItem, @@ -46,8 +47,11 @@ export class CommissionService extends BaseService { * 审批通过提现申请 * POST /api/admin/commission/withdrawal-requests/{id}/approve */ - static approveWithdrawal(id: number, params?: ApproveWithdrawalParams): Promise { - return this.post( + static approveWithdrawal( + id: number, + params: ApproveWithdrawalParams + ): Promise> { + return this.post>( `/api/admin/commission/withdrawal-requests/${id}/approve`, params ) diff --git a/src/types/api/commission.ts b/src/types/api/commission.ts index c322f87..cb66f70 100644 --- a/src/types/api/commission.ts +++ b/src/types/api/commission.ts @@ -88,7 +88,23 @@ export interface WithdrawalRequestQueryParams extends PaginationParams { * 审批提现申请参数 */ export interface ApproveWithdrawalParams { + account_name?: string | null // 修正后的收款人姓名 + account_number?: string | null // 修正后的收款账号 + amount?: number | null // 修正后的提现金额(分) + payment_type: PaymentType // 放款类型 remark?: string // 审批备注 + withdrawal_method?: WithdrawalMethod | null // 修正后的收款类型 +} + +/** + * 审批提现申请响应 + */ +export interface WithdrawalApprovalResponse { + id?: number // 提现申请ID + processed_at?: string // 处理时间 + status?: WithdrawalStatus // 状态 + status_name?: string // 状态名称 + withdrawal_no?: string // 提现单号 } /** diff --git a/src/types/components.d.ts b/src/types/components.d.ts index 6680882..bb31417 100644 --- a/src/types/components.d.ts +++ b/src/types/components.d.ts @@ -93,11 +93,9 @@ declare module 'vue' { ElAlert: typeof import('element-plus/es')['ElAlert'] ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElButton: typeof import('element-plus/es')['ElButton'] - ElCalendar: typeof import('element-plus/es')['ElCalendar'] ElCard: typeof import('element-plus/es')['ElCard'] ElCascader: typeof import('element-plus/es')['ElCascader'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] - ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCol: typeof import('element-plus/es')['ElCol'] ElCollapse: typeof import('element-plus/es')['ElCollapse'] ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] @@ -115,6 +113,7 @@ declare module 'vue' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] + ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElMenu: typeof import('element-plus/es')['ElMenu'] @@ -122,13 +121,13 @@ declare module 'vue' { ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] - ElProgress: typeof import('element-plus/es')['ElProgress'] ElRadio: typeof import('element-plus/es')['ElRadio'] - ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] + ElResult: typeof import('element-plus/es')['ElResult'] ElRow: typeof import('element-plus/es')['ElRow'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSelect: typeof import('element-plus/es')['ElSelect'] + ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTable: typeof import('element-plus/es')['ElTable'] diff --git a/src/views/account-management/account/index.vue b/src/views/account-management/account/index.vue index f57c5cf..a4d17af 100644 --- a/src/views/account-management/account/index.vue +++ b/src/views/account-management/account/index.vue @@ -592,7 +592,8 @@ { prop: 'username', label: '账号名称', - minWidth: 120 + minWidth: 160, + showOverflowTooltip: true }, { prop: 'phone', @@ -617,6 +618,7 @@ prop: 'shop_name', label: '店铺名称', minWidth: 150, + showOverflowTooltip: true, formatter: (row: any) => { return row.shop_name || '-' } diff --git a/src/views/asset-management/asset-information/composables/useAssetInfo.ts b/src/views/asset-management/asset-information/composables/useAssetInfo.ts index fd57211..7374438 100644 --- a/src/views/asset-management/asset-information/composables/useAssetInfo.ts +++ b/src/views/asset-management/asset-information/composables/useAssetInfo.ts @@ -7,6 +7,7 @@ import { ref } from 'vue' import { ElMessage } from 'element-plus' import { AssetService } from '@/api/modules' import { FrontendRateLimitError } from '@/utils/business/apiRateLimit' +import { normalizeApiError } from '@/utils/business/apiError' import type { AssetBoundCard, AssetWalletResponse, @@ -32,6 +33,53 @@ const NULLABLE_REALTIME_CARD_FIELDS = new Set([ // 详情接口是绑定卡实名时间的唯一来源;实时状态接口不应覆盖或保留旧值。 const DETAIL_ONLY_BOUND_CARD_FIELDS = new Set(['real_name_at']) +const ASSET_NOT_FOUND_CODE = 1006 + +const isLegacyNineteenDigitIdentifier = (identifier: string) => /^89\d{17}$/.test(identifier) + +const isAssetNotFound = (value: any) => + value?.code === ASSET_NOT_FOUND_CODE || value?.response?.data?.code === ASSET_NOT_FOUND_CODE + +const isUnauthorized = (value: any) => + value?.code === 401 || value?.response?.status === 401 || value?.response?.data?.code === 401 + +const calculateLuhnCheckDigit = (identifier: string) => { + let sum = 0 + let doubleDigit = true + + for (let index = identifier.length - 1; index >= 0; index -= 1) { + let digit = Number(identifier[index]) + if (doubleDigit) { + digit *= 2 + if (digit > 9) digit -= 9 + } + sum += digit + doubleDigit = !doubleDigit + } + + return String((10 - (sum % 10)) % 10) +} + +const resolveAssetForAssetInfo = async (identifier: string) => { + const params = { include_usage_summary: true } + if (!isLegacyNineteenDigitIdentifier(identifier)) { + return AssetService.resolveAsset(identifier, params) + } + + const silentConfig = { requestOptions: { errorMessageMode: 'none' as const } } + let firstResponse = null + + try { + firstResponse = await AssetService.resolveAsset(identifier, params, silentConfig) + } catch (error) { + if (!isAssetNotFound(error)) throw error + } + + if (firstResponse && !isAssetNotFound(firstResponse)) return firstResponse + + const correctedIdentifier = `${identifier}${calculateLuhnCheckDigit(identifier)}` + return AssetService.resolveAsset(correctedIdentifier, params, silentConfig) +} /** * 格式化数据大小(MB -> GB/MB) @@ -96,9 +144,10 @@ export function useAssetInfo() { * @param identifier - 资产标识符(ICCID、IMEI、SN、虚拟号或MSISDN) */ const fetchAssetDetail = async (identifier: string) => { + const shouldShowSilentRequestError = isLegacyNineteenDigitIdentifier(identifier) try { loading.value = true - const response = await AssetService.resolveAsset(identifier, { include_usage_summary: true }) + const response = await resolveAssetForAssetInfo(identifier) if (response.code === 0 && response.data) { const data = response.data @@ -199,8 +248,12 @@ export function useAssetInfo() { ElMessage.error(response.msg || '查询失败') cardInfo.value = null } - } catch { + } catch (error: any) { cardInfo.value = null + if (shouldShowSilentRequestError && !isUnauthorized(error)) { + const backendMessage = error?.response?.data?.msg + ElMessage.error(backendMessage || normalizeApiError(error).message) + } } finally { loading.value = false } diff --git a/src/views/asset-management/asset-information/index.vue b/src/views/asset-management/asset-information/index.vue index ca8e75a..12b63b7 100644 --- a/src/views/asset-management/asset-information/index.vue +++ b/src/views/asset-management/asset-information/index.vue @@ -409,12 +409,14 @@ // fetchAssetDetail 内部已经会并行加载所有相关数据,不需要重复调用 await fetchAssetDetail(identifier) + if (!cardInfo.value) return // 如果是设备类型,需要再次调用搜索接口获取完整数据 if (cardInfo.value?.asset_type === 'device') { const deviceIdentifier = cardInfo.value.virtual_no || cardInfo.value.iccid || identifier await fetchAssetDetail(deviceIdentifier) } + if (!cardInfo.value) return if (cardInfo.value) { walletTransactionSearchToken.value += 1 @@ -428,7 +430,8 @@ // 重置分页并加载套餐列表 packagePagination.value.page = 1 - const result = await loadPackageList(identifier, 1, packagePagination.value.pageSize) + const resolvedIdentifier = cardInfo.value.identifier || identifier + const result = await loadPackageList(resolvedIdentifier, 1, packagePagination.value.pageSize) packagePagination.value.total = result.total } diff --git a/src/views/commission-management/withdrawal-approval/index.vue b/src/views/commission-management/withdrawal-approval/index.vue index d3bc637..9d82ecd 100644 --- a/src/views/commission-management/withdrawal-approval/index.vue +++ b/src/views/commission-management/withdrawal-approval/index.vue @@ -69,6 +69,30 @@ + + + + + + + + + + @@ -77,28 +101,30 @@