Files
klp-oa/klp-ui/src/views/crm/orderItem/index.vue
王文昊 efc6a9f0df refactor(crm order item): 移排序逻辑到后端并简化前端代码
1. 将原前端的分组排序逻辑迁移到后端service实现
2. 后端实现三级排序:交货日期倒序→订单ID升序→创建时间倒序,保证跨页排序一致性和合同组连续排列
3. 移除前端冗余的groupSort方法和排序状态变量,简化前端页面逻辑
4. 更新前端排序提示文案为固定描述
2026-05-22 02:46:43 +08:00

719 lines
22 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="order-item-page">
<!-- 筛选栏 -->
<div class="filter-section">
<el-input
v-model="queryParams.contractCode"
placeholder="合同号"
size="small"
clearable
style="width: 140px"
@keyup.enter.native="handleQuery"
/>
<el-input
v-model="queryParams.customer"
placeholder="客户名称"
size="small"
clearable
style="width: 140px"
@keyup.enter.native="handleQuery"
/>
<el-input
v-model="queryParams.productName"
placeholder="产品名称"
size="small"
clearable
style="width: 140px"
@keyup.enter.native="handleQuery"
/>
<el-input
v-model="queryParams.material"
placeholder="材质"
size="small"
clearable
style="width: 120px"
@keyup.enter.native="handleQuery"
/>
<el-input
v-model="queryParams.surfaceTreatment"
placeholder="表面处理"
size="small"
clearable
style="width: 130px"
@keyup.enter.native="handleQuery"
/>
<el-input
v-model="queryParams.packagingReq"
placeholder="包装要求"
size="small"
clearable
style="width: 250px"
@keyup.enter.native="handleQuery"
/>
<el-date-picker
v-model="signDateRange"
type="daterange"
range-separator=""
start-placeholder="签订开始"
end-placeholder="签订结束"
size="small"
style="width: 220px"
value-format="yyyy-MM-dd"
:unlink-panels="true"
@change="handleSignDateChange"
/>
<el-date-picker
v-model="deliveryDateRange"
type="daterange"
range-separator=""
start-placeholder="交货开始"
end-placeholder="交货结束"
size="small"
style="width: 220px"
value-format="yyyy-MM-dd"
:unlink-panels="true"
@change="handleDeliveryDateChange"
/>
<el-button type="primary" size="small" icon="el-icon-search" @click="handleQuery">筛选</el-button>
<el-button size="small" icon="el-icon-refresh" @click="resetQuery">重置</el-button>
<span class="sort-hint">已按合同交货日期倒序 + 明细创建时间倒序排列</span>
</div>
<!-- 订单明细表格 -->
<div class="table-section">
<el-table
:data="orderItemList"
size="small"
border
v-loading="loading"
style="width: 100%"
class="order-item-table"
:header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: 600 }"
:row-class-name="groupRowClassName"
>
<!-- 合同信息列只读灰色背景 -->
<el-table-column label="合同信息" align="center">
<el-table-column label="合同号" prop="contractCode" min-width="170">
<template slot-scope="{ row }">
<span class="contract-info" :title="row.contractCode">{{ row.contractCode }}</span>
</template>
</el-table-column>
<el-table-column label="供方" prop="supplier" min-width="200">
<template slot-scope="{ row }">
<span class="contract-info" :title="row.supplier">{{ row.supplier }}</span>
</template>
</el-table-column>
<el-table-column label="需方" prop="customer" min-width="240">
<template slot-scope="{ row }">
<span class="contract-info" :title="row.customer">{{ row.customer }}</span>
</template>
</el-table-column>
<el-table-column label="签订日期" prop="signTime" width="100">
<template slot-scope="{ row }">
<span class="contract-info contract-date">{{ formatDate(row.signTime) }}</span>
</template>
</el-table-column>
<el-table-column label="交货日期" prop="deliveryDate" width="100">
<template slot-scope="{ row }">
<span class="contract-info contract-date">{{ formatDate(row.deliveryDate) }}</span>
</template>
</el-table-column>
</el-table-column>
<!-- 订单明细列可编辑 -->
<el-table-column label="订单明细" align="center">
<el-table-column label="序号" prop="seqNo" width="60">
<template slot-scope="scope">
<div class="editable-cell-wrapper">
<el-input
v-model="scope.row.seqNo"
size="small"
:title="scope.row.seqNo"
@blur="saveRow(scope.row)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="产品名称" prop="productName" min-width="120">
<template slot-scope="scope">
<div class="editable-cell-wrapper">
<el-input
v-model="scope.row.productName"
size="small"
class="editable-cell"
:title="scope.row.productName"
@blur="saveRow(scope.row)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="规格" prop="productSpec" min-width="110">
<template slot-scope="scope">
<div class="editable-cell-wrapper">
<el-input
v-model="scope.row.productSpec"
size="small"
class="editable-cell"
:title="scope.row.productSpec"
@blur="saveRow(scope.row)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="材质" prop="material" width="80">
<template slot-scope="scope">
<div class="editable-cell-wrapper">
<el-input
v-model="scope.row.material"
size="small"
class="editable-cell-blue"
:title="scope.row.material"
@blur="saveRow(scope.row)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="宽度(mm)" prop="width" width="85">
<template slot-scope="scope">
<div class="editable-cell-wrapper">
<el-input
v-model="scope.row.width"
size="small"
class="editable-cell-blue"
:title="scope.row.width"
@blur="saveRow(scope.row)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="厚度(mm)" prop="thickness" width="85">
<template slot-scope="scope">
<div class="editable-cell-wrapper">
<el-input
v-model="scope.row.thickness"
size="small"
class="editable-cell-blue"
:title="scope.row.thickness"
@blur="saveRow(scope.row)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="数量" prop="quantity" width="80">
<template slot-scope="scope">
<div class="editable-cell-wrapper">
<el-input
v-model="scope.row.quantity"
size="small"
class="editable-cell-green"
:title="scope.row.quantity"
@blur="saveRow(scope.row)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="重量(吨)" prop="weight" width="90">
<template slot-scope="scope">
<div class="editable-cell-wrapper">
<el-input
v-model="scope.row.weight"
size="small"
class="editable-cell-green"
:title="scope.row.weight"
@blur="saveRow(scope.row)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="单价" prop="unitPrice" width="90">
<template slot-scope="scope">
<div class="editable-cell-wrapper">
<el-input
v-model="scope.row.unitPrice"
size="small"
class="editable-cell-green"
:title="scope.row.unitPrice"
@blur="saveRow(scope.row)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="金额" prop="amount" width="100">
<template slot-scope="scope">
<div class="editable-cell-wrapper">
<el-input
v-model="scope.row.amount"
size="small"
class="editable-cell-green"
:title="scope.row.amount"
@blur="saveRow(scope.row)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="表面处理" prop="surfaceTreatment" width="130">
<template slot-scope="scope">
<div class="editable-cell-wrapper">
<el-input
v-model="scope.row.surfaceTreatment"
size="small"
class="editable-cell-pink"
:title="scope.row.surfaceTreatment"
@blur="saveRow(scope.row)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="包装要求" prop="packagingReq" min-width="275">
<template slot-scope="scope">
<div class="editable-cell-wrapper">
<el-input
v-model="scope.row.packagingReq"
size="small"
class="editable-cell-pink"
:title="scope.row.packagingReq"
@blur="saveRow(scope.row)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="备注" prop="remark" min-width="120">
<template slot-scope="scope">
<div class="editable-cell-wrapper">
<el-input
v-model="scope.row.remark"
size="small"
class="editable-cell-pink"
:title="scope.row.remark"
@blur="saveRow(scope.row)"
/>
</div>
</template>
</el-table-column>
</el-table-column>
<!-- 操作列 -->
<el-table-column label="操作" width="70" fixed="right" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="saveRow(scope.row)">保存</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</div>
</template>
<script>
import { listOrderItem, updateOrderItem } from '@/api/crm/orderItem'
import { listOrder } from '@/api/crm/order'
import { listCategory } from '@/api/wms/category'
export default {
name: 'OrderItemList',
data() {
return {
loading: false,
orderItemList: [],
total: 0,
signDateRange: null,
deliveryDateRange: null,
queryParams: {
pageNum: 1,
pageSize: 50,
contractCode: undefined,
customer: undefined,
productName: undefined,
material: undefined,
surfaceTreatment: undefined,
packagingReq: undefined,
signDateStart: undefined,
signDateEnd: undefined,
deliveryDateStart: undefined,
deliveryDateEnd: undefined
},
// 缓存合同信息
contractMap: {},
// 存储原始数据,用于判断是否有修改
originalData: {},
// 表面处理选项
surfaceTreatmentOptions: []
}
},
created() {
this.getList()
this.loadSurfaceTreatmentOptions()
},
methods: {
// 格式化日期为 YYYY-MM-DD
formatDate(dateStr) {
if (!dateStr) return ''
// 处理各种日期格式
const date = new Date(dateStr)
if (isNaN(date.getTime())) {
// 如果不是有效日期尝试直接截取前10位
return String(dateStr).substring(0, 10)
}
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
// 获取订单明细列表
getList() {
this.loading = true
listOrderItem(this.queryParams).then(res => {
const items = res.rows || []
this.total = res.total || 0
// 获取所有相关的合同ID
const orderIds = [...new Set(items.map(item => item.orderId).filter(id => id))]
if (orderIds.length > 0) {
// 批量获取合同信息
this.loadContractInfo(orderIds, items)
} else {
this.orderItemList = items
this.loading = false
}
}).catch(e => {
console.error(e)
this.loading = false
})
},
// 加载合同信息
loadContractInfo(orderIds, items) {
// 使用listOrder获取合同信息通过params传递orderIds
const params = {
pageNum: 1,
pageSize: 9999,
orderIds: orderIds.join(',')
}
listOrder(params).then(res => {
const contracts = res.rows || []
// 构建合同ID到合同信息的映射
contracts.forEach(contract => {
this.contractMap[contract.orderId] = contract
})
// 直接合并数据(后端已按 deliveryDate DESC + createTime DESC 排序)
this.orderItemList = items.map(item => {
const contract = this.contractMap[item.orderId] || {}
return {
...item,
contractCode: contract.contractCode || '',
supplier: contract.supplier || '',
customer: contract.customer || '',
signTime: contract.signTime || '',
deliveryDate: contract.deliveryDate || ''
}
})
// 保存原始数据副本
this.originalData = {}
this.orderItemList.forEach(row => {
this.originalData[row.itemId || row.detailId] = JSON.stringify(row)
})
}).catch(e => {
console.error(e)
this.orderItemList = items
}).finally(() => {
this.loading = false
})
},
// 分组行样式:同一合同组使用交替背景色
groupRowClassName({ row, rowIndex }) {
// 记录上一个orderId切换时翻转颜色
if (rowIndex === 0 || (this._lastOrderId !== undefined && this._lastOrderId !== row.orderId)) {
this._groupColorIndex = (this._groupColorIndex || 0) + 1
}
this._lastOrderId = row.orderId
return this._groupColorIndex % 2 === 1 ? 'group-row-a' : 'group-row-b'
},
// 判断数据是否有变化
hasChanged(row) {
const id = row.itemId || row.detailId
if (!id || !this.originalData[id]) return false
return this.originalData[id] !== JSON.stringify(row)
},
// 筛选
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
// 签订日期范围变更
handleSignDateChange(val) {
if (val && val.length === 2) {
this.queryParams.signDateStart = val[0]
this.queryParams.signDateEnd = val[1]
} else {
this.queryParams.signDateStart = undefined
this.queryParams.signDateEnd = undefined
}
this.handleQuery()
},
// 交货日期范围变更
handleDeliveryDateChange(val) {
if (val && val.length === 2) {
this.queryParams.deliveryDateStart = val[0]
this.queryParams.deliveryDateEnd = val[1]
} else {
this.queryParams.deliveryDateStart = undefined
this.queryParams.deliveryDateEnd = undefined
}
this.handleQuery()
},
// 重置
resetQuery() {
this.signDateRange = null
this.deliveryDateRange = null
this.queryParams.contractCode = undefined
this.queryParams.customer = undefined
this.queryParams.productName = undefined
this.queryParams.material = undefined
this.queryParams.surfaceTreatment = undefined
this.queryParams.packagingReq = undefined
this.queryParams.signDateStart = undefined
this.queryParams.signDateEnd = undefined
this.queryParams.deliveryDateStart = undefined
this.queryParams.deliveryDateEnd = undefined
this.queryParams.pageNum = 1
this.getList()
},
// 加载表面处理选项
loadSurfaceTreatmentOptions() {
listCategory({ categoryType: 'surface_treatment', pageNum: 1, pageSize: 100 }).then(res => {
this.surfaceTreatmentOptions = res.rows || []
}).catch(e => {
console.error('获取表面处理选项失败', e)
})
},
// 保存行数据
saveRow(row) {
if (!row.itemId && !row.detailId) {
this.$message.warning('数据ID不存在')
return
}
// 判断是否有修改
if (!this.hasChanged(row)) {
return
}
// 只保存订单明细相关字段
const data = {
itemId: row.itemId,
detailId: row.detailId,
orderId: row.orderId,
seqNo: row.seqNo,
productName: row.productName,
productSpec: row.productSpec,
material: row.material,
width: row.width,
thickness: row.thickness,
quantity: row.quantity,
weight: row.weight,
unitPrice: row.unitPrice,
amount: row.amount,
surfaceTreatment: row.surfaceTreatment,
packagingReq: row.packagingReq,
remark: row.remark
}
updateOrderItem(data).then(() => {
// 更新原始数据
const id = row.itemId || row.detailId
this.originalData[id] = JSON.stringify(row)
this.$message.success('保存成功')
}).catch(e => {
this.$message.error('保存失败')
console.error(e)
// 刷新数据
this.getList()
})
}
}
}
</script>
<style scoped>
.order-item-page {
padding: 16px;
min-height: 100%;
}
.filter-section {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
padding: 12px 16px;
background: #fff;
border-radius: 6px;
border: 1px solid #ebeef5;
}
.table-section {
background: #fff;
border-radius: 6px;
border: 1px solid #ebeef5;
padding: 16px;
}
/* 合同信息样式(只读) */
.contract-info {
color: #606266;
background: #f5f7fa;
padding: 4px 6px;
border-radius: 4px;
display: inline-block;
width: 100%;
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 日期单元格样式 */
.contract-date {
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 11px;
letter-spacing: -0.3px;
padding: 4px 4px;
text-align: center;
}
/* 可编辑单元格包装器 */
.editable-cell-wrapper {
width: 100%;
}
/* 可编辑单元格样式 */
.editable-cell ::v-deep .el-input__inner {
background-color: #fff3e6;
border-color: transparent;
padding: 0 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.editable-cell ::v-deep .el-input__inner:focus {
border-color: #5F7BA0;
background-color: #fff;
overflow: visible;
white-space: normal;
}
.editable-cell-blue ::v-deep .el-input__inner {
background-color: #e6f7ff;
border-color: transparent;
padding: 0 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.editable-cell-blue ::v-deep .el-input__inner:focus {
border-color: #5F7BA0;
background-color: #fff;
overflow: visible;
white-space: normal;
}
.editable-cell-green ::v-deep .el-input__inner {
background-color: #f6ffed;
border-color: transparent;
padding: 0 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.editable-cell-green ::v-deep .el-input__inner:focus {
border-color: #5F7BA0;
background-color: #fff;
overflow: visible;
white-space: normal;
}
.editable-cell-pink ::v-deep .el-input__inner {
background-color: #fff0f6;
border-color: transparent;
padding: 0 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.editable-cell-pink ::v-deep .el-input__inner:focus {
border-color: #5F7BA0;
background-color: #fff;
overflow: visible;
white-space: normal;
}
/* 表格样式优化 */
.order-item-table ::v-deep .el-input__inner {
border-color: transparent;
}
.order-item-table ::v-deep .el-input__inner:focus {
border-color: #5F7BA0;
}
.order-item-table ::v-deep .el-table__body td {
padding: 4px 0;
white-space: normal !important;
word-break: break-all !important;
}
.order-item-table ::v-deep .el-table__body td .cell {
white-space: normal !important;
word-break: break-all !important;
line-height: 1.4;
}
/* 合同信息文本换行 */
.contract-info {
white-space: normal !important;
word-break: break-all !important;
overflow: visible;
text-overflow: clip;
}
::v-deep .el-button--primary {
color: #fff !important;
background: #5F7BA0 !important;
border-color: #5F7BA0 !important;
}
::v-deep .el-button--primary:hover,
::v-deep .el-button--primary:focus {
background: #4d6a8e !important;
border-color: #4d6a8e !important;
}
/* 排序状态提示 */
.sort-hint {
margin-left: auto;
font-size: 12px;
color: #909399;
white-space: nowrap;
}
/* 分组交替背景色 */
::v-deep .el-table .group-row-a td {
background-color: #ffffff !important;
}
::v-deep .el-table .group-row-b td {
background-color: #fafcff !important;
}
</style>