fetch(add): 运营商管理
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 2m23s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 2m23s
This commit is contained in:
38
openspec/changes/add-carrier-management/proposal.md
Normal file
38
openspec/changes/add-carrier-management/proposal.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Change: 新增运营商管理功能
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
当前系统缺少对运营商基础信息的统一管理能力。需要提供一个集中的运营商管理模块,用于维护运营商的基础信息(名称、编码、类型、描述等),以便后续在网卡、套餐等业务模块中关联使用。
|
||||||
|
|
||||||
|
运营商管理是物联网卡管理系统的基础数据模块,需要支持对运营商的 CRUD 操作以及状态管理,方便运营人员统一维护运营商信息。
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- 新增运营商管理 API 服务层(CarrierService)
|
||||||
|
- 新增运营商相关 TypeScript 类型定义
|
||||||
|
- 新增运营商管理页面,支持以下功能:
|
||||||
|
- 运营商列表查询(支持按名称、类型、状态筛选)
|
||||||
|
- 创建运营商
|
||||||
|
- 编辑运营商信息
|
||||||
|
- 删除运营商
|
||||||
|
- 状态切换(启用/禁用)
|
||||||
|
- 查看运营商详情
|
||||||
|
- 新增运营商类型常量配置(CMCC/CUCC/CTCC/CBN)
|
||||||
|
- 在账户管理菜单下新增运营商管理入口
|
||||||
|
- 新增路由配置
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- **新增文件**:
|
||||||
|
- `src/api/modules/carrier.ts` - API 服务层
|
||||||
|
- `src/types/api/carrier.ts` - TypeScript 类型定义
|
||||||
|
- `src/views/finance/carrier-management/index.vue` - 运营商管理页面
|
||||||
|
- `src/config/constants/carrierTypes.ts` - 运营商类型常量
|
||||||
|
- **修改文件**:
|
||||||
|
- `src/api/modules/index.ts` - 导出 CarrierService
|
||||||
|
- `src/types/api/index.ts` - 导出 carrier 类型
|
||||||
|
- `src/router/routesAlias.ts` - 新增路由别名
|
||||||
|
- `src/router/routes/asyncRoutes.ts` - 新增路由配置
|
||||||
|
- `src/config/constants/index.ts` - 导出运营商类型常量
|
||||||
|
- **受影响的业务模块**: 账户管理
|
||||||
|
- **不涉及破坏性变更**
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
# Carrier Management Specification
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: 运营商列表查询
|
||||||
|
|
||||||
|
系统 SHALL 提供运营商列表查询功能,支持分页和多条件筛选。
|
||||||
|
|
||||||
|
#### Scenario: 获取所有运营商列表
|
||||||
|
|
||||||
|
- **WHEN** 用户访问运营商管理页面
|
||||||
|
- **THEN** 系统应显示运营商列表,包含运营商 ID、名称、编码、类型、描述、状态、创建时间、更新时间
|
||||||
|
- **AND** 列表应支持分页展示
|
||||||
|
|
||||||
|
#### Scenario: 按运营商名称模糊搜索
|
||||||
|
|
||||||
|
- **WHEN** 用户在搜索框输入运营商名称并点击搜索
|
||||||
|
- **THEN** 系统应返回名称匹配的运营商列表
|
||||||
|
- **AND** 支持模糊匹配
|
||||||
|
|
||||||
|
#### Scenario: 按运营商类型筛选
|
||||||
|
|
||||||
|
- **WHEN** 用户选择运营商类型(CMCC/CUCC/CTCC/CBN)并点击搜索
|
||||||
|
- **THEN** 系统应返回指定类型的运营商列表
|
||||||
|
|
||||||
|
#### Scenario: 按状态筛选
|
||||||
|
|
||||||
|
- **WHEN** 用户选择状态(启用/禁用)并点击搜索
|
||||||
|
- **THEN** 系统应返回指定状态的运营商列表
|
||||||
|
|
||||||
|
#### Scenario: 组合条件筛选
|
||||||
|
|
||||||
|
- **WHEN** 用户同时指定多个筛选条件
|
||||||
|
- **THEN** 系统应返回满足所有条件的运营商列表
|
||||||
|
|
||||||
|
### Requirement: 创建运营商
|
||||||
|
|
||||||
|
系统 SHALL 提供创建运营商的功能,允许运营人员添加新的运营商信息。
|
||||||
|
|
||||||
|
#### Scenario: 成功创建运营商
|
||||||
|
|
||||||
|
- **WHEN** 用户点击"新增运营商"按钮并填写完整的必填信息(运营商编码、运营商名称、运营商类型)
|
||||||
|
- **THEN** 系统应创建新的运营商记录
|
||||||
|
- **AND** 显示成功消息
|
||||||
|
- **AND** 刷新运营商列表
|
||||||
|
|
||||||
|
#### Scenario: 创建运营商时必填字段验证
|
||||||
|
|
||||||
|
- **WHEN** 用户提交创建表单但缺少必填字段
|
||||||
|
- **THEN** 系统应显示验证错误消息
|
||||||
|
- **AND** 阻止表单提交
|
||||||
|
|
||||||
|
#### Scenario: 运营商编码长度验证
|
||||||
|
|
||||||
|
- **WHEN** 用户输入的运营商编码长度不在 1-50 个字符之间
|
||||||
|
- **THEN** 系统应显示验证错误消息
|
||||||
|
|
||||||
|
#### Scenario: 运营商名称长度验证
|
||||||
|
|
||||||
|
- **WHEN** 用户输入的运营商名称长度不在 1-100 个字符之间
|
||||||
|
- **THEN** 系统应显示验证错误消息
|
||||||
|
|
||||||
|
#### Scenario: 运营商描述长度验证
|
||||||
|
|
||||||
|
- **WHEN** 用户输入的运营商描述超过 500 个字符
|
||||||
|
- **THEN** 系统应显示验证错误消息
|
||||||
|
|
||||||
|
### Requirement: 编辑运营商
|
||||||
|
|
||||||
|
系统 SHALL 允许用户编辑现有运营商的信息。
|
||||||
|
|
||||||
|
#### Scenario: 成功编辑运营商
|
||||||
|
|
||||||
|
- **WHEN** 用户点击编辑按钮并修改运营商信息后提交
|
||||||
|
- **THEN** 系统应更新运营商记录
|
||||||
|
- **AND** 显示成功消息
|
||||||
|
- **AND** 刷新运营商列表
|
||||||
|
|
||||||
|
#### Scenario: 编辑时只能修改名称和描述
|
||||||
|
|
||||||
|
- **WHEN** 用户编辑运营商时
|
||||||
|
- **THEN** 系统应只允许修改运营商名称和描述
|
||||||
|
- **AND** 运营商编码、运营商类型应不可修改
|
||||||
|
|
||||||
|
#### Scenario: 编辑时表单验证
|
||||||
|
|
||||||
|
- **WHEN** 用户修改运营商信息但不符合验证规则
|
||||||
|
- **THEN** 系统应显示验证错误消息
|
||||||
|
- **AND** 阻止表单提交
|
||||||
|
|
||||||
|
### Requirement: 删除运营商
|
||||||
|
|
||||||
|
系统 SHALL 允许用户删除运营商记录。
|
||||||
|
|
||||||
|
#### Scenario: 成功删除运营商
|
||||||
|
|
||||||
|
- **WHEN** 用户点击删除按钮并确认删除操作
|
||||||
|
- **THEN** 系统应删除该运营商记录
|
||||||
|
- **AND** 显示成功消息
|
||||||
|
- **AND** 刷新运营商列表
|
||||||
|
|
||||||
|
#### Scenario: 删除前二次确认
|
||||||
|
|
||||||
|
- **WHEN** 用户点击删除按钮
|
||||||
|
- **THEN** 系统应显示确认对话框
|
||||||
|
- **AND** 提示用户确认删除操作
|
||||||
|
|
||||||
|
#### Scenario: 取消删除操作
|
||||||
|
|
||||||
|
- **WHEN** 用户在确认对话框中点击取消
|
||||||
|
- **THEN** 系统应取消删除操作
|
||||||
|
- **AND** 保留运营商记录
|
||||||
|
|
||||||
|
### Requirement: 运营商状态管理
|
||||||
|
|
||||||
|
系统 SHALL 提供运营商状态切换功能(启用/禁用)。
|
||||||
|
|
||||||
|
#### Scenario: 成功切换运营商状态
|
||||||
|
|
||||||
|
- **WHEN** 用户点击状态开关
|
||||||
|
- **THEN** 系统应立即更新运营商状态
|
||||||
|
- **AND** 显示成功消息
|
||||||
|
- **AND** UI 应反映新的状态
|
||||||
|
|
||||||
|
#### Scenario: 状态切换失败时回滚
|
||||||
|
|
||||||
|
- **WHEN** 状态切换请求失败
|
||||||
|
- **THEN** 系统应恢复原状态
|
||||||
|
- **AND** 显示错误消息
|
||||||
|
|
||||||
|
### Requirement: 获取运营商详情
|
||||||
|
|
||||||
|
系统 SHALL 提供获取单个运营商详细信息的功能。
|
||||||
|
|
||||||
|
#### Scenario: 成功获取运营商详情
|
||||||
|
|
||||||
|
- **WHEN** 系统需要获取特定运营商的详细信息
|
||||||
|
- **THEN** 系统应返回该运营商的完整信息
|
||||||
|
- **AND** 包含运营商 ID、编码、名称、类型、描述、状态、创建时间、更新时间
|
||||||
|
|
||||||
|
### Requirement: 运营商类型定义
|
||||||
|
|
||||||
|
系统 SHALL 支持以下运营商类型。
|
||||||
|
|
||||||
|
#### Scenario: 运营商类型枚举
|
||||||
|
|
||||||
|
- **WHEN** 系统处理运营商类型
|
||||||
|
- **THEN** 系统应支持以下类型:
|
||||||
|
- CMCC: 中国移动
|
||||||
|
- CUCC: 中国联通
|
||||||
|
- CTCC: 中国电信
|
||||||
|
- CBN: 中国广电
|
||||||
|
|
||||||
|
### Requirement: 数据展示和格式化
|
||||||
|
|
||||||
|
系统 SHALL 正确展示和格式化运营商数据。
|
||||||
|
|
||||||
|
#### Scenario: 运营商类型显示
|
||||||
|
|
||||||
|
- **WHEN** 在列表中显示运营商类型
|
||||||
|
- **THEN** 系统应将类型代码转换为可读的中文名称
|
||||||
|
- **AND** 使用不同的标签颜色区分不同类型
|
||||||
|
|
||||||
|
#### Scenario: 状态显示
|
||||||
|
|
||||||
|
- **WHEN** 在列表中显示运营商状态
|
||||||
|
- **THEN** 系统应使用开关组件展示状态
|
||||||
|
- **AND** 启用状态显示为绿色"启用"
|
||||||
|
- **AND** 禁用状态显示为红色"禁用"
|
||||||
|
|
||||||
|
#### Scenario: 时间格式化
|
||||||
|
|
||||||
|
- **WHEN** 显示创建时间和更新时间
|
||||||
|
- **THEN** 系统应将时间格式化为 YYYY-MM-DD HH:mm:ss 格式
|
||||||
|
|
||||||
|
### Requirement: 权限控制
|
||||||
|
|
||||||
|
系统 SHALL 对运营商管理功能进行权限控制。
|
||||||
|
|
||||||
|
#### Scenario: 基于角色的访问控制
|
||||||
|
|
||||||
|
- **WHEN** 用户访问运营商管理页面
|
||||||
|
- **THEN** 系统应验证用户是否有访问权限
|
||||||
|
- **AND** 无权限用户应被重定向到 403 页面
|
||||||
|
|
||||||
|
### Requirement: 错误处理
|
||||||
|
|
||||||
|
系统 SHALL 正确处理各种错误情况。
|
||||||
|
|
||||||
|
#### Scenario: API 请求失败处理
|
||||||
|
|
||||||
|
- **WHEN** API 请求失败
|
||||||
|
- **THEN** 系统应显示友好的错误消息
|
||||||
|
- **AND** 不应中断用户操作流程
|
||||||
|
|
||||||
|
#### Scenario: 网络错误处理
|
||||||
|
|
||||||
|
- **WHEN** 发生网络错误
|
||||||
|
- **THEN** 系统应提示用户检查网络连接
|
||||||
|
- **AND** 允许用户重试操作
|
||||||
48
openspec/changes/add-carrier-management/tasks.md
Normal file
48
openspec/changes/add-carrier-management/tasks.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Implementation Tasks
|
||||||
|
|
||||||
|
## 1. 类型定义和常量配置
|
||||||
|
|
||||||
|
- [x] 1.1 创建运营商类型定义文件 `src/types/api/carrier.ts`
|
||||||
|
- [x] 1.2 在 `src/types/api/index.ts` 中导出 carrier 类型
|
||||||
|
- [x] 1.3 创建运营商类型常量 `src/config/constants/carrierTypes.ts`
|
||||||
|
- [x] 1.4 在 `src/config/constants/index.ts` 中导出运营商类型常量
|
||||||
|
|
||||||
|
## 2. API 服务层
|
||||||
|
|
||||||
|
- [x] 2.1 创建 CarrierService 类 `src/api/modules/carrier.ts`
|
||||||
|
- [x] 2.2 实现获取运营商列表接口(支持分页和筛选)
|
||||||
|
- [x] 2.3 实现创建运营商接口
|
||||||
|
- [x] 2.4 实现更新运营商接口
|
||||||
|
- [x] 2.5 实现删除运营商接口
|
||||||
|
- [x] 2.6 实现获取运营商详情接口
|
||||||
|
- [x] 2.7 实现更新运营商状态接口
|
||||||
|
- [x] 2.8 在 `src/api/modules/index.ts` 中导出 CarrierService
|
||||||
|
|
||||||
|
## 3. 路由配置
|
||||||
|
|
||||||
|
- [x] 3.1 在 `src/router/routesAlias.ts` 添加运营商管理路由别名
|
||||||
|
- [x] 3.2 在 `src/router/routes/asyncRoutes.ts` 添加运营商管理路由配置
|
||||||
|
|
||||||
|
## 4. 运营商管理页面
|
||||||
|
|
||||||
|
- [x] 4.1 创建运营商管理页面组件 `src/views/finance/carrier-management/index.vue`
|
||||||
|
- [x] 4.2 实现搜索栏(运营商名称、运营商类型、状态筛选)
|
||||||
|
- [x] 4.3 实现运营商列表表格展示
|
||||||
|
- [x] 4.4 实现列筛选和表格头部工具栏
|
||||||
|
- [x] 4.5 实现分页功能
|
||||||
|
- [x] 4.6 实现新增运营商对话框
|
||||||
|
- [x] 4.7 实现编辑运营商对话框
|
||||||
|
- [x] 4.8 实现删除运营商确认
|
||||||
|
- [x] 4.9 实现状态开关切换
|
||||||
|
- [x] 4.10 实现表单验证规则
|
||||||
|
- [x] 4.11 实现数据加载和错误处理
|
||||||
|
|
||||||
|
## 5. 集成测试
|
||||||
|
|
||||||
|
- [x] 5.1 测试运营商列表查询功能(包括筛选和分页)
|
||||||
|
- [x] 5.2 测试创建运营商功能
|
||||||
|
- [x] 5.3 测试编辑运营商功能
|
||||||
|
- [x] 5.4 测试删除运营商功能
|
||||||
|
- [x] 5.5 测试状态切换功能
|
||||||
|
- [x] 5.6 验证表单验证规则是否正确
|
||||||
|
- [x] 5.7 验证权限控制是否正确
|
||||||
@@ -79,13 +79,21 @@ export class CardService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据ICCID获取单卡信息
|
* 根据ICCID获取单卡信息(旧接口,用于现有功能)
|
||||||
* @param iccid ICCID
|
* @param iccid ICCID
|
||||||
*/
|
*/
|
||||||
static getCardByIccid(iccid: string): Promise<BaseResponse<Card>> {
|
static getCardByIccid(iccid: string): Promise<BaseResponse<Card>> {
|
||||||
return this.getOne<Card>(`/api/cards/iccid/${iccid}`)
|
return this.getOne<Card>(`/api/cards/iccid/${iccid}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过ICCID查询单卡详情(新接口,用于单卡查询页面)
|
||||||
|
* @param iccid ICCID
|
||||||
|
*/
|
||||||
|
static getIotCardDetailByIccid(iccid: string): Promise<BaseResponse<any>> {
|
||||||
|
return this.getOne<any>(`/api/admin/iot-cards/by-iccid/${iccid}`)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 网卡操作(充值、停复机、增减流量等)
|
* 网卡操作(充值、停复机、增减流量等)
|
||||||
* @param params 操作参数
|
* @param params 操作参数
|
||||||
|
|||||||
73
src/api/modules/carrier.ts
Normal file
73
src/api/modules/carrier.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* 运营商管理相关 API
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BaseService } from '../BaseService'
|
||||||
|
import type {
|
||||||
|
Carrier,
|
||||||
|
CarrierQueryParams,
|
||||||
|
CreateCarrierParams,
|
||||||
|
UpdateCarrierParams,
|
||||||
|
UpdateCarrierStatusParams,
|
||||||
|
BaseResponse,
|
||||||
|
PaginationResponse
|
||||||
|
} from '@/types/api'
|
||||||
|
|
||||||
|
export class CarrierService extends BaseService {
|
||||||
|
/**
|
||||||
|
* 获取运营商列表
|
||||||
|
* GET /api/admin/carriers
|
||||||
|
* @param params 查询参数
|
||||||
|
*/
|
||||||
|
static getCarriers(params?: CarrierQueryParams): Promise<PaginationResponse<Carrier>> {
|
||||||
|
return this.getPage<Carrier>('/api/admin/carriers', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建运营商
|
||||||
|
* POST /api/admin/carriers
|
||||||
|
* @param data 运营商数据
|
||||||
|
*/
|
||||||
|
static createCarrier(data: CreateCarrierParams): Promise<BaseResponse<Carrier>> {
|
||||||
|
return this.create<Carrier>('/api/admin/carriers', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新运营商
|
||||||
|
* PUT /api/admin/carriers/{id}
|
||||||
|
* @param id 运营商ID
|
||||||
|
* @param data 运营商数据
|
||||||
|
*/
|
||||||
|
static updateCarrier(id: number, data: UpdateCarrierParams): Promise<BaseResponse<Carrier>> {
|
||||||
|
return this.update<Carrier>(`/api/admin/carriers/${id}`, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除运营商
|
||||||
|
* DELETE /api/admin/carriers/{id}
|
||||||
|
* @param id 运营商ID
|
||||||
|
*/
|
||||||
|
static deleteCarrier(id: number): Promise<BaseResponse> {
|
||||||
|
return this.remove(`/api/admin/carriers/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取运营商详情
|
||||||
|
* GET /api/admin/carriers/{id}
|
||||||
|
* @param id 运营商ID
|
||||||
|
*/
|
||||||
|
static getCarrierDetail(id: number): Promise<BaseResponse<Carrier>> {
|
||||||
|
return this.getOne<Carrier>(`/api/admin/carriers/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新运营商状态
|
||||||
|
* PUT /api/admin/carriers/{id}/status
|
||||||
|
* @param id 运营商ID
|
||||||
|
* @param status 状态 (1:启用, 0:禁用)
|
||||||
|
*/
|
||||||
|
static updateCarrierStatus(id: number, status: number): Promise<BaseResponse> {
|
||||||
|
const data: UpdateCarrierStatusParams = { status }
|
||||||
|
return this.update(`/api/admin/carriers/${id}/status`, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,14 @@ export class DeviceService extends BaseService {
|
|||||||
return this.getOne<Device>(`/api/admin/devices/${id}`)
|
return this.getOne<Device>(`/api/admin/devices/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过设备号查询设备详情
|
||||||
|
* @param imei 设备号(IMEI)
|
||||||
|
*/
|
||||||
|
static getDeviceByImei(imei: string): Promise<BaseResponse<Device>> {
|
||||||
|
return this.getOne<Device>(`/api/admin/devices/by-imei/${imei}`)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除设备
|
* 删除设备
|
||||||
* @param id 设备ID
|
* @param id 设备ID
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export { CustomerAccountService } from './customerAccount'
|
|||||||
export { StorageService } from './storage'
|
export { StorageService } from './storage'
|
||||||
export { AuthorizationService } from './authorization'
|
export { AuthorizationService } from './authorization'
|
||||||
export { DeviceService } from './device'
|
export { DeviceService } from './device'
|
||||||
|
export { CarrierService } from './carrier'
|
||||||
|
|
||||||
// TODO: 按需添加其他业务模块
|
// TODO: 按需添加其他业务模块
|
||||||
// export { PackageService } from './package'
|
// export { PackageService } from './package'
|
||||||
|
|||||||
32
src/config/constants/carrierTypes.ts
Normal file
32
src/config/constants/carrierTypes.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 运营商类型配置
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { CarrierType } from '@/types/api'
|
||||||
|
|
||||||
|
// 运营商类型选项
|
||||||
|
export const CARRIER_TYPE_OPTIONS = [
|
||||||
|
{ label: '中国移动', value: CarrierType.CMCC, color: '#4CAF50' },
|
||||||
|
{ label: '中国联通', value: CarrierType.CUCC, color: '#2196F3' },
|
||||||
|
{ label: '中国电信', value: CarrierType.CTCC, color: '#FF9800' },
|
||||||
|
{ label: '中国广电', value: CarrierType.CBN, color: '#9C27B0' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 运营商类型映射
|
||||||
|
export const CARRIER_TYPE_MAP = CARRIER_TYPE_OPTIONS.reduce(
|
||||||
|
(map, item) => {
|
||||||
|
map[item.value] = item
|
||||||
|
return map
|
||||||
|
},
|
||||||
|
{} as Record<CarrierType, { label: string; value: CarrierType; color: string }>
|
||||||
|
)
|
||||||
|
|
||||||
|
// 获取运营商类型标签
|
||||||
|
export function getCarrierTypeLabel(type: CarrierType): string {
|
||||||
|
return CARRIER_TYPE_MAP[type]?.label || type
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取运营商类型颜色
|
||||||
|
export function getCarrierTypeColor(type: CarrierType): string {
|
||||||
|
return CARRIER_TYPE_MAP[type]?.color || '#666'
|
||||||
|
}
|
||||||
@@ -19,3 +19,6 @@ export * from './status'
|
|||||||
|
|
||||||
// IoT卡相关
|
// IoT卡相关
|
||||||
export * from './iotCard'
|
export * from './iotCard'
|
||||||
|
|
||||||
|
// 运营商类型相关
|
||||||
|
export * from './carrierTypes'
|
||||||
|
|||||||
@@ -834,6 +834,26 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/my-simcard',
|
||||||
|
name: 'MySimcard',
|
||||||
|
component: RoutesAlias.Home,
|
||||||
|
meta: {
|
||||||
|
title: '我的网卡',
|
||||||
|
icon: ''
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'single-card',
|
||||||
|
name: 'SingleCard',
|
||||||
|
component: RoutesAlias.SingleCard,
|
||||||
|
meta: {
|
||||||
|
title: '单卡信息',
|
||||||
|
keepAlive: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/asset-management',
|
path: '/asset-management',
|
||||||
name: 'AssetManagement',
|
name: 'AssetManagement',
|
||||||
@@ -844,11 +864,20 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'single-card',
|
path: 'card-search',
|
||||||
name: 'SingleCard',
|
name: 'CardSearch',
|
||||||
component: RoutesAlias.SingleCard,
|
component: RoutesAlias.CardSearch,
|
||||||
meta: {
|
meta: {
|
||||||
title: 'menus.assetManagement.singleCard',
|
title: '单卡查询',
|
||||||
|
keepAlive: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'device-search',
|
||||||
|
name: 'DeviceSearch',
|
||||||
|
component: RoutesAlias.DeviceSearch,
|
||||||
|
meta: {
|
||||||
|
title: '设备查询',
|
||||||
keepAlive: true
|
keepAlive: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -975,6 +1004,15 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
keepAlive: true
|
keepAlive: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'carrier-management',
|
||||||
|
name: 'CarrierManagement',
|
||||||
|
component: RoutesAlias.CarrierManagement,
|
||||||
|
meta: {
|
||||||
|
title: '运营商管理',
|
||||||
|
keepAlive: true
|
||||||
|
}
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// path: 'my-account',
|
// path: 'my-account',
|
||||||
// name: 'MyAccount',
|
// name: 'MyAccount',
|
||||||
@@ -1102,24 +1140,24 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
keepAlive: true
|
keepAlive: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
path: 'offline-batch-recharge',
|
// path: 'offline-batch-recharge',
|
||||||
name: 'OfflineBatchRecharge',
|
// name: 'OfflineBatchRecharge',
|
||||||
component: RoutesAlias.OfflineBatchRecharge,
|
// component: RoutesAlias.OfflineBatchRecharge,
|
||||||
meta: {
|
// meta: {
|
||||||
title: 'menus.batch.offlineBatchRecharge',
|
// title: 'menus.batch.offlineBatchRecharge',
|
||||||
keepAlive: true
|
// keepAlive: true
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
path: 'card-change-notice',
|
// path: 'card-change-notice',
|
||||||
name: 'CardChangeNotice',
|
// name: 'CardChangeNotice',
|
||||||
component: RoutesAlias.CardChangeNotice,
|
// component: RoutesAlias.CardChangeNotice,
|
||||||
meta: {
|
// meta: {
|
||||||
title: 'menus.batch.cardChangeNotice',
|
// title: 'menus.batch.cardChangeNotice',
|
||||||
keepAlive: true
|
// keepAlive: true
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ export enum RoutesAlias {
|
|||||||
SimCardAssign = '/product/sim-card-assign', // 号卡分配
|
SimCardAssign = '/product/sim-card-assign', // 号卡分配
|
||||||
|
|
||||||
// 资产管理
|
// 资产管理
|
||||||
|
CardSearch = '/asset-management/card-search', // 单卡查询
|
||||||
|
DeviceSearch = '/asset-management/device-search', // 设备查询
|
||||||
StandaloneCardList = '/asset-management/card-list', // 单卡列表(未绑定设备)
|
StandaloneCardList = '/asset-management/card-list', // 单卡列表(未绑定设备)
|
||||||
TaskManagement = '/asset-management/task-management', // 任务管理
|
TaskManagement = '/asset-management/task-management', // 任务管理
|
||||||
TaskDetail = '/asset-management/task-detail', // 任务详情
|
TaskDetail = '/asset-management/task-detail', // 任务详情
|
||||||
@@ -104,6 +106,7 @@ export enum RoutesAlias {
|
|||||||
// 账户管理
|
// 账户管理
|
||||||
CustomerAccountList = '/finance/customer-account', // 客户账号
|
CustomerAccountList = '/finance/customer-account', // 客户账号
|
||||||
MyAccount = '/finance/my-account', // 我的账户
|
MyAccount = '/finance/my-account', // 我的账户
|
||||||
|
CarrierManagement = '/finance/carrier-management', // 运营商管理
|
||||||
|
|
||||||
// 佣金管理
|
// 佣金管理
|
||||||
WithdrawalApproval = '/finance/commission/withdrawal-approval', // 提现审批
|
WithdrawalApproval = '/finance/commission/withdrawal-approval', // 提现审批
|
||||||
|
|||||||
@@ -449,3 +449,32 @@ export interface AssetAllocationRecord {
|
|||||||
export interface AssetAllocationRecordDetail extends AssetAllocationRecord {
|
export interface AssetAllocationRecordDetail extends AssetAllocationRecord {
|
||||||
related_card_ids: number[] // 关联卡ID列表
|
related_card_ids: number[] // 关联卡ID列表
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== 单卡详情查询相关 ==========
|
||||||
|
|
||||||
|
// IoT卡详情响应(对应 /api/admin/iot-cards/by-iccid/{iccid} 接口)
|
||||||
|
export interface IotCardDetailResponse {
|
||||||
|
id: number // 卡ID
|
||||||
|
iccid: string // ICCID
|
||||||
|
imsi: string // IMSI
|
||||||
|
msisdn: string // 卡接入号
|
||||||
|
carrier_id: number // 运营商ID
|
||||||
|
carrier_name: string // 运营商名称
|
||||||
|
carrier_type: string // 运营商类型 (CMCC:中国移动, CUCC:中国联通, CTCC:中国电信, CBN:中国广电)
|
||||||
|
card_type: string // 卡类型
|
||||||
|
card_category: string // 卡业务类型 (normal:普通卡, industry:行业卡)
|
||||||
|
status: number // 状态 (1:在库, 2:已分销, 3:已激活, 4:已停用)
|
||||||
|
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 // 更新时间
|
||||||
|
}
|
||||||
|
|||||||
51
src/types/api/carrier.ts
Normal file
51
src/types/api/carrier.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* 运营商管理相关类型定义
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { PaginationParams } from './common'
|
||||||
|
|
||||||
|
// 运营商类型枚举
|
||||||
|
export enum CarrierType {
|
||||||
|
CMCC = 'CMCC', // 中国移动
|
||||||
|
CUCC = 'CUCC', // 中国联通
|
||||||
|
CTCC = 'CTCC', // 中国电信
|
||||||
|
CBN = 'CBN' // 中国广电
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运营商响应数据
|
||||||
|
export interface Carrier {
|
||||||
|
id: number
|
||||||
|
carrier_code: string
|
||||||
|
carrier_name: string
|
||||||
|
carrier_type: CarrierType
|
||||||
|
description?: string
|
||||||
|
status: number
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运营商列表查询参数
|
||||||
|
export interface CarrierQueryParams extends PaginationParams {
|
||||||
|
carrier_name?: string
|
||||||
|
carrier_type?: CarrierType
|
||||||
|
status?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建运营商请求参数
|
||||||
|
export interface CreateCarrierParams {
|
||||||
|
carrier_code: string
|
||||||
|
carrier_name: string
|
||||||
|
carrier_type: CarrierType
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新运营商请求参数
|
||||||
|
export interface UpdateCarrierParams {
|
||||||
|
carrier_name?: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新运营商状态参数
|
||||||
|
export interface UpdateCarrierStatusParams {
|
||||||
|
status: number
|
||||||
|
}
|
||||||
@@ -52,3 +52,6 @@ export * from './authorization'
|
|||||||
|
|
||||||
// 企业卡授权相关
|
// 企业卡授权相关
|
||||||
export * from './enterpriseCard'
|
export * from './enterpriseCard'
|
||||||
|
|
||||||
|
// 运营商相关
|
||||||
|
export * from './carrier'
|
||||||
|
|||||||
208
src/views/asset-management/card-search/index.vue
Normal file
208
src/views/asset-management/card-search/index.vue
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card-search-page">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<ElCard shadow="never" class="search-card">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>单卡查询</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="search-content">
|
||||||
|
<ElForm :model="searchForm" label-width="100px">
|
||||||
|
<ElFormItem label="ICCID">
|
||||||
|
<ElInput
|
||||||
|
v-model="searchForm.iccid"
|
||||||
|
placeholder="请输入ICCID"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleSearch"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<ElButton type="primary" :loading="loading" @click="handleSearch">
|
||||||
|
查询
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElInput>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
|
||||||
|
<!-- 卡片详情区域 -->
|
||||||
|
<ElCard v-if="cardDetail" shadow="never" class="detail-card" style="margin-top: 16px">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>卡片详情</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<ElDescriptions :column="3" border>
|
||||||
|
<ElDescriptionsItem label="卡ID">{{ cardDetail.id }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="ICCID" :span="2">{{ cardDetail.iccid }}</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="IMSI">{{ cardDetail.imsi || '--' }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="卡接入号">{{ cardDetail.msisdn || '--' }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="运营商">{{ cardDetail.carrier_name }}</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="运营商类型">{{ getCarrierTypeText(cardDetail.carrier_type) }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="卡类型">{{ cardDetail.card_type }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="卡业务类型">{{ getCardCategoryText(cardDetail.card_category) }}</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="状态">
|
||||||
|
<ElTag :type="getStatusTagType(cardDetail.status)">
|
||||||
|
{{ getStatusText(cardDetail.status) }}
|
||||||
|
</ElTag>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="激活状态">
|
||||||
|
<ElTag :type="cardDetail.activation_status === 1 ? 'success' : 'info'">
|
||||||
|
{{ cardDetail.activation_status === 1 ? '已激活' : '未激活' }}
|
||||||
|
</ElTag>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="网络状态">
|
||||||
|
<ElTag :type="cardDetail.network_status === 1 ? 'success' : 'danger'">
|
||||||
|
{{ cardDetail.network_status === 1 ? '开机' : '停机' }}
|
||||||
|
</ElTag>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="实名状态">
|
||||||
|
<ElTag :type="cardDetail.real_name_status === 1 ? 'success' : 'warning'">
|
||||||
|
{{ cardDetail.real_name_status === 1 ? '已实名' : '未实名' }}
|
||||||
|
</ElTag>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="批次号">{{ cardDetail.batch_no }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="供应商">{{ cardDetail.supplier || '--' }}</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="店铺名称">{{ cardDetail.shop_name || '--' }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="成本价">{{ formatPrice(cardDetail.cost_price) }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="分销价">{{ formatPrice(cardDetail.distribute_price) }}</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="累计流量使用">{{ cardDetail.data_usage_mb }} MB</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="激活时间">{{ cardDetail.activated_at || '--' }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="创建时间">{{ cardDetail.created_at }}</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
</ElCard>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<ElEmpty v-if="searched && !cardDetail && !loading" description="未找到相关卡片信息" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { CardService } from '@/api/modules/card'
|
||||||
|
|
||||||
|
defineOptions({ name: 'CardSearch' })
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const searched = ref(false)
|
||||||
|
const searchForm = reactive({
|
||||||
|
iccid: ''
|
||||||
|
})
|
||||||
|
const cardDetail = ref<any>(null)
|
||||||
|
|
||||||
|
// 查询卡片详情
|
||||||
|
const handleSearch = async () => {
|
||||||
|
if (!searchForm.iccid.trim()) {
|
||||||
|
ElMessage.warning('请输入ICCID')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
searched.value = true
|
||||||
|
cardDetail.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await CardService.getIotCardDetailByIccid(searchForm.iccid.trim())
|
||||||
|
if (res.code === 0 && res.data) {
|
||||||
|
cardDetail.value = res.data
|
||||||
|
ElMessage.success('查询成功')
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '查询失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('查询卡片详情失败:', error)
|
||||||
|
ElMessage.error(error?.message || '查询失败,请检查ICCID是否正确')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取运营商类型文本
|
||||||
|
const getCarrierTypeText = (type: string) => {
|
||||||
|
const typeMap: Record<string, string> = {
|
||||||
|
CMCC: '中国移动',
|
||||||
|
CUCC: '中国联通',
|
||||||
|
CTCC: '中国电信',
|
||||||
|
CBN: '中国广电'
|
||||||
|
}
|
||||||
|
return typeMap[type] || type
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取卡业务类型文本
|
||||||
|
const getCardCategoryText = (category: string) => {
|
||||||
|
const categoryMap: Record<string, string> = {
|
||||||
|
normal: '普通卡',
|
||||||
|
industry: '行业卡'
|
||||||
|
}
|
||||||
|
return categoryMap[category] || category
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取状态文本
|
||||||
|
const getStatusText = (status: number) => {
|
||||||
|
const statusMap: Record<number, string> = {
|
||||||
|
1: '在库',
|
||||||
|
2: '已分销',
|
||||||
|
3: '已激活',
|
||||||
|
4: '已停用'
|
||||||
|
}
|
||||||
|
return statusMap[status] || '未知'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取状态标签类型
|
||||||
|
const getStatusTagType = (status: number) => {
|
||||||
|
const typeMap: Record<number, any> = {
|
||||||
|
1: 'info',
|
||||||
|
2: 'warning',
|
||||||
|
3: 'success',
|
||||||
|
4: 'danger'
|
||||||
|
}
|
||||||
|
return typeMap[status] || 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化价格(分转元)
|
||||||
|
const formatPrice = (price: number) => {
|
||||||
|
return `¥${(price / 100).toFixed(2)}`
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card-search-page {
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-content {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-card {
|
||||||
|
animation: fadeIn 0.3s ease-in;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -258,14 +258,14 @@
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
config: {
|
config: {
|
||||||
clearable: true,
|
clearable: true,
|
||||||
placeholder: '请选择状态',
|
placeholder: '请选择状态'
|
||||||
options: [
|
},
|
||||||
{ label: '在库', value: 1 },
|
options: () => [
|
||||||
{ label: '已分销', value: 2 },
|
{ label: '在库', value: 1 },
|
||||||
{ label: '已激活', value: 3 },
|
{ label: '已分销', value: 2 },
|
||||||
{ label: '已停用', value: 4 }
|
{ label: '已激活', value: 3 },
|
||||||
]
|
{ label: '已停用', value: 4 }
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '批次号',
|
label: '批次号',
|
||||||
|
|||||||
152
src/views/asset-management/device-search/index.vue
Normal file
152
src/views/asset-management/device-search/index.vue
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
<template>
|
||||||
|
<div class="device-search-page">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<ElCard shadow="never" class="search-card">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>设备查询</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="search-content">
|
||||||
|
<ElForm :model="searchForm" label-width="100px">
|
||||||
|
<ElFormItem label="设备号(IMEI)">
|
||||||
|
<ElInput
|
||||||
|
v-model="searchForm.imei"
|
||||||
|
placeholder="请输入设备号(IMEI)"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleSearch"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<ElButton type="primary" :loading="loading" @click="handleSearch">
|
||||||
|
查询
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElInput>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
|
||||||
|
<!-- 设备详情区域 -->
|
||||||
|
<ElCard v-if="deviceDetail" shadow="never" class="detail-card" style="margin-top: 16px">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>设备详情</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<ElDescriptions :column="3" border>
|
||||||
|
<ElDescriptionsItem label="设备ID">{{ deviceDetail.id }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="设备号" :span="2">{{ deviceDetail.device_no }}</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="设备名称">{{ deviceDetail.device_name || '--' }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="设备型号">{{ deviceDetail.device_model || '--' }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="设备类型">{{ deviceDetail.device_type || '--' }}</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="制造商">{{ deviceDetail.manufacturer || '--' }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="最大插槽数">{{ deviceDetail.max_sim_slots }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="已绑定卡数量">{{ deviceDetail.bound_card_count }}</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="状态">
|
||||||
|
<ElTag :type="getStatusTagType(deviceDetail.status)">
|
||||||
|
{{ deviceDetail.status_name }}
|
||||||
|
</ElTag>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="店铺名称">{{ deviceDetail.shop_name || '--' }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="批次号">{{ deviceDetail.batch_no }}</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="激活时间">{{ deviceDetail.activated_at || '--' }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="创建时间">{{ deviceDetail.created_at }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="更新时间">{{ deviceDetail.updated_at }}</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
</ElCard>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<ElEmpty v-if="searched && !deviceDetail && !loading" description="未找到相关设备信息" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { DeviceService } from '@/api/modules/device'
|
||||||
|
|
||||||
|
defineOptions({ name: 'DeviceSearch' })
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const searched = ref(false)
|
||||||
|
const searchForm = reactive({
|
||||||
|
imei: ''
|
||||||
|
})
|
||||||
|
const deviceDetail = ref<any>(null)
|
||||||
|
|
||||||
|
// 查询设备详情
|
||||||
|
const handleSearch = async () => {
|
||||||
|
if (!searchForm.imei.trim()) {
|
||||||
|
ElMessage.warning('请输入设备号(IMEI)')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
searched.value = true
|
||||||
|
deviceDetail.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await DeviceService.getDeviceByImei(searchForm.imei.trim())
|
||||||
|
if (res.code === 0 && res.data) {
|
||||||
|
deviceDetail.value = res.data
|
||||||
|
ElMessage.success('查询成功')
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '查询失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('查询设备详情失败:', error)
|
||||||
|
ElMessage.error(error?.message || '查询失败,请检查设备号是否正确')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取状态标签类型
|
||||||
|
const getStatusTagType = (status: number) => {
|
||||||
|
const typeMap: Record<number, any> = {
|
||||||
|
1: 'info', // 在库
|
||||||
|
2: 'warning', // 已分销
|
||||||
|
3: 'success', // 已激活
|
||||||
|
4: 'danger' // 已停用
|
||||||
|
}
|
||||||
|
return typeMap[status] || 'info'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.device-search-page {
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-content {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-card {
|
||||||
|
animation: fadeIn 0.3s ease-in;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -140,18 +140,15 @@
|
|||||||
const contextMenuItems: MenuItemType[] = [
|
const contextMenuItems: MenuItemType[] = [
|
||||||
{
|
{
|
||||||
key: 'cardOperation',
|
key: 'cardOperation',
|
||||||
label: '卡务操作',
|
label: '卡务操作'
|
||||||
icon: ''
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'download',
|
key: 'download',
|
||||||
label: '下载',
|
label: '下载'
|
||||||
icon: ''
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
label: '删除',
|
label: '删除',
|
||||||
icon: '',
|
|
||||||
showLine: true
|
showLine: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
464
src/views/finance/carrier-management/index.vue
Normal file
464
src/views/finance/carrier-management/index.vue
Normal file
@@ -0,0 +1,464 @@
|
|||||||
|
<template>
|
||||||
|
<ArtTableFullScreen>
|
||||||
|
<div class="carrier-page" id="table-full-screen">
|
||||||
|
<!-- 搜索栏 -->
|
||||||
|
<ArtSearchBar
|
||||||
|
v-model:filter="searchForm"
|
||||||
|
:items="searchFormItems"
|
||||||
|
label-width="85"
|
||||||
|
:show-expand="false"
|
||||||
|
@reset="handleReset"
|
||||||
|
@search="handleSearch"
|
||||||
|
></ArtSearchBar>
|
||||||
|
|
||||||
|
<ElCard shadow="never" class="art-table-card">
|
||||||
|
<!-- 表格头部 -->
|
||||||
|
<ArtTableHeader
|
||||||
|
:columnList="columnOptions"
|
||||||
|
v-model:columns="columnChecks"
|
||||||
|
@refresh="handleRefresh"
|
||||||
|
>
|
||||||
|
<template #left>
|
||||||
|
<ElButton @click="showDialog('add')">新增运营商</ElButton>
|
||||||
|
</template>
|
||||||
|
</ArtTableHeader>
|
||||||
|
|
||||||
|
<!-- 表格 -->
|
||||||
|
<ArtTable
|
||||||
|
ref="tableRef"
|
||||||
|
row-key="id"
|
||||||
|
:loading="loading"
|
||||||
|
:data="carrierList"
|
||||||
|
:currentPage="pagination.page"
|
||||||
|
:pageSize="pagination.pageSize"
|
||||||
|
:total="pagination.total"
|
||||||
|
:marginTop="10"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
|
</template>
|
||||||
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 新增/编辑对话框 -->
|
||||||
|
<ElDialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="dialogType === 'add' ? '新增运营商' : '编辑运营商'"
|
||||||
|
width="30%"
|
||||||
|
>
|
||||||
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||||
|
<ElFormItem label="运营商编码" prop="carrier_code">
|
||||||
|
<ElInput
|
||||||
|
v-model="form.carrier_code"
|
||||||
|
placeholder="请输入运营商编码"
|
||||||
|
:disabled="dialogType === 'edit'"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="运营商名称" prop="carrier_name">
|
||||||
|
<ElInput v-model="form.carrier_name" placeholder="请输入运营商名称" />
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="运营商类型" prop="carrier_type">
|
||||||
|
<ElSelect
|
||||||
|
v-model="form.carrier_type"
|
||||||
|
placeholder="请选择运营商类型"
|
||||||
|
style="width: 100%"
|
||||||
|
:disabled="dialogType === 'edit'"
|
||||||
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="item in CARRIER_TYPE_OPTIONS"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="运营商描述" prop="description">
|
||||||
|
<ElInput
|
||||||
|
v-model="form.description"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="请输入运营商描述"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="handleSubmit(formRef)" :loading="submitLoading">
|
||||||
|
提交
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
</ElCard>
|
||||||
|
</div>
|
||||||
|
</ArtTableFullScreen>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { h, watch, nextTick } from 'vue'
|
||||||
|
import { CarrierService } from '@/api/modules'
|
||||||
|
import { ElMessage, ElMessageBox, ElTag, ElSwitch } from 'element-plus'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import type { Carrier, CarrierType } from '@/types/api'
|
||||||
|
import type { SearchFormItem } from '@/types'
|
||||||
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
import {
|
||||||
|
CommonStatus,
|
||||||
|
getStatusText,
|
||||||
|
CARRIER_TYPE_OPTIONS,
|
||||||
|
getCarrierTypeLabel,
|
||||||
|
getCarrierTypeColor
|
||||||
|
} from '@/config/constants'
|
||||||
|
|
||||||
|
defineOptions({ name: 'CarrierManagement' })
|
||||||
|
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
const submitLoading = ref(false)
|
||||||
|
const tableRef = ref()
|
||||||
|
|
||||||
|
// 搜索表单初始值
|
||||||
|
const initialSearchState = {
|
||||||
|
carrier_name: '',
|
||||||
|
carrier_type: null,
|
||||||
|
status: null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = reactive({ ...initialSearchState })
|
||||||
|
|
||||||
|
// 搜索表单配置
|
||||||
|
const searchFormItems: SearchFormItem[] = [
|
||||||
|
{
|
||||||
|
label: '运营商名称',
|
||||||
|
prop: 'carrier_name',
|
||||||
|
type: 'input',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请输入运营商名称'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '运营商类型',
|
||||||
|
prop: 'carrier_type',
|
||||||
|
type: 'select',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请选择运营商类型'
|
||||||
|
},
|
||||||
|
options: () => [
|
||||||
|
{ label: '中国移动', value: 'CMCC' },
|
||||||
|
{ label: '中国联通', value: 'CUCC' },
|
||||||
|
{ label: '中国电信', value: 'CTCC' },
|
||||||
|
{ label: '中国广电', value: 'CBN' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '状态',
|
||||||
|
prop: 'status',
|
||||||
|
type: 'select',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请选择状态'
|
||||||
|
},
|
||||||
|
options: () => [
|
||||||
|
{ label: '启用', value: CommonStatus.ENABLED },
|
||||||
|
{ label: '禁用', value: CommonStatus.DISABLED }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
const pagination = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 列配置
|
||||||
|
const columnOptions = [
|
||||||
|
{ label: '运营商编码', prop: 'carrier_code' },
|
||||||
|
{ label: '运营商名称', prop: 'carrier_name' },
|
||||||
|
{ label: '运营商类型', prop: 'carrier_type' },
|
||||||
|
{ label: '运营商描述', prop: 'description' },
|
||||||
|
{ label: '状态', prop: 'status' },
|
||||||
|
{ label: '创建时间', prop: 'created_at' },
|
||||||
|
{ label: '操作', prop: 'operation' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
const rules = reactive<FormRules>({
|
||||||
|
carrier_code: [
|
||||||
|
{ required: true, message: '请输入运营商编码', trigger: 'blur' },
|
||||||
|
{ min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
carrier_name: [
|
||||||
|
{ required: true, message: '请输入运营商名称', trigger: 'blur' },
|
||||||
|
{ min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
carrier_type: [{ required: true, message: '请选择运营商类型', trigger: 'change' }],
|
||||||
|
description: [{ max: 500, message: '描述不能超过500个字符', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
const form = reactive<any>({
|
||||||
|
id: 0,
|
||||||
|
carrier_code: '',
|
||||||
|
carrier_name: '',
|
||||||
|
carrier_type: null,
|
||||||
|
description: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const carrierList = ref<Carrier[]>([])
|
||||||
|
|
||||||
|
// 动态列配置
|
||||||
|
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||||
|
{
|
||||||
|
prop: 'carrier_code',
|
||||||
|
label: '运营商编码',
|
||||||
|
minWidth: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'carrier_name',
|
||||||
|
label: '运营商名称',
|
||||||
|
minWidth: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'carrier_type',
|
||||||
|
label: '运营商类型',
|
||||||
|
width: 120,
|
||||||
|
formatter: (row: any) => {
|
||||||
|
const color = getCarrierTypeColor(row.carrier_type)
|
||||||
|
return h(
|
||||||
|
ElTag,
|
||||||
|
{ style: `background-color: ${color}; border-color: ${color}; color: #fff;` },
|
||||||
|
() => getCarrierTypeLabel(row.carrier_type)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'description',
|
||||||
|
label: '运营商描述',
|
||||||
|
minWidth: 200,
|
||||||
|
showOverflowTooltip: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'status',
|
||||||
|
label: '状态',
|
||||||
|
width: 100,
|
||||||
|
formatter: (row: any) => {
|
||||||
|
return h(ElSwitch, {
|
||||||
|
modelValue: row.status,
|
||||||
|
activeValue: CommonStatus.ENABLED,
|
||||||
|
inactiveValue: CommonStatus.DISABLED,
|
||||||
|
activeText: getStatusText(CommonStatus.ENABLED),
|
||||||
|
inactiveText: getStatusText(CommonStatus.DISABLED),
|
||||||
|
inlinePrompt: true,
|
||||||
|
'onUpdate:modelValue': (val: string | number | boolean) =>
|
||||||
|
handleStatusChange(row, val as number)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'created_at',
|
||||||
|
label: '创建时间',
|
||||||
|
width: 180,
|
||||||
|
formatter: (row: any) => formatDateTime(row.created_at)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'operation',
|
||||||
|
label: '操作',
|
||||||
|
width: 150,
|
||||||
|
fixed: 'right',
|
||||||
|
formatter: (row: any) => {
|
||||||
|
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
||||||
|
h(ArtButtonTable, {
|
||||||
|
type: 'edit',
|
||||||
|
onClick: () => showDialog('edit', row)
|
||||||
|
}),
|
||||||
|
h(ArtButtonTable, {
|
||||||
|
type: 'delete',
|
||||||
|
onClick: () => deleteCarrier(row)
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getTableData()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听对话框关闭,清除验证状态
|
||||||
|
watch(dialogVisible, (val) => {
|
||||||
|
if (!val) {
|
||||||
|
nextTick(() => {
|
||||||
|
formRef.value?.clearValidate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取运营商列表
|
||||||
|
const getTableData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
page: pagination.page,
|
||||||
|
page_size: pagination.pageSize,
|
||||||
|
carrier_name: searchForm.carrier_name || undefined,
|
||||||
|
carrier_type: searchForm.carrier_type || undefined,
|
||||||
|
status: searchForm.status !== null ? searchForm.status : undefined
|
||||||
|
}
|
||||||
|
const res = await CarrierService.getCarriers(params)
|
||||||
|
if (res.code === 0) {
|
||||||
|
carrierList.value = res.data.items || []
|
||||||
|
pagination.total = res.data.total || 0
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置搜索
|
||||||
|
const handleReset = () => {
|
||||||
|
Object.assign(searchForm, { ...initialSearchState })
|
||||||
|
pagination.page = 1
|
||||||
|
getTableData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleSearch = () => {
|
||||||
|
pagination.page = 1
|
||||||
|
getTableData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新表格
|
||||||
|
const handleRefresh = () => {
|
||||||
|
getTableData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理表格分页变化
|
||||||
|
const handleSizeChange = (newPageSize: number) => {
|
||||||
|
pagination.pageSize = newPageSize
|
||||||
|
getTableData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCurrentChange = (newCurrentPage: number) => {
|
||||||
|
pagination.page = newCurrentPage
|
||||||
|
getTableData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogType = ref('add')
|
||||||
|
|
||||||
|
// 显示新增/编辑对话框
|
||||||
|
const showDialog = (type: string, row?: any) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
dialogType.value = type
|
||||||
|
|
||||||
|
if (type === 'edit' && row) {
|
||||||
|
form.id = row.id
|
||||||
|
form.carrier_code = row.carrier_code
|
||||||
|
form.carrier_name = row.carrier_name
|
||||||
|
form.carrier_type = row.carrier_type
|
||||||
|
form.description = row.description
|
||||||
|
} else {
|
||||||
|
form.id = 0
|
||||||
|
form.carrier_code = ''
|
||||||
|
form.carrier_name = ''
|
||||||
|
form.carrier_type = null
|
||||||
|
form.description = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除表单验证状态
|
||||||
|
nextTick(() => {
|
||||||
|
formRef.value?.clearValidate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除运营商
|
||||||
|
const deleteCarrier = (row: any) => {
|
||||||
|
ElMessageBox.confirm(`确定删除运营商 ${row.carrier_name} 吗?`, '删除确认', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
try {
|
||||||
|
await CarrierService.deleteCarrier(row.id)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
await getTableData()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// 用户取消删除
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return
|
||||||
|
|
||||||
|
await formEl.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
submitLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = {
|
||||||
|
carrier_code: form.carrier_code,
|
||||||
|
carrier_name: form.carrier_name,
|
||||||
|
carrier_type: form.carrier_type as CarrierType,
|
||||||
|
description: form.description || undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dialogType.value === 'add') {
|
||||||
|
await CarrierService.createCarrier(data)
|
||||||
|
ElMessage.success('新增成功')
|
||||||
|
} else {
|
||||||
|
const updateData = {
|
||||||
|
carrier_name: form.carrier_name,
|
||||||
|
description: form.description || undefined
|
||||||
|
}
|
||||||
|
await CarrierService.updateCarrier(form.id, updateData)
|
||||||
|
ElMessage.success('修改成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogVisible.value = false
|
||||||
|
formEl.resetFields()
|
||||||
|
await getTableData()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态切换
|
||||||
|
const handleStatusChange = async (row: any, newStatus: number) => {
|
||||||
|
const oldStatus = row.status
|
||||||
|
// 先更新UI
|
||||||
|
row.status = newStatus
|
||||||
|
try {
|
||||||
|
await CarrierService.updateCarrierStatus(row.id, newStatus)
|
||||||
|
ElMessage.success('状态切换成功')
|
||||||
|
} catch (error) {
|
||||||
|
// 切换失败,恢复原状态
|
||||||
|
row.status = oldStatus
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.carrier-page {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -177,16 +177,32 @@
|
|||||||
<div class="operation-group primary-operations">
|
<div class="operation-group primary-operations">
|
||||||
<h4 class="group-title">主要操作</h4>
|
<h4 class="group-title">主要操作</h4>
|
||||||
<div class="operation-buttons">
|
<div class="operation-buttons">
|
||||||
<ElButton @click="handleOperation('recharge')" class="operation-btn">
|
<ElButton
|
||||||
|
@click="handleOperation('recharge')"
|
||||||
|
:loading="operationLoading"
|
||||||
|
class="operation-btn"
|
||||||
|
>
|
||||||
套餐充值
|
套餐充值
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton @click="handleOperation('activate')" class="operation-btn">
|
<ElButton
|
||||||
|
@click="handleOperation('activate')"
|
||||||
|
:loading="operationLoading"
|
||||||
|
class="operation-btn"
|
||||||
|
>
|
||||||
激活
|
激活
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton @click="handleOperation('suspend')" class="operation-btn">
|
<ElButton
|
||||||
|
@click="handleOperation('suspend')"
|
||||||
|
:loading="operationLoading"
|
||||||
|
class="operation-btn"
|
||||||
|
>
|
||||||
保号停机
|
保号停机
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton @click="handleOperation('resume')" class="operation-btn">
|
<ElButton
|
||||||
|
@click="handleOperation('resume')"
|
||||||
|
:loading="operationLoading"
|
||||||
|
class="operation-btn"
|
||||||
|
>
|
||||||
保号复机
|
保号复机
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -293,20 +309,39 @@
|
|||||||
ElEmpty,
|
ElEmpty,
|
||||||
ElSkeleton,
|
ElSkeleton,
|
||||||
ElDescriptions,
|
ElDescriptions,
|
||||||
ElDescriptionsItem
|
ElDescriptionsItem,
|
||||||
|
ElMessageBox
|
||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
import { EnterpriseService } from '@/api/modules/enterprise'
|
||||||
|
|
||||||
defineOptions({ name: 'SingleCard' })
|
defineOptions({ name: 'SingleCard' })
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
|
const operationLoading = ref(false)
|
||||||
|
|
||||||
|
// 从 URL 获取参数
|
||||||
|
const enterpriseId = computed(() => {
|
||||||
|
const id = route.query.enterpriseId || route.query.enterprise_id
|
||||||
|
return id ? Number(id) : null
|
||||||
|
})
|
||||||
|
|
||||||
|
const cardId = computed(() => {
|
||||||
|
const urlId = route.query.cardId || route.query.card_id
|
||||||
|
if (urlId) {
|
||||||
|
return Number(urlId)
|
||||||
|
}
|
||||||
|
// 如果URL中没有,从卡片数据中获取
|
||||||
|
return cardInfo.value?.id ? Number(cardInfo.value.id) : null
|
||||||
|
})
|
||||||
|
|
||||||
// 卡片信息
|
// 卡片信息
|
||||||
const cardInfo = ref<any>(null)
|
const cardInfo = ref<any>(null)
|
||||||
|
|
||||||
// 模拟卡片数据
|
// 模拟卡片数据
|
||||||
const mockCardData = {
|
const mockCardData = {
|
||||||
|
id: 1, // 卡片ID
|
||||||
iccid: '8986062357007989203',
|
iccid: '8986062357007989203',
|
||||||
accessNumber: '1440012345678',
|
accessNumber: '1440012345678',
|
||||||
imei: '860123456789012',
|
imei: '860123456789012',
|
||||||
@@ -411,7 +446,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 处理操作按钮点击
|
// 处理操作按钮点击
|
||||||
const handleOperation = (operation: string) => {
|
const handleOperation = async (operation: string) => {
|
||||||
if (!cardInfo.value) {
|
if (!cardInfo.value) {
|
||||||
ElMessage.warning('请先查询卡片信息')
|
ElMessage.warning('请先查询卡片信息')
|
||||||
return
|
return
|
||||||
@@ -439,6 +474,75 @@
|
|||||||
deviceOperation: '设备操作'
|
deviceOperation: '设备操作'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保号停机
|
||||||
|
if (operation === 'suspend') {
|
||||||
|
// 检查必需的参数
|
||||||
|
if (!enterpriseId.value || !cardId.value) {
|
||||||
|
ElMessage.error('缺少必需的参数:企业ID或卡片ID')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
`确认要对卡片 ${cardInfo.value.iccid} 进行保号停机操作吗?`,
|
||||||
|
'保号停机确认',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
operationLoading.value = true
|
||||||
|
await EnterpriseService.suspendCard(enterpriseId.value, cardId.value)
|
||||||
|
ElMessage.success('保号停机操作成功')
|
||||||
|
// 刷新卡片信息
|
||||||
|
await fetchCardDetail()
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
ElMessage.error(error?.message || '保号停机操作失败')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
operationLoading.value = false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保号复机
|
||||||
|
if (operation === 'resume') {
|
||||||
|
// 检查必需的参数
|
||||||
|
if (!enterpriseId.value || !cardId.value) {
|
||||||
|
ElMessage.error('缺少必需的参数:企业ID或卡片ID')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
`确认要对卡片 ${cardInfo.value.iccid} 进行保号复机操作吗?`,
|
||||||
|
'保号复机确认',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
operationLoading.value = true
|
||||||
|
await EnterpriseService.resumeCard(enterpriseId.value, cardId.value)
|
||||||
|
ElMessage.success('保号复机操作成功')
|
||||||
|
// 刷新卡片信息
|
||||||
|
await fetchCardDetail()
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
ElMessage.error(error?.message || '保号复机操作失败')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
operationLoading.value = false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他操作暂时只显示提示
|
||||||
ElMessage.info(`执行${operationNames[operation] || operation}操作`)
|
ElMessage.info(`执行${operationNames[operation] || operation}操作`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user