diff --git a/src/api/modules/card.ts b/src/api/modules/card.ts index 4459b49..8cadd0d 100644 --- a/src/api/modules/card.ts +++ b/src/api/modules/card.ts @@ -381,4 +381,28 @@ export class CardService extends BaseService { `/api/admin/iot-cards/${iccid}/realname-link` ) } + + /** + * 启用 IoT 卡 + * @param id IoT卡ID + */ + static enableIotCard(id: number): Promise { + return this.post(`/api/admin/iot-cards/${id}/enable`, {}) + } + + /** + * 停用 IoT 卡 + * @param id IoT卡ID + */ + static disableIotCard(id: number): Promise { + return this.post(`/api/admin/iot-cards/${id}/disable`, {}) + } + + /** + * 手动停用 IoT 卡 + * @param id IoT卡ID + */ + static deactivateIotCard(id: number): Promise { + return this.patch(`/api/admin/iot-cards/${id}/deactivate`, {}) + } } diff --git a/src/api/modules/device.ts b/src/api/modules/device.ts index dfbe9f1..f17be3e 100644 --- a/src/api/modules/device.ts +++ b/src/api/modules/device.ts @@ -230,4 +230,12 @@ export class DeviceService extends BaseService { data ) } + + /** + * 手动停用设备 + * @param id 设备ID + */ + static deactivateDevice(id: number): Promise { + return this.patch(`/api/admin/devices/${id}/deactivate`, {}) + } } diff --git a/src/api/modules/exchange.ts b/src/api/modules/exchange.ts new file mode 100644 index 0000000..2b60814 --- /dev/null +++ b/src/api/modules/exchange.ts @@ -0,0 +1,129 @@ +/** + * 换货管理 API 服务 + */ + +import { BaseService } from '../BaseService' +import type { BaseResponse, PaginationResponse } from '@/types/api' + +// 换货单查询参数 +export interface ExchangeQueryParams { + page?: number + page_size?: number + status?: number // 换货状态 + identifier?: string // 资产标识符(模糊匹配) + created_at_start?: string // 创建时间起始 + created_at_end?: string // 创建时间结束 +} + +// 创建换货单请求 +export interface CreateExchangeRequest { + exchange_reason: string // 换货原因 + old_asset_type: string // 旧资产类型 (iot_card 或 device) + old_identifier: string // 旧资产标识符(ICCID/虚拟号/IMEI/SN) + remark?: string // 备注(可选) +} + +// 换货单响应 +export interface ExchangeResponse { + id: number + exchange_no: string + exchange_reason: string + old_asset_type: string + old_asset_identifier: string + new_asset_type: string + new_asset_identifier: string + status: number // 换货状态(1:待填写信息, 2:待发货, 3:已发货待确认, 4:已完成, 5:已取消) + status_text: string + recipient_name?: string + recipient_phone?: string + recipient_address?: string + express_company?: string + express_no?: string + remark?: string + created_at: string + updated_at: string +} + +// 换货发货请求 +export interface ShipExchangeRequest { + express_company: string // 快递公司 + express_no: string // 快递单号 + migrate_data: boolean // 是否迁移数据 + new_identifier: string // 新资产标识符(ICCID/虚拟号/IMEI/SN) +} + +// 取消换货请求 +export interface CancelExchangeRequest { + remark?: string // 取消备注(可选) +} + +export class ExchangeService extends BaseService { + /** + * 获取换货单列表 + * GET /api/admin/exchanges + * @param params 查询参数 + */ + static getExchanges( + params?: ExchangeQueryParams + ): Promise> { + return this.get< + BaseResponse<{ list: ExchangeResponse[]; page: number; page_size: number; total: number }> + >('/api/admin/exchanges', params) + } + + /** + * 创建换货单 + * POST /api/admin/exchanges + * @param data 创建参数 + */ + static createExchange(data: CreateExchangeRequest): Promise> { + return this.create('/api/admin/exchanges', data) + } + + /** + * 获取换货单详情 + * GET /api/admin/exchanges/{id} + * @param id 换货单ID + */ + static getExchangeDetail(id: number): Promise> { + return this.getOne(`/api/admin/exchanges/${id}`) + } + + /** + * 取消换货 + * POST /api/admin/exchanges/{id}/cancel + * @param id 换货单ID + * @param data 取消参数 + */ + static cancelExchange(id: number, data?: CancelExchangeRequest): Promise { + return this.post(`/api/admin/exchanges/${id}/cancel`, data || {}) + } + + /** + * 确认换货完成 + * POST /api/admin/exchanges/{id}/complete + * @param id 换货单ID + */ + static completeExchange(id: number): Promise { + return this.post(`/api/admin/exchanges/${id}/complete`, {}) + } + + /** + * 旧资产转新 + * POST /api/admin/exchanges/{id}/renew + * @param id 换货单ID + */ + static renewExchange(id: number): Promise { + return this.post(`/api/admin/exchanges/${id}/renew`, {}) + } + + /** + * 换货发货 + * POST /api/admin/exchanges/{id}/ship + * @param id 换货单ID + * @param data 发货参数 + */ + static shipExchange(id: number, data: ShipExchangeRequest): Promise> { + return this.post>(`/api/admin/exchanges/${id}/ship`, data) + } +} diff --git a/src/api/modules/index.ts b/src/api/modules/index.ts index 74036b0..53803b0 100644 --- a/src/api/modules/index.ts +++ b/src/api/modules/index.ts @@ -28,6 +28,7 @@ export { OrderService } from './order' export { AssetService } from './asset' export { AgentRechargeService } from './agentRecharge' export { WechatConfigService } from './wechatConfig' +export { ExchangeService } from './exchange' // TODO: 按需添加其他业务模块 // export { SettingService } from './setting' diff --git a/src/api/modules/packageManage.ts b/src/api/modules/packageManage.ts index 4ed1647..d150df7 100644 --- a/src/api/modules/packageManage.ts +++ b/src/api/modules/packageManage.ts @@ -87,4 +87,14 @@ export class PackageManageService extends BaseService { const data: UpdatePackageShelfStatusRequest = { shelf_status } return this.patch(`/api/admin/packages/${id}/shelf`, data) } + + /** + * 修改套餐零售价(代理) + * PATCH /api/admin/packages/{id}/retail-price + * @param id 套餐ID + * @param retail_price 零售价(分) + */ + static updateRetailPrice(id: number, retail_price: number): Promise { + return this.patch(`/api/admin/packages/${id}/retail-price`, { retail_price }) + } } diff --git a/src/components/device/SetWiFiDialog.vue b/src/components/device/SetWiFiDialog.vue new file mode 100644 index 0000000..f9ff027 --- /dev/null +++ b/src/components/device/SetWiFiDialog.vue @@ -0,0 +1,134 @@ + + + diff --git a/src/components/device/SpeedLimitDialog.vue b/src/components/device/SpeedLimitDialog.vue new file mode 100644 index 0000000..5865c57 --- /dev/null +++ b/src/components/device/SpeedLimitDialog.vue @@ -0,0 +1,111 @@ + + + diff --git a/src/components/device/SwitchCardDialog.vue b/src/components/device/SwitchCardDialog.vue new file mode 100644 index 0000000..a1f2d16 --- /dev/null +++ b/src/components/device/SwitchCardDialog.vue @@ -0,0 +1,153 @@ + + + diff --git a/src/locales/langs/en.json b/src/locales/langs/en.json index e23bcf3..7666ff4 100644 --- a/src/locales/langs/en.json +++ b/src/locales/langs/en.json @@ -458,7 +458,9 @@ "authorizationRecordDetail": "Authorization Record Details", "enterpriseDevices": "Enterprise Devices", "recordsManagement": "Records Management", - "taskManagement": "Task Management" + "taskManagement": "Task Management", + "exchangeManagement": "Exchange Management", + "exchangeDetail": "Exchange Order Detail" }, "settings": { "title": "Settings Management", diff --git a/src/locales/langs/zh.json b/src/locales/langs/zh.json index 79021e9..f3e3aa1 100644 --- a/src/locales/langs/zh.json +++ b/src/locales/langs/zh.json @@ -455,7 +455,9 @@ "authorizationRecordDetail": "授权记录详情", "enterpriseDevices": "企业设备列表", "recordsManagement": "记录管理", - "taskManagement": "任务管理" + "taskManagement": "任务管理", + "exchangeManagement": "换货管理", + "exchangeDetail": "换货单详情" }, "account": { "title": "账户管理", diff --git a/src/router/routes/asyncRoutes.ts b/src/router/routes/asyncRoutes.ts index 542ddc6..91d0ef3 100644 --- a/src/router/routes/asyncRoutes.ts +++ b/src/router/routes/asyncRoutes.ts @@ -1004,6 +1004,26 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: false } }, + // 换货管理 + { + path: 'exchange-management', + name: 'ExchangeManagement', + component: RoutesAlias.ExchangeManagement, + meta: { + title: 'menus.assetManagement.exchangeManagement', + keepAlive: true + } + }, + { + path: 'exchange-management/detail/:id', + name: 'ExchangeDetail', + component: RoutesAlias.ExchangeDetail, + meta: { + title: 'menus.assetManagement.exchangeDetail', + isHide: true, + keepAlive: false + } + }, // 记录管理 { path: 'records', diff --git a/src/router/routesAlias.ts b/src/router/routesAlias.ts index ef7bcaf..6f8edbf 100644 --- a/src/router/routesAlias.ts +++ b/src/router/routesAlias.ts @@ -106,6 +106,8 @@ export enum RoutesAlias { AuthorizationRecords = '/asset-management/authorization-records', // 授权记录 AuthorizationRecordDetail = '/asset-management/authorization-records/detail', // 授权记录详情 EnterpriseDevices = '/asset-management/enterprise-devices', // 企业设备列表 + ExchangeManagement = '/asset-management/exchange-management', // 换货管理 + ExchangeDetail = '/asset-management/exchange-management/detail', // 换货单详情 // 账户管理 CustomerAccountList = '/finance/customer-account', // 客户账号 diff --git a/src/types/components.d.ts b/src/types/components.d.ts index 0b260cb..c4f23d7 100644 --- a/src/types/components.d.ts +++ b/src/types/components.d.ts @@ -157,7 +157,10 @@ declare module 'vue' { SettingDrawer: typeof import('./../components/core/layouts/art-settings-panel/widget/SettingDrawer.vue')['default'] SettingHeader: typeof import('./../components/core/layouts/art-settings-panel/widget/SettingHeader.vue')['default'] SettingItem: typeof import('./../components/core/layouts/art-settings-panel/widget/SettingItem.vue')['default'] + SetWiFiDialog: typeof import('./../components/device/SetWiFiDialog.vue')['default'] SidebarSubmenu: typeof import('./../components/core/layouts/art-menus/art-sidebar-menu/widget/SidebarSubmenu.vue')['default'] + SpeedLimitDialog: typeof import('./../components/device/SpeedLimitDialog.vue')['default'] + SwitchCardDialog: typeof import('./../components/device/SwitchCardDialog.vue')['default'] TableContextMenuHint: typeof import('./../components/core/others/TableContextMenuHint.vue')['default'] ThemeSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/ThemeSettings.vue')['default'] } diff --git a/src/views/asset-management/device-list/index.vue b/src/views/asset-management/device-list/index.vue index c9d3920..2cf9a7f 100644 --- a/src/views/asset-management/device-list/index.vue +++ b/src/views/asset-management/device-list/index.vue @@ -452,7 +452,7 @@ label-width="120px" > - {{ currentOperatingDevice }} + {{ currentOperatingImei }} - - - - {{ currentOperatingDevice }} - - - - - + +
+ +
加载设备绑定的卡列表中...
+
+ + + @@ -519,7 +562,7 @@ label-width="120px" > - {{ currentOperatingDevice }} + {{ currentOperatingImei }} @@ -683,11 +726,12 @@ { type: 'number', min: 1, message: '速率不能小于1KB/s', trigger: 'blur' } ] }) - const currentOperatingDevice = ref('') // 设备操作右键菜单 const deviceOperationMenuRef = ref>() const currentOperatingDeviceNo = ref('') + const currentOperatingDevice = ref(null) + const currentOperatingImei = ref('') // 用于存储当前操作设备的IMEI const switchCardDialogVisible = ref(false) const switchCardLoading = ref(false) @@ -696,8 +740,10 @@ target_iccid: '' }) const switchCardRules = reactive({ - target_iccid: [{ required: true, message: '请输入目标ICCID', trigger: 'blur' }] + target_iccid: [{ required: true, message: '请选择目标ICCID', trigger: 'change' }] }) + const deviceBindingCards = ref([]) // 设备绑定的卡列表 + const loadingDeviceCards = ref(false) // 加载设备绑定卡列表的状态 const setWiFiDialogVisible = ref(false) const setWiFiLoading = ref(false) @@ -1454,6 +1500,9 @@ case 'set-wifi': showSetWiFiDialog(deviceNo) break + case 'manual-deactivate': + handleManualDeactivateDevice() + break case 'delete': handleDeleteDeviceByNo(deviceNo) break @@ -1470,6 +1519,39 @@ } } + // 手动停用设备(资产层面的停用) + const handleManualDeactivateDevice = () => { + const device = currentOperatingDevice.value + if (!device) { + ElMessage.error('未找到设备信息') + return + } + + ElMessageBox.confirm( + `确定要手动停用设备 ${device.virtual_no} 吗?此操作将在资产管理层面停用该设备。`, + '确认手动停用', + { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + } + ) + .then(async () => { + try { + const res = await DeviceService.deactivateDevice(device.id) + if (res.code === 0) { + ElMessage.success('手动停用成功') + loadDeviceList() + } + } catch (error: any) { + console.error('手动停用失败:', error) + } + }) + .catch(() => { + // 用户取消 + }) + } + // 通过设备号删除设备 const handleDeleteDeviceByNo = async (deviceNo: string) => { // 先根据设备号找到设备对象 @@ -1537,7 +1619,7 @@ // 显示设置限速对话框 const showSpeedLimitDialog = (imei: string) => { - currentOperatingDevice.value = imei + currentOperatingImei.value = imei speedLimitForm.download_speed = 1024 speedLimitForm.upload_speed = 512 speedLimitDialogVisible.value = true @@ -1551,7 +1633,7 @@ if (valid) { speedLimitLoading.value = true try { - const res = await DeviceService.setSpeedLimit(currentOperatingDevice.value, { + const res = await DeviceService.setSpeedLimit(currentOperatingImei.value, { download_speed: speedLimitForm.download_speed, upload_speed: speedLimitForm.upload_speed }) @@ -1572,10 +1654,35 @@ } // 显示切换SIM卡对话框 - const showSwitchCardDialog = (imei: string) => { - currentOperatingDevice.value = imei + const showSwitchCardDialog = async (deviceNo: string) => { + currentOperatingImei.value = deviceNo switchCardForm.target_iccid = '' + deviceBindingCards.value = [] switchCardDialogVisible.value = true + + // 使用当前操作的设备 + const device = currentOperatingDevice.value + if (!device) { + ElMessage.error('未找到设备信息') + return + } + + // 加载设备绑定的卡列表 + loadingDeviceCards.value = true + try { + const res = await DeviceService.getDeviceCards(device.id) + if (res.code === 0 && res.data) { + deviceBindingCards.value = res.data.bindings || [] + if (deviceBindingCards.value.length === 0) { + ElMessage.warning('该设备暂无绑定的SIM卡') + } + } + } catch (error) { + console.error('加载设备绑定卡列表失败:', error) + ElMessage.error('加载卡列表失败') + } finally { + loadingDeviceCards.value = false + } } // 确认切换SIM卡 @@ -1586,7 +1693,7 @@ if (valid) { switchCardLoading.value = true try { - const res = await DeviceService.switchCard(currentOperatingDevice.value, { + const res = await DeviceService.switchCard(currentOperatingImei.value, { target_iccid: switchCardForm.target_iccid }) if (res.code === 0) { @@ -1607,7 +1714,7 @@ // 显示设置WiFi对话框 const showSetWiFiDialog = (imei: string) => { - currentOperatingDevice.value = imei + currentOperatingImei.value = imei setWiFiForm.enabled = 1 setWiFiForm.ssid = '' setWiFiForm.password = '' @@ -1622,7 +1729,7 @@ if (valid) { setWiFiLoading.value = true try { - const res = await DeviceService.setWiFi(currentOperatingDevice.value, { + const res = await DeviceService.setWiFi(currentOperatingImei.value, { enabled: setWiFiForm.enabled, ssid: setWiFiForm.ssid, password: setWiFiForm.password @@ -1690,6 +1797,13 @@ }) } + if (hasAuth('device:manual_deactivate')) { + items.push({ + key: 'manual-deactivate', + label: '手动停用' + }) + } + if (hasAuth('device:delete')) { items.push({ key: 'delete', @@ -1701,10 +1815,11 @@ }) // 显示设备操作菜单 - const showDeviceOperationMenu = (e: MouseEvent, deviceNo: string) => { + const showDeviceOperationMenu = (e: MouseEvent, deviceNo: string, device?: Device) => { e.preventDefault() e.stopPropagation() currentOperatingDeviceNo.value = deviceNo + currentOperatingDevice.value = device || null deviceOperationMenuRef.value?.show(e) } @@ -1718,7 +1833,7 @@ // 处理表格行右键菜单 const handleRowContextMenu = (row: Device, column: any, event: MouseEvent) => { - showDeviceOperationMenu(event, row.virtual_no) + showDeviceOperationMenu(event, row.virtual_no, row) } diff --git a/src/views/asset-management/exchange-management/detail.vue b/src/views/asset-management/exchange-management/detail.vue new file mode 100644 index 0000000..f5d6177 --- /dev/null +++ b/src/views/asset-management/exchange-management/detail.vue @@ -0,0 +1,330 @@ + + + + + diff --git a/src/views/asset-management/exchange-management/index.vue b/src/views/asset-management/exchange-management/index.vue new file mode 100644 index 0000000..87580b6 --- /dev/null +++ b/src/views/asset-management/exchange-management/index.vue @@ -0,0 +1,385 @@ + + + + + diff --git a/src/views/asset-management/iot-card-management/index.vue b/src/views/asset-management/iot-card-management/index.vue index 59a90e2..1975aa1 100644 --- a/src/views/asset-management/iot-card-management/index.vue +++ b/src/views/asset-management/iot-card-management/index.vue @@ -662,6 +662,7 @@ const moreMenuRef = ref>() const cardOperationMenuRef = ref>() const currentOperatingIccid = ref('') + const currentOperatingCard = ref(null) // 店铺相关 const targetShopLoading = ref(false) @@ -1563,6 +1564,13 @@ }) } + if (hasAuth('iot_card:manual_deactivate')) { + items.push({ + key: 'manual-deactivate', + label: '手动停用' + }) + } + return items }) @@ -1601,10 +1609,11 @@ } // 显示卡操作菜单 - const showCardOperationMenu = (e: MouseEvent, iccid: string) => { + const showCardOperationMenu = (e: MouseEvent, iccid: string, card?: StandaloneIotCard) => { e.preventDefault() e.stopPropagation() currentOperatingIccid.value = iccid + currentOperatingCard.value = card || null cardOperationMenuRef.value?.show(e) } @@ -1660,6 +1669,9 @@ case 'stop-card': handleStopCard(iccid) break + case 'manual-deactivate': + handleManualDeactivate() + break } } @@ -1810,9 +1822,42 @@ }) } + // 手动停用 IoT 卡(资产层面的停用) + const handleManualDeactivate = () => { + const card = currentOperatingCard.value + if (!card) { + ElMessage.error('未找到卡片信息') + return + } + + ElMessageBox.confirm( + `确定要手动停用卡片 ${card.iccid} 吗?此操作将在资产管理层面停用该卡。`, + '确认手动停用', + { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + } + ) + .then(async () => { + try { + const res = await CardService.deactivateIotCard(card.id) + if (res.code === 0) { + ElMessage.success('手动停用成功') + getTableData() + } + } catch (error: any) { + console.error('手动停用失败:', error) + } + }) + .catch(() => { + // 用户取消 + }) + } + // 处理表格行右键菜单 const handleRowContextMenu = (row: StandaloneIotCard, column: any, event: MouseEvent) => { - showCardOperationMenu(event, row.iccid) + showCardOperationMenu(event, row.iccid, row) } diff --git a/src/views/finance/carrier-management/index.vue b/src/views/finance/carrier-management/index.vue index 6e9ddf0..aa32010 100644 --- a/src/views/finance/carrier-management/index.vue +++ b/src/views/finance/carrier-management/index.vue @@ -96,6 +96,32 @@ placeholder="请输入运营商描述" /> + + + + + + + + + +
+ 支持变量:{iccid}、{msisdn}等 +
+
- + {{ cardInfo?.virtual_no || '--' }} @@ -212,6 +146,80 @@ cardInfo?.activated_at || '--' }} + +
+ + 启用此卡 + + + 停用此卡 + + + 手动停用 + +
+ + +
+ + 重启设备 + + + 恢复出厂 + + + 设置限速 + + + 切换SIM卡 + + + 设置WiFi + + + 手动停用 + +
@@ -720,6 +728,162 @@ + + + + + + {{ cardInfo?.imei || cardInfo?.virtual_no }} + + + +
单位: KB/s
+
+ + +
单位: KB/s
+
+
+ +
+ + + +
+ +
加载设备绑定的卡列表中...
+
+ + + + +
+ + + + + + {{ cardInfo?.imei || cardInfo?.virtual_no }} + + + + 启用 + 禁用 + + + + + + + + + + + @@ -727,6 +891,7 @@ import { ElTag, ElMessage, + ElButton, ElTable, ElTableColumn, ElProgress, @@ -741,11 +906,22 @@ ElSelect, ElOption, ElDatePicker, - ElPagination + ElPagination, + ElDialog, + ElForm, + ElFormItem, + ElInputNumber, + ElAlert, + ElIcon, + ElRadioGroup, + ElRadio, + ElInput } from 'element-plus' + import type { FormInstance, FormRules } from 'element-plus' + import { Loading } from '@element-plus/icons-vue' import { useRoute, useRouter } from 'vue-router' import { EnterpriseService } from '@/api/modules/enterprise' - import { CardService, AssetService } from '@/api/modules' + import { CardService, AssetService, DeviceService } from '@/api/modules' import { formatDateTime } from '@/utils/business/format' import type { AssetWalletTransactionItem, @@ -762,8 +938,66 @@ const refreshLoading = ref(false) const stopLoading = ref(false) const startLoading = ref(false) + + // IoT卡操作loading + const enableCardLoading = ref(false) + const disableCardLoading = ref(false) + const manualDeactivateCardLoading = ref(false) + + // 设备操作loading + const rebootDeviceLoading = ref(false) + const resetDeviceLoading = ref(false) + const speedLimitLoading = ref(false) + const switchCardLoading = ref(false) + const setWiFiLoading = ref(false) + const manualDeactivateDeviceLoading = ref(false) + + // 弹窗相关 + const speedLimitDialogVisible = ref(false) + const speedLimitConfirmLoading = ref(false) + const speedLimitFormRef = ref(null) + const speedLimitForm = reactive({ + download_speed: 1024, + upload_speed: 512 + }) + const speedLimitRules = reactive({ + download_speed: [{ required: true, message: '请输入下行速率', trigger: 'blur' }], + upload_speed: [{ required: true, message: '请输入上行速率', trigger: 'blur' }] + }) + + const switchCardDialogVisible = ref(false) + const switchCardConfirmLoading = ref(false) + const switchCardFormRef = ref(null) + const switchCardForm = reactive({ + target_iccid: '' + }) + const switchCardRules = reactive({ + target_iccid: [{ required: true, message: '请选择目标ICCID', trigger: 'change' }] + }) + const deviceBindingCards = ref([]) + const loadingDeviceCards = ref(false) + + const setWiFiDialogVisible = ref(false) + const setWiFiConfirmLoading = ref(false) + const setWiFiFormRef = ref(null) + const setWiFiForm = reactive({ + enabled: 1, + ssid: '', + password: '' + }) + const setWiFiRules = reactive({ + ssid: [ + { required: true, message: '请输入WiFi名称', trigger: 'blur' }, + { min: 1, max: 32, message: 'WiFi名称长度为1-32个字符', trigger: 'blur' } + ], + password: [ + { required: true, message: '请输入WiFi密码', trigger: 'blur' }, + { min: 8, max: 63, message: 'WiFi密码长度为8-63个字符', trigger: 'blur' } + ] + }) + const isSearchCardFixed = ref(false) - const searchCardRef = ref(null) + const searchCardRef = ref(null) const searchCardPlaceholder = ref(null) const cardOriginalTop = ref(0) const cardLeft = ref(0) @@ -1190,8 +1424,8 @@ // 更新卡片位置信息 const updateCardPosition = () => { - if (searchCardRef.value) { - const rect = searchCardRef.value.getBoundingClientRect() + if (searchCardRef.value && searchCardRef.value.$el) { + const rect = searchCardRef.value.$el.getBoundingClientRect() cardOriginalTop.value = rect.top + window.scrollY cardLeft.value = rect.left + window.scrollX cardWidth.value = rect.width @@ -1703,6 +1937,288 @@ } }) } + + // ========== IoT卡操作 ========== + + // 启用此卡 + const handleEnableCard = async () => { + try { + await ElMessageBox.confirm('确认要启用此卡吗?', '提示', { + type: 'warning' + }) + + enableCardLoading.value = true + const res = await CardService.enableIotCard(cardInfo.value.asset_id) + if (res.code === 0) { + ElMessage.success('启用成功') + await handleRefreshAsset() + } + } catch (error: any) { + if (error !== 'cancel') { + console.error('启用失败:', error) + ElMessage.error(error?.message || '启用失败') + } + } finally { + enableCardLoading.value = false + } + } + + // 停用此卡 + const handleDisableCard = async () => { + try { + await ElMessageBox.confirm('确认要停用此卡吗?', '提示', { + type: 'warning' + }) + + disableCardLoading.value = true + const res = await CardService.disableIotCard(cardInfo.value.asset_id) + if (res.code === 0) { + ElMessage.success('停用成功') + await handleRefreshAsset() + } + } catch (error: any) { + if (error !== 'cancel') { + console.error('停用失败:', error) + ElMessage.error(error?.message || '停用失败') + } + } finally { + disableCardLoading.value = false + } + } + + // 手动停用IoT卡 + const handleManualDeactivateCard = async () => { + try { + await ElMessageBox.confirm( + '手动停用后卡片将永久停用,无法再次使用。此操作不可逆,请谨慎操作。确认要手动停用吗?', + '警告', + { + type: 'error', + confirmButtonText: '确认停用', + cancelButtonText: '取消' + } + ) + + manualDeactivateCardLoading.value = true + const res = await CardService.deactivateIotCard(cardInfo.value.asset_id) + if (res.code === 0) { + ElMessage.success('手动停用成功') + await handleRefreshAsset() + } + } catch (error: any) { + if (error !== 'cancel') { + console.error('手动停用失败:', error) + ElMessage.error(error?.message || '手动停用失败') + } + } finally { + manualDeactivateCardLoading.value = false + } + } + + // ========== 设备操作 ========== + + // 重启设备 + const handleRebootDevice = async () => { + try { + await ElMessageBox.confirm('确认要重启设备吗?', '提示', { + type: 'warning' + }) + + rebootDeviceLoading.value = true + const res = await DeviceService.rebootDevice(cardInfo.value.imei) + if (res.code === 0) { + ElMessage.success(res.data?.message || '重启指令已发送') + await handleRefreshAsset() + } + } catch (error: any) { + if (error !== 'cancel') { + console.error('重启失败:', error) + ElMessage.error(error?.message || '重启失败') + } + } finally { + rebootDeviceLoading.value = false + } + } + + // 恢复出厂设置 + const handleResetDevice = async () => { + try { + await ElMessageBox.confirm('恢复出厂设置将清除设备所有数据和配置,确认要继续吗?', '警告', { + type: 'error', + confirmButtonText: '确认恢复', + cancelButtonText: '取消' + }) + + resetDeviceLoading.value = true + const res = await DeviceService.resetDevice(cardInfo.value.imei) + if (res.code === 0) { + ElMessage.success(res.data?.message || '恢复出厂设置指令已发送') + await handleRefreshAsset() + } + } catch (error: any) { + if (error !== 'cancel') { + console.error('恢复出厂失败:', error) + ElMessage.error(error?.message || '恢复出厂失败') + } + } finally { + resetDeviceLoading.value = false + } + } + + // 设置限速(显示对话框) + const handleShowSpeedLimitDialog = () => { + speedLimitForm.download_speed = 1024 + speedLimitForm.upload_speed = 512 + speedLimitDialogVisible.value = true + } + + // 确认设置限速 + const handleConfirmSpeedLimit = async () => { + if (!speedLimitFormRef.value) return + + await speedLimitFormRef.value.validate(async (valid: boolean) => { + if (valid) { + speedLimitConfirmLoading.value = true + try { + const res = await DeviceService.setSpeedLimit(cardInfo.value.imei, { + download_speed: speedLimitForm.download_speed, + upload_speed: speedLimitForm.upload_speed + }) + if (res.code === 0) { + ElMessage.success('限速设置成功') + speedLimitDialogVisible.value = false + await handleRefreshAsset() + } else { + ElMessage.error(res.message || '设置失败') + } + } catch (error: any) { + console.error('设置限速失败:', error) + ElMessage.error(error?.message || '设置失败') + } finally { + speedLimitConfirmLoading.value = false + } + } + }) + } + + // 切换SIM卡(显示对话框) + const handleShowSwitchCardDialog = async () => { + switchCardForm.target_iccid = '' + deviceBindingCards.value = [] + switchCardDialogVisible.value = true + + // 加载设备绑定的卡列表 + loadingDeviceCards.value = true + try { + const res = await DeviceService.getDeviceCards(cardInfo.value.asset_id) + if (res.code === 0 && res.data) { + deviceBindingCards.value = res.data.bindings || [] + if (deviceBindingCards.value.length === 0) { + ElMessage.warning('该设备暂无绑定的SIM卡') + } + } + } catch (error) { + console.error('加载设备绑定卡列表失败:', error) + ElMessage.error('加载卡列表失败') + } finally { + loadingDeviceCards.value = false + } + } + + // 确认切换SIM卡 + const handleConfirmSwitchCard = async () => { + if (!switchCardFormRef.value) return + + await switchCardFormRef.value.validate(async (valid: boolean) => { + if (valid) { + switchCardConfirmLoading.value = true + try { + const res = await DeviceService.switchCard(cardInfo.value.imei, { + target_iccid: switchCardForm.target_iccid + }) + if (res.code === 0) { + ElMessage.success('切换SIM卡指令已发送') + switchCardDialogVisible.value = false + await handleRefreshAsset() + } else { + ElMessage.error(res.message || '切换失败') + } + } catch (error: any) { + console.error('切换SIM卡失败:', error) + ElMessage.error(error?.message || '切换失败') + } finally { + switchCardConfirmLoading.value = false + } + } + }) + } + + // 设置WiFi(显示对话框) + const handleShowSetWiFiDialog = () => { + setWiFiForm.enabled = 1 + setWiFiForm.ssid = '' + setWiFiForm.password = '' + setWiFiDialogVisible.value = true + } + + // 确认设置WiFi + const handleConfirmSetWiFi = async () => { + if (!setWiFiFormRef.value) return + + await setWiFiFormRef.value.validate(async (valid: boolean) => { + if (valid) { + setWiFiConfirmLoading.value = true + try { + const res = await DeviceService.setWiFi(cardInfo.value.imei, { + enabled: setWiFiForm.enabled, + ssid: setWiFiForm.ssid, + password: setWiFiForm.password + }) + if (res.code === 0) { + ElMessage.success('WiFi设置成功') + setWiFiDialogVisible.value = false + await handleRefreshAsset() + } else { + ElMessage.error(res.message || '设置失败') + } + } catch (error: any) { + console.error('设置WiFi失败:', error) + ElMessage.error(error?.message || '设置WiFi失败') + } finally { + setWiFiConfirmLoading.value = false + } + } + }) + } + + // 手动停用设备 + const handleManualDeactivateDevice = async () => { + try { + await ElMessageBox.confirm( + '手动停用后设备将永久停用,无法再次使用。此操作不可逆,请谨慎操作。确认要手动停用吗?', + '警告', + { + type: 'error', + confirmButtonText: '确认停用', + cancelButtonText: '取消' + } + ) + + manualDeactivateDeviceLoading.value = true + const res = await DeviceService.deactivateDevice(cardInfo.value.asset_id) + if (res.code === 0) { + ElMessage.success('手动停用成功') + await handleRefreshAsset() + } + } catch (error: any) { + if (error !== 'cancel') { + console.error('手动停用失败:', error) + ElMessage.error(error?.message || '手动停用失败') + } + } finally { + manualDeactivateDeviceLoading.value = false + } + }