Files
my-mom-system/erp-frontend-vue/src/views/Sales/Order/view.vue
2026-02-27 23:50:25 +08:00

636 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="view-container">
<!-- 顶部操作栏 -->
<div class="page-header">
<div class="header-left">
<el-button @click="handleBack" :icon="ArrowLeft">返回</el-button>
<span class="page-title">销售订单详情</span>
<el-tag :type="getStatusType(orderData.orderStatus)" class="status-tag">
{{ getStatusLabel(orderData.orderStatus) }}
</el-tag>
</div>
<div class="header-right">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增
</el-button>
<el-button
:disabled="orderData.orderStatus === '审核'"
@click="handleEdit"
>
<el-icon><Edit /></el-icon>编辑
</el-button>
<el-button
v-if="orderData.orderStatus === '开立'"
type="success"
@click="handleAudit"
>
<el-icon><Finished /></el-icon>审核
</el-button>
<el-button
v-if="orderData.orderStatus === '审核'"
type="warning"
@click="handleUnaudit"
>
反审核
</el-button>
<el-button @click="handlePrint">
<el-icon><Printer /></el-icon>打印
</el-button>
<el-dropdown trigger="click">
<el-button>
操作<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="handleExport">导出</el-dropdown-item>
<el-dropdown-item @click="handleCopy">复制</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button @click="toggleCollapse">
{{ isCollapsed ? '展开' : '收起' }}
</el-button>
</div>
</div>
<el-card shadow="never" class="main-card" v-loading="loading">
<!-- 基础信息区域 -->
<el-collapse v-model="activeCollapse">
<el-collapse-item title="基础信息" name="basic">
<!-- 第一行单据信息 -->
<el-row :gutter="16" class="info-row">
<el-col :span="6">
<div class="info-item">
<span class="info-label">单据编码</span>
<span class="info-value">{{ orderData.orderCode || '-' }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="info-label">单据日期</span>
<span class="info-value">{{ orderData.orderDate || '-' }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="info-label">单据状态</span>
<el-tag :type="getStatusType(orderData.orderStatus)">
{{ getStatusLabel(orderData.orderStatus) }}
</el-tag>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="info-label">单据类型</span>
<span class="info-value">销售订单</span>
</div>
</el-col>
</el-row>
<!-- 第二行业务信息 -->
<el-row :gutter="16" class="info-row">
<el-col :span="6">
<div class="info-item">
<span class="info-label">业务类型</span>
<span class="info-value">{{ getBizTypeLabel(orderData.bizType) }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="info-label">业务状态</span>
<span class="info-value">{{ getBizStatusLabel(orderData.bizStatus) }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="info-label">销售部门</span>
<span class="info-value">{{ orderData.deptName || '-' }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="info-label">销售人员</span>
<span class="info-value">{{ orderData.salesmanName || '-' }}</span>
</div>
</el-col>
</el-row>
<!-- 第三行操作人员 -->
<el-row :gutter="16" class="info-row">
<el-col :span="6">
<div class="info-item">
<span class="info-label">操作员</span>
<span class="info-value">{{ orderData.operatorName || '-' }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="info-label">审核员</span>
<span class="info-value">{{ orderData.auditorName || '-' }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="info-label">审核日期</span>
<span class="info-value">{{ orderData.auditDate || '-' }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="info-label">销售类型</span>
<span class="info-value">{{ getSalesTypeLabel(orderData.salesType) }}</span>
</div>
</el-col>
</el-row>
<!-- 第四行供货方式 -->
<el-row :gutter="16" class="info-row">
<el-col :span="6">
<div class="info-item">
<span class="info-label">供货方式</span>
<span class="info-value">{{ getSupplyModeLabel(orderData.supplyMode) }}</span>
</div>
</el-col>
</el-row>
<!-- 第五行客户信息 -->
<el-row :gutter="16" class="info-row">
<el-col :span="6">
<div class="info-item">
<span class="info-label">客户名称</span>
<span class="info-value highlight">{{ orderData.clientName || '-' }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="info-label">合同号</span>
<span class="info-value">
<el-link v-if="orderData.contractNo" type="primary">
{{ orderData.contractNo }}
</el-link>
<span v-else>-</span>
</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="info-label">付款条件</span>
<span class="info-value">{{ orderData.paymentTerms || '-' }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="info-label">交付日期</span>
<span class="info-value">{{ orderData.deliveryDate || '-' }}</span>
</div>
</el-col>
</el-row>
<!-- 第六行交期/收货信息 -->
<el-row :gutter="16" class="info-row">
<el-col :span="6">
<div class="info-item">
<span class="info-label">交期状态</span>
<span class="info-value">{{ getDeliveryStatusLabel(orderData.deliveryStatus) }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="info-label">收货人</span>
<span class="info-value">{{ orderData.receiver || '-' }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="info-label">电话</span>
<span class="info-value">{{ orderData.receiverPhone || '-' }}</span>
</div>
</el-col>
<el-col :span="6">
<div class="info-item">
<span class="info-label">目的国家</span>
<span class="info-value">{{ orderData.destCountry || '-' }}</span>
</div>
</el-col>
</el-row>
<!-- 第七行地址/备注 -->
<el-row :gutter="16" class="info-row">
<el-col :span="12">
<div class="info-item">
<span class="info-label">地址</span>
<span class="info-value">{{ orderData.receiverAddress || '-' }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<span class="info-label">备注信息</span>
<span class="info-value">{{ orderData.remark || '-' }}</span>
</div>
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
<!-- 物料明细区域 -->
<div class="material-section">
<div class="section-header">
<span class="section-title">物料信息</span>
</div>
<el-table
:data="orderData.lines"
stripe
border
style="width: 100%"
max-height="400"
>
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="itemCode" label="物料编码" width="130" />
<el-table-column prop="itemName" label="物料名称" min-width="180" show-overflow-tooltip />
<el-table-column prop="specification" label="型号规格" width="150" show-overflow-tooltip />
<el-table-column prop="unitOfMeasure" label="主计量" width="80" />
<el-table-column prop="quantity" label="数量" width="100" align="right">
<template #default="{ row }">
{{ formatNumber(row.quantity) }}
</template>
</el-table-column>
<el-table-column prop="unitPrice" label="单价" width="120" align="right">
<template #default="{ row }">
{{ formatCurrency(row.unitPrice) }}
</template>
</el-table-column>
<el-table-column prop="amount" label="金额" width="120" align="right">
<template #default="{ row }">
{{ formatCurrency(row.amount) }}
</template>
</el-table-column>
<el-table-column prop="qualityReq" label="质量要求" width="150" show-overflow-tooltip>
<template #default="{ row }">
{{ row.qualityReq || '-' }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip>
<template #default="{ row }">
{{ row.remark || '-' }}
</template>
</el-table-column>
</el-table>
<!-- 合计区域 -->
<div class="summary-row">
<div class="summary-item">
<span>物料行数</span>
<span class="summary-value">{{ orderData.lines?.length || 0 }}</span>
</div>
<div class="summary-item">
<span>总数量</span>
<span class="summary-value">{{ formatNumber(totalQuantity) }}</span>
</div>
<div class="summary-item">
<span>合计金额</span>
<span class="total-amount">{{ formatCurrency(orderData.totalAmount) }}</span>
</div>
</div>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
ArrowLeft, ArrowDown, Plus, Edit, Finished, Printer
} from '@element-plus/icons-vue'
import {
getSalesOrderDetail,
auditSalesOrder,
unauditSalesOrder,
ORDER_STATUS_MAP,
SALES_TYPE_OPTIONS,
SUPPLY_MODE_OPTIONS,
DELIVERY_STATUS_OPTIONS,
BIZ_TYPE_OPTIONS,
BIZ_STATUS_OPTIONS,
type SalesOrder
} from '@/api/salesOrder'
const route = useRoute()
const router = useRouter()
// ============ 响应式状态 ============
const loading = ref(false)
const isCollapsed = ref(false)
const activeCollapse = ref(['basic'])
const orderData = ref<SalesOrder>({
orderId: undefined,
orderCode: '',
orderDate: '',
orderStatus: '',
bizType: '',
bizStatus: '',
salesType: '',
supplyMode: '',
deliveryStatus: '',
clientId: undefined,
clientCode: '',
clientName: '',
contractNo: '',
paymentTerms: '',
deliveryDate: '',
receiver: '',
receiverPhone: '',
receiverAddress: '',
destCountry: '',
deptId: undefined,
deptName: '',
salesmanId: undefined,
salesmanName: '',
operatorId: undefined,
operatorName: '',
auditorId: undefined,
auditorName: '',
auditDate: '',
totalAmount: 0,
currency: 'CNY',
remark: '',
lines: []
})
// ============ 计算属性 ============
const totalQuantity = computed(() => {
return (orderData.value.lines || []).reduce((sum, line) => sum + (line.quantity || 0), 0)
})
// ============ 方法 ============
function getStatusType(status?: string): string {
return ORDER_STATUS_MAP[status || '']?.type || ''
}
function getStatusLabel(status?: string): string {
return ORDER_STATUS_MAP[status || '']?.label || status || '-'
}
function getSalesTypeLabel(value?: string): string {
const item = SALES_TYPE_OPTIONS.find(opt => opt.value === value)
return item?.label || value || '-'
}
function getSupplyModeLabel(value?: string): string {
const item = SUPPLY_MODE_OPTIONS.find(opt => opt.value === value)
return item?.label || value || '-'
}
function getDeliveryStatusLabel(value?: string): string {
const item = DELIVERY_STATUS_OPTIONS.find(opt => opt.value === value)
return item?.label || value || '-'
}
function getBizTypeLabel(value?: string): string {
const item = BIZ_TYPE_OPTIONS.find(opt => opt.value === value)
return item?.label || value || '销售订单'
}
function getBizStatusLabel(value?: string): string {
const item = BIZ_STATUS_OPTIONS.find(opt => opt.value === value)
return item?.label || value || '正常'
}
function formatNumber(val: number | undefined): string {
if (val === undefined || val === null) return '-'
return val.toLocaleString()
}
function formatCurrency(val: number | undefined): string {
if (val === undefined || val === null) return '-'
return '¥' + val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
function toggleCollapse() {
isCollapsed.value = !isCollapsed.value
activeCollapse.value = isCollapsed.value ? [] : ['basic']
}
async function loadOrderData() {
const id = route.params.id as string
if (!id) {
ElMessage.error('订单ID不存在')
return
}
loading.value = true
try {
const data = await getSalesOrderDetail(Number(id))
// 确保明细行正确赋值(后端可能返回 slaveList
const lines = data.lines || data.slaveList || []
orderData.value = {
...data,
lines: lines
}
console.log('加载订单明细:', orderData.value.lines)
} catch (error) {
console.error('加载订单失败:', error)
ElMessage.error('加载订单数据失败')
} finally {
loading.value = false
}
}
function handleBack() {
router.push('/sales/order')
}
function handleAdd() {
router.push('/sales/order/form')
}
function handleEdit() {
router.push(`/sales/order/form/${orderData.value.orderId}`)
}
async function handleAudit() {
if (orderData.value?.orderId == null) {
ElMessage.warning('订单信息未加载,无法审核')
return
}
try {
await ElMessageBox.confirm('确认审核此订单吗?', '提示', { type: 'info' })
await auditSalesOrder(orderData.value.orderId)
ElMessage.success('审核成功')
loadOrderData()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error?.message || '审核失败')
}
}
}
async function handleUnaudit() {
if (orderData.value?.orderId == null) {
ElMessage.warning('订单信息未加载,无法反审核')
return
}
try {
await ElMessageBox.confirm('确认反审核此订单吗?', '警告', { type: 'warning' })
await unauditSalesOrder(orderData.value.orderId)
ElMessage.success('反审核成功')
loadOrderData()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error?.message || '反审核失败')
}
}
}
function handlePrint() {
ElMessage.info('打印功能开发中')
}
function handleExport() {
ElMessage.info('导出功能开发中')
}
function handleCopy() {
ElMessage.info('复制功能开发中')
}
// ============ 生命周期 ============
onMounted(() => {
loadOrderData()
})
</script>
<style scoped>
.view-container {
padding: 8px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background: #fff;
border-radius: 4px;
margin-bottom: 12px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.header-left {
display: flex;
align-items: center;
gap: 16px;
}
.page-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
.status-tag {
margin-left: 8px;
}
.header-right {
display: flex;
gap: 8px;
}
.main-card :deep(.el-card__body) {
padding: 16px;
}
.main-card :deep(.el-collapse-item__header) {
font-weight: 500;
font-size: 14px;
}
.main-card :deep(.el-collapse-item__content) {
padding-top: 16px;
}
.info-row {
margin-bottom: 16px;
}
.info-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.info-label {
font-size: 12px;
color: #909399;
}
.info-value {
font-size: 14px;
color: #303133;
}
.info-value.highlight {
color: #409eff;
font-weight: 500;
}
.material-section {
margin-top: 20px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
}
.section-title {
font-size: 14px;
font-weight: 500;
color: #303133;
}
.summary-row {
margin-top: 16px;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 32px;
padding: 12px 16px;
background: #f5f7fa;
border-radius: 4px;
}
.summary-item {
font-size: 14px;
color: #606266;
}
.summary-value {
font-weight: 500;
color: #303133;
margin-left: 4px;
}
.total-amount {
font-size: 20px;
font-weight: bold;
color: #f56c6c;
margin-left: 8px;
}
</style>