删除多余代码

This commit is contained in:
sexygoat
2026-04-08 19:31:22 +08:00
parent b510b4539f
commit d1c6588d8f
110 changed files with 897 additions and 24613 deletions

View File

@@ -3811,27 +3811,27 @@
.wallet-balance-cards {
display: flex;
flex-direction: column;
gap: 14px;
gap: 8px;
.balance-card {
display: flex;
gap: 18px;
gap: 10px;
align-items: center;
padding: 18px 20px;
padding: 10px 14px;
background: var(--el-fill-color-light);
border: 1px solid var(--el-border-color-lighter);
border-radius: 8px;
border-radius: 4px;
transition: all 0.3s ease;
&:hover {
border-color: var(--el-color-primary);
box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
transform: translateX(4px);
box-shadow: 0 2px 6px rgb(0 0 0 / 6%);
transform: translateX(2px);
}
.balance-icon {
flex-shrink: 0;
font-size: 32px;
font-size: 20px;
line-height: 1;
}
@@ -3839,17 +3839,17 @@
display: flex;
flex: 1;
flex-direction: column;
gap: 6px;
gap: 2px;
.balance-label {
font-size: 13px;
font-size: 11px;
font-weight: 500;
color: var(--el-text-color-secondary);
}
.balance-value {
font-size: 22px;
font-weight: 700;
font-size: 16px;
font-weight: 600;
line-height: 1.2;
color: var(--el-text-color-primary);
}

View File

@@ -1,542 +0,0 @@
<template>
<div class="page-content">
<!-- 搜索和筛选区 -->
<ElRow :gutter="12">
<ElCol :xs="24" :sm="12" :lg="6">
<ElInput v-model="searchQuery" placeholder="申请单号/ICCID" clearable />
</ElCol>
<ElCol :xs="24" :sm="12" :lg="6">
<ElSelect v-model="statusFilter" placeholder="状态筛选" clearable style="width: 100%">
<ElOption label="全部" value="" />
<ElOption label="待处理" value="pending" />
<ElOption label="处理中" value="processing" />
<ElOption label="已完成" value="completed" />
<ElOption label="已拒绝" value="rejected" />
</ElSelect>
</ElCol>
<ElCol :xs="24" :sm="12" :lg="6">
<ElDatePicker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
style="width: 100%"
/>
</ElCol>
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
<ElButton v-ripple @click="handleSearch">搜索</ElButton>
<ElButton v-ripple @click="exportData">导出</ElButton>
</ElCol>
</ElRow>
<!-- 统计卡片 -->
<ElRow :gutter="20" style="margin: 20px 0">
<ElCol :xs="24" :sm="12" :lg="6">
<ElCard shadow="hover" class="stat-card">
<div class="stat-content">
<div class="stat-label">待处理</div>
<div class="stat-value" style="color: var(--el-color-warning)">{{
statistics.pending
}}</div>
</div>
<el-icon class="stat-icon" style="color: var(--el-color-warning)"><Clock /></el-icon>
</ElCard>
</ElCol>
<ElCol :xs="24" :sm="12" :lg="6">
<ElCard shadow="hover" class="stat-card">
<div class="stat-content">
<div class="stat-label">处理中</div>
<div class="stat-value" style="color: var(--el-color-primary)">{{
statistics.processing
}}</div>
</div>
<el-icon class="stat-icon" style="color: var(--el-color-primary)"><Loading /></el-icon>
</ElCard>
</ElCol>
<ElCol :xs="24" :sm="12" :lg="6">
<ElCard shadow="hover" class="stat-card">
<div class="stat-content">
<div class="stat-label">已完成</div>
<div class="stat-value" style="color: var(--el-color-success)">{{
statistics.completed
}}</div>
</div>
<el-icon class="stat-icon" style="color: var(--el-color-success)"
><CircleCheck
/></el-icon>
</ElCard>
</ElCol>
<ElCol :xs="24" :sm="12" :lg="6">
<ElCard shadow="hover" class="stat-card">
<div class="stat-content">
<div class="stat-label">已拒绝</div>
<div class="stat-value" style="color: var(--el-color-danger)">{{
statistics.rejected
}}</div>
</div>
<el-icon class="stat-icon" style="color: var(--el-color-danger)"><CircleClose /></el-icon>
</ElCard>
</ElCol>
</ElRow>
<!-- 换卡申请列表 -->
<ArtTable :data="filteredData" index>
<template #default>
<ElTableColumn label="申请单号" prop="requestNo" width="180" />
<ElTableColumn label="旧卡ICCID" prop="oldIccid" width="200" />
<ElTableColumn label="申请人" prop="applicant" width="120" />
<ElTableColumn label="联系电话" prop="phone" width="130" />
<ElTableColumn label="换卡原因" prop="reason" min-width="180" show-overflow-tooltip />
<ElTableColumn label="新卡ICCID" prop="newIccid" width="200">
<template #default="scope">
<ElTag v-if="scope.row.newIccid" type="success" size="small">
{{ scope.row.newIccid }}
</ElTag>
<span v-else style="color: var(--el-text-color-secondary)">待填充</span>
</template>
</ElTableColumn>
<ElTableColumn label="状态" prop="status" width="100">
<template #default="scope">
<ElTag v-if="scope.row.status === 'pending'" type="warning">待处理</ElTag>
<ElTag v-else-if="scope.row.status === 'processing'" type="primary">处理中</ElTag>
<ElTag v-else-if="scope.row.status === 'completed'" type="success">已完成</ElTag>
<ElTag v-else type="danger">已拒绝</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="申请时间" prop="applyTime" width="180" />
<ElTableColumn fixed="right" label="操作" width="240">
<template #default="scope">
<el-button link :icon="View" @click="viewDetail(scope.row)">详情</el-button>
<el-button
v-if="scope.row.status === 'pending'"
link
type="primary"
@click="handleProcess(scope.row)"
>
处理
</el-button>
<el-button
v-if="scope.row.status === 'processing'"
link
type="success"
@click="fillNewIccid(scope.row)"
>
填充新卡
</el-button>
<el-button
v-if="scope.row.status === 'pending'"
link
type="danger"
@click="handleReject(scope.row)"
>
拒绝
</el-button>
</template>
</ElTableColumn>
</template>
</ArtTable>
<!-- 详情对话框 -->
<ElDialog v-model="detailDialogVisible" title="换卡申请详情" width="800px" align-center>
<ElDescriptions :column="2" border>
<ElDescriptionsItem label="申请单号">{{ currentRequest.requestNo }}</ElDescriptionsItem>
<ElDescriptionsItem label="申请人">{{ currentRequest.applicant }}</ElDescriptionsItem>
<ElDescriptionsItem label="联系电话">{{ currentRequest.phone }}</ElDescriptionsItem>
<ElDescriptionsItem label="申请时间">{{ currentRequest.applyTime }}</ElDescriptionsItem>
<ElDescriptionsItem label="旧卡ICCID" :span="2">
<ElTag type="warning">{{ currentRequest.oldIccid }}</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="新卡ICCID" :span="2">
<ElTag v-if="currentRequest.newIccid" type="success">{{ currentRequest.newIccid }}</ElTag>
<span v-else style="color: var(--el-text-color-secondary)">待填充</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="换卡原因" :span="2">
{{ currentRequest.reason }}
</ElDescriptionsItem>
<ElDescriptionsItem label="详细说明" :span="2">
{{ currentRequest.description || '无' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="状态">
<ElTag v-if="currentRequest.status === 'pending'" type="warning">待处理</ElTag>
<ElTag v-else-if="currentRequest.status === 'processing'" type="primary">处理中</ElTag>
<ElTag v-else-if="currentRequest.status === 'completed'" type="success">已完成</ElTag>
<ElTag v-else type="danger">已拒绝</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="处理人">
{{ currentRequest.processor || '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="处理时间" :span="2">
{{ currentRequest.processTime || '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem v-if="currentRequest.rejectReason" label="拒绝原因" :span="2">
<span style="color: var(--el-color-danger)">{{ currentRequest.rejectReason }}</span>
</ElDescriptionsItem>
</ElDescriptions>
<template #footer>
<ElButton @click="detailDialogVisible = false">关闭</ElButton>
</template>
</ElDialog>
<!-- 填充新卡对话框 -->
<ElDialog v-model="fillDialogVisible" title="填充新卡ICCID" width="500px" align-center>
<ElForm ref="fillFormRef" :model="fillForm" :rules="fillRules" label-width="100px">
<ElFormItem label="旧卡ICCID">
<ElInput :value="currentRequest.oldIccid" disabled />
</ElFormItem>
<ElFormItem label="新卡ICCID" prop="newIccid">
<ElInput v-model="fillForm.newIccid" placeholder="请输入新卡ICCID" maxlength="20" />
</ElFormItem>
<ElFormItem label="验证新卡">
<ElButton @click="validateNewIccid">验证ICCID</ElButton>
<div v-if="validationResult" style="margin-top: 8px">
<ElTag v-if="validationResult === 'success'" type="success" size="small">
验证通过该卡可用
</ElTag>
<ElTag v-else type="danger" size="small"> 验证失败{{ validationMessage }} </ElTag>
</div>
</ElFormItem>
<ElFormItem label="备注">
<ElInput
v-model="fillForm.remark"
type="textarea"
:rows="3"
placeholder="请输入备注信息"
/>
</ElFormItem>
<ElAlert type="info" :closable="false">
填充新卡后系统将自动完成换卡操作旧卡将被停用
</ElAlert>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="fillDialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleFillSubmit">确认填充</ElButton>
</div>
</template>
</ElDialog>
<!-- 拒绝对话框 -->
<ElDialog v-model="rejectDialogVisible" title="拒绝换卡申请" width="500px" align-center>
<ElForm ref="rejectFormRef" :model="rejectForm" :rules="rejectRules" label-width="100px">
<ElFormItem label="拒绝原因" prop="reason">
<ElInput
v-model="rejectForm.reason"
type="textarea"
:rows="4"
placeholder="请输入拒绝原因"
/>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="rejectDialogVisible = false">取消</ElButton>
<ElButton type="danger" @click="handleRejectSubmit">确认拒绝</ElButton>
</div>
</template>
</ElDialog>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
View,
Clock,
Loading as LoadingIcon,
CircleCheck,
CircleClose
} from '@element-plus/icons-vue'
import type { FormInstance, FormRules } from 'element-plus'
defineOptions({ name: 'CardReplacementRequest' })
interface ReplacementRequest {
id: string
requestNo: string
oldIccid: string
newIccid?: string
applicant: string
phone: string
reason: string
description?: string
status: 'pending' | 'processing' | 'completed' | 'rejected'
applyTime: string
processor?: string
processTime?: string
rejectReason?: string
}
const searchQuery = ref('')
const statusFilter = ref('')
const dateRange = ref<[Date, Date] | null>(null)
const detailDialogVisible = ref(false)
const fillDialogVisible = ref(false)
const rejectDialogVisible = ref(false)
const fillFormRef = ref<FormInstance>()
const rejectFormRef = ref<FormInstance>()
const validationResult = ref<string>('')
const validationMessage = ref('')
const statistics = reactive({
pending: 15,
processing: 8,
completed: 102,
rejected: 5
})
const fillForm = reactive({
newIccid: '',
remark: ''
})
const fillRules = reactive<FormRules>({
newIccid: [
{ required: true, message: '请输入新卡ICCID', trigger: 'blur' },
{ len: 20, message: 'ICCID长度必须为20位', trigger: 'blur' }
]
})
const rejectForm = reactive({
reason: ''
})
const rejectRules = reactive<FormRules>({
reason: [{ required: true, message: '请输入拒绝原因', trigger: 'blur' }]
})
const mockData = ref<ReplacementRequest[]>([
{
id: '1',
requestNo: 'REP202601090001',
oldIccid: '89860123456789012345',
applicant: '张三',
phone: '13800138000',
reason: '卡片损坏',
description: '卡片物理损坏无法使用',
status: 'pending',
applyTime: '2026-01-09 09:30:00'
},
{
id: '2',
requestNo: 'REP202601080002',
oldIccid: '89860123456789012346',
newIccid: '89860123456789012350',
applicant: '李四',
phone: '13900139000',
reason: '信号不稳定',
description: '长期信号不稳定,影响使用',
status: 'processing',
applyTime: '2026-01-08 14:20:00',
processor: 'admin'
},
{
id: '3',
requestNo: 'REP202601070003',
oldIccid: '89860123456789012347',
newIccid: '89860123456789012351',
applicant: '王五',
phone: '13700137000',
reason: '卡片丢失',
description: '卡片意外丢失',
status: 'completed',
applyTime: '2026-01-07 10:00:00',
processor: 'admin',
processTime: '2026-01-07 15:30:00'
},
{
id: '4',
requestNo: 'REP202601060004',
oldIccid: '89860123456789012348',
applicant: '赵六',
phone: '13600136000',
reason: '套餐到期',
description: '套餐到期需要换新卡',
status: 'rejected',
applyTime: '2026-01-06 11:00:00',
processor: 'admin',
processTime: '2026-01-06 12:00:00',
rejectReason: '套餐到期应该续费而不是换卡'
}
])
const currentRequest = ref<ReplacementRequest>({
id: '',
requestNo: '',
oldIccid: '',
applicant: '',
phone: '',
reason: '',
status: 'pending',
applyTime: ''
})
const filteredData = computed(() => {
let data = mockData.value
if (searchQuery.value) {
data = data.filter(
(item) =>
item.requestNo.includes(searchQuery.value) ||
item.oldIccid.includes(searchQuery.value) ||
(item.newIccid && item.newIccid.includes(searchQuery.value))
)
}
if (statusFilter.value) {
data = data.filter((item) => item.status === statusFilter.value)
}
return data
})
const handleSearch = () => {}
const exportData = () => {
ElMessage.success('数据导出中...')
}
const viewDetail = (row: ReplacementRequest) => {
currentRequest.value = { ...row }
detailDialogVisible.value = true
}
const handleProcess = (row: ReplacementRequest) => {
ElMessageBox.confirm('确定要处理该换卡申请吗?', '处理确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'info'
}).then(() => {
row.status = 'processing'
row.processor = 'admin'
ElMessage.success('已标记为处理中')
})
}
const fillNewIccid = (row: ReplacementRequest) => {
currentRequest.value = { ...row }
fillForm.newIccid = ''
fillForm.remark = ''
validationResult.value = ''
fillDialogVisible.value = true
}
const validateNewIccid = () => {
if (!fillForm.newIccid) {
ElMessage.warning('请先输入新卡ICCID')
return
}
if (fillForm.newIccid.length !== 20) {
validationResult.value = 'error'
validationMessage.value = 'ICCID长度必须为20位'
return
}
// 模拟验证
setTimeout(() => {
const exists = mockData.value.some((item) => item.oldIccid === fillForm.newIccid)
if (exists) {
validationResult.value = 'error'
validationMessage.value = '该ICCID已被使用'
} else {
validationResult.value = 'success'
validationMessage.value = ''
ElMessage.success('验证通过')
}
}, 500)
}
const handleFillSubmit = async () => {
if (!fillFormRef.value) return
if (validationResult.value !== 'success') {
ElMessage.warning('请先验证新卡ICCID')
return
}
await fillFormRef.value.validate((valid) => {
if (valid) {
const index = mockData.value.findIndex((item) => item.id === currentRequest.value.id)
if (index !== -1) {
mockData.value[index].newIccid = fillForm.newIccid
mockData.value[index].status = 'completed'
mockData.value[index].processTime = new Date().toLocaleString('zh-CN')
statistics.processing--
statistics.completed++
}
fillDialogVisible.value = false
ElMessage.success('新卡填充成功,换卡操作已完成')
}
})
}
const handleReject = (row: ReplacementRequest) => {
currentRequest.value = { ...row }
rejectForm.reason = ''
rejectDialogVisible.value = true
}
const handleRejectSubmit = async () => {
if (!rejectFormRef.value) return
await rejectFormRef.value.validate((valid) => {
if (valid) {
const index = mockData.value.findIndex((item) => item.id === currentRequest.value.id)
if (index !== -1) {
mockData.value[index].status = 'rejected'
mockData.value[index].rejectReason = rejectForm.reason
mockData.value[index].processor = 'admin'
mockData.value[index].processTime = new Date().toLocaleString('zh-CN')
statistics.pending--
statistics.rejected++
}
rejectDialogVisible.value = false
ElMessage.success('已拒绝该换卡申请')
}
})
}
</script>
<style lang="scss" scoped>
.page-content {
.stat-card {
:deep(.el-card__body) {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px;
}
.stat-content {
.stat-label {
margin-bottom: 8px;
font-size: 14px;
color: var(--el-text-color-secondary);
}
.stat-value {
font-size: 24px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
.stat-icon {
font-size: 40px;
opacity: 0.6;
}
}
}
</style>

View File

@@ -439,46 +439,6 @@
</div>
</ElDialog>
<!-- 设置限速对话框 -->
<ElDialog v-model="speedLimitDialogVisible" title="设置限速" width="50%">
<ElForm
ref="speedLimitFormRef"
:model="speedLimitForm"
:rules="speedLimitRules"
label-width="120px"
>
<ElFormItem label="设备号">
<span style="font-weight: bold; color: #409eff">{{ currentOperatingImei }}</span>
</ElFormItem>
<ElFormItem label="下行速率" prop="download_speed">
<ElInputNumber
v-model="speedLimitForm.download_speed"
:min="1"
:step="128"
controls-position="right"
style="width: 100%"
/>
<div style="margin-top: 4px; font-size: 12px; color: #909399">单位: KB/s</div>
</ElFormItem>
<ElFormItem label="上行速率" prop="upload_speed">
<ElInputNumber
v-model="speedLimitForm.upload_speed"
:min="1"
:step="128"
controls-position="right"
style="width: 100%"
/>
<div style="margin-top: 4px; font-size: 12px; color: #909399">单位: KB/s</div>
</ElFormItem>
</ElForm>
<template #footer>
<ElButton @click="speedLimitDialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleConfirmSpeedLimit" :loading="speedLimitLoading">
确认设置
</ElButton>
</template>
</ElDialog>
<!-- 切换SIM卡对话框 -->
<ElDialog v-model="switchCardDialogVisible" title="切换SIM卡" width="600px">
<div v-if="loadingDeviceCards" style="padding: 40px; text-align: center">
@@ -604,6 +564,7 @@
<script setup lang="ts">
import { h } from 'vue'
import { useRouter } from 'vue-router'
import { RoutesAlias } from '@/router/routesAlias'
import {
DeviceService,
ShopService,
@@ -615,10 +576,8 @@
ElMessage,
ElMessageBox,
ElTag,
ElSwitch,
ElIcon,
ElTreeSelect,
ElInputNumber,
ElRadioGroup,
ElRadio
} from 'element-plus'
@@ -634,9 +593,7 @@
import type { SearchFormItem } from '@/types'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { useAuth } from '@/composables/useAuth'
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
import { formatDateTime } from '@/utils/business/format'
import { CommonStatus, getStatusText } from '@/config/constants'
import type { PackageSeriesResponse } from '@/types/api'
defineOptions({ name: 'DeviceList' })
@@ -694,23 +651,6 @@
})
// 设备操作相关对话框
const speedLimitDialogVisible = ref(false)
const speedLimitLoading = ref(false)
const speedLimitFormRef = ref<FormInstance>()
const speedLimitForm = reactive({
download_speed: 1024,
upload_speed: 512
})
const speedLimitRules = reactive<FormRules>({
download_speed: [
{ required: true, message: '请输入下行速率', trigger: 'blur' },
{ type: 'number', min: 1, message: '速率不能小于1KB/s', trigger: 'blur' }
],
upload_speed: [
{ required: true, message: '请输入上行速率', trigger: 'blur' },
{ type: 'number', min: 1, message: '速率不能小于1KB/s', trigger: 'blur' }
]
})
const currentOperatingImei = ref<string>('') // 用于存储当前操作设备的IMEI
const currentOperatingDevice = ref<Device | null>(null)
@@ -872,7 +812,7 @@
const goToDeviceSearchDetail = (deviceNo: string) => {
if (hasAuth('device:view_detail')) {
router.push({
path: '/asset-management/asset-information',
path: RoutesAlias.AssetInformation,
query: {
virtual_no: deviceNo
}
@@ -975,7 +915,7 @@
// 重置表单
bindCardForm.iot_card_id = undefined
bindCardForm.slot_position = 1
bindCardFormRef.value.resetFields()
bindCardFormRef.value?.resetFields()
// 重新加载卡列表
await loadDeviceCards(currentDeviceDetail.value.virtual_no)
// 刷新设备详情以更新绑定卡数量
@@ -1016,7 +956,7 @@
currentDeviceDetail.value = detailRes.data
}
} else {
ElMessage.error(res.message || '解绑失败')
ElMessage.error(res.msg || '解绑失败')
}
} catch (error: any) {
console.error('解绑卡失败:', error)
@@ -1027,28 +967,6 @@
})
}
// 获取卡状态标签类型
const getCardStatusTagType = (status: number) => {
const typeMap: Record<number, any> = {
1: 'info', // 在库
2: 'warning', // 已分销
3: 'success', // 已激活
4: 'danger' // 已停用
}
return typeMap[status] || 'info'
}
// 获取卡状态文本
const getCardStatusText = (status: number) => {
const textMap: Record<number, string> = {
1: '在库',
2: '已分销',
3: '已激活',
4: '已停用'
}
return textMap[status] || '未知'
}
// 获取网络状态标签类型
const getNetworkStatusTagType = (networkStatus: number) => {
// 0: 停机 (danger 红色)
@@ -1581,22 +1499,13 @@
handleResetDevice(imei)
break
}
case 'speed-limit': {
const imei = device?.imei || (await getDeviceImei(deviceNo))
if (!imei) {
ElMessage.error('无法获取设备IMEI,无法执行限速操作')
return
}
showSpeedLimitDialog(imei)
break
}
case 'switch-card': {
const imei = device?.imei || (await getDeviceImei(deviceNo))
if (!imei) {
ElMessage.error('无法获取设备IMEI,无法执行切卡操作')
return
}
showSwitchCardDialog(imei)
await showSwitchCardDialog(imei)
break
}
case 'set-wifi': {
@@ -1612,7 +1521,7 @@
handleManualDeactivateDevice()
break
case 'delete':
handleDeleteDeviceByNo(deviceNo)
await handleDeleteDeviceByNo(deviceNo)
break
}
}
@@ -1649,7 +1558,7 @@
const res = await AssetService.deactivateAsset(device.virtual_no)
if (res.code === 0) {
ElMessage.success('手动停用成功')
loadDeviceList()
await getTableData()
}
} catch (error: any) {
console.error('手动停用失败:', error)
@@ -1684,7 +1593,7 @@
if (res.code === 0) {
ElMessage.success('重启指令已发送')
} else {
ElMessage.error(res.message || '重启失败')
ElMessage.error(res.msg || '重启失败')
}
} catch (error: any) {
console.error('重启设备失败:', error)
@@ -1712,7 +1621,7 @@
if (res.code === 0) {
ElMessage.success('恢复出厂设置指令已发送')
} else {
ElMessage.error(res.message || '操作失败')
ElMessage.error(res.msg || '操作失败')
}
} catch (error: any) {
console.error('恢复出厂设置失败:', error)
@@ -1723,41 +1632,6 @@
})
}
// 显示设置限速对话框
const showSpeedLimitDialog = (imei: string) => {
currentOperatingImei.value = imei
speedLimitForm.download_speed = 1024
speedLimitForm.upload_speed = 512
speedLimitDialogVisible.value = true
}
// 确认设置限速
const handleConfirmSpeedLimit = async () => {
if (!speedLimitFormRef.value) return
await speedLimitFormRef.value.validate(async (valid) => {
if (valid) {
speedLimitLoading.value = true
try {
const res = await DeviceService.setSpeedLimit(currentOperatingImei.value, {
download_speed: speedLimitForm.download_speed,
upload_speed: speedLimitForm.upload_speed
})
if (res.code === 0) {
ElMessage.success('限速设置成功')
speedLimitDialogVisible.value = false
} else {
ElMessage.error(res.message || '设置失败')
}
} catch (error: any) {
console.error('设置限速失败:', error)
} finally {
speedLimitLoading.value = false
}
}
})
}
// 显示切换SIM卡对话框
const showSwitchCardDialog = async (imei: string) => {
currentOperatingImei.value = imei
@@ -1812,7 +1686,7 @@
ElMessage.success('切换SIM卡指令已发送')
switchCardDialogVisible.value = false
} else {
ElMessage.error(res.message || '切换失败')
ElMessage.error(res.msg || '切换失败')
}
} catch (error: any) {
console.error('切换SIM卡失败:', error)
@@ -1849,7 +1723,7 @@
ElMessage.success('WiFi设置成功')
setWiFiDialogVisible.value = false
} else {
ElMessage.error(res.message || '设置失败')
ElMessage.error(res.msg || '设置失败')
}
} catch (error: any) {
console.error('设置WiFi失败:', error)

View File

@@ -1,215 +0,0 @@
<template>
<div class="device-search-page">
<!-- 搜索区域 -->
<ElCard shadow="never" class="search-card">
<template #header>
<div class="card-header">
<span>设备查询</span>
</div>
</template>
<div class="search-content">
<ElForm :model="searchForm" label-width="100px">
<ElFormItem label="设备号(IMEI)">
<ElInput
v-model="searchForm.imei"
placeholder="请输入设备号(IMEI)"
clearable
@keyup.enter="handleSearch"
>
<template #append>
<ElButton type="primary" :loading="loading" @click="handleSearch"> 查询 </ElButton>
</template>
</ElInput>
</ElFormItem>
</ElForm>
</div>
</ElCard>
<!-- 设备详情区域 -->
<ElCard v-if="deviceDetail" shadow="never" class="detail-card" style="margin-top: 16px">
<template #header>
<div class="card-header">
<span>设备详情</span>
</div>
</template>
<ElDescriptions :column="3" border>
<ElDescriptionsItem label="设备ID">{{ deviceDetail.id }}</ElDescriptionsItem>
<ElDescriptionsItem label="设备号" :span="2">{{
deviceDetail.virtual_no
}}</ElDescriptionsItem>
<ElDescriptionsItem label="设备名称">{{
deviceDetail.device_name || '--'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="设备型号">{{
deviceDetail.device_model || '--'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="设备类型">{{
deviceDetail.device_type || '--'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="制造商">{{
deviceDetail.manufacturer || '--'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="最大插槽数">{{ deviceDetail.max_sim_slots }}</ElDescriptionsItem>
<ElDescriptionsItem label="已绑定卡数量">{{
deviceDetail.bound_card_count
}}</ElDescriptionsItem>
<ElDescriptionsItem label="状态">
<ElTag :type="getStatusTagType(deviceDetail.status)">
{{ deviceDetail.status_name }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="在线状态">
<ElTag :type="getOnlineStatusTagType(deviceDetail.online_status)">
{{ getOnlineStatusText(deviceDetail.online_status) }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="店铺名称">{{
deviceDetail.shop_name || '--'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="固件版本">{{
deviceDetail.software_version || '--'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="切卡模式">{{
getSwitchModeText(deviceDetail.switch_mode)
}}</ElDescriptionsItem>
<ElDescriptionsItem label="批次号">{{ deviceDetail.batch_no }}</ElDescriptionsItem>
<ElDescriptionsItem label="最后在线时间">{{
deviceDetail.last_online_time || '--'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="最后同步时间">{{
deviceDetail.last_gateway_sync_at || '--'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="激活时间">{{
deviceDetail.activated_at || '--'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间">{{ deviceDetail.created_at }}</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间">{{ deviceDetail.updated_at }}</ElDescriptionsItem>
<ElDescriptionsItem></ElDescriptionsItem>
</ElDescriptions>
</ElCard>
<!-- 空状态 -->
<ElEmpty v-if="searched && !deviceDetail && !loading" description="未找到相关设备信息" />
</div>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { AssetService } from '@/api/modules'
defineOptions({ name: 'DeviceSearch' })
const loading = ref(false)
const searched = ref(false)
const searchForm = reactive({
imei: ''
})
const deviceDetail = ref<any>(null)
// 查询设备详情
const handleSearch = async () => {
if (!searchForm.imei.trim()) {
ElMessage.warning('请输入设备号(IMEI)')
return
}
loading.value = true
searched.value = true
deviceDetail.value = null
try {
const res = await AssetService.resolveAsset(searchForm.imei.trim())
if (res.code === 0 && res.data) {
deviceDetail.value = res.data
ElMessage.success('查询成功')
} else {
ElMessage.error(res.msg || '查询失败')
}
} catch (error: any) {
console.error('查询设备详情失败:', error)
console.log(error?.message || '查询失败,请检查设备号是否正确')
} finally {
loading.value = false
}
}
// 获取状态标签类型
const getStatusTagType = (status: number) => {
const typeMap: Record<number, any> = {
1: 'info', // 在库
2: 'warning', // 已分销
3: 'success', // 已激活
4: 'danger' // 已停用
}
return typeMap[status] || 'info'
}
// 获取在线状态标签类型
const getOnlineStatusTagType = (status?: number) => {
const typeMap: Record<number, any> = {
0: 'info', // 未知
1: 'success', // 在线
2: 'danger' // 离线
}
return typeMap[status ?? 0] || 'info'
}
// 获取在线状态文本
const getOnlineStatusText = (status?: number) => {
const textMap: Record<number, string> = {
0: '未知',
1: '在线',
2: '离线'
}
return textMap[status ?? 0] || '未知'
}
// 获取切卡模式文本
const getSwitchModeText = (mode?: string) => {
const modeMap: Record<string, string> = {
'0': '自动',
'1': '手动'
}
return modeMap[mode || ''] || '--'
}
</script>
<style lang="scss" scoped>
.device-search-page {
padding: 16px;
.card-header {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.search-content {
max-width: 800px;
}
.detail-card {
animation: fadeIn 0.3s ease-in;
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -31,28 +31,16 @@
:pageSize="pagination.page_size"
:total="pagination.total"
:marginTop="10"
:actions="getActions"
:actionsWidth="120"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
@row-contextmenu="handleRowContextMenu"
@cell-mouse-enter="handleCellMouseEnter"
@cell-mouse-leave="handleCellMouseLeave"
>
<template #default>
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
</template>
</ArtTable>
<!-- 鼠标悬浮提示 -->
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
<!-- 右键菜单 -->
<ArtMenuRight
ref="contextMenuRef"
:menu-items="contextMenuItems"
:menu-width="140"
@select="handleContextMenuSelect"
/>
<!-- 创建换货单对话框 -->
<ElDialog
v-model="createDialogVisible"
@@ -217,25 +205,15 @@
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 { RoutesAlias } from '@/router/routesAlias'
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
defineOptions({ name: 'ExchangeManagement' })
const { hasAuth } = useAuth()
const router = useRouter()
// 右键菜单相关
const { showContextMenuHint, hintPosition, handleCellMouseEnter, handleCellMouseLeave } =
useTableContextMenu()
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
const currentExchangeRow = ref<ExchangeResponse | null>(null)
const loading = ref(false)
const shipDialogVisible = ref(false)
const shipLoading = ref(false)
@@ -621,17 +599,18 @@
router.push(`${RoutesAlias.ExchangeDetail}/${row.id}`)
}
// 右键菜单项
const contextMenuItems = computed<MenuItemType[]>(() => {
const items: MenuItemType[] = []
const status = currentExchangeRow.value?.status
// 获取操作按钮
const getActions = (row: ExchangeResponse) => {
const actions: any[] = []
const status = row.status
// 状态1待填写信息取消换货
if (status === 1) {
if (hasAuth('exchange:cancel')) {
items.push({
key: 'cancel',
label: '取消换货'
actions.push({
label: '取消换货',
handler: () => handleCancelExchange(row),
type: 'danger'
})
}
}
@@ -639,15 +618,17 @@
// 状态2待发货发货、取消换货
if (status === 2) {
if (hasAuth('exchange:ship')) {
items.push({
key: 'ship',
label: '发货'
actions.push({
label: '发货',
handler: () => handleShipExchange(row),
type: 'primary'
})
}
if (hasAuth('exchange:cancel')) {
items.push({
key: 'cancel',
label: '取消换货'
actions.push({
label: '取消换货',
handler: () => handleCancelExchange(row),
type: 'danger'
})
}
}
@@ -655,9 +636,10 @@
// 状态3已发货待确认确认完成
if (status === 3) {
if (hasAuth('exchange:complete')) {
items.push({
key: 'complete',
label: '确认完成'
actions.push({
label: '确认完成',
handler: () => handleCompleteExchange(row),
type: 'success'
})
}
}
@@ -665,42 +647,15 @@
// 状态4已完成旧资产转新
if (status === 4) {
if (hasAuth('exchange:renew')) {
items.push({
key: 'renew',
label: '旧资产转新'
actions.push({
label: '旧资产转新',
handler: () => handleRenewExchange(row),
type: 'warning'
})
}
}
return items
})
// 处理右键菜单
const handleRowContextMenu = (row: ExchangeResponse, _column: any, event: MouseEvent) => {
event.preventDefault()
event.stopPropagation()
currentExchangeRow.value = row
contextMenuRef.value?.show(event)
}
// 处理右键菜单选择
const handleContextMenuSelect = (item: MenuItemType) => {
if (!currentExchangeRow.value) return
switch (item.key) {
case 'cancel':
handleCancelExchange(currentExchangeRow.value)
break
case 'ship':
handleShipExchange(currentExchangeRow.value)
break
case 'complete':
handleCompleteExchange(currentExchangeRow.value)
break
case 'renew':
handleRenewExchange(currentExchangeRow.value)
break
}
return actions
}
// 取消换货

View File

@@ -572,6 +572,7 @@
<script setup lang="ts">
import { h } from 'vue'
import { useRouter } from 'vue-router'
import { RoutesAlias } from '@/router/routesAlias'
import { CardService, ShopService, PackageSeriesService, AssetService } from '@/api/modules'
import { ElMessage, ElTag, ElIcon, ElMessageBox } from 'element-plus'
import { Loading } from '@element-plus/icons-vue'
@@ -906,7 +907,7 @@
const goToCardDetail = (iccid: string) => {
if (hasAuth('iot_card:view_detail')) {
router.push({
path: '/asset-management/asset-information',
path: RoutesAlias.AssetInformation,
query: {
iccid: iccid
}

View File

@@ -1,217 +0,0 @@
<template>
<div class="card-search-page">
<!-- 搜索区域 -->
<ElCard shadow="never" class="search-card">
<template #header>
<div class="card-header">
<span>单卡查询</span>
</div>
</template>
<div class="search-content">
<ElForm :model="searchForm" label-width="100px">
<ElFormItem label="ICCID">
<ElInput
v-model="searchForm.iccid"
placeholder="请输入ICCID"
clearable
@keyup.enter="handleSearch"
>
<template #append>
<ElButton type="primary" :loading="loading" @click="handleSearch"> 查询 </ElButton>
</template>
</ElInput>
</ElFormItem>
</ElForm>
</div>
</ElCard>
<!-- 卡片详情区域 -->
<ElCard v-if="cardDetail" shadow="never" class="detail-card" style="margin-top: 16px">
<template #header>
<div class="card-header">
<span>卡片详情</span>
</div>
</template>
<ElDescriptions :column="3" border>
<ElDescriptionsItem label="卡ID">{{ cardDetail.id }}</ElDescriptionsItem>
<ElDescriptionsItem label="ICCID" :span="2">{{ cardDetail.iccid }}</ElDescriptionsItem>
<ElDescriptionsItem label="IMSI">{{ cardDetail.imsi || '--' }}</ElDescriptionsItem>
<ElDescriptionsItem label="卡接入号">{{ cardDetail.msisdn || '--' }}</ElDescriptionsItem>
<ElDescriptionsItem label="运营商">{{ cardDetail.carrier_name }}</ElDescriptionsItem>
<ElDescriptionsItem label="运营商类型">{{
getCarrierTypeText(cardDetail.carrier_type)
}}</ElDescriptionsItem>
<ElDescriptionsItem label="卡类型">{{ cardDetail.card_type }}</ElDescriptionsItem>
<ElDescriptionsItem label="卡业务类型">{{
getCardCategoryText(cardDetail.card_category)
}}</ElDescriptionsItem>
<ElDescriptionsItem label="状态">
<ElTag :type="getStatusTagType(cardDetail.status)">
{{ getStatusText(cardDetail.status) }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="激活状态">
<ElTag :type="cardDetail.activation_status === 1 ? 'success' : 'info'">
{{ cardDetail.activation_status === 1 ? '已激活' : '未激活' }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="网络状态">
<ElTag :type="cardDetail.network_status === 1 ? 'success' : 'danger'">
{{ cardDetail.network_status === 1 ? '开机' : '停机' }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="实名状态">
<ElTag :type="cardDetail.real_name_status === 1 ? 'success' : 'warning'">
{{ cardDetail.real_name_status === 1 ? '已实名' : '未实名' }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="批次号">{{ cardDetail.batch_no }}</ElDescriptionsItem>
<ElDescriptionsItem label="供应商">{{ cardDetail.supplier || '--' }}</ElDescriptionsItem>
<ElDescriptionsItem label="店铺名称">{{ cardDetail.shop_name || '--' }}</ElDescriptionsItem>
<ElDescriptionsItem label="成本价">{{
formatPrice(cardDetail.cost_price)
}}</ElDescriptionsItem>
<ElDescriptionsItem label="分销价">{{
formatPrice(cardDetail.distribute_price)
}}</ElDescriptionsItem>
<ElDescriptionsItem label="累计流量使用"
>{{ cardDetail.data_usage_mb }} MB</ElDescriptionsItem
>
<ElDescriptionsItem label="激活时间">{{
cardDetail.activated_at || '--'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间">{{ cardDetail.created_at }}</ElDescriptionsItem>
</ElDescriptions>
</ElCard>
<!-- 空状态 -->
<ElEmpty v-if="searched && !cardDetail && !loading" description="未找到相关卡片信息" />
</div>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { CardService } from '@/api/modules/card'
defineOptions({ name: 'CardSearch' })
const loading = ref(false)
const searched = ref(false)
const searchForm = reactive({
iccid: ''
})
const cardDetail = ref<any>(null)
// 查询卡片详情
const handleSearch = async () => {
if (!searchForm.iccid.trim()) {
ElMessage.warning('请输入ICCID')
return
}
loading.value = true
searched.value = true
cardDetail.value = null
try {
const res = await CardService.getIotCardDetailByIccid(searchForm.iccid.trim())
if (res.code === 0 && res.data) {
cardDetail.value = res.data
ElMessage.success('查询成功')
} else {
ElMessage.error(res.message || '查询失败')
}
} catch (error: any) {
console.error('查询卡片详情失败:', error)
} finally {
loading.value = false
}
}
// 获取运营商类型文本
const getCarrierTypeText = (type: string) => {
const typeMap: Record<string, string> = {
CMCC: '中国移动',
CUCC: '中国联通',
CTCC: '中国电信',
CBN: '中国广电'
}
return typeMap[type] || type
}
// 获取卡业务类型文本
const getCardCategoryText = (category: string) => {
const categoryMap: Record<string, string> = {
normal: '普通卡',
industry: '行业卡'
}
return categoryMap[category] || category
}
// 获取状态文本
const getStatusText = (status: number) => {
const statusMap: Record<number, string> = {
1: '在库',
2: '已分销',
3: '已激活',
4: '已停用'
}
return statusMap[status] || '未知'
}
// 获取状态标签类型
const getStatusTagType = (status: number) => {
const typeMap: Record<number, any> = {
1: 'info',
2: 'warning',
3: 'success',
4: 'danger'
}
return typeMap[status] || 'info'
}
// 格式化价格(分转元)
const formatPrice = (price: number) => {
return `¥${(price / 100).toFixed(2)}`
}
</script>
<style lang="scss" scoped>
.card-search-page {
padding: 16px;
.card-header {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.search-content {
max-width: 800px;
}
.detail-card {
animation: fadeIn 0.3s ease-in;
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@@ -39,28 +39,15 @@
:pageSize="pagination.pageSize"
:total="pagination.total"
:marginTop="10"
:row-class-name="getRowClassName"
:actions="getActions"
:actionsWidth="120"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
@row-contextmenu="handleRowContextMenu"
@cell-mouse-enter="handleCellMouseEnter"
@cell-mouse-leave="handleCellMouseLeave"
>
<template #default>
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
</template>
</ArtTable>
<!-- 鼠标悬浮提示 -->
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
<!-- 右键菜单 -->
<ArtMenuRight
ref="contextMenuRef"
:menu-items="contextMenuItems"
:menu-width="120"
@select="handleContextMenuSelect"
/>
</ElCard>
</div>
@@ -124,6 +111,7 @@
<script setup lang="ts">
import { h } from 'vue'
import { useRouter } from 'vue-router'
import { ref, reactive, onMounted } from 'vue'
import { DeviceService } from '@/api/modules'
import { ElMessage, ElTag } from 'element-plus'
import { Download, UploadFilled, Upload } from '@element-plus/icons-vue'
@@ -131,12 +119,7 @@
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 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 { StorageService } from '@/api/modules/storage'
import type { DeviceImportTask, DeviceImportTaskStatus } from '@/types/api/device'
import { RoutesAlias } from '@/router/routesAlias'
@@ -146,28 +129,18 @@
const router = useRouter()
const { hasAuth } = useAuth()
// 使
const {
showContextMenuHint,
hintPosition,
getRowClassName,
handleCellMouseEnter,
handleCellMouseLeave
} = useTableContextMenu()
const loading = ref(false)
const tableRef = ref()
const uploadRef = ref<UploadInstance>()
const fileList = ref<File[]>([])
const uploading = ref(false)
const importDialogVisible = ref(false)
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
const currentRow = ref<DeviceImportTask | null>(null)
//
const initialSearchState = {
status: undefined,
batch_no: '',
dateRange: undefined as any,
start_time: '',
end_time: ''
}
@@ -560,7 +533,8 @@
})
if (uploadUrlRes.code !== 0) {
throw new Error(uploadUrlRes.msg || '获取上传地址失败')
ElMessage.error(uploadUrlRes.msg || '获取上传地址失败')
return
}
const { upload_url, file_key } = uploadUrlRes.data
@@ -579,13 +553,14 @@
})
if (importRes.code !== 0) {
throw new Error(importRes.msg || '创建导入任务失败')
ElMessage.error(importRes.msg || '创建导入任务失败')
return
}
const taskNo = importRes.data.task_no
handleCancelImport()
getTableData()
await getTableData()
ElMessage.success({
message: `导入任务已创建!任务编号:${taskNo}`,
@@ -594,6 +569,7 @@
})
} catch (error: any) {
console.error('设备导入失败:', error)
ElMessage.error(error.message || '设备导入失败')
} finally {
uploading.value = false
}
@@ -646,36 +622,19 @@
}
}
//
const contextMenuItems = computed((): MenuItemType[] => {
if (!currentRow.value) return []
//
const getActions = (row: DeviceImportTask) => {
const actions: any[] = []
const items: MenuItemType[] = []
if (currentRow.value.fail_count > 0 && hasAuth('device_task:download_fail_data')) {
items.push({ key: 'failData', label: '失败数据' })
if (row.fail_count > 0 && hasAuth('device_task:download_fail_data')) {
actions.push({
label: '失败数据',
handler: () => downloadFailDataByRow(row),
type: 'danger'
})
}
return items
})
//
const handleRowContextMenu = (row: DeviceImportTask, column: any, event: MouseEvent) => {
event.preventDefault()
event.stopPropagation()
currentRow.value = row
contextMenuRef.value?.show(event)
}
//
const handleContextMenuSelect = (item: MenuItemType) => {
if (!currentRow.value) return
switch (item.key) {
case 'failData':
downloadFailDataByRow(currentRow.value)
break
}
return actions
}
</script>
@@ -697,8 +656,4 @@
}
}
}
:deep(.el-table__row.table-row-with-context-menu) {
cursor: pointer;
}
</style>

View File

@@ -39,28 +39,15 @@
:pageSize="pagination.pageSize"
:total="pagination.total"
:marginTop="10"
:row-class-name="getRowClassName"
:actions="getActions"
:actionsWidth="120"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
@row-contextmenu="handleRowContextMenu"
@cell-mouse-enter="handleCellMouseEnter"
@cell-mouse-leave="handleCellMouseLeave"
>
<template #default>
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
</template>
</ArtTable>
<!-- 鼠标悬浮提示 -->
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
<!-- 右键菜单 -->
<ArtMenuRight
ref="contextMenuRef"
:menu-items="contextMenuItems"
:menu-width="120"
@select="handleContextMenuSelect"
/>
</ElCard>
</div>
@@ -185,6 +172,7 @@
<script setup lang="ts">
import { h } from 'vue'
import { useRouter } from 'vue-router'
import { ref, reactive, computed, onMounted } from 'vue'
import { CardService, CarrierService } from '@/api/modules'
import {
ElMessage,
@@ -200,11 +188,7 @@
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 { StorageService } from '@/api/modules/storage'
import { RoutesAlias } from '@/router/routesAlias'
import type { IotCardImportTask, IotCardImportTaskStatus } from '@/types/api/card'
@@ -216,15 +200,6 @@
const { hasAuth } = useAuth()
// 使
const {
showContextMenuHint,
hintPosition,
getRowClassName,
handleCellMouseEnter,
handleCellMouseLeave
} = useTableContextMenu()
const loading = ref(false)
const tableRef = ref()
const uploadRef = ref<UploadInstance>()
@@ -234,8 +209,6 @@
const selectedCarrierId = ref<number>()
const carrierList = ref<Carrier[]>([])
const carrierLoading = ref(false)
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
const currentRow = ref<IotCardImportTask | null>(null)
const failDataDialogVisible = ref(false)
const failDataLoading = ref(false)
const failedItems = ref<any[]>([])
@@ -280,7 +253,7 @@
label: '运营商',
prop: 'carrier_id',
type: 'select',
options: carrierList.value.map((carrier) => ({
options: carrierList.value.map((carrier: Carrier) => ({
label: carrier.carrier_name,
value: carrier.id
})),
@@ -738,7 +711,8 @@
})
if (uploadUrlRes.code !== 0) {
throw new Error(uploadUrlRes.msg || '获取上传地址失败')
ElMessage.error(uploadUrlRes.msg || '获取上传地址失败')
return
}
const { upload_url, file_key } = uploadUrlRes.data
@@ -758,7 +732,8 @@
})
if (importRes.code !== 0) {
throw new Error(importRes.msg || '创建导入任务失败')
ElMessage.error(importRes.msg || '创建导入任务失败')
return
}
const taskNo = importRes.data.task_no
@@ -773,41 +748,25 @@
})
} catch (error: any) {
console.error('IoT卡导入失败:', error)
ElMessage.error(error.message || 'IoT卡导入失败')
} finally {
uploading.value = false
}
}
//
const contextMenuItems = computed((): MenuItemType[] => {
if (!currentRow.value) return []
//
const getActions = (row: IotCardImportTask) => {
const actions: any[] = []
const items: MenuItemType[] = []
if (currentRow.value.fail_count > 0 && hasAuth('iot_card_task:download_fail_data')) {
items.push({ key: 'failData', label: '失败数据' })
if (row.fail_count > 0 && hasAuth('iot_card_task:download_fail_data')) {
actions.push({
label: '失败数据',
handler: () => showFailDataDialog(row),
type: 'danger'
})
}
return items
})
//
const handleRowContextMenu = (row: IotCardImportTask, column: any, event: MouseEvent) => {
event.preventDefault()
event.stopPropagation()
currentRow.value = row
contextMenuRef.value?.show(event)
}
//
const handleContextMenuSelect = (item: MenuItemType) => {
if (!currentRow.value) return
switch (item.key) {
case 'failData':
showFailDataDialog(currentRow.value)
break
}
return actions
}
</script>
@@ -829,8 +788,4 @@
}
}
}
:deep(.el-table__row.table-row-with-context-menu) {
cursor: pointer;
}
</style>

View File

@@ -47,16 +47,17 @@
</template>
<script setup lang="ts">
import { h, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { computed, h } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ref, onMounted } from 'vue'
import { CardService, DeviceService } from '@/api/modules'
import { ElMessage, ElTag, ElDivider, ElTable, ElTableColumn, ElIcon } from 'element-plus'
import { ElDivider, ElIcon, ElMessage, ElTable, ElTableColumn, ElTag } from 'element-plus'
import { ArrowLeft } from '@element-plus/icons-vue'
import { formatDateTime } from '@/utils/business/format'
import type { IotCardImportTaskDetail, IotCardImportTaskStatus } from '@/types/api/card'
import type { DeviceImportTaskDetail } from '@/types/api/device'
import DetailPage from '@/components/common/DetailPage.vue'
import type { DetailSection } from '@/components/common/DetailPage.vue'
import DetailPage from '@/components/common/DetailPage.vue'
defineOptions({ name: 'TaskDetail' })
@@ -89,11 +90,11 @@
//
const detailSections = computed((): DetailSection[] => {
const sections: DetailSection[] = [
return [
{
title: '任务基本信息',
fields: [
{ label: '任务编号', prop: 'task_no', formatter: (value) => value || '-' },
{ label: '任务编号', prop: 'task_no', formatter: (value: string) => value || '-' },
{
label: '任务类型',
render: () => {
@@ -104,8 +105,8 @@
)
}
},
{ label: '批次号', prop: 'batch_no', formatter: (value) => value || '-' },
{ label: '文件名', prop: 'file_name', formatter: (value) => value || '-' },
{ label: '批次号', prop: 'batch_no', formatter: (value: string) => value || '-' },
{ label: '文件名', prop: 'file_name', formatter: (value: string) => value || '-' },
{
label: '任务状态',
render: (data: TaskDetail) => {
@@ -124,17 +125,17 @@
{
label: '创建时间',
prop: 'created_at',
formatter: (value) => (value ? formatDateTime(value) : '-')
formatter: (value: Date) => (value ? formatDateTime(value) : '-')
},
{
label: '开始处理时间',
prop: 'started_at',
formatter: (value) => (value ? formatDateTime(value) : '-')
formatter: (value: Date) => (value ? formatDateTime(value) : '-')
},
{
label: '完成时间',
prop: 'completed_at',
formatter: (value) => (value ? formatDateTime(value) : '-')
formatter: (value: Date) => (value ? formatDateTime(value) : '-')
},
...(taskDetail.value?.error_message
? [
@@ -195,8 +196,6 @@
]
}
]
return sections
})
//