This commit is contained in:
179
src/api/modules/asset.ts
Normal file
179
src/api/modules/asset.ts
Normal file
@@ -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<BaseResponse<AssetResolveResponse>> {
|
||||||
|
return this.getOne<AssetResolveResponse>(`/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<BaseResponse<AssetRealtimeStatusResponse>> {
|
||||||
|
return this.getOne<AssetRealtimeStatusResponse>(
|
||||||
|
`/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<BaseResponse<AssetRefreshResponse>> {
|
||||||
|
return this.post<BaseResponse<AssetRefreshResponse>>(
|
||||||
|
`/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<BaseResponse<AssetPackageUsageRecord[]>> {
|
||||||
|
return this.get<BaseResponse<AssetPackageUsageRecord[]>>(
|
||||||
|
`/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<BaseResponse<AssetCurrentPackageResponse>> {
|
||||||
|
return this.getOne<AssetCurrentPackageResponse>(
|
||||||
|
`/api/admin/assets/${assetType}/${id}/current-package`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 设备停复机操作 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量停机设备下所有已实名卡
|
||||||
|
* POST /api/admin/assets/device/:device_id/stop
|
||||||
|
* 停机成功后设置 1 小时停机保护期(保护期内禁止复机)
|
||||||
|
* @param deviceId 设备ID
|
||||||
|
*/
|
||||||
|
static stopDevice(deviceId: number): Promise<BaseResponse<DeviceStopResponse>> {
|
||||||
|
return this.post<BaseResponse<DeviceStopResponse>>(
|
||||||
|
`/api/admin/assets/device/${deviceId}/stop`,
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量复机设备下所有已实名卡
|
||||||
|
* POST /api/admin/assets/device/:device_id/start
|
||||||
|
* 复机成功后设置 1 小时复机保护期(保护期内禁止停机)
|
||||||
|
* @param deviceId 设备ID
|
||||||
|
*/
|
||||||
|
static startDevice(deviceId: number): Promise<BaseResponse<void>> {
|
||||||
|
return this.post<BaseResponse<void>>(`/api/admin/assets/device/${deviceId}/start`, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 单卡停复机操作 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动停机单张卡(通过 ICCID)
|
||||||
|
* POST /api/admin/assets/card/:iccid/stop
|
||||||
|
* 若卡绑定的设备在复机保护期内,返回 403
|
||||||
|
* @param iccid ICCID
|
||||||
|
*/
|
||||||
|
static stopCard(iccid: string): Promise<BaseResponse<void>> {
|
||||||
|
return this.post<BaseResponse<void>>(`/api/admin/assets/card/${iccid}/stop`, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动复机单张卡(通过 ICCID)
|
||||||
|
* POST /api/admin/assets/card/:iccid/start
|
||||||
|
* 若卡绑定的设备在停机保护期内,返回 403
|
||||||
|
* @param iccid ICCID
|
||||||
|
*/
|
||||||
|
static startCard(iccid: string): Promise<BaseResponse<void>> {
|
||||||
|
return this.post<BaseResponse<void>>(`/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<BaseResponse<AssetWalletResponse>> {
|
||||||
|
return this.getOne<AssetWalletResponse>(`/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<BaseResponse<AssetWalletTransactionListResponse>> {
|
||||||
|
return this.get<BaseResponse<AssetWalletTransactionListResponse>>(
|
||||||
|
`/api/admin/assets/${assetType}/${id}/wallet/transactions`,
|
||||||
|
params
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,9 +19,6 @@ import type {
|
|||||||
BaseResponse,
|
BaseResponse,
|
||||||
PaginationResponse,
|
PaginationResponse,
|
||||||
ListResponse,
|
ListResponse,
|
||||||
GatewayFlowUsageResponse,
|
|
||||||
GatewayRealnameStatusResponse,
|
|
||||||
GatewayCardStatusResponse,
|
|
||||||
GatewayRealnameLinkResponse
|
GatewayRealnameLinkResponse
|
||||||
} from '@/types/api'
|
} from '@/types/api'
|
||||||
|
|
||||||
@@ -91,7 +88,8 @@ export class CardService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过ICCID查询单卡详情(新接口,用于单卡查询页面)
|
* 通过ICCID查询单卡详情(旧接口,已废弃)
|
||||||
|
* @deprecated 使用 AssetService.resolveAsset 替代
|
||||||
* @param iccid ICCID
|
* @param iccid ICCID
|
||||||
*/
|
*/
|
||||||
static getIotCardDetailByIccid(iccid: string): Promise<BaseResponse<any>> {
|
static getIotCardDetailByIccid(iccid: string): Promise<BaseResponse<any>> {
|
||||||
@@ -374,36 +372,6 @@ export class CardService extends BaseService {
|
|||||||
|
|
||||||
// ========== IoT卡网关操作相关 ==========
|
// ========== IoT卡网关操作相关 ==========
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询流量使用
|
|
||||||
* @param iccid ICCID
|
|
||||||
*/
|
|
||||||
static getGatewayFlow(iccid: string): Promise<BaseResponse<GatewayFlowUsageResponse>> {
|
|
||||||
return this.get<BaseResponse<GatewayFlowUsageResponse>>(
|
|
||||||
`/api/admin/iot-cards/${iccid}/gateway-flow`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询实名认证状态
|
|
||||||
* @param iccid ICCID
|
|
||||||
*/
|
|
||||||
static getGatewayRealname(iccid: string): Promise<BaseResponse<GatewayRealnameStatusResponse>> {
|
|
||||||
return this.get<BaseResponse<GatewayRealnameStatusResponse>>(
|
|
||||||
`/api/admin/iot-cards/${iccid}/gateway-realname`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询卡实时状态
|
|
||||||
* @param iccid ICCID
|
|
||||||
*/
|
|
||||||
static getGatewayStatus(iccid: string): Promise<BaseResponse<GatewayCardStatusResponse>> {
|
|
||||||
return this.get<BaseResponse<GatewayCardStatusResponse>>(
|
|
||||||
`/api/admin/iot-cards/${iccid}/gateway-status`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取实名认证链接
|
* 获取实名认证链接
|
||||||
* @param iccid ICCID
|
* @param iccid ICCID
|
||||||
@@ -413,20 +381,4 @@ export class CardService extends BaseService {
|
|||||||
`/api/admin/iot-cards/${iccid}/realname-link`
|
`/api/admin/iot-cards/${iccid}/realname-link`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 启用物联网卡(复机)
|
|
||||||
* @param iccid ICCID
|
|
||||||
*/
|
|
||||||
static startCard(iccid: string): Promise<BaseResponse> {
|
|
||||||
return this.post<BaseResponse>(`/api/admin/iot-cards/${iccid}/start`, {})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停用物联网卡(停机)
|
|
||||||
* @param iccid ICCID
|
|
||||||
*/
|
|
||||||
static stopCard(iccid: string): Promise<BaseResponse> {
|
|
||||||
return this.post<BaseResponse>(`/api/admin/iot-cards/${iccid}/stop`, {})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,14 +46,6 @@ 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}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过ICCID查询设备详情
|
* 通过ICCID查询设备详情
|
||||||
* @param iccid ICCID
|
* @param iccid ICCID
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export { PackageSeriesService } from './packageSeries'
|
|||||||
export { PackageManageService } from './packageManage'
|
export { PackageManageService } from './packageManage'
|
||||||
export { ShopSeriesGrantService } from './shopSeriesGrant'
|
export { ShopSeriesGrantService } from './shopSeriesGrant'
|
||||||
export { OrderService } from './order'
|
export { OrderService } from './order'
|
||||||
|
export { AssetService } from './asset'
|
||||||
|
|
||||||
// TODO: 按需添加其他业务模块
|
// TODO: 按需添加其他业务模块
|
||||||
// export { SettingService } from './setting'
|
// export { SettingService } from './setting'
|
||||||
|
|||||||
277
src/types/api/asset.ts
Normal file
277
src/types/api/asset.ts
Normal file
@@ -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)
|
||||||
|
}
|
||||||
@@ -312,6 +312,7 @@ export interface StandaloneCardQueryParams extends PaginationParams {
|
|||||||
shop_id?: number // 分销商ID
|
shop_id?: number // 分销商ID
|
||||||
iccid?: string // ICCID(模糊查询)
|
iccid?: string // ICCID(模糊查询)
|
||||||
msisdn?: string // 卡接入号(模糊查询)
|
msisdn?: string // 卡接入号(模糊查询)
|
||||||
|
virtual_no?: string // 虚拟号(模糊查询)
|
||||||
batch_no?: string // 批次号
|
batch_no?: string // 批次号
|
||||||
package_id?: number // 套餐ID
|
package_id?: number // 套餐ID
|
||||||
is_distributed?: boolean // 是否已分销
|
is_distributed?: boolean // 是否已分销
|
||||||
@@ -327,6 +328,7 @@ export interface StandaloneIotCard {
|
|||||||
iccid: string // ICCID
|
iccid: string // ICCID
|
||||||
imsi?: string // IMSI (可选)
|
imsi?: string // IMSI (可选)
|
||||||
msisdn?: string // 卡接入号 (可选)
|
msisdn?: string // 卡接入号 (可选)
|
||||||
|
virtual_no?: string // 虚拟号(可空)
|
||||||
carrier_id: number // 运营商ID
|
carrier_id: number // 运营商ID
|
||||||
carrier_type: string // 运营商类型 (CMCC:中国移动, CUCC:中国联通, CTCC:中国电信, CBN:中国广电)
|
carrier_type: string // 运营商类型 (CMCC:中国移动, CUCC:中国联通, CTCC:中国电信, CBN:中国广电)
|
||||||
carrier_name: string // 运营商名称
|
carrier_name: string // 运营商名称
|
||||||
@@ -465,6 +467,7 @@ export interface IotCardDetailResponse {
|
|||||||
iccid: string // ICCID
|
iccid: string // ICCID
|
||||||
imsi: string // IMSI
|
imsi: string // IMSI
|
||||||
msisdn: string // 卡接入号
|
msisdn: string // 卡接入号
|
||||||
|
virtual_no?: string // 虚拟号(可空)
|
||||||
carrier_id: number // 运营商ID
|
carrier_id: number // 运营商ID
|
||||||
carrier_name: string // 运营商名称
|
carrier_name: string // 运营商名称
|
||||||
carrier_type: string // 运营商类型 (CMCC:中国移动, CUCC:中国联通, CTCC:中国电信, CBN:中国广电)
|
carrier_type: string // 运营商类型 (CMCC:中国移动, CUCC:中国联通, CTCC:中国电信, CBN:中国广电)
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ export interface ShopCommissionRecordItem {
|
|||||||
order_no?: string // 订单号
|
order_no?: string // 订单号
|
||||||
order_created_at?: string // 订单创建时间
|
order_created_at?: string // 订单创建时间
|
||||||
iccid?: string // ICCID
|
iccid?: string // ICCID
|
||||||
device_no?: string // 设备号
|
virtual_no?: string // 虚拟号(原 device_no)
|
||||||
created_at: string // 佣金入账时间
|
created_at: string // 佣金入账时间
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export enum DeviceStatus {
|
|||||||
// 设备信息
|
// 设备信息
|
||||||
export interface Device {
|
export interface Device {
|
||||||
id: number // 设备ID
|
id: number // 设备ID
|
||||||
device_no: string // 设备号
|
virtual_no: string // 虚拟号(原 device_no)
|
||||||
device_name: string // 设备名称
|
device_name: string // 设备名称
|
||||||
device_model: string // 设备型号
|
device_model: string // 设备型号
|
||||||
device_type: string // 设备类型
|
device_type: string // 设备类型
|
||||||
@@ -39,7 +39,7 @@ export interface Device {
|
|||||||
|
|
||||||
// 设备查询参数
|
// 设备查询参数
|
||||||
export interface DeviceQueryParams extends PaginationParams {
|
export interface DeviceQueryParams extends PaginationParams {
|
||||||
device_no?: string // 设备号(模糊查询)
|
virtual_no?: string // 虚拟号(模糊查询,原 device_no)
|
||||||
device_name?: string // 设备名称(模糊查询)
|
device_name?: string // 设备名称(模糊查询)
|
||||||
status?: DeviceStatus // 状态
|
status?: DeviceStatus // 状态
|
||||||
shop_id?: number | null // 店铺ID (NULL表示平台库存)
|
shop_id?: number | null // 店铺ID (NULL表示平台库存)
|
||||||
@@ -107,7 +107,7 @@ export interface AllocateDevicesRequest {
|
|||||||
// 分配失败项
|
// 分配失败项
|
||||||
export interface AllocationDeviceFailedItem {
|
export interface AllocationDeviceFailedItem {
|
||||||
device_id: number // 设备ID
|
device_id: number // 设备ID
|
||||||
device_no: string // 设备号
|
virtual_no: string // 虚拟号(原 device_no)
|
||||||
reason: string // 失败原因
|
reason: string // 失败原因
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ export interface DeviceImportTaskListResponse {
|
|||||||
// 导入结果详细项
|
// 导入结果详细项
|
||||||
export interface DeviceImportResultItem {
|
export interface DeviceImportResultItem {
|
||||||
line: number // 行号
|
line: number // 行号
|
||||||
device_no: string // 设备号
|
virtual_no: string // 虚拟号(原 device_no)
|
||||||
reason: string // 原因
|
reason: string // 原因
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ export interface BatchSetDeviceSeriesBindingRequest {
|
|||||||
// 设备套餐系列绑定失败项
|
// 设备套餐系列绑定失败项
|
||||||
export interface DeviceSeriesBindingFailedItem {
|
export interface DeviceSeriesBindingFailedItem {
|
||||||
device_id: number // 设备ID
|
device_id: number // 设备ID
|
||||||
device_no: string // 设备号
|
virtual_no: string // 虚拟号(原 device_no)
|
||||||
reason: string // 失败原因
|
reason: string // 失败原因
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ export interface FailedItem {
|
|||||||
export interface AllocatedDevice {
|
export interface AllocatedDevice {
|
||||||
/** 设备ID */
|
/** 设备ID */
|
||||||
device_id: number
|
device_id: number
|
||||||
/** 设备号 */
|
/** 虚拟号(原 device_no) */
|
||||||
device_no: string
|
virtual_no: string
|
||||||
/** 卡数量 */
|
/** 卡数量 */
|
||||||
card_count: number
|
card_count: number
|
||||||
/** 卡ICCID列表 */
|
/** 卡ICCID列表 */
|
||||||
@@ -92,8 +92,8 @@ export interface DeviceBundleCard {
|
|||||||
export interface DeviceBundle {
|
export interface DeviceBundle {
|
||||||
/** 设备ID */
|
/** 设备ID */
|
||||||
device_id: number
|
device_id: number
|
||||||
/** 设备号 */
|
/** 虚拟号(原 device_no) */
|
||||||
device_no: string
|
virtual_no: string
|
||||||
/** 触发卡(用户选择的卡) */
|
/** 触发卡(用户选择的卡) */
|
||||||
trigger_card: DeviceBundleCard
|
trigger_card: DeviceBundleCard
|
||||||
/** 连带卡(同设备的其他卡) */
|
/** 连带卡(同设备的其他卡) */
|
||||||
@@ -158,8 +158,8 @@ export interface EnterpriseCardItem {
|
|||||||
package_name?: string
|
package_name?: string
|
||||||
/** 设备ID */
|
/** 设备ID */
|
||||||
device_id?: number | null
|
device_id?: number | null
|
||||||
/** 设备号 */
|
/** 虚拟号(原 device_no) */
|
||||||
device_no?: string
|
virtual_no?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -176,8 +176,8 @@ export interface EnterpriseCardListParams {
|
|||||||
carrier_id?: number
|
carrier_id?: number
|
||||||
/** ICCID(模糊查询) */
|
/** ICCID(模糊查询) */
|
||||||
iccid?: string
|
iccid?: string
|
||||||
/** 设备号(模糊查询) */
|
/** 虚拟号(模糊查询,原 device_no) */
|
||||||
device_no?: string
|
virtual_no?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -200,8 +200,8 @@ export interface EnterpriseCardPageResult {
|
|||||||
export interface RecalledDevice {
|
export interface RecalledDevice {
|
||||||
/** 设备ID */
|
/** 设备ID */
|
||||||
device_id: number
|
device_id: number
|
||||||
/** 设备号 */
|
/** 虚拟号(原 device_no) */
|
||||||
device_no: string
|
virtual_no: string
|
||||||
/** 卡数量 */
|
/** 卡数量 */
|
||||||
card_count: number
|
card_count: number
|
||||||
/** 卡ICCID列表 */
|
/** 卡ICCID列表 */
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { PaginationParams } from '@/types'
|
|||||||
*/
|
*/
|
||||||
export interface EnterpriseDeviceItem {
|
export interface EnterpriseDeviceItem {
|
||||||
device_id: number // 设备ID
|
device_id: number // 设备ID
|
||||||
device_no: string // 设备号
|
virtual_no: string // 虚拟号(原 device_no)
|
||||||
device_name: string // 设备名称
|
device_name: string // 设备名称
|
||||||
device_model: string // 设备型号
|
device_model: string // 设备型号
|
||||||
card_count: number // 绑定卡数量
|
card_count: number // 绑定卡数量
|
||||||
@@ -22,7 +22,7 @@ export interface EnterpriseDeviceItem {
|
|||||||
* 企业设备列表查询参数
|
* 企业设备列表查询参数
|
||||||
*/
|
*/
|
||||||
export interface EnterpriseDeviceListParams extends PaginationParams {
|
export interface EnterpriseDeviceListParams extends PaginationParams {
|
||||||
device_no?: string // 设备号(模糊搜索)
|
virtual_no?: string // 虚拟号(模糊搜索,原 device_no)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,7 +39,7 @@ export interface EnterpriseDevicePageResult {
|
|||||||
* 授权设备请求参数
|
* 授权设备请求参数
|
||||||
*/
|
*/
|
||||||
export interface AllocateDevicesRequest {
|
export interface AllocateDevicesRequest {
|
||||||
device_nos: string[] | null // 设备号列表(最多100个)
|
virtual_nos: string[] | null // 虚拟号列表(最多100个,原 device_nos)
|
||||||
remark?: string // 授权备注
|
remark?: string // 授权备注
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ export interface AllocateDevicesRequest {
|
|||||||
*/
|
*/
|
||||||
export interface AuthorizedDeviceItem {
|
export interface AuthorizedDeviceItem {
|
||||||
device_id: number // 设备ID
|
device_id: number // 设备ID
|
||||||
device_no: string // 设备号
|
virtual_no: string // 虚拟号(原 device_no)
|
||||||
card_count: number // 绑定卡数量
|
card_count: number // 绑定卡数量
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ export interface AuthorizedDeviceItem {
|
|||||||
* 失败设备项
|
* 失败设备项
|
||||||
*/
|
*/
|
||||||
export interface FailedDeviceItem {
|
export interface FailedDeviceItem {
|
||||||
device_no: string // 设备号
|
virtual_no: string // 虚拟号(原 device_no)
|
||||||
reason: string // 失败原因
|
reason: string // 失败原因
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ export interface AllocateDevicesResponse {
|
|||||||
* 撤销设备授权请求参数
|
* 撤销设备授权请求参数
|
||||||
*/
|
*/
|
||||||
export interface RecallDevicesRequest {
|
export interface RecallDevicesRequest {
|
||||||
device_nos: string[] | null // 设备号列表(最多100个)
|
virtual_nos: string[] | null // 虚拟号列表(最多100个,原 device_nos)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -76,3 +76,6 @@ export * from './carrier'
|
|||||||
|
|
||||||
// 订单相关
|
// 订单相关
|
||||||
export * from './order'
|
export * from './order'
|
||||||
|
|
||||||
|
// 资产管理相关
|
||||||
|
export * from './asset'
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ export interface PackageResponse {
|
|||||||
data_reset_cycle?: string // 流量重置周期 (daily:每日, monthly:每月, yearly:每年, none:不重置)
|
data_reset_cycle?: string // 流量重置周期 (daily:每日, monthly:每月, yearly:每年, none:不重置)
|
||||||
real_data_mb?: number // 真流量额度(MB)
|
real_data_mb?: number // 真流量额度(MB)
|
||||||
virtual_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_virtual_data?: boolean // 是否启用虚流量
|
||||||
enable_realname_activation?: boolean // 是否启用实名激活 (true:需实名后激活, false:立即激活)
|
enable_realname_activation?: boolean // 是否启用实名激活 (true:需实名后激活, false:立即激活)
|
||||||
cost_price?: number // 成本价(分)
|
cost_price?: number // 成本价(分)
|
||||||
|
|||||||
@@ -318,7 +318,7 @@
|
|||||||
// 搜索表单初始值
|
// 搜索表单初始值
|
||||||
const initialSearchState = {
|
const initialSearchState = {
|
||||||
iccid: '',
|
iccid: '',
|
||||||
device_no: '',
|
virtual_no: '',
|
||||||
carrier_id: undefined as number | undefined,
|
carrier_id: undefined as number | undefined,
|
||||||
status: undefined as number | undefined
|
status: undefined as number | undefined
|
||||||
}
|
}
|
||||||
@@ -354,7 +354,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '设备号',
|
label: '设备号',
|
||||||
prop: 'device_no',
|
prop: 'virtual_no',
|
||||||
type: 'input',
|
type: 'input',
|
||||||
config: {
|
config: {
|
||||||
clearable: true,
|
clearable: true,
|
||||||
@@ -460,7 +460,7 @@
|
|||||||
const columnOptions = [
|
const columnOptions = [
|
||||||
{ label: 'ICCID', prop: 'iccid' },
|
{ label: 'ICCID', prop: 'iccid' },
|
||||||
{ label: '卡接入号', prop: 'msisdn' },
|
{ label: '卡接入号', prop: 'msisdn' },
|
||||||
{ label: '设备号', prop: 'device_no' },
|
{ label: '设备号', prop: 'virtual_no' },
|
||||||
{ label: '运营商ID', prop: 'carrier_id' },
|
{ label: '运营商ID', prop: 'carrier_id' },
|
||||||
{ label: '运营商', prop: 'carrier_name' },
|
{ label: '运营商', prop: 'carrier_name' },
|
||||||
{ label: '套餐名称', prop: 'package_name' },
|
{ label: '套餐名称', prop: 'package_name' },
|
||||||
@@ -527,7 +527,7 @@
|
|||||||
width: 130
|
width: 130
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'device_no',
|
prop: 'virtual_no',
|
||||||
label: '设备号',
|
label: '设备号',
|
||||||
width: 150
|
width: 150
|
||||||
},
|
},
|
||||||
@@ -612,7 +612,7 @@
|
|||||||
page: pagination.page,
|
page: pagination.page,
|
||||||
page_size: pagination.pageSize,
|
page_size: pagination.pageSize,
|
||||||
iccid: searchForm.iccid || undefined,
|
iccid: searchForm.iccid || undefined,
|
||||||
device_no: searchForm.device_no || undefined,
|
virtual_no: searchForm.virtual_no || undefined,
|
||||||
carrier_id: searchForm.carrier_id,
|
carrier_id: searchForm.carrier_id,
|
||||||
status: searchForm.status
|
status: searchForm.status
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,42 +87,36 @@
|
|||||||
</ElRow>
|
</ElRow>
|
||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="省份" prop="province">
|
<ElFormItem label="所在地区" prop="region">
|
||||||
<ElInput v-model="form.province" placeholder="请输入省份" />
|
<ElCascader
|
||||||
</ElFormItem>
|
v-model="form.region"
|
||||||
</ElCol>
|
:options="regionData"
|
||||||
<ElCol :span="12">
|
placeholder="请选择省/市/区"
|
||||||
<ElFormItem label="城市" prop="city">
|
clearable
|
||||||
<ElInput v-model="form.city" placeholder="请输入城市" />
|
filterable
|
||||||
</ElFormItem>
|
style="width: 100%"
|
||||||
</ElCol>
|
@change="handleRegionChange"
|
||||||
</ElRow>
|
/>
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol :span="12">
|
|
||||||
<ElFormItem label="区县" prop="district">
|
|
||||||
<ElInput v-model="form.district" placeholder="请输入区县" />
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<!-- 只有非代理账号才显示归属店铺选择 -->
|
<!-- 只有非代理账号才显示归属店铺选择 -->
|
||||||
<ElCol :span="12" v-if="!isAgentAccount">
|
<ElCol :span="12" v-if="!isAgentAccount">
|
||||||
<ElFormItem label="归属店铺" prop="owner_shop_id">
|
<ElFormItem label="归属店铺" prop="owner_shop_id">
|
||||||
<ElSelect
|
<ElTreeSelect
|
||||||
v-model="form.owner_shop_id"
|
v-model="form.owner_shop_id"
|
||||||
placeholder="请选择店铺"
|
:data="shopTreeData"
|
||||||
|
placeholder="请选择归属店铺"
|
||||||
filterable
|
filterable
|
||||||
remote
|
|
||||||
:remote-method="searchShops"
|
|
||||||
:loading="shopLoading"
|
|
||||||
clearable
|
clearable
|
||||||
|
check-strictly
|
||||||
|
:render-after-expand="false"
|
||||||
|
:props="{
|
||||||
|
label: 'shop_name',
|
||||||
|
value: 'id',
|
||||||
|
children: 'children'
|
||||||
|
}"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
|
||||||
<ElOption
|
|
||||||
v-for="shop in shopList"
|
|
||||||
:key="shop.id"
|
|
||||||
:label="shop.shop_name"
|
|
||||||
:value="shop.id"
|
|
||||||
/>
|
/>
|
||||||
</ElSelect>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
@@ -220,7 +214,7 @@
|
|||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { EnterpriseService, ShopService } from '@/api/modules'
|
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 { FormInstance, FormRules } from 'element-plus'
|
||||||
import type { EnterpriseItem, ShopResponse } from '@/types/api'
|
import type { EnterpriseItem, ShopResponse } from '@/types/api'
|
||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
@@ -233,6 +227,7 @@
|
|||||||
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
import { regionData } from '@/utils/constants/regionData'
|
||||||
|
|
||||||
defineOptions({ name: 'EnterpriseCustomer' })
|
defineOptions({ name: 'EnterpriseCustomer' })
|
||||||
|
|
||||||
@@ -261,7 +256,7 @@
|
|||||||
const shopLoading = ref(false)
|
const shopLoading = ref(false)
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const currentEnterpriseId = ref<number>(0)
|
const currentEnterpriseId = ref<number>(0)
|
||||||
const shopList = ref<ShopResponse[]>([])
|
const shopTreeData = ref<ShopResponse[]>([])
|
||||||
|
|
||||||
// 右键菜单
|
// 右键菜单
|
||||||
const enterpriseOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
const enterpriseOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
@@ -388,6 +383,7 @@
|
|||||||
enterprise_name: '',
|
enterprise_name: '',
|
||||||
business_license: '',
|
business_license: '',
|
||||||
legal_person: '',
|
legal_person: '',
|
||||||
|
region: [] as string[],
|
||||||
province: '',
|
province: '',
|
||||||
city: '',
|
city: '',
|
||||||
district: '',
|
district: '',
|
||||||
@@ -518,20 +514,19 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 加载店铺列表(默认加载20条)
|
// 加载店铺列表(获取所有店铺构建树形结构)
|
||||||
const loadShopList = async (shopName?: string) => {
|
const loadShopList = async () => {
|
||||||
shopLoading.value = true
|
shopLoading.value = true
|
||||||
try {
|
try {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 20
|
page_size: 9999 // 获取所有数据用于构建树形结构
|
||||||
}
|
|
||||||
if (shopName) {
|
|
||||||
params.shop_name = shopName
|
|
||||||
}
|
}
|
||||||
const res = await ShopService.getShops(params)
|
const res = await ShopService.getShops(params)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
shopList.value = res.data.items || []
|
const items = res.data.items || []
|
||||||
|
// 构建树形数据
|
||||||
|
shopTreeData.value = buildTreeData(items)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取店铺列表失败:', error)
|
console.error('获取店铺列表失败:', error)
|
||||||
@@ -540,13 +535,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索店铺
|
// 构建树形数据
|
||||||
const searchShops = (query: string) => {
|
const buildTreeData = (items: ShopResponse[]) => {
|
||||||
if (query) {
|
const map = new Map<number, ShopResponse & { children?: ShopResponse[] }>()
|
||||||
loadShopList(query)
|
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 {
|
} else {
|
||||||
loadShopList()
|
// 没有父节点或父节点不存在,作为根节点
|
||||||
|
tree.push(node)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return tree
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取企业客户列表
|
// 获取企业客户列表
|
||||||
@@ -602,6 +615,19 @@
|
|||||||
getTableData()
|
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')
|
const dialogType = ref('add')
|
||||||
|
|
||||||
// 显示新增/编辑对话框
|
// 显示新增/编辑对话框
|
||||||
@@ -617,6 +643,12 @@
|
|||||||
form.province = row.province
|
form.province = row.province
|
||||||
form.city = row.city
|
form.city = row.city
|
||||||
form.district = row.district
|
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.address = row.address
|
||||||
form.contact_name = row.contact_name
|
form.contact_name = row.contact_name
|
||||||
form.contact_phone = row.contact_phone
|
form.contact_phone = row.contact_phone
|
||||||
@@ -628,6 +660,7 @@
|
|||||||
form.enterprise_name = ''
|
form.enterprise_name = ''
|
||||||
form.business_license = ''
|
form.business_license = ''
|
||||||
form.legal_person = ''
|
form.legal_person = ''
|
||||||
|
form.region = []
|
||||||
form.province = ''
|
form.province = ''
|
||||||
form.city = ''
|
form.city = ''
|
||||||
form.district = ''
|
form.district = ''
|
||||||
|
|||||||
@@ -122,7 +122,7 @@
|
|||||||
:key="item.device_id"
|
:key="item.device_id"
|
||||||
style="margin-bottom: 8px; font-size: 12px; color: #f56c6c"
|
style="margin-bottom: 8px; font-size: 12px; color: #f56c6c"
|
||||||
>
|
>
|
||||||
设备号: {{ item.device_no }} - {{ item.reason }}
|
设备号: {{ item.virtual_no }} - {{ item.reason }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
:key="item.device_id"
|
:key="item.device_id"
|
||||||
style="margin-bottom: 8px; font-size: 12px; color: #f56c6c"
|
style="margin-bottom: 8px; font-size: 12px; color: #f56c6c"
|
||||||
>
|
>
|
||||||
设备号: {{ item.device_no }} - {{ item.reason }}
|
设备号: {{ item.virtual_no }} - {{ item.reason }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -260,7 +260,7 @@
|
|||||||
:key="item.device_id"
|
:key="item.device_id"
|
||||||
style="margin-bottom: 8px; font-size: 12px; color: #f56c6c"
|
style="margin-bottom: 8px; font-size: 12px; color: #f56c6c"
|
||||||
>
|
>
|
||||||
设备号: {{ item.device_no }} - {{ item.reason }}
|
设备号: {{ item.virtual_no }} - {{ item.reason }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -292,7 +292,7 @@
|
|||||||
<ElDescriptions :column="3" border style="margin-bottom: 20px">
|
<ElDescriptions :column="3" border style="margin-bottom: 20px">
|
||||||
<ElDescriptionsItem label="设备ID">{{ currentDeviceDetail.id }}</ElDescriptionsItem>
|
<ElDescriptionsItem label="设备ID">{{ currentDeviceDetail.id }}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="设备号" :span="2">{{
|
<ElDescriptionsItem label="设备号" :span="2">{{
|
||||||
currentDeviceDetail.device_no
|
currentDeviceDetail.virtual_no
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
|
|
||||||
<ElDescriptionsItem label="设备名称">{{
|
<ElDescriptionsItem label="设备名称">{{
|
||||||
@@ -332,10 +332,72 @@
|
|||||||
</ElDialog>
|
</ElDialog>
|
||||||
|
|
||||||
<!-- 绑定卡片列表弹窗 -->
|
<!-- 绑定卡片列表弹窗 -->
|
||||||
<ElDialog v-model="deviceCardsDialogVisible" title="绑定的卡片" width="900px">
|
<ElDialog v-model="deviceCardsDialogVisible" title="设备绑定的卡" width="60%">
|
||||||
<div style="margin-bottom: 10px; text-align: right">
|
<!-- 绑定新卡表单 -->
|
||||||
<ElButton type="primary" @click="handleBindCard">绑定新卡</ElButton>
|
<ElCard shadow="never" class="bind-card-section" style="margin-bottom: 20px">
|
||||||
</div>
|
<template #header>
|
||||||
|
<div style="font-weight: bold">绑定新卡</div>
|
||||||
|
</template>
|
||||||
|
<ElForm
|
||||||
|
ref="bindCardFormRef"
|
||||||
|
:model="bindCardForm"
|
||||||
|
:rules="bindCardRules"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<ElRow :gutter="20">
|
||||||
|
<ElCol :span="10">
|
||||||
|
<ElFormItem label="IoT卡" prop="iot_card_id">
|
||||||
|
<ElSelect
|
||||||
|
v-model="bindCardForm.iot_card_id"
|
||||||
|
placeholder="请选择或搜索IoT卡(支持ICCID搜索)"
|
||||||
|
filterable
|
||||||
|
remote
|
||||||
|
reserve-keyword
|
||||||
|
:remote-method="searchIotCards"
|
||||||
|
:loading="iotCardSearchLoading"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="card in iotCardList"
|
||||||
|
:key="card.id"
|
||||||
|
:label="`${card.iccid} ${card.msisdn ? '(' + card.msisdn + ')' : ''}`"
|
||||||
|
:value="card.id"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="10">
|
||||||
|
<ElFormItem label="插槽位置" prop="slot_position">
|
||||||
|
<ElSelect
|
||||||
|
v-model="bindCardForm.slot_position"
|
||||||
|
placeholder="请选择插槽位置"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="i in currentDeviceDetail?.max_sim_slots || 4"
|
||||||
|
:key="i"
|
||||||
|
:label="`插槽 ${i}`"
|
||||||
|
:value="i"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="4">
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
@click="handleConfirmBindCard"
|
||||||
|
:loading="bindCardLoading"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
确认绑定
|
||||||
|
</ElButton>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
</ElForm>
|
||||||
|
</ElCard>
|
||||||
|
|
||||||
|
<!-- 已绑定卡片列表 -->
|
||||||
<div v-if="deviceCardsLoading" style="text-align: center; padding: 40px 0">
|
<div v-if="deviceCardsLoading" style="text-align: center; padding: 40px 0">
|
||||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||||
</div>
|
</div>
|
||||||
@@ -368,7 +430,7 @@
|
|||||||
v-if="deviceCards.length === 0"
|
v-if="deviceCards.length === 0"
|
||||||
style="text-align: center; padding: 20px; color: #909399"
|
style="text-align: center; padding: 20px; color: #909399"
|
||||||
>
|
>
|
||||||
暂无绑定的卡片
|
暂无设备绑定的卡
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
@@ -381,57 +443,6 @@
|
|||||||
@select="handleDeviceOperationMenuSelect"
|
@select="handleDeviceOperationMenuSelect"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 绑定卡弹窗 -->
|
|
||||||
<ElDialog v-model="bindCardDialogVisible" title="绑定卡到设备" width="500px">
|
|
||||||
<ElForm
|
|
||||||
ref="bindCardFormRef"
|
|
||||||
:model="bindCardForm"
|
|
||||||
:rules="bindCardRules"
|
|
||||||
label-width="100px"
|
|
||||||
>
|
|
||||||
<ElFormItem label="IoT卡" prop="iot_card_id">
|
|
||||||
<ElSelect
|
|
||||||
v-model="bindCardForm.iot_card_id"
|
|
||||||
placeholder="请选择或搜索IoT卡(支持ICCID搜索)"
|
|
||||||
filterable
|
|
||||||
remote
|
|
||||||
reserve-keyword
|
|
||||||
:remote-method="searchIotCards"
|
|
||||||
:loading="iotCardSearchLoading"
|
|
||||||
clearable
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<ElOption
|
|
||||||
v-for="card in iotCardList"
|
|
||||||
:key="card.id"
|
|
||||||
:label="`${card.iccid} ${card.msisdn ? '(' + card.msisdn + ')' : ''}`"
|
|
||||||
:value="card.id"
|
|
||||||
/>
|
|
||||||
</ElSelect>
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="插槽位置" prop="slot_position">
|
|
||||||
<ElSelect
|
|
||||||
v-model="bindCardForm.slot_position"
|
|
||||||
placeholder="请选择插槽位置"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<ElOption
|
|
||||||
v-for="i in currentDeviceDetail?.max_sim_slots || 4"
|
|
||||||
:key="i"
|
|
||||||
:label="`插槽 ${i}`"
|
|
||||||
:value="i"
|
|
||||||
/>
|
|
||||||
</ElSelect>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElForm>
|
|
||||||
<template #footer>
|
|
||||||
<ElButton @click="bindCardDialogVisible = false">取消</ElButton>
|
|
||||||
<ElButton type="primary" @click="handleConfirmBindCard" :loading="bindCardLoading">
|
|
||||||
确认绑定
|
|
||||||
</ElButton>
|
|
||||||
</template>
|
|
||||||
</ElDialog>
|
|
||||||
|
|
||||||
<!-- 设置限速对话框 -->
|
<!-- 设置限速对话框 -->
|
||||||
<ElDialog v-model="speedLimitDialogVisible" title="设置限速" width="500px">
|
<ElDialog v-model="speedLimitDialogVisible" title="设置限速" width="500px">
|
||||||
<ElForm
|
<ElForm
|
||||||
@@ -552,7 +563,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { DeviceService, ShopService, CardService, PackageSeriesService } from '@/api/modules'
|
import {
|
||||||
|
DeviceService,
|
||||||
|
ShopService,
|
||||||
|
CardService,
|
||||||
|
PackageSeriesService,
|
||||||
|
AssetService
|
||||||
|
} from '@/api/modules'
|
||||||
import {
|
import {
|
||||||
ElMessage,
|
ElMessage,
|
||||||
ElMessageBox,
|
ElMessageBox,
|
||||||
@@ -635,7 +652,6 @@
|
|||||||
const deviceCardsDialogVisible = ref(false)
|
const deviceCardsDialogVisible = ref(false)
|
||||||
|
|
||||||
// 绑定卡相关
|
// 绑定卡相关
|
||||||
const bindCardDialogVisible = ref(false)
|
|
||||||
const bindCardLoading = ref(false)
|
const bindCardLoading = ref(false)
|
||||||
const bindCardFormRef = ref<FormInstance>()
|
const bindCardFormRef = ref<FormInstance>()
|
||||||
const iotCardList = ref<any[]>([])
|
const iotCardList = ref<any[]>([])
|
||||||
@@ -704,7 +720,7 @@
|
|||||||
|
|
||||||
// 搜索表单初始值
|
// 搜索表单初始值
|
||||||
const initialSearchState = {
|
const initialSearchState = {
|
||||||
device_no: '',
|
virtual_no: '',
|
||||||
device_name: '',
|
device_name: '',
|
||||||
status: undefined as DeviceStatus | undefined,
|
status: undefined as DeviceStatus | undefined,
|
||||||
batch_no: '',
|
batch_no: '',
|
||||||
@@ -719,7 +735,7 @@
|
|||||||
const searchFormItems: SearchFormItem[] = [
|
const searchFormItems: SearchFormItem[] = [
|
||||||
{
|
{
|
||||||
label: '设备号',
|
label: '设备号',
|
||||||
prop: 'device_no',
|
prop: 'virtual_no',
|
||||||
type: 'input',
|
type: 'input',
|
||||||
config: {
|
config: {
|
||||||
clearable: true,
|
clearable: true,
|
||||||
@@ -788,7 +804,7 @@
|
|||||||
|
|
||||||
// 列配置
|
// 列配置
|
||||||
const columnOptions = [
|
const columnOptions = [
|
||||||
{ label: '设备号', prop: 'device_no' },
|
{ label: '设备号', prop: 'virtual_no' },
|
||||||
{ label: '设备名称', prop: 'device_name' },
|
{ label: '设备名称', prop: 'device_name' },
|
||||||
{ label: '设备型号', prop: 'device_model' },
|
{ label: '设备型号', prop: 'device_model' },
|
||||||
{ label: '设备类型', prop: 'device_type' },
|
{ label: '设备类型', prop: 'device_type' },
|
||||||
@@ -825,7 +841,7 @@
|
|||||||
router.push({
|
router.push({
|
||||||
path: '/asset-management/single-card',
|
path: '/asset-management/single-card',
|
||||||
query: {
|
query: {
|
||||||
device_no: deviceNo
|
virtual_no: deviceNo
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -833,12 +849,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查看设备绑定的卡片
|
// 查看设备设备绑定的卡
|
||||||
const handleViewCards = async (device: Device) => {
|
const handleViewCards = async (device: Device) => {
|
||||||
currentDeviceDetail.value = device
|
currentDeviceDetail.value = device
|
||||||
deviceCards.value = []
|
deviceCards.value = []
|
||||||
deviceCardsDialogVisible.value = true
|
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卡列表
|
// 加载默认的IoT卡列表
|
||||||
const loadDefaultIotCards = async () => {
|
const loadDefaultIotCards = async () => {
|
||||||
iotCardSearchLoading.value = true
|
iotCardSearchLoading.value = true
|
||||||
@@ -920,22 +931,20 @@
|
|||||||
})
|
})
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
ElMessage.success('绑定成功')
|
ElMessage.success('绑定成功')
|
||||||
bindCardDialogVisible.value = false
|
// 重置表单
|
||||||
|
bindCardForm.iot_card_id = undefined
|
||||||
|
bindCardForm.slot_position = 1
|
||||||
|
bindCardFormRef.value.resetFields()
|
||||||
// 重新加载卡列表
|
// 重新加载卡列表
|
||||||
await loadDeviceCards(currentDeviceDetail.value.id)
|
await loadDeviceCards(currentDeviceDetail.value.id)
|
||||||
// 刷新设备详情以更新绑定卡数量
|
// 刷新设备详情以更新绑定卡数量
|
||||||
const detailRes = await DeviceService.getDeviceByImei(
|
const detailRes = await AssetService.resolveAsset(currentDeviceDetail.value.virtual_no)
|
||||||
currentDeviceDetail.value.device_no
|
|
||||||
)
|
|
||||||
if (detailRes.code === 0 && detailRes.data) {
|
if (detailRes.code === 0 && detailRes.data) {
|
||||||
currentDeviceDetail.value = detailRes.data
|
currentDeviceDetail.value = detailRes.data
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ElMessage.error(res.message || '绑定失败')
|
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('绑定卡失败:', error)
|
console.error('绑定卡失败:', error)
|
||||||
ElMessage.error(error?.message || '绑定失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
bindCardLoading.value = false
|
bindCardLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -958,9 +967,7 @@
|
|||||||
// 重新加载卡列表
|
// 重新加载卡列表
|
||||||
await loadDeviceCards(currentDeviceDetail.value.id)
|
await loadDeviceCards(currentDeviceDetail.value.id)
|
||||||
// 刷新设备详情以更新绑定卡数量
|
// 刷新设备详情以更新绑定卡数量
|
||||||
const detailRes = await DeviceService.getDeviceByImei(
|
const detailRes = await AssetService.resolveAsset(currentDeviceDetail.value.virtual_no)
|
||||||
currentDeviceDetail.value.device_no
|
|
||||||
)
|
|
||||||
if (detailRes.code === 0 && detailRes.data) {
|
if (detailRes.code === 0 && detailRes.data) {
|
||||||
currentDeviceDetail.value = detailRes.data
|
currentDeviceDetail.value = detailRes.data
|
||||||
}
|
}
|
||||||
@@ -1013,7 +1020,7 @@
|
|||||||
// 动态列配置
|
// 动态列配置
|
||||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||||
{
|
{
|
||||||
prop: 'device_no',
|
prop: 'virtual_no',
|
||||||
label: '设备号',
|
label: '设备号',
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
showOverflowTooltip: true,
|
showOverflowTooltip: true,
|
||||||
@@ -1024,10 +1031,10 @@
|
|||||||
style: 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;',
|
style: 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;',
|
||||||
onClick: (e: MouseEvent) => {
|
onClick: (e: MouseEvent) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
goToDeviceSearchDetail(row.device_no)
|
goToDeviceSearchDetail(row.virtual_no)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
row.device_no
|
row.virtual_no
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1156,7 +1163,7 @@
|
|||||||
const params = {
|
const params = {
|
||||||
page: pagination.page,
|
page: pagination.page,
|
||||||
page_size: pagination.pageSize,
|
page_size: pagination.pageSize,
|
||||||
device_no: searchForm.device_no || undefined,
|
virtual_no: searchForm.virtual_no || undefined,
|
||||||
device_name: searchForm.device_name || undefined,
|
device_name: searchForm.device_name || undefined,
|
||||||
status: searchForm.status,
|
status: searchForm.status,
|
||||||
batch_no: searchForm.batch_no || undefined,
|
batch_no: searchForm.batch_no || undefined,
|
||||||
@@ -1212,11 +1219,15 @@
|
|||||||
|
|
||||||
// 删除设备
|
// 删除设备
|
||||||
const deleteDevice = (row: Device) => {
|
const deleteDevice = (row: Device) => {
|
||||||
ElMessageBox.confirm(`确定删除设备 ${row.device_no} 吗?删除后将自动解绑所有卡。`, '删除确认', {
|
ElMessageBox.confirm(
|
||||||
|
`确定删除设备 ${row.virtual_no} 吗?删除后将自动解绑所有卡。`,
|
||||||
|
'删除确认',
|
||||||
|
{
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'error'
|
type: 'error'
|
||||||
})
|
}
|
||||||
|
)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
try {
|
try {
|
||||||
await DeviceService.deleteDevice(row.id)
|
await DeviceService.deleteDevice(row.id)
|
||||||
@@ -1449,9 +1460,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通过设备号查看卡片
|
// 通过设备号设备绑定的卡
|
||||||
const handleViewCardsByDeviceNo = (deviceNo: string) => {
|
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) {
|
if (device) {
|
||||||
handleViewCards(device)
|
handleViewCards(device)
|
||||||
} else {
|
} else {
|
||||||
@@ -1462,7 +1473,7 @@
|
|||||||
// 通过设备号删除设备
|
// 通过设备号删除设备
|
||||||
const handleDeleteDeviceByNo = async (deviceNo: string) => {
|
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) {
|
if (device) {
|
||||||
deleteDevice(device)
|
deleteDevice(device)
|
||||||
} else {
|
} else {
|
||||||
@@ -1636,11 +1647,11 @@
|
|||||||
const deviceOperationMenuItems = computed((): MenuItemType[] => {
|
const deviceOperationMenuItems = computed((): MenuItemType[] => {
|
||||||
const items: MenuItemType[] = []
|
const items: MenuItemType[] = []
|
||||||
|
|
||||||
// 添加查看卡片到菜单最前面
|
// 添加设备绑定的卡到菜单最前面
|
||||||
if (hasAuth('device:view_cards')) {
|
if (hasAuth('device:view_cards')) {
|
||||||
items.push({
|
items.push({
|
||||||
key: 'view-cards',
|
key: 'view-cards',
|
||||||
label: '查看卡片'
|
label: '设备绑定的卡'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1707,7 +1718,7 @@
|
|||||||
|
|
||||||
// 处理表格行右键菜单
|
// 处理表格行右键菜单
|
||||||
const handleRowContextMenu = (row: Device, column: any, event: MouseEvent) => {
|
const handleRowContextMenu = (row: Device, column: any, event: MouseEvent) => {
|
||||||
showDeviceOperationMenu(event, row.device_no)
|
showDeviceOperationMenu(event, row.virtual_no)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<ElDescriptions :column="3" border>
|
<ElDescriptions :column="3" border>
|
||||||
<ElDescriptionsItem label="设备ID">{{ deviceDetail.id }}</ElDescriptionsItem>
|
<ElDescriptionsItem label="设备ID">{{ deviceDetail.id }}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="设备号" :span="2">{{
|
<ElDescriptionsItem label="设备号" :span="2">{{
|
||||||
deviceDetail.device_no
|
deviceDetail.virtual_no
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
|
|
||||||
<ElDescriptionsItem label="设备名称">{{
|
<ElDescriptionsItem label="设备名称">{{
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { DeviceService } from '@/api/modules/device'
|
import { AssetService } from '@/api/modules'
|
||||||
|
|
||||||
defineOptions({ name: 'DeviceSearch' })
|
defineOptions({ name: 'DeviceSearch' })
|
||||||
|
|
||||||
@@ -106,12 +106,12 @@
|
|||||||
deviceDetail.value = null
|
deviceDetail.value = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await DeviceService.getDeviceByImei(searchForm.imei.trim())
|
const res = await AssetService.resolveAsset(searchForm.imei.trim())
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
deviceDetail.value = res.data
|
deviceDetail.value = res.data
|
||||||
ElMessage.success('查询成功')
|
ElMessage.success('查询成功')
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.message || '查询失败')
|
ElMessage.error(res.msg || '查询失败')
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('查询设备详情失败:', error)
|
console.error('查询设备详情失败:', error)
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
<p>3. 列格式请设置为文本格式,避免长数字被转为科学计数法</p>
|
<p>3. 列格式请设置为文本格式,避免长数字被转为科学计数法</p>
|
||||||
<p
|
<p
|
||||||
>4.
|
>4.
|
||||||
必填列:device_no(设备号)、device_name(设备名称)、device_model(设备型号)、device_type(设备类型)</p
|
必填列:virtual_no(设备号)、device_name(设备名称)、device_model(设备型号)、device_type(设备类型)</p
|
||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
>5. 可选列:manufacturer(制造商)、max_sim_slots(最大插槽数,默认4)、iccid_1 ~
|
>5. 可选列:manufacturer(制造商)、max_sim_slots(最大插槽数,默认4)、iccid_1 ~
|
||||||
@@ -441,7 +441,7 @@
|
|||||||
// 创建示例数据
|
// 创建示例数据
|
||||||
const templateData = [
|
const templateData = [
|
||||||
{
|
{
|
||||||
device_no: '862639070731999',
|
virtual_no: '862639070731999',
|
||||||
device_name: '智能水表01',
|
device_name: '智能水表01',
|
||||||
device_model: 'WM-2000',
|
device_model: 'WM-2000',
|
||||||
device_type: '智能水表',
|
device_type: '智能水表',
|
||||||
@@ -453,7 +453,7 @@
|
|||||||
iccid_4: ''
|
iccid_4: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
device_no: '862639070750932',
|
virtual_no: '862639070750932',
|
||||||
device_name: 'GPS定位器01',
|
device_name: 'GPS定位器01',
|
||||||
device_model: 'GPS-3000',
|
device_model: 'GPS-3000',
|
||||||
device_type: '定位设备',
|
device_type: '定位设备',
|
||||||
@@ -472,7 +472,7 @@
|
|||||||
|
|
||||||
// 设置列宽
|
// 设置列宽
|
||||||
ws['!cols'] = [
|
ws['!cols'] = [
|
||||||
{ wch: 20 }, // device_no
|
{ wch: 20 }, // virtual_no
|
||||||
{ wch: 20 }, // device_name
|
{ wch: 20 }, // device_name
|
||||||
{ wch: 15 }, // device_model
|
{ wch: 15 }, // device_model
|
||||||
{ wch: 15 }, // device_type
|
{ wch: 15 }, // device_type
|
||||||
@@ -607,7 +607,7 @@
|
|||||||
const failReasons =
|
const failReasons =
|
||||||
detail.failed_items?.map((item: any) => ({
|
detail.failed_items?.map((item: any) => ({
|
||||||
line: item.line || '-',
|
line: item.line || '-',
|
||||||
deviceNo: item.device_no || '-',
|
deviceNo: item.virtual_no || '-',
|
||||||
message: item.reason || '未知错误'
|
message: item.reason || '未知错误'
|
||||||
})) || []
|
})) || []
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
:rules="allocateRules"
|
:rules="allocateRules"
|
||||||
label-width="120px"
|
label-width="120px"
|
||||||
>
|
>
|
||||||
<ElFormItem :label="$t('enterpriseDevices.form.deviceNos')" prop="device_nos">
|
<ElFormItem :label="$t('enterpriseDevices.form.deviceNos')" prop="virtual_nos">
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="deviceNosText"
|
v-model="deviceNosText"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
<div style="margin-top: 4px; font-size: 12px; color: var(--el-color-info)">
|
<div style="margin-top: 4px; font-size: 12px; color: var(--el-color-info)">
|
||||||
{{
|
{{
|
||||||
$t('enterpriseDevices.form.selectedCount', {
|
$t('enterpriseDevices.form.selectedCount', {
|
||||||
count: allocateForm.device_nos?.length || 0
|
count: allocateForm.virtual_nos?.length || 0
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
@@ -171,7 +171,7 @@
|
|||||||
}}</ElDivider>
|
}}</ElDivider>
|
||||||
<ElTable :data="operationResult.failed_items" border max-height="300">
|
<ElTable :data="operationResult.failed_items" border max-height="300">
|
||||||
<ElTableColumn
|
<ElTableColumn
|
||||||
prop="device_no"
|
prop="virtual_no"
|
||||||
:label="$t('enterpriseDevices.result.deviceNo')"
|
:label="$t('enterpriseDevices.result.deviceNo')"
|
||||||
width="180"
|
width="180"
|
||||||
/>
|
/>
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
}}</ElDivider>
|
}}</ElDivider>
|
||||||
<ElTable :data="operationResult.authorized_devices" border max-height="200">
|
<ElTable :data="operationResult.authorized_devices" border max-height="200">
|
||||||
<ElTableColumn
|
<ElTableColumn
|
||||||
prop="device_no"
|
prop="virtual_no"
|
||||||
:label="$t('enterpriseDevices.result.deviceNo')"
|
:label="$t('enterpriseDevices.result.deviceNo')"
|
||||||
width="180"
|
width="180"
|
||||||
/>
|
/>
|
||||||
@@ -267,7 +267,7 @@
|
|||||||
|
|
||||||
// 搜索表单初始值
|
// 搜索表单初始值
|
||||||
const initialSearchState = {
|
const initialSearchState = {
|
||||||
device_no: ''
|
virtual_no: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索表单
|
// 搜索表单
|
||||||
@@ -275,13 +275,13 @@
|
|||||||
|
|
||||||
// 授权表单
|
// 授权表单
|
||||||
const allocateForm = reactive({
|
const allocateForm = reactive({
|
||||||
device_nos: [] as string[],
|
virtual_nos: [] as string[],
|
||||||
remark: ''
|
remark: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// 授权表单验证规则
|
// 授权表单验证规则
|
||||||
const allocateRules = reactive<FormRules>({
|
const allocateRules = reactive<FormRules>({
|
||||||
device_nos: [
|
virtual_nos: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
validator: (rule, value, callback) => {
|
validator: (rule, value, callback) => {
|
||||||
@@ -315,7 +315,7 @@
|
|||||||
const searchFormItems: SearchFormItem[] = [
|
const searchFormItems: SearchFormItem[] = [
|
||||||
{
|
{
|
||||||
label: t('enterpriseDevices.searchForm.deviceNo'),
|
label: t('enterpriseDevices.searchForm.deviceNo'),
|
||||||
prop: 'device_no',
|
prop: 'virtual_no',
|
||||||
type: 'input',
|
type: 'input',
|
||||||
config: {
|
config: {
|
||||||
clearable: true,
|
clearable: true,
|
||||||
@@ -327,7 +327,7 @@
|
|||||||
// 列配置
|
// 列配置
|
||||||
const columnOptions = [
|
const columnOptions = [
|
||||||
{ label: t('enterpriseDevices.table.deviceId'), prop: 'device_id' },
|
{ label: t('enterpriseDevices.table.deviceId'), prop: 'device_id' },
|
||||||
{ label: t('enterpriseDevices.table.deviceNo'), prop: 'device_no' },
|
{ label: t('enterpriseDevices.table.deviceNo'), prop: 'virtual_no' },
|
||||||
{ label: t('enterpriseDevices.table.deviceName'), prop: 'device_name' },
|
{ label: t('enterpriseDevices.table.deviceName'), prop: 'device_name' },
|
||||||
{ label: t('enterpriseDevices.table.deviceModel'), prop: 'device_model' },
|
{ label: t('enterpriseDevices.table.deviceModel'), prop: 'device_model' },
|
||||||
{ label: t('enterpriseDevices.table.cardCount'), prop: 'card_count' },
|
{ label: t('enterpriseDevices.table.cardCount'), prop: 'card_count' },
|
||||||
@@ -344,7 +344,7 @@
|
|||||||
width: 100
|
width: 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'device_no',
|
prop: 'virtual_no',
|
||||||
label: t('enterpriseDevices.table.deviceNo'),
|
label: t('enterpriseDevices.table.deviceNo'),
|
||||||
minWidth: 150
|
minWidth: 150
|
||||||
},
|
},
|
||||||
@@ -407,7 +407,7 @@
|
|||||||
const params: any = {
|
const params: any = {
|
||||||
page: pagination.page,
|
page: pagination.page,
|
||||||
page_size: pagination.pageSize,
|
page_size: pagination.pageSize,
|
||||||
device_no: searchForm.device_no || undefined
|
virtual_no: searchForm.virtual_no || undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清理空值
|
// 清理空值
|
||||||
@@ -473,7 +473,7 @@
|
|||||||
const showAllocateDialog = () => {
|
const showAllocateDialog = () => {
|
||||||
allocateDialogVisible.value = true
|
allocateDialogVisible.value = true
|
||||||
deviceNosText.value = ''
|
deviceNosText.value = ''
|
||||||
allocateForm.device_nos = []
|
allocateForm.virtual_nos = []
|
||||||
allocateForm.remark = ''
|
allocateForm.remark = ''
|
||||||
if (allocateFormRef.value) {
|
if (allocateFormRef.value) {
|
||||||
allocateFormRef.value.resetFields()
|
allocateFormRef.value.resetFields()
|
||||||
@@ -487,7 +487,7 @@
|
|||||||
.split(/[,\s\n]+/)
|
.split(/[,\s\n]+/)
|
||||||
.map((no) => no.trim())
|
.map((no) => no.trim())
|
||||||
.filter((no) => no.length > 0)
|
.filter((no) => no.length > 0)
|
||||||
allocateForm.device_nos = deviceNos
|
allocateForm.virtual_nos = deviceNos
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行授权
|
// 执行授权
|
||||||
@@ -496,12 +496,12 @@
|
|||||||
|
|
||||||
await allocateFormRef.value.validate(async (valid) => {
|
await allocateFormRef.value.validate(async (valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
if (allocateForm.device_nos.length === 0) {
|
if (allocateForm.virtual_nos.length === 0) {
|
||||||
ElMessage.warning(t('enterpriseDevices.messages.deviceNosEmpty'))
|
ElMessage.warning(t('enterpriseDevices.messages.deviceNosEmpty'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allocateForm.device_nos.length > 100) {
|
if (allocateForm.virtual_nos.length > 100) {
|
||||||
ElMessage.warning(t('enterpriseDevices.messages.deviceNosMaxLimit'))
|
ElMessage.warning(t('enterpriseDevices.messages.deviceNosMaxLimit'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -509,7 +509,7 @@
|
|||||||
allocateLoading.value = true
|
allocateLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await EnterpriseService.allocateDevices(enterpriseId.value, {
|
const res = await EnterpriseService.allocateDevices(enterpriseId.value, {
|
||||||
device_nos: allocateForm.device_nos,
|
virtual_nos: allocateForm.virtual_nos,
|
||||||
remark: allocateForm.remark || undefined
|
remark: allocateForm.remark || undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -582,7 +582,7 @@
|
|||||||
recallLoading.value = true
|
recallLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await EnterpriseService.recallDevices(enterpriseId.value, {
|
const res = await EnterpriseService.recallDevices(enterpriseId.value, {
|
||||||
device_nos: selectedDevices.value.map((device) => device.device_no)
|
virtual_nos: selectedDevices.value.map((device) => device.virtual_no)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
|
|||||||
@@ -471,12 +471,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ElDescriptions v-else-if="flowUsageData" :column="1" border>
|
<ElDescriptions v-else-if="flowUsageData" :column="1" border>
|
||||||
<ElDescriptionsItem label="已用流量">{{
|
<ElDescriptionsItem label="套餐总流量"
|
||||||
flowUsageData.usedFlow || 0
|
>{{ flowUsageData.totalFlow || 0 }} MB</ElDescriptionsItem
|
||||||
}}</ElDescriptionsItem>
|
>
|
||||||
<ElDescriptionsItem label="流量单位">{{
|
<ElDescriptionsItem label="已用流量"
|
||||||
flowUsageData.unit || 'MB'
|
>{{ flowUsageData.usedFlow || 0 }} MB</ElDescriptionsItem
|
||||||
}}</ElDescriptionsItem>
|
>
|
||||||
|
<ElDescriptionsItem label="剩余流量"
|
||||||
|
>{{ flowUsageData.remainFlow || 0 }} MB</ElDescriptionsItem
|
||||||
|
>
|
||||||
<ElDescriptionsItem v-if="flowUsageData.extend" label="扩展信息">{{
|
<ElDescriptionsItem v-if="flowUsageData.extend" label="扩展信息">{{
|
||||||
flowUsageData.extend
|
flowUsageData.extend
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
@@ -538,40 +541,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
|
|
||||||
<!-- 实名认证链接对话框 -->
|
|
||||||
<ElDialog v-model="realnameLinkDialogVisible" title="实名认证链接" width="500px">
|
|
||||||
<div v-if="realnameLinkLoading" style="text-align: center; padding: 40px">
|
|
||||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
|
||||||
<div style="margin-top: 16px">获取中...</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="realnameLinkData && realnameLinkData.link" style="text-align: center">
|
|
||||||
<div style="margin-bottom: 16px">
|
|
||||||
<img v-if="qrcodeDataURL" :src="qrcodeDataURL" alt="实名认证二维码" />
|
|
||||||
</div>
|
|
||||||
<ElDescriptions :column="1" border>
|
|
||||||
<ElDescriptionsItem label="实名链接">
|
|
||||||
<a
|
|
||||||
:href="realnameLinkData.link"
|
|
||||||
target="_blank"
|
|
||||||
style="color: var(--el-color-primary)"
|
|
||||||
>
|
|
||||||
{{ realnameLinkData.link }}
|
|
||||||
</a>
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem v-if="realnameLinkData.extend" label="扩展信息">{{
|
|
||||||
realnameLinkData.extend
|
|
||||||
}}</ElDescriptionsItem>
|
|
||||||
</ElDescriptions>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<ElButton type="primary" @click="realnameLinkDialogVisible = false">关闭</ElButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</ElDialog>
|
|
||||||
|
|
||||||
<!-- 更多操作右键菜单 -->
|
<!-- 更多操作右键菜单 -->
|
||||||
<ArtMenuRight
|
<ArtMenuRight
|
||||||
ref="moreMenuRef"
|
ref="moreMenuRef"
|
||||||
@@ -595,11 +564,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { CardService, ShopService, PackageSeriesService } from '@/api/modules'
|
import { CardService, ShopService, PackageSeriesService, AssetService } from '@/api/modules'
|
||||||
import { ElMessage, ElTag, ElIcon, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElTag, ElIcon, ElMessageBox } from 'element-plus'
|
||||||
import { Loading } from '@element-plus/icons-vue'
|
import { Loading } from '@element-plus/icons-vue'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import QRCode from 'qrcode'
|
|
||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
@@ -690,11 +658,6 @@
|
|||||||
const cardStatusLoading = ref(false)
|
const cardStatusLoading = ref(false)
|
||||||
const cardStatusData = ref<any>(null)
|
const cardStatusData = ref<any>(null)
|
||||||
|
|
||||||
const realnameLinkDialogVisible = ref(false)
|
|
||||||
const realnameLinkLoading = ref(false)
|
|
||||||
const realnameLinkData = ref<any>(null)
|
|
||||||
const qrcodeDataURL = ref<string>('')
|
|
||||||
|
|
||||||
// 更多操作右键菜单
|
// 更多操作右键菜单
|
||||||
const moreMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
const moreMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
const cardOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
const cardOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
@@ -712,6 +675,7 @@
|
|||||||
carrier_id: undefined,
|
carrier_id: undefined,
|
||||||
iccid: '',
|
iccid: '',
|
||||||
msisdn: '',
|
msisdn: '',
|
||||||
|
virtual_no: '',
|
||||||
is_distributed: undefined
|
is_distributed: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -861,6 +825,15 @@
|
|||||||
placeholder: '请输入卡接入号'
|
placeholder: '请输入卡接入号'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '虚拟号',
|
||||||
|
prop: 'virtual_no',
|
||||||
|
type: 'input',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请输入虚拟号'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '是否已分销',
|
label: '是否已分销',
|
||||||
prop: 'is_distributed',
|
prop: 'is_distributed',
|
||||||
@@ -880,6 +853,7 @@
|
|||||||
const columnOptions = [
|
const columnOptions = [
|
||||||
{ label: 'ICCID', prop: 'iccid' },
|
{ label: 'ICCID', prop: 'iccid' },
|
||||||
{ label: '卡接入号', prop: 'msisdn' },
|
{ label: '卡接入号', prop: 'msisdn' },
|
||||||
|
{ label: '虚拟号', prop: 'virtual_no' },
|
||||||
{ label: '卡业务类型', prop: 'card_category' },
|
{ label: '卡业务类型', prop: 'card_category' },
|
||||||
{ label: '运营商', prop: 'carrier_name' },
|
{ label: '运营商', prop: 'carrier_name' },
|
||||||
{ label: '店铺名称', prop: 'shop_name' },
|
{ label: '店铺名称', prop: 'shop_name' },
|
||||||
@@ -1010,6 +984,12 @@
|
|||||||
label: '卡接入号',
|
label: '卡接入号',
|
||||||
width: 130
|
width: 130
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
prop: 'virtual_no',
|
||||||
|
label: '虚拟号',
|
||||||
|
width: 130,
|
||||||
|
formatter: (row: StandaloneIotCard) => row.virtual_no || '-'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
prop: 'card_category',
|
prop: 'card_category',
|
||||||
label: '卡业务类型',
|
label: '卡业务类型',
|
||||||
@@ -1569,24 +1549,17 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasAuth('iot_card:get_realname_link')) {
|
|
||||||
items.push({
|
|
||||||
key: 'realname-link',
|
|
||||||
label: '获取实名链接'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAuth('iot_card:start_card')) {
|
if (hasAuth('iot_card:start_card')) {
|
||||||
items.push({
|
items.push({
|
||||||
key: 'start-card',
|
key: 'start-card',
|
||||||
label: '启用卡片'
|
label: '启用此卡'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasAuth('iot_card:stop_card')) {
|
if (hasAuth('iot_card:stop_card')) {
|
||||||
items.push({
|
items.push({
|
||||||
key: 'stop-card',
|
key: 'stop-card',
|
||||||
label: '停用卡片'
|
label: '停用此卡'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1681,9 +1654,6 @@
|
|||||||
case 'card-status':
|
case 'card-status':
|
||||||
showCardStatusDialog(iccid)
|
showCardStatusDialog(iccid)
|
||||||
break
|
break
|
||||||
case 'realname-link':
|
|
||||||
showRealnameLinkDialog(iccid)
|
|
||||||
break
|
|
||||||
case 'start-card':
|
case 'start-card':
|
||||||
handleStartCard(iccid)
|
handleStartCard(iccid)
|
||||||
break
|
break
|
||||||
@@ -1700,9 +1670,16 @@
|
|||||||
flowUsageData.value = null
|
flowUsageData.value = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await CardService.getGatewayFlow(iccid)
|
// 通过 ICCID 解析获取资产信息,resolveAsset 接口已包含所有需要的数据
|
||||||
if (res.code === 0) {
|
const res = await AssetService.resolveAsset(iccid)
|
||||||
flowUsageData.value = res.data
|
if (res.code === 0 && res.data) {
|
||||||
|
// 直接使用 resolveAsset 返回的流量信息
|
||||||
|
flowUsageData.value = {
|
||||||
|
totalFlow: res.data.package_total_mb || 0,
|
||||||
|
usedFlow: res.data.package_used_mb || 0,
|
||||||
|
remainFlow: res.data.package_remain_mb || 0,
|
||||||
|
extend: res.data.extend
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.message || '查询失败')
|
ElMessage.error(res.message || '查询失败')
|
||||||
flowUsageDialogVisible.value = false
|
flowUsageDialogVisible.value = false
|
||||||
@@ -1723,9 +1700,26 @@
|
|||||||
realnameStatusData.value = null
|
realnameStatusData.value = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await CardService.getGatewayRealname(iccid)
|
// 通过 ICCID 解析获取资产信息,resolveAsset 接口已包含所有需要的数据
|
||||||
if (res.code === 0) {
|
const res = await AssetService.resolveAsset(iccid)
|
||||||
realnameStatusData.value = res.data
|
if (res.code === 0 && res.data) {
|
||||||
|
// 直接使用 resolveAsset 返回的实名状态,real_name_status: 0未实名 1实名中 2已实名
|
||||||
|
let statusText = '未知'
|
||||||
|
switch (res.data.real_name_status) {
|
||||||
|
case 0:
|
||||||
|
statusText = '未实名'
|
||||||
|
break
|
||||||
|
case 1:
|
||||||
|
statusText = '实名中'
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
statusText = '已实名'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
realnameStatusData.value = {
|
||||||
|
status: statusText,
|
||||||
|
extend: res.data.extend
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.message || '查询失败')
|
ElMessage.error(res.message || '查询失败')
|
||||||
realnameStatusDialogVisible.value = false
|
realnameStatusDialogVisible.value = false
|
||||||
@@ -1746,9 +1740,15 @@
|
|||||||
cardStatusData.value = null
|
cardStatusData.value = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await CardService.getGatewayStatus(iccid)
|
// 通过 ICCID 解析获取资产信息,resolveAsset 接口已包含所有需要的数据
|
||||||
if (res.code === 0) {
|
const res = await AssetService.resolveAsset(iccid)
|
||||||
cardStatusData.value = res.data
|
if (res.code === 0 && res.data) {
|
||||||
|
// 直接使用 resolveAsset 返回的网络状态
|
||||||
|
cardStatusData.value = {
|
||||||
|
iccid: iccid,
|
||||||
|
cardStatus: res.data.network_status === 1 ? '开机' : '停机',
|
||||||
|
extend: res.data.extend
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.message || '查询失败')
|
ElMessage.error(res.message || '查询失败')
|
||||||
cardStatusDialogVisible.value = false
|
cardStatusDialogVisible.value = false
|
||||||
@@ -1762,36 +1762,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取实名认证链接
|
// 启用此卡(复机)
|
||||||
const showRealnameLinkDialog = async (iccid: string) => {
|
|
||||||
realnameLinkDialogVisible.value = true
|
|
||||||
realnameLinkLoading.value = true
|
|
||||||
realnameLinkData.value = null
|
|
||||||
qrcodeDataURL.value = ''
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await CardService.getRealnameLink(iccid)
|
|
||||||
if (res.code === 0 && res.data?.link) {
|
|
||||||
realnameLinkData.value = res.data
|
|
||||||
// 生成二维码
|
|
||||||
qrcodeDataURL.value = await QRCode.toDataURL(res.data.link, {
|
|
||||||
width: 200,
|
|
||||||
margin: 1
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
ElMessage.error(res.message || '获取失败')
|
|
||||||
realnameLinkDialogVisible.value = false
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('获取实名链接失败:', error)
|
|
||||||
ElMessage.error(error?.message || '获取失败')
|
|
||||||
realnameLinkDialogVisible.value = false
|
|
||||||
} finally {
|
|
||||||
realnameLinkLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启用卡片(复机)
|
|
||||||
const handleStartCard = (iccid: string) => {
|
const handleStartCard = (iccid: string) => {
|
||||||
ElMessageBox.confirm('确定要启用该卡片吗?', '确认启用', {
|
ElMessageBox.confirm('确定要启用该卡片吗?', '确认启用', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
@@ -1800,16 +1771,14 @@
|
|||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await CardService.startCard(iccid)
|
// 使用新接口:直接通过 ICCID 启用此卡
|
||||||
|
const res = await AssetService.startCard(iccid)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
ElMessage.success('启用成功')
|
ElMessage.success('启用成功')
|
||||||
getTableData()
|
getTableData()
|
||||||
} else {
|
|
||||||
ElMessage.error(res.message || '启用失败')
|
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('启用卡片失败:', error)
|
console.error('启用此卡失败:', error)
|
||||||
ElMessage.error(error?.message || '启用失败')
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -1817,7 +1786,7 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 停用卡片(停机)
|
// 停用此卡(停机)
|
||||||
const handleStopCard = (iccid: string) => {
|
const handleStopCard = (iccid: string) => {
|
||||||
ElMessageBox.confirm('确定要停用该卡片吗?', '确认停用', {
|
ElMessageBox.confirm('确定要停用该卡片吗?', '确认停用', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
@@ -1826,16 +1795,14 @@
|
|||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await CardService.stopCard(iccid)
|
// 使用新接口:直接通过 ICCID 停用此卡
|
||||||
|
const res = await AssetService.stopCard(iccid)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
ElMessage.success('停用成功')
|
ElMessage.success('停用成功')
|
||||||
getTableData()
|
getTableData()
|
||||||
} else {
|
|
||||||
ElMessage.error(res.message || '停用失败')
|
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('停用卡片失败:', error)
|
console.error('停用此卡失败:', error)
|
||||||
ElMessage.error(error?.message || '停用失败')
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -64,6 +64,51 @@
|
|||||||
</ElCard>
|
</ElCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 失败数据对话框 -->
|
||||||
|
<ElDialog
|
||||||
|
v-model="failDataDialogVisible"
|
||||||
|
title="失败数据详情"
|
||||||
|
width="900px"
|
||||||
|
align-center
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<div v-if="failDataLoading" style="text-align: center; padding: 40px">
|
||||||
|
<ElSkeleton :rows="5" animated />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="failedItems.length > 0">
|
||||||
|
<ElAlert type="warning" :closable="false" style="margin-bottom: 20px">
|
||||||
|
<template #title>
|
||||||
|
<div style="line-height: 1.8">
|
||||||
|
<p><strong>失败数据汇总:</strong></p>
|
||||||
|
<p>共 {{ failedItems.length }} 条记录导入失败</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ElAlert>
|
||||||
|
|
||||||
|
<ElTable :data="failedItems" max-height="400" border>
|
||||||
|
<ElTableColumn prop="line" label="行号" width="80" />
|
||||||
|
<ElTableColumn prop="iccid" label="ICCID" width="200" show-overflow-tooltip />
|
||||||
|
<ElTableColumn prop="msisdn" label="MSISDN" width="150" show-overflow-tooltip />
|
||||||
|
<ElTableColumn prop="message" label="失败原因" min-width="200" show-overflow-tooltip />
|
||||||
|
</ElTable>
|
||||||
|
</div>
|
||||||
|
<div v-else style="text-align: center; padding: 40px">
|
||||||
|
<ElEmpty description="暂无失败数据" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<ElButton @click="failDataDialogVisible = false">关闭</ElButton>
|
||||||
|
<ElButton
|
||||||
|
v-if="failedItems.length > 0"
|
||||||
|
type="primary"
|
||||||
|
:icon="Download"
|
||||||
|
@click="downloadCurrentFailData"
|
||||||
|
>
|
||||||
|
下载失败数据
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
|
||||||
<!-- 导入对话框 -->
|
<!-- 导入对话框 -->
|
||||||
<ElDialog v-model="importDialogVisible" title="批量导入IoT卡" width="700px" align-center>
|
<ElDialog v-model="importDialogVisible" title="批量导入IoT卡" width="700px" align-center>
|
||||||
<ElAlert type="info" :closable="false" style="margin-bottom: 20px">
|
<ElAlert type="info" :closable="false" style="margin-bottom: 20px">
|
||||||
@@ -73,7 +118,7 @@
|
|||||||
<p>1. 请先下载 Excel 模板文件,按照模板格式填写IoT卡信息</p>
|
<p>1. 请先下载 Excel 模板文件,按照模板格式填写IoT卡信息</p>
|
||||||
<p>2. 仅支持 Excel 格式(.xlsx),单次最多导入 1000 条</p>
|
<p>2. 仅支持 Excel 格式(.xlsx),单次最多导入 1000 条</p>
|
||||||
<p>3. 列格式请设置为文本格式,避免长数字被转为科学计数法</p>
|
<p>3. 列格式请设置为文本格式,避免长数字被转为科学计数法</p>
|
||||||
<p>4. 必填字段:ICCID、MSISDN(手机号)</p>
|
<p>4. 必填字段:ICCID、MSISDN(手机号);可选字段:virtual_no(虚拟号)</p>
|
||||||
<p>5. 必须选择运营商</p>
|
<p>5. 必须选择运营商</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -139,7 +184,15 @@
|
|||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { CardService, CarrierService } from '@/api/modules'
|
import { CardService, CarrierService } from '@/api/modules'
|
||||||
import { ElMessage, ElTag, ElFormItem, ElSelect, ElOption } from 'element-plus'
|
import {
|
||||||
|
ElMessage,
|
||||||
|
ElTag,
|
||||||
|
ElFormItem,
|
||||||
|
ElSelect,
|
||||||
|
ElOption,
|
||||||
|
ElSkeleton,
|
||||||
|
ElEmpty
|
||||||
|
} from 'element-plus'
|
||||||
import { Download, UploadFilled, Upload } from '@element-plus/icons-vue'
|
import { Download, UploadFilled, Upload } from '@element-plus/icons-vue'
|
||||||
import type { UploadInstance } from 'element-plus'
|
import type { UploadInstance } from 'element-plus'
|
||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
@@ -181,6 +234,10 @@
|
|||||||
const carrierLoading = ref(false)
|
const carrierLoading = ref(false)
|
||||||
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
const currentRow = ref<IotCardImportTask | null>(null)
|
const currentRow = ref<IotCardImportTask | null>(null)
|
||||||
|
const failDataDialogVisible = ref(false)
|
||||||
|
const failDataLoading = ref(false)
|
||||||
|
const failedItems = ref<any[]>([])
|
||||||
|
const currentFailTask = ref<IotCardImportTask | null>(null)
|
||||||
|
|
||||||
// 搜索表单初始值
|
// 搜索表单初始值
|
||||||
const initialSearchState = {
|
const initialSearchState = {
|
||||||
@@ -473,13 +530,18 @@
|
|||||||
getTableData()
|
getTableData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从行数据下载失败数据
|
// 显示失败数据弹窗
|
||||||
const downloadFailDataByRow = async (row: IotCardImportTask) => {
|
const showFailDataDialog = async (row: IotCardImportTask) => {
|
||||||
try {
|
try {
|
||||||
|
failDataDialogVisible.value = true
|
||||||
|
failDataLoading.value = true
|
||||||
|
failedItems.value = []
|
||||||
|
currentFailTask.value = row
|
||||||
|
|
||||||
const res = await CardService.getIotCardImportTaskDetail(row.id)
|
const res = await CardService.getIotCardImportTaskDetail(row.id)
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
const detail = res.data
|
const detail = res.data
|
||||||
const failReasons =
|
failedItems.value =
|
||||||
detail.failed_items?.map((item: any) => ({
|
detail.failed_items?.map((item: any) => ({
|
||||||
line: item.line || '-',
|
line: item.line || '-',
|
||||||
iccid: item.iccid || '-',
|
iccid: item.iccid || '-',
|
||||||
@@ -487,15 +549,30 @@
|
|||||||
message: item.reason || item.error || '未知错误'
|
message: item.reason || item.error || '未知错误'
|
||||||
})) || []
|
})) || []
|
||||||
|
|
||||||
if (failReasons.length === 0) {
|
if (failedItems.value.length === 0) {
|
||||||
|
ElMessage.warning('没有失败数据')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取失败数据失败:', error)
|
||||||
|
ElMessage.error('获取失败数据失败')
|
||||||
|
} finally {
|
||||||
|
failDataLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载当前失败数据
|
||||||
|
const downloadCurrentFailData = () => {
|
||||||
|
if (!currentFailTask.value || failedItems.value.length === 0) {
|
||||||
ElMessage.warning('没有失败数据可下载')
|
ElMessage.warning('没有失败数据可下载')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const headers = ['行号', 'ICCID', 'MSISDN', '失败原因']
|
const headers = ['行号', 'ICCID', 'MSISDN', '失败原因']
|
||||||
const csvRows = [
|
const csvRows = [
|
||||||
headers.join(','),
|
headers.join(','),
|
||||||
...failReasons.map((item: any) =>
|
...failedItems.value.map((item: any) =>
|
||||||
[item.line, item.iccid, item.msisdn, `"${item.message}"`].join(',')
|
[item.line, item.iccid, item.msisdn, `"${item.message}"`].join(',')
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@@ -507,7 +584,7 @@
|
|||||||
const link = document.createElement('a')
|
const link = document.createElement('a')
|
||||||
const url = URL.createObjectURL(blob)
|
const url = URL.createObjectURL(blob)
|
||||||
link.setAttribute('href', url)
|
link.setAttribute('href', url)
|
||||||
link.setAttribute('download', `IoT卡导入失败数据_${row.task_no}.csv`)
|
link.setAttribute('download', `IoT卡导入失败数据_${currentFailTask.value.task_no}.csv`)
|
||||||
link.style.visibility = 'hidden'
|
link.style.visibility = 'hidden'
|
||||||
document.body.appendChild(link)
|
document.body.appendChild(link)
|
||||||
link.click()
|
link.click()
|
||||||
@@ -515,7 +592,6 @@
|
|||||||
URL.revokeObjectURL(url)
|
URL.revokeObjectURL(url)
|
||||||
|
|
||||||
ElMessage.success('失败数据下载成功')
|
ElMessage.success('失败数据下载成功')
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('下载失败数据失败:', error)
|
console.error('下载失败数据失败:', error)
|
||||||
ElMessage.error('下载失败数据失败')
|
ElMessage.error('下载失败数据失败')
|
||||||
@@ -532,15 +608,18 @@
|
|||||||
const templateData = [
|
const templateData = [
|
||||||
{
|
{
|
||||||
ICCID: '89860123456789012345',
|
ICCID: '89860123456789012345',
|
||||||
MSISDN: '13800138000'
|
MSISDN: '13800138000',
|
||||||
|
virtual_no: 'V001'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ICCID: '89860123456789012346',
|
ICCID: '89860123456789012346',
|
||||||
MSISDN: '13800138001'
|
MSISDN: '13800138001',
|
||||||
|
virtual_no: 'V002'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ICCID: '89860123456789012347',
|
ICCID: '89860123456789012347',
|
||||||
MSISDN: '13800138002'
|
MSISDN: '13800138002',
|
||||||
|
virtual_no: 'V003'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -551,7 +630,8 @@
|
|||||||
// 设置列宽
|
// 设置列宽
|
||||||
ws['!cols'] = [
|
ws['!cols'] = [
|
||||||
{ wch: 25 }, // ICCID
|
{ wch: 25 }, // ICCID
|
||||||
{ wch: 15 } // MSISDN
|
{ wch: 15 }, // MSISDN
|
||||||
|
{ wch: 15 } // virtual_no
|
||||||
]
|
]
|
||||||
|
|
||||||
// 将所有单元格设置为文本格式,防止科学计数法
|
// 将所有单元格设置为文本格式,防止科学计数法
|
||||||
@@ -727,7 +807,7 @@
|
|||||||
|
|
||||||
switch (item.key) {
|
switch (item.key) {
|
||||||
case 'failData':
|
case 'failData':
|
||||||
downloadFailDataByRow(currentRow.value)
|
showFailDataDialog(currentRow.value)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<ElTable :data="taskDetail.failed_items" border style="width: 100%">
|
<ElTable :data="taskDetail.failed_items" border style="width: 100%">
|
||||||
<ElTableColumn prop="line" label="行号" width="100" />
|
<ElTableColumn prop="line" label="行号" width="100" />
|
||||||
<ElTableColumn v-if="taskType === 'card'" prop="iccid" label="ICCID" min-width="180" />
|
<ElTableColumn v-if="taskType === 'card'" prop="iccid" label="ICCID" min-width="180" />
|
||||||
<ElTableColumn v-else prop="device_no" label="设备号" min-width="180" />
|
<ElTableColumn v-else prop="virtual_no" label="设备号" min-width="180" />
|
||||||
<ElTableColumn prop="reason" label="失败原因" min-width="300" />
|
<ElTableColumn prop="reason" label="失败原因" min-width="300" />
|
||||||
</ElTable>
|
</ElTable>
|
||||||
</div>
|
</div>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<ElTable :data="taskDetail.skipped_items" border style="width: 100%">
|
<ElTable :data="taskDetail.skipped_items" border style="width: 100%">
|
||||||
<ElTableColumn prop="line" label="行号" width="100" />
|
<ElTableColumn prop="line" label="行号" width="100" />
|
||||||
<ElTableColumn v-if="taskType === 'card'" prop="iccid" label="ICCID" min-width="180" />
|
<ElTableColumn v-if="taskType === 'card'" prop="iccid" label="ICCID" min-width="180" />
|
||||||
<ElTableColumn v-else prop="device_no" label="设备号" min-width="180" />
|
<ElTableColumn v-else prop="virtual_no" label="设备号" min-width="180" />
|
||||||
<ElTableColumn prop="reason" label="跳过原因" min-width="300" />
|
<ElTableColumn prop="reason" label="跳过原因" min-width="300" />
|
||||||
</ElTable>
|
</ElTable>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<p>3. CSV 文件编码:UTF-8(推荐)或 GBK</p>
|
<p>3. CSV 文件编码:UTF-8(推荐)或 GBK</p>
|
||||||
<p
|
<p
|
||||||
>4.
|
>4.
|
||||||
必填字段:device_no(设备号)、device_name(设备名称)、device_model(设备型号)</p
|
必填字段:virtual_no(设备号)、device_name(设备名称)、device_model(设备型号)</p
|
||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
>5.
|
>5.
|
||||||
@@ -370,7 +370,7 @@
|
|||||||
// CSV模板内容 - 包含表头和示例数据
|
// CSV模板内容 - 包含表头和示例数据
|
||||||
const csvContent = [
|
const csvContent = [
|
||||||
// 表头
|
// 表头
|
||||||
'device_no,device_name,device_model,device_type,manufacturer,max_sim_slots',
|
'virtual_no,device_name,device_model,device_type,manufacturer,max_sim_slots',
|
||||||
// 示例数据
|
// 示例数据
|
||||||
'DEV001,智能水表01,WM-2000,智能水表,华为,1',
|
'DEV001,智能水表01,WM-2000,智能水表,华为,1',
|
||||||
'DEV002,GPS定位器01,GPS-3000,定位设备,小米,2',
|
'DEV002,GPS定位器01,GPS-3000,定位设备,小米,2',
|
||||||
@@ -559,7 +559,7 @@
|
|||||||
failReasons:
|
failReasons:
|
||||||
detail.failed_items?.map((item: any, index: number) => ({
|
detail.failed_items?.map((item: any, index: number) => ({
|
||||||
row: index + 1,
|
row: index + 1,
|
||||||
deviceCode: item.device_no || '-',
|
deviceCode: item.virtual_no || '-',
|
||||||
iccid: item.iccid || '-',
|
iccid: item.iccid || '-',
|
||||||
message: item.reason || item.error || '未知错误'
|
message: item.reason || item.error || '未知错误'
|
||||||
})) || []
|
})) || []
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -142,7 +142,7 @@
|
|||||||
<ElTableColumn label="ICCID" prop="iccid" min-width="150" show-overflow-tooltip />
|
<ElTableColumn label="ICCID" prop="iccid" min-width="150" show-overflow-tooltip />
|
||||||
<ElTableColumn
|
<ElTableColumn
|
||||||
label="设备号"
|
label="设备号"
|
||||||
prop="device_no"
|
prop="virtual_no"
|
||||||
min-width="150"
|
min-width="150"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
/>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -125,11 +125,11 @@
|
|||||||
<ElOption
|
<ElOption
|
||||||
v-for="device in deviceOptions"
|
v-for="device in deviceOptions"
|
||||||
:key="device.id"
|
:key="device.id"
|
||||||
:label="`${device.device_no} (${device.device_name})`"
|
:label="`${device.virtual_no} (${device.device_name})`"
|
||||||
:value="device.id"
|
:value="device.id"
|
||||||
>
|
>
|
||||||
<div style="display: flex; justify-content: space-between">
|
<div style="display: flex; justify-content: space-between">
|
||||||
<span>{{ device.device_no }}</span>
|
<span>{{ device.virtual_no }}</span>
|
||||||
<span style="color: var(--el-text-color-secondary); font-size: 12px">
|
<span style="color: var(--el-text-color-secondary); font-size: 12px">
|
||||||
{{ device.device_name }}
|
{{ device.device_name }}
|
||||||
</span>
|
</span>
|
||||||
@@ -590,12 +590,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索设备(根据设备号device_no)
|
// 搜索设备(根据设备号virtual_no)
|
||||||
const searchDevices = async (query: string) => {
|
const searchDevices = async (query: string) => {
|
||||||
deviceSearchLoading.value = true
|
deviceSearchLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await DeviceService.getDevices({
|
const res = await DeviceService.getDevices({
|
||||||
device_no: query || undefined,
|
virtual_no: query || undefined,
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 20
|
page_size: 20
|
||||||
})
|
})
|
||||||
@@ -719,10 +719,8 @@
|
|||||||
purchased_by_platform: 'danger',
|
purchased_by_platform: 'danger',
|
||||||
purchase_for_subordinate: 'info'
|
purchase_for_subordinate: 'info'
|
||||||
}
|
}
|
||||||
return h(
|
return h(ElTag, { type: roleTypeMap[row.purchase_role] || 'info', size: 'small' }, () =>
|
||||||
ElTag,
|
getPurchaseRoleText(row.purchase_role)
|
||||||
{ type: roleTypeMap[row.purchase_role] || 'info', size: 'small' },
|
|
||||||
() => getPurchaseRoleText(row.purchase_role)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -61,11 +61,13 @@
|
|||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:title="dialogType === 'add' ? '新增套餐' : '编辑套餐'"
|
:title="dialogType === 'add' ? '新增套餐' : '编辑套餐'"
|
||||||
width="600px"
|
width="60%"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
@closed="handleDialogClosed"
|
@closed="handleDialogClosed"
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="150px">
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||||
|
<ElRow :gutter="20">
|
||||||
|
<ElCol :span="24">
|
||||||
<ElFormItem label="套餐编码" prop="package_code">
|
<ElFormItem label="套餐编码" prop="package_code">
|
||||||
<div style="display: flex; gap: 8px">
|
<div style="display: flex; gap: 8px">
|
||||||
<ElInput
|
<ElInput
|
||||||
@@ -73,16 +75,22 @@
|
|||||||
placeholder="请输入套餐编码或点击生成"
|
placeholder="请输入套餐编码或点击生成"
|
||||||
:disabled="dialogType === 'edit'"
|
:disabled="dialogType === 'edit'"
|
||||||
clearable
|
clearable
|
||||||
style="flex: 1"
|
|
||||||
/>
|
/>
|
||||||
<ElButton v-if="dialogType === 'add'" @click="handleGeneratePackageCode">
|
<ElButton v-if="dialogType === 'add'" @click="handleGeneratePackageCode">
|
||||||
生成编码
|
生成编码
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
|
||||||
|
<ElRow :gutter="20">
|
||||||
|
<ElCol :span="12">
|
||||||
<ElFormItem label="套餐名称" prop="package_name">
|
<ElFormItem label="套餐名称" prop="package_name">
|
||||||
<ElInput v-model="form.package_name" placeholder="请输入套餐名称" clearable />
|
<ElInput v-model="form.package_name" placeholder="请输入套餐名称" clearable />
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="12">
|
||||||
<ElFormItem label="所属系列" prop="series_id">
|
<ElFormItem label="所属系列" prop="series_id">
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="form.series_id"
|
v-model="form.series_id"
|
||||||
@@ -102,6 +110,11 @@
|
|||||||
/>
|
/>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
|
||||||
|
<ElRow :gutter="20">
|
||||||
|
<ElCol :span="12">
|
||||||
<ElFormItem label="套餐类型" prop="package_type">
|
<ElFormItem label="套餐类型" prop="package_type">
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="form.package_type"
|
v-model="form.package_type"
|
||||||
@@ -116,6 +129,8 @@
|
|||||||
/>
|
/>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="12">
|
||||||
<ElFormItem label="套餐周期类型" prop="calendar_type">
|
<ElFormItem label="套餐周期类型" prop="calendar_type">
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="form.calendar_type"
|
v-model="form.calendar_type"
|
||||||
@@ -127,6 +142,11 @@
|
|||||||
<ElOption label="按天" value="by_day" />
|
<ElOption label="按天" value="by_day" />
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
|
||||||
|
<ElRow :gutter="20">
|
||||||
|
<ElCol :span="12">
|
||||||
<ElFormItem label="有效期(月)" prop="duration_months">
|
<ElFormItem label="有效期(月)" prop="duration_months">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="form.duration_months"
|
v-model="form.duration_months"
|
||||||
@@ -137,11 +157,9 @@
|
|||||||
placeholder="请输入有效期(月)"
|
placeholder="请输入有效期(月)"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem
|
</ElCol>
|
||||||
v-if="form.calendar_type === 'by_day'"
|
<ElCol :span="12" v-if="form.calendar_type === 'by_day'">
|
||||||
label="套餐天数"
|
<ElFormItem label="套餐天数" prop="duration_days">
|
||||||
prop="duration_days"
|
|
||||||
>
|
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="form.duration_days"
|
v-model="form.duration_days"
|
||||||
:min="1"
|
:min="1"
|
||||||
@@ -151,6 +169,8 @@
|
|||||||
placeholder="请输入套餐天数"
|
placeholder="请输入套餐天数"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="12" v-if="form.calendar_type !== 'by_day'">
|
||||||
<ElFormItem label="流量重置周期" prop="data_reset_cycle">
|
<ElFormItem label="流量重置周期" prop="data_reset_cycle">
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="form.data_reset_cycle"
|
v-model="form.data_reset_cycle"
|
||||||
@@ -164,6 +184,26 @@
|
|||||||
<ElOption label="不重置" value="none" />
|
<ElOption label="不重置" value="none" />
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
|
||||||
|
<ElRow :gutter="20" v-if="form.calendar_type === 'by_day'">
|
||||||
|
<ElCol :span="12">
|
||||||
|
<ElFormItem label="流量重置周期" prop="data_reset_cycle">
|
||||||
|
<ElSelect
|
||||||
|
v-model="form.data_reset_cycle"
|
||||||
|
placeholder="请选择流量重置周期"
|
||||||
|
style="width: 100%"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<ElOption label="每日" value="daily" />
|
||||||
|
<ElOption label="每月" value="monthly" />
|
||||||
|
<ElOption label="每年" value="yearly" />
|
||||||
|
<ElOption label="不重置" value="none" />
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="12">
|
||||||
<ElFormItem label="真流量额度(MB)" prop="real_data_mb">
|
<ElFormItem label="真流量额度(MB)" prop="real_data_mb">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="form.real_data_mb"
|
v-model="form.real_data_mb"
|
||||||
@@ -173,6 +213,22 @@
|
|||||||
placeholder="请输入真流量额度"
|
placeholder="请输入真流量额度"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
|
||||||
|
<ElRow :gutter="20" v-if="form.calendar_type !== 'by_day'">
|
||||||
|
<ElCol :span="12">
|
||||||
|
<ElFormItem label="真流量额度(MB)" prop="real_data_mb">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="form.real_data_mb"
|
||||||
|
:min="0"
|
||||||
|
:controls="false"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请输入真流量额度"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="12">
|
||||||
<ElFormItem label="启用虚流量">
|
<ElFormItem label="启用虚流量">
|
||||||
<ElSwitch
|
<ElSwitch
|
||||||
v-model="form.enable_virtual_data"
|
v-model="form.enable_virtual_data"
|
||||||
@@ -180,11 +236,21 @@
|
|||||||
inactive-text="不启用"
|
inactive-text="不启用"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem
|
</ElCol>
|
||||||
label="虚流量额度(MB)"
|
</ElRow>
|
||||||
prop="virtual_data_mb"
|
|
||||||
v-if="form.enable_virtual_data"
|
<ElRow :gutter="20" v-if="form.calendar_type === 'by_day'">
|
||||||
>
|
<ElCol :span="12">
|
||||||
|
<ElFormItem label="启用虚流量">
|
||||||
|
<ElSwitch
|
||||||
|
v-model="form.enable_virtual_data"
|
||||||
|
active-text="启用"
|
||||||
|
inactive-text="不启用"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="12" v-if="form.enable_virtual_data">
|
||||||
|
<ElFormItem label="虚流量额度(MB)" prop="virtual_data_mb">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="form.virtual_data_mb"
|
v-model="form.virtual_data_mb"
|
||||||
:min="0"
|
:min="0"
|
||||||
@@ -193,6 +259,8 @@
|
|||||||
placeholder="请输入虚流量额度"
|
placeholder="请输入虚流量额度"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="12" v-if="!form.enable_virtual_data">
|
||||||
<ElFormItem label="启用实名激活">
|
<ElFormItem label="启用实名激活">
|
||||||
<ElSwitch
|
<ElSwitch
|
||||||
v-model="form.enable_realname_activation"
|
v-model="form.enable_realname_activation"
|
||||||
@@ -200,6 +268,43 @@
|
|||||||
inactive-text="不启用"
|
inactive-text="不启用"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
|
||||||
|
<ElRow :gutter="20" v-if="form.calendar_type !== 'by_day' && form.enable_virtual_data">
|
||||||
|
<ElCol :span="12">
|
||||||
|
<ElFormItem label="虚流量额度(MB)" prop="virtual_data_mb">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="form.virtual_data_mb"
|
||||||
|
:min="0"
|
||||||
|
:controls="false"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请输入虚流量额度"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="12">
|
||||||
|
<ElFormItem label="启用实名激活">
|
||||||
|
<ElSwitch
|
||||||
|
v-model="form.enable_realname_activation"
|
||||||
|
active-text="启用"
|
||||||
|
inactive-text="不启用"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
|
||||||
|
<ElRow :gutter="20" v-if="form.calendar_type !== 'by_day' && !form.enable_virtual_data">
|
||||||
|
<ElCol :span="12">
|
||||||
|
<ElFormItem label="启用实名激活">
|
||||||
|
<ElSwitch
|
||||||
|
v-model="form.enable_realname_activation"
|
||||||
|
active-text="启用"
|
||||||
|
inactive-text="不启用"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="12">
|
||||||
<ElFormItem label="成本价(元)" prop="cost_price">
|
<ElFormItem label="成本价(元)" prop="cost_price">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="form.cost_price"
|
v-model="form.cost_price"
|
||||||
@@ -211,6 +316,55 @@
|
|||||||
placeholder="请输入成本价"
|
placeholder="请输入成本价"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
|
||||||
|
<ElRow :gutter="20" v-if="form.calendar_type === 'by_day' && form.enable_virtual_data">
|
||||||
|
<ElCol :span="12">
|
||||||
|
<ElFormItem label="启用实名激活">
|
||||||
|
<ElSwitch
|
||||||
|
v-model="form.enable_realname_activation"
|
||||||
|
active-text="启用"
|
||||||
|
inactive-text="不启用"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="12">
|
||||||
|
<ElFormItem label="成本价(元)" prop="cost_price">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="form.cost_price"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.01"
|
||||||
|
:controls="false"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请输入成本价"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
|
||||||
|
<ElRow
|
||||||
|
:gutter="20"
|
||||||
|
v-if="
|
||||||
|
(form.calendar_type !== 'by_day' && form.enable_virtual_data) ||
|
||||||
|
(form.calendar_type === 'by_day' && !form.enable_virtual_data)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<ElCol :span="12">
|
||||||
|
<ElFormItem label="成本价(元)" prop="cost_price">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="form.cost_price"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.01"
|
||||||
|
:controls="false"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请输入成本价"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="12">
|
||||||
<ElFormItem label="建议售价(元)" prop="suggested_retail_price">
|
<ElFormItem label="建议售价(元)" prop="suggested_retail_price">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="form.suggested_retail_price"
|
v-model="form.suggested_retail_price"
|
||||||
@@ -222,6 +376,27 @@
|
|||||||
placeholder="请输入建议售价(可选)"
|
placeholder="请输入建议售价(可选)"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
|
||||||
|
<ElRow :gutter="20" v-if="form.calendar_type === 'by_day' && form.enable_virtual_data">
|
||||||
|
<ElCol :span="12">
|
||||||
|
<ElFormItem label="建议售价(元)" prop="suggested_retail_price">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="form.suggested_retail_price"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.01"
|
||||||
|
:controls="false"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请输入建议售价(可选)"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
|
||||||
|
<ElRow :gutter="20">
|
||||||
|
<ElCol :span="24">
|
||||||
<ElFormItem label="套餐描述" prop="description">
|
<ElFormItem label="套餐描述" prop="description">
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="form.description"
|
v-model="form.description"
|
||||||
@@ -232,6 +407,8 @@
|
|||||||
show-word-limit
|
show-word-limit
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
@@ -258,7 +435,6 @@
|
|||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
@@ -398,6 +574,7 @@
|
|||||||
{ label: '套餐类型', prop: 'package_type' },
|
{ label: '套餐类型', prop: 'package_type' },
|
||||||
{ label: '真流量', prop: 'real_data_mb' },
|
{ label: '真流量', prop: 'real_data_mb' },
|
||||||
{ label: '虚流量', prop: 'virtual_data_mb' },
|
{ label: '虚流量', prop: 'virtual_data_mb' },
|
||||||
|
{ label: '虚流量比例', prop: 'virtual_ratio' },
|
||||||
{ label: '有效期', prop: 'duration_months' },
|
{ label: '有效期', prop: 'duration_months' },
|
||||||
{ label: '成本价', prop: 'cost_price' },
|
{ label: '成本价', prop: 'cost_price' },
|
||||||
{ label: '建议售价', prop: 'suggested_retail_price' },
|
{ label: '建议售价', prop: 'suggested_retail_price' },
|
||||||
@@ -519,6 +696,20 @@
|
|||||||
width: 100,
|
width: 100,
|
||||||
formatter: (row: PackageResponse) => `${row.virtual_data_mb}MB`
|
formatter: (row: PackageResponse) => `${row.virtual_data_mb}MB`
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
prop: 'virtual_ratio',
|
||||||
|
label: '虚流量比例',
|
||||||
|
width: 120,
|
||||||
|
formatter: (row: PackageResponse) => {
|
||||||
|
// 如果启用虚流量且虚流量大于0,计算比例
|
||||||
|
if (row.enable_virtual_data && row.virtual_data_mb > 0) {
|
||||||
|
const ratio = row.real_data_mb / row.virtual_data_mb
|
||||||
|
return ratio.toFixed(2)
|
||||||
|
}
|
||||||
|
// 否则返回 1.0
|
||||||
|
return '1.00'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
prop: 'duration_months',
|
prop: 'duration_months',
|
||||||
label: '有效期',
|
label: '有效期',
|
||||||
|
|||||||
@@ -1809,10 +1809,14 @@
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 可选:强制充值配置
|
// 强制充值配置(无论开关状态都要传递)
|
||||||
|
data.enable_force_recharge = form.enable_force_recharge
|
||||||
if (form.enable_force_recharge) {
|
if (form.enable_force_recharge) {
|
||||||
data.enable_force_recharge = true
|
// 启用时传递强充金额(分)
|
||||||
data.force_recharge_amount = Math.round((form.force_recharge_amount || 0) * 100)
|
data.force_recharge_amount = Math.round((form.force_recharge_amount || 0) * 100)
|
||||||
|
} else {
|
||||||
|
// 关闭时传递 0
|
||||||
|
data.force_recharge_amount = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// 可选:套餐配置(将元转换为分)
|
// 可选:套餐配置(将元转换为分)
|
||||||
|
|||||||
Reference in New Issue
Block a user