This commit is contained in:
@@ -381,4 +381,28 @@ export class CardService extends BaseService {
|
|||||||
`/api/admin/iot-cards/${iccid}/realname-link`
|
`/api/admin/iot-cards/${iccid}/realname-link`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用 IoT 卡
|
||||||
|
* @param id IoT卡ID
|
||||||
|
*/
|
||||||
|
static enableIotCard(id: number): Promise<BaseResponse> {
|
||||||
|
return this.post<BaseResponse>(`/api/admin/iot-cards/${id}/enable`, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停用 IoT 卡
|
||||||
|
* @param id IoT卡ID
|
||||||
|
*/
|
||||||
|
static disableIotCard(id: number): Promise<BaseResponse> {
|
||||||
|
return this.post<BaseResponse>(`/api/admin/iot-cards/${id}/disable`, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动停用 IoT 卡
|
||||||
|
* @param id IoT卡ID
|
||||||
|
*/
|
||||||
|
static deactivateIotCard(id: number): Promise<BaseResponse> {
|
||||||
|
return this.patch<BaseResponse>(`/api/admin/iot-cards/${id}/deactivate`, {})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,4 +230,12 @@ export class DeviceService extends BaseService {
|
|||||||
data
|
data
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动停用设备
|
||||||
|
* @param id 设备ID
|
||||||
|
*/
|
||||||
|
static deactivateDevice(id: number): Promise<BaseResponse> {
|
||||||
|
return this.patch<BaseResponse>(`/api/admin/devices/${id}/deactivate`, {})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
129
src/api/modules/exchange.ts
Normal file
129
src/api/modules/exchange.ts
Normal file
@@ -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<BaseResponse<{ list: ExchangeResponse[]; page: number; page_size: number; total: number }>> {
|
||||||
|
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<BaseResponse<ExchangeResponse>> {
|
||||||
|
return this.create<ExchangeResponse>('/api/admin/exchanges', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取换货单详情
|
||||||
|
* GET /api/admin/exchanges/{id}
|
||||||
|
* @param id 换货单ID
|
||||||
|
*/
|
||||||
|
static getExchangeDetail(id: number): Promise<BaseResponse<ExchangeResponse>> {
|
||||||
|
return this.getOne<ExchangeResponse>(`/api/admin/exchanges/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消换货
|
||||||
|
* POST /api/admin/exchanges/{id}/cancel
|
||||||
|
* @param id 换货单ID
|
||||||
|
* @param data 取消参数
|
||||||
|
*/
|
||||||
|
static cancelExchange(id: number, data?: CancelExchangeRequest): Promise<BaseResponse> {
|
||||||
|
return this.post<BaseResponse>(`/api/admin/exchanges/${id}/cancel`, data || {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认换货完成
|
||||||
|
* POST /api/admin/exchanges/{id}/complete
|
||||||
|
* @param id 换货单ID
|
||||||
|
*/
|
||||||
|
static completeExchange(id: number): Promise<BaseResponse> {
|
||||||
|
return this.post<BaseResponse>(`/api/admin/exchanges/${id}/complete`, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 旧资产转新
|
||||||
|
* POST /api/admin/exchanges/{id}/renew
|
||||||
|
* @param id 换货单ID
|
||||||
|
*/
|
||||||
|
static renewExchange(id: number): Promise<BaseResponse> {
|
||||||
|
return this.post<BaseResponse>(`/api/admin/exchanges/${id}/renew`, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 换货发货
|
||||||
|
* POST /api/admin/exchanges/{id}/ship
|
||||||
|
* @param id 换货单ID
|
||||||
|
* @param data 发货参数
|
||||||
|
*/
|
||||||
|
static shipExchange(id: number, data: ShipExchangeRequest): Promise<BaseResponse<ExchangeResponse>> {
|
||||||
|
return this.post<BaseResponse<ExchangeResponse>>(`/api/admin/exchanges/${id}/ship`, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ export { OrderService } from './order'
|
|||||||
export { AssetService } from './asset'
|
export { AssetService } from './asset'
|
||||||
export { AgentRechargeService } from './agentRecharge'
|
export { AgentRechargeService } from './agentRecharge'
|
||||||
export { WechatConfigService } from './wechatConfig'
|
export { WechatConfigService } from './wechatConfig'
|
||||||
|
export { ExchangeService } from './exchange'
|
||||||
|
|
||||||
// TODO: 按需添加其他业务模块
|
// TODO: 按需添加其他业务模块
|
||||||
// export { SettingService } from './setting'
|
// export { SettingService } from './setting'
|
||||||
|
|||||||
@@ -87,4 +87,14 @@ export class PackageManageService extends BaseService {
|
|||||||
const data: UpdatePackageShelfStatusRequest = { shelf_status }
|
const data: UpdatePackageShelfStatusRequest = { shelf_status }
|
||||||
return this.patch<BaseResponse>(`/api/admin/packages/${id}/shelf`, data)
|
return this.patch<BaseResponse>(`/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<BaseResponse> {
|
||||||
|
return this.patch<BaseResponse>(`/api/admin/packages/${id}/retail-price`, { retail_price })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
134
src/components/device/SetWiFiDialog.vue
Normal file
134
src/components/device/SetWiFiDialog.vue
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
<template>
|
||||||
|
<ElDialog v-model="visible" title="设置WiFi" width="500px" @close="handleClose">
|
||||||
|
<ElForm
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<ElFormItem label="设备信息">
|
||||||
|
<span style="font-weight: bold; color: #409eff">{{ deviceInfo }}</span>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="WiFi状态" prop="enabled">
|
||||||
|
<ElRadioGroup v-model="formData.enabled">
|
||||||
|
<ElRadio :value="1">启用</ElRadio>
|
||||||
|
<ElRadio :value="0">禁用</ElRadio>
|
||||||
|
</ElRadioGroup>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="WiFi名称" prop="ssid">
|
||||||
|
<ElInput
|
||||||
|
v-model="formData.ssid"
|
||||||
|
placeholder="请输入WiFi名称(1-32个字符)"
|
||||||
|
maxlength="32"
|
||||||
|
show-word-limit
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="WiFi密码" prop="password">
|
||||||
|
<ElInput
|
||||||
|
v-model="formData.password"
|
||||||
|
type="password"
|
||||||
|
placeholder="请输入WiFi密码(8-63个字符)"
|
||||||
|
maxlength="63"
|
||||||
|
show-word-limit
|
||||||
|
show-password
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
<template #footer>
|
||||||
|
<ElButton @click="visible = false">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="handleConfirm" :loading="confirmLoading">
|
||||||
|
确认设置
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, watch } from 'vue'
|
||||||
|
import {
|
||||||
|
ElDialog,
|
||||||
|
ElForm,
|
||||||
|
ElFormItem,
|
||||||
|
ElInput,
|
||||||
|
ElButton,
|
||||||
|
ElRadioGroup,
|
||||||
|
ElRadio
|
||||||
|
} from 'element-plus'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: boolean
|
||||||
|
deviceInfo: string
|
||||||
|
loading?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:modelValue', value: boolean): void
|
||||||
|
(e: 'confirm', data: { enabled: number; ssid: string; password: string }): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
loading: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const visible = ref(props.modelValue)
|
||||||
|
const confirmLoading = ref(props.loading)
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
enabled: 1,
|
||||||
|
ssid: '',
|
||||||
|
password: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = reactive<FormRules>({
|
||||||
|
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' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (val) => {
|
||||||
|
visible.value = val
|
||||||
|
if (val) {
|
||||||
|
// 重置表单
|
||||||
|
formData.enabled = 1
|
||||||
|
formData.ssid = ''
|
||||||
|
formData.password = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(visible, (val) => {
|
||||||
|
emit('update:modelValue', val)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.loading, (val) => {
|
||||||
|
confirmLoading.value = val
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirm = async () => {
|
||||||
|
if (!formRef.value) return
|
||||||
|
|
||||||
|
await formRef.value.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
emit('confirm', {
|
||||||
|
enabled: formData.enabled,
|
||||||
|
ssid: formData.ssid,
|
||||||
|
password: formData.password
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
111
src/components/device/SpeedLimitDialog.vue
Normal file
111
src/components/device/SpeedLimitDialog.vue
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<template>
|
||||||
|
<ElDialog v-model="visible" title="设置限速" width="500px" @close="handleClose">
|
||||||
|
<ElForm
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<ElFormItem label="设备信息">
|
||||||
|
<span style="font-weight: bold; color: #409eff">{{ deviceInfo }}</span>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="下行速率" prop="download_speed">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="formData.download_speed"
|
||||||
|
:min="1"
|
||||||
|
:step="128"
|
||||||
|
controls-position="right"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
<div style="color: #909399; font-size: 12px; margin-top: 4px">单位: KB/s</div>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="上行速率" prop="upload_speed">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="formData.upload_speed"
|
||||||
|
:min="1"
|
||||||
|
:step="128"
|
||||||
|
controls-position="right"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
<div style="color: #909399; font-size: 12px; margin-top: 4px">单位: KB/s</div>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
<template #footer>
|
||||||
|
<ElButton @click="visible = false">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="handleConfirm" :loading="confirmLoading">
|
||||||
|
确认设置
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, watch } from 'vue'
|
||||||
|
import { ElDialog, ElForm, ElFormItem, ElInputNumber, ElButton, ElMessage } from 'element-plus'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: boolean
|
||||||
|
deviceInfo: string
|
||||||
|
loading?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:modelValue', value: boolean): void
|
||||||
|
(e: 'confirm', data: { download_speed: number; upload_speed: number }): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
loading: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const visible = ref(props.modelValue)
|
||||||
|
const confirmLoading = ref(props.loading)
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
download_speed: 1024,
|
||||||
|
upload_speed: 512
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = reactive<FormRules>({
|
||||||
|
download_speed: [{ required: true, message: '请输入下行速率', trigger: 'blur' }],
|
||||||
|
upload_speed: [{ required: true, message: '请输入上行速率', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (val) => {
|
||||||
|
visible.value = val
|
||||||
|
if (val) {
|
||||||
|
// 重置表单
|
||||||
|
formData.download_speed = 1024
|
||||||
|
formData.upload_speed = 512
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(visible, (val) => {
|
||||||
|
emit('update:modelValue', val)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.loading, (val) => {
|
||||||
|
confirmLoading.value = val
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirm = async () => {
|
||||||
|
if (!formRef.value) return
|
||||||
|
|
||||||
|
await formRef.value.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
emit('confirm', {
|
||||||
|
download_speed: formData.download_speed,
|
||||||
|
upload_speed: formData.upload_speed
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
153
src/components/device/SwitchCardDialog.vue
Normal file
153
src/components/device/SwitchCardDialog.vue
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
<template>
|
||||||
|
<ElDialog v-model="visible" title="切换SIM卡" width="600px" @close="handleClose">
|
||||||
|
<div v-if="loading" style="text-align: center; padding: 40px">
|
||||||
|
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||||
|
<div style="margin-top: 16px">加载设备绑定的卡列表中...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<ElAlert
|
||||||
|
v-if="cards.length === 0"
|
||||||
|
title="该设备暂无绑定的SIM卡"
|
||||||
|
type="warning"
|
||||||
|
:closable="false"
|
||||||
|
style="margin-bottom: 20px"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<div>当前设备没有绑定任何SIM卡,无法进行切换操作。</div>
|
||||||
|
<div>请先为设备绑定SIM卡后再进行切换。</div>
|
||||||
|
</template>
|
||||||
|
</ElAlert>
|
||||||
|
|
||||||
|
<ElForm
|
||||||
|
v-if="cards.length > 0"
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<ElFormItem label="设备信息">
|
||||||
|
<span style="font-weight: bold; color: #409eff">{{ deviceInfo }}</span>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="目标ICCID" prop="target_iccid">
|
||||||
|
<ElSelect
|
||||||
|
v-model="formData.target_iccid"
|
||||||
|
placeholder="请选择要切换到的目标ICCID"
|
||||||
|
style="width: 100%"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="card in cards"
|
||||||
|
:key="card.iccid"
|
||||||
|
:label="`${card.iccid} - 插槽${card.slot_position} - ${card.carrier_name}`"
|
||||||
|
:value="card.iccid"
|
||||||
|
>
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||||
|
<span>{{ card.iccid }}</span>
|
||||||
|
<ElTag size="small" style="margin-left: 10px">插槽{{ card.slot_position }}</ElTag>
|
||||||
|
</div>
|
||||||
|
</ElOption>
|
||||||
|
</ElSelect>
|
||||||
|
<div style="margin-top: 8px; font-size: 12px; color: #909399">
|
||||||
|
当前设备共绑定 {{ cards.length }} 张SIM卡
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<ElButton @click="visible = false">取消</ElButton>
|
||||||
|
<ElButton
|
||||||
|
v-if="cards.length > 0"
|
||||||
|
type="primary"
|
||||||
|
@click="handleConfirm"
|
||||||
|
:loading="confirmLoading"
|
||||||
|
>
|
||||||
|
确认切换
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, watch } from 'vue'
|
||||||
|
import {
|
||||||
|
ElDialog,
|
||||||
|
ElForm,
|
||||||
|
ElFormItem,
|
||||||
|
ElSelect,
|
||||||
|
ElOption,
|
||||||
|
ElButton,
|
||||||
|
ElAlert,
|
||||||
|
ElIcon,
|
||||||
|
ElTag
|
||||||
|
} from 'element-plus'
|
||||||
|
import { Loading } from '@element-plus/icons-vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
|
||||||
|
interface CardBinding {
|
||||||
|
id: number
|
||||||
|
iccid: string
|
||||||
|
slot_position: number
|
||||||
|
carrier_name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: boolean
|
||||||
|
deviceInfo: string
|
||||||
|
cards: CardBinding[]
|
||||||
|
loading?: boolean
|
||||||
|
confirmLoading?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:modelValue', value: boolean): void
|
||||||
|
(e: 'confirm', data: { target_iccid: string }): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
loading: false,
|
||||||
|
confirmLoading: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const visible = ref(props.modelValue)
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
target_iccid: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = reactive<FormRules>({
|
||||||
|
target_iccid: [{ required: true, message: '请选择目标ICCID', trigger: 'change' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (val) => {
|
||||||
|
visible.value = val
|
||||||
|
if (val) {
|
||||||
|
// 重置表单
|
||||||
|
formData.target_iccid = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(visible, (val) => {
|
||||||
|
emit('update:modelValue', val)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirm = async () => {
|
||||||
|
if (!formRef.value) return
|
||||||
|
|
||||||
|
await formRef.value.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
emit('confirm', {
|
||||||
|
target_iccid: formData.target_iccid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -458,7 +458,9 @@
|
|||||||
"authorizationRecordDetail": "Authorization Record Details",
|
"authorizationRecordDetail": "Authorization Record Details",
|
||||||
"enterpriseDevices": "Enterprise Devices",
|
"enterpriseDevices": "Enterprise Devices",
|
||||||
"recordsManagement": "Records Management",
|
"recordsManagement": "Records Management",
|
||||||
"taskManagement": "Task Management"
|
"taskManagement": "Task Management",
|
||||||
|
"exchangeManagement": "Exchange Management",
|
||||||
|
"exchangeDetail": "Exchange Order Detail"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Settings Management",
|
"title": "Settings Management",
|
||||||
|
|||||||
@@ -455,7 +455,9 @@
|
|||||||
"authorizationRecordDetail": "授权记录详情",
|
"authorizationRecordDetail": "授权记录详情",
|
||||||
"enterpriseDevices": "企业设备列表",
|
"enterpriseDevices": "企业设备列表",
|
||||||
"recordsManagement": "记录管理",
|
"recordsManagement": "记录管理",
|
||||||
"taskManagement": "任务管理"
|
"taskManagement": "任务管理",
|
||||||
|
"exchangeManagement": "换货管理",
|
||||||
|
"exchangeDetail": "换货单详情"
|
||||||
},
|
},
|
||||||
"account": {
|
"account": {
|
||||||
"title": "账户管理",
|
"title": "账户管理",
|
||||||
|
|||||||
@@ -1004,6 +1004,26 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
keepAlive: false
|
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',
|
path: 'records',
|
||||||
|
|||||||
@@ -106,6 +106,8 @@ export enum RoutesAlias {
|
|||||||
AuthorizationRecords = '/asset-management/authorization-records', // 授权记录
|
AuthorizationRecords = '/asset-management/authorization-records', // 授权记录
|
||||||
AuthorizationRecordDetail = '/asset-management/authorization-records/detail', // 授权记录详情
|
AuthorizationRecordDetail = '/asset-management/authorization-records/detail', // 授权记录详情
|
||||||
EnterpriseDevices = '/asset-management/enterprise-devices', // 企业设备列表
|
EnterpriseDevices = '/asset-management/enterprise-devices', // 企业设备列表
|
||||||
|
ExchangeManagement = '/asset-management/exchange-management', // 换货管理
|
||||||
|
ExchangeDetail = '/asset-management/exchange-management/detail', // 换货单详情
|
||||||
|
|
||||||
// 账户管理
|
// 账户管理
|
||||||
CustomerAccountList = '/finance/customer-account', // 客户账号
|
CustomerAccountList = '/finance/customer-account', // 客户账号
|
||||||
|
|||||||
3
src/types/components.d.ts
vendored
3
src/types/components.d.ts
vendored
@@ -157,7 +157,10 @@ declare module 'vue' {
|
|||||||
SettingDrawer: typeof import('./../components/core/layouts/art-settings-panel/widget/SettingDrawer.vue')['default']
|
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']
|
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']
|
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']
|
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']
|
TableContextMenuHint: typeof import('./../components/core/others/TableContextMenuHint.vue')['default']
|
||||||
ThemeSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/ThemeSettings.vue')['default']
|
ThemeSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/ThemeSettings.vue')['default']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -452,7 +452,7 @@
|
|||||||
label-width="120px"
|
label-width="120px"
|
||||||
>
|
>
|
||||||
<ElFormItem label="设备号">
|
<ElFormItem label="设备号">
|
||||||
<span style="font-weight: bold; color: #409eff">{{ currentOperatingDevice }}</span>
|
<span style="font-weight: bold; color: #409eff">{{ currentOperatingImei }}</span>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="下行速率" prop="download_speed">
|
<ElFormItem label="下行速率" prop="download_speed">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
@@ -484,27 +484,70 @@
|
|||||||
</ElDialog>
|
</ElDialog>
|
||||||
|
|
||||||
<!-- 切换SIM卡对话框 -->
|
<!-- 切换SIM卡对话框 -->
|
||||||
<ElDialog v-model="switchCardDialogVisible" title="切换SIM卡" width="500px">
|
<ElDialog v-model="switchCardDialogVisible" title="切换SIM卡" width="600px">
|
||||||
|
<div v-if="loadingDeviceCards" style="text-align: center; padding: 40px">
|
||||||
|
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||||
|
<div style="margin-top: 16px">加载设备绑定的卡列表中...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<ElAlert
|
||||||
|
v-if="deviceBindingCards.length === 0"
|
||||||
|
title="该设备暂无绑定的SIM卡"
|
||||||
|
type="warning"
|
||||||
|
:closable="false"
|
||||||
|
style="margin-bottom: 20px"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<div>当前设备没有绑定任何SIM卡,无法进行切换操作。</div>
|
||||||
|
<div>请先为设备绑定SIM卡后再进行切换。</div>
|
||||||
|
</template>
|
||||||
|
</ElAlert>
|
||||||
|
|
||||||
<ElForm
|
<ElForm
|
||||||
|
v-if="deviceBindingCards.length > 0"
|
||||||
ref="switchCardFormRef"
|
ref="switchCardFormRef"
|
||||||
:model="switchCardForm"
|
:model="switchCardForm"
|
||||||
:rules="switchCardRules"
|
:rules="switchCardRules"
|
||||||
label-width="120px"
|
label-width="120px"
|
||||||
>
|
>
|
||||||
<ElFormItem label="设备号">
|
<ElFormItem label="设备号">
|
||||||
<span style="font-weight: bold; color: #409eff">{{ currentOperatingDevice }}</span>
|
<span style="font-weight: bold; color: #409eff">{{ currentOperatingImei }}</span>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="目标ICCID" prop="target_iccid">
|
<ElFormItem label="目标ICCID" prop="target_iccid">
|
||||||
<ElInput
|
<ElSelect
|
||||||
v-model="switchCardForm.target_iccid"
|
v-model="switchCardForm.target_iccid"
|
||||||
placeholder="请输入要切换到的目标ICCID"
|
placeholder="请选择要切换到的目标ICCID"
|
||||||
|
style="width: 100%"
|
||||||
clearable
|
clearable
|
||||||
/>
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="card in deviceBindingCards"
|
||||||
|
:key="card.iccid"
|
||||||
|
:label="`${card.iccid} - 插槽${card.slot_position} - ${card.carrier_name}`"
|
||||||
|
:value="card.iccid"
|
||||||
|
>
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||||
|
<span>{{ card.iccid }}</span>
|
||||||
|
<ElTag size="small" style="margin-left: 10px">插槽{{ card.slot_position }}</ElTag>
|
||||||
|
</div>
|
||||||
|
</ElOption>
|
||||||
|
</ElSelect>
|
||||||
|
<div style="margin-top: 8px; font-size: 12px; color: #909399">
|
||||||
|
当前设备共绑定 {{ deviceBindingCards.length }} 张SIM卡
|
||||||
|
</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<ElButton @click="switchCardDialogVisible = false">取消</ElButton>
|
<ElButton @click="switchCardDialogVisible = false">取消</ElButton>
|
||||||
<ElButton type="primary" @click="handleConfirmSwitchCard" :loading="switchCardLoading">
|
<ElButton
|
||||||
|
v-if="deviceBindingCards.length > 0"
|
||||||
|
type="primary"
|
||||||
|
@click="handleConfirmSwitchCard"
|
||||||
|
:loading="switchCardLoading"
|
||||||
|
>
|
||||||
确认切换
|
确认切换
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</template>
|
</template>
|
||||||
@@ -519,7 +562,7 @@
|
|||||||
label-width="120px"
|
label-width="120px"
|
||||||
>
|
>
|
||||||
<ElFormItem label="设备号">
|
<ElFormItem label="设备号">
|
||||||
<span style="font-weight: bold; color: #409eff">{{ currentOperatingDevice }}</span>
|
<span style="font-weight: bold; color: #409eff">{{ currentOperatingImei }}</span>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="WiFi状态" prop="enabled">
|
<ElFormItem label="WiFi状态" prop="enabled">
|
||||||
<ElRadioGroup v-model="setWiFiForm.enabled">
|
<ElRadioGroup v-model="setWiFiForm.enabled">
|
||||||
@@ -683,11 +726,12 @@
|
|||||||
{ type: 'number', min: 1, message: '速率不能小于1KB/s', trigger: 'blur' }
|
{ type: 'number', min: 1, message: '速率不能小于1KB/s', trigger: 'blur' }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
const currentOperatingDevice = ref<string>('')
|
|
||||||
|
|
||||||
// 设备操作右键菜单
|
// 设备操作右键菜单
|
||||||
const deviceOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
const deviceOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
const currentOperatingDeviceNo = ref<string>('')
|
const currentOperatingDeviceNo = ref<string>('')
|
||||||
|
const currentOperatingDevice = ref<Device | null>(null)
|
||||||
|
const currentOperatingImei = ref<string>('') // 用于存储当前操作设备的IMEI
|
||||||
|
|
||||||
const switchCardDialogVisible = ref(false)
|
const switchCardDialogVisible = ref(false)
|
||||||
const switchCardLoading = ref(false)
|
const switchCardLoading = ref(false)
|
||||||
@@ -696,8 +740,10 @@
|
|||||||
target_iccid: ''
|
target_iccid: ''
|
||||||
})
|
})
|
||||||
const switchCardRules = reactive<FormRules>({
|
const switchCardRules = reactive<FormRules>({
|
||||||
target_iccid: [{ required: true, message: '请输入目标ICCID', trigger: 'blur' }]
|
target_iccid: [{ required: true, message: '请选择目标ICCID', trigger: 'change' }]
|
||||||
})
|
})
|
||||||
|
const deviceBindingCards = ref<any[]>([]) // 设备绑定的卡列表
|
||||||
|
const loadingDeviceCards = ref(false) // 加载设备绑定卡列表的状态
|
||||||
|
|
||||||
const setWiFiDialogVisible = ref(false)
|
const setWiFiDialogVisible = ref(false)
|
||||||
const setWiFiLoading = ref(false)
|
const setWiFiLoading = ref(false)
|
||||||
@@ -1454,6 +1500,9 @@
|
|||||||
case 'set-wifi':
|
case 'set-wifi':
|
||||||
showSetWiFiDialog(deviceNo)
|
showSetWiFiDialog(deviceNo)
|
||||||
break
|
break
|
||||||
|
case 'manual-deactivate':
|
||||||
|
handleManualDeactivateDevice()
|
||||||
|
break
|
||||||
case 'delete':
|
case 'delete':
|
||||||
handleDeleteDeviceByNo(deviceNo)
|
handleDeleteDeviceByNo(deviceNo)
|
||||||
break
|
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) => {
|
const handleDeleteDeviceByNo = async (deviceNo: string) => {
|
||||||
// 先根据设备号找到设备对象
|
// 先根据设备号找到设备对象
|
||||||
@@ -1537,7 +1619,7 @@
|
|||||||
|
|
||||||
// 显示设置限速对话框
|
// 显示设置限速对话框
|
||||||
const showSpeedLimitDialog = (imei: string) => {
|
const showSpeedLimitDialog = (imei: string) => {
|
||||||
currentOperatingDevice.value = imei
|
currentOperatingImei.value = imei
|
||||||
speedLimitForm.download_speed = 1024
|
speedLimitForm.download_speed = 1024
|
||||||
speedLimitForm.upload_speed = 512
|
speedLimitForm.upload_speed = 512
|
||||||
speedLimitDialogVisible.value = true
|
speedLimitDialogVisible.value = true
|
||||||
@@ -1551,7 +1633,7 @@
|
|||||||
if (valid) {
|
if (valid) {
|
||||||
speedLimitLoading.value = true
|
speedLimitLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await DeviceService.setSpeedLimit(currentOperatingDevice.value, {
|
const res = await DeviceService.setSpeedLimit(currentOperatingImei.value, {
|
||||||
download_speed: speedLimitForm.download_speed,
|
download_speed: speedLimitForm.download_speed,
|
||||||
upload_speed: speedLimitForm.upload_speed
|
upload_speed: speedLimitForm.upload_speed
|
||||||
})
|
})
|
||||||
@@ -1572,10 +1654,35 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 显示切换SIM卡对话框
|
// 显示切换SIM卡对话框
|
||||||
const showSwitchCardDialog = (imei: string) => {
|
const showSwitchCardDialog = async (deviceNo: string) => {
|
||||||
currentOperatingDevice.value = imei
|
currentOperatingImei.value = deviceNo
|
||||||
switchCardForm.target_iccid = ''
|
switchCardForm.target_iccid = ''
|
||||||
|
deviceBindingCards.value = []
|
||||||
switchCardDialogVisible.value = true
|
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卡
|
// 确认切换SIM卡
|
||||||
@@ -1586,7 +1693,7 @@
|
|||||||
if (valid) {
|
if (valid) {
|
||||||
switchCardLoading.value = true
|
switchCardLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await DeviceService.switchCard(currentOperatingDevice.value, {
|
const res = await DeviceService.switchCard(currentOperatingImei.value, {
|
||||||
target_iccid: switchCardForm.target_iccid
|
target_iccid: switchCardForm.target_iccid
|
||||||
})
|
})
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -1607,7 +1714,7 @@
|
|||||||
|
|
||||||
// 显示设置WiFi对话框
|
// 显示设置WiFi对话框
|
||||||
const showSetWiFiDialog = (imei: string) => {
|
const showSetWiFiDialog = (imei: string) => {
|
||||||
currentOperatingDevice.value = imei
|
currentOperatingImei.value = imei
|
||||||
setWiFiForm.enabled = 1
|
setWiFiForm.enabled = 1
|
||||||
setWiFiForm.ssid = ''
|
setWiFiForm.ssid = ''
|
||||||
setWiFiForm.password = ''
|
setWiFiForm.password = ''
|
||||||
@@ -1622,7 +1729,7 @@
|
|||||||
if (valid) {
|
if (valid) {
|
||||||
setWiFiLoading.value = true
|
setWiFiLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await DeviceService.setWiFi(currentOperatingDevice.value, {
|
const res = await DeviceService.setWiFi(currentOperatingImei.value, {
|
||||||
enabled: setWiFiForm.enabled,
|
enabled: setWiFiForm.enabled,
|
||||||
ssid: setWiFiForm.ssid,
|
ssid: setWiFiForm.ssid,
|
||||||
password: setWiFiForm.password
|
password: setWiFiForm.password
|
||||||
@@ -1690,6 +1797,13 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasAuth('device:manual_deactivate')) {
|
||||||
|
items.push({
|
||||||
|
key: 'manual-deactivate',
|
||||||
|
label: '手动停用'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (hasAuth('device:delete')) {
|
if (hasAuth('device:delete')) {
|
||||||
items.push({
|
items.push({
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
@@ -1701,10 +1815,11 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 显示设备操作菜单
|
// 显示设备操作菜单
|
||||||
const showDeviceOperationMenu = (e: MouseEvent, deviceNo: string) => {
|
const showDeviceOperationMenu = (e: MouseEvent, deviceNo: string, device?: Device) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
currentOperatingDeviceNo.value = deviceNo
|
currentOperatingDeviceNo.value = deviceNo
|
||||||
|
currentOperatingDevice.value = device || null
|
||||||
deviceOperationMenuRef.value?.show(e)
|
deviceOperationMenuRef.value?.show(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1718,7 +1833,7 @@
|
|||||||
|
|
||||||
// 处理表格行右键菜单
|
// 处理表格行右键菜单
|
||||||
const handleRowContextMenu = (row: Device, column: any, event: MouseEvent) => {
|
const handleRowContextMenu = (row: Device, column: any, event: MouseEvent) => {
|
||||||
showDeviceOperationMenu(event, row.virtual_no)
|
showDeviceOperationMenu(event, row.virtual_no, row)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
330
src/views/asset-management/exchange-management/detail.vue
Normal file
330
src/views/asset-management/exchange-management/detail.vue
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
<template>
|
||||||
|
<div class="exchange-detail-page">
|
||||||
|
<ElPageHeader @back="handleBack">
|
||||||
|
<template #content>
|
||||||
|
<span class="page-header-title">换货单详情</span>
|
||||||
|
</template>
|
||||||
|
</ElPageHeader>
|
||||||
|
|
||||||
|
<ElCard v-loading="loading" style="margin-top: 20px">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>换货单信息</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<ElDescriptions v-if="exchangeDetail" :column="2" border>
|
||||||
|
<ElDescriptionsItem label="换货单号">
|
||||||
|
{{ exchangeDetail.exchange_no }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="状态">
|
||||||
|
<ElTag :type="getStatusType(exchangeDetail.status)">
|
||||||
|
{{ exchangeDetail.status_text }}
|
||||||
|
</ElTag>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="换货原因" :span="2">
|
||||||
|
{{ exchangeDetail.exchange_reason }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="旧资产类型">
|
||||||
|
{{ exchangeDetail.old_asset_type === 'iot_card' ? 'IoT卡' : '设备' }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="旧资产标识符">
|
||||||
|
{{ exchangeDetail.old_asset_identifier }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="新资产类型">
|
||||||
|
{{
|
||||||
|
exchangeDetail.new_asset_type
|
||||||
|
? exchangeDetail.new_asset_type === 'iot_card'
|
||||||
|
? 'IoT卡'
|
||||||
|
: '设备'
|
||||||
|
: '--'
|
||||||
|
}}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="新资产标识符">
|
||||||
|
{{ exchangeDetail.new_asset_identifier || '--' }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="收货人姓名">
|
||||||
|
{{ exchangeDetail.recipient_name || '--' }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="收货人电话">
|
||||||
|
{{ exchangeDetail.recipient_phone || '--' }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="收货地址" :span="2">
|
||||||
|
{{ exchangeDetail.recipient_address || '--' }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="快递公司">
|
||||||
|
{{ exchangeDetail.express_company || '--' }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="快递单号">
|
||||||
|
{{ exchangeDetail.express_no || '--' }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="备注" :span="2">
|
||||||
|
{{ exchangeDetail.remark || '--' }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
|
<ElDescriptionsItem label="创建时间">
|
||||||
|
{{ formatDateTime(exchangeDetail.created_at) }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="更新时间">
|
||||||
|
{{ formatDateTime(exchangeDetail.updated_at) }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
|
||||||
|
<ElEmpty v-else description="未找到换货单信息" />
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div v-if="exchangeDetail" style="margin-top: 20px; text-align: center">
|
||||||
|
<ElButton
|
||||||
|
v-if="exchangeDetail.status === 1"
|
||||||
|
type="warning"
|
||||||
|
@click="handleCancel"
|
||||||
|
v-permission="'exchange:cancel'"
|
||||||
|
>
|
||||||
|
取消换货
|
||||||
|
</ElButton>
|
||||||
|
|
||||||
|
<ElButton
|
||||||
|
v-if="exchangeDetail.status === 2"
|
||||||
|
@click="handleRenew"
|
||||||
|
v-permission="'exchange:renew'"
|
||||||
|
>
|
||||||
|
旧资产转新
|
||||||
|
</ElButton>
|
||||||
|
|
||||||
|
<ElButton
|
||||||
|
v-if="exchangeDetail.status === 2"
|
||||||
|
type="primary"
|
||||||
|
@click="showShipDialog"
|
||||||
|
v-permission="'exchange:ship'"
|
||||||
|
>
|
||||||
|
发货
|
||||||
|
</ElButton>
|
||||||
|
|
||||||
|
<ElButton
|
||||||
|
v-if="exchangeDetail.status === 3"
|
||||||
|
type="success"
|
||||||
|
@click="handleComplete"
|
||||||
|
v-permission="'exchange:complete'"
|
||||||
|
>
|
||||||
|
确认完成
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
|
||||||
|
<!-- 发货对话框 -->
|
||||||
|
<ElDialog v-model="shipDialogVisible" title="换货发货" width="600px">
|
||||||
|
<ElForm ref="shipFormRef" :model="shipForm" :rules="shipRules" label-width="120px">
|
||||||
|
<ElFormItem label="新资产标识符" prop="new_identifier">
|
||||||
|
<ElInput v-model="shipForm.new_identifier" placeholder="请输入新资产标识符(ICCID/虚拟号/IMEI/SN)" />
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="快递公司" prop="express_company">
|
||||||
|
<ElInput v-model="shipForm.express_company" placeholder="请输入快递公司" />
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="快递单号" prop="express_no">
|
||||||
|
<ElInput v-model="shipForm.express_no" placeholder="请输入快递单号" />
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="是否迁移数据">
|
||||||
|
<ElSwitch v-model="shipForm.migrate_data" />
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<ElButton @click="shipDialogVisible = false">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="handleConfirmShip" :loading="shipLoading">
|
||||||
|
确认发货
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { ExchangeService } from '@/api/modules'
|
||||||
|
import type { ExchangeResponse } from '@/api/modules/exchange'
|
||||||
|
import { ElMessage, ElMessageBox, ElTag, ElSwitch } from 'element-plus'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
|
||||||
|
defineOptions({ name: 'ExchangeDetail' })
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const { hasAuth } = useAuth()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const shipDialogVisible = ref(false)
|
||||||
|
const shipLoading = ref(false)
|
||||||
|
const shipFormRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
const exchangeDetail = ref<ExchangeResponse | null>(null)
|
||||||
|
const exchangeId = ref<number>(0)
|
||||||
|
|
||||||
|
const shipForm = reactive({
|
||||||
|
new_identifier: '',
|
||||||
|
express_company: '',
|
||||||
|
express_no: '',
|
||||||
|
migrate_data: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const shipRules = reactive<FormRules>({
|
||||||
|
new_identifier: [{ required: true, message: '请输入新资产标识符', trigger: 'blur' }],
|
||||||
|
express_company: [{ required: true, message: '请输入快递公司', trigger: 'blur' }],
|
||||||
|
express_no: [{ required: true, message: '请输入快递单号', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
const getStatusType = (status: number) => {
|
||||||
|
const types: Record<number, string> = {
|
||||||
|
1: 'warning',
|
||||||
|
2: 'info',
|
||||||
|
3: 'primary',
|
||||||
|
4: 'success',
|
||||||
|
5: 'danger'
|
||||||
|
}
|
||||||
|
return types[status] || 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载换货单详情
|
||||||
|
const loadExchangeDetail = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await ExchangeService.getExchangeDetail(exchangeId.value)
|
||||||
|
if (res.code === 0 && res.data) {
|
||||||
|
exchangeDetail.value = res.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载换货单详情失败:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消换货
|
||||||
|
const handleCancel = () => {
|
||||||
|
ElMessageBox.prompt('请输入取消备注', '取消换货', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
inputType: 'textarea'
|
||||||
|
})
|
||||||
|
.then(async ({ value }) => {
|
||||||
|
try {
|
||||||
|
const res = await ExchangeService.cancelExchange(exchangeId.value, { remark: value })
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('取消成功')
|
||||||
|
loadExchangeDetail()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('取消换货失败:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 旧资产转新
|
||||||
|
const handleRenew = () => {
|
||||||
|
ElMessageBox.confirm('确定要将旧资产转为新资产吗?', '确认操作', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
try {
|
||||||
|
const res = await ExchangeService.renewExchange(exchangeId.value)
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('操作成功')
|
||||||
|
loadExchangeDetail()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('旧资产转新失败:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示发货对话框
|
||||||
|
const showShipDialog = () => {
|
||||||
|
shipDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认发货
|
||||||
|
const handleConfirmShip = () => {
|
||||||
|
shipFormRef.value?.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
shipLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await ExchangeService.shipExchange(exchangeId.value, shipForm)
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('发货成功')
|
||||||
|
shipDialogVisible.value = false
|
||||||
|
loadExchangeDetail()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('发货失败:', error)
|
||||||
|
} finally {
|
||||||
|
shipLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认完成
|
||||||
|
const handleComplete = () => {
|
||||||
|
ElMessageBox.confirm('确定要确认换货完成吗?', '确认操作', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
try {
|
||||||
|
const res = await ExchangeService.completeExchange(exchangeId.value)
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('操作成功')
|
||||||
|
loadExchangeDetail()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('确认完成失败:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回
|
||||||
|
const handleBack = () => {
|
||||||
|
router.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
exchangeId.value = Number(route.params.id)
|
||||||
|
if (exchangeId.value) {
|
||||||
|
loadExchangeDetail()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.exchange-detail-page {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.page-header-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
385
src/views/asset-management/exchange-management/index.vue
Normal file
385
src/views/asset-management/exchange-management/index.vue
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
<template>
|
||||||
|
<ArtTableFullScreen>
|
||||||
|
<div class="exchange-management-page" id="table-full-screen">
|
||||||
|
<!-- 搜索栏 -->
|
||||||
|
<ArtSearchBar
|
||||||
|
v-model:filter="searchForm"
|
||||||
|
:items="searchFormItems"
|
||||||
|
:show-expand="false"
|
||||||
|
label-width="90"
|
||||||
|
@reset="handleReset"
|
||||||
|
@search="handleSearch"
|
||||||
|
></ArtSearchBar>
|
||||||
|
|
||||||
|
<ElCard shadow="never" class="art-table-card">
|
||||||
|
<!-- 表格头部 -->
|
||||||
|
<ArtTableHeader
|
||||||
|
v-model:columns="columnChecks"
|
||||||
|
@refresh="handleRefresh"
|
||||||
|
>
|
||||||
|
<template #left>
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
@click="showCreateDialog"
|
||||||
|
v-permission="'exchange:create'"
|
||||||
|
>
|
||||||
|
创建换货单
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ArtTableHeader>
|
||||||
|
|
||||||
|
<!-- 表格 -->
|
||||||
|
<ArtTable
|
||||||
|
ref="tableRef"
|
||||||
|
row-key="id"
|
||||||
|
:loading="loading"
|
||||||
|
:data="exchangeList"
|
||||||
|
:currentPage="pagination.page"
|
||||||
|
:pageSize="pagination.page_size"
|
||||||
|
:total="pagination.total"
|
||||||
|
:marginTop="10"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
|
</template>
|
||||||
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 创建换货单对话框 -->
|
||||||
|
<ElDialog
|
||||||
|
v-model="createDialogVisible"
|
||||||
|
title="创建换货单"
|
||||||
|
width="600px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@closed="handleCloseCreateDialog"
|
||||||
|
>
|
||||||
|
<ElForm
|
||||||
|
ref="createFormRef"
|
||||||
|
:model="createForm"
|
||||||
|
:rules="createRules"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<ElFormItem label="换货原因" prop="exchange_reason">
|
||||||
|
<ElInput
|
||||||
|
v-model="createForm.exchange_reason"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="请输入换货原因"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="旧资产类型" prop="old_asset_type">
|
||||||
|
<ElSelect v-model="createForm.old_asset_type" placeholder="请选择旧资产类型" style="width: 100%">
|
||||||
|
<ElOption label="IoT卡" value="iot_card" />
|
||||||
|
<ElOption label="设备" value="device" />
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="旧资产标识符" prop="old_identifier">
|
||||||
|
<ElInput
|
||||||
|
v-model="createForm.old_identifier"
|
||||||
|
placeholder="请输入ICCID/虚拟号/IMEI/SN"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="备注">
|
||||||
|
<ElInput
|
||||||
|
v-model="createForm.remark"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
placeholder="请输入备注(可选)"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<ElButton @click="createDialogVisible = false">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="handleConfirmCreate" :loading="createLoading">
|
||||||
|
确认创建
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
</ElCard>
|
||||||
|
</div>
|
||||||
|
</ArtTableFullScreen>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ExchangeService } from '@/api/modules'
|
||||||
|
import type { ExchangeResponse } from '@/api/modules/exchange'
|
||||||
|
import { ElMessage, ElTag, ElButton } from 'element-plus'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import type { SearchFormItem } from '@/types'
|
||||||
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
import { RoutesAlias } from '@/router/routesAlias'
|
||||||
|
|
||||||
|
defineOptions({ name: 'ExchangeManagement' })
|
||||||
|
|
||||||
|
const { hasAuth } = useAuth()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const createDialogVisible = ref(false)
|
||||||
|
const createLoading = ref(false)
|
||||||
|
const tableRef = ref()
|
||||||
|
const createFormRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
const exchangeList = ref<ExchangeResponse[]>([])
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = reactive({
|
||||||
|
status: undefined,
|
||||||
|
identifier: '',
|
||||||
|
created_at_start: '',
|
||||||
|
created_at_end: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 创建换货单表单
|
||||||
|
const createForm = reactive({
|
||||||
|
exchange_reason: '',
|
||||||
|
old_asset_type: '',
|
||||||
|
old_identifier: '',
|
||||||
|
remark: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const createRules = reactive<FormRules>({
|
||||||
|
exchange_reason: [{ required: true, message: '请输入换货原因', trigger: 'blur' }],
|
||||||
|
old_asset_type: [{ required: true, message: '请选择旧资产类型', trigger: 'change' }],
|
||||||
|
old_identifier: [{ required: true, message: '请输入旧资产标识符', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
const pagination = reactive({
|
||||||
|
page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 搜索表单配置
|
||||||
|
const searchFormItems: SearchFormItem[] = [
|
||||||
|
{
|
||||||
|
label: '换货状态',
|
||||||
|
prop: 'status',
|
||||||
|
type: 'select',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '全部'
|
||||||
|
},
|
||||||
|
options: () => [
|
||||||
|
{ label: '待填写信息', value: 1 },
|
||||||
|
{ label: '待发货', value: 2 },
|
||||||
|
{ label: '已发货待确认', value: 3 },
|
||||||
|
{ label: '已完成', value: 4 },
|
||||||
|
{ label: '已取消', value: 5 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '资产标识符',
|
||||||
|
prop: 'identifier',
|
||||||
|
type: 'input',
|
||||||
|
config: {
|
||||||
|
placeholder: '请输入资产标识符',
|
||||||
|
clearable: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '创建时间',
|
||||||
|
prop: 'created_at_range',
|
||||||
|
type: 'date',
|
||||||
|
config: {
|
||||||
|
type: 'datetimerange',
|
||||||
|
rangeSeparator: '至',
|
||||||
|
startPlaceholder: '开始时间',
|
||||||
|
endPlaceholder: '结束时间',
|
||||||
|
valueFormat: 'YYYY-MM-DD HH:mm:ss'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 动态列配置
|
||||||
|
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||||
|
{
|
||||||
|
prop: 'exchange_no',
|
||||||
|
label: '换货单号',
|
||||||
|
width: 220,
|
||||||
|
formatter: (row: any) => row.exchange_no || '--'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'exchange_reason',
|
||||||
|
label: '换货原因',
|
||||||
|
minWidth: 150,
|
||||||
|
showOverflowTooltip: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'old_asset_type',
|
||||||
|
label: '旧资产类型',
|
||||||
|
width: 120,
|
||||||
|
formatter: (row: any) => (row.old_asset_type === 'iot_card' ? 'IoT卡' : '设备')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'old_asset_identifier',
|
||||||
|
label: '旧资产标识符',
|
||||||
|
width: 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'new_asset_identifier',
|
||||||
|
label: '新资产标识符',
|
||||||
|
width: 180,
|
||||||
|
formatter: (row: any) => row.new_asset_identifier || '--'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'status',
|
||||||
|
label: '状态',
|
||||||
|
width: 130,
|
||||||
|
formatter: (row: any) =>
|
||||||
|
h(ElTag, { type: getStatusType(row.status) }, () => row.status_text)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'created_at',
|
||||||
|
label: '创建时间',
|
||||||
|
width: 180,
|
||||||
|
formatter: (row: any) => formatDateTime(row.created_at)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'action',
|
||||||
|
label: '操作',
|
||||||
|
width: 120,
|
||||||
|
fixed: 'right',
|
||||||
|
formatter: (row: any) =>
|
||||||
|
h(
|
||||||
|
ElButton,
|
||||||
|
{
|
||||||
|
type: 'primary',
|
||||||
|
link: true,
|
||||||
|
onClick: () => handleViewDetail(row)
|
||||||
|
},
|
||||||
|
() => '查看详情'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const getStatusType = (status: number) => {
|
||||||
|
const types: Record<number, string> = {
|
||||||
|
1: 'warning',
|
||||||
|
2: 'info',
|
||||||
|
3: 'primary',
|
||||||
|
4: 'success',
|
||||||
|
5: 'danger'
|
||||||
|
}
|
||||||
|
return types[status] || 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载换货单列表
|
||||||
|
const loadExchangeList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
// 过滤掉空值参数
|
||||||
|
const params: any = {
|
||||||
|
page: pagination.page,
|
||||||
|
page_size: pagination.page_size
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchForm.status !== undefined && searchForm.status !== null) {
|
||||||
|
params.status = searchForm.status
|
||||||
|
}
|
||||||
|
if (searchForm.identifier) {
|
||||||
|
params.identifier = searchForm.identifier
|
||||||
|
}
|
||||||
|
if (searchForm.created_at_start) {
|
||||||
|
params.created_at_start = searchForm.created_at_start
|
||||||
|
}
|
||||||
|
if (searchForm.created_at_end) {
|
||||||
|
params.created_at_end = searchForm.created_at_end
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await ExchangeService.getExchanges(params)
|
||||||
|
if (res.code === 0 && res.data) {
|
||||||
|
exchangeList.value = res.data.list || []
|
||||||
|
pagination.total = res.data.total || 0
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载换货单列表失败:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleSearch = () => {
|
||||||
|
pagination.page = 1
|
||||||
|
loadExchangeList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const handleReset = () => {
|
||||||
|
pagination.page = 1
|
||||||
|
loadExchangeList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新
|
||||||
|
const handleRefresh = () => {
|
||||||
|
loadExchangeList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
const handleSizeChange = (size: number) => {
|
||||||
|
pagination.page_size = size
|
||||||
|
loadExchangeList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCurrentChange = (page: number) => {
|
||||||
|
pagination.page = page
|
||||||
|
loadExchangeList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示创建对话框
|
||||||
|
const showCreateDialog = () => {
|
||||||
|
createDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认创建
|
||||||
|
const handleConfirmCreate = () => {
|
||||||
|
createFormRef.value?.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
createLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await ExchangeService.createExchange(createForm)
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('换货单创建成功')
|
||||||
|
createDialogVisible.value = false
|
||||||
|
loadExchangeList()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('创建换货单失败:', error)
|
||||||
|
} finally {
|
||||||
|
createLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭创建对话框
|
||||||
|
const handleCloseCreateDialog = () => {
|
||||||
|
createFormRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
const handleViewDetail = (row: any) => {
|
||||||
|
router.push(`${RoutesAlias.ExchangeDetail}/${row.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
loadExchangeList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.exchange-management-page {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -662,6 +662,7 @@
|
|||||||
const moreMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
const moreMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
const cardOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
const cardOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
const currentOperatingIccid = ref<string>('')
|
const currentOperatingIccid = ref<string>('')
|
||||||
|
const currentOperatingCard = ref<StandaloneIotCard | null>(null)
|
||||||
|
|
||||||
// 店铺相关
|
// 店铺相关
|
||||||
const targetShopLoading = ref(false)
|
const targetShopLoading = ref(false)
|
||||||
@@ -1563,6 +1564,13 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasAuth('iot_card:manual_deactivate')) {
|
||||||
|
items.push({
|
||||||
|
key: 'manual-deactivate',
|
||||||
|
label: '手动停用'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return items
|
return items
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1601,10 +1609,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 显示卡操作菜单
|
// 显示卡操作菜单
|
||||||
const showCardOperationMenu = (e: MouseEvent, iccid: string) => {
|
const showCardOperationMenu = (e: MouseEvent, iccid: string, card?: StandaloneIotCard) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
currentOperatingIccid.value = iccid
|
currentOperatingIccid.value = iccid
|
||||||
|
currentOperatingCard.value = card || null
|
||||||
cardOperationMenuRef.value?.show(e)
|
cardOperationMenuRef.value?.show(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1660,6 +1669,9 @@
|
|||||||
case 'stop-card':
|
case 'stop-card':
|
||||||
handleStopCard(iccid)
|
handleStopCard(iccid)
|
||||||
break
|
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) => {
|
const handleRowContextMenu = (row: StandaloneIotCard, column: any, event: MouseEvent) => {
|
||||||
showCardOperationMenu(event, row.iccid)
|
showCardOperationMenu(event, row.iccid, row)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,32 @@
|
|||||||
placeholder="请输入运营商描述"
|
placeholder="请输入运营商描述"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
<ElFormItem label="实名链接类型" prop="realname_link_type">
|
||||||
|
<ElSelect
|
||||||
|
v-model="form.realname_link_type"
|
||||||
|
placeholder="请选择实名链接类型"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<ElOption label="无需实名" value="none" />
|
||||||
|
<ElOption label="模板实名" value="template" />
|
||||||
|
<ElOption label="网关实名" value="gateway" />
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem
|
||||||
|
v-if="form.realname_link_type === 'template'"
|
||||||
|
label="实名链接模板"
|
||||||
|
prop="realname_link_template"
|
||||||
|
>
|
||||||
|
<ElInput
|
||||||
|
v-model="form.realname_link_template"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
placeholder="请输入实名链接模板"
|
||||||
|
/>
|
||||||
|
<div style="margin-top: 5px; font-size: 12px; color: #909399">
|
||||||
|
支持变量:{iccid}、{msisdn}等
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
@@ -233,7 +259,21 @@
|
|||||||
{ min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' }
|
{ min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
carrier_type: [{ required: true, message: '请选择运营商类型', trigger: 'change' }],
|
carrier_type: [{ required: true, message: '请选择运营商类型', trigger: 'change' }],
|
||||||
description: [{ max: 500, message: '描述不能超过500个字符', trigger: 'blur' }]
|
description: [{ max: 500, message: '描述不能超过500个字符', trigger: 'blur' }],
|
||||||
|
realname_link_type: [{ required: true, message: '请选择实名链接类型', trigger: 'change' }],
|
||||||
|
realname_link_template: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
validator: (rule: any, value: any, callback: any) => {
|
||||||
|
if (form.realname_link_type === 'template' && !value) {
|
||||||
|
callback(new Error('请输入实名链接模板'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const form = reactive<any>({
|
const form = reactive<any>({
|
||||||
@@ -241,7 +281,9 @@
|
|||||||
carrier_code: '',
|
carrier_code: '',
|
||||||
carrier_name: '',
|
carrier_name: '',
|
||||||
carrier_type: null,
|
carrier_type: null,
|
||||||
description: ''
|
description: '',
|
||||||
|
realname_link_type: 'none',
|
||||||
|
realname_link_template: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const carrierList = ref<Carrier[]>([])
|
const carrierList = ref<Carrier[]>([])
|
||||||
@@ -381,12 +423,16 @@
|
|||||||
form.carrier_name = row.carrier_name
|
form.carrier_name = row.carrier_name
|
||||||
form.carrier_type = row.carrier_type
|
form.carrier_type = row.carrier_type
|
||||||
form.description = row.description
|
form.description = row.description
|
||||||
|
form.realname_link_type = row.realname_link_type || 'none'
|
||||||
|
form.realname_link_template = row.realname_link_template || ''
|
||||||
} else {
|
} else {
|
||||||
form.id = 0
|
form.id = 0
|
||||||
form.carrier_code = ''
|
form.carrier_code = ''
|
||||||
form.carrier_name = ''
|
form.carrier_name = ''
|
||||||
form.carrier_type = null
|
form.carrier_type = null
|
||||||
form.description = ''
|
form.description = ''
|
||||||
|
form.realname_link_type = 'none'
|
||||||
|
form.realname_link_template = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除表单验证状态
|
// 清除表单验证状态
|
||||||
|
|||||||
@@ -47,75 +47,9 @@
|
|||||||
|
|
||||||
<!-- 操作按钮组 -->
|
<!-- 操作按钮组 -->
|
||||||
<div v-if="cardInfo" class="operation-button-group">
|
<div v-if="cardInfo" class="operation-button-group">
|
||||||
<ElButton
|
<ElButton @click="handleRefreshAsset" :loading="refreshLoading" type="primary">
|
||||||
@click="handleRefreshAsset"
|
|
||||||
:loading="refreshLoading"
|
|
||||||
type="primary"
|
|
||||||
style="margin-right: 10px"
|
|
||||||
>
|
|
||||||
刷新
|
刷新
|
||||||
</ElButton>
|
</ElButton>
|
||||||
|
|
||||||
<ElDropdown trigger="click" @command="handleOperation">
|
|
||||||
<ElButton>主要操作</ElButton>
|
|
||||||
<template #dropdown>
|
|
||||||
<ElDropdownMenu>
|
|
||||||
<ElDropdownItem command="recharge">套餐充值</ElDropdownItem>
|
|
||||||
<ElDropdownItem command="activate">激活</ElDropdownItem>
|
|
||||||
<!-- 单卡停复机 -->
|
|
||||||
<template v-if="cardInfo?.asset_type === 'card'">
|
|
||||||
<ElDropdownItem v-if="cardInfo?.network_status === 1" command="stopCard">
|
|
||||||
停机
|
|
||||||
</ElDropdownItem>
|
|
||||||
<ElDropdownItem v-else command="startCard">复机</ElDropdownItem>
|
|
||||||
</template>
|
|
||||||
<!-- 设备停复机 -->
|
|
||||||
<template v-if="cardInfo?.asset_type === 'device'">
|
|
||||||
<ElDropdownItem command="stopDevice">停机</ElDropdownItem>
|
|
||||||
<ElDropdownItem command="startDevice">复机</ElDropdownItem>
|
|
||||||
</template>
|
|
||||||
</ElDropdownMenu>
|
|
||||||
</template>
|
|
||||||
</ElDropdown>
|
|
||||||
|
|
||||||
<ElDropdown trigger="click" @command="handleOperation">
|
|
||||||
<ElButton>查询记录</ElButton>
|
|
||||||
<template #dropdown>
|
|
||||||
<ElDropdownMenu>
|
|
||||||
<ElDropdownItem command="trafficDetail">流量详单</ElDropdownItem>
|
|
||||||
<ElDropdownItem command="suspendRecord">停复机记录</ElDropdownItem>
|
|
||||||
<ElDropdownItem command="orderHistory">往期订单</ElDropdownItem>
|
|
||||||
</ElDropdownMenu>
|
|
||||||
</template>
|
|
||||||
</ElDropdown>
|
|
||||||
|
|
||||||
<ElDropdown trigger="click" @command="handleOperation">
|
|
||||||
<ElButton>管理操作</ElButton>
|
|
||||||
<template #dropdown>
|
|
||||||
<ElDropdownMenu>
|
|
||||||
<ElDropdownItem command="rebind">机卡重绑</ElDropdownItem>
|
|
||||||
<ElDropdownItem command="changeExpire">更改过期时间</ElDropdownItem>
|
|
||||||
<ElDropdownItem command="transferCard">转新卡</ElDropdownItem>
|
|
||||||
<ElDropdownItem command="adjustTraffic">增减流量</ElDropdownItem>
|
|
||||||
<ElDropdownItem command="speedLimit">单卡限速</ElDropdownItem>
|
|
||||||
<ElDropdownItem command="instantLimit">即时限速</ElDropdownItem>
|
|
||||||
</ElDropdownMenu>
|
|
||||||
</template>
|
|
||||||
</ElDropdown>
|
|
||||||
|
|
||||||
<ElDropdown trigger="click" @command="handleOperation">
|
|
||||||
<ElButton>其他操作</ElButton>
|
|
||||||
<template #dropdown>
|
|
||||||
<ElDropdownMenu>
|
|
||||||
<ElDropdownItem command="changeBalance">变更钱包余额</ElDropdownItem>
|
|
||||||
<ElDropdownItem command="resetPassword">重置支付密码</ElDropdownItem>
|
|
||||||
<ElDropdownItem command="renewRecharge">续充</ElDropdownItem>
|
|
||||||
<ElDropdownItem command="deviceOperation">设备操作</ElDropdownItem>
|
|
||||||
<ElDropdownItem command="recoverFromRoaming">窜卡复机</ElDropdownItem>
|
|
||||||
<ElDropdownItem command="roaming">窜卡</ElDropdownItem>
|
|
||||||
</ElDropdownMenu>
|
|
||||||
</template>
|
|
||||||
</ElDropdown>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
@@ -135,7 +69,7 @@
|
|||||||
</ElTag>
|
</ElTag>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<ElDescriptions :column="2" border>
|
<ElDescriptions :column="3" border>
|
||||||
<ElDescriptionsItem label="虚拟号">{{
|
<ElDescriptionsItem label="虚拟号">{{
|
||||||
cardInfo?.virtual_no || '--'
|
cardInfo?.virtual_no || '--'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
@@ -212,6 +146,80 @@
|
|||||||
cardInfo?.activated_at || '--'
|
cardInfo?.activated_at || '--'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
</ElDescriptions>
|
</ElDescriptions>
|
||||||
|
<!-- IoT卡操作按钮 -->
|
||||||
|
<div
|
||||||
|
v-if="cardInfo?.asset_type === 'card'"
|
||||||
|
class="card-operations"
|
||||||
|
style="margin-top: 16px; text-align: right"
|
||||||
|
>
|
||||||
|
<ElButton
|
||||||
|
type="success"
|
||||||
|
@click="handleEnableCard"
|
||||||
|
:loading="enableCardLoading"
|
||||||
|
>
|
||||||
|
启用此卡
|
||||||
|
</ElButton>
|
||||||
|
<ElButton
|
||||||
|
type="warning"
|
||||||
|
@click="handleDisableCard"
|
||||||
|
:loading="disableCardLoading"
|
||||||
|
>
|
||||||
|
停用此卡
|
||||||
|
</ElButton>
|
||||||
|
<ElButton
|
||||||
|
type="danger"
|
||||||
|
@click="handleManualDeactivateCard"
|
||||||
|
:loading="manualDeactivateCardLoading"
|
||||||
|
>
|
||||||
|
手动停用
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 设备操作按钮 -->
|
||||||
|
<div v-else class="device-operations" style="margin-top: 16px; text-align: right;">
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
@click="handleRebootDevice"
|
||||||
|
:loading="rebootDeviceLoading"
|
||||||
|
>
|
||||||
|
重启设备
|
||||||
|
</ElButton>
|
||||||
|
<ElButton
|
||||||
|
type="warning"
|
||||||
|
@click="handleResetDevice"
|
||||||
|
:loading="resetDeviceLoading"
|
||||||
|
>
|
||||||
|
恢复出厂
|
||||||
|
</ElButton>
|
||||||
|
<ElButton
|
||||||
|
type="info"
|
||||||
|
@click="handleShowSpeedLimitDialog"
|
||||||
|
:loading="speedLimitLoading"
|
||||||
|
>
|
||||||
|
设置限速
|
||||||
|
</ElButton>
|
||||||
|
<ElButton
|
||||||
|
type="success"
|
||||||
|
@click="handleShowSwitchCardDialog"
|
||||||
|
:loading="switchCardLoading"
|
||||||
|
>
|
||||||
|
切换SIM卡
|
||||||
|
</ElButton>
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
@click="handleShowSetWiFiDialog"
|
||||||
|
:loading="setWiFiLoading"
|
||||||
|
>
|
||||||
|
设置WiFi
|
||||||
|
</ElButton>
|
||||||
|
<ElButton
|
||||||
|
type="danger"
|
||||||
|
@click="handleManualDeactivateDevice"
|
||||||
|
:loading="manualDeactivateDeviceLoading"
|
||||||
|
>
|
||||||
|
手动停用
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -720,6 +728,162 @@
|
|||||||
<ElCard v-else>
|
<ElCard v-else>
|
||||||
<ElEmpty description="请在上方输入虚拟号、ICCID、IMEI、SN或MSISDN进行查询" />
|
<ElEmpty description="请在上方输入虚拟号、ICCID、IMEI、SN或MSISDN进行查询" />
|
||||||
</ElCard>
|
</ElCard>
|
||||||
|
|
||||||
|
<!-- 设置限速对话框 -->
|
||||||
|
<ElDialog v-model="speedLimitDialogVisible" title="设置限速" width="500px">
|
||||||
|
<ElForm
|
||||||
|
ref="speedLimitFormRef"
|
||||||
|
:model="speedLimitForm"
|
||||||
|
:rules="speedLimitRules"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<ElFormItem label="设备信息">
|
||||||
|
<span style="font-weight: bold; color: #409eff">{{ cardInfo?.imei || cardInfo?.virtual_no }}</span>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="下行速率" prop="download_speed">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="speedLimitForm.download_speed"
|
||||||
|
:min="1"
|
||||||
|
:step="128"
|
||||||
|
controls-position="right"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
<div style="color: #909399; font-size: 12px; margin-top: 4px">单位: KB/s</div>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="上行速率" prop="upload_speed">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="speedLimitForm.upload_speed"
|
||||||
|
:min="1"
|
||||||
|
:step="128"
|
||||||
|
controls-position="right"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
<div style="color: #909399; font-size: 12px; margin-top: 4px">单位: KB/s</div>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
<template #footer>
|
||||||
|
<ElButton @click="speedLimitDialogVisible = false">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="handleConfirmSpeedLimit" :loading="speedLimitConfirmLoading">
|
||||||
|
确认设置
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
|
||||||
|
<!-- 切换SIM卡对话框 -->
|
||||||
|
<ElDialog v-model="switchCardDialogVisible" title="切换SIM卡" width="600px">
|
||||||
|
<div v-if="loadingDeviceCards" style="text-align: center; padding: 40px">
|
||||||
|
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||||
|
<div style="margin-top: 16px">加载设备绑定的卡列表中...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<ElAlert
|
||||||
|
v-if="deviceBindingCards.length === 0"
|
||||||
|
title="该设备暂无绑定的SIM卡"
|
||||||
|
type="warning"
|
||||||
|
:closable="false"
|
||||||
|
style="margin-bottom: 20px"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<div>当前设备没有绑定任何SIM卡,无法进行切换操作。</div>
|
||||||
|
<div>请先为设备绑定SIM卡后再进行切换。</div>
|
||||||
|
</template>
|
||||||
|
</ElAlert>
|
||||||
|
|
||||||
|
<ElForm
|
||||||
|
v-if="deviceBindingCards.length > 0"
|
||||||
|
ref="switchCardFormRef"
|
||||||
|
:model="switchCardForm"
|
||||||
|
:rules="switchCardRules"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<ElFormItem label="设备信息">
|
||||||
|
<span style="font-weight: bold; color: #409eff">{{ cardInfo?.imei || cardInfo?.virtual_no }}</span>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="目标ICCID" prop="target_iccid">
|
||||||
|
<ElSelect
|
||||||
|
v-model="switchCardForm.target_iccid"
|
||||||
|
placeholder="请选择要切换到的目标ICCID"
|
||||||
|
style="width: 100%"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="card in deviceBindingCards"
|
||||||
|
:key="card.iccid"
|
||||||
|
:label="`${card.iccid} - 插槽${card.slot_position} - ${card.carrier_name}`"
|
||||||
|
:value="card.iccid"
|
||||||
|
>
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||||
|
<span>{{ card.iccid }}</span>
|
||||||
|
<ElTag size="small" style="margin-left: 10px">插槽{{ card.slot_position }}</ElTag>
|
||||||
|
</div>
|
||||||
|
</ElOption>
|
||||||
|
</ElSelect>
|
||||||
|
<div style="margin-top: 8px; font-size: 12px; color: #909399">
|
||||||
|
当前设备共绑定 {{ deviceBindingCards.length }} 张SIM卡
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<ElButton @click="switchCardDialogVisible = false">取消</ElButton>
|
||||||
|
<ElButton
|
||||||
|
v-if="deviceBindingCards.length > 0"
|
||||||
|
type="primary"
|
||||||
|
@click="handleConfirmSwitchCard"
|
||||||
|
:loading="switchCardConfirmLoading"
|
||||||
|
>
|
||||||
|
确认切换
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
|
||||||
|
<!-- 设置WiFi对话框 -->
|
||||||
|
<ElDialog v-model="setWiFiDialogVisible" title="设置WiFi" width="500px">
|
||||||
|
<ElForm
|
||||||
|
ref="setWiFiFormRef"
|
||||||
|
:model="setWiFiForm"
|
||||||
|
:rules="setWiFiRules"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<ElFormItem label="设备信息">
|
||||||
|
<span style="font-weight: bold; color: #409eff">{{ cardInfo?.imei || cardInfo?.virtual_no }}</span>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="WiFi状态" prop="enabled">
|
||||||
|
<ElRadioGroup v-model="setWiFiForm.enabled">
|
||||||
|
<ElRadio :value="1">启用</ElRadio>
|
||||||
|
<ElRadio :value="0">禁用</ElRadio>
|
||||||
|
</ElRadioGroup>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="WiFi名称" prop="ssid">
|
||||||
|
<ElInput
|
||||||
|
v-model="setWiFiForm.ssid"
|
||||||
|
placeholder="请输入WiFi名称(1-32个字符)"
|
||||||
|
maxlength="32"
|
||||||
|
show-word-limit
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="WiFi密码" prop="password">
|
||||||
|
<ElInput
|
||||||
|
v-model="setWiFiForm.password"
|
||||||
|
type="password"
|
||||||
|
placeholder="请输入WiFi密码(8-63个字符)"
|
||||||
|
maxlength="63"
|
||||||
|
show-word-limit
|
||||||
|
show-password
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
<template #footer>
|
||||||
|
<ElButton @click="setWiFiDialogVisible = false">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="handleConfirmSetWiFi" :loading="setWiFiConfirmLoading">
|
||||||
|
确认设置
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -727,6 +891,7 @@
|
|||||||
import {
|
import {
|
||||||
ElTag,
|
ElTag,
|
||||||
ElMessage,
|
ElMessage,
|
||||||
|
ElButton,
|
||||||
ElTable,
|
ElTable,
|
||||||
ElTableColumn,
|
ElTableColumn,
|
||||||
ElProgress,
|
ElProgress,
|
||||||
@@ -741,11 +906,22 @@
|
|||||||
ElSelect,
|
ElSelect,
|
||||||
ElOption,
|
ElOption,
|
||||||
ElDatePicker,
|
ElDatePicker,
|
||||||
ElPagination
|
ElPagination,
|
||||||
|
ElDialog,
|
||||||
|
ElForm,
|
||||||
|
ElFormItem,
|
||||||
|
ElInputNumber,
|
||||||
|
ElAlert,
|
||||||
|
ElIcon,
|
||||||
|
ElRadioGroup,
|
||||||
|
ElRadio,
|
||||||
|
ElInput
|
||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import { Loading } from '@element-plus/icons-vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { EnterpriseService } from '@/api/modules/enterprise'
|
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 { formatDateTime } from '@/utils/business/format'
|
||||||
import type {
|
import type {
|
||||||
AssetWalletTransactionItem,
|
AssetWalletTransactionItem,
|
||||||
@@ -762,8 +938,66 @@
|
|||||||
const refreshLoading = ref(false)
|
const refreshLoading = ref(false)
|
||||||
const stopLoading = ref(false)
|
const stopLoading = ref(false)
|
||||||
const startLoading = 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<any>(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<any>(null)
|
||||||
|
const switchCardForm = reactive({
|
||||||
|
target_iccid: ''
|
||||||
|
})
|
||||||
|
const switchCardRules = reactive({
|
||||||
|
target_iccid: [{ required: true, message: '请选择目标ICCID', trigger: 'change' }]
|
||||||
|
})
|
||||||
|
const deviceBindingCards = ref<any[]>([])
|
||||||
|
const loadingDeviceCards = ref(false)
|
||||||
|
|
||||||
|
const setWiFiDialogVisible = ref(false)
|
||||||
|
const setWiFiConfirmLoading = ref(false)
|
||||||
|
const setWiFiFormRef = ref<any>(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 isSearchCardFixed = ref(false)
|
||||||
const searchCardRef = ref<HTMLElement | null>(null)
|
const searchCardRef = ref<any>(null)
|
||||||
const searchCardPlaceholder = ref<HTMLElement | null>(null)
|
const searchCardPlaceholder = ref<HTMLElement | null>(null)
|
||||||
const cardOriginalTop = ref(0)
|
const cardOriginalTop = ref(0)
|
||||||
const cardLeft = ref(0)
|
const cardLeft = ref(0)
|
||||||
@@ -1190,8 +1424,8 @@
|
|||||||
|
|
||||||
// 更新卡片位置信息
|
// 更新卡片位置信息
|
||||||
const updateCardPosition = () => {
|
const updateCardPosition = () => {
|
||||||
if (searchCardRef.value) {
|
if (searchCardRef.value && searchCardRef.value.$el) {
|
||||||
const rect = searchCardRef.value.getBoundingClientRect()
|
const rect = searchCardRef.value.$el.getBoundingClientRect()
|
||||||
cardOriginalTop.value = rect.top + window.scrollY
|
cardOriginalTop.value = rect.top + window.scrollY
|
||||||
cardLeft.value = rect.left + window.scrollX
|
cardLeft.value = rect.left + window.scrollX
|
||||||
cardWidth.value = rect.width
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -156,7 +156,7 @@
|
|||||||
formatter: (value) => value || '-'
|
formatter: (value) => value || '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '订单角色',
|
label: '订单渠道',
|
||||||
formatter: (_, data) => (data.purchase_role ? getPurchaseRoleText(data.purchase_role) : '-')
|
formatter: (_, data) => (data.purchase_role ? getPurchaseRoleText(data.purchase_role) : '-')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -423,10 +423,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '订单角色',
|
label: '订单渠道',
|
||||||
prop: 'purchase_role',
|
prop: 'purchase_role',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
placeholder: '请选择订单角色',
|
placeholder: '请选择订单渠道',
|
||||||
options: [
|
options: [
|
||||||
{ label: '自己购买', value: 'self_purchase' },
|
{ label: '自己购买', value: 'self_purchase' },
|
||||||
{ label: '上级代理购买', value: 'purchased_by_parent' },
|
{ label: '上级代理购买', value: 'purchased_by_parent' },
|
||||||
@@ -476,7 +476,7 @@
|
|||||||
{ label: t('orderManagement.table.orderNo'), prop: 'order_no' },
|
{ label: t('orderManagement.table.orderNo'), prop: 'order_no' },
|
||||||
{ label: t('orderManagement.table.orderType'), prop: 'order_type' },
|
{ label: t('orderManagement.table.orderType'), prop: 'order_type' },
|
||||||
{ label: t('orderManagement.table.buyerType'), prop: 'buyer_type' },
|
{ label: t('orderManagement.table.buyerType'), prop: 'buyer_type' },
|
||||||
{ label: '订单角色', prop: 'purchase_role' },
|
{ label: '订单渠道', prop: 'purchase_role' },
|
||||||
{ label: '购买备注', prop: 'purchase_remark' },
|
{ label: '购买备注', prop: 'purchase_remark' },
|
||||||
{ label: '操作者', prop: 'operator_name' },
|
{ label: '操作者', prop: 'operator_name' },
|
||||||
{ label: t('orderManagement.table.paymentStatus'), prop: 'payment_status' },
|
{ label: t('orderManagement.table.paymentStatus'), prop: 'payment_status' },
|
||||||
@@ -718,7 +718,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'purchase_role',
|
prop: 'purchase_role',
|
||||||
label: '订单角色',
|
label: '订单渠道',
|
||||||
width: 140,
|
width: 140,
|
||||||
formatter: (row: Order) => {
|
formatter: (row: Order) => {
|
||||||
if (!row.purchase_role) return '-'
|
if (!row.purchase_role) return '-'
|
||||||
|
|||||||
@@ -419,6 +419,49 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
|
|
||||||
|
<!-- 修改零售价对话框 -->
|
||||||
|
<ElDialog
|
||||||
|
v-model="retailPriceDialogVisible"
|
||||||
|
title="修改零售价"
|
||||||
|
width="500px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@closed="handleCloseRetailPriceDialog"
|
||||||
|
>
|
||||||
|
<ElForm
|
||||||
|
ref="retailPriceFormRef"
|
||||||
|
:model="retailPriceForm"
|
||||||
|
:rules="retailPriceRules"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<ElFormItem label="套餐名称">
|
||||||
|
<span>{{ retailPriceForm.package_name }}</span>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="零售价" prop="retail_price">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="retailPriceForm.retail_price"
|
||||||
|
:min="0"
|
||||||
|
:max="999999"
|
||||||
|
:step="0.01"
|
||||||
|
:precision="2"
|
||||||
|
placeholder="请输入零售价"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
<div style="margin-top: 5px; font-size: 12px; color: #909399">
|
||||||
|
单位:元 | 存储值:{{ Math.round(retailPriceForm.retail_price * 100) }} 分
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<ElButton @click="retailPriceDialogVisible = false">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="handleConfirmUpdateRetailPrice" :loading="retailPriceLoading">
|
||||||
|
确认修改
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
</div>
|
</div>
|
||||||
</ArtTableFullScreen>
|
</ArtTableFullScreen>
|
||||||
@@ -428,7 +471,7 @@
|
|||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { PackageManageService, PackageSeriesService } from '@/api/modules'
|
import { PackageManageService, PackageSeriesService } from '@/api/modules'
|
||||||
import { ElMessage, ElMessageBox, ElTag, ElSwitch, ElButton } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElTag, ElSwitch, ElButton, ElInputNumber } from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import type { PackageResponse, SeriesSelectOption } from '@/types/api'
|
import type { PackageResponse, SeriesSelectOption } from '@/types/api'
|
||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
@@ -467,6 +510,22 @@
|
|||||||
const seriesOptions = ref<SeriesSelectOption[]>([])
|
const seriesOptions = ref<SeriesSelectOption[]>([])
|
||||||
const searchSeriesOptions = ref<SeriesSelectOption[]>([])
|
const searchSeriesOptions = ref<SeriesSelectOption[]>([])
|
||||||
|
|
||||||
|
// 零售价修改对话框
|
||||||
|
const retailPriceDialogVisible = ref(false)
|
||||||
|
const retailPriceLoading = ref(false)
|
||||||
|
const retailPriceFormRef = ref<FormInstance>()
|
||||||
|
const retailPriceForm = reactive({
|
||||||
|
id: 0,
|
||||||
|
package_name: '',
|
||||||
|
retail_price: 0
|
||||||
|
})
|
||||||
|
const retailPriceRules = reactive<FormRules>({
|
||||||
|
retail_price: [
|
||||||
|
{ required: true, message: '请输入零售价', trigger: 'blur' },
|
||||||
|
{ type: 'number', message: '零售价必须为数字', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
// 使用表格右键菜单功能
|
// 使用表格右键菜单功能
|
||||||
const {
|
const {
|
||||||
showContextMenuHint,
|
showContextMenuHint,
|
||||||
@@ -784,6 +843,13 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasAuth('package:update_retail_price')) {
|
||||||
|
items.push({
|
||||||
|
key: 'update-retail-price',
|
||||||
|
label: '修改零售价'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (hasAuth('package:delete')) {
|
if (hasAuth('package:delete')) {
|
||||||
items.push({
|
items.push({
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
@@ -1145,6 +1211,44 @@
|
|||||||
contextMenuRef.value?.show(event)
|
contextMenuRef.value?.show(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示零售价修改对话框
|
||||||
|
const showRetailPriceDialog = (row: PackageResponse) => {
|
||||||
|
retailPriceForm.id = row.id
|
||||||
|
retailPriceForm.package_name = row.package_name
|
||||||
|
// 将分转换为元显示
|
||||||
|
retailPriceForm.retail_price = row.retail_price ? row.retail_price / 100 : 0
|
||||||
|
retailPriceDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认修改零售价
|
||||||
|
const handleConfirmUpdateRetailPrice = () => {
|
||||||
|
retailPriceFormRef.value?.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
retailPriceLoading.value = true
|
||||||
|
try {
|
||||||
|
// 将元转换为分
|
||||||
|
const priceInCents = Math.round(retailPriceForm.retail_price * 100)
|
||||||
|
const res = await PackageManageService.updateRetailPrice(retailPriceForm.id, priceInCents)
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('零售价修改成功')
|
||||||
|
retailPriceDialogVisible.value = false
|
||||||
|
loadPackageList()
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('修改零售价失败:', error)
|
||||||
|
} finally {
|
||||||
|
retailPriceLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭零售价对话框
|
||||||
|
const handleCloseRetailPriceDialog = () => {
|
||||||
|
retailPriceDialogVisible.value = false
|
||||||
|
retailPriceFormRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
// 处理右键菜单选择
|
// 处理右键菜单选择
|
||||||
const handleContextMenuSelect = (item: MenuItemType) => {
|
const handleContextMenuSelect = (item: MenuItemType) => {
|
||||||
if (!currentRow.value) return
|
if (!currentRow.value) return
|
||||||
@@ -1153,6 +1257,9 @@
|
|||||||
case 'edit':
|
case 'edit':
|
||||||
showDialog('edit', currentRow.value)
|
showDialog('edit', currentRow.value)
|
||||||
break
|
break
|
||||||
|
case 'update-retail-price':
|
||||||
|
showRetailPriceDialog(currentRow.value)
|
||||||
|
break
|
||||||
case 'delete':
|
case 'delete':
|
||||||
deletePackage(currentRow.value)
|
deletePackage(currentRow.value)
|
||||||
break
|
break
|
||||||
|
|||||||
Reference in New Issue
Block a user