diff --git a/src/api/modules/card.ts b/src/api/modules/card.ts index c4a1805..b30326d 100644 --- a/src/api/modules/card.ts +++ b/src/api/modules/card.ts @@ -269,4 +269,86 @@ export class CardService extends BaseService { static getCardChangeNotices(params?: any): Promise> { return this.getPage('/api/card-change-notices', params) } + + // ========== ICCID批量导入相关 ========== + + /** + * 批量导入ICCID + * @param file Excel文件 + * @param carrier_id 运营商ID + * @param batch_no 批次号(可选) + */ + static importIotCards( + file: File, + carrier_id: number, + batch_no?: string + ): Promise { + const formData = new FormData() + formData.append('file', file) + formData.append('carrier_id', carrier_id.toString()) + if (batch_no) { + formData.append('batch_no', batch_no) + } + return this.upload('/api/admin/iot-cards/import', file, { carrier_id, batch_no }) + } + + /** + * 获取导入任务列表 + * @param params 查询参数 + */ + static getIotCardImportTasks(params?: any): Promise> { + return this.getPage('/api/admin/iot-cards/import-tasks', params) + } + + /** + * 获取导入任务详情 + * @param id 任务ID + */ + static getIotCardImportTaskDetail(id: number): Promise> { + return this.getOne(`/api/admin/iot-cards/import-tasks/${id}`) + } + + // ========== 单卡列表(未绑定设备)相关 ========== + + /** + * 获取单卡列表(未绑定设备) + * @param params 查询参数 + */ + static getStandaloneIotCards(params?: any): Promise> { + return this.getPage('/api/admin/iot-cards/standalone', params) + } + + /** + * 批量分配单卡 + * @param data 分配参数 + */ + static allocateStandaloneCards(data: any): Promise> { + return this.post('/api/admin/iot-cards/standalone/allocate', data) + } + + /** + * 批量回收单卡 + * @param data 回收参数 + */ + static recallStandaloneCards(data: any): Promise> { + return this.post('/api/admin/iot-cards/standalone/recall', data) + } + + // ========== 资产分配记录相关 ========== + + /** + * 获取资产分配记录列表 + * @param params 查询参数 + */ + static getAssetAllocationRecords(params?: any): Promise> { + return this.getPage('/api/admin/asset-allocation-records', params) + } + + /** + * 获取资产分配记录详情 + * @param id 记录ID + */ + static getAssetAllocationRecordDetail(id: number): Promise> { + return this.getOne(`/api/admin/asset-allocation-records/${id}`) + } } diff --git a/src/config/constants/index.ts b/src/config/constants/index.ts index 675f2c1..d44caa1 100644 --- a/src/config/constants/index.ts +++ b/src/config/constants/index.ts @@ -16,3 +16,6 @@ export * from './commission' // 通用状态相关 export * from './status' + +// IoT卡相关 +export * from './iotCard' diff --git a/src/config/constants/iotCard.ts b/src/config/constants/iotCard.ts new file mode 100644 index 0000000..46c55b2 --- /dev/null +++ b/src/config/constants/iotCard.ts @@ -0,0 +1,141 @@ +/** + * IoT卡相关常量配置 + */ + +// IoT卡导入任务状态枚举 +export enum IotCardImportTaskStatus { + PENDING = 1, // 待处理 + PROCESSING = 2, // 处理中 + COMPLETED = 3, // 已完成 + FAILED = 4 // 失败 +} + +// IoT卡导入任务状态选项 +export const IOT_CARD_IMPORT_TASK_STATUS_OPTIONS = [ + { + label: '待处理', + value: IotCardImportTaskStatus.PENDING, + type: 'info' as const, + color: '#909399' + }, + { + label: '处理中', + value: IotCardImportTaskStatus.PROCESSING, + type: 'warning' as const, + color: '#E6A23C' + }, + { + label: '已完成', + value: IotCardImportTaskStatus.COMPLETED, + type: 'success' as const, + color: '#67C23A' + }, + { + label: '失败', + value: IotCardImportTaskStatus.FAILED, + type: 'danger' as const, + color: '#F56C6C' + } +] + +// IoT卡导入任务状态映射 +export const IOT_CARD_IMPORT_TASK_STATUS_MAP = IOT_CARD_IMPORT_TASK_STATUS_OPTIONS.reduce( + (map, item) => { + map[item.value] = item + return map + }, + {} as Record< + IotCardImportTaskStatus, + { + label: string + value: IotCardImportTaskStatus + type: 'info' | 'warning' | 'success' | 'danger' + color: string + } + > +) + +// 获取IoT卡导入任务状态标签 +export function getIotCardImportTaskStatusLabel(status: number): string { + return IOT_CARD_IMPORT_TASK_STATUS_MAP[status as IotCardImportTaskStatus]?.label || '未知' +} + +// 获取IoT卡导入任务状态类型(用于 ElTag) +export function getIotCardImportTaskStatusType(status: number) { + return IOT_CARD_IMPORT_TASK_STATUS_MAP[status as IotCardImportTaskStatus]?.type || 'info' +} + +// 获取IoT卡导入任务状态颜色 +export function getIotCardImportTaskStatusColor(status: number): string { + return IOT_CARD_IMPORT_TASK_STATUS_MAP[status as IotCardImportTaskStatus]?.color || '#909399' +} + +// ========== 单卡状态相关 ========== + +// 单卡状态枚举 +export enum StandaloneCardStatus { + IN_STOCK = 1, // 在库 + DISTRIBUTED = 2, // 已分销 + ACTIVATED = 3, // 已激活 + DEACTIVATED = 4 // 已停用 +} + +// 单卡状态选项 +export const STANDALONE_CARD_STATUS_OPTIONS = [ + { + label: '在库', + value: StandaloneCardStatus.IN_STOCK, + type: 'info' as const, + color: '#909399' + }, + { + label: '已分销', + value: StandaloneCardStatus.DISTRIBUTED, + type: 'warning' as const, + color: '#E6A23C' + }, + { + label: '已激活', + value: StandaloneCardStatus.ACTIVATED, + type: 'success' as const, + color: '#67C23A' + }, + { + label: '已停用', + value: StandaloneCardStatus.DEACTIVATED, + type: 'danger' as const, + color: '#F56C6C' + } +] + +// 单卡状态映射 +export const STANDALONE_CARD_STATUS_MAP = STANDALONE_CARD_STATUS_OPTIONS.reduce( + (map, item) => { + map[item.value] = item + return map + }, + {} as Record< + StandaloneCardStatus, + { + label: string + value: StandaloneCardStatus + type: 'info' | 'warning' | 'success' | 'danger' + color: string + } + > +) + +// 获取单卡状态标签 +export function getStandaloneCardStatusLabel(status: number): string { + return STANDALONE_CARD_STATUS_MAP[status as StandaloneCardStatus]?.label || '未知' +} + +// 获取单卡状态类型(用于 ElTag) +export function getStandaloneCardStatusType(status: number) { + return STANDALONE_CARD_STATUS_MAP[status as StandaloneCardStatus]?.type || 'info' +} + +// 获取单卡状态颜色 +export function getStandaloneCardStatusColor(status: number): string { + return STANDALONE_CARD_STATUS_MAP[status as StandaloneCardStatus]?.color || '#909399' +} diff --git a/src/locales/langs/zh.json b/src/locales/langs/zh.json index f8375ff..a8d2d46 100644 --- a/src/locales/langs/zh.json +++ b/src/locales/langs/zh.json @@ -435,9 +435,11 @@ "assetManagement": { "title": "资产管理", "singleCard": "单卡信息", - "cardList": "网卡管理", + "standaloneCardList": "单卡列表", + "taskManagement": "任务管理", + "taskDetail": "任务详情", "devices": "设备管理", - "assetAssign": "资产分配", + "assetAssign": "分配记录", "cardReplacementRequest": "换卡申请" }, "account": { diff --git a/src/router/routes/asyncRoutes.ts b/src/router/routes/asyncRoutes.ts index 5a0782b..931686b 100644 --- a/src/router/routes/asyncRoutes.ts +++ b/src/router/routes/asyncRoutes.ts @@ -844,13 +844,32 @@ export const asyncRoutes: AppRouteRecord[] = [ }, { path: 'card-list', - name: 'CardList', - component: RoutesAlias.CardList, + name: 'StandaloneCardList', + component: RoutesAlias.StandaloneCardList, meta: { - title: 'menus.assetManagement.cardList', + title: 'menus.assetManagement.standaloneCardList', keepAlive: true } }, + { + path: 'task-management', + name: 'TaskManagement', + component: RoutesAlias.TaskManagement, + meta: { + title: 'menus.assetManagement.taskManagement', + keepAlive: true + } + }, + { + path: 'task-detail', + name: 'TaskDetail', + component: RoutesAlias.TaskDetail, + meta: { + title: 'menus.assetManagement.taskDetail', + isHide: true, + keepAlive: false + } + }, { path: 'devices', name: 'DeviceList', @@ -869,6 +888,16 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true } }, + { + path: 'allocation-record-detail', + name: 'AllocationRecordDetail', + component: RoutesAlias.AllocationRecordDetail, + meta: { + title: '分配记录详情', + isHide: true, + keepAlive: false + } + }, { path: 'card-replacement-request', name: 'CardReplacementRequest', diff --git a/src/router/routesAlias.ts b/src/router/routesAlias.ts index 1c55d1d..3649c77 100644 --- a/src/router/routesAlias.ts +++ b/src/router/routesAlias.ts @@ -92,7 +92,11 @@ export enum RoutesAlias { SimCardAssign = '/product/sim-card-assign', // 号卡分配 // 资产管理 - AssetAssign = '/asset-management/asset-assign', // 资产分配 + StandaloneCardList = '/asset-management/card-list', // 单卡列表(未绑定设备) + TaskManagement = '/asset-management/task-management', // 任务管理 + TaskDetail = '/asset-management/task-detail', // 任务详情 + AssetAssign = '/asset-management/asset-assign', // 资产分配(分配记录) + AllocationRecordDetail = '/asset-management/allocation-record-detail', // 分配记录详情 CardReplacementRequest = '/asset-management/card-replacement-request', // 换卡申请 // 账户管理 diff --git a/src/types/api/card.ts b/src/types/api/card.ts index 19e0797..fddef20 100644 --- a/src/types/api/card.ts +++ b/src/types/api/card.ts @@ -227,3 +227,225 @@ export interface CardOrder { createTime: string payTime?: string } + +// ========== ICCID批量导入相关 ========== + +// ICCID导入任务状态枚举 +export enum IotCardImportTaskStatus { + PENDING = 1, // 待处理 + PROCESSING = 2, // 处理中 + COMPLETED = 3, // 已完成 + FAILED = 4 // 失败 +} + +// ICCID导入请求参数 +export interface ImportIotCardParams { + carrier_id: number // 运营商ID + batch_no?: string // 批次号 + file: File // 文件 +} + +// ICCID导入响应 +export interface ImportIotCardResponse { + message: string + task_id: number + task_no: string +} + +// 导入任务记录 +export interface IotCardImportTask { + id: number // 任务ID + task_no: string // 任务编号 + batch_no: string // 批次号 + carrier_id: number // 运营商ID + carrier_name: string // 运营商名称 + file_name: string // 文件名 + status: IotCardImportTaskStatus // 任务状态 + status_text: string // 任务状态文本 + total_count: number // 总数 + success_count: number // 成功数 + fail_count: number // 失败数 + skip_count: number // 跳过数 + error_message: string // 错误信息 + created_at: string // 创建时间 + started_at: string | null // 开始处理时间 + completed_at: string | null // 完成时间 +} + +// 导入任务查询参数 +export interface IotCardImportTaskQueryParams extends PaginationParams { + status?: IotCardImportTaskStatus // 任务状态 + carrier_id?: number // 运营商ID + batch_no?: string // 批次号(模糊查询) + start_time?: string // 创建时间起始 + end_time?: string // 创建时间结束 +} + +// 导入结果详细项 +export interface ImportResultItem { + line: number // 行号 + iccid: string // ICCID + reason: string // 原因 +} + +// 导入任务详情 +export interface IotCardImportTaskDetail extends IotCardImportTask { + failed_items: ImportResultItem[] | null // 失败记录详情 + skipped_items: ImportResultItem[] | null // 跳过记录详情 +} + +// ========== 单卡列表(未绑定设备)相关 ========== + +// 单卡状态枚举 +export enum StandaloneCardStatus { + IN_STOCK = 1, // 在库 + DISTRIBUTED = 2, // 已分销 + ACTIVATED = 3, // 已激活 + DEACTIVATED = 4 // 已停用 +} + +// 单卡查询参数 +export interface StandaloneCardQueryParams extends PaginationParams { + status?: StandaloneCardStatus // 状态 + carrier_id?: number // 运营商ID + shop_id?: number // 分销商ID + iccid?: string // ICCID(模糊查询) + msisdn?: string // 卡接入号(模糊查询) + batch_no?: string // 批次号 + package_id?: number // 套餐ID + is_distributed?: boolean // 是否已分销 + is_replaced?: boolean // 是否有换卡记录 + iccid_start?: string // ICCID起始号 + iccid_end?: string // ICCID结束号 +} + +// 单卡信息 +export interface StandaloneIotCard { + id: number // 卡ID + iccid: string // ICCID + imsi: string // IMSI + msisdn: string // 卡接入号 + carrier_id: number // 运营商ID + carrier_name: string // 运营商名称 + card_type: string // 卡类型 + card_category: string // 卡业务类型 (normal:普通卡, industry:行业卡) + status: StandaloneCardStatus // 状态 + activation_status: number // 激活状态 (0:未激活, 1:已激活) + network_status: number // 网络状态 (0:停机, 1:开机) + real_name_status: number // 实名状态 (0:未实名, 1:已实名) + batch_no: string // 批次号 + supplier: string // 供应商 + shop_id: number | null // 店铺ID + shop_name: string // 店铺名称 + cost_price: number // 成本价(分) + distribute_price: number // 分销价(分) + data_usage_mb: number // 累计流量使用(MB) + activated_at: string | null // 激活时间 + created_at: string // 创建时间 + updated_at: string // 更新时间 +} + +// ========== 单卡批量分配和回收相关 ========== + +// 选卡方式枚举 +export enum CardSelectionType { + LIST = 'list', // ICCID列表 + RANGE = 'range', // 号段范围 + FILTER = 'filter' // 筛选条件 +} + +// 批量分配单卡请求参数 +export interface AllocateStandaloneCardsRequest { + selection_type: CardSelectionType // 选卡方式 + to_shop_id: number // 目标店铺ID + iccids?: string[] // ICCID列表(selection_type=list时必填) + iccid_start?: string // 起始ICCID(selection_type=range时必填) + iccid_end?: string // 结束ICCID(selection_type=range时必填) + carrier_id?: number // 运营商ID(selection_type=filter时可选) + status?: StandaloneCardStatus // 卡状态(selection_type=filter时可选) + batch_no?: string // 批次号(selection_type=filter时可选) + remark?: string // 备注 +} + +// 批量回收单卡请求参数 +export interface RecallStandaloneCardsRequest { + selection_type: CardSelectionType // 选卡方式 + from_shop_id: number // 来源店铺ID(被回收方) + iccids?: string[] // ICCID列表(selection_type=list时必填) + iccid_start?: string // 起始ICCID(selection_type=range时必填) + iccid_end?: string // 结束ICCID(selection_type=range时必填) + carrier_id?: number // 运营商ID(selection_type=filter时可选) + batch_no?: string // 批次号(selection_type=filter时可选) + remark?: string // 备注 +} + +// 分配失败项 +export interface AllocationFailedItem { + iccid: string // ICCID + reason: string // 失败原因 +} + +// 批量分配/回收响应 +export interface AllocateStandaloneCardsResponse { + allocation_no: string // 分配/回收单号 + total_count: number // 待分配/回收总数 + success_count: number // 成功数 + fail_count: number // 失败数 + failed_items: AllocationFailedItem[] | null // 失败项列表 +} + +// ========== 资产分配记录相关 ========== + +// 分配类型枚举 +export enum AllocationTypeEnum { + ALLOCATE = 'allocate', // 分配 + RECALL = 'recall' // 回收 +} + +// 资产类型枚举 +export enum AssetTypeEnum { + IOT_CARD = 'iot_card', // 物联网卡 + DEVICE = 'device' // 设备 +} + +// 资产分配记录查询参数 +export interface AssetAllocationRecordQueryParams extends PaginationParams { + allocation_type?: AllocationTypeEnum // 分配类型 + asset_type?: AssetTypeEnum // 资产类型 + asset_identifier?: string // 资产标识符(ICCID或设备号) + allocation_no?: string // 分配单号 + from_shop_id?: number // 来源店铺ID + to_shop_id?: number // 目标店铺ID + operator_id?: number // 操作人ID + created_at_start?: string // 创建时间起始 + created_at_end?: string // 创建时间结束 +} + +// 资产分配记录 +export interface AssetAllocationRecord { + id: number // 记录ID + allocation_no: string // 分配单号 + allocation_type: AllocationTypeEnum // 分配类型 + allocation_name: string // 分配类型名称 + asset_type: AssetTypeEnum // 资产类型 + asset_type_name: string // 资产类型名称 + asset_id: number // 资产ID + asset_identifier: string // 资产标识符(ICCID或设备号) + from_owner_id: number | null // 来源所有者ID + from_owner_name: string // 来源所有者名称 + from_owner_type: string // 来源所有者类型 + to_owner_id: number // 目标所有者ID + to_owner_name: string // 目标所有者名称 + to_owner_type: string // 目标所有者类型 + operator_id: number // 操作人ID + operator_name: string // 操作人名称 + related_card_count: number // 关联卡数量 + related_device_id: number | null // 关联设备ID + remark: string // 备注 + created_at: string // 创建时间 +} + +// 资产分配记录详情 +export interface AssetAllocationRecordDetail extends AssetAllocationRecord { + related_card_ids: number[] // 关联卡ID列表 +} diff --git a/src/views/asset-management/allocation-record-detail/index.vue b/src/views/asset-management/allocation-record-detail/index.vue new file mode 100644 index 0000000..28b5a4c --- /dev/null +++ b/src/views/asset-management/allocation-record-detail/index.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/src/views/asset-management/asset-assign/index.vue b/src/views/asset-management/asset-assign/index.vue index fcd0bfa..0ce7756 100644 --- a/src/views/asset-management/asset-assign/index.vue +++ b/src/views/asset-management/asset-assign/index.vue @@ -1,352 +1,324 @@ diff --git a/src/views/asset-management/card-list/index.vue b/src/views/asset-management/card-list/index.vue new file mode 100644 index 0000000..5fd8e93 --- /dev/null +++ b/src/views/asset-management/card-list/index.vue @@ -0,0 +1,916 @@ + + + + + diff --git a/src/views/asset-management/task-detail/index.vue b/src/views/asset-management/task-detail/index.vue new file mode 100644 index 0000000..f53bccc --- /dev/null +++ b/src/views/asset-management/task-detail/index.vue @@ -0,0 +1,178 @@ + + + + + diff --git a/src/views/asset-management/task-management/index.vue b/src/views/asset-management/task-management/index.vue new file mode 100644 index 0000000..f4bf013 --- /dev/null +++ b/src/views/asset-management/task-management/index.vue @@ -0,0 +1,329 @@ + + + + + diff --git a/src/views/card-management/card-detail/index.vue b/src/views/card-management/card-detail/index.vue index 63a01e8..7876d6f 100644 --- a/src/views/card-management/card-detail/index.vue +++ b/src/views/card-management/card-detail/index.vue @@ -773,8 +773,9 @@ 'div', { style: - 'display: flex; justify-content: center; align-items: center; gap: 4px; cursor: pointer; color: var(--el-color-primary);', - onClick: (e: MouseEvent) => { + 'display: flex; justify-content: center; align-items: center; gap: 4px; cursor: context-menu; color: var(--el-color-primary);', + onContextmenu: (e: MouseEvent) => { + e.preventDefault() e.stopPropagation() handleOperationClick(row, e) }