This commit is contained in:
@@ -1,188 +1,50 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="exchange-detail-page">
|
<div class="exchange-detail-page">
|
||||||
<ElPageHeader @back="handleBack">
|
<ElCard shadow="never">
|
||||||
<template #content>
|
<!-- 页面头部 -->
|
||||||
<span class="page-header-title">换货单详情</span>
|
<div class="detail-header">
|
||||||
|
<ElButton @click="handleBack">
|
||||||
|
<template #icon>
|
||||||
|
<ElIcon><ArrowLeft /></ElIcon>
|
||||||
</template>
|
</template>
|
||||||
</ElPageHeader>
|
返回
|
||||||
|
</ElButton>
|
||||||
<ElCard v-loading="loading" style="margin-top: 20px">
|
<h2 class="detail-title">换货单详情</h2>
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<span>换货单信息</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
|
|
||||||
<ElDescriptions v-if="exchangeDetail" :column="2" border>
|
<!-- 详情内容 -->
|
||||||
<ElDescriptionsItem label="换货单号">
|
<DetailPage v-if="exchangeDetail" :sections="detailSections" :data="exchangeDetail" />
|
||||||
{{ exchangeDetail.exchange_no }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="状态">
|
|
||||||
<ElTag :type="getStatusType(exchangeDetail.status)">
|
|
||||||
{{ exchangeDetail.status_text }}
|
|
||||||
</ElTag>
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
|
|
||||||
<ElDescriptionsItem label="换货原因" :span="2">
|
<!-- 加载中 -->
|
||||||
{{ exchangeDetail.exchange_reason }}
|
<div v-if="loading" class="loading-container">
|
||||||
</ElDescriptionsItem>
|
<ElIcon class="is-loading"><Loading /></ElIcon>
|
||||||
|
<span>加载中...</span>
|
||||||
<ElDescriptionsItem label="旧资产类型">
|
|
||||||
{{ exchangeDetail.old_asset_type === 'iot_card' ? 'IoT卡' : '设备' }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="旧资产标识符">
|
|
||||||
{{ exchangeDetail.old_asset_identifier }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
|
|
||||||
<ElDescriptionsItem label="新资产类型">
|
|
||||||
{{
|
|
||||||
exchangeDetail.new_asset_type
|
|
||||||
? exchangeDetail.new_asset_type === 'iot_card'
|
|
||||||
? 'IoT卡'
|
|
||||||
: '设备'
|
|
||||||
: '--'
|
|
||||||
}}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="新资产标识符">
|
|
||||||
{{ exchangeDetail.new_asset_identifier || '--' }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
|
|
||||||
<ElDescriptionsItem label="收货人姓名">
|
|
||||||
{{ exchangeDetail.recipient_name || '--' }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="收货人电话">
|
|
||||||
{{ exchangeDetail.recipient_phone || '--' }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
|
|
||||||
<ElDescriptionsItem label="收货地址" :span="2">
|
|
||||||
{{ exchangeDetail.recipient_address || '--' }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
|
|
||||||
<ElDescriptionsItem label="快递公司">
|
|
||||||
{{ exchangeDetail.express_company || '--' }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="快递单号">
|
|
||||||
{{ exchangeDetail.express_no || '--' }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
|
|
||||||
<ElDescriptionsItem label="备注" :span="2">
|
|
||||||
{{ exchangeDetail.remark || '--' }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
|
|
||||||
<ElDescriptionsItem label="创建时间">
|
|
||||||
{{ formatDateTime(exchangeDetail.created_at) }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="更新时间">
|
|
||||||
{{ formatDateTime(exchangeDetail.updated_at) }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
</ElDescriptions>
|
|
||||||
|
|
||||||
<ElEmpty v-else description="未找到换货单信息" />
|
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
|
||||||
<div v-if="exchangeDetail" style="margin-top: 20px; text-align: center">
|
|
||||||
<ElButton
|
|
||||||
v-if="exchangeDetail.status === 1"
|
|
||||||
type="warning"
|
|
||||||
@click="handleCancel"
|
|
||||||
v-permission="'exchange:cancel'"
|
|
||||||
>
|
|
||||||
取消换货
|
|
||||||
</ElButton>
|
|
||||||
|
|
||||||
<ElButton
|
|
||||||
v-if="exchangeDetail.status === 2"
|
|
||||||
@click="handleRenew"
|
|
||||||
v-permission="'exchange:renew'"
|
|
||||||
>
|
|
||||||
旧资产转新
|
|
||||||
</ElButton>
|
|
||||||
|
|
||||||
<ElButton
|
|
||||||
v-if="exchangeDetail.status === 2"
|
|
||||||
type="primary"
|
|
||||||
@click="showShipDialog"
|
|
||||||
v-permission="'exchange:ship'"
|
|
||||||
>
|
|
||||||
发货
|
|
||||||
</ElButton>
|
|
||||||
|
|
||||||
<ElButton
|
|
||||||
v-if="exchangeDetail.status === 3"
|
|
||||||
type="success"
|
|
||||||
@click="handleComplete"
|
|
||||||
v-permission="'exchange:complete'"
|
|
||||||
>
|
|
||||||
确认完成
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
</div>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
|
|
||||||
<!-- 发货对话框 -->
|
|
||||||
<ElDialog v-model="shipDialogVisible" title="换货发货" width="600px">
|
|
||||||
<ElForm ref="shipFormRef" :model="shipForm" :rules="shipRules" label-width="120px">
|
|
||||||
<ElFormItem label="新资产标识符" prop="new_identifier">
|
|
||||||
<ElInput v-model="shipForm.new_identifier" placeholder="请输入新资产标识符(ICCID/虚拟号/IMEI/SN)" />
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="快递公司" prop="express_company">
|
|
||||||
<ElInput v-model="shipForm.express_company" placeholder="请输入快递公司" />
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="快递单号" prop="express_no">
|
|
||||||
<ElInput v-model="shipForm.express_no" placeholder="请输入快递单号" />
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="是否迁移数据">
|
|
||||||
<ElSwitch v-model="shipForm.migrate_data" />
|
|
||||||
</ElFormItem>
|
|
||||||
</ElForm>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<ElButton @click="shipDialogVisible = false">取消</ElButton>
|
|
||||||
<ElButton type="primary" @click="handleConfirmShip" :loading="shipLoading">
|
|
||||||
确认发货
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</ElDialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ExchangeService } from '@/api/modules'
|
import { ExchangeService } from '@/api/modules'
|
||||||
import type { ExchangeResponse } from '@/api/modules/exchange'
|
import type { ExchangeResponse } from '@/api/modules/exchange'
|
||||||
import { ElMessage, ElMessageBox, ElTag, ElSwitch } from 'element-plus'
|
import { ElTag, ElCard, ElButton, ElIcon } from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import DetailPage from '@/components/common/DetailPage.vue'
|
||||||
|
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'ExchangeDetail' })
|
defineOptions({ name: 'ExchangeDetail' })
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { hasAuth } = useAuth()
|
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const shipDialogVisible = ref(false)
|
|
||||||
const shipLoading = ref(false)
|
|
||||||
const shipFormRef = ref<FormInstance>()
|
|
||||||
|
|
||||||
const exchangeDetail = ref<ExchangeResponse | null>(null)
|
const exchangeDetail = ref<ExchangeResponse | null>(null)
|
||||||
const exchangeId = ref<number>(0)
|
const exchangeId = ref<number>(0)
|
||||||
|
|
||||||
const shipForm = reactive({
|
|
||||||
new_identifier: '',
|
|
||||||
express_company: '',
|
|
||||||
express_no: '',
|
|
||||||
migrate_data: true
|
|
||||||
})
|
|
||||||
|
|
||||||
const shipRules = reactive<FormRules>({
|
|
||||||
new_identifier: [{ required: true, message: '请输入新资产标识符', trigger: 'blur' }],
|
|
||||||
express_company: [{ required: true, message: '请输入快递公司', trigger: 'blur' }],
|
|
||||||
express_no: [{ required: true, message: '请输入快递单号', trigger: 'blur' }]
|
|
||||||
})
|
|
||||||
|
|
||||||
const getStatusType = (status: number) => {
|
const getStatusType = (status: number) => {
|
||||||
const types: Record<number, string> = {
|
const types: Record<number, string> = {
|
||||||
1: 'warning',
|
1: 'warning',
|
||||||
@@ -194,6 +56,102 @@
|
|||||||
return types[status] || 'info'
|
return types[status] || 'info'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 详情页配置
|
||||||
|
const detailSections: DetailSection[] = [
|
||||||
|
{
|
||||||
|
title: '基本信息',
|
||||||
|
fields: [
|
||||||
|
{ label: '换货单号', prop: 'exchange_no' },
|
||||||
|
{
|
||||||
|
label: '状态',
|
||||||
|
render: (data) =>
|
||||||
|
h(ElTag, { type: getStatusType(data.status) }, () => data.status_text)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '换货原因',
|
||||||
|
prop: 'exchange_reason',
|
||||||
|
fullWidth: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '旧资产类型',
|
||||||
|
formatter: (_, data) => (data.old_asset_type === 'iot_card' ? 'IoT卡' : '设备')
|
||||||
|
},
|
||||||
|
{ label: '旧资产标识符', prop: 'old_asset_identifier' },
|
||||||
|
{
|
||||||
|
label: '新资产类型',
|
||||||
|
formatter: (_, data) =>
|
||||||
|
data.new_asset_type
|
||||||
|
? data.new_asset_type === 'iot_card'
|
||||||
|
? 'IoT卡'
|
||||||
|
: '设备'
|
||||||
|
: '--'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '新资产标识符',
|
||||||
|
formatter: (value) => value || '--',
|
||||||
|
prop: 'new_asset_identifier'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '收货信息',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: '收货人姓名',
|
||||||
|
formatter: (value) => value || '--',
|
||||||
|
prop: 'recipient_name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '收货人电话',
|
||||||
|
formatter: (value) => value || '--',
|
||||||
|
prop: 'recipient_phone'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '收货地址',
|
||||||
|
formatter: (value) => value || '--',
|
||||||
|
prop: 'recipient_address',
|
||||||
|
fullWidth: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '物流信息',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: '快递公司',
|
||||||
|
formatter: (value) => value || '--',
|
||||||
|
prop: 'express_company'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '快递单号',
|
||||||
|
formatter: (value) => value || '--',
|
||||||
|
prop: 'express_no'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '其他信息',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: '备注',
|
||||||
|
formatter: (value) => value || '--',
|
||||||
|
prop: 'remark',
|
||||||
|
fullWidth: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '创建时间',
|
||||||
|
formatter: (value) => formatDateTime(value),
|
||||||
|
prop: 'created_at'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '更新时间',
|
||||||
|
formatter: (value) => formatDateTime(value),
|
||||||
|
prop: 'updated_at'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
// 加载换货单详情
|
// 加载换货单详情
|
||||||
const loadExchangeDetail = async () => {
|
const loadExchangeDetail = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -209,95 +167,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消换货
|
|
||||||
const handleCancel = () => {
|
|
||||||
ElMessageBox.prompt('请输入取消备注', '取消换货', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
inputType: 'textarea'
|
|
||||||
})
|
|
||||||
.then(async ({ value }) => {
|
|
||||||
try {
|
|
||||||
const res = await ExchangeService.cancelExchange(exchangeId.value, { remark: value })
|
|
||||||
if (res.code === 0) {
|
|
||||||
ElMessage.success('取消成功')
|
|
||||||
loadExchangeDetail()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('取消换货失败:', error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 旧资产转新
|
|
||||||
const handleRenew = () => {
|
|
||||||
ElMessageBox.confirm('确定要将旧资产转为新资产吗?', '确认操作', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
.then(async () => {
|
|
||||||
try {
|
|
||||||
const res = await ExchangeService.renewExchange(exchangeId.value)
|
|
||||||
if (res.code === 0) {
|
|
||||||
ElMessage.success('操作成功')
|
|
||||||
loadExchangeDetail()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('旧资产转新失败:', error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示发货对话框
|
|
||||||
const showShipDialog = () => {
|
|
||||||
shipDialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认发货
|
|
||||||
const handleConfirmShip = () => {
|
|
||||||
shipFormRef.value?.validate(async (valid) => {
|
|
||||||
if (!valid) return
|
|
||||||
|
|
||||||
shipLoading.value = true
|
|
||||||
try {
|
|
||||||
const res = await ExchangeService.shipExchange(exchangeId.value, shipForm)
|
|
||||||
if (res.code === 0) {
|
|
||||||
ElMessage.success('发货成功')
|
|
||||||
shipDialogVisible.value = false
|
|
||||||
loadExchangeDetail()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('发货失败:', error)
|
|
||||||
} finally {
|
|
||||||
shipLoading.value = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认完成
|
|
||||||
const handleComplete = () => {
|
|
||||||
ElMessageBox.confirm('确定要确认换货完成吗?', '确认操作', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
.then(async () => {
|
|
||||||
try {
|
|
||||||
const res = await ExchangeService.completeExchange(exchangeId.value)
|
|
||||||
if (res.code === 0) {
|
|
||||||
ElMessage.success('操作成功')
|
|
||||||
loadExchangeDetail()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('确认完成失败:', error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回
|
// 返回
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
router.back()
|
router.back()
|
||||||
@@ -314,17 +183,34 @@
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.exchange-detail-page {
|
.exchange-detail-page {
|
||||||
|
height: 100%;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|
||||||
.page-header-title {
|
.detail-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.detail-title {
|
||||||
|
margin-left: 16px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.loading-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 0;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -40,12 +40,26 @@
|
|||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
|
<!-- 右键菜单 -->
|
||||||
|
<ArtMenuRight
|
||||||
|
ref="contextMenuRef"
|
||||||
|
:menu-items="contextMenuItems"
|
||||||
|
:menu-width="140"
|
||||||
|
@select="handleContextMenuSelect"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 创建换货单对话框 -->
|
<!-- 创建换货单对话框 -->
|
||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="createDialogVisible"
|
v-model="createDialogVisible"
|
||||||
@@ -99,6 +113,50 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
|
|
||||||
|
<!-- 发货对话框 -->
|
||||||
|
<ElDialog
|
||||||
|
v-model="shipDialogVisible"
|
||||||
|
title="换货发货"
|
||||||
|
width="600px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@closed="handleCloseShipDialog"
|
||||||
|
>
|
||||||
|
<ElForm
|
||||||
|
ref="shipFormRef"
|
||||||
|
:model="shipForm"
|
||||||
|
:rules="shipRules"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<ElFormItem label="新资产标识符" prop="new_identifier">
|
||||||
|
<ElInput
|
||||||
|
v-model="shipForm.new_identifier"
|
||||||
|
placeholder="请输入新资产标识符(ICCID/虚拟号/IMEI/SN)"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="快递公司" prop="express_company">
|
||||||
|
<ElInput v-model="shipForm.express_company" placeholder="请输入快递公司" />
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="快递单号" prop="express_no">
|
||||||
|
<ElInput v-model="shipForm.express_no" placeholder="请输入快递单号" />
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="是否迁移数据">
|
||||||
|
<ElSwitch v-model="shipForm.migrate_data" />
|
||||||
|
<div style="font-size: 12px; color: #909399; margin-top: 4px">
|
||||||
|
开启后将迁移钱包余额、套餐记录到新资产
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<ElButton @click="shipDialogVisible = false">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="handleConfirmShip" :loading="shipLoading">
|
||||||
|
确认发货
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
</div>
|
</div>
|
||||||
</ArtTableFullScreen>
|
</ArtTableFullScreen>
|
||||||
@@ -109,20 +167,38 @@
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ExchangeService } from '@/api/modules'
|
import { ExchangeService } from '@/api/modules'
|
||||||
import type { ExchangeResponse } from '@/api/modules/exchange'
|
import type { ExchangeResponse } from '@/api/modules/exchange'
|
||||||
import { ElMessage, ElTag, ElButton } from 'element-plus'
|
import { ElMessage, ElTag, ElButton, ElMessageBox, ElSwitch } from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { RoutesAlias } from '@/router/routesAlias'
|
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' })
|
defineOptions({ name: 'ExchangeManagement' })
|
||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
const router = useRouter()
|
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 loading = ref(false)
|
||||||
|
const shipDialogVisible = ref(false)
|
||||||
|
const shipLoading = ref(false)
|
||||||
|
const shipFormRef = ref<FormInstance>()
|
||||||
const createDialogVisible = ref(false)
|
const createDialogVisible = ref(false)
|
||||||
const createLoading = ref(false)
|
const createLoading = ref(false)
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
@@ -152,6 +228,20 @@
|
|||||||
old_identifier: [{ required: true, message: '请输入旧资产标识符', trigger: 'blur' }]
|
old_identifier: [{ required: true, message: '请输入旧资产标识符', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 发货表单
|
||||||
|
const shipForm = reactive({
|
||||||
|
new_identifier: '',
|
||||||
|
express_company: '',
|
||||||
|
express_no: '',
|
||||||
|
migrate_data: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const shipRules = reactive<FormRules>({
|
||||||
|
new_identifier: [{ required: true, message: '请输入新资产标识符', trigger: 'blur' }],
|
||||||
|
express_company: [{ required: true, message: '请输入快递公司', trigger: 'blur' }],
|
||||||
|
express_no: [{ required: true, message: '请输入快递单号', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
|
||||||
// 分页
|
// 分页
|
||||||
const pagination = reactive({
|
const pagination = reactive({
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -206,7 +296,16 @@
|
|||||||
prop: 'exchange_no',
|
prop: 'exchange_no',
|
||||||
label: '换货单号',
|
label: '换货单号',
|
||||||
width: 220,
|
width: 220,
|
||||||
formatter: (row: any) => row.exchange_no || '--'
|
formatter: (row: any) =>
|
||||||
|
h(
|
||||||
|
ElButton,
|
||||||
|
{
|
||||||
|
type: 'primary',
|
||||||
|
link: true,
|
||||||
|
onClick: () => handleViewDetail(row)
|
||||||
|
},
|
||||||
|
() => row.exchange_no || '--'
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'exchange_reason',
|
prop: 'exchange_reason',
|
||||||
@@ -244,22 +343,6 @@
|
|||||||
width: 180,
|
width: 180,
|
||||||
formatter: (row: any) => formatDateTime(row.created_at)
|
formatter: (row: any) => formatDateTime(row.created_at)
|
||||||
},
|
},
|
||||||
{
|
|
||||||
prop: 'action',
|
|
||||||
label: '操作',
|
|
||||||
width: 120,
|
|
||||||
fixed: 'right',
|
|
||||||
formatter: (row: any) =>
|
|
||||||
h(
|
|
||||||
ElButton,
|
|
||||||
{
|
|
||||||
type: 'primary',
|
|
||||||
link: true,
|
|
||||||
onClick: () => handleViewDetail(row)
|
|
||||||
},
|
|
||||||
() => '查看详情'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const getStatusType = (status: number) => {
|
const getStatusType = (status: number) => {
|
||||||
@@ -372,6 +455,188 @@
|
|||||||
router.push(`${RoutesAlias.ExchangeDetail}/${row.id}`)
|
router.push(`${RoutesAlias.ExchangeDetail}/${row.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 右键菜单项
|
||||||
|
const contextMenuItems = computed<MenuItemType[]>(() => {
|
||||||
|
const items: MenuItemType[] = []
|
||||||
|
const status = currentExchangeRow.value?.status
|
||||||
|
|
||||||
|
// 状态1(待填写信息):取消换货
|
||||||
|
if (status === 1) {
|
||||||
|
if (hasAuth('exchange:cancel')) {
|
||||||
|
items.push({
|
||||||
|
key: 'cancel',
|
||||||
|
label: '取消换货'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态2(待发货):发货、取消换货
|
||||||
|
if (status === 2) {
|
||||||
|
if (hasAuth('exchange:ship')) {
|
||||||
|
items.push({
|
||||||
|
key: 'ship',
|
||||||
|
label: '发货'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (hasAuth('exchange:cancel')) {
|
||||||
|
items.push({
|
||||||
|
key: 'cancel',
|
||||||
|
label: '取消换货'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态3(已发货待确认):确认完成
|
||||||
|
if (status === 3) {
|
||||||
|
if (hasAuth('exchange:complete')) {
|
||||||
|
items.push({
|
||||||
|
key: 'complete',
|
||||||
|
label: '确认完成'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态4(已完成):旧资产转新
|
||||||
|
if (status === 4) {
|
||||||
|
if (hasAuth('exchange:renew')) {
|
||||||
|
items.push({
|
||||||
|
key: 'renew',
|
||||||
|
label: '旧资产转新'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消换货
|
||||||
|
const handleCancelExchange = (row: ExchangeResponse) => {
|
||||||
|
ElMessageBox.prompt('请输入取消备注', '取消换货', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
inputType: 'textarea'
|
||||||
|
})
|
||||||
|
.then(async ({ value }) => {
|
||||||
|
try {
|
||||||
|
const res = await ExchangeService.cancelExchange(row.id, { remark: value })
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('取消成功')
|
||||||
|
loadExchangeList()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('取消换货失败:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发货
|
||||||
|
const handleShipExchange = (row: ExchangeResponse) => {
|
||||||
|
currentExchangeRow.value = row
|
||||||
|
// 重置表单
|
||||||
|
shipForm.new_identifier = ''
|
||||||
|
shipForm.express_company = ''
|
||||||
|
shipForm.express_no = ''
|
||||||
|
shipForm.migrate_data = true
|
||||||
|
shipDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认发货
|
||||||
|
const handleConfirmShip = () => {
|
||||||
|
shipFormRef.value?.validate(async (valid) => {
|
||||||
|
if (!valid || !currentExchangeRow.value) return
|
||||||
|
|
||||||
|
shipLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await ExchangeService.shipExchange(currentExchangeRow.value.id, shipForm)
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('发货成功')
|
||||||
|
shipDialogVisible.value = false
|
||||||
|
loadExchangeList()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('发货失败:', error)
|
||||||
|
} finally {
|
||||||
|
shipLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭发货对话框
|
||||||
|
const handleCloseShipDialog = () => {
|
||||||
|
shipFormRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认完成
|
||||||
|
const handleCompleteExchange = (row: ExchangeResponse) => {
|
||||||
|
ElMessageBox.confirm('确定要确认换货完成吗?', '确认操作', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
try {
|
||||||
|
const res = await ExchangeService.completeExchange(row.id)
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('操作成功')
|
||||||
|
loadExchangeList()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('确认完成失败:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 旧资产转新
|
||||||
|
const handleRenewExchange = (row: ExchangeResponse) => {
|
||||||
|
ElMessageBox.confirm('确定要将旧资产转为新资产吗?', '确认操作', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
try {
|
||||||
|
const res = await ExchangeService.renewExchange(row.id)
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('操作成功')
|
||||||
|
loadExchangeList()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('旧资产转新失败:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadExchangeList()
|
loadExchangeList()
|
||||||
|
|||||||
@@ -862,7 +862,7 @@
|
|||||||
const navigateToSingleCard = (iccid: string) => {
|
const navigateToSingleCard = (iccid: string) => {
|
||||||
// 使用路由跳转到单卡信息页面,并传递 ICCID 参数
|
// 使用路由跳转到单卡信息页面,并传递 ICCID 参数
|
||||||
router.push({
|
router.push({
|
||||||
path: '/card-management/single-card',
|
path: '/asset-management/single-card',
|
||||||
query: { iccid }
|
query: { iccid }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,893 +0,0 @@
|
|||||||
<template>
|
|
||||||
<ArtTableFullScreen>
|
|
||||||
<div class="single-card-page" id="table-full-screen">
|
|
||||||
<!-- 标识符查询区域 -->
|
|
||||||
<ElCard shadow="never" class="search-card" style="margin-bottom: 16px">
|
|
||||||
<template #header>
|
|
||||||
<span>资产查询</span>
|
|
||||||
</template>
|
|
||||||
<div class="search-box">
|
|
||||||
<ElInput
|
|
||||||
v-model="searchIdentifier"
|
|
||||||
placeholder="请输入虚拟号、ICCID、IMEI、SN或MSISDN"
|
|
||||||
clearable
|
|
||||||
style="width: 500px; margin-right: 16px"
|
|
||||||
@keyup.enter="handleSearchAsset"
|
|
||||||
>
|
|
||||||
<template #prepend>标识符</template>
|
|
||||||
</ElInput>
|
|
||||||
<ElButton type="primary" @click="handleSearchAsset" :loading="loading">查询</ElButton>
|
|
||||||
<ElButton
|
|
||||||
@click="handleRefreshAsset"
|
|
||||||
:loading="refreshing"
|
|
||||||
:disabled="!assetInfo.asset_id"
|
|
||||||
>
|
|
||||||
<ElIcon><Refresh /></ElIcon>
|
|
||||||
刷新数据
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
|
||||||
<!-- 格式化显示的标识符 -->
|
|
||||||
<div v-if="formattedIdentifier" class="formatted-identifier">
|
|
||||||
{{ formattedIdentifier }}
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
|
|
||||||
<!-- 资产信息卡片 -->
|
|
||||||
<ElCard shadow="never" class="asset-info-card" style="margin-bottom: 16px">
|
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<span>资产信息</span>
|
|
||||||
<ElTag
|
|
||||||
v-if="assetInfo.asset_type"
|
|
||||||
:type="assetInfo.asset_type === 'card' ? 'success' : 'primary'"
|
|
||||||
>
|
|
||||||
{{ assetInfo.asset_type === 'card' ? 'IoT卡' : '设备' }}
|
|
||||||
</ElTag>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div v-if="!hasSearched" class="empty-state">
|
|
||||||
<p style="text-align: center; color: var(--el-text-color-secondary); padding: 40px">
|
|
||||||
请在上方输入标识符进行查询
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<ElForm v-else :model="assetInfo" label-width="140px" :inline="false">
|
|
||||||
<!-- 通用信息 -->
|
|
||||||
<ElRow :gutter="24">
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="资产ID:">
|
|
||||||
<span>{{ assetInfo.asset_id || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="虚拟号:">
|
|
||||||
<span>{{ assetInfo.virtual_no || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="状态:">
|
|
||||||
<ElTag v-if="assetInfo.status" :type="getStatusTagType(assetInfo.status)">
|
|
||||||
{{ getStatusName(assetInfo.status) }}
|
|
||||||
</ElTag>
|
|
||||||
<span v-else>-</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
<ElRow :gutter="24">
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="所属店铺:">
|
|
||||||
<span>{{ assetInfo.shop_name || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="套餐系列:">
|
|
||||||
<span>{{ assetInfo.series_name || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="批次号:">
|
|
||||||
<span>{{ assetInfo.batch_no || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
<ElRow :gutter="24">
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="实名状态:">
|
|
||||||
<ElTag :type="getRealNameStatusTagType(assetInfo.real_name_status)">
|
|
||||||
{{ getRealNameStatusName(assetInfo.real_name_status) }}
|
|
||||||
</ElTag>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8" v-if="assetInfo.asset_type === 'card'">
|
|
||||||
<ElFormItem label="网络状态:">
|
|
||||||
<ElTag :type="assetInfo.network_status === 1 ? 'success' : 'danger'">
|
|
||||||
{{ assetInfo.network_status === 1 ? '开机' : '停机' }}
|
|
||||||
</ElTag>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8" v-if="assetInfo.asset_type === 'device'">
|
|
||||||
<ElFormItem label="保护期状态:">
|
|
||||||
<ElTag :type="getProtectStatusTagType(assetInfo.device_protect_status)">
|
|
||||||
{{ getProtectStatusName(assetInfo.device_protect_status) }}
|
|
||||||
</ElTag>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="激活时间:">
|
|
||||||
<span>{{ assetInfo.activated_at || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
|
|
||||||
<!-- 卡专属字段 -->
|
|
||||||
<template v-if="assetInfo.asset_type === 'card'">
|
|
||||||
<ElDivider content-position="left">卡信息</ElDivider>
|
|
||||||
<ElRow :gutter="24">
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="ICCID:">
|
|
||||||
<span>{{ assetInfo.iccid || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="IMSI:">
|
|
||||||
<span>{{ assetInfo.imsi || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="手机号:">
|
|
||||||
<span>{{ assetInfo.msisdn || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
<ElRow :gutter="24">
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="运营商:">
|
|
||||||
<span>{{ assetInfo.carrier_name || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="供应商:">
|
|
||||||
<span>{{ assetInfo.supplier || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="绑定设备:">
|
|
||||||
<span>{{ assetInfo.bound_device_name || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 设备专属字段 -->
|
|
||||||
<template v-if="assetInfo.asset_type === 'device'">
|
|
||||||
<ElDivider content-position="left">设备信息</ElDivider>
|
|
||||||
<ElRow :gutter="24">
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="设备名称:">
|
|
||||||
<span>{{ assetInfo.device_name || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="设备型号:">
|
|
||||||
<span>{{ assetInfo.device_model || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="设备类型:">
|
|
||||||
<span>{{ assetInfo.device_type || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
<ElRow :gutter="24">
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="制造商:">
|
|
||||||
<span>{{ assetInfo.manufacturer || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="IMEI:">
|
|
||||||
<span>{{ assetInfo.imei || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="序列号:">
|
|
||||||
<span>{{ assetInfo.sn || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
<ElRow :gutter="24">
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="最大插槽数:">
|
|
||||||
<span>{{ assetInfo.max_sim_slots || '-' }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="8">
|
|
||||||
<ElFormItem label="已绑定卡数:">
|
|
||||||
<span>{{ assetInfo.bound_card_count || 0 }}</span>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
</template>
|
|
||||||
</ElForm>
|
|
||||||
</ElCard>
|
|
||||||
|
|
||||||
<!-- 操作区域 -->
|
|
||||||
<ElCard
|
|
||||||
v-if="hasSearched && assetInfo.asset_id"
|
|
||||||
shadow="never"
|
|
||||||
class="operation-card"
|
|
||||||
style="margin-bottom: 16px"
|
|
||||||
>
|
|
||||||
<template #header>
|
|
||||||
<span>操作区域</span>
|
|
||||||
</template>
|
|
||||||
<div class="operation-buttons">
|
|
||||||
<!-- 卡操作 -->
|
|
||||||
<template v-if="assetInfo.asset_type === 'card'">
|
|
||||||
<ElButton
|
|
||||||
type="success"
|
|
||||||
@click="handleStartCard"
|
|
||||||
:disabled="assetInfo.network_status === 1"
|
|
||||||
>
|
|
||||||
复机
|
|
||||||
</ElButton>
|
|
||||||
<ElButton
|
|
||||||
type="warning"
|
|
||||||
@click="handleStopCard"
|
|
||||||
:disabled="assetInfo.network_status === 0"
|
|
||||||
>
|
|
||||||
停机
|
|
||||||
</ElButton>
|
|
||||||
</template>
|
|
||||||
<!-- 设备操作 -->
|
|
||||||
<template v-if="assetInfo.asset_type === 'device'">
|
|
||||||
<ElButton
|
|
||||||
type="success"
|
|
||||||
@click="handleStartDevice"
|
|
||||||
:disabled="assetInfo.device_protect_status === 'stop'"
|
|
||||||
>
|
|
||||||
批量复机
|
|
||||||
</ElButton>
|
|
||||||
<ElButton
|
|
||||||
type="warning"
|
|
||||||
@click="handleStopDevice"
|
|
||||||
:disabled="assetInfo.device_protect_status === 'start'"
|
|
||||||
>
|
|
||||||
批量停机
|
|
||||||
</ElButton>
|
|
||||||
</template>
|
|
||||||
<ElButton type="primary" @click="loadPackages">查看套餐</ElButton>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
|
|
||||||
<!-- 流量/套餐信息 -->
|
|
||||||
<ElCard
|
|
||||||
v-if="hasSearched && currentPackage"
|
|
||||||
shadow="never"
|
|
||||||
class="traffic-card"
|
|
||||||
style="margin-bottom: 16px"
|
|
||||||
>
|
|
||||||
<template #header>
|
|
||||||
<span>当前套餐信息</span>
|
|
||||||
</template>
|
|
||||||
<ElRow :gutter="24">
|
|
||||||
<ElCol :span="6">
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="info-label">套餐名称</div>
|
|
||||||
<div class="info-value">{{ assetInfo.current_package || '-' }}</div>
|
|
||||||
</div>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="6">
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="info-label">总流量</div>
|
|
||||||
<div class="info-value">{{ formatMB(assetInfo.package_total_mb) }}</div>
|
|
||||||
</div>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="6">
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="info-label">已用流量</div>
|
|
||||||
<div class="info-value used">{{ formatMB(assetInfo.package_used_mb) }}</div>
|
|
||||||
</div>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="6">
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="info-label">剩余流量</div>
|
|
||||||
<div class="info-value remaining">{{ formatMB(assetInfo.package_remain_mb) }}</div>
|
|
||||||
</div>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
<ElProgress
|
|
||||||
:percentage="getUsagePercentage()"
|
|
||||||
:color="getProgressColor()"
|
|
||||||
style="margin-top: 16px"
|
|
||||||
/>
|
|
||||||
</ElCard>
|
|
||||||
|
|
||||||
<!-- 绑定卡列表(仅设备) -->
|
|
||||||
<ElCard
|
|
||||||
v-if="
|
|
||||||
hasSearched &&
|
|
||||||
assetInfo.asset_type === 'device' &&
|
|
||||||
assetInfo.cards &&
|
|
||||||
assetInfo.cards.length
|
|
||||||
"
|
|
||||||
shadow="never"
|
|
||||||
style="margin-bottom: 16px"
|
|
||||||
>
|
|
||||||
<template #header>
|
|
||||||
<span>绑定卡列表</span>
|
|
||||||
</template>
|
|
||||||
<ElTable :data="assetInfo.cards" border stripe>
|
|
||||||
<ElTableColumn prop="iccid" label="ICCID" min-width="180" />
|
|
||||||
<ElTableColumn prop="msisdn" label="手机号" width="120" />
|
|
||||||
<ElTableColumn prop="slot_position" label="插槽位置" width="100" align="center" />
|
|
||||||
<ElTableColumn prop="network_status" label="网络状态" width="100" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<ElTag :type="row.network_status === 1 ? 'success' : 'danger'">
|
|
||||||
{{ row.network_status === 1 ? '开机' : '停机' }}
|
|
||||||
</ElTag>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="real_name_status" label="实名状态" width="100" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<ElTag :type="getRealNameStatusTagType(row.real_name_status)">
|
|
||||||
{{ getRealNameStatusName(row.real_name_status) }}
|
|
||||||
</ElTag>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
</ElTable>
|
|
||||||
</ElCard>
|
|
||||||
|
|
||||||
<!-- 套餐列表对话框 -->
|
|
||||||
<ElDialog v-model="packagesDialogVisible" title="套餐列表" width="90%" align-center>
|
|
||||||
<ElTable :data="packages" border stripe v-loading="packagesLoading">
|
|
||||||
<ElTableColumn prop="package_usage_id" label="使用记录ID" width="110" align="center" />
|
|
||||||
<ElTableColumn prop="package_id" label="套餐ID" width="90" align="center" />
|
|
||||||
<ElTableColumn
|
|
||||||
prop="package_name"
|
|
||||||
label="套餐名称"
|
|
||||||
min-width="150"
|
|
||||||
show-overflow-tooltip
|
|
||||||
/>
|
|
||||||
<ElTableColumn prop="package_type" label="套餐类型" width="100" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<ElTag :type="row.package_type === 'formal' ? 'primary' : 'warning'" size="small">
|
|
||||||
{{ row.package_type === 'formal' ? '正式套餐' : '加油包' }}
|
|
||||||
</ElTag>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="usage_type" label="使用类型" width="110" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<ElTag :type="row.usage_type === 'single_card' ? 'success' : 'info'" size="small">
|
|
||||||
{{ row.usage_type === 'single_card' ? '单卡' : '设备' }}
|
|
||||||
</ElTag>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="status_name" label="状态" width="100" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<ElTag :type="getPackageStatusTagType(row.status)" size="small">
|
|
||||||
{{ row.status_name }}
|
|
||||||
</ElTag>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="data_limit_mb" label="真流量总量" width="120" align="right">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ formatMB(row.data_limit_mb) }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="data_usage_mb" label="真流量已用" width="120" align="right">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ formatMB(row.data_usage_mb) }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="virtual_limit_mb" label="虚流量总量" width="120" align="right">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ formatMB(row.virtual_limit_mb) }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="virtual_used_mb" label="虚流量已用" width="120" align="right">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ formatMB(row.virtual_used_mb) }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="virtual_remain_mb" label="虚流量剩余" width="120" align="right">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ formatMB(row.virtual_remain_mb) }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="virtual_ratio" label="虚流量比例" width="100" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ row.virtual_ratio?.toFixed(2) || '1.00' }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="priority" label="优先级" width="80" align="center" />
|
|
||||||
<ElTableColumn prop="master_usage_id" label="主套餐ID" width="100" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ row.master_usage_id || '-' }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="activated_at" label="激活时间" width="170" show-overflow-tooltip>
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ formatDateTime(row.activated_at) }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="expires_at" label="到期时间" width="170" show-overflow-tooltip>
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ formatDateTime(row.expires_at) }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="created_at" label="创建时间" width="170" show-overflow-tooltip>
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ formatDateTime(row.created_at) }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
</ElTable>
|
|
||||||
</ElDialog>
|
|
||||||
</div>
|
|
||||||
</ArtTableFullScreen>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { Refresh } from '@element-plus/icons-vue'
|
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
import { AssetService } from '@/api/modules'
|
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
|
||||||
import type {
|
|
||||||
AssetResolveResponse,
|
|
||||||
AssetPackageUsageRecord,
|
|
||||||
AssetCurrentPackageResponse
|
|
||||||
} from '@/types/api'
|
|
||||||
|
|
||||||
defineOptions({ name: 'SingleCard' })
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
const refreshing = ref(false)
|
|
||||||
const packagesDialogVisible = ref(false)
|
|
||||||
const packagesLoading = ref(false)
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
// 标识符搜索相关
|
|
||||||
const searchIdentifier = ref('')
|
|
||||||
const hasSearched = ref(false)
|
|
||||||
|
|
||||||
// 资产信息
|
|
||||||
const assetInfo = ref<Partial<AssetResolveResponse>>({})
|
|
||||||
|
|
||||||
// 当前套餐
|
|
||||||
const currentPackage = ref<AssetCurrentPackageResponse | null>(null)
|
|
||||||
|
|
||||||
// 套餐列表
|
|
||||||
const packages = ref<AssetPackageUsageRecord[]>([])
|
|
||||||
|
|
||||||
// 格式化显示的标识符
|
|
||||||
const formattedIdentifier = computed(() => {
|
|
||||||
const identifier = assetInfo.value.virtual_no || assetInfo.value.iccid || searchIdentifier.value
|
|
||||||
if (!identifier) return ''
|
|
||||||
// 如果是ICCID(19-20位数字),进行格式化
|
|
||||||
if (/^\d{19,20}$/.test(identifier)) {
|
|
||||||
return identifier.replace(/(\d{4})(?=\d)/g, '$1-')
|
|
||||||
}
|
|
||||||
return identifier
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取状态标签类型
|
|
||||||
const getStatusTagType = (status: number) => {
|
|
||||||
const map: Record<number, any> = {
|
|
||||||
1: 'info',
|
|
||||||
2: 'success',
|
|
||||||
3: 'success',
|
|
||||||
4: 'danger'
|
|
||||||
}
|
|
||||||
return map[status] || 'info'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取状态名称
|
|
||||||
const getStatusName = (status: number) => {
|
|
||||||
const map: Record<number, string> = {
|
|
||||||
1: '在库',
|
|
||||||
2: '已分销',
|
|
||||||
3: '已激活',
|
|
||||||
4: '已停用'
|
|
||||||
}
|
|
||||||
return map[status] || '未知'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取实名状态标签类型
|
|
||||||
const getRealNameStatusTagType = (status: number) => {
|
|
||||||
const map: Record<number, any> = {
|
|
||||||
0: 'info',
|
|
||||||
1: 'warning',
|
|
||||||
2: 'success'
|
|
||||||
}
|
|
||||||
return map[status] || 'info'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取实名状态名称
|
|
||||||
const getRealNameStatusName = (status: number) => {
|
|
||||||
const map: Record<number, string> = {
|
|
||||||
0: '未实名',
|
|
||||||
1: '实名中',
|
|
||||||
2: '已实名'
|
|
||||||
}
|
|
||||||
return map[status] || '未知'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取保护期状态标签类型
|
|
||||||
const getProtectStatusTagType = (status?: string) => {
|
|
||||||
const map: Record<string, any> = {
|
|
||||||
none: 'info',
|
|
||||||
stop: 'warning',
|
|
||||||
start: 'success'
|
|
||||||
}
|
|
||||||
return map[status || 'none'] || 'info'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取保护期状态名称
|
|
||||||
const getProtectStatusName = (status?: string) => {
|
|
||||||
const map: Record<string, string> = {
|
|
||||||
none: '无保护期',
|
|
||||||
stop: '停机保护期',
|
|
||||||
start: '复机保护期'
|
|
||||||
}
|
|
||||||
return map[status || 'none'] || '无'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取套餐状态标签类型
|
|
||||||
const getPackageStatusTagType = (status: number) => {
|
|
||||||
const map: Record<number, any> = {
|
|
||||||
0: 'info',
|
|
||||||
1: 'success',
|
|
||||||
2: 'warning',
|
|
||||||
3: 'danger',
|
|
||||||
4: 'info'
|
|
||||||
}
|
|
||||||
return map[status] || 'info'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化MB
|
|
||||||
const formatMB = (mb: number | undefined) => {
|
|
||||||
if (mb === undefined || mb === null) return '0MB'
|
|
||||||
if (mb >= 1024) {
|
|
||||||
return `${(mb / 1024).toFixed(2)}GB`
|
|
||||||
}
|
|
||||||
return `${mb.toFixed(2)}MB`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取使用百分比
|
|
||||||
const getUsagePercentage = () => {
|
|
||||||
const total = assetInfo.value.package_total_mb || 0
|
|
||||||
const used = assetInfo.value.package_used_mb || 0
|
|
||||||
if (total === 0) return 0
|
|
||||||
return Math.min(Math.round((used / total) * 100), 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取进度条颜色
|
|
||||||
const getProgressColor = () => {
|
|
||||||
const percentage = getUsagePercentage()
|
|
||||||
if (percentage >= 90) return '#F56C6C'
|
|
||||||
if (percentage >= 70) return '#E6A23C'
|
|
||||||
return '#67C23A'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理资产搜索
|
|
||||||
const handleSearchAsset = async () => {
|
|
||||||
if (!searchIdentifier.value.trim()) {
|
|
||||||
ElMessage.warning('请输入资产标识符')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await loadAssetInfo(searchIdentifier.value.trim())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载资产信息
|
|
||||||
const loadAssetInfo = async (identifier: string) => {
|
|
||||||
loading.value = true
|
|
||||||
hasSearched.value = false
|
|
||||||
currentPackage.value = null
|
|
||||||
try {
|
|
||||||
const response = await AssetService.resolveAsset(identifier)
|
|
||||||
if (response.code === 200 && response.data) {
|
|
||||||
assetInfo.value = response.data
|
|
||||||
hasSearched.value = true
|
|
||||||
|
|
||||||
// 如果有当前套餐,尝试加载套餐详情
|
|
||||||
if (response.data.current_package) {
|
|
||||||
await loadCurrentPackage()
|
|
||||||
}
|
|
||||||
|
|
||||||
ElMessage.success('查询成功')
|
|
||||||
} else {
|
|
||||||
ElMessage.error(response.message || '查询失败')
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('获取资产信息失败:', error)
|
|
||||||
if (error?.response?.status === 404) {
|
|
||||||
ElMessage.error('未找到该资产')
|
|
||||||
} else if (error?.response?.status === 403) {
|
|
||||||
ElMessage.error('无权限访问该资产')
|
|
||||||
} else {
|
|
||||||
ElMessage.error(error?.message || '获取资产信息失败')
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新资产数据
|
|
||||||
const handleRefreshAsset = async () => {
|
|
||||||
if (!assetInfo.value.asset_type || !assetInfo.value.asset_id) {
|
|
||||||
ElMessage.warning('请先查询资产')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshing.value = true
|
|
||||||
try {
|
|
||||||
const response = await AssetService.refreshAsset(
|
|
||||||
assetInfo.value.asset_type,
|
|
||||||
assetInfo.value.asset_id
|
|
||||||
)
|
|
||||||
if (response.code === 200) {
|
|
||||||
ElMessage.success('数据刷新成功')
|
|
||||||
// 刷新完成后重新加载资产信息
|
|
||||||
if (assetInfo.value.virtual_no) {
|
|
||||||
await loadAssetInfo(assetInfo.value.virtual_no)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ElMessage.error(response.message || '刷新失败')
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('刷新数据失败:', error)
|
|
||||||
if (error?.response?.status === 429) {
|
|
||||||
ElMessage.warning('刷新太频繁,请稍后再试(30秒冷却)')
|
|
||||||
} else {
|
|
||||||
ElMessage.error(error?.message || '刷新数据失败')
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
refreshing.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载当前套餐
|
|
||||||
const loadCurrentPackage = async () => {
|
|
||||||
if (!assetInfo.value.asset_type || !assetInfo.value.asset_id) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await AssetService.getCurrentPackage(
|
|
||||||
assetInfo.value.asset_type,
|
|
||||||
assetInfo.value.asset_id
|
|
||||||
)
|
|
||||||
if (response.code === 200 && response.data) {
|
|
||||||
currentPackage.value = response.data
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
// 404表示无生效套餐,不显示错误
|
|
||||||
if (error?.response?.status !== 404) {
|
|
||||||
console.error('获取当前套餐失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载套餐列表
|
|
||||||
const loadPackages = async () => {
|
|
||||||
if (!assetInfo.value.asset_type || !assetInfo.value.asset_id) {
|
|
||||||
ElMessage.warning('请先查询资产')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
packagesDialogVisible.value = true
|
|
||||||
packagesLoading.value = true
|
|
||||||
try {
|
|
||||||
const response = await AssetService.getAssetPackages(
|
|
||||||
assetInfo.value.asset_type,
|
|
||||||
assetInfo.value.asset_id
|
|
||||||
)
|
|
||||||
if (response.code === 200 && response.data) {
|
|
||||||
packages.value = response.data
|
|
||||||
} else {
|
|
||||||
ElMessage.error(response.message || '获取套餐列表失败')
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('获取套餐列表失败:', error)
|
|
||||||
ElMessage.error(error?.message || '获取套餐列表失败')
|
|
||||||
} finally {
|
|
||||||
packagesLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 单卡复机
|
|
||||||
const handleStartCard = async () => {
|
|
||||||
if (!assetInfo.value.iccid) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
await ElMessageBox.confirm('确定要复机该卡吗?', '复机确认', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
|
|
||||||
await AssetService.startCard(assetInfo.value.iccid)
|
|
||||||
ElMessage.success('复机成功')
|
|
||||||
|
|
||||||
// 重新加载资产信息
|
|
||||||
if (assetInfo.value.virtual_no) {
|
|
||||||
await loadAssetInfo(assetInfo.value.virtual_no)
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error !== 'cancel') {
|
|
||||||
if (error?.response?.status === 403) {
|
|
||||||
ElMessage.error('该卡绑定的设备在保护期内,无法复机')
|
|
||||||
} else {
|
|
||||||
ElMessage.error(error?.message || '复机失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 单卡停机
|
|
||||||
const handleStopCard = async () => {
|
|
||||||
if (!assetInfo.value.iccid) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
await ElMessageBox.confirm('确定要停机该卡吗?', '停机确认', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
|
|
||||||
await AssetService.stopCard(assetInfo.value.iccid)
|
|
||||||
ElMessage.success('停机成功')
|
|
||||||
|
|
||||||
// 重新加载资产信息
|
|
||||||
if (assetInfo.value.virtual_no) {
|
|
||||||
await loadAssetInfo(assetInfo.value.virtual_no)
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error !== 'cancel') {
|
|
||||||
if (error?.response?.status === 403) {
|
|
||||||
ElMessage.error('该卡绑定的设备在保护期内,无法停机')
|
|
||||||
} else {
|
|
||||||
ElMessage.error(error?.message || '停机失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设备批量复机
|
|
||||||
const handleStartDevice = async () => {
|
|
||||||
if (!assetInfo.value.asset_id) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
await ElMessageBox.confirm(
|
|
||||||
'确定要批量复机设备下所有已实名卡吗?复机成功后将设置1小时复机保护期。',
|
|
||||||
'批量复机确认',
|
|
||||||
{
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
await AssetService.startDevice(assetInfo.value.asset_id)
|
|
||||||
ElMessage.success('批量复机成功')
|
|
||||||
|
|
||||||
// 重新加载资产信息
|
|
||||||
if (assetInfo.value.virtual_no) {
|
|
||||||
await loadAssetInfo(assetInfo.value.virtual_no)
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error !== 'cancel') {
|
|
||||||
ElMessage.error(error?.message || '批量复机失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设备批量停机
|
|
||||||
const handleStopDevice = async () => {
|
|
||||||
if (!assetInfo.value.asset_id) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
await ElMessageBox.confirm(
|
|
||||||
'确定要批量停机设备下所有已实名卡吗?停机成功后将设置1小时停机保护期。',
|
|
||||||
'批量停机确认',
|
|
||||||
{
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const response = await AssetService.stopDevice(assetInfo.value.asset_id)
|
|
||||||
if (response.code === 200 && response.data) {
|
|
||||||
const { success_count, failed_cards } = response.data
|
|
||||||
if (failed_cards && failed_cards.length > 0) {
|
|
||||||
ElMessage.warning(`停机部分成功,成功${success_count}张,失败${failed_cards.length}张`)
|
|
||||||
} else {
|
|
||||||
ElMessage.success(`批量停机成功,共停机${success_count}张卡`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重新加载资产信息
|
|
||||||
if (assetInfo.value.virtual_no) {
|
|
||||||
await loadAssetInfo(assetInfo.value.virtual_no)
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error !== 'cancel') {
|
|
||||||
ElMessage.error(error?.message || '批量停机失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// 检查是否有传递的标识符参数
|
|
||||||
const identifierFromQuery = (route.query.iccid || route.query.identifier) as string
|
|
||||||
if (identifierFromQuery) {
|
|
||||||
searchIdentifier.value = identifierFromQuery
|
|
||||||
loadAssetInfo(identifierFromQuery)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.single-card-page {
|
|
||||||
.search-card {
|
|
||||||
.search-box {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formatted-identifier {
|
|
||||||
margin-top: 16px;
|
|
||||||
padding: 16px;
|
|
||||||
background-color: var(--el-fill-color-light);
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 2px;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
font-family: 'Courier New', Courier, monospace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.operation-buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.traffic-card,
|
|
||||||
.asset-info-card {
|
|
||||||
.info-item {
|
|
||||||
padding: 16px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: var(--el-fill-color-light);
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--el-text-color-regular);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
|
|
||||||
&.used {
|
|
||||||
color: var(--el-color-warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.remaining {
|
|
||||||
color: var(--el-color-success);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -146,6 +146,39 @@
|
|||||||
cardInfo?.activated_at || '--'
|
cardInfo?.activated_at || '--'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
</ElDescriptions>
|
</ElDescriptions>
|
||||||
|
|
||||||
|
<!-- 设备绑定卡列表 (放在设备信息卡片内) -->
|
||||||
|
<template v-if="cardInfo?.asset_type === 'device'">
|
||||||
|
<ElDivider content-position="left">绑定卡列表</ElDivider>
|
||||||
|
<ElTable
|
||||||
|
v-if="cardInfo.cards && cardInfo.cards.length > 0"
|
||||||
|
:data="cardInfo.cards"
|
||||||
|
style="margin-top: 16px"
|
||||||
|
>
|
||||||
|
<ElTableColumn prop="iccid" label="ICCID" min-width="180" />
|
||||||
|
<ElTableColumn prop="msisdn" label="MSISDN" min-width="120" />
|
||||||
|
<ElTableColumn prop="slot_position" label="卡槽位置" width="100" align="center" />
|
||||||
|
<ElTableColumn label="网络状态" width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<ElTag
|
||||||
|
:type="scope.row.network_status === 1 ? 'success' : 'danger'"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ scope.row.network_status === 1 ? '开机' : '停机' }}
|
||||||
|
</ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="实名状态" width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<ElTag :type="getRealNameStatusType(scope.row.real_name_status)" size="small">
|
||||||
|
{{ getRealNameStatusName(scope.row.real_name_status) }}
|
||||||
|
</ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
</ElTable>
|
||||||
|
<ElEmpty v-else description="暂无绑定卡" :image-size="80" style="margin-top: 16px" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- IoT卡操作按钮 -->
|
<!-- IoT卡操作按钮 -->
|
||||||
<div
|
<div
|
||||||
v-if="cardInfo?.asset_type === 'card'"
|
v-if="cardInfo?.asset_type === 'card'"
|
||||||
@@ -440,48 +473,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第四行:设备绑定卡列表 (仅设备显示) -->
|
<!-- 套餐列表 -->
|
||||||
<template v-if="cardInfo?.asset_type === 'device'">
|
|
||||||
<div class="row full-width">
|
|
||||||
<ElCard shadow="never" class="info-card">
|
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<span>绑定卡列表</span>
|
|
||||||
<ElTag type="info" size="small">共 {{ cardInfo?.cards?.length || 0 }} 张</ElTag>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<ElTable
|
|
||||||
v-if="cardInfo.cards && cardInfo.cards.length > 0"
|
|
||||||
:data="cardInfo.cards"
|
|
||||||
class="package-table"
|
|
||||||
>
|
|
||||||
<ElTableColumn prop="iccid" label="ICCID" min-width="180" />
|
|
||||||
<ElTableColumn prop="msisdn" label="MSISDN" min-width="120" />
|
|
||||||
<ElTableColumn prop="slot_position" label="卡槽位置" width="100" align="center" />
|
|
||||||
<ElTableColumn label="网络状态" width="100" align="center">
|
|
||||||
<template #default="scope">
|
|
||||||
<ElTag
|
|
||||||
:type="scope.row.network_status === 1 ? 'success' : 'danger'"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{{ scope.row.network_status === 1 ? '开机' : '停机' }}
|
|
||||||
</ElTag>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn label="实名状态" width="100" align="center">
|
|
||||||
<template #default="scope">
|
|
||||||
<ElTag :type="getRealNameStatusType(scope.row.real_name_status)" size="small">
|
|
||||||
{{ getRealNameStatusName(scope.row.real_name_status) }}
|
|
||||||
</ElTag>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
</ElTable>
|
|
||||||
<ElEmpty v-else description="暂无绑定卡" :image-size="100" />
|
|
||||||
</ElCard>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 第五行:套餐列表 -->
|
|
||||||
<div class="row full-width">
|
<div class="row full-width">
|
||||||
<ElCard shadow="never" class="info-card package-info">
|
<ElCard shadow="never" class="info-card package-info">
|
||||||
<template #header>
|
<template #header>
|
||||||
|
|||||||
Reference in New Issue
Block a user