This commit is contained in:
4045
src/views/asset-management/asset-information/index.vue
Normal file
4045
src/views/asset-management/asset-information/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -53,13 +53,11 @@
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
:row-class-name="getRowClassName"
|
||||
:actions="getActions"
|
||||
:actionsWidth="180"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-contextmenu="handleRowContextMenu"
|
||||
@cell-mouse-enter="handleCellMouseEnter"
|
||||
@cell-mouse-leave="handleCellMouseLeave"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn type="selection" width="55" />
|
||||
@@ -67,9 +65,6 @@
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 鼠标悬浮提示 -->
|
||||
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||
|
||||
<!-- 批量分配对话框 -->
|
||||
<ElDialog v-model="allocateDialogVisible" title="批量分配设备" width="600px">
|
||||
<ElForm
|
||||
@@ -284,7 +279,7 @@
|
||||
|
||||
<!-- 设备详情弹窗 -->
|
||||
<ElDialog v-model="deviceDetailDialogVisible" title="设备详情" width="1000px">
|
||||
<div v-if="deviceDetailLoading" style=" padding: 40px 0;text-align: center">
|
||||
<div v-if="deviceDetailLoading" style="padding: 40px 0; text-align: center">
|
||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||
</div>
|
||||
<div v-else-if="currentDeviceDetail">
|
||||
@@ -296,17 +291,17 @@
|
||||
}}</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="设备名称">{{
|
||||
currentDeviceDetail.device_name || '--'
|
||||
currentDeviceDetail.device_name || '-'
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="设备型号">{{
|
||||
currentDeviceDetail.device_model || '--'
|
||||
currentDeviceDetail.device_model || '-'
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="设备类型">{{
|
||||
currentDeviceDetail.device_type || '--'
|
||||
currentDeviceDetail.device_type || '-'
|
||||
}}</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="制造商">{{
|
||||
currentDeviceDetail.manufacturer || '--'
|
||||
currentDeviceDetail.manufacturer || '-'
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="最大插槽数">{{
|
||||
currentDeviceDetail.max_sim_slots
|
||||
@@ -321,11 +316,11 @@
|
||||
</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="批次号" :span="2">{{
|
||||
currentDeviceDetail.batch_no || '--'
|
||||
currentDeviceDetail.batch_no || '-'
|
||||
}}</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="创建时间" :span="3">{{
|
||||
formatDateTime(currentDeviceDetail.created_at) || '--'
|
||||
formatDateTime(currentDeviceDetail.created_at) || '-'
|
||||
}}</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</div>
|
||||
@@ -398,7 +393,7 @@
|
||||
</ElCard>
|
||||
|
||||
<!-- 已绑定卡片列表 -->
|
||||
<div v-if="deviceCardsLoading" style=" padding: 40px 0;text-align: center">
|
||||
<div v-if="deviceCardsLoading" style="padding: 40px 0; text-align: center">
|
||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||
</div>
|
||||
<div v-else>
|
||||
@@ -406,15 +401,15 @@
|
||||
<ElTableColumn prop="slot_position" label="插槽位置" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<div
|
||||
style="display: flex; gap: 4px; align-items: center; justify-content: center"
|
||||
style="display: flex; gap: 10px; align-items: center; justify-content: center"
|
||||
>
|
||||
<span>{{ row.slot_position }}</span>
|
||||
<span>SIM-{{ row.slot_position }}</span>
|
||||
<ElTag v-if="row.is_current" type="success" size="small">当前</ElTag>
|
||||
</div>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="iccid" label="ICCID" min-width="180" />
|
||||
<ElTableColumn prop="msisdn" label="接入号" width="140" />
|
||||
<ElTableColumn prop="iccid" label="ICCID" min-width="120" />
|
||||
<ElTableColumn prop="msisdn" label="接入号" width="160" />
|
||||
<ElTableColumn prop="network_status" label="网络状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<ElTag :type="getNetworkStatusTagType(row.network_status)">
|
||||
@@ -424,7 +419,7 @@
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="bind_time" label="绑定时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.bind_time) || '--' }}
|
||||
{{ formatDateTime(row.bind_time) || '-' }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="操作" width="100" fixed="right" align="center">
|
||||
@@ -437,21 +432,13 @@
|
||||
</ElTable>
|
||||
<div
|
||||
v-if="deviceCards.length === 0"
|
||||
style=" padding: 20px; color: #909399;text-align: center"
|
||||
style="padding: 20px; color: #909399; text-align: center"
|
||||
>
|
||||
暂无设备绑定的卡
|
||||
</div>
|
||||
</div>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 设备操作右键菜单 -->
|
||||
<ArtMenuRight
|
||||
ref="deviceOperationMenuRef"
|
||||
:menu-items="deviceOperationMenuItems"
|
||||
:menu-width="140"
|
||||
@select="handleDeviceOperationMenuSelect"
|
||||
/>
|
||||
|
||||
<!-- 设置限速对话框 -->
|
||||
<ElDialog v-model="speedLimitDialogVisible" title="设置限速" width="50%">
|
||||
<ElForm
|
||||
@@ -471,7 +458,7 @@
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<div style=" margin-top: 4px; font-size: 12px;color: #909399">单位: KB/s</div>
|
||||
<div style="margin-top: 4px; font-size: 12px; color: #909399">单位: KB/s</div>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="上行速率" prop="upload_speed">
|
||||
<ElInputNumber
|
||||
@@ -481,7 +468,7 @@
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<div style=" margin-top: 4px; font-size: 12px;color: #909399">单位: KB/s</div>
|
||||
<div style="margin-top: 4px; font-size: 12px; color: #909399">单位: KB/s</div>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
@@ -494,7 +481,7 @@
|
||||
|
||||
<!-- 切换SIM卡对话框 -->
|
||||
<ElDialog v-model="switchCardDialogVisible" title="切换SIM卡" width="600px">
|
||||
<div v-if="loadingDeviceCards" style=" padding: 40px;text-align: center">
|
||||
<div v-if="loadingDeviceCards" style="padding: 40px; text-align: center">
|
||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||
<div style="margin-top: 16px">加载设备绑定的卡列表中...</div>
|
||||
</div>
|
||||
@@ -647,11 +634,7 @@
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { CommonStatus, getStatusText } from '@/config/constants'
|
||||
import type { PackageSeriesResponse } from '@/types/api'
|
||||
@@ -661,15 +644,6 @@
|
||||
const { hasAuth } = useAuth()
|
||||
const router = useRouter()
|
||||
|
||||
// 使用表格右键菜单功能
|
||||
const {
|
||||
showContextMenuHint,
|
||||
hintPosition,
|
||||
getRowClassName,
|
||||
handleCellMouseEnter,
|
||||
handleCellMouseLeave
|
||||
} = useTableContextMenu()
|
||||
|
||||
const loading = ref(false)
|
||||
const allocateLoading = ref(false)
|
||||
const recallLoading = ref(false)
|
||||
@@ -738,11 +712,8 @@
|
||||
]
|
||||
})
|
||||
|
||||
// 设备操作右键菜单
|
||||
const deviceOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||
const currentOperatingDeviceNo = ref<string>('')
|
||||
const currentOperatingDevice = ref<Device | null>(null)
|
||||
const currentOperatingImei = ref<string>('') // 用于存储当前操作设备的IMEI
|
||||
const currentOperatingDevice = ref<Device | null>(null)
|
||||
|
||||
const switchCardDialogVisible = ref(false)
|
||||
const switchCardLoading = ref(false)
|
||||
@@ -901,7 +872,7 @@
|
||||
const goToDeviceSearchDetail = (deviceNo: string) => {
|
||||
if (hasAuth('device:view_detail')) {
|
||||
router.push({
|
||||
path: '/asset-management/single-card',
|
||||
path: '/asset-management/asset-information',
|
||||
query: {
|
||||
virtual_no: deviceNo
|
||||
}
|
||||
@@ -920,14 +891,14 @@
|
||||
bindCardForm.iot_card_id = undefined
|
||||
bindCardForm.slot_position = 1
|
||||
// 加载设备卡列表和默认IoT卡列表
|
||||
await Promise.all([loadDeviceCards(device.id), loadDefaultIotCards()])
|
||||
await Promise.all([loadDeviceCards(device.virtual_no), loadDefaultIotCards()])
|
||||
}
|
||||
|
||||
// 加载设备绑定的卡列表
|
||||
const loadDeviceCards = async (deviceId: number) => {
|
||||
const loadDeviceCards = async (virtualNo: string) => {
|
||||
deviceCardsLoading.value = true
|
||||
try {
|
||||
const res = await DeviceService.getDeviceCards(deviceId)
|
||||
const res = await DeviceService.getDeviceCards(virtualNo)
|
||||
if (res.code === 0 && res.data) {
|
||||
deviceCards.value = res.data.bindings || []
|
||||
console.log('设备绑定的卡列表:', deviceCards.value)
|
||||
@@ -995,7 +966,7 @@
|
||||
if (valid) {
|
||||
bindCardLoading.value = true
|
||||
try {
|
||||
const res = await DeviceService.bindCard(currentDeviceDetail.value.id, {
|
||||
const res = await DeviceService.bindCard(currentDeviceDetail.value.virtual_no, {
|
||||
iot_card_id: bindCardForm.iot_card_id!,
|
||||
slot_position: bindCardForm.slot_position
|
||||
})
|
||||
@@ -1006,7 +977,7 @@
|
||||
bindCardForm.slot_position = 1
|
||||
bindCardFormRef.value.resetFields()
|
||||
// 重新加载卡列表
|
||||
await loadDeviceCards(currentDeviceDetail.value.id)
|
||||
await loadDeviceCards(currentDeviceDetail.value.virtual_no)
|
||||
// 刷新设备详情以更新绑定卡数量
|
||||
const detailRes = await AssetService.resolveAsset(currentDeviceDetail.value.virtual_no)
|
||||
if (detailRes.code === 0 && detailRes.data) {
|
||||
@@ -1031,11 +1002,14 @@
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
const res = await DeviceService.unbindCard(currentDeviceDetail.value.id, row.iot_card_id)
|
||||
const res = await DeviceService.unbindCard(
|
||||
currentDeviceDetail.value.virtual_no,
|
||||
row.iccid
|
||||
)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('解绑成功')
|
||||
// 重新加载卡列表
|
||||
await loadDeviceCards(currentDeviceDetail.value.id)
|
||||
await loadDeviceCards(currentDeviceDetail.value.virtual_no)
|
||||
// 刷新设备详情以更新绑定卡数量
|
||||
const detailRes = await AssetService.resolveAsset(currentDeviceDetail.value.virtual_no)
|
||||
if (detailRes.code === 0 && detailRes.data) {
|
||||
@@ -1367,7 +1341,7 @@
|
||||
)
|
||||
.then(async () => {
|
||||
try {
|
||||
await DeviceService.deleteDevice(row.id)
|
||||
await DeviceService.deleteDevice(row.virtual_no)
|
||||
ElMessage.success('删除成功')
|
||||
await getTableData()
|
||||
} catch (error) {
|
||||
@@ -1581,13 +1555,16 @@
|
||||
}
|
||||
|
||||
// 设备操作路由
|
||||
const handleDeviceOperation = async (command: string, deviceNo: string) => {
|
||||
const handleDeviceOperation = async (command: string, deviceNo: string, device?: Device) => {
|
||||
// 设置当前操作的设备
|
||||
currentOperatingDevice.value = device || null
|
||||
|
||||
switch (command) {
|
||||
case 'view-cards':
|
||||
handleViewCardsByDeviceNo(deviceNo)
|
||||
break
|
||||
case 'reboot': {
|
||||
const imei = await getDeviceImei(deviceNo)
|
||||
const imei = device?.imei || (await getDeviceImei(deviceNo))
|
||||
if (!imei) {
|
||||
ElMessage.error('无法获取设备IMEI,无法执行重启操作')
|
||||
return
|
||||
@@ -1596,7 +1573,7 @@
|
||||
break
|
||||
}
|
||||
case 'reset': {
|
||||
const imei = await getDeviceImei(deviceNo)
|
||||
const imei = device?.imei || (await getDeviceImei(deviceNo))
|
||||
if (!imei) {
|
||||
ElMessage.error('无法获取设备IMEI,无法执行重置操作')
|
||||
return
|
||||
@@ -1605,7 +1582,7 @@
|
||||
break
|
||||
}
|
||||
case 'speed-limit': {
|
||||
const imei = await getDeviceImei(deviceNo)
|
||||
const imei = device?.imei || (await getDeviceImei(deviceNo))
|
||||
if (!imei) {
|
||||
ElMessage.error('无法获取设备IMEI,无法执行限速操作')
|
||||
return
|
||||
@@ -1614,7 +1591,7 @@
|
||||
break
|
||||
}
|
||||
case 'switch-card': {
|
||||
const imei = await getDeviceImei(deviceNo)
|
||||
const imei = device?.imei || (await getDeviceImei(deviceNo))
|
||||
if (!imei) {
|
||||
ElMessage.error('无法获取设备IMEI,无法执行切卡操作')
|
||||
return
|
||||
@@ -1623,7 +1600,7 @@
|
||||
break
|
||||
}
|
||||
case 'set-wifi': {
|
||||
const imei = await getDeviceImei(deviceNo)
|
||||
const imei = device?.imei || (await getDeviceImei(deviceNo))
|
||||
if (!imei) {
|
||||
ElMessage.error('无法获取设备IMEI,无法执行WiFi设置操作')
|
||||
return
|
||||
@@ -1669,7 +1646,7 @@
|
||||
)
|
||||
.then(async () => {
|
||||
try {
|
||||
const res = await DeviceService.deactivateDevice(device.id)
|
||||
const res = await AssetService.deactivateAsset(device.virtual_no)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('手动停用成功')
|
||||
loadDeviceList()
|
||||
@@ -1798,7 +1775,7 @@
|
||||
// 加载设备绑定的卡列表
|
||||
loadingDeviceCards.value = true
|
||||
try {
|
||||
const res = await DeviceService.getDeviceCards(device.id)
|
||||
const res = await DeviceService.getDeviceCards(device.virtual_no)
|
||||
if (res.code === 0 && res.data) {
|
||||
deviceBindingCards.value = res.data.bindings || []
|
||||
|
||||
@@ -1884,89 +1861,76 @@
|
||||
}
|
||||
|
||||
// 设备操作菜单项配置
|
||||
const deviceOperationMenuItems = computed((): MenuItemType[] => {
|
||||
const items: MenuItemType[] = []
|
||||
// 表格操作列配置
|
||||
const getActions = (row: Device) => {
|
||||
const actions: any[] = []
|
||||
|
||||
// 第一个按钮:切换SIM卡
|
||||
if (hasAuth('device:switch_sim')) {
|
||||
actions.push({
|
||||
label: '切换SIM卡',
|
||||
handler: () => handleDeviceOperation('switch-card', row.virtual_no, row),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
// 其他操作放到"更多"下拉菜单中
|
||||
const moreActions: any[] = []
|
||||
|
||||
// 添加设备绑定的卡到菜单最前面
|
||||
if (hasAuth('device:view_cards')) {
|
||||
items.push({
|
||||
key: 'view-cards',
|
||||
label: '设备绑定的卡'
|
||||
moreActions.push({
|
||||
label: '设备绑定的卡',
|
||||
handler: () => handleDeviceOperation('view-cards', row.virtual_no, row),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('device:reboot')) {
|
||||
items.push({
|
||||
key: 'reboot',
|
||||
label: '重启设备'
|
||||
moreActions.push({
|
||||
label: '重启设备',
|
||||
handler: () => handleDeviceOperation('reboot', row.virtual_no, row),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('device:factory_reset')) {
|
||||
items.push({
|
||||
key: 'reset',
|
||||
label: '恢复出厂'
|
||||
})
|
||||
}
|
||||
|
||||
// if (hasAuth('device:set_speed_limit')) {
|
||||
// items.push({
|
||||
// key: 'speed-limit',
|
||||
// label: '设置限速'
|
||||
// })
|
||||
// }
|
||||
|
||||
if (hasAuth('device:switch_sim')) {
|
||||
items.push({
|
||||
key: 'switch-card',
|
||||
label: '切换SIM卡'
|
||||
moreActions.push({
|
||||
label: '恢复出厂',
|
||||
handler: () => handleDeviceOperation('reset', row.virtual_no, row),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('device:set_wifi')) {
|
||||
items.push({
|
||||
key: 'set-wifi',
|
||||
label: '设置WiFi'
|
||||
moreActions.push({
|
||||
label: '设置WiFi',
|
||||
handler: () => handleDeviceOperation('set-wifi', row.virtual_no, row),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('device:manual_deactivate')) {
|
||||
items.push({
|
||||
key: 'manual-deactivate',
|
||||
label: '手动停用'
|
||||
moreActions.push({
|
||||
label: '手动停用',
|
||||
handler: () => handleDeviceOperation('manual-deactivate', row.virtual_no, row),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('device:delete')) {
|
||||
items.push({
|
||||
key: 'delete',
|
||||
label: '删除设备'
|
||||
moreActions.push({
|
||||
label: '删除设备',
|
||||
handler: () => handleDeviceOperation('delete', row.virtual_no, row),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
// 如果有更多操作,添加到 actions 中
|
||||
if (moreActions.length > 0) {
|
||||
actions.push(...moreActions)
|
||||
}
|
||||
|
||||
// 显示设备操作菜单
|
||||
const showDeviceOperationMenu = (e: MouseEvent, deviceNo: string, device?: Device) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
currentOperatingDeviceNo.value = deviceNo
|
||||
currentOperatingDevice.value = device || null
|
||||
deviceOperationMenuRef.value?.show(e)
|
||||
}
|
||||
|
||||
// 处理设备操作菜单选择
|
||||
const handleDeviceOperationMenuSelect = (item: MenuItemType) => {
|
||||
const deviceNo = currentOperatingDeviceNo.value
|
||||
if (!deviceNo) return
|
||||
|
||||
handleDeviceOperation(item.key, deviceNo)
|
||||
}
|
||||
|
||||
// 处理表格行右键菜单
|
||||
const handleRowContextMenu = (row: Device, column: any, event: MouseEvent) => {
|
||||
showDeviceOperationMenu(event, row.virtual_no, row)
|
||||
return actions
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -37,13 +37,18 @@
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #left>
|
||||
<ElButton type="primary" @click="showAllocateDialog">{{
|
||||
$t('enterpriseDevices.buttons.allocateDevices')
|
||||
}}</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="showAllocateDialog"
|
||||
v-permission="'enterprise_device:allocate'"
|
||||
>
|
||||
{{ $t('enterpriseDevices.buttons.allocateDevices') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="warning"
|
||||
:disabled="selectedDevices.length === 0"
|
||||
@click="showRecallDialog"
|
||||
v-permission="'enterprise_device:recall'"
|
||||
>
|
||||
{{ $t('enterpriseDevices.buttons.recallDevices') }}
|
||||
</ElButton>
|
||||
|
||||
@@ -75,16 +75,73 @@
|
||||
v-model="createForm.old_asset_type"
|
||||
placeholder="请选择旧资产类型"
|
||||
style="width: 100%"
|
||||
@change="handleOldAssetTypeChange"
|
||||
>
|
||||
<ElOption label="IoT卡" value="iot_card" />
|
||||
<ElOption label="设备" value="device" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="旧资产标识符" prop="old_identifier">
|
||||
<ElInput
|
||||
<ElFormItem
|
||||
v-if="createForm.old_asset_type === 'iot_card'"
|
||||
label="旧资产标识符"
|
||||
prop="old_identifier"
|
||||
>
|
||||
<ElSelect
|
||||
v-model="createForm.old_identifier"
|
||||
placeholder="请输入ICCID/虚拟号/IMEI/SN"
|
||||
/>
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
placeholder="请输入ICCID搜索"
|
||||
:remote-method="searchOldIotCards"
|
||||
:loading="oldCardSearchLoading"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
>
|
||||
<ElOption
|
||||
v-for="card in oldIotCardOptions"
|
||||
:key="card.id"
|
||||
:label="`${card.iccid} (${card.msisdn || '无接入号'})`"
|
||||
:value="card.iccid"
|
||||
>
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<span>{{ card.iccid }}</span>
|
||||
<span style="font-size: 12px; color: var(--el-text-color-secondary)">
|
||||
{{ card.msisdn || '无接入号' }}
|
||||
</span>
|
||||
</div>
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="createForm.old_asset_type === 'device'"
|
||||
label="旧资产标识符"
|
||||
prop="old_identifier"
|
||||
>
|
||||
<ElSelect
|
||||
v-model="createForm.old_identifier"
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
placeholder="请输入虚拟号或IMEI搜索"
|
||||
:remote-method="searchOldDevices"
|
||||
:loading="oldDeviceSearchLoading"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
>
|
||||
<ElOption
|
||||
v-for="device in oldDeviceOptions"
|
||||
:key="device.id"
|
||||
:label="`${device.virtual_no} (${device.device_name || device.imei})`"
|
||||
:value="device.virtual_no"
|
||||
>
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<span>{{ device.virtual_no }}</span>
|
||||
<span style="font-size: 12px; color: var(--el-text-color-secondary)">
|
||||
{{ device.device_name || device.imei }}
|
||||
</span>
|
||||
</div>
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="备注">
|
||||
<ElInput
|
||||
@@ -129,7 +186,7 @@
|
||||
</ElFormItem>
|
||||
<ElFormItem label="是否迁移数据">
|
||||
<ElSwitch v-model="shipForm.migrate_data" />
|
||||
<div style=" margin-top: 4px;font-size: 12px; color: #909399">
|
||||
<div style="margin-top: 4px; font-size: 12px; color: #909399">
|
||||
开启后将迁移钱包余额、套餐记录到新资产
|
||||
</div>
|
||||
</ElFormItem>
|
||||
@@ -152,8 +209,9 @@
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ExchangeService } from '@/api/modules'
|
||||
import { ExchangeService, CardService, DeviceService } from '@/api/modules'
|
||||
import type { ExchangeResponse } from '@/api/modules/exchange'
|
||||
import type { StandaloneIotCard, Device } from '@/types/api'
|
||||
import { ElMessage, ElTag, ElButton, ElMessageBox, ElSwitch } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
@@ -208,9 +266,15 @@
|
||||
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' }]
|
||||
old_identifier: [{ required: true, message: '请选择旧资产标识符', trigger: 'change' }]
|
||||
})
|
||||
|
||||
// 旧资产搜索相关
|
||||
const oldIotCardOptions = ref<StandaloneIotCard[]>([])
|
||||
const oldCardSearchLoading = ref(false)
|
||||
const oldDeviceOptions = ref<Device[]>([])
|
||||
const oldDeviceSearchLoading = ref(false)
|
||||
|
||||
// 发货表单
|
||||
const shipForm = reactive({
|
||||
new_identifier: '',
|
||||
@@ -311,7 +375,7 @@
|
||||
prop: 'new_asset_identifier',
|
||||
label: '新资产标识符',
|
||||
width: 180,
|
||||
formatter: (row: any) => row.new_asset_identifier || '--'
|
||||
formatter: (row: any) => row.new_asset_identifier
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
@@ -327,8 +391,8 @@
|
||||
}
|
||||
])
|
||||
|
||||
const getStatusType = (status: number) => {
|
||||
const types: Record<number, string> = {
|
||||
const getStatusType = (status: number): 'success' | 'info' | 'warning' | 'danger' | 'primary' => {
|
||||
const types: Record<number, 'success' | 'info' | 'warning' | 'danger' | 'primary'> = {
|
||||
1: 'warning',
|
||||
2: 'info',
|
||||
3: 'primary',
|
||||
@@ -363,7 +427,7 @@
|
||||
|
||||
const res = await ExchangeService.getExchanges(params)
|
||||
if (res.code === 0 && res.data) {
|
||||
exchangeList.value = res.data.list || []
|
||||
exchangeList.value = res.data.items || []
|
||||
pagination.total = res.data.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -406,6 +470,123 @@
|
||||
createDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 旧资产类型变更
|
||||
const handleOldAssetTypeChange = async () => {
|
||||
// 清空旧资产标识符
|
||||
createForm.old_identifier = ''
|
||||
oldIotCardOptions.value = []
|
||||
oldDeviceOptions.value = []
|
||||
|
||||
// 根据类型加载默认数据
|
||||
if (createForm.old_asset_type === 'iot_card') {
|
||||
await loadDefaultIotCards()
|
||||
} else if (createForm.old_asset_type === 'device') {
|
||||
await loadDefaultDevices()
|
||||
}
|
||||
}
|
||||
|
||||
// 加载默认IoT卡列表
|
||||
const loadDefaultIotCards = async () => {
|
||||
oldCardSearchLoading.value = true
|
||||
try {
|
||||
const res = await CardService.getStandaloneIotCards({
|
||||
page: 1,
|
||||
page_size: 10
|
||||
})
|
||||
if (res.code === 0 && res.data) {
|
||||
oldIotCardOptions.value = res.data.items || []
|
||||
if (oldIotCardOptions.value.length === 0) {
|
||||
ElMessage.info('暂无可用的IoT卡')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载IoT卡列表失败:', error)
|
||||
oldIotCardOptions.value = []
|
||||
} finally {
|
||||
oldCardSearchLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载默认设备列表
|
||||
const loadDefaultDevices = async () => {
|
||||
oldDeviceSearchLoading.value = true
|
||||
try {
|
||||
const res = await DeviceService.getDevices({
|
||||
page: 1,
|
||||
page_size: 10
|
||||
})
|
||||
if (res.code === 0 && res.data) {
|
||||
oldDeviceOptions.value = res.data.items || []
|
||||
if (oldDeviceOptions.value.length === 0) {
|
||||
ElMessage.info('暂无可用的设备')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载设备列表失败:', error)
|
||||
oldDeviceOptions.value = []
|
||||
} finally {
|
||||
oldDeviceSearchLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索旧IoT卡
|
||||
const searchOldIotCards = async (query: string) => {
|
||||
if (!query) {
|
||||
// 如果清空搜索,加载默认列表
|
||||
await loadDefaultIotCards()
|
||||
return
|
||||
}
|
||||
|
||||
oldCardSearchLoading.value = true
|
||||
try {
|
||||
const res = await CardService.getStandaloneIotCards({
|
||||
iccid: query,
|
||||
page: 1,
|
||||
page_size: 20
|
||||
})
|
||||
if (res.code === 0 && res.data) {
|
||||
oldIotCardOptions.value = res.data.items || []
|
||||
if (oldIotCardOptions.value.length === 0) {
|
||||
ElMessage.info('未找到匹配的IoT卡')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索IoT卡失败:', error)
|
||||
oldIotCardOptions.value = []
|
||||
} finally {
|
||||
oldCardSearchLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索旧设备
|
||||
const searchOldDevices = async (query: string) => {
|
||||
if (!query) {
|
||||
// 如果清空搜索,加载默认列表
|
||||
await loadDefaultDevices()
|
||||
return
|
||||
}
|
||||
|
||||
oldDeviceSearchLoading.value = true
|
||||
try {
|
||||
const res = await DeviceService.getDevices({
|
||||
keyword: query,
|
||||
page: 1,
|
||||
page_size: 20
|
||||
})
|
||||
if (res.code === 0 && res.data) {
|
||||
oldDeviceOptions.value = res.data.items || []
|
||||
if (oldDeviceOptions.value.length === 0) {
|
||||
ElMessage.info('未找到匹配的设备')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索设备失败:', error)
|
||||
oldDeviceOptions.value = []
|
||||
} finally {
|
||||
oldDeviceSearchLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 确认创建
|
||||
const handleConfirmCreate = () => {
|
||||
createFormRef.value?.validate(async (valid) => {
|
||||
@@ -417,7 +598,7 @@
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('换货单创建成功')
|
||||
createDialogVisible.value = false
|
||||
loadExchangeList()
|
||||
await loadExchangeList()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建换货单失败:', error)
|
||||
@@ -430,6 +611,9 @@
|
||||
// 关闭创建对话框
|
||||
const handleCloseCreateDialog = () => {
|
||||
createFormRef.value?.resetFields()
|
||||
// 清空搜索选项
|
||||
oldIotCardOptions.value = []
|
||||
oldDeviceOptions.value = []
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
@@ -492,7 +676,7 @@
|
||||
})
|
||||
|
||||
// 处理右键菜单
|
||||
const handleRowContextMenu = (row: ExchangeResponse, column: any, event: MouseEvent) => {
|
||||
const handleRowContextMenu = (row: ExchangeResponse, _column: any, event: MouseEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
currentExchangeRow.value = row
|
||||
@@ -531,7 +715,7 @@
|
||||
const res = await ExchangeService.cancelExchange(row.id, { remark: value })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('取消成功')
|
||||
loadExchangeList()
|
||||
await loadExchangeList()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('取消换货失败:', error)
|
||||
@@ -562,7 +746,7 @@
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('发货成功')
|
||||
shipDialogVisible.value = false
|
||||
loadExchangeList()
|
||||
await loadExchangeList()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发货失败:', error)
|
||||
@@ -589,7 +773,7 @@
|
||||
const res = await ExchangeService.completeExchange(row.id)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('操作成功')
|
||||
loadExchangeList()
|
||||
await loadExchangeList()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('确认完成失败:', error)
|
||||
@@ -610,7 +794,7 @@
|
||||
const res = await ExchangeService.renewExchange(row.id)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('操作成功')
|
||||
loadExchangeList()
|
||||
await loadExchangeList()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('旧资产转新失败:', error)
|
||||
|
||||
@@ -68,13 +68,11 @@
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
:row-class-name="getRowClassName"
|
||||
:actions="getActions"
|
||||
:actionsWidth="160"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-contextmenu="handleRowContextMenu"
|
||||
@cell-mouse-enter="handleCellMouseEnter"
|
||||
@cell-mouse-leave="handleCellMouseLeave"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn type="selection" width="55" />
|
||||
@@ -82,9 +80,6 @@
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 鼠标悬浮提示 -->
|
||||
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||
|
||||
<!-- 批量分配对话框 -->
|
||||
<ElDialog
|
||||
v-model="allocateDialogVisible"
|
||||
@@ -126,26 +121,32 @@
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem v-if="allocateForm.selection_type === 'list'" label="ICCID列表">
|
||||
<ElFormItem
|
||||
v-if="allocateForm.selection_type === CardSelectionType.LIST"
|
||||
label="ICCID列表"
|
||||
>
|
||||
<div>已选择 {{ selectedCards.length }} 张卡</div>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem
|
||||
v-if="allocateForm.selection_type === 'range'"
|
||||
v-if="allocateForm.selection_type === CardSelectionType.RANGE"
|
||||
label="起始ICCID"
|
||||
prop="iccid_start"
|
||||
>
|
||||
<ElInput v-model="allocateForm.iccid_start" placeholder="请输入起始ICCID" />
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="allocateForm.selection_type === 'range'"
|
||||
v-if="allocateForm.selection_type === CardSelectionType.RANGE"
|
||||
label="结束ICCID"
|
||||
prop="iccid_end"
|
||||
>
|
||||
<ElInput v-model="allocateForm.iccid_end" placeholder="请输入结束ICCID" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem v-if="allocateForm.selection_type === 'filter'" label="运营商">
|
||||
<ElFormItem
|
||||
v-if="allocateForm.selection_type === CardSelectionType.FILTER"
|
||||
label="运营商"
|
||||
>
|
||||
<ElSelect
|
||||
v-model="allocateForm.carrier_id"
|
||||
placeholder="请选择运营商"
|
||||
@@ -157,7 +158,10 @@
|
||||
<ElOption label="中国电信" :value="3" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="allocateForm.selection_type === 'filter'" label="卡状态">
|
||||
<ElFormItem
|
||||
v-if="allocateForm.selection_type === CardSelectionType.FILTER"
|
||||
label="卡状态"
|
||||
>
|
||||
<ElSelect
|
||||
v-model="allocateForm.status"
|
||||
placeholder="请选择状态"
|
||||
@@ -170,7 +174,10 @@
|
||||
<ElOption label="已停用" :value="4" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="allocateForm.selection_type === 'filter'" label="批次号">
|
||||
<ElFormItem
|
||||
v-if="allocateForm.selection_type === CardSelectionType.FILTER"
|
||||
label="批次号"
|
||||
>
|
||||
<ElInput v-model="allocateForm.batch_no" placeholder="请输入批次号" />
|
||||
</ElFormItem>
|
||||
|
||||
@@ -209,26 +216,32 @@
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem v-if="recallForm.selection_type === 'list'" label="ICCID列表">
|
||||
<ElFormItem
|
||||
v-if="recallForm.selection_type === CardSelectionType.LIST"
|
||||
label="ICCID列表"
|
||||
>
|
||||
<div>已选择 {{ selectedCards.length }} 张卡</div>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem
|
||||
v-if="recallForm.selection_type === 'range'"
|
||||
v-if="recallForm.selection_type === CardSelectionType.RANGE"
|
||||
label="起始ICCID"
|
||||
prop="iccid_start"
|
||||
>
|
||||
<ElInput v-model="recallForm.iccid_start" placeholder="请输入起始ICCID" />
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="recallForm.selection_type === 'range'"
|
||||
v-if="recallForm.selection_type === CardSelectionType.RANGE"
|
||||
label="结束ICCID"
|
||||
prop="iccid_end"
|
||||
>
|
||||
<ElInput v-model="recallForm.iccid_end" placeholder="请输入结束ICCID" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem v-if="recallForm.selection_type === 'filter'" label="运营商">
|
||||
<ElFormItem
|
||||
v-if="recallForm.selection_type === CardSelectionType.FILTER"
|
||||
label="运营商"
|
||||
>
|
||||
<ElSelect
|
||||
v-model="recallForm.carrier_id"
|
||||
placeholder="请选择运营商"
|
||||
@@ -240,7 +253,10 @@
|
||||
<ElOption label="中国电信" :value="3" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="recallForm.selection_type === 'filter'" label="批次号">
|
||||
<ElFormItem
|
||||
v-if="recallForm.selection_type === CardSelectionType.FILTER"
|
||||
label="批次号"
|
||||
>
|
||||
<ElInput v-model="recallForm.batch_no" placeholder="请输入批次号" />
|
||||
</ElFormItem>
|
||||
|
||||
@@ -385,7 +401,7 @@
|
||||
|
||||
<!-- 卡详情对话框 -->
|
||||
<ElDialog v-model="cardDetailDialogVisible" title="卡片详情" width="900px">
|
||||
<div v-if="cardDetailLoading" style=" padding: 40px;text-align: center">
|
||||
<div v-if="cardDetailLoading" style="padding: 40px; text-align: center">
|
||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||
<div style="margin-top: 16px">加载中...</div>
|
||||
</div>
|
||||
@@ -465,7 +481,7 @@
|
||||
|
||||
<!-- 流量使用查询对话框 -->
|
||||
<ElDialog v-model="flowUsageDialogVisible" title="流量使用查询" width="500px">
|
||||
<div v-if="flowUsageLoading" style=" padding: 40px;text-align: center">
|
||||
<div v-if="flowUsageLoading" style="padding: 40px; text-align: center">
|
||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||
<div style="margin-top: 16px">查询中...</div>
|
||||
</div>
|
||||
@@ -494,7 +510,7 @@
|
||||
|
||||
<!-- 实名状态查询对话框 -->
|
||||
<ElDialog v-model="realnameStatusDialogVisible" title="实名认证状态" width="500px">
|
||||
<div v-if="realnameStatusLoading" style=" padding: 40px;text-align: center">
|
||||
<div v-if="realnameStatusLoading" style="padding: 40px; text-align: center">
|
||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||
<div style="margin-top: 16px">查询中...</div>
|
||||
</div>
|
||||
@@ -517,7 +533,7 @@
|
||||
|
||||
<!-- 卡实时状态查询对话框 -->
|
||||
<ElDialog v-model="cardStatusDialogVisible" title="卡实时状态" width="500px">
|
||||
<div v-if="cardStatusLoading" style=" padding: 40px;text-align: center">
|
||||
<div v-if="cardStatusLoading" style="padding: 40px; text-align: center">
|
||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||
<div style="margin-top: 16px">查询中...</div>
|
||||
</div>
|
||||
@@ -548,14 +564,6 @@
|
||||
:menu-width="180"
|
||||
@select="handleMoreMenuSelect"
|
||||
/>
|
||||
|
||||
<!-- 表格行操作右键菜单 -->
|
||||
<ArtMenuRight
|
||||
ref="cardOperationMenuRef"
|
||||
:menu-items="cardOperationMenuItems"
|
||||
:menu-width="160"
|
||||
@select="handleCardOperationMenuSelect"
|
||||
/>
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
@@ -571,12 +579,9 @@
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||
import type {
|
||||
StandaloneIotCard,
|
||||
StandaloneCardStatus,
|
||||
@@ -585,23 +590,14 @@
|
||||
AllocateStandaloneCardsResponse,
|
||||
BatchSetCardSeriesBindingResponse
|
||||
} from '@/types/api/card'
|
||||
import { CardSelectionType } from '@/types/api/card'
|
||||
import type { PackageSeriesResponse } from '@/types/api'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
|
||||
defineOptions({ name: 'StandaloneCardList' })
|
||||
|
||||
const { hasAuth } = useAuth()
|
||||
const router = useRouter()
|
||||
|
||||
// 使用表格右键菜单功能
|
||||
const {
|
||||
showContextMenuHint,
|
||||
hintPosition,
|
||||
getRowClassName,
|
||||
handleCellMouseEnter,
|
||||
handleCellMouseLeave
|
||||
} = useTableContextMenu()
|
||||
|
||||
const loading = ref(false)
|
||||
const allocateDialogVisible = ref(false)
|
||||
const allocateLoading = ref(false)
|
||||
@@ -660,18 +656,21 @@
|
||||
|
||||
// 更多操作右键菜单
|
||||
const moreMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||
const cardOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||
const currentOperatingIccid = ref<string>('')
|
||||
const currentOperatingCard = ref<StandaloneIotCard | null>(null)
|
||||
|
||||
// 店铺相关
|
||||
const targetShopLoading = ref(false)
|
||||
const fromShopLoading = ref(false)
|
||||
const targetShopList = ref<any[]>([])
|
||||
const fromShopList = ref<any[]>([])
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState = {
|
||||
const initialSearchState: {
|
||||
status: undefined | number
|
||||
carrier_id: undefined | number
|
||||
iccid: string
|
||||
msisdn: string
|
||||
virtual_no: string
|
||||
is_distributed: undefined | number
|
||||
[key: string]: any
|
||||
} = {
|
||||
status: undefined,
|
||||
carrier_id: undefined,
|
||||
iccid: '',
|
||||
@@ -685,7 +684,7 @@
|
||||
|
||||
// 批量分配表单
|
||||
const allocateForm = reactive<Partial<AllocateStandaloneCardsRequest>>({
|
||||
selection_type: 'list',
|
||||
selection_type: CardSelectionType.LIST,
|
||||
to_shop_id: undefined,
|
||||
iccids: [],
|
||||
iccid_start: '',
|
||||
@@ -703,8 +702,8 @@
|
||||
iccid_start: [
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value, callback) => {
|
||||
if (allocateForm.selection_type === 'range' && !value) {
|
||||
validator: (_rule, value, callback) => {
|
||||
if (allocateForm.selection_type === CardSelectionType.RANGE && !value) {
|
||||
callback(new Error('请输入起始ICCID'))
|
||||
} else {
|
||||
callback()
|
||||
@@ -716,8 +715,8 @@
|
||||
iccid_end: [
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value, callback) => {
|
||||
if (allocateForm.selection_type === 'range' && !value) {
|
||||
validator: (_rule, value, callback) => {
|
||||
if (allocateForm.selection_type === CardSelectionType.RANGE && !value) {
|
||||
callback(new Error('请输入结束ICCID'))
|
||||
} else {
|
||||
callback()
|
||||
@@ -730,7 +729,7 @@
|
||||
|
||||
// 批量回收表单
|
||||
const recallForm = reactive<Partial<RecallStandaloneCardsRequest>>({
|
||||
selection_type: 'list',
|
||||
selection_type: CardSelectionType.LIST,
|
||||
iccids: [],
|
||||
iccid_start: '',
|
||||
iccid_end: '',
|
||||
@@ -745,8 +744,8 @@
|
||||
iccid_start: [
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value, callback) => {
|
||||
if (recallForm.selection_type === 'range' && !value) {
|
||||
validator: (_rule, value, callback) => {
|
||||
if (recallForm.selection_type === CardSelectionType.RANGE && !value) {
|
||||
callback(new Error('请输入起始ICCID'))
|
||||
} else {
|
||||
callback()
|
||||
@@ -758,8 +757,8 @@
|
||||
iccid_end: [
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value, callback) => {
|
||||
if (recallForm.selection_type === 'range' && !value) {
|
||||
validator: (_rule, value, callback) => {
|
||||
if (recallForm.selection_type === CardSelectionType.RANGE && !value) {
|
||||
callback(new Error('请输入结束ICCID'))
|
||||
} else {
|
||||
callback()
|
||||
@@ -907,7 +906,7 @@
|
||||
const goToCardDetail = (iccid: string) => {
|
||||
if (hasAuth('iot_card:view_detail')) {
|
||||
router.push({
|
||||
path: '/asset-management/single-card',
|
||||
path: '/asset-management/asset-information',
|
||||
query: {
|
||||
iccid: iccid
|
||||
}
|
||||
@@ -983,13 +982,13 @@
|
||||
{
|
||||
prop: 'msisdn',
|
||||
label: '卡接入号',
|
||||
width: 130
|
||||
width: 160
|
||||
},
|
||||
{
|
||||
prop: 'virtual_no',
|
||||
label: '虚拟号',
|
||||
width: 130,
|
||||
formatter: (row: StandaloneIotCard) => row.virtual_no || '-'
|
||||
width: 160,
|
||||
formatter: (row: StandaloneIotCard) => row.virtual_no
|
||||
},
|
||||
{
|
||||
prop: 'card_category',
|
||||
@@ -1081,6 +1080,63 @@
|
||||
}
|
||||
])
|
||||
|
||||
// 操作列配置
|
||||
const getActions = (row: StandaloneIotCard) => {
|
||||
const actions: any[] = []
|
||||
|
||||
// 查询流量按钮
|
||||
if (hasAuth('iot_card:query_flow')) {
|
||||
actions.push({
|
||||
label: '查询流量',
|
||||
handler: () => showFlowUsageDialog(row.iccid),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
// 其他操作放到更多中
|
||||
if (hasAuth('iot_card:query_realname_status')) {
|
||||
actions.push({
|
||||
label: '查询实名状态',
|
||||
handler: () => handleCardOperation('realname-status', row.iccid),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('iot_card:query_card_status')) {
|
||||
actions.push({
|
||||
label: '查询卡状态',
|
||||
handler: () => handleCardOperation('card-status', row.iccid),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('iot_card:start_card')) {
|
||||
actions.push({
|
||||
label: '启用此卡',
|
||||
handler: () => handleCardOperation('start-card', row.iccid),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('iot_card:stop_card')) {
|
||||
actions.push({
|
||||
label: '停用此卡',
|
||||
handler: () => handleCardOperation('stop-card', row.iccid),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('iot_card:manual_deactivate')) {
|
||||
actions.push({
|
||||
label: '手动停用',
|
||||
handler: () => handleCardOperation('manual-deactivate', row.iccid),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
return actions
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
})
|
||||
@@ -1089,7 +1145,7 @@
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
const params: Record<string, any> = {
|
||||
page: pagination.page,
|
||||
page_size: pagination.pageSize,
|
||||
...formFilters
|
||||
@@ -1174,33 +1230,6 @@
|
||||
await loadTargetShops(query || undefined)
|
||||
}
|
||||
|
||||
// 加载来源店铺列表
|
||||
const loadFromShops = async (shopName?: string) => {
|
||||
fromShopLoading.value = true
|
||||
try {
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 20
|
||||
}
|
||||
if (shopName) {
|
||||
params.shop_name = shopName
|
||||
}
|
||||
const res = await ShopService.getShops(params)
|
||||
if (res.code === 0) {
|
||||
fromShopList.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取来源店铺列表失败:', error)
|
||||
} finally {
|
||||
fromShopLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索来源店铺
|
||||
const searchFromShops = async (query: string) => {
|
||||
await loadFromShops(query || undefined)
|
||||
}
|
||||
|
||||
// 显示批量分配对话框
|
||||
const showAllocateDialog = () => {
|
||||
if (selectedCards.value.length === 0) {
|
||||
@@ -1209,7 +1238,7 @@
|
||||
}
|
||||
allocateDialogVisible.value = true
|
||||
Object.assign(allocateForm, {
|
||||
selection_type: 'list',
|
||||
selection_type: CardSelectionType.LIST,
|
||||
to_shop_id: undefined,
|
||||
iccids: selectedCards.value.map((card) => card.iccid),
|
||||
iccid_start: '',
|
||||
@@ -1234,7 +1263,7 @@
|
||||
}
|
||||
recallDialogVisible.value = true
|
||||
Object.assign(recallForm, {
|
||||
selection_type: 'list',
|
||||
selection_type: CardSelectionType.LIST,
|
||||
iccids: selectedCards.value.map((card) => card.iccid),
|
||||
iccid_start: '',
|
||||
iccid_end: '',
|
||||
@@ -1274,16 +1303,16 @@
|
||||
remark: allocateForm.remark
|
||||
}
|
||||
|
||||
if (allocateForm.selection_type === 'list') {
|
||||
if (allocateForm.selection_type === CardSelectionType.LIST) {
|
||||
params.iccids = selectedCards.value.map((card) => card.iccid)
|
||||
if (params.iccids.length === 0) {
|
||||
ElMessage.warning('请先选择要分配的卡')
|
||||
return
|
||||
}
|
||||
} else if (allocateForm.selection_type === 'range') {
|
||||
} else if (allocateForm.selection_type === CardSelectionType.RANGE) {
|
||||
params.iccid_start = allocateForm.iccid_start
|
||||
params.iccid_end = allocateForm.iccid_end
|
||||
} else if (allocateForm.selection_type === 'filter') {
|
||||
} else if (allocateForm.selection_type === CardSelectionType.FILTER) {
|
||||
if (allocateForm.carrier_id) params.carrier_id = allocateForm.carrier_id
|
||||
if (allocateForm.status) params.status = allocateForm.status
|
||||
if (allocateForm.batch_no) params.batch_no = allocateForm.batch_no
|
||||
@@ -1327,16 +1356,16 @@
|
||||
remark: recallForm.remark
|
||||
}
|
||||
|
||||
if (recallForm.selection_type === 'list') {
|
||||
if (recallForm.selection_type === CardSelectionType.LIST) {
|
||||
params.iccids = selectedCards.value.map((card) => card.iccid)
|
||||
if (params.iccids.length === 0) {
|
||||
ElMessage.warning('请先选择要回收的卡')
|
||||
return
|
||||
}
|
||||
} else if (recallForm.selection_type === 'range') {
|
||||
} else if (recallForm.selection_type === CardSelectionType.RANGE) {
|
||||
params.iccid_start = recallForm.iccid_start
|
||||
params.iccid_end = recallForm.iccid_end
|
||||
} else if (recallForm.selection_type === 'filter') {
|
||||
} else if (recallForm.selection_type === CardSelectionType.FILTER) {
|
||||
if (recallForm.carrier_id) params.carrier_id = recallForm.carrier_id
|
||||
if (recallForm.batch_no) params.batch_no = recallForm.batch_no
|
||||
}
|
||||
@@ -1358,7 +1387,7 @@
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} else {
|
||||
// code !== 0 才是真正的失败(接口调用失败)
|
||||
// code !== 0 才是真正失败(接口调用失败)
|
||||
ElMessage.error(res.msg || '批量回收失败,请重试')
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -1501,60 +1530,6 @@
|
||||
})
|
||||
|
||||
// 卡操作菜单项配置
|
||||
const cardOperationMenuItems = computed((): MenuItemType[] => {
|
||||
const items: MenuItemType[] = []
|
||||
|
||||
if (hasAuth('iot_card:query_flow')) {
|
||||
items.push({
|
||||
key: 'query-flow',
|
||||
label: '查询流量'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('iot_card:query_realname_status')) {
|
||||
items.push({
|
||||
key: 'realname-status',
|
||||
label: '查询实名状态'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('iot_card:query_card_status')) {
|
||||
items.push({
|
||||
key: 'card-status',
|
||||
label: '查询卡状态'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('iot_card:start_card')) {
|
||||
items.push({
|
||||
key: 'start-card',
|
||||
label: '启用此卡'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('iot_card:stop_card')) {
|
||||
items.push({
|
||||
key: 'stop-card',
|
||||
label: '停用此卡'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('iot_card:manual_deactivate')) {
|
||||
items.push({
|
||||
key: 'manual-deactivate',
|
||||
label: '手动停用'
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
// 显示更多操作菜单 (右键)
|
||||
const showMoreMenu = (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
moreMenuRef.value?.show(e)
|
||||
}
|
||||
|
||||
// 显示更多操作菜单 (左键点击)
|
||||
const showMoreMenuOnClick = (e: MouseEvent) => {
|
||||
@@ -1583,27 +1558,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 显示卡操作菜单
|
||||
const showCardOperationMenu = (e: MouseEvent, iccid: string, card?: StandaloneIotCard) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
currentOperatingIccid.value = iccid
|
||||
currentOperatingCard.value = card || null
|
||||
cardOperationMenuRef.value?.show(e)
|
||||
}
|
||||
|
||||
// 处理卡操作菜单选择
|
||||
const handleCardOperationMenuSelect = (item: MenuItemType) => {
|
||||
const iccid = currentOperatingIccid.value
|
||||
if (!iccid) return
|
||||
|
||||
if (item.key === 'query-flow') {
|
||||
showFlowUsageDialog(iccid)
|
||||
} else {
|
||||
handleCardOperation(item.key, iccid)
|
||||
}
|
||||
}
|
||||
|
||||
// 网卡分销 - 正在开发中
|
||||
const cardDistribution = () => {
|
||||
ElMessage.info('功能正在开发中')
|
||||
@@ -1645,7 +1599,7 @@
|
||||
handleStopCard(iccid)
|
||||
break
|
||||
case 'manual-deactivate':
|
||||
handleManualDeactivate()
|
||||
handleManualDeactivate(iccid)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -1668,7 +1622,7 @@
|
||||
extend: res.data.extend
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.message || '查询失败')
|
||||
ElMessage.error(res.msg || '查询失败')
|
||||
flowUsageDialogVisible.value = false
|
||||
}
|
||||
} catch (error: any) {
|
||||
@@ -1704,7 +1658,7 @@
|
||||
extend: res.data.extend
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.message || '查询失败')
|
||||
ElMessage.error(res.msg || '查询失败')
|
||||
realnameStatusDialogVisible.value = false
|
||||
}
|
||||
} catch (error: any) {
|
||||
@@ -1732,7 +1686,7 @@
|
||||
extend: res.data.extend
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.message || '查询失败')
|
||||
ElMessage.error(res.msg || '查询失败')
|
||||
cardStatusDialogVisible.value = false
|
||||
}
|
||||
} catch (error: any) {
|
||||
@@ -1752,11 +1706,11 @@
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
// 使用新接口:直接通过 ICCID 启用此卡
|
||||
const res = await AssetService.startCard(iccid)
|
||||
// 使用统一接口:通过 identifier (ICCID) 启用资产
|
||||
const res = await AssetService.startAsset(iccid)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('启用成功')
|
||||
getTableData()
|
||||
await getTableData()
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('启用此卡失败:', error)
|
||||
@@ -1776,11 +1730,11 @@
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
// 使用新接口:直接通过 ICCID 停用此卡
|
||||
const res = await AssetService.stopCard(iccid)
|
||||
// 使用统一接口:通过 identifier (ICCID) 停用资产
|
||||
const res = await AssetService.stopAsset(iccid)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('停用成功')
|
||||
getTableData()
|
||||
await getTableData()
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('停用此卡失败:', error)
|
||||
@@ -1792,15 +1746,9 @@
|
||||
}
|
||||
|
||||
// 手动停用 IoT 卡(资产层面的停用)
|
||||
const handleManualDeactivate = () => {
|
||||
const card = currentOperatingCard.value
|
||||
if (!card) {
|
||||
ElMessage.error('未找到卡片信息')
|
||||
return
|
||||
}
|
||||
|
||||
const handleManualDeactivate = (iccid: string) => {
|
||||
ElMessageBox.confirm(
|
||||
`确定要手动停用卡片 ${card.iccid} 吗?此操作将在资产管理层面停用该卡。`,
|
||||
`确定要手动停用卡片 ${iccid} 吗?此操作将在资产管理层面停用该卡。`,
|
||||
'确认手动停用',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
@@ -1810,7 +1758,7 @@
|
||||
)
|
||||
.then(async () => {
|
||||
try {
|
||||
const res = await CardService.deactivateIotCard(card.id)
|
||||
const res = await AssetService.deactivateAsset(iccid)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('手动停用成功')
|
||||
await getTableData()
|
||||
@@ -1823,18 +1771,9 @@
|
||||
// 用户取消
|
||||
})
|
||||
}
|
||||
|
||||
// 处理表格行右键菜单
|
||||
const handleRowContextMenu = (row: StandaloneIotCard, column: any, event: MouseEvent) => {
|
||||
showCardOperationMenu(event, row.iccid, row)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.standalone-card-list-page {
|
||||
// Card list page styles
|
||||
}
|
||||
|
||||
:deep(.el-table__row.table-row-with-context-menu) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
align-center
|
||||
destroy-on-close
|
||||
>
|
||||
<div v-if="failDataLoading" style=" padding: 40px;text-align: center">
|
||||
<div v-if="failDataLoading" style="padding: 40px; text-align: center">
|
||||
<ElSkeleton :rows="5" animated />
|
||||
</div>
|
||||
<div v-else-if="failedItems.length > 0">
|
||||
@@ -92,7 +92,7 @@
|
||||
<ElTableColumn prop="message" label="失败原因" min-width="200" show-overflow-tooltip />
|
||||
</ElTable>
|
||||
</div>
|
||||
<div v-else style=" padding: 40px;text-align: center">
|
||||
<div v-else style="padding: 40px; text-align: center">
|
||||
<ElEmpty description="暂无失败数据" />
|
||||
</div>
|
||||
|
||||
@@ -118,8 +118,10 @@
|
||||
<p>1. 请先下载 Excel 模板文件,按照模板格式填写IoT卡信息</p>
|
||||
<p>2. 仅支持 Excel 格式(.xlsx),单次最多导入 1000 条</p>
|
||||
<p>3. 列格式请设置为文本格式,避免长数字被转为科学计数法</p>
|
||||
<p>4. 必填字段:ICCID、MSISDN(手机号);可选字段:virtual_no(虚拟号)</p>
|
||||
<p>5. 必须选择运营商</p>
|
||||
<p>4. <strong>必填字段:ICCID、MSISDN(手机号)、virtual_no(虚拟号)</strong></p>
|
||||
<p>5. <strong>Excel 文件必须包含 virtual_no 列,否则整批导入将失败</strong></p>
|
||||
<p>6. virtual_no 为空将导入失败</p>
|
||||
<p>7. 必须选择运营商</p>
|
||||
</div>
|
||||
</template>
|
||||
</ElAlert>
|
||||
|
||||
Reference in New Issue
Block a user