新增
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m7s

This commit is contained in:
sexygoat
2026-03-19 18:32:02 +08:00
parent 407287f538
commit f06d8c9133
22 changed files with 2260 additions and 117 deletions

View File

@@ -452,7 +452,7 @@
label-width="120px"
>
<ElFormItem label="设备号">
<span style="font-weight: bold; color: #409eff">{{ currentOperatingDevice }}</span>
<span style="font-weight: bold; color: #409eff">{{ currentOperatingImei }}</span>
</ElFormItem>
<ElFormItem label="下行速率" prop="download_speed">
<ElInputNumber
@@ -484,27 +484,70 @@
</ElDialog>
<!-- 切换SIM卡对话框 -->
<ElDialog v-model="switchCardDialogVisible" title="切换SIM卡" width="500px">
<ElForm
ref="switchCardFormRef"
:model="switchCardForm"
:rules="switchCardRules"
label-width="120px"
>
<ElFormItem label="设备号">
<span style="font-weight: bold; color: #409eff">{{ currentOperatingDevice }}</span>
</ElFormItem>
<ElFormItem label="目标ICCID" prop="target_iccid">
<ElInput
v-model="switchCardForm.target_iccid"
placeholder="请输入要切换到的目标ICCID"
clearable
/>
</ElFormItem>
</ElForm>
<ElDialog v-model="switchCardDialogVisible" title="切换SIM卡" width="600px">
<div v-if="loadingDeviceCards" style="text-align: center; padding: 40px">
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
<div style="margin-top: 16px">加载设备绑定的卡列表中...</div>
</div>
<template v-else>
<ElAlert
v-if="deviceBindingCards.length === 0"
title="该设备暂无绑定的SIM卡"
type="warning"
:closable="false"
style="margin-bottom: 20px"
>
<template #default>
<div>当前设备没有绑定任何SIM卡,无法进行切换操作</div>
<div>请先为设备绑定SIM卡后再进行切换</div>
</template>
</ElAlert>
<ElForm
v-if="deviceBindingCards.length > 0"
ref="switchCardFormRef"
:model="switchCardForm"
:rules="switchCardRules"
label-width="120px"
>
<ElFormItem label="设备号">
<span style="font-weight: bold; color: #409eff">{{ currentOperatingImei }}</span>
</ElFormItem>
<ElFormItem label="目标ICCID" prop="target_iccid">
<ElSelect
v-model="switchCardForm.target_iccid"
placeholder="请选择要切换到的目标ICCID"
style="width: 100%"
clearable
>
<ElOption
v-for="card in deviceBindingCards"
:key="card.iccid"
:label="`${card.iccid} - 插槽${card.slot_position} - ${card.carrier_name}`"
:value="card.iccid"
>
<div style="display: flex; justify-content: space-between; align-items: center">
<span>{{ card.iccid }}</span>
<ElTag size="small" style="margin-left: 10px">插槽{{ card.slot_position }}</ElTag>
</div>
</ElOption>
</ElSelect>
<div style="margin-top: 8px; font-size: 12px; color: #909399">
当前设备共绑定 {{ deviceBindingCards.length }} 张SIM卡
</div>
</ElFormItem>
</ElForm>
</template>
<template #footer>
<ElButton @click="switchCardDialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleConfirmSwitchCard" :loading="switchCardLoading">
<ElButton
v-if="deviceBindingCards.length > 0"
type="primary"
@click="handleConfirmSwitchCard"
:loading="switchCardLoading"
>
确认切换
</ElButton>
</template>
@@ -519,7 +562,7 @@
label-width="120px"
>
<ElFormItem label="设备号">
<span style="font-weight: bold; color: #409eff">{{ currentOperatingDevice }}</span>
<span style="font-weight: bold; color: #409eff">{{ currentOperatingImei }}</span>
</ElFormItem>
<ElFormItem label="WiFi状态" prop="enabled">
<ElRadioGroup v-model="setWiFiForm.enabled">
@@ -683,11 +726,12 @@
{ type: 'number', min: 1, message: '速率不能小于1KB/s', trigger: 'blur' }
]
})
const currentOperatingDevice = ref<string>('')
// 设备操作右键菜单
const deviceOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
const currentOperatingDeviceNo = ref<string>('')
const currentOperatingDevice = ref<Device | null>(null)
const currentOperatingImei = ref<string>('') // 用于存储当前操作设备的IMEI
const switchCardDialogVisible = ref(false)
const switchCardLoading = ref(false)
@@ -696,8 +740,10 @@
target_iccid: ''
})
const switchCardRules = reactive<FormRules>({
target_iccid: [{ required: true, message: '请输入目标ICCID', trigger: 'blur' }]
target_iccid: [{ required: true, message: '请选择目标ICCID', trigger: 'change' }]
})
const deviceBindingCards = ref<any[]>([]) // 设备绑定的卡列表
const loadingDeviceCards = ref(false) // 加载设备绑定卡列表的状态
const setWiFiDialogVisible = ref(false)
const setWiFiLoading = ref(false)
@@ -1454,6 +1500,9 @@
case 'set-wifi':
showSetWiFiDialog(deviceNo)
break
case 'manual-deactivate':
handleManualDeactivateDevice()
break
case 'delete':
handleDeleteDeviceByNo(deviceNo)
break
@@ -1470,6 +1519,39 @@
}
}
// 手动停用设备(资产层面的停用)
const handleManualDeactivateDevice = () => {
const device = currentOperatingDevice.value
if (!device) {
ElMessage.error('未找到设备信息')
return
}
ElMessageBox.confirm(
`确定要手动停用设备 ${device.virtual_no} 吗?此操作将在资产管理层面停用该设备。`,
'确认手动停用',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
.then(async () => {
try {
const res = await DeviceService.deactivateDevice(device.id)
if (res.code === 0) {
ElMessage.success('手动停用成功')
loadDeviceList()
}
} catch (error: any) {
console.error('手动停用失败:', error)
}
})
.catch(() => {
// 用户取消
})
}
// 通过设备号删除设备
const handleDeleteDeviceByNo = async (deviceNo: string) => {
// 先根据设备号找到设备对象
@@ -1537,7 +1619,7 @@
// 显示设置限速对话框
const showSpeedLimitDialog = (imei: string) => {
currentOperatingDevice.value = imei
currentOperatingImei.value = imei
speedLimitForm.download_speed = 1024
speedLimitForm.upload_speed = 512
speedLimitDialogVisible.value = true
@@ -1551,7 +1633,7 @@
if (valid) {
speedLimitLoading.value = true
try {
const res = await DeviceService.setSpeedLimit(currentOperatingDevice.value, {
const res = await DeviceService.setSpeedLimit(currentOperatingImei.value, {
download_speed: speedLimitForm.download_speed,
upload_speed: speedLimitForm.upload_speed
})
@@ -1572,10 +1654,35 @@
}
// 显示切换SIM卡对话框
const showSwitchCardDialog = (imei: string) => {
currentOperatingDevice.value = imei
const showSwitchCardDialog = async (deviceNo: string) => {
currentOperatingImei.value = deviceNo
switchCardForm.target_iccid = ''
deviceBindingCards.value = []
switchCardDialogVisible.value = true
// 使用当前操作的设备
const device = currentOperatingDevice.value
if (!device) {
ElMessage.error('未找到设备信息')
return
}
// 加载设备绑定的卡列表
loadingDeviceCards.value = true
try {
const res = await DeviceService.getDeviceCards(device.id)
if (res.code === 0 && res.data) {
deviceBindingCards.value = res.data.bindings || []
if (deviceBindingCards.value.length === 0) {
ElMessage.warning('该设备暂无绑定的SIM卡')
}
}
} catch (error) {
console.error('加载设备绑定卡列表失败:', error)
ElMessage.error('加载卡列表失败')
} finally {
loadingDeviceCards.value = false
}
}
// 确认切换SIM卡
@@ -1586,7 +1693,7 @@
if (valid) {
switchCardLoading.value = true
try {
const res = await DeviceService.switchCard(currentOperatingDevice.value, {
const res = await DeviceService.switchCard(currentOperatingImei.value, {
target_iccid: switchCardForm.target_iccid
})
if (res.code === 0) {
@@ -1607,7 +1714,7 @@
// 显示设置WiFi对话框
const showSetWiFiDialog = (imei: string) => {
currentOperatingDevice.value = imei
currentOperatingImei.value = imei
setWiFiForm.enabled = 1
setWiFiForm.ssid = ''
setWiFiForm.password = ''
@@ -1622,7 +1729,7 @@
if (valid) {
setWiFiLoading.value = true
try {
const res = await DeviceService.setWiFi(currentOperatingDevice.value, {
const res = await DeviceService.setWiFi(currentOperatingImei.value, {
enabled: setWiFiForm.enabled,
ssid: setWiFiForm.ssid,
password: setWiFiForm.password
@@ -1690,6 +1797,13 @@
})
}
if (hasAuth('device:manual_deactivate')) {
items.push({
key: 'manual-deactivate',
label: '手动停用'
})
}
if (hasAuth('device:delete')) {
items.push({
key: 'delete',
@@ -1701,10 +1815,11 @@
})
// 显示设备操作菜单
const showDeviceOperationMenu = (e: MouseEvent, deviceNo: string) => {
const showDeviceOperationMenu = (e: MouseEvent, deviceNo: string, device?: Device) => {
e.preventDefault()
e.stopPropagation()
currentOperatingDeviceNo.value = deviceNo
currentOperatingDevice.value = device || null
deviceOperationMenuRef.value?.show(e)
}
@@ -1718,7 +1833,7 @@
// 处理表格行右键菜单
const handleRowContextMenu = (row: Device, column: any, event: MouseEvent) => {
showDeviceOperationMenu(event, row.virtual_no)
showDeviceOperationMenu(event, row.virtual_no, row)
}
</script>

View File

@@ -0,0 +1,330 @@
<template>
<div class="exchange-detail-page">
<ElPageHeader @back="handleBack">
<template #content>
<span class="page-header-title">换货单详情</span>
</template>
</ElPageHeader>
<ElCard v-loading="loading" style="margin-top: 20px">
<template #header>
<div class="card-header">
<span>换货单信息</span>
</div>
</template>
<ElDescriptions v-if="exchangeDetail" :column="2" border>
<ElDescriptionsItem label="换货单号">
{{ exchangeDetail.exchange_no }}
</ElDescriptionsItem>
<ElDescriptionsItem label="状态">
<ElTag :type="getStatusType(exchangeDetail.status)">
{{ exchangeDetail.status_text }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="换货原因" :span="2">
{{ exchangeDetail.exchange_reason }}
</ElDescriptionsItem>
<ElDescriptionsItem label="旧资产类型">
{{ exchangeDetail.old_asset_type === 'iot_card' ? 'IoT卡' : '设备' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="旧资产标识符">
{{ exchangeDetail.old_asset_identifier }}
</ElDescriptionsItem>
<ElDescriptionsItem label="新资产类型">
{{
exchangeDetail.new_asset_type
? exchangeDetail.new_asset_type === 'iot_card'
? 'IoT卡'
: '设备'
: '--'
}}
</ElDescriptionsItem>
<ElDescriptionsItem label="新资产标识符">
{{ exchangeDetail.new_asset_identifier || '--' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="收货人姓名">
{{ exchangeDetail.recipient_name || '--' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="收货人电话">
{{ exchangeDetail.recipient_phone || '--' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="收货地址" :span="2">
{{ exchangeDetail.recipient_address || '--' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="快递公司">
{{ exchangeDetail.express_company || '--' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="快递单号">
{{ exchangeDetail.express_no || '--' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="备注" :span="2">
{{ exchangeDetail.remark || '--' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间">
{{ formatDateTime(exchangeDetail.created_at) }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间">
{{ formatDateTime(exchangeDetail.updated_at) }}
</ElDescriptionsItem>
</ElDescriptions>
<ElEmpty v-else description="未找到换货单信息" />
<!-- 操作按钮 -->
<div v-if="exchangeDetail" style="margin-top: 20px; text-align: center">
<ElButton
v-if="exchangeDetail.status === 1"
type="warning"
@click="handleCancel"
v-permission="'exchange:cancel'"
>
取消换货
</ElButton>
<ElButton
v-if="exchangeDetail.status === 2"
@click="handleRenew"
v-permission="'exchange:renew'"
>
旧资产转新
</ElButton>
<ElButton
v-if="exchangeDetail.status === 2"
type="primary"
@click="showShipDialog"
v-permission="'exchange:ship'"
>
发货
</ElButton>
<ElButton
v-if="exchangeDetail.status === 3"
type="success"
@click="handleComplete"
v-permission="'exchange:complete'"
>
确认完成
</ElButton>
</div>
</ElCard>
<!-- 发货对话框 -->
<ElDialog v-model="shipDialogVisible" title="换货发货" width="600px">
<ElForm ref="shipFormRef" :model="shipForm" :rules="shipRules" label-width="120px">
<ElFormItem label="新资产标识符" prop="new_identifier">
<ElInput v-model="shipForm.new_identifier" placeholder="请输入新资产标识符(ICCID/虚拟号/IMEI/SN)" />
</ElFormItem>
<ElFormItem label="快递公司" prop="express_company">
<ElInput v-model="shipForm.express_company" placeholder="请输入快递公司" />
</ElFormItem>
<ElFormItem label="快递单号" prop="express_no">
<ElInput v-model="shipForm.express_no" placeholder="请输入快递单号" />
</ElFormItem>
<ElFormItem label="是否迁移数据">
<ElSwitch v-model="shipForm.migrate_data" />
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="shipDialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleConfirmShip" :loading="shipLoading">
确认发货
</ElButton>
</div>
</template>
</ElDialog>
</div>
</template>
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
import { ExchangeService } from '@/api/modules'
import type { ExchangeResponse } from '@/api/modules/exchange'
import { ElMessage, ElMessageBox, ElTag, ElSwitch } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { formatDateTime } from '@/utils/business/format'
import { useAuth } from '@/composables/useAuth'
defineOptions({ name: 'ExchangeDetail' })
const route = useRoute()
const router = useRouter()
const { hasAuth } = useAuth()
const loading = ref(false)
const shipDialogVisible = ref(false)
const shipLoading = ref(false)
const shipFormRef = ref<FormInstance>()
const exchangeDetail = ref<ExchangeResponse | null>(null)
const exchangeId = ref<number>(0)
const shipForm = reactive({
new_identifier: '',
express_company: '',
express_no: '',
migrate_data: true
})
const shipRules = reactive<FormRules>({
new_identifier: [{ required: true, message: '请输入新资产标识符', trigger: 'blur' }],
express_company: [{ required: true, message: '请输入快递公司', trigger: 'blur' }],
express_no: [{ required: true, message: '请输入快递单号', trigger: 'blur' }]
})
const getStatusType = (status: number) => {
const types: Record<number, string> = {
1: 'warning',
2: 'info',
3: 'primary',
4: 'success',
5: 'danger'
}
return types[status] || 'info'
}
// 加载换货单详情
const loadExchangeDetail = async () => {
loading.value = true
try {
const res = await ExchangeService.getExchangeDetail(exchangeId.value)
if (res.code === 0 && res.data) {
exchangeDetail.value = res.data
}
} catch (error) {
console.error('加载换货单详情失败:', error)
} finally {
loading.value = false
}
}
// 取消换货
const handleCancel = () => {
ElMessageBox.prompt('请输入取消备注', '取消换货', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputType: 'textarea'
})
.then(async ({ value }) => {
try {
const res = await ExchangeService.cancelExchange(exchangeId.value, { remark: value })
if (res.code === 0) {
ElMessage.success('取消成功')
loadExchangeDetail()
}
} catch (error) {
console.error('取消换货失败:', error)
}
})
.catch(() => {})
}
// 旧资产转新
const handleRenew = () => {
ElMessageBox.confirm('确定要将旧资产转为新资产吗?', '确认操作', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
try {
const res = await ExchangeService.renewExchange(exchangeId.value)
if (res.code === 0) {
ElMessage.success('操作成功')
loadExchangeDetail()
}
} catch (error) {
console.error('旧资产转新失败:', error)
}
})
.catch(() => {})
}
// 显示发货对话框
const showShipDialog = () => {
shipDialogVisible.value = true
}
// 确认发货
const handleConfirmShip = () => {
shipFormRef.value?.validate(async (valid) => {
if (!valid) return
shipLoading.value = true
try {
const res = await ExchangeService.shipExchange(exchangeId.value, shipForm)
if (res.code === 0) {
ElMessage.success('发货成功')
shipDialogVisible.value = false
loadExchangeDetail()
}
} catch (error) {
console.error('发货失败:', error)
} finally {
shipLoading.value = false
}
})
}
// 确认完成
const handleComplete = () => {
ElMessageBox.confirm('确定要确认换货完成吗?', '确认操作', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
try {
const res = await ExchangeService.completeExchange(exchangeId.value)
if (res.code === 0) {
ElMessage.success('操作成功')
loadExchangeDetail()
}
} catch (error) {
console.error('确认完成失败:', error)
}
})
.catch(() => {})
}
// 返回
const handleBack = () => {
router.back()
}
// 初始化
onMounted(() => {
exchangeId.value = Number(route.params.id)
if (exchangeId.value) {
loadExchangeDetail()
}
})
</script>
<style lang="scss" scoped>
.exchange-detail-page {
padding: 20px;
.page-header-title {
font-size: 18px;
font-weight: bold;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
}
</style>

View File

@@ -0,0 +1,385 @@
<template>
<ArtTableFullScreen>
<div class="exchange-management-page" id="table-full-screen">
<!-- 搜索栏 -->
<ArtSearchBar
v-model:filter="searchForm"
:items="searchFormItems"
:show-expand="false"
label-width="90"
@reset="handleReset"
@search="handleSearch"
></ArtSearchBar>
<ElCard shadow="never" class="art-table-card">
<!-- 表格头部 -->
<ArtTableHeader
v-model:columns="columnChecks"
@refresh="handleRefresh"
>
<template #left>
<ElButton
type="primary"
@click="showCreateDialog"
v-permission="'exchange:create'"
>
创建换货单
</ElButton>
</template>
</ArtTableHeader>
<!-- 表格 -->
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="exchangeList"
:currentPage="pagination.page"
:pageSize="pagination.page_size"
:total="pagination.total"
:marginTop="10"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
</template>
</ArtTable>
<!-- 创建换货单对话框 -->
<ElDialog
v-model="createDialogVisible"
title="创建换货单"
width="600px"
:close-on-click-modal="false"
@closed="handleCloseCreateDialog"
>
<ElForm
ref="createFormRef"
:model="createForm"
:rules="createRules"
label-width="120px"
>
<ElFormItem label="换货原因" prop="exchange_reason">
<ElInput
v-model="createForm.exchange_reason"
type="textarea"
:rows="3"
placeholder="请输入换货原因"
/>
</ElFormItem>
<ElFormItem label="旧资产类型" prop="old_asset_type">
<ElSelect v-model="createForm.old_asset_type" placeholder="请选择旧资产类型" style="width: 100%">
<ElOption label="IoT卡" value="iot_card" />
<ElOption label="设备" value="device" />
</ElSelect>
</ElFormItem>
<ElFormItem label="旧资产标识符" prop="old_identifier">
<ElInput
v-model="createForm.old_identifier"
placeholder="请输入ICCID/虚拟号/IMEI/SN"
/>
</ElFormItem>
<ElFormItem label="备注">
<ElInput
v-model="createForm.remark"
type="textarea"
:rows="2"
placeholder="请输入备注(可选)"
/>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="createDialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleConfirmCreate" :loading="createLoading">
确认创建
</ElButton>
</div>
</template>
</ElDialog>
</ElCard>
</div>
</ArtTableFullScreen>
</template>
<script setup lang="ts">
import { h } from 'vue'
import { useRouter } from 'vue-router'
import { ExchangeService } from '@/api/modules'
import type { ExchangeResponse } from '@/api/modules/exchange'
import { ElMessage, ElTag, ElButton } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import type { SearchFormItem } from '@/types'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { useAuth } from '@/composables/useAuth'
import { formatDateTime } from '@/utils/business/format'
import { RoutesAlias } from '@/router/routesAlias'
defineOptions({ name: 'ExchangeManagement' })
const { hasAuth } = useAuth()
const router = useRouter()
const loading = ref(false)
const createDialogVisible = ref(false)
const createLoading = ref(false)
const tableRef = ref()
const createFormRef = ref<FormInstance>()
const exchangeList = ref<ExchangeResponse[]>([])
// 搜索表单
const searchForm = reactive({
status: undefined,
identifier: '',
created_at_start: '',
created_at_end: ''
})
// 创建换货单表单
const createForm = reactive({
exchange_reason: '',
old_asset_type: '',
old_identifier: '',
remark: ''
})
const createRules = reactive<FormRules>({
exchange_reason: [{ required: true, message: '请输入换货原因', trigger: 'blur' }],
old_asset_type: [{ required: true, message: '请选择旧资产类型', trigger: 'change' }],
old_identifier: [{ required: true, message: '请输入旧资产标识符', trigger: 'blur' }]
})
// 分页
const pagination = reactive({
page: 1,
page_size: 20,
total: 0
})
// 搜索表单配置
const searchFormItems: SearchFormItem[] = [
{
label: '换货状态',
prop: 'status',
type: 'select',
config: {
clearable: true,
placeholder: '全部'
},
options: () => [
{ label: '待填写信息', value: 1 },
{ label: '待发货', value: 2 },
{ label: '已发货待确认', value: 3 },
{ label: '已完成', value: 4 },
{ label: '已取消', value: 5 }
]
},
{
label: '资产标识符',
prop: 'identifier',
type: 'input',
config: {
placeholder: '请输入资产标识符',
clearable: true
}
},
{
label: '创建时间',
prop: 'created_at_range',
type: 'date',
config: {
type: 'datetimerange',
rangeSeparator: '至',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
valueFormat: 'YYYY-MM-DD HH:mm:ss'
}
}
]
// 动态列配置
const { columnChecks, columns } = useCheckedColumns(() => [
{
prop: 'exchange_no',
label: '换货单号',
width: 220,
formatter: (row: any) => row.exchange_no || '--'
},
{
prop: 'exchange_reason',
label: '换货原因',
minWidth: 150,
showOverflowTooltip: true
},
{
prop: 'old_asset_type',
label: '旧资产类型',
width: 120,
formatter: (row: any) => (row.old_asset_type === 'iot_card' ? 'IoT卡' : '设备')
},
{
prop: 'old_asset_identifier',
label: '旧资产标识符',
width: 220
},
{
prop: 'new_asset_identifier',
label: '新资产标识符',
width: 180,
formatter: (row: any) => row.new_asset_identifier || '--'
},
{
prop: 'status',
label: '状态',
width: 130,
formatter: (row: any) =>
h(ElTag, { type: getStatusType(row.status) }, () => row.status_text)
},
{
prop: 'created_at',
label: '创建时间',
width: 180,
formatter: (row: any) => formatDateTime(row.created_at)
},
{
prop: 'action',
label: '操作',
width: 120,
fixed: 'right',
formatter: (row: any) =>
h(
ElButton,
{
type: 'primary',
link: true,
onClick: () => handleViewDetail(row)
},
() => '查看详情'
)
}
])
const getStatusType = (status: number) => {
const types: Record<number, string> = {
1: 'warning',
2: 'info',
3: 'primary',
4: 'success',
5: 'danger'
}
return types[status] || 'info'
}
// 加载换货单列表
const loadExchangeList = async () => {
loading.value = true
try {
// 过滤掉空值参数
const params: any = {
page: pagination.page,
page_size: pagination.page_size
}
if (searchForm.status !== undefined && searchForm.status !== null) {
params.status = searchForm.status
}
if (searchForm.identifier) {
params.identifier = searchForm.identifier
}
if (searchForm.created_at_start) {
params.created_at_start = searchForm.created_at_start
}
if (searchForm.created_at_end) {
params.created_at_end = searchForm.created_at_end
}
const res = await ExchangeService.getExchanges(params)
if (res.code === 0 && res.data) {
exchangeList.value = res.data.list || []
pagination.total = res.data.total || 0
}
} catch (error) {
console.error('加载换货单列表失败:', error)
} finally {
loading.value = false
}
}
// 搜索
const handleSearch = () => {
pagination.page = 1
loadExchangeList()
}
// 重置
const handleReset = () => {
pagination.page = 1
loadExchangeList()
}
// 刷新
const handleRefresh = () => {
loadExchangeList()
}
// 分页
const handleSizeChange = (size: number) => {
pagination.page_size = size
loadExchangeList()
}
const handleCurrentChange = (page: number) => {
pagination.page = page
loadExchangeList()
}
// 显示创建对话框
const showCreateDialog = () => {
createDialogVisible.value = true
}
// 确认创建
const handleConfirmCreate = () => {
createFormRef.value?.validate(async (valid) => {
if (!valid) return
createLoading.value = true
try {
const res = await ExchangeService.createExchange(createForm)
if (res.code === 0) {
ElMessage.success('换货单创建成功')
createDialogVisible.value = false
loadExchangeList()
}
} catch (error) {
console.error('创建换货单失败:', error)
} finally {
createLoading.value = false
}
})
}
// 关闭创建对话框
const handleCloseCreateDialog = () => {
createFormRef.value?.resetFields()
}
// 查看详情
const handleViewDetail = (row: any) => {
router.push(`${RoutesAlias.ExchangeDetail}/${row.id}`)
}
// 初始化
onMounted(() => {
loadExchangeList()
})
</script>
<style lang="scss" scoped>
.exchange-management-page {
height: 100%;
}
</style>

View File

@@ -662,6 +662,7 @@
const moreMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
const cardOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
const currentOperatingIccid = ref<string>('')
const currentOperatingCard = ref<StandaloneIotCard | null>(null)
// 店铺相关
const targetShopLoading = ref(false)
@@ -1563,6 +1564,13 @@
})
}
if (hasAuth('iot_card:manual_deactivate')) {
items.push({
key: 'manual-deactivate',
label: '手动停用'
})
}
return items
})
@@ -1601,10 +1609,11 @@
}
// 显示卡操作菜单
const showCardOperationMenu = (e: MouseEvent, iccid: string) => {
const showCardOperationMenu = (e: MouseEvent, iccid: string, card?: StandaloneIotCard) => {
e.preventDefault()
e.stopPropagation()
currentOperatingIccid.value = iccid
currentOperatingCard.value = card || null
cardOperationMenuRef.value?.show(e)
}
@@ -1660,6 +1669,9 @@
case 'stop-card':
handleStopCard(iccid)
break
case 'manual-deactivate':
handleManualDeactivate()
break
}
}
@@ -1810,9 +1822,42 @@
})
}
// 手动停用 IoT 卡(资产层面的停用)
const handleManualDeactivate = () => {
const card = currentOperatingCard.value
if (!card) {
ElMessage.error('未找到卡片信息')
return
}
ElMessageBox.confirm(
`确定要手动停用卡片 ${card.iccid} 吗?此操作将在资产管理层面停用该卡。`,
'确认手动停用',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
.then(async () => {
try {
const res = await CardService.deactivateIotCard(card.id)
if (res.code === 0) {
ElMessage.success('手动停用成功')
getTableData()
}
} catch (error: any) {
console.error('手动停用失败:', error)
}
})
.catch(() => {
// 用户取消
})
}
// 处理表格行右键菜单
const handleRowContextMenu = (row: StandaloneIotCard, column: any, event: MouseEvent) => {
showCardOperationMenu(event, row.iccid)
showCardOperationMenu(event, row.iccid, row)
}
</script>