fix: 修复销售订单页面Bug(issues-0311-2)
- salesOrder.ts: 列表API添加normalizeOrderRow字段别名映射 (salesUserName→salesmanName, salesUserId→salesmanId, saleType→salesType) - salesOrder.ts: 详情API添加审核信息字段别名映射 (checkUserName→auditorName, checkDate→auditDate等) - form.vue: 销售人员下拉及保存优先使用nickName昵称 - form.vue: 主表数量/单价/质量要求/备注输入框去掉size=small,高度恢复默认 - form.vue: 主表物料编码列宽从130增至min-width=260 - view.vue: 详情页基础信息区域样式美化(卡片式布局、label加重、min-height) - Progress.vue: 新建生产进度页面,展示订单信息及关联生产工单 - router/index.ts: 修复生产进度路由指向Progress.vue(原误指向index.vue) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -158,10 +158,20 @@ export const BIZ_STATUS_OPTIONS = [
|
|||||||
|
|
||||||
const BASE = '/erp/sl/order'
|
const BASE = '/erp/sl/order'
|
||||||
|
|
||||||
|
/** 将列表行数据做字段别名映射 */
|
||||||
|
function normalizeOrderRow(row: any): any {
|
||||||
|
return {
|
||||||
|
...row,
|
||||||
|
salesmanId: row.salesmanId ?? row.salesUserId,
|
||||||
|
salesmanName: row.salesmanName ?? row.salesUserName ?? '',
|
||||||
|
salesType: row.salesType ?? row.saleType ?? '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 获取销售订单列表(单据视图) */
|
/** 获取销售订单列表(单据视图) */
|
||||||
export function getSalesOrderList(params: SalesOrderQuery): Promise<SalesOrderListResponse> {
|
export function getSalesOrderList(params: SalesOrderQuery): Promise<SalesOrderListResponse> {
|
||||||
return request.get(`${BASE}/list`, { params }).then((res: any) => {
|
return request.get(`${BASE}/list`, { params }).then((res: any) => {
|
||||||
const rows = res.rows ?? res.data?.rows ?? []
|
const rows = (res.rows ?? res.data?.rows ?? []).map(normalizeOrderRow)
|
||||||
const total = res.total ?? res.data?.total ?? 0
|
const total = res.total ?? res.data?.total ?? 0
|
||||||
return { list: rows, total }
|
return { list: rows, total }
|
||||||
})
|
})
|
||||||
@@ -170,7 +180,7 @@ export function getSalesOrderList(params: SalesOrderQuery): Promise<SalesOrderLi
|
|||||||
/** 获取销售订单明细列表(明细视图,展开物料维度) */
|
/** 获取销售订单明细列表(明细视图,展开物料维度) */
|
||||||
export function getSalesOrderDetailList(params: SalesOrderQuery): Promise<SalesOrderDetailListResponse> {
|
export function getSalesOrderDetailList(params: SalesOrderQuery): Promise<SalesOrderDetailListResponse> {
|
||||||
return request.get(`${BASE}/lineList`, { params }).then((res: any) => {
|
return request.get(`${BASE}/lineList`, { params }).then((res: any) => {
|
||||||
const rows = res.rows ?? res.data?.rows ?? []
|
const rows = (res.rows ?? res.data?.rows ?? []).map(normalizeOrderRow)
|
||||||
const total = res.total ?? res.data?.total ?? 0
|
const total = res.total ?? res.data?.total ?? 0
|
||||||
return { list: rows, total }
|
return { list: rows, total }
|
||||||
})
|
})
|
||||||
@@ -187,6 +197,11 @@ export function getSalesOrderDetail(orderId: number): Promise<SalesOrder> {
|
|||||||
// 后端返回 salesUserId/salesUserName,前端表单使用 salesmanId/salesmanName,便于下拉回显
|
// 后端返回 salesUserId/salesUserName,前端表单使用 salesmanId/salesmanName,便于下拉回显
|
||||||
if (data.salesUserId !== undefined) data.salesmanId = data.salesUserId
|
if (data.salesUserId !== undefined) data.salesmanId = data.salesUserId
|
||||||
if (data.salesUserName !== undefined) data.salesmanName = data.salesUserName
|
if (data.salesUserName !== undefined) data.salesmanName = data.salesUserName
|
||||||
|
// 审核信息字段别名映射(后端可能使用 checkBy/checkDate 等命名)
|
||||||
|
// 使用 == null 而非 ! 判断,避免 auditorId=0 或 auditorName='' 被误判为缺失
|
||||||
|
if (data.auditorId == null) data.auditorId = data.checkUserId ?? data.checkById ?? data.auditById
|
||||||
|
if (data.auditorName == null) data.auditorName = data.checkUserName ?? data.checkByName ?? data.auditByName ?? data.auditUser
|
||||||
|
if (data.auditDate == null) data.auditDate = data.checkDate ?? data.auditTime ?? data.checkTime
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: 'order/progress/:id',
|
path: 'order/progress/:id',
|
||||||
name: 'SalesOrderProgress',
|
name: 'SalesOrderProgress',
|
||||||
component: () => import('@/views/Sales/Order/index.vue'),
|
component: () => import('@/views/Sales/Order/Progress.vue'),
|
||||||
meta: { title: '生产进度' }
|
meta: { title: '生产进度' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
216
erp-frontend-vue/src/views/Sales/Order/Progress.vue
Normal file
216
erp-frontend-vue/src/views/Sales/Order/Progress.vue
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
<template>
|
||||||
|
<div class="progress-container">
|
||||||
|
<!-- 顶部操作栏 -->
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="header-left">
|
||||||
|
<el-button @click="handleBack" :icon="ArrowLeft">返回</el-button>
|
||||||
|
<span class="page-title">生产进度</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 订单基础信息 -->
|
||||||
|
<el-card shadow="never" class="info-card" v-loading="orderLoading">
|
||||||
|
<template #header>
|
||||||
|
<span class="card-title">销售订单信息</span>
|
||||||
|
</template>
|
||||||
|
<el-row :gutter="16">
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">单据编码</span>
|
||||||
|
<span class="info-value">{{ orderInfo.orderCode || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">客户名称</span>
|
||||||
|
<span class="info-value">{{ orderInfo.clientName || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">单据状态</span>
|
||||||
|
<span class="info-value">
|
||||||
|
<el-tag :type="getStatusType(orderInfo.orderStatus)">
|
||||||
|
{{ getStatusLabel(orderInfo.orderStatus) }}
|
||||||
|
</el-tag>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">销售人员</span>
|
||||||
|
<span class="info-value">{{ orderInfo.salesmanName || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 生产工单列表 -->
|
||||||
|
<el-card shadow="never" class="table-card" style="margin-top: 16px;">
|
||||||
|
<template #header>
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<span class="card-title">关联生产工单</span>
|
||||||
|
<el-button type="primary" size="small" @click="loadWorkOrders">
|
||||||
|
<el-icon><Refresh /></el-icon>刷新
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-table
|
||||||
|
v-loading="workOrderLoading"
|
||||||
|
:data="workOrders"
|
||||||
|
stripe
|
||||||
|
border
|
||||||
|
style="width: 100%"
|
||||||
|
empty-text="暂无关联的生产工单"
|
||||||
|
>
|
||||||
|
<el-table-column type="index" label="序号" width="60" />
|
||||||
|
<el-table-column prop="workorderCode" label="工单编码" width="140" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="workorderName" label="工单名称" min-width="180" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="productCode" label="产品编码" width="130" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="productName" label="产品名称" min-width="160" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="quantity" label="计划数量" width="100" align="right" />
|
||||||
|
<el-table-column prop="quantityProduced" label="完工数量" width="100" align="right" />
|
||||||
|
<el-table-column prop="status" label="工单状态" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="getWorkOrderStatusType(row.status)">
|
||||||
|
{{ getWorkOrderStatusLabel(row.status) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="productionDate" label="计划开始" width="110" />
|
||||||
|
<el-table-column prop="finishDate" label="完工日期" width="110" />
|
||||||
|
<el-table-column label="操作" width="80" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" link @click="handleViewWorkOrder(row)">查看</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { ArrowLeft, Refresh } from '@element-plus/icons-vue'
|
||||||
|
import { getSalesOrderDetail, ORDER_STATUS_MAP, type SalesOrder } from '@/api/salesOrder'
|
||||||
|
import { getWorkOrderList, STATUS_MAP, type WorkOrder } from '@/api/workOrder'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const orderLoading = ref(false)
|
||||||
|
const workOrderLoading = ref(false)
|
||||||
|
const orderInfo = ref<Partial<SalesOrder>>({})
|
||||||
|
const workOrders = ref<WorkOrder[]>([])
|
||||||
|
|
||||||
|
function getStatusType(status?: string): string {
|
||||||
|
return ORDER_STATUS_MAP[status || '']?.type || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusLabel(status?: string): string {
|
||||||
|
return ORDER_STATUS_MAP[status || '']?.label || status || '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWorkOrderStatusType(status?: string): string {
|
||||||
|
return STATUS_MAP[status || '']?.type || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWorkOrderStatusLabel(status?: string): string {
|
||||||
|
return STATUS_MAP[status || '']?.label || status || '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadOrderInfo() {
|
||||||
|
const raw = route.params.id
|
||||||
|
const id = Array.isArray(raw) ? raw[0] : raw
|
||||||
|
if (!id) return
|
||||||
|
orderLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = await getSalesOrderDetail(Number(id))
|
||||||
|
orderInfo.value = data
|
||||||
|
await loadWorkOrders()
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('加载订单信息失败')
|
||||||
|
} finally {
|
||||||
|
orderLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadWorkOrders() {
|
||||||
|
if (!orderInfo.value.orderCode) return
|
||||||
|
workOrderLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getWorkOrderList({
|
||||||
|
sourceCode: orderInfo.value.orderCode,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 200
|
||||||
|
})
|
||||||
|
workOrders.value = res.rows
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('加载生产工单失败')
|
||||||
|
} finally {
|
||||||
|
workOrderLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBack() {
|
||||||
|
router.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleViewWorkOrder(row: WorkOrder) {
|
||||||
|
router.push(`/production/work-order/view/${row.workorderId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadOrderInfo()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.progress-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
<el-option
|
<el-option
|
||||||
v-for="item in salesmanOptions"
|
v-for="item in salesmanOptions"
|
||||||
:key="item.userId"
|
:key="item.userId"
|
||||||
:label="item.userName || item.nickName || ''"
|
:label="item.nickName || item.userName || ''"
|
||||||
:value="item.userId"
|
:value="item.userId"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -318,7 +318,7 @@
|
|||||||
empty-text="暂无物料明细,请点击上方按钮添加物料"
|
empty-text="暂无物料明细,请点击上方按钮添加物料"
|
||||||
>
|
>
|
||||||
<el-table-column type="index" label="序号" width="60" />
|
<el-table-column type="index" label="序号" width="60" />
|
||||||
<el-table-column prop="itemCode" label="物料编码" width="130">
|
<el-table-column prop="itemCode" label="物料编码" min-width="260">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<template v-if="!isView">
|
<template v-if="!isView">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -347,7 +347,6 @@
|
|||||||
:min="0"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
size="small"
|
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change="calculateLineAmount(row)"
|
@change="calculateLineAmount(row)"
|
||||||
@@ -364,7 +363,6 @@
|
|||||||
:min="0"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
size="small"
|
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change="calculateLineAmount(row)"
|
@change="calculateLineAmount(row)"
|
||||||
@@ -383,7 +381,6 @@
|
|||||||
<el-input
|
<el-input
|
||||||
v-if="!isView"
|
v-if="!isView"
|
||||||
v-model="row.qualityReq"
|
v-model="row.qualityReq"
|
||||||
size="small"
|
|
||||||
placeholder="质量要求"
|
placeholder="质量要求"
|
||||||
/>
|
/>
|
||||||
<span v-else>{{ row.qualityReq || '-' }}</span>
|
<span v-else>{{ row.qualityReq || '-' }}</span>
|
||||||
@@ -394,7 +391,6 @@
|
|||||||
<el-input
|
<el-input
|
||||||
v-if="!isView"
|
v-if="!isView"
|
||||||
v-model="row.remark"
|
v-model="row.remark"
|
||||||
size="small"
|
|
||||||
placeholder="备注"
|
placeholder="备注"
|
||||||
/>
|
/>
|
||||||
<span v-else>{{ row.remark || '-' }}</span>
|
<span v-else>{{ row.remark || '-' }}</span>
|
||||||
@@ -825,11 +821,11 @@ function onDeptChange(deptId: number | undefined) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 销售人员变更:同步名称 */
|
/** 销售人员变更:同步昵称(nickName 优先于 userName) */
|
||||||
function onSalesmanChange(userId: number | undefined) {
|
function onSalesmanChange(userId: number | undefined) {
|
||||||
if (userId) {
|
if (userId) {
|
||||||
const user = userOptions.value.find(u => u.userId === userId)
|
const user = userOptions.value.find(u => u.userId === userId)
|
||||||
formData.salesmanName = user?.userName ?? user?.nickName ?? ''
|
formData.salesmanName = user?.nickName ?? user?.userName ?? ''
|
||||||
} else {
|
} else {
|
||||||
formData.salesmanName = ''
|
formData.salesmanName = ''
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -561,28 +561,37 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-row {
|
.info-row {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-item {
|
.info-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 6px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: #fafbfc;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 6px;
|
||||||
|
min-height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-label {
|
.info-label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-value {
|
.info-value {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
|
min-height: 20px;
|
||||||
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-value.highlight {
|
.info-value.highlight {
|
||||||
color: #409eff;
|
color: #409eff;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.material-section {
|
.material-section {
|
||||||
|
|||||||
Reference in New Issue
Block a user