修复bug
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 11m14s

This commit is contained in:
sexygoat
2026-04-08 10:19:25 +08:00
parent ff087abd44
commit b510b4539f
55 changed files with 2274 additions and 2612 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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>

View File

@@ -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>

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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. 必填字段ICCIDMSISDN手机号可选字段virtual_no虚拟号</p>
<p>5. 必须选择运营商</p>
<p>4. <strong>必填字段ICCIDMSISDN手机号virtual_no虚拟号</strong></p>
<p>5. <strong>Excel 文件必须包含 virtual_no 否则整批导入将失败</strong></p>
<p>6. virtual_no 为空将导入失败</p>
<p>7. 必须选择运营商</p>
</div>
</template>
</ElAlert>