From f4ccf9ed24bd5d7f9af96e87e1980f3e994c5aa3 Mon Sep 17 00:00:00 2001 From: sexygoat <1538832180@qq.com> Date: Tue, 17 Mar 2026 09:31:37 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/modules/asset.ts | 179 ++ src/api/modules/card.ts | 52 +- src/api/modules/device.ts | 8 - src/api/modules/index.ts | 1 + src/types/api/asset.ts | 277 +++ src/types/api/card.ts | 3 + src/types/api/commission.ts | 2 +- src/types/api/device.ts | 10 +- src/types/api/enterpriseCard.ts | 20 +- src/types/api/enterpriseDevice.ts | 12 +- src/types/api/index.ts | 3 + src/types/api/packageManagement.ts | 1 + .../enterprise-cards/index.vue | 10 +- .../enterprise-customer/index.vue | 119 +- .../asset-management/device-list/index.vue | 215 ++- .../asset-management/device-search/index.vue | 8 +- .../asset-management/device-task/index.vue | 10 +- .../enterprise-devices/index.vue | 34 +- .../iot-card-management/index.vue | 185 +- .../asset-management/iot-card-task/index.vue | 154 +- .../asset-management/task-detail/index.vue | 4 +- src/views/batch/device-import/index.vue | 6 +- .../card-management/single-card/index.vue | 1116 +++++++---- .../commission/agent-commission/index.vue | 2 +- src/views/my-simcard/single-card/index.vue | 1650 ++++++++++++++--- .../order-management/order-list/index.vue | 14 +- .../package-management/package-list/index.vue | 527 ++++-- .../series-grants/index.vue | 8 +- 28 files changed, 3383 insertions(+), 1247 deletions(-) create mode 100644 src/api/modules/asset.ts create mode 100644 src/types/api/asset.ts diff --git a/src/api/modules/asset.ts b/src/api/modules/asset.ts new file mode 100644 index 0000000..4e13b76 --- /dev/null +++ b/src/api/modules/asset.ts @@ -0,0 +1,179 @@ +/** + * 资产管理 API 服务 + * 对应文档:asset-detail-refactor-api-changes.md + */ + +import { BaseService } from '../BaseService' +import type { + BaseResponse, + AssetType, + AssetResolveResponse, + AssetRealtimeStatusResponse, + AssetRefreshResponse, + AssetPackageUsageRecord, + AssetCurrentPackageResponse, + DeviceStopResponse, + DeviceStartResponse, + CardStopResponse, + CardStartResponse, + AssetWalletTransactionListResponse, + AssetWalletTransactionParams, + AssetWalletResponse +} from '@/types/api' + +export class AssetService extends BaseService { + /** + * 通过任意标识符查询设备或卡的完整详情 + * 支持虚拟号、ICCID、IMEI、SN、MSISDN + * GET /api/admin/assets/resolve/:identifier + * @param identifier 资产标识符(虚拟号、ICCID、IMEI、SN、MSISDN) + */ + static resolveAsset(identifier: string): Promise> { + return this.getOne(`/api/admin/assets/resolve/${identifier}`) + } + + /** + * 读取资产实时状态(直接读 DB/Redis,不调网关) + * GET /api/admin/assets/:asset_type/:id/realtime-status + * @param assetType 资产类型 (card 或 device) + * @param id 资产ID + */ + static getRealtimeStatus( + assetType: AssetType, + id: number + ): Promise> { + return this.getOne( + `/api/admin/assets/${assetType}/${id}/realtime-status` + ) + } + + /** + * 主动调网关拉取最新数据后返回 + * POST /api/admin/assets/:asset_type/:id/refresh + * 注意:设备有 30 秒冷却期,冷却中调用返回 429 + * @param assetType 资产类型 (card 或 device) + * @param id 资产ID + */ + static refreshAsset( + assetType: AssetType, + id: number + ): Promise> { + return this.post>( + `/api/admin/assets/${assetType}/${id}/refresh`, + {} + ) + } + + /** + * 查询该资产所有套餐记录,含虚流量换算字段 + * GET /api/admin/assets/:asset_type/:id/packages + * @param assetType 资产类型 (card 或 device) + * @param id 资产ID + */ + static getAssetPackages( + assetType: AssetType, + id: number + ): Promise> { + return this.get>( + `/api/admin/assets/${assetType}/${id}/packages` + ) + } + + /** + * 查询当前生效中的主套餐 + * GET /api/admin/assets/:asset_type/:id/current-package + * 无生效套餐时返回 404 + * @param assetType 资产类型 (card 或 device) + * @param id 资产ID + */ + static getCurrentPackage( + assetType: AssetType, + id: number + ): Promise> { + return this.getOne( + `/api/admin/assets/${assetType}/${id}/current-package` + ) + } + + // ========== 设备停复机操作 ========== + + /** + * 批量停机设备下所有已实名卡 + * POST /api/admin/assets/device/:device_id/stop + * 停机成功后设置 1 小时停机保护期(保护期内禁止复机) + * @param deviceId 设备ID + */ + static stopDevice(deviceId: number): Promise> { + return this.post>( + `/api/admin/assets/device/${deviceId}/stop`, + {} + ) + } + + /** + * 批量复机设备下所有已实名卡 + * POST /api/admin/assets/device/:device_id/start + * 复机成功后设置 1 小时复机保护期(保护期内禁止停机) + * @param deviceId 设备ID + */ + static startDevice(deviceId: number): Promise> { + return this.post>(`/api/admin/assets/device/${deviceId}/start`, {}) + } + + // ========== 单卡停复机操作 ========== + + /** + * 手动停机单张卡(通过 ICCID) + * POST /api/admin/assets/card/:iccid/stop + * 若卡绑定的设备在复机保护期内,返回 403 + * @param iccid ICCID + */ + static stopCard(iccid: string): Promise> { + return this.post>(`/api/admin/assets/card/${iccid}/stop`, {}) + } + + /** + * 手动复机单张卡(通过 ICCID) + * POST /api/admin/assets/card/:iccid/start + * 若卡绑定的设备在停机保护期内,返回 403 + * @param iccid ICCID + */ + static startCard(iccid: string): Promise> { + return this.post>(`/api/admin/assets/card/${iccid}/start`, {}) + } + + // ========== 钱包查询 ========== + + /** + * 查询指定卡或设备的钱包余额概况 + * GET /api/admin/assets/:asset_type/:id/wallet + * 企业账号禁止调用 + * @param assetType 资产类型 (card 或 device) + * @param id 资产ID + */ + static getAssetWallet( + assetType: AssetType, + id: number + ): Promise> { + return this.getOne(`/api/admin/assets/${assetType}/${id}/wallet`) + } + + /** + * 分页查询指定资产的钱包收支流水 + * GET /api/admin/assets/:asset_type/:id/wallet/transactions + * 企业账号禁止调用 + * @param assetType 资产类型 (card 或 device) + * @param id 资产ID + * @param params 查询参数 + */ + static getWalletTransactions( + assetType: AssetType, + id: number, + params?: AssetWalletTransactionParams + ): Promise> { + return this.get>( + `/api/admin/assets/${assetType}/${id}/wallet/transactions`, + params + ) + } +} diff --git a/src/api/modules/card.ts b/src/api/modules/card.ts index d363880..4459b49 100644 --- a/src/api/modules/card.ts +++ b/src/api/modules/card.ts @@ -19,9 +19,6 @@ import type { BaseResponse, PaginationResponse, ListResponse, - GatewayFlowUsageResponse, - GatewayRealnameStatusResponse, - GatewayCardStatusResponse, GatewayRealnameLinkResponse } from '@/types/api' @@ -91,7 +88,8 @@ export class CardService extends BaseService { } /** - * 通过ICCID查询单卡详情(新接口,用于单卡查询页面) + * 通过ICCID查询单卡详情(旧接口,已废弃) + * @deprecated 使用 AssetService.resolveAsset 替代 * @param iccid ICCID */ static getIotCardDetailByIccid(iccid: string): Promise> { @@ -374,36 +372,6 @@ export class CardService extends BaseService { // ========== IoT卡网关操作相关 ========== - /** - * 查询流量使用 - * @param iccid ICCID - */ - static getGatewayFlow(iccid: string): Promise> { - return this.get>( - `/api/admin/iot-cards/${iccid}/gateway-flow` - ) - } - - /** - * 查询实名认证状态 - * @param iccid ICCID - */ - static getGatewayRealname(iccid: string): Promise> { - return this.get>( - `/api/admin/iot-cards/${iccid}/gateway-realname` - ) - } - - /** - * 查询卡实时状态 - * @param iccid ICCID - */ - static getGatewayStatus(iccid: string): Promise> { - return this.get>( - `/api/admin/iot-cards/${iccid}/gateway-status` - ) - } - /** * 获取实名认证链接 * @param iccid ICCID @@ -413,20 +381,4 @@ export class CardService extends BaseService { `/api/admin/iot-cards/${iccid}/realname-link` ) } - - /** - * 启用物联网卡(复机) - * @param iccid ICCID - */ - static startCard(iccid: string): Promise { - return this.post(`/api/admin/iot-cards/${iccid}/start`, {}) - } - - /** - * 停用物联网卡(停机) - * @param iccid ICCID - */ - static stopCard(iccid: string): Promise { - return this.post(`/api/admin/iot-cards/${iccid}/stop`, {}) - } } diff --git a/src/api/modules/device.ts b/src/api/modules/device.ts index b17072a..dfbe9f1 100644 --- a/src/api/modules/device.ts +++ b/src/api/modules/device.ts @@ -46,14 +46,6 @@ export class DeviceService extends BaseService { return this.getOne(`/api/admin/devices/${id}`) } - /** - * 通过设备号查询设备详情 - * @param imei 设备号(IMEI) - */ - static getDeviceByImei(imei: string): Promise> { - return this.getOne(`/api/admin/devices/by-imei/${imei}`) - } - /** * 通过ICCID查询设备详情 * @param iccid ICCID diff --git a/src/api/modules/index.ts b/src/api/modules/index.ts index 5df33df..ed2c0b4 100644 --- a/src/api/modules/index.ts +++ b/src/api/modules/index.ts @@ -25,6 +25,7 @@ export { PackageSeriesService } from './packageSeries' export { PackageManageService } from './packageManage' export { ShopSeriesGrantService } from './shopSeriesGrant' export { OrderService } from './order' +export { AssetService } from './asset' // TODO: 按需添加其他业务模块 // export { SettingService } from './setting' diff --git a/src/types/api/asset.ts b/src/types/api/asset.ts new file mode 100644 index 0000000..87f9f37 --- /dev/null +++ b/src/types/api/asset.ts @@ -0,0 +1,277 @@ +/** + * 资产管理相关类型定义 + * 对应文档:asset-detail-refactor-api-changes.md + */ + +// ========== 资产类型枚举 ========== + +// 资产类型 +export type AssetType = 'card' | 'device' + +// 网络状态 +export enum NetworkStatus { + OFFLINE = 0, // 停机 + ONLINE = 1 // 开机 +} + +// 实名状态 +export enum RealNameStatus { + NOT_VERIFIED = 0, // 未实名 + VERIFYING = 1, // 实名中 + VERIFIED = 2 // 已实名 +} + +// 保护期状态 +export type DeviceProtectStatus = 'none' | 'stop' | 'start' + +// 套餐使用状态 +export enum PackageUsageStatus { + PENDING = 0, // 待生效 + ACTIVE = 1, // 生效中 + USED_UP = 2, // 已用完 + EXPIRED = 3, // 已过期 + INVALID = 4 // 已失效 +} + +// 套餐类型 +export type PackageType = 'formal' | 'addon' + +// 套餐使用类型 +export type UsageType = 'single_card' | 'device' + +// ========== 资产详情响应 ========== + +/** + * 资产解析/详情响应 + * 对应接口:GET /api/admin/assets/resolve/:identifier + */ +export interface AssetResolveResponse { + asset_type: AssetType // 资产类型:card 或 device + asset_id: number // 数据库 ID + virtual_no: string // 虚拟号 + status: number // 资产状态 + batch_no: string // 批次号 + shop_id: number // 所属店铺 ID + shop_name: string // 所属店铺名称 + series_id: number // 套餐系列 ID + series_name: string // 套餐系列名称 + real_name_status: RealNameStatus // 实名状态:0 未实名 / 1 实名中 / 2 已实名 + network_status?: NetworkStatus // 网络状态:0 停机 / 1 开机(仅 card) + current_package: string // 当前套餐名称(无则空) + package_total_mb: number // 当前套餐总虚流量 MB + package_used_mb: number // 已用虚流量 MB + package_remain_mb: number // 剩余虚流量 MB + device_protect_status?: DeviceProtectStatus // 保护期状态:none / stop / start(仅 device) + activated_at: string // 激活时间 + created_at: string // 创建时间 + updated_at: string // 更新时间 + accumulated_recharge?: number // 累计充值金额(分) + first_commission_paid?: boolean // 一次性佣金是否已发放 + + // ===== 卡专属字段 (asset_type === 'card' 时) ===== + iccid?: string // 卡 ICCID + bound_device_id?: number // 绑定设备 ID + bound_device_no?: string // 绑定设备虚拟号 + bound_device_name?: string // 绑定设备名称 + carrier_id?: number // 运营商ID + carrier_type?: string // 运营商类型 + carrier_name?: string // 运营商名称 + msisdn?: string // 手机号 + imsi?: string // IMSI + card_category?: string // 卡业务类型 + supplier?: string // 供应商 + activation_status?: number // 激活状态 + enable_polling?: boolean // 是否参与轮询 + + // ===== 设备专属字段 (asset_type === 'device' 时) ===== + bound_card_count?: number // 绑定卡数量 + cards?: AssetBoundCard[] // 绑定卡列表 + device_name?: string // 设备名称 + imei?: string // IMEI + sn?: string // 序列号 + device_model?: string // 设备型号 + device_type?: string // 设备类型 + max_sim_slots?: number // 最大插槽数 + manufacturer?: string // 制造商 +} + +/** + * 设备绑定的卡信息 + */ +export interface AssetBoundCard { + card_id: number // 卡 ID + iccid: string // ICCID + msisdn: string // 手机号 + network_status: NetworkStatus // 网络状态 + real_name_status: RealNameStatus // 实名状态 + slot_position: number // 插槽位置 +} + +// ========== 资产实时状态响应 ========== + +/** + * 资产实时状态响应 + * 对应接口:GET /api/admin/assets/:asset_type/:id/realtime-status + */ +export interface AssetRealtimeStatusResponse { + asset_type: AssetType // 资产类型 + asset_id: number // 资产 ID + + // ===== 卡专属字段 ===== + network_status?: NetworkStatus // 网络状态(仅 card) + real_name_status?: RealNameStatus // 实名状态(仅 card) + current_month_usage_mb?: number // 本月已用流量 MB(仅 card) + last_sync_time?: string // 最后同步时间(仅 card) + + // ===== 设备专属字段 ===== + device_protect_status?: DeviceProtectStatus // 保护期(仅 device) + cards?: AssetBoundCard[] // 所有绑定卡的状态(仅 device) +} + +/** + * 资产刷新响应(结构与 realtime-status 完全相同) + * 对应接口:POST /api/admin/assets/:asset_type/:id/refresh + */ +export type AssetRefreshResponse = AssetRealtimeStatusResponse + +// ========== 资产套餐查询响应 ========== + +/** + * 资产套餐使用记录 + * 对应接口:GET /api/admin/assets/:asset_type/:id/packages + */ +export interface AssetPackageUsageRecord { + package_usage_id?: number // 套餐使用记录 ID + package_id?: number // 套餐 ID + package_name?: string // 套餐名称 + package_type?: PackageType // formal(正式套餐)/ addon(加油包) + usage_type?: UsageType // 使用类型:single_card/device + status?: PackageUsageStatus // 0 待生效 / 1 生效中 / 2 已用完 / 3 已过期 / 4 已失效 + status_name?: string // 状态中文名 + data_limit_mb?: number // 真流量总量 MB + virtual_limit_mb?: number // 虚流量总量 MB(已按 virtual_ratio 换算) + data_usage_mb?: number // 已用真流量 MB + virtual_used_mb?: number // 已用虚流量 MB + virtual_remain_mb?: number // 剩余虚流量 MB + virtual_ratio?: number // 虚流量比例(real/virtual) + activated_at?: string // 激活时间 + expires_at?: string // 到期时间 + master_usage_id?: number | null // 主套餐 ID(加油包时有值) + priority?: number // 优先级 + created_at?: string // 创建时间 +} + +/** + * 当前套餐响应(结构同套餐使用记录单项) + * 对应接口:GET /api/admin/assets/:asset_type/:id/current-package + */ +export type AssetCurrentPackageResponse = AssetPackageUsageRecord + +// ========== 设备停复机响应 ========== + +/** + * 设备批量停机响应 + * 对应接口:POST /api/admin/assets/device/:device_id/stop + */ +export interface DeviceStopResponse { + message: string // 操作结果描述 + success_count: number // 成功停机的卡数量 + failed_cards: DeviceStopFailedCard[] // 停机失败列表 +} + +/** + * 停机失败的卡信息 + */ +export interface DeviceStopFailedCard { + iccid: string // ICCID + reason: string // 失败原因 +} + +/** + * 设备批量复机响应(HTTP 200 即成功,无 body) + * 对应接口:POST /api/admin/assets/device/:device_id/start + */ +export type DeviceStartResponse = void + +/** + * 单卡停机响应(HTTP 200 即成功,无 body) + * 对应接口:POST /api/admin/assets/card/:iccid/stop + */ +export type CardStopResponse = void + +/** + * 单卡复机响应(HTTP 200 即成功,无 body) + * 对应接口:POST /api/admin/assets/card/:iccid/start + */ +export type CardStartResponse = void + +// ========== 钱包流水响应 ========== + +/** + * 交易类型 + */ +export type TransactionType = 'recharge' | 'deduct' | 'refund' + +/** + * 关联业务类型 + */ +export type ReferenceType = 'recharge' | 'order' + +/** + * 钱包流水单项 + */ +export interface AssetWalletTransactionItem { + id?: number // 流水记录ID + transaction_type?: TransactionType // 交易类型:recharge/deduct/refund + transaction_type_text?: string // 交易类型文本:充值/扣款/退款 + amount?: number // 变动金额(分),充值为正数,扣款/退款为负数 + balance_before?: number // 变动前余额(分) + balance_after?: number // 变动后余额(分) + reference_type?: ReferenceType | null // 关联业务类型:recharge 或 order(可空) + reference_no?: string | null // 关联业务编号:充值单号(CRCH…)或订单号(ORD…)(可空) + remark?: string | null // 备注(可空) + created_at?: string // 流水创建时间(RFC3339) +} + +/** + * 钱包流水列表响应 + * 对应接口:GET /api/admin/assets/:asset_type/:id/wallet/transactions + */ +export interface AssetWalletTransactionListResponse { + list?: AssetWalletTransactionItem[] | null // 流水列表 + page?: number // 当前页码 + page_size?: number // 每页数量 + total?: number // 总记录数 + total_pages?: number // 总页数 +} + +/** + * 钱包流水查询参数 + */ +export interface AssetWalletTransactionParams { + page?: number // 页码,默认1 + page_size?: number // 每页数量,默认20,最大100 + transaction_type?: TransactionType | null // 交易类型过滤:recharge/deduct/refund + start_time?: string | null // 开始时间(RFC3339) + end_time?: string | null // 结束时间(RFC3339) +} + +// ========== 钱包概况响应 ========== + +/** + * 资产钱包概况响应 + * 对应接口:GET /api/admin/assets/:asset_type/:id/wallet + */ +export interface AssetWalletResponse { + wallet_id?: number // 钱包数据库ID + resource_id?: number // 对应卡或设备的数据库ID + resource_type?: string // 资源类型:iot_card 或 device + balance?: number // 总余额(分) + frozen_balance?: number // 冻结余额(分) + available_balance?: number // 可用余额 = balance - frozen_balance(分) + currency?: string // 币种,目前固定 CNY + status?: number // 钱包状态:1-正常 2-冻结 3-关闭 + status_text?: string // 状态文本 + created_at?: string // 创建时间(RFC3339) + updated_at?: string // 更新时间(RFC3339) +} diff --git a/src/types/api/card.ts b/src/types/api/card.ts index 8cb2094..235f150 100644 --- a/src/types/api/card.ts +++ b/src/types/api/card.ts @@ -312,6 +312,7 @@ export interface StandaloneCardQueryParams extends PaginationParams { shop_id?: number // 分销商ID iccid?: string // ICCID(模糊查询) msisdn?: string // 卡接入号(模糊查询) + virtual_no?: string // 虚拟号(模糊查询) batch_no?: string // 批次号 package_id?: number // 套餐ID is_distributed?: boolean // 是否已分销 @@ -327,6 +328,7 @@ export interface StandaloneIotCard { iccid: string // ICCID imsi?: string // IMSI (可选) msisdn?: string // 卡接入号 (可选) + virtual_no?: string // 虚拟号(可空) carrier_id: number // 运营商ID carrier_type: string // 运营商类型 (CMCC:中国移动, CUCC:中国联通, CTCC:中国电信, CBN:中国广电) carrier_name: string // 运营商名称 @@ -465,6 +467,7 @@ export interface IotCardDetailResponse { iccid: string // ICCID imsi: string // IMSI msisdn: string // 卡接入号 + virtual_no?: string // 虚拟号(可空) carrier_id: number // 运营商ID carrier_name: string // 运营商名称 carrier_type: string // 运营商类型 (CMCC:中国移动, CUCC:中国联通, CTCC:中国电信, CBN:中国广电) diff --git a/src/types/api/commission.ts b/src/types/api/commission.ts index 0153ec4..067d1c6 100644 --- a/src/types/api/commission.ts +++ b/src/types/api/commission.ts @@ -184,7 +184,7 @@ export interface ShopCommissionRecordItem { order_no?: string // 订单号 order_created_at?: string // 订单创建时间 iccid?: string // ICCID - device_no?: string // 设备号 + virtual_no?: string // 虚拟号(原 device_no) created_at: string // 佣金入账时间 } diff --git a/src/types/api/device.ts b/src/types/api/device.ts index 947248f..097ddab 100644 --- a/src/types/api/device.ts +++ b/src/types/api/device.ts @@ -19,7 +19,7 @@ export enum DeviceStatus { // 设备信息 export interface Device { id: number // 设备ID - device_no: string // 设备号 + virtual_no: string // 虚拟号(原 device_no) device_name: string // 设备名称 device_model: string // 设备型号 device_type: string // 设备类型 @@ -39,7 +39,7 @@ export interface Device { // 设备查询参数 export interface DeviceQueryParams extends PaginationParams { - device_no?: string // 设备号(模糊查询) + virtual_no?: string // 虚拟号(模糊查询,原 device_no) device_name?: string // 设备名称(模糊查询) status?: DeviceStatus // 状态 shop_id?: number | null // 店铺ID (NULL表示平台库存) @@ -107,7 +107,7 @@ export interface AllocateDevicesRequest { // 分配失败项 export interface AllocationDeviceFailedItem { device_id: number // 设备ID - device_no: string // 设备号 + virtual_no: string // 虚拟号(原 device_no) reason: string // 失败原因 } @@ -194,7 +194,7 @@ export interface DeviceImportTaskListResponse { // 导入结果详细项 export interface DeviceImportResultItem { line: number // 行号 - device_no: string // 设备号 + virtual_no: string // 虚拟号(原 device_no) reason: string // 原因 } @@ -215,7 +215,7 @@ export interface BatchSetDeviceSeriesBindingRequest { // 设备套餐系列绑定失败项 export interface DeviceSeriesBindingFailedItem { device_id: number // 设备ID - device_no: string // 设备号 + virtual_no: string // 虚拟号(原 device_no) reason: string // 失败原因 } diff --git a/src/types/api/enterpriseCard.ts b/src/types/api/enterpriseCard.ts index 1d25dae..e11124b 100644 --- a/src/types/api/enterpriseCard.ts +++ b/src/types/api/enterpriseCard.ts @@ -18,8 +18,8 @@ export interface FailedItem { export interface AllocatedDevice { /** 设备ID */ device_id: number - /** 设备号 */ - device_no: string + /** 虚拟号(原 device_no) */ + virtual_no: string /** 卡数量 */ card_count: number /** 卡ICCID列表 */ @@ -92,8 +92,8 @@ export interface DeviceBundleCard { export interface DeviceBundle { /** 设备ID */ device_id: number - /** 设备号 */ - device_no: string + /** 虚拟号(原 device_no) */ + virtual_no: string /** 触发卡(用户选择的卡) */ trigger_card: DeviceBundleCard /** 连带卡(同设备的其他卡) */ @@ -158,8 +158,8 @@ export interface EnterpriseCardItem { package_name?: string /** 设备ID */ device_id?: number | null - /** 设备号 */ - device_no?: string + /** 虚拟号(原 device_no) */ + virtual_no?: string } /** @@ -176,8 +176,8 @@ export interface EnterpriseCardListParams { carrier_id?: number /** ICCID(模糊查询) */ iccid?: string - /** 设备号(模糊查询) */ - device_no?: string + /** 虚拟号(模糊查询,原 device_no) */ + virtual_no?: string } /** @@ -200,8 +200,8 @@ export interface EnterpriseCardPageResult { export interface RecalledDevice { /** 设备ID */ device_id: number - /** 设备号 */ - device_no: string + /** 虚拟号(原 device_no) */ + virtual_no: string /** 卡数量 */ card_count: number /** 卡ICCID列表 */ diff --git a/src/types/api/enterpriseDevice.ts b/src/types/api/enterpriseDevice.ts index 3cf6902..f9e1f7f 100644 --- a/src/types/api/enterpriseDevice.ts +++ b/src/types/api/enterpriseDevice.ts @@ -11,7 +11,7 @@ import { PaginationParams } from '@/types' */ export interface EnterpriseDeviceItem { device_id: number // 设备ID - device_no: string // 设备号 + virtual_no: string // 虚拟号(原 device_no) device_name: string // 设备名称 device_model: string // 设备型号 card_count: number // 绑定卡数量 @@ -22,7 +22,7 @@ export interface EnterpriseDeviceItem { * 企业设备列表查询参数 */ export interface EnterpriseDeviceListParams extends PaginationParams { - device_no?: string // 设备号(模糊搜索) + virtual_no?: string // 虚拟号(模糊搜索,原 device_no) } /** @@ -39,7 +39,7 @@ export interface EnterpriseDevicePageResult { * 授权设备请求参数 */ export interface AllocateDevicesRequest { - device_nos: string[] | null // 设备号列表(最多100个) + virtual_nos: string[] | null // 虚拟号列表(最多100个,原 device_nos) remark?: string // 授权备注 } @@ -48,7 +48,7 @@ export interface AllocateDevicesRequest { */ export interface AuthorizedDeviceItem { device_id: number // 设备ID - device_no: string // 设备号 + virtual_no: string // 虚拟号(原 device_no) card_count: number // 绑定卡数量 } @@ -56,7 +56,7 @@ export interface AuthorizedDeviceItem { * 失败设备项 */ export interface FailedDeviceItem { - device_no: string // 设备号 + virtual_no: string // 虚拟号(原 device_no) reason: string // 失败原因 } @@ -76,7 +76,7 @@ export interface AllocateDevicesResponse { * 撤销设备授权请求参数 */ export interface RecallDevicesRequest { - device_nos: string[] | null // 设备号列表(最多100个) + virtual_nos: string[] | null // 虚拟号列表(最多100个,原 device_nos) } /** diff --git a/src/types/api/index.ts b/src/types/api/index.ts index f67cbf1..6f75f84 100644 --- a/src/types/api/index.ts +++ b/src/types/api/index.ts @@ -76,3 +76,6 @@ export * from './carrier' // 订单相关 export * from './order' + +// 资产管理相关 +export * from './asset' diff --git a/src/types/api/packageManagement.ts b/src/types/api/packageManagement.ts index c74e9d8..6484c8c 100644 --- a/src/types/api/packageManagement.ts +++ b/src/types/api/packageManagement.ts @@ -112,6 +112,7 @@ export interface PackageResponse { data_reset_cycle?: string // 流量重置周期 (daily:每日, monthly:每月, yearly:每年, none:不重置) real_data_mb?: number // 真流量额度(MB) virtual_data_mb?: number // 虚流量额度(MB) + virtual_ratio?: number // 虚流量比例(real_data_mb / virtual_data_mb)。启用虚流量时计算,否则为 1.0 enable_virtual_data?: boolean // 是否启用虚流量 enable_realname_activation?: boolean // 是否启用实名激活 (true:需实名后激活, false:立即激活) cost_price?: number // 成本价(分) diff --git a/src/views/account-management/enterprise-cards/index.vue b/src/views/account-management/enterprise-cards/index.vue index 667e384..ab6f45f 100644 --- a/src/views/account-management/enterprise-cards/index.vue +++ b/src/views/account-management/enterprise-cards/index.vue @@ -318,7 +318,7 @@ // 搜索表单初始值 const initialSearchState = { iccid: '', - device_no: '', + virtual_no: '', carrier_id: undefined as number | undefined, status: undefined as number | undefined } @@ -354,7 +354,7 @@ }, { label: '设备号', - prop: 'device_no', + prop: 'virtual_no', type: 'input', config: { clearable: true, @@ -460,7 +460,7 @@ const columnOptions = [ { label: 'ICCID', prop: 'iccid' }, { label: '卡接入号', prop: 'msisdn' }, - { label: '设备号', prop: 'device_no' }, + { label: '设备号', prop: 'virtual_no' }, { label: '运营商ID', prop: 'carrier_id' }, { label: '运营商', prop: 'carrier_name' }, { label: '套餐名称', prop: 'package_name' }, @@ -527,7 +527,7 @@ width: 130 }, { - prop: 'device_no', + prop: 'virtual_no', label: '设备号', width: 150 }, @@ -612,7 +612,7 @@ page: pagination.page, page_size: pagination.pageSize, iccid: searchForm.iccid || undefined, - device_no: searchForm.device_no || undefined, + virtual_no: searchForm.virtual_no || undefined, carrier_id: searchForm.carrier_id, status: searchForm.status } diff --git a/src/views/account-management/enterprise-customer/index.vue b/src/views/account-management/enterprise-customer/index.vue index 89301d8..db76ae7 100644 --- a/src/views/account-management/enterprise-customer/index.vue +++ b/src/views/account-management/enterprise-customer/index.vue @@ -87,42 +87,36 @@ - - - - - - - - - - - - - - + + - - - + /> @@ -220,7 +214,7 @@ import { h } from 'vue' import { useRouter } from 'vue-router' import { EnterpriseService, ShopService } from '@/api/modules' - import { ElMessage, ElSwitch } from 'element-plus' + import { ElMessage, ElSwitch, ElCascader, ElTreeSelect } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus' import type { EnterpriseItem, ShopResponse } from '@/types/api' import type { SearchFormItem } from '@/types' @@ -233,6 +227,7 @@ import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue' import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue' import { formatDateTime } from '@/utils/business/format' + import { regionData } from '@/utils/constants/regionData' defineOptions({ name: 'EnterpriseCustomer' }) @@ -261,7 +256,7 @@ const shopLoading = ref(false) const tableRef = ref() const currentEnterpriseId = ref(0) - const shopList = ref([]) + const shopTreeData = ref([]) // 右键菜单 const enterpriseOperationMenuRef = ref>() @@ -388,6 +383,7 @@ enterprise_name: '', business_license: '', legal_person: '', + region: [] as string[], province: '', city: '', district: '', @@ -518,20 +514,19 @@ } }) - // 加载店铺列表(默认加载20条) - const loadShopList = async (shopName?: string) => { + // 加载店铺列表(获取所有店铺构建树形结构) + const loadShopList = async () => { shopLoading.value = true try { const params: any = { page: 1, - pageSize: 20 - } - if (shopName) { - params.shop_name = shopName + page_size: 9999 // 获取所有数据用于构建树形结构 } const res = await ShopService.getShops(params) if (res.code === 0) { - shopList.value = res.data.items || [] + const items = res.data.items || [] + // 构建树形数据 + shopTreeData.value = buildTreeData(items) } } catch (error) { console.error('获取店铺列表失败:', error) @@ -540,13 +535,31 @@ } } - // 搜索店铺 - const searchShops = (query: string) => { - if (query) { - loadShopList(query) - } else { - loadShopList() - } + // 构建树形数据 + 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 } // 获取企业客户列表 @@ -602,6 +615,19 @@ getTableData() } + // 处理地区选择变化 + const handleRegionChange = (value: string[]) => { + if (value && value.length === 3) { + form.province = value[0] + form.city = value[1] + form.district = value[2] + } else { + form.province = '' + form.city = '' + form.district = '' + } + } + const dialogType = ref('add') // 显示新增/编辑对话框 @@ -617,6 +643,12 @@ form.province = row.province form.city = row.city form.district = row.district + // 设置地区级联选择器的值 + if (row.province && row.city && row.district) { + form.region = [row.province, row.city, row.district] + } else { + form.region = [] + } form.address = row.address form.contact_name = row.contact_name form.contact_phone = row.contact_phone @@ -628,6 +660,7 @@ form.enterprise_name = '' form.business_license = '' form.legal_person = '' + form.region = [] form.province = '' form.city = '' form.district = '' diff --git a/src/views/asset-management/device-list/index.vue b/src/views/asset-management/device-list/index.vue index 32208bf..c9d3920 100644 --- a/src/views/asset-management/device-list/index.vue +++ b/src/views/asset-management/device-list/index.vue @@ -122,7 +122,7 @@ :key="item.device_id" style="margin-bottom: 8px; font-size: 12px; color: #f56c6c" > - 设备号: {{ item.device_no }} - {{ item.reason }} + 设备号: {{ item.virtual_no }} - {{ item.reason }} @@ -178,7 +178,7 @@ :key="item.device_id" style="margin-bottom: 8px; font-size: 12px; color: #f56c6c" > - 设备号: {{ item.device_no }} - {{ item.reason }} + 设备号: {{ item.virtual_no }} - {{ item.reason }} @@ -260,7 +260,7 @@ :key="item.device_id" style="margin-bottom: 8px; font-size: 12px; color: #f56c6c" > - 设备号: {{ item.device_no }} - {{ item.reason }} + 设备号: {{ item.virtual_no }} - {{ item.reason }} @@ -292,7 +292,7 @@ {{ currentDeviceDetail.id }} {{ - currentDeviceDetail.device_no + currentDeviceDetail.virtual_no }} {{ @@ -332,10 +332,72 @@ - -
- 绑定新卡 -
+ + + + + + + + + + + + + + + + + + + + + + + 确认绑定 + + + + + + +
@@ -368,7 +430,7 @@ v-if="deviceCards.length === 0" style="text-align: center; padding: 20px; color: #909399" > - 暂无绑定的卡片 + 暂无设备绑定的卡
@@ -381,57 +443,6 @@ @select="handleDeviceOperationMenuSelect" /> - - - - - - - - - - - - - - - - - import { h } from 'vue' import { useRouter } from 'vue-router' - import { DeviceService, ShopService, CardService, PackageSeriesService } from '@/api/modules' + import { + DeviceService, + ShopService, + CardService, + PackageSeriesService, + AssetService + } from '@/api/modules' import { ElMessage, ElMessageBox, @@ -635,7 +652,6 @@ const deviceCardsDialogVisible = ref(false) // 绑定卡相关 - const bindCardDialogVisible = ref(false) const bindCardLoading = ref(false) const bindCardFormRef = ref() const iotCardList = ref([]) @@ -704,7 +720,7 @@ // 搜索表单初始值 const initialSearchState = { - device_no: '', + virtual_no: '', device_name: '', status: undefined as DeviceStatus | undefined, batch_no: '', @@ -719,7 +735,7 @@ const searchFormItems: SearchFormItem[] = [ { label: '设备号', - prop: 'device_no', + prop: 'virtual_no', type: 'input', config: { clearable: true, @@ -788,7 +804,7 @@ // 列配置 const columnOptions = [ - { label: '设备号', prop: 'device_no' }, + { label: '设备号', prop: 'virtual_no' }, { label: '设备名称', prop: 'device_name' }, { label: '设备型号', prop: 'device_model' }, { label: '设备类型', prop: 'device_type' }, @@ -825,7 +841,7 @@ router.push({ path: '/asset-management/single-card', query: { - device_no: deviceNo + virtual_no: deviceNo } }) } else { @@ -833,12 +849,16 @@ } } - // 查看设备绑定的卡片 + // 查看设备设备绑定的卡 const handleViewCards = async (device: Device) => { currentDeviceDetail.value = device deviceCards.value = [] deviceCardsDialogVisible.value = true - await loadDeviceCards(device.id) + // 重置绑定卡表单 + bindCardForm.iot_card_id = undefined + bindCardForm.slot_position = 1 + // 加载设备卡列表和默认IoT卡列表 + await Promise.all([loadDeviceCards(device.id), loadDefaultIotCards()]) } // 加载设备绑定的卡列表 @@ -856,15 +876,6 @@ } } - // 打开绑定卡弹窗 - const handleBindCard = async () => { - bindCardForm.iot_card_id = undefined - bindCardForm.slot_position = 1 - bindCardDialogVisible.value = true - // 加载默认的IoT卡列表 - await loadDefaultIotCards() - } - // 加载默认的IoT卡列表 const loadDefaultIotCards = async () => { iotCardSearchLoading.value = true @@ -920,22 +931,20 @@ }) if (res.code === 0) { ElMessage.success('绑定成功') - bindCardDialogVisible.value = false + // 重置表单 + bindCardForm.iot_card_id = undefined + bindCardForm.slot_position = 1 + bindCardFormRef.value.resetFields() // 重新加载卡列表 await loadDeviceCards(currentDeviceDetail.value.id) // 刷新设备详情以更新绑定卡数量 - const detailRes = await DeviceService.getDeviceByImei( - currentDeviceDetail.value.device_no - ) + const detailRes = await AssetService.resolveAsset(currentDeviceDetail.value.virtual_no) if (detailRes.code === 0 && detailRes.data) { currentDeviceDetail.value = detailRes.data } - } else { - ElMessage.error(res.message || '绑定失败') } } catch (error: any) { console.error('绑定卡失败:', error) - ElMessage.error(error?.message || '绑定失败') } finally { bindCardLoading.value = false } @@ -958,9 +967,7 @@ // 重新加载卡列表 await loadDeviceCards(currentDeviceDetail.value.id) // 刷新设备详情以更新绑定卡数量 - const detailRes = await DeviceService.getDeviceByImei( - currentDeviceDetail.value.device_no - ) + const detailRes = await AssetService.resolveAsset(currentDeviceDetail.value.virtual_no) if (detailRes.code === 0 && detailRes.data) { currentDeviceDetail.value = detailRes.data } @@ -1013,7 +1020,7 @@ // 动态列配置 const { columnChecks, columns } = useCheckedColumns(() => [ { - prop: 'device_no', + prop: 'virtual_no', label: '设备号', minWidth: 150, showOverflowTooltip: true, @@ -1024,10 +1031,10 @@ style: 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;', onClick: (e: MouseEvent) => { e.stopPropagation() - goToDeviceSearchDetail(row.device_no) + goToDeviceSearchDetail(row.virtual_no) } }, - row.device_no + row.virtual_no ) } }, @@ -1156,7 +1163,7 @@ const params = { page: pagination.page, page_size: pagination.pageSize, - device_no: searchForm.device_no || undefined, + virtual_no: searchForm.virtual_no || undefined, device_name: searchForm.device_name || undefined, status: searchForm.status, batch_no: searchForm.batch_no || undefined, @@ -1212,11 +1219,15 @@ // 删除设备 const deleteDevice = (row: Device) => { - ElMessageBox.confirm(`确定删除设备 ${row.device_no} 吗?删除后将自动解绑所有卡。`, '删除确认', { - confirmButtonText: '确定', - cancelButtonText: '取消', - type: 'error' - }) + ElMessageBox.confirm( + `确定删除设备 ${row.virtual_no} 吗?删除后将自动解绑所有卡。`, + '删除确认', + { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'error' + } + ) .then(async () => { try { await DeviceService.deleteDevice(row.id) @@ -1449,9 +1460,9 @@ } } - // 通过设备号查看卡片 + // 通过设备号设备绑定的卡 const handleViewCardsByDeviceNo = (deviceNo: string) => { - const device = deviceList.value.find((d) => d.device_no === deviceNo) + const device = deviceList.value.find((d) => d.virtual_no === deviceNo) if (device) { handleViewCards(device) } else { @@ -1462,7 +1473,7 @@ // 通过设备号删除设备 const handleDeleteDeviceByNo = async (deviceNo: string) => { // 先根据设备号找到设备对象 - const device = deviceList.value.find((d) => d.device_no === deviceNo) + const device = deviceList.value.find((d) => d.virtual_no === deviceNo) if (device) { deleteDevice(device) } else { @@ -1636,11 +1647,11 @@ const deviceOperationMenuItems = computed((): MenuItemType[] => { const items: MenuItemType[] = [] - // 添加查看卡片到菜单最前面 + // 添加设备绑定的卡到菜单最前面 if (hasAuth('device:view_cards')) { items.push({ key: 'view-cards', - label: '查看卡片' + label: '设备绑定的卡' }) } @@ -1707,7 +1718,7 @@ // 处理表格行右键菜单 const handleRowContextMenu = (row: Device, column: any, event: MouseEvent) => { - showDeviceOperationMenu(event, row.device_no) + showDeviceOperationMenu(event, row.virtual_no) } diff --git a/src/views/asset-management/device-search/index.vue b/src/views/asset-management/device-search/index.vue index c9f883e..05c528e 100644 --- a/src/views/asset-management/device-search/index.vue +++ b/src/views/asset-management/device-search/index.vue @@ -37,7 +37,7 @@ {{ deviceDetail.id }} {{ - deviceDetail.device_no + deviceDetail.virtual_no }} {{ @@ -83,7 +83,7 @@