修改资产详情
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 2m35s

This commit is contained in:
sexygoat
2026-03-20 10:26:57 +08:00
parent f06d8c9133
commit 62f828f314
5 changed files with 457 additions and 1207 deletions

View File

@@ -1,188 +1,50 @@
<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'"
>
取消换货
<ElCard shadow="never">
<!-- 页面头部 -->
<div class="detail-header">
<ElButton @click="handleBack">
<template #icon>
<ElIcon><ArrowLeft /></ElIcon>
</template>
返回
</ElButton>
<h2 class="detail-title">换货单详情</h2>
</div>
<ElButton
v-if="exchangeDetail.status === 2"
@click="handleRenew"
v-permission="'exchange:renew'"
>
旧资产转新
</ElButton>
<!-- 详情内容 -->
<DetailPage v-if="exchangeDetail" :sections="detailSections" :data="exchangeDetail" />
<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 v-if="loading" class="loading-container">
<ElIcon class="is-loading"><Loading /></ElIcon>
<span>加载中...</span>
</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 { h } from 'vue'
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 { ElTag, ElCard, ElButton, ElIcon } from 'element-plus'
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
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' })
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',
@@ -194,6 +56,102 @@
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 () => {
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 = () => {
router.back()
@@ -314,17 +183,34 @@
<style lang="scss" scoped>
.exchange-detail-page {
height: 100%;
padding: 20px;
.page-header-title {
font-size: 18px;
font-weight: bold;
.detail-header {
display: flex;
align-items: center;
margin-bottom: 20px;
.detail-title {
margin-left: 16px;
font-size: 18px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
.card-header {
.loading-container {
display: flex;
justify-content: space-between;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 0;
color: var(--el-text-color-secondary);
.el-icon {
margin-bottom: 8px;
font-size: 40px;
}
}
}
</style>

View File

@@ -40,12 +40,26 @@
:marginTop="10"
@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"
@@ -99,6 +113,50 @@
</div>
</template>
</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>
</div>
</ArtTableFullScreen>
@@ -109,20 +167,38 @@
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 { ElMessage, ElTag, ElButton, ElMessageBox, ElSwitch } 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 { 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)
const shipFormRef = ref<FormInstance>()
const createDialogVisible = ref(false)
const createLoading = ref(false)
const tableRef = ref()
@@ -152,6 +228,20 @@
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({
page: 1,
@@ -206,7 +296,16 @@
prop: 'exchange_no',
label: '换货单号',
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',
@@ -244,22 +343,6 @@
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) => {
@@ -372,6 +455,188 @@
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(() => {
loadExchangeList()