feat(wms): 新增带改判记录的钢卷列表接口及展示

feat(crm): 在订单异议页面添加异议内容和处理结果的HTML展示

feat(erp): 新增ERP仪表盘页面并优化采购订单表单

refactor(wms): 移除钢卷列表中的冗余代码并添加改判原因列
This commit is contained in:
2026-05-09 14:08:11 +08:00
parent e42afdaf20
commit d09079f4c1
6 changed files with 1005 additions and 1114 deletions

View File

@@ -424,3 +424,15 @@ export function fixMismatchedItemCoil(coilId) {
}
})
}
/**
* 带有改判记录的钢卷列表
*/
export function listWithAdjustRecordCoil(params) {
return request({
url: '/wms/materialCoil/listWithRejudge',
method: 'get',
timeout: 600000,
params
})
}

View File

@@ -27,7 +27,11 @@
<el-table v-loading="loading" :data="salesObjectionList" @selection-change="handleSelectionChange">
<el-table-column label="编号" align="center" prop="objectionCode" />
<!-- <el-table-column label="异议类型" align="center" prop="objectionType" /> -->
<!-- <el-table-column label="异议内容" align="center" prop="objectionContent" /> -->
<el-table-column label="异议内容" align="center" prop="objectionContent" show-overflow-tooltip>
<template slot-scope="scope">
<div class="cell-html" v-html="scope.row.objectionContent"></div>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="objectionStatus">
<template slot-scope="scope">
<el-tag v-if="scope.row.objectionStatus === 0" type="danger">待处理</el-tag>
@@ -35,7 +39,11 @@
<el-tag v-else-if="scope.row.objectionStatus === 2" type="info">已关闭</el-tag>
</template>
</el-table-column>
<!-- <el-table-column label="处理内容" align="center" prop="handleContent" /> -->
<el-table-column label="处理结果" align="center" prop="handleContent" show-overflow-tooltip>
<template slot-scope="scope">
<div class="cell-html" v-html="scope.row.handleContent"></div>
</template>
</el-table-column>
<el-table-column label="处理人" align="center" prop="handleUser" />
<el-table-column label="处理时间" align="center" prop="handleTime" width="180">
<template slot-scope="scope">
@@ -133,8 +141,12 @@
</div>
</el-dialog>
<el-dialog title="查看处理结果" :visible.sync="viewOpen" width="500px" append-to-body>
<el-descriptions :column="2" :data="viewForm" label-width="80px">
<el-dialog title="查看处理结果" :visible.sync="viewOpen" width="700px" append-to-body>
<el-descriptions :column="2" :data="viewForm" label-width="80px" border>
<el-descriptions-item label="异议编号" :span="2">{{ viewForm.objectionCode }}</el-descriptions-item>
<el-descriptions-item label="异议内容" :span="2">
<div v-html="viewForm.objectionContent"></div>
</el-descriptions-item>
<el-descriptions-item label="处理人">{{ viewForm.handleUser }}</el-descriptions-item>
<el-descriptions-item label="处理时间">{{ viewForm.handleTime }}</el-descriptions-item>
<el-descriptions-item label="处理内容" :span="2">
@@ -379,3 +391,12 @@ export default {
}
};
</script>
<style scoped>
.cell-html {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200px;
}
</style>

View File

@@ -0,0 +1,804 @@
<template>
<div class="erp-dashboard-page">
<section class="stats-section">
<div class="stat-card">
<div class="stat-icon supplier-icon">
<i class="el-icon-office-building"></i>
</div>
<div class="stat-content">
<div class="stat-value">{{ supplierCount }}</div>
<div class="stat-label">供应商总数</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon order-icon">
<i class="el-icon-document"></i>
</div>
<div class="stat-content">
<div class="stat-value">{{ orderCount }}</div>
<div class="stat-label">采购订单总数</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon doing-icon">
<i class="el-icon-loading"></i>
</div>
<div class="stat-content">
<div class="stat-value">{{ doingOrderCount }}</div>
<div class="stat-label">执行中订单</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon complete-icon">
<i class="el-icon-circle-check"></i>
</div>
<div class="stat-content">
<div class="stat-value">{{ completeOrderCount }}</div>
<div class="stat-label">已完成订单</div>
</div>
</div>
</section>
<div class="charts-section">
<div class="chart-card">
<div class="chart-header">
<span>订单状态分布</span>
</div>
<div ref="statusChart" class="chart-container"></div>
</div>
<div class="chart-card">
<div class="chart-header">
<span>供应商订单占比</span>
</div>
<div ref="supplierChart" class="chart-container"></div>
</div>
</div>
<div class="charts-section">
<div class="chart-card full-width">
<div class="chart-header">
<span>订单趋势按天</span>
</div>
<div ref="trendChart" class="chart-container"></div>
</div>
</div>
<div class="charts-section">
<div class="chart-card">
<div class="chart-header">
<span>业务员订单分布</span>
</div>
<div ref="salesmanBarChart" class="chart-container"></div>
</div>
<div class="chart-card">
<div class="chart-header">
<span>部门订单分布</span>
</div>
<div ref="deptBarChart" class="chart-container"></div>
</div>
<div class="chart-card">
<div class="chart-header">
<span>按业务员订单占比饼图</span>
</div>
<div ref="salesmanPieChart" class="chart-container"></div>
</div>
<div class="chart-card">
<div class="chart-header">
<span>按部门订单占比饼图</span>
</div>
<div ref="deptPieChart" class="chart-container"></div>
</div>
</div>
<div class="charts-section">
<div class="chart-card">
<div class="chart-header">
<span>供应商信用等级分布</span>
</div>
<div ref="creditChart" class="chart-container"></div>
</div>
<div class="chart-card">
<div class="chart-header">
<span>供应商类型分布</span>
</div>
<div ref="typeChart" class="chart-container"></div>
</div>
</div>
</div>
</template>
<script>
import { listSupplier, listPurchaseOrder } from '@/api/erp/purchase'
import * as echarts from 'echarts'
export default {
name: 'ErpDashboard',
data() {
return {
supplierCount: 0,
orderCount: 0,
doingOrderCount: 0,
completeOrderCount: 0,
supplierList: [],
orderList: [],
loading: false
}
},
mounted() {
this.loadData()
},
beforeDestroy() {
if (this.statusChart) this.statusChart.dispose()
if (this.supplierChart) this.supplierChart.dispose()
if (this.trendChart) this.trendChart.dispose()
if (this.creditChart) this.creditChart.dispose()
if (this.typeChart) this.typeChart.dispose()
if (this.salesmanBarChart) this.salesmanBarChart.dispose()
if (this.salesmanPieChart) this.salesmanPieChart.dispose()
if (this.deptBarChart) this.deptBarChart.dispose()
if (this.deptPieChart) this.deptPieChart.dispose()
if (this.supplierBarChart) this.supplierBarChart.dispose()
if (this.supplierPieChart) this.supplierPieChart.dispose()
window.removeEventListener('resize', this.handleResize)
},
methods: {
async loadData() {
this.loading = true
try {
const [supplierRes, orderRes] = await Promise.all([
listSupplier({ pageNum: 1, pageSize: 100000 }),
listPurchaseOrder({ pageNum: 1, pageSize: 100000 })
])
this.supplierList = supplierRes.rows || []
this.orderList = orderRes.rows || []
this.supplierCount = this.supplierList.length
this.orderCount = this.orderList.length
this.doingOrderCount = this.orderList.filter(o => o.orderStatus === 1).length
this.completeOrderCount = this.orderList.filter(o => o.orderStatus === 3).length
this.$nextTick(() => {
this.initCharts()
})
} catch (error) {
this.$message.error('加载数据失败')
console.error(error)
} finally {
this.loading = false
}
},
initCharts() {
this.initStatusChart()
this.initSupplierChart()
this.initTrendChart()
this.initCreditChart()
this.initTypeChart()
this.initSalesmanCharts()
this.initDeptCharts()
this.initSupplierNameCharts()
window.addEventListener('resize', this.handleResize)
},
handleResize() {
this.statusChart && this.statusChart.resize()
this.supplierChart && this.supplierChart.resize()
this.trendChart && this.trendChart.resize()
this.creditChart && this.creditChart.resize()
this.typeChart && this.typeChart.resize()
this.salesmanBarChart && this.salesmanBarChart.resize()
this.salesmanPieChart && this.salesmanPieChart.resize()
this.deptBarChart && this.deptBarChart.resize()
this.deptPieChart && this.deptPieChart.resize()
this.supplierBarChart && this.supplierBarChart.resize()
this.supplierPieChart && this.supplierPieChart.resize()
},
initStatusChart() {
this.statusChart = echarts.init(this.$refs.statusChart)
const statusMap = {
0: '草稿',
1: '执行中',
2: '部分到货',
3: '已完成',
4: '已取消'
}
const statusData = {}
this.orderList.forEach(order => {
const status = order.orderStatus || 0
statusData[status] = (statusData[status] || 0) + 1
})
const data = Object.keys(statusData).map(key => ({
name: statusMap[key],
value: statusData[key]
}))
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '订单状态',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}: {c}'
},
data: data
}
]
}
this.statusChart.setOption(option)
},
initSupplierChart() {
this.supplierChart = echarts.init(this.$refs.supplierChart)
const supplierOrderCount = {}
this.orderList.forEach(order => {
const supplierName = order.supplierName || '未知'
supplierOrderCount[supplierName] = (supplierOrderCount[supplierName] || 0) + 1
})
let data = Object.keys(supplierOrderCount).map(name => ({
name,
value: supplierOrderCount[name]
}))
data = data.sort((a, b) => b.value - a.value).slice(0, 10)
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '供应商订单',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}: {c}'
},
data: data
}
]
}
this.supplierChart.setOption(option)
},
initTrendChart() {
this.trendChart = echarts.init(this.$refs.trendChart)
const dayData = {}
this.orderList.forEach(order => {
if (order.orderDate) {
const day = order.orderDate
dayData[day] = (dayData[day] || 0) + 1
}
})
const days = Object.keys(dayData).sort()
const counts = days.map(d => dayData[d])
const option = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['订单数量']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: days,
axisLabel: {
rotate: 45
}
},
yAxis: {
type: 'value'
},
dataZoom: [
{
type: 'inside',
start: 0,
end: 100
},
{
start: 0,
end: 100
}
],
series: [
{
name: '订单数量',
type: 'line',
smooth: true,
data: counts,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(80, 141, 255, 0.5)' },
{ offset: 1, color: 'rgba(80, 141, 255, 0.1)' }
])
},
lineStyle: {
color: '#508dff'
},
itemStyle: {
color: '#508dff'
}
}
]
}
this.trendChart.setOption(option)
},
initCreditChart() {
this.creditChart = echarts.init(this.$refs.creditChart)
const creditData = {}
this.supplierList.forEach(supplier => {
const credit = supplier.creditRating || '-'
creditData[credit] = (creditData[credit] || 0) + 1
})
const data = Object.keys(creditData).map(key => ({
name: key,
value: creditData[key]
}))
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
xAxis: {
type: 'category',
data: Object.keys(creditData)
},
yAxis: {
type: 'value'
},
series: [
{
name: '供应商数量',
type: 'bar',
data: Object.values(creditData),
itemStyle: {
color: function (params) {
const colorMap = {
'A': '#67c23a',
'B': '#e6a23c',
'C': '#f56c6c',
'D': '#909399',
'-': '#409eff'
}
return colorMap[params.name] || '#409eff'
}
}
}
]
}
this.creditChart.setOption(option)
},
initTypeChart() {
this.typeChart = echarts.init(this.$refs.typeChart)
const typeData = {}
this.supplierList.forEach(supplier => {
const type = supplier.type === 'RAW' ? '原料供应商' : '其他供应商'
typeData[type] = (typeData[type] || 0) + 1
})
const data = Object.keys(typeData).map(key => ({
name: key,
value: typeData[key]
}))
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '供应商类型',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}: {c}'
},
data: data
}
]
}
this.typeChart.setOption(option)
},
initSalesmanCharts() {
// 柱状图
this.salesmanBarChart = echarts.init(this.$refs.salesmanBarChart)
const salesmanData = {}
this.orderList.forEach(order => {
const salesman = order.salesman || '未知'
salesmanData[salesman] = (salesmanData[salesman] || 0) + 1
})
const salesmanList = Object.keys(salesmanData).sort((a, b) => salesmanData[b] - salesmanData[a])
const salesmanCounts = salesmanList.map(s => salesmanData[s])
const barOption = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: salesmanList,
axisLabel: {
rotate: 45
}
},
yAxis: {
type: 'value'
},
series: [
{
name: '订单数量',
type: 'bar',
data: salesmanCounts,
itemStyle: {
color: '#67c23a'
}
}
]
}
this.salesmanBarChart.setOption(barOption)
// 饼图
this.salesmanPieChart = echarts.init(this.$refs.salesmanPieChart)
let pieData = salesmanList.map(name => ({
name,
value: salesmanData[name]
}))
pieData = pieData.slice(0, 10)
const pieOption = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '业务员订单',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}: {c}'
},
data: pieData
}
]
}
this.salesmanPieChart.setOption(pieOption)
},
initDeptCharts() {
// 柱状图
this.deptBarChart = echarts.init(this.$refs.deptBarChart)
const deptData = {}
this.orderList.forEach(order => {
const dept = order.deptName || '未知'
deptData[dept] = (deptData[dept] || 0) + 1
})
const deptList = Object.keys(deptData).sort((a, b) => deptData[b] - deptData[a])
const deptCounts = deptList.map(d => deptData[d])
const barOption = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: deptList,
axisLabel: {
rotate: 45
}
},
yAxis: {
type: 'value'
},
series: [
{
name: '订单数量',
type: 'bar',
data: deptCounts,
itemStyle: {
color: '#e6a23c'
}
}
]
}
this.deptBarChart.setOption(barOption)
// 饼图
this.deptPieChart = echarts.init(this.$refs.deptPieChart)
let pieData = deptList.map(name => ({
name,
value: deptData[name]
}))
pieData = pieData.slice(0, 10)
const pieOption = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '部门订单',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}: {c}'
},
data: pieData
}
]
}
this.deptPieChart.setOption(pieOption)
},
initSupplierNameCharts() {
// 柱状图
this.supplierBarChart = echarts.init(this.$refs.supplierBarChart)
const supplierData = {}
this.orderList.forEach(order => {
const supplier = order.supplierName || '未知'
supplierData[supplier] = (supplierData[supplier] || 0) + 1
})
const supplierList = Object.keys(supplierData).sort((a, b) => supplierData[b] - supplierData[a])
const supplierCounts = supplierList.map(s => supplierData[s])
const barOption = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: supplierList.slice(0, 15),
axisLabel: {
rotate: 45
}
},
yAxis: {
type: 'value'
},
series: [
{
name: '订单数量',
type: 'bar',
data: supplierCounts.slice(0, 15),
itemStyle: {
color: '#f56c6c'
}
}
]
}
this.supplierBarChart.setOption(barOption)
// 饼图
this.supplierPieChart = echarts.init(this.$refs.supplierPieChart)
let pieData = supplierList.map(name => ({
name,
value: supplierData[name]
}))
pieData = pieData.slice(0, 10)
const pieOption = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '供应商订单',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}: {c}'
},
data: pieData
}
]
}
this.supplierPieChart.setOption(pieOption)
}
}
}
</script>
<style lang="scss" scoped>
.erp-dashboard-page {
padding: 16px;
min-height: 100%;
background: #f0f2f5;
}
.stats-section {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 16px;
margin-bottom: 16px;
}
.stat-card {
background: #fff;
border-radius: 8px;
padding: 20px;
display: flex;
align-items: center;
gap: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
}
.stat-icon {
width: 60px;
height: 60px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
&.supplier-icon {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
}
&.order-icon {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: #fff;
}
&.doing-icon {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: #fff;
}
&.complete-icon {
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
color: #fff;
}
}
.stat-content {
flex: 1;
}
.stat-value {
font-size: 28px;
font-weight: 700;
color: #1f2a37;
margin-bottom: 4px;
}
.stat-label {
font-size: 14px;
color: #7a8694;
}
.charts-section {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
margin-bottom: 16px;
}
.chart-card {
background: #fff;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
&.full-width {
grid-column: span 2;
}
}
.chart-header {
font-size: 16px;
font-weight: 600;
color: #1f2a37;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #e4e7ed;
}
.chart-container {
height: 300px;
width: 100%;
}
</style>

View File

@@ -5,7 +5,6 @@
<span>采购订单列表</span>
<div class="surface-actions">
<el-button type="primary" size="small" @click="openOrderDialog()">新增订单</el-button>
<el-button size="small" @click="openItemDialog()">维护明细</el-button>
</div>
</header>
<div class="inline-filter">
@@ -51,26 +50,37 @@
</div>
</div>
<el-table :data="orderList" border stripe highlight-current-row size="small" v-loading="orderLoading">
<el-table-column prop="orderCode" label="订单编号" width="150" fixed="left" />
<el-table-column prop="supplierName" label="供应商" />
<el-table-column prop="orderDate" label="下单日期" />
<el-table-column prop="expectedArrival" label="期望到货" />
<el-table-column prop="orderType" label="类型" />
<el-table-column prop="totalAmount" label="金额" />
<el-table-column label="状态" width="120">
<el-table-column prop="orderCode" label="订单编号" width="140" fixed="left" />
<el-table-column prop="supplierName" label="供应商" width="150" />
<el-table-column prop="billType" label="单据类型" width="100" />
<el-table-column prop="receiveType" label="收发类别" width="100" />
<el-table-column prop="deptName" label="部门" width="100" />
<el-table-column prop="salesman" label="业务员" width="80" />
<el-table-column prop="orderDate" label="下单日期" width="100" />
<el-table-column prop="expectedArrival" label="期望到货" width="100" />
<el-table-column prop="orderType" label="订单类型" width="100" />
<el-table-column prop="totalAmount" label="金额" width="100" />
<el-table-column prop="maker" label="制单人" width="80" />
<el-table-column prop="auditor" label="审核人" width="80" />
<el-table-column prop="bookkeeper" label="记账人" width="80" />
<el-table-column prop="auditDate" label="审核日期" width="100" />
<el-table-column prop="stockCode" label="存货编码" width="100" />
<el-table-column prop="stockName" label="存货名称" width="120" />
<el-table-column prop="specModel" label="规格型号" width="100" />
<el-table-column label="状态" width="90">
<template slot-scope="scope">
<el-tag :type="statusTag(scope.row.orderStatus)" size="mini">{{ statusText(scope.row.orderStatus) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="360" fixed="right">
<el-table-column label="操作" width="140" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openOrderDialog(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click="handleDeleteOrder(scope.row)" style="color:#c0392b">删除</el-button>
<el-divider direction="vertical" />
<!-- <el-divider direction="vertical" />
<el-button type="text" size="mini" @click="confirmOrder(scope.row)">下达</el-button>
<el-button type="text" size="mini" @click="partialOrder(scope.row)">部分到货</el-button>
<el-button type="text" size="mini" @click="completeOrder(scope.row)">完成</el-button>
<el-button type="text" size="mini" @click="cancelOrder(scope.row)">取消</el-button>
<el-button type="text" size="mini" @click="cancelOrder(scope.row)">取消</el-button> -->
</template>
</el-table-column>
</el-table>
@@ -84,23 +94,30 @@
</section>
<!-- 订单弹窗 -->
<el-dialog :title="orderDialog.title" :visible.sync="orderDialog.visible" width="960px" class="order-dialog" @close="closeOrderDialog">
<el-dialog :title="orderDialog.title" :visible.sync="orderDialog.visible" width="1200px" class="order-dialog" @close="closeOrderDialog">
<div class="contract-layout">
<section class="contract-card">
<div class="contract-card__title">基础信息</div>
<el-form :model="orderDialog.form" :rules="orderRules" ref="orderForm" label-width="100px" size="small" class="contract-form">
<el-row :gutter="16">
<el-col :span="12">
<el-col :span="8">
<el-form-item label="订单编号" prop="orderCode">
<el-input v-model="orderDialog.form.orderCode" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="单类型">
<el-input v-model="orderDialog.form.orderType" placeholder="如标准采购、年度合同" />
<el-col :span="8">
<el-form-item label="单类型">
<el-input v-model="orderDialog.form.billType" placeholder="请输入单据类型" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-col :span="8">
<el-form-item label="收发类别">
<el-input v-model="orderDialog.form.receiveType" placeholder="请输入收发类别" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="供应商" prop="supplierId">
<el-select
v-model="orderDialog.form.supplierId"
@@ -111,7 +128,6 @@
:loading="supplierLoading"
placeholder="请选择供应商"
@visible-change="handleSupplierDropdown"
@change="handleSupplierChange"
>
<el-option
v-for="sp in supplierOptions"
@@ -122,19 +138,95 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-col :span="8">
<el-form-item label="部门">
<el-input v-model="orderDialog.form.deptName" placeholder="请输入部门" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="业务员">
<el-input v-model="orderDialog.form.salesman" placeholder="请输入业务员" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="订单类型">
<el-input v-model="orderDialog.form.orderType" placeholder="如标准采购、年度合同" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="下单日期" prop="orderDate">
<el-date-picker v-model="orderDialog.form.orderDate" type="date" value-format="yyyy-MM-dd" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-col :span="8">
<el-form-item label="期望到货">
<el-date-picker v-model="orderDialog.form.expectedArrival" type="date" value-format="yyyy-MM-dd" />
</el-form-item>
</el-col>
<el-col :span="12">
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="制单人">
<el-input v-model="orderDialog.form.maker" placeholder="请输入制单人" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="审核人">
<el-input v-model="orderDialog.form.auditor" placeholder="请输入审核人" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="记账人">
<el-input v-model="orderDialog.form.bookkeeper" placeholder="请输入记账人" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="审核日期">
<el-date-picker v-model="orderDialog.form.auditDate" type="date" value-format="yyyy-MM-dd" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="总金额">
<el-input-number v-model="orderDialog.form.totalAmount" :min="0" :precision="2" placeholder="可选" />
<el-input-number v-model="orderDialog.form.totalAmount" :min="0" :precision="2" placeholder="可选" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="存货编码">
<el-input v-model="orderDialog.form.stockCode" placeholder="请输入存货编码" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="存货名称">
<el-input v-model="orderDialog.form.stockName" placeholder="请输入存货名称" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="规格型号">
<el-input v-model="orderDialog.form.specModel" placeholder="请输入规格型号" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="主计量单位">
<el-input v-model="orderDialog.form.mainUnit" placeholder="请输入主计量单位" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="入库数量">
<el-input v-model="orderDialog.form.stockQuantity" placeholder="请输入入库数量" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="仓库ID">
<WarehouseSelect v-model="orderDialog.form.warehouseId" />
<!-- <el-input-number v-model="orderDialog.form.warehouseId" :min="0" placeholder="可选" style="width:100%" /> -->
</el-form-item>
</el-col>
</el-row>
@@ -143,126 +235,12 @@
</el-form-item>
</el-form>
</section>
<section class="contract-card">
<div class="contract-card__title">
采购明细
<span class="subtitle" v-if="orderDialog.form.orderId">订单号{{ orderDialog.form.orderCode || orderDialog.form.orderId }}</span>
</div>
<div class="contract-card__toolbar">
<el-select
v-model="selectedSupplierGood"
placeholder="从供应商价目表快速添加"
filterable
clearable
:disabled="!orderDialog.form.orderId"
@focus="ensureSupplierGoods"
@change="handleSelectGoods"
>
<el-option
v-for="good in supplierGoods"
:key="good.materialTypeCode + '_' + (good.specification || '')"
:label="formatGoodLabel(good)"
:value="good.materialTypeCode"
/>
</el-select>
<el-button
type="primary"
size="mini"
icon="el-icon-plus"
:disabled="!orderDialog.form.orderId"
@click="openItemForm(null, { orderId: orderDialog.form.orderId })"
>
手动新增
</el-button>
<span class="toolbar-tip" v-if="!orderDialog.form.orderId">请先保存订单再维护物料明细</span>
</div>
<el-table
v-loading="dialogItemLoading"
:data="dialogItems"
size="small"
border
empty-text="暂未添加物料"
>
<el-table-column type="index" width="50" />
<el-table-column prop="materialTypeCode" label="物料类型" />
<el-table-column prop="specification" label="规格" />
<el-table-column prop="quantity" label="数量" width="120" />
<el-table-column prop="unitPrice" label="含税单价" width="120" />
<el-table-column label="操作" width="140">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openItemForm(scope.row)">编辑</el-button>
<el-button type="text" size="mini" class="danger" @click="handleDeleteItem(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</section>
</div>
<div slot="footer" class="contract-footer">
<el-button @click="orderDialog.visible = false"> </el-button>
<el-button type="primary" @click="submitOrder"> </el-button>
</div>
</el-dialog>
<!-- 明细弹窗 -->
<el-dialog :title="itemDialog.title" :visible.sync="itemDialog.visible" width="640px">
<div class="inline-filter">
<el-input v-model="itemQuery.orderId" placeholder="订单ID" size="small" clearable />
<div class="filter-actions">
<el-button size="small" type="primary" @click="loadItems">查询</el-button>
<el-button size="small" @click="resetItemQuery">重置</el-button>
<el-button type="primary" size="small" @click="openItemForm()">新增明细</el-button>
</div>
</div>
<el-table :data="itemList" border size="small" v-loading="itemLoading">
<el-table-column prop="orderId" label="订单ID" />
<el-table-column prop="materialTypeCode" label="物料类型" />
<el-table-column prop="specification" label="规格" />
<el-table-column prop="quantity" label="数量" />
<el-table-column prop="unitPrice" label="单价" />
<el-table-column label="操作" width="150">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openItemForm(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click="handleDeleteItem(scope.row)" style="color:#c0392b">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="itemTotal > 0"
:total="itemTotal"
:page.sync="itemQuery.pageNum"
:limit.sync="itemQuery.pageSize"
@pagination="loadItems"
/>
</el-dialog>
<!-- 明细编辑 -->
<el-dialog :title="itemFormDialog.title" :visible.sync="itemFormDialog.visible" width="520px">
<el-form :model="itemFormDialog.form" :rules="itemRules" ref="itemForm" label-width="110px" size="small">
<el-form-item label="订单ID" prop="orderId">
<el-input v-model="itemFormDialog.form.orderId" />
</el-form-item>
<el-form-item label="物料类型" prop="materialTypeCode">
<el-input v-model="itemFormDialog.form.materialTypeCode" />
</el-form-item>
<el-form-item label="规格">
<el-input v-model="itemFormDialog.form.specification" />
</el-form-item>
<el-form-item label="采购数量" prop="quantity">
<el-input-number v-model="itemFormDialog.form.quantity" :min="0" :precision="3" />
</el-form-item>
<el-form-item label="含税单价">
<el-input-number v-model="itemFormDialog.form.unitPrice" :min="0" :precision="2" />
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="itemFormDialog.form.remark" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="itemFormDialog.visible = false"> </el-button>
<el-button type="primary" @click="submitItem"> </el-button>
</div>
</el-dialog>
</div>
</template>
@@ -276,13 +254,9 @@ import {
partialPurchaseOrder,
completePurchaseOrder,
cancelPurchaseOrder,
listPurchaseOrderItem,
addPurchaseOrderItem,
updatePurchaseOrderItem,
delPurchaseOrderItem,
listSupplier,
listSupplierPrice
listSupplier
} from '@/api/erp/purchase'
import WarehouseSelect from "@/components/KLPService/WarehouseSelect/index.vue"
export default {
name: 'ErpPurchaseOrder',
@@ -298,26 +272,13 @@ export default {
orderCode: [{ required: true, message: '请输入订单编号', trigger: 'blur' }],
supplierId: [{ required: true, message: '请选择供应商', trigger: 'change' }]
},
itemDialog: { visible: false, title: '订单明细' },
itemQuery: { pageNum: 1, pageSize: 10, orderId: null },
itemList: [],
itemTotal: 0,
itemLoading: false,
itemFormDialog: { visible: false, title: '', form: {} },
itemRules: {
orderId: [{ required: true, message: '请输入订单ID', trigger: 'blur' }],
materialTypeCode: [{ required: true, message: '请输入物料类型', trigger: 'blur' }],
quantity: [{ required: true, message: '请输入数量', trigger: 'blur' }]
},
supplierOptions: [],
supplierLoading: false,
dialogItems: [],
dialogItemLoading: false,
supplierGoods: [],
goodsLoading: false,
selectedSupplierGood: null
supplierLoading: false
}
},
components: {
WarehouseSelect,
},
created() {
this.loadOrders()
// 默认加载一批供应商作为下拉初始选项
@@ -368,24 +329,36 @@ export default {
if (row) {
this.orderDialog.form = { ...row }
this.orderDialog.title = '编辑采购订单'
this.loadDialogItems(row.orderId)
if (row.supplierId) {
this.handleSupplierChange(row.supplierId)
}
} else {
this.orderDialog.form = { orderCode: '', supplierId: '', orderDate: '', orderStatus: 0 }
this.orderDialog.form = {
orderCode: '',
supplierId: '',
orderDate: '',
orderStatus: 0,
billType: '',
receiveType: '',
deptName: '',
salesman: '',
maker: '',
auditor: '',
bookkeeper: '',
auditDate: '',
totalAmount: 0,
supplyUnitId: null,
stockCode: '',
stockName: '',
specModel: '',
mainUnit: '',
stockQuantity: '',
warehouseId: null,
remark: ''
}
this.orderDialog.title = '新增采购订单'
this.dialogItems = []
this.supplierGoods = []
this.selectedSupplierGood = null
}
this.orderDialog.visible = true
this.$nextTick(() => this.$refs.orderForm && this.$refs.orderForm.clearValidate())
},
closeOrderDialog() {
this.dialogItems = []
this.supplierGoods = []
this.selectedSupplierGood = null
},
submitOrder() {
this.$refs.orderForm.validate(valid => {
@@ -394,7 +367,6 @@ export default {
api(this.orderDialog.form).then(() => {
this.$message.success('保存成功')
this.orderDialog.visible = false
this.closeOrderDialog()
this.loadOrders()
})
})
@@ -450,134 +422,6 @@ export default {
4: 'danger'
}
return map[status] || 'info'
},
openItemDialog() {
this.itemDialog.visible = true
this.loadItems()
},
loadItems() {
this.itemLoading = true
listPurchaseOrderItem(this.itemQuery)
.then(res => {
this.itemList = res.rows || []
this.itemTotal = res.total || 0
})
.finally(() => {
this.itemLoading = false
})
},
resetItemQuery() {
this.itemQuery = { pageNum: 1, pageSize: 10, orderId: null }
this.loadItems()
},
openItemForm(row, preset = {}) {
if (row) {
this.itemFormDialog.form = { ...row }
this.itemFormDialog.title = '编辑明细'
} else {
const targetOrderId = preset.orderId || this.orderDialog.form.orderId || ''
if (!targetOrderId) {
this.$message.warning('请先保存订单,再新增物料')
return
}
this.itemFormDialog.form = {
orderId: targetOrderId,
materialTypeCode: preset.materialTypeCode || '',
specification: preset.specification || '',
quantity: preset.quantity || 0,
unitPrice: preset.unitPrice || 0,
remark: preset.remark || ''
}
this.itemFormDialog.title = '新增明细'
}
this.itemFormDialog.visible = true
this.$nextTick(() => this.$refs.itemForm && this.$refs.itemForm.clearValidate())
},
submitItem() {
this.$refs.itemForm.validate(valid => {
if (!valid) return
const api = this.itemFormDialog.form.itemId ? updatePurchaseOrderItem : addPurchaseOrderItem
api(this.itemFormDialog.form).then(() => {
this.$message.success('保存成功')
this.itemFormDialog.visible = false
this.loadItems()
if (this.orderDialog.form.orderId) {
this.loadDialogItems(this.orderDialog.form.orderId)
}
})
})
},
handleDeleteItem(row) {
this.$confirm('确定删除该明细吗?', '提示').then(() => {
return delPurchaseOrderItem(row.itemId)
}).then(() => {
this.$message.success('删除成功')
this.loadItems()
if (this.orderDialog.form.orderId) {
this.loadDialogItems(this.orderDialog.form.orderId)
}
})
},
async loadDialogItems(orderId) {
if (!orderId) {
this.dialogItems = []
return
}
this.dialogItemLoading = true
try {
const res = await listPurchaseOrderItem({ pageNum: 1, pageSize: 100, orderId })
this.dialogItems = res.rows || []
} finally {
this.dialogItemLoading = false
}
},
handleSupplierChange(val) {
if (!val) {
this.supplierGoods = []
this.selectedSupplierGood = null
return
}
this.selectedSupplierGood = null
this.loadSupplierGoods(val)
},
ensureSupplierGoods() {
if (!this.orderDialog.form.supplierId || this.supplierGoods.length || this.goodsLoading) return
this.loadSupplierGoods(this.orderDialog.form.supplierId)
},
async loadSupplierGoods(supplierId) {
if (!supplierId) return
this.goodsLoading = true
try {
const res = await listSupplierPrice({ pageNum: 1, pageSize: 50, supplierId })
this.supplierGoods = res.rows || []
} finally {
this.goodsLoading = false
}
},
handleSelectGoods(code) {
if (!code) return
if (!this.orderDialog.form.orderId) {
this.$message.warning('请先保存订单,再添加物料')
this.selectedSupplierGood = null
return
}
const good = this.supplierGoods.find(item => item.materialTypeCode === code)
if (!good) return
this.openItemForm(null, {
orderId: this.orderDialog.form.orderId,
materialTypeCode: good.materialTypeCode,
specification: good.specification,
unitPrice: good.price || 0
})
this.$nextTick(() => {
this.selectedSupplierGood = null
})
},
formatGoodLabel(good) {
if (!good) return ''
const spec = good.specification ? ` / ${good.specification}` : ''
const price = good.price != null ? ` · ¥${good.price}` : ''
return `${good.materialTypeCode}${spec}${price}`
}
}
}
@@ -619,11 +463,8 @@ export default {
gap: 8px;
}
}
.danger {
color: #c0392b;
}
.order-dialog ::v-deep .el-dialog {
max-width: 960px;
max-width: 1200px;
}
.contract-layout {
display: flex;
@@ -641,26 +482,6 @@ export default {
font-size: 15px;
color: #1f2d3d;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
.subtitle {
font-size: 12px;
color: #9099a8;
}
}
.contract-card__toolbar {
display: flex;
gap: 12px;
align-items: center;
margin-bottom: 12px;
.toolbar-tip {
font-size: 12px;
color: #c97a1a;
}
.el-select {
max-width: 320px;
}
}
.contract-footer {
text-align: right;

View File

@@ -1,765 +0,0 @@
<template>
<div class="erp-purchase-page">
<section class="surface-panel metrics-panel">
<div class="metrics-grid">
<div class="metric">
<p class="label">采购总金额</p>
<p class="value">{{ summary.totalAmount | formatAmount }}</p>
</div>
<div class="metric">
<p class="label">计划采购量</p>
<p class="value">{{ summary.suggestionTotal }} </p>
</div>
<div class="metric">
<p class="label">待处理订单</p>
<p class="value">{{ summary.pendingOrder }}</p>
</div>
<div class="metric">
<p class="label">供应商数量</p>
<p class="value">{{ summary.supplierBrief.length }}</p>
</div>
</div>
</section>
<section class="surface-panel">
<header class="surface-header">
<span>采购需求分析</span>
<div class="surface-actions">
<el-switch v-model="persistResult" active-text="写入建议表" inactive-text="仅计算" />
<el-button type="primary" size="small" @click="handleAnalyze" :loading="analysisLoading">执行分析</el-button>
</div>
</header>
<el-table :data="mappingRows" border size="small" class="mapping-table">
<el-table-column label="产品">
<template slot-scope="scope">
<!-- <el-input v-model="scope.row.productId" placeholder="产品ID" /> -->
<ProductSelect v-model="scope.row.productId" placeholder="选择产品" />
</template>
</el-table-column>
<el-table-column label="原料">
<template slot-scope="scope">
<!-- <el-input v-model="scope.row.rawMaterialId" placeholder="原料ID" /> -->
<RawMaterialSelect v-model="scope.row.rawMaterialId" placeholder="选择原料" />
</template>
</el-table-column>
<el-table-column label="转换率">
<template slot-scope="scope">
<el-input-number v-model="scope.row.conversionRate" :min="0" :max="1" :step="0.01" />
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template slot-scope="scope">
<el-button type="text" @click="removeMappingRow(scope.$index)" :disabled="mappingRows.length === 1">移除</el-button>
</template>
</el-table-column>
</el-table>
<div class="mapping-toolbar">
<el-button icon="el-icon-plus" size="mini" @click="addMappingRow">新增映射</el-button>
</div>
<el-table
:data="analysisData"
v-loading="analysisLoading"
size="small"
border
class="analysis-table"
show-summary
:summary-method="analysisSummary"
>
<el-table-column label="产品" prop="productName">
<template slot-scope="scope">
<div class="cell-title">{{ scope.row.productName || '-' }}</div>
<div class="cell-sub">ID: {{ scope.row.productId || '-' }}</div>
</template>
</el-table-column>
<el-table-column label="规格" prop="specification" />
<el-table-column label="销售需求(吨)" prop="salesDemand" />
<el-table-column label="成品库存/卷">
<template slot-scope="scope">
<div>{{ scope.row.productStockWeight }}</div>
<div class="cell-sub">{{ scope.row.productStockCoilCount }} </div>
</template>
</el-table-column>
<el-table-column label="原料折算(吨)" prop="rawStockConverted" />
<el-table-column label="在途折算(吨)" prop="inTransitConverted" />
<el-table-column label="待下达折算(吨)" prop="pendingConverted" />
<el-table-column label="建议采购(吨)" prop="suggestedPurchase" />
</el-table>
</section>
<section class="surface-panel">
<header class="surface-header">
<span>执行控制</span>
</header>
<el-tabs v-model="activeTab" class="plain-tabs">
<el-tab-pane label="收货记录" name="receipt">
<div class="inline-filter">
<!-- <el-input v-model="receiptQuery.orderId" placeholder="订单ID" size="small" clearable /> -->
<OrderSelect v-model="receiptQuery.orderId" size="small" clearable />
<el-input v-model="receiptQuery.itemId" placeholder="明细ID" size="small" clearable />
<div class="filter-actions">
<el-button size="small" type="primary" @click="loadReceipts">查询</el-button>
<el-button size="small" @click="resetReceiptQuery">重置</el-button>
<el-button type="primary" size="small" @click="openReceiptDialog()">新增收货</el-button>
</div>
</div>
<el-table :data="receiptList" v-loading="receiptLoading" border size="small">
<el-table-column prop="receiptId" label="ID" width="80" />
<el-table-column prop="orderId" label="订单ID" />
<el-table-column prop="itemId" label="明细ID" />
<el-table-column prop="receivedQty" label="收货数量" />
<el-table-column prop="qualityResult" label="质检结果">
<template slot-scope="scope">
<el-tag :type="scope.row.qualityResult === 'NG' ? 'danger' : 'success'" size="mini">
{{ scope.row.qualityResult || '合格' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="receiptTime" label="收货时间" />
<el-table-column label="操作" width="160">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openReceiptDialog(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click="handleDeleteReceipt(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="receiptTotal > 0"
:total="receiptTotal"
:page.sync="receiptQuery.pageNum"
:limit.sync="receiptQuery.pageSize"
@pagination="loadReceipts"
/>
</el-tab-pane>
<el-tab-pane label="退货管理" name="return">
<div class="inline-filter">
<!-- <el-input v-model="returnQuery.orderId" placeholder="订单ID" size="small" clearable /> -->
<OrderSelect v-model="returnQuery.orderId" placeholder="选择订单" size="small" clearable />
<el-select v-model="returnQuery.status" placeholder="状态" size="small" clearable>
<el-option label="草稿" :value="0" />
<el-option label="完成" :value="1" />
</el-select>
<div class="filter-actions">
<el-button size="small" type="primary" @click="loadReturns">查询</el-button>
<el-button size="small" @click="resetReturnQuery">重置</el-button>
<el-button type="primary" size="small" @click="openReturnDialog()">新增退货</el-button>
<el-button size="small" @click="openReturnItemDialog()">退货明细</el-button>
</div>
</div>
<el-table :data="returnList" v-loading="returnLoading" border size="small">
<el-table-column prop="returnId" label="退货单ID" width="120" />
<el-table-column prop="orderId" label="订单ID" />
<el-table-column prop="returnType" label="类型" />
<el-table-column prop="reason" label="原因" />
<el-table-column prop="status" label="状态" width="140">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'info'" size="mini">
{{ scope.row.status === 1 ? '完成' : '草稿' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openReturnDialog(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click="handleDeleteReturn(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="returnTotal > 0"
:total="returnTotal"
:page.sync="returnQuery.pageNum"
:limit.sync="returnQuery.pageSize"
@pagination="loadReturns"
/>
</el-tab-pane>
</el-tabs>
</section>
<section class="surface-panel">
<header class="surface-header">
<span>采购报表</span>
<div class="surface-actions">
<el-date-picker
v-model="reportRange"
type="monthrange"
unlink-panels
value-format="yyyy-MM"
range-separator=""
start-placeholder="开始月份"
end-placeholder="结束月份"
@change="loadReports"
size="small"
/>
</div>
</header>
<div class="report-grid">
<div class="report-card">
<h4>供应商采购额TOP</h4>
<el-table :data="summary.supplierBrief" size="mini" height="220" border>
<el-table-column prop="supplierId" label="供应商ID" />
<el-table-column prop="totalAmount" label="金额" />
<el-table-column prop="orderCount" label="订单" width="90" />
</el-table>
</div>
<div class="report-card">
<h4>价格趋势</h4>
<el-table :data="priceTrend" size="mini" height="220" border>
<el-table-column prop="period" label="月份" />
<el-table-column prop="materialCode" label="物料编码" />
<el-table-column prop="avgPrice" label="平均含税单价" />
</el-table>
</div>
<div class="report-card">
<h4>供应商退货率</h4>
<el-table :data="supplierQuality" size="mini" height="220" border>
<el-table-column prop="supplierId" label="供应商ID" />
<el-table-column prop="receivedQty" label="收货量" />
<el-table-column prop="returnQty" label="退货量" />
<el-table-column label="退货率">
<template slot-scope="scope">
{{ formatPercent(scope.row.returnRate) }}
</template>
</el-table-column>
</el-table>
</div>
</div>
</section>
<!-- 收货弹窗 -->
<el-dialog :title="receiptDialog.title" :visible.sync="receiptDialog.visible" width="480px">
<el-form :model="receiptDialog.form" :rules="receiptRules" ref="receiptForm" label-width="100px" size="small">
<el-form-item label="订单ID" prop="orderId">
<!-- <el-input v-model="receiptDialog.form.orderId" /> -->
<OrderSelect v-model="receiptDialog.form.orderId" />
</el-form-item>
<el-form-item label="明细ID" prop="itemId">
<!-- <el-input v-model="receiptDialog.form.itemId" /> -->
<OrderDetailSelect v-model="receiptDialog.form.itemId" :orderId="receiptDialog.form.orderId" />
</el-form-item>
<el-form-item label="收货数量" prop="receivedQty">
<el-input-number v-model="receiptDialog.form.receivedQty" :min="0" :precision="3" />
</el-form-item>
<el-form-item label="质检结果" prop="qualityResult">
<el-select v-model="receiptDialog.form.qualityResult" clearable placeholder="默认合格">
<el-option label="合格" value="OK" />
<el-option label="不合格" value="NG" />
</el-select>
</el-form-item>
<el-form-item label="收货时间">
<el-date-picker
v-model="receiptDialog.form.receiptTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="自动取当前"
/>
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="receiptDialog.form.remark" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="receiptDialog.visible = false"> </el-button>
<el-button type="primary" @click="submitReceipt"> </el-button>
</div>
</el-dialog>
<!-- 退货弹窗 -->
<el-dialog :title="returnDialog.title" :visible.sync="returnDialog.visible" width="600px">
<el-form :model="returnDialog.form" :rules="returnRules" ref="returnForm" label-width="100px" size="small">
<el-form-item label="订单ID" prop="orderId">
<OrderSelect v-model="returnDialog.form.orderId" placeholder="选择订单" size="small" clearable />
</el-form-item>
<el-form-item label="退货类型">
<el-select v-model="returnDialog.form.returnType" clearable>
<el-option label="质量问题" value="QUALITY" />
<el-option label="数量错误" value="QTY" />
<el-option label="规格不符" value="SPEC" />
<el-option label="其他" value="OTHER" />
</el-select>
</el-form-item>
<el-form-item label="退货原因">
<el-input type="textarea" v-model="returnDialog.form.reason" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="returnDialog.form.status">
<el-option label="草稿" :value="0" />
<el-option label="完成" :value="1" />
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="returnDialog.form.remark" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="returnDialog.visible = false"> </el-button>
<el-button type="primary" @click="submitReturn"> </el-button>
</div>
</el-dialog>
<!-- 退货明细弹窗 -->
<el-dialog :title="returnItemDialog.title" :visible.sync="returnItemDialog.visible" width="600px">
<div class="toolbar">
<el-input v-model="returnItemQuery.returnId" placeholder="退货单ID" size="small" clearable class="toolbar-input" />
<OrderSelect v-model="returnItemQuery.orderId" />
<OrderDetailSelect v-model="returnItemQuery.itemId" :orderId="returnItemQuery.orderId" placeholder="选择订单明细" size="small" clearable />
<el-button size="small" type="primary" @click="loadReturnItems">查询</el-button>
<el-button size="small" @click="resetReturnItemQuery">重置</el-button>
<div class="toolbar-spacer"></div>
<el-button type="primary" size="small" @click="openReturnItemForm()">新增明细</el-button>
</div>
<el-table :data="returnItemList" v-loading="returnItemLoading" border size="small">
<el-table-column prop="returnItemId" label="ID" width="80" />
<el-table-column prop="returnId" label="退货单ID" width="100" />
<el-table-column prop="itemId" label="订单明细ID" width="120" />
<el-table-column prop="returnQty" label="退货数量" width="120" />
<el-table-column label="操作" width="140">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openReturnItemForm(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click="handleDeleteReturnItem(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="returnItemTotal > 0"
:total="returnItemTotal"
:page.sync="returnItemQuery.pageNum"
:limit.sync="returnItemQuery.pageSize"
@pagination="loadReturnItems"
/>
</el-dialog>
<!-- 退货明细编辑 -->
<el-dialog :title="returnItemFormDialog.title" :visible.sync="returnItemFormDialog.visible" width="420px">
<el-form ref="returnItemForm" :model="returnItemFormDialog.form" :rules="returnItemRules" label-width="110px" size="small">
<el-form-item label="退货单ID" prop="returnId">
<el-input v-model="returnItemFormDialog.form.returnId" />
</el-form-item>
<el-form-item label="订单ID" prop="orderId">
<OrderSelect v-model="returnItemFormDialog.form.orderId" placeholder="选择订单" size="small" clearable />
</el-form-item>
<el-form-item label="订单明细ID" prop="itemId">
<OrderDetailSelect v-model="returnItemFormDialog.form.itemId" :orderId="returnItemFormDialog.form.orderId" placeholder="选择订单明细" size="small" clearable />
</el-form-item>
<el-form-item label="退货数量" prop="returnQty">
<el-input-number v-model="returnItemFormDialog.form.returnQty" :min="0" :precision="3" />
</el-form-item>
<el-form-item label="照片">
<el-input v-model="returnItemFormDialog.form.photos" placeholder="图片URL用逗号分隔" />
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="returnItemFormDialog.form.remark" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="returnItemFormDialog.visible = false"> </el-button>
<el-button type="primary" @click="submitReturnItem"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
analyzePurchaseRequirement,
listPurchaseReceipt,
addPurchaseReceipt,
updatePurchaseReceipt,
delPurchaseReceipt,
listPurchaseReturn,
addPurchaseReturn,
updatePurchaseReturn,
delPurchaseReturn,
listPurchaseReturnItem,
addPurchaseReturnItem,
updatePurchaseReturnItem,
delPurchaseReturnItem,
getPurchaseReportSummary,
getPurchasePriceTrend,
getSupplierQuality
} from '@/api/erp/purchase'
import RawMaterialSelect from '@/components/KLPService/RawMaterialSelect'
import ProductSelect from '@/components/KLPService/ProductSelect'
import OrderSelect from '@/components/KLPService/OrderSelect'
import OrderDetailSelect from '@/components/KLPService/OrderDetailSelect'
export default {
name: 'ErpPurchaseWorkbench',
components: {
RawMaterialSelect,
ProductSelect,
OrderSelect,
OrderDetailSelect,
},
data() {
return {
mappingRows: [{ productId: '', rawMaterialId: '', conversionRate: 1 }],
persistResult: false,
analysisLoading: false,
analysisData: [],
summary: { totalAmount: 0, suggestionTotal: 0, pendingOrder: 0, supplierBrief: [] },
reportRange: [],
priceTrend: [],
supplierQuality: [],
activeTab: 'receipt',
receiptList: [],
receiptTotal: 0,
receiptLoading: false,
receiptQuery: { pageNum: 1, pageSize: 10, orderId: null, itemId: null },
receiptDialog: { visible: false, title: '', form: {} },
receiptRules: {
orderId: [{ required: true, message: '请输入订单ID', trigger: 'blur' }],
itemId: [{ required: true, message: '请输入明细ID', trigger: 'blur' }],
receivedQty: [{ required: true, message: '请输入收货数量', trigger: 'blur' }]
},
returnList: [],
returnTotal: 0,
returnLoading: false,
returnQuery: { pageNum: 1, pageSize: 10, orderId: null, status: null },
returnDialog: { visible: false, title: '', form: {} },
returnRules: {
orderId: [{ required: true, message: '请输入订单ID', trigger: 'blur' }]
},
returnItemDialog: { visible: false, title: '退货明细列表' },
returnItemQuery: { pageNum: 1, pageSize: 10, returnId: null, itemId: null },
returnItemList: [],
returnItemTotal: 0,
returnItemLoading: false,
returnItemFormDialog: { visible: false, title: '', form: {} },
returnItemRules: {
returnId: [{ required: true, message: '请输入退货单ID', trigger: 'blur' }],
itemId: [{ required: true, message: '请输入订单明细ID', trigger: 'blur' }],
returnQty: [{ required: true, message: '请输入退货数量', trigger: 'blur' }]
}
}
},
filters: {
formatAmount(val) {
if (!val) return '0.00'
return Number(val).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
},
created() {
this.loadReports()
this.loadReceipts()
this.loadReturns()
},
methods: {
addMappingRow() {
this.mappingRows.push({ productId: '', rawMaterialId: '', conversionRate: 1 })
},
removeMappingRow(index) {
if (this.mappingRows.length === 1) return
this.mappingRows.splice(index, 1)
},
handleAnalyze() {
if (!this.mappingRows.every(row => row.productId && row.rawMaterialId)) {
this.$message.warning('请补全产品与原料映射')
return
}
this.analysisLoading = true
const payload = {
mappings: this.mappingRows.map(row => ({
productId: Number(row.productId),
rawMaterialId: Number(row.rawMaterialId),
conversionRate: Number(row.conversionRate || 0)
})),
persistResult: this.persistResult
}
analyzePurchaseRequirement(payload)
.then(res => {
this.analysisData = res || []
this.summary.suggestionTotal = this.analysisData.reduce((sum, item) => sum + Number(item.suggestedPurchase || 0), 0)
this.$message.success('分析完成')
})
.finally(() => {
this.analysisLoading = false
})
},
analysisSummary({ data }) {
const sums = []
const columns = ['salesDemand', 'productStockWeight', 'rawStockConverted', 'inTransitConverted', 'pendingConverted', 'suggestedPurchase']
sums[0] = '总计'
data.forEach(row => {
columns.forEach((col, idx) => {
const value = Number(row[col] || 0)
sums[idx + 2] = (Number(sums[idx + 2]) || 0) + value
})
})
return sums
},
loadReports() {
const params = {}
if (this.reportRange && this.reportRange.length === 2) {
params.beginTime = `${this.reportRange[0]}-01`
params.endTime = `${this.reportRange[1]}-31`
}
getPurchaseReportSummary(params).then(res => {
this.summary.totalAmount = res.totalAmount || 0
this.summary.supplierBrief = res.bySupplier || []
this.summary.pendingOrder = this.summary.supplierBrief.reduce((sum, item) => sum + (item.orderCount || 0), 0)
})
getPurchasePriceTrend(params).then(res => {
this.priceTrend = res || []
})
getSupplierQuality(params).then(res => {
this.supplierQuality = res || []
})
},
formatPercent(value) {
if (!value || !isFinite(value)) return '0%'
return `${(Number(value) * 100).toFixed(2)}%`
},
// 收货
loadReceipts() {
this.receiptLoading = true
listPurchaseReceipt(this.receiptQuery)
.then(res => {
this.receiptList = res.rows || []
this.receiptTotal = res.total || 0
})
.finally(() => {
this.receiptLoading = false
})
},
resetReceiptQuery() {
this.receiptQuery = { pageNum: 1, pageSize: 10, orderId: null, itemId: null }
this.loadReceipts()
},
openReceiptDialog(row) {
if (row) {
this.receiptDialog.form = { ...row }
this.receiptDialog.title = '编辑收货'
} else {
this.receiptDialog.form = { orderId: '', itemId: '', receivedQty: 0, qualityResult: 'OK' }
this.receiptDialog.title = '新增收货'
}
this.receiptDialog.visible = true
this.$nextTick(() => this.$refs.receiptForm && this.$refs.receiptForm.clearValidate())
},
submitReceipt() {
this.$refs.receiptForm.validate(valid => {
if (!valid) return
const api = this.receiptDialog.form.receiptId ? updatePurchaseReceipt : addPurchaseReceipt
api(this.receiptDialog.form).then(() => {
this.$message.success('保存成功')
this.receiptDialog.visible = false
this.loadReceipts()
})
})
},
handleDeleteReceipt(row) {
this.$confirm('确定删除该收货记录吗?', '提示').then(() => {
return delPurchaseReceipt(row.receiptId)
}).then(() => {
this.$message.success('删除成功')
this.loadReceipts()
})
},
// 退货
loadReturns() {
this.returnLoading = true
listPurchaseReturn(this.returnQuery)
.then(res => {
this.returnList = res.rows || []
this.returnTotal = res.total || 0
})
.finally(() => {
this.returnLoading = false
})
},
resetReturnQuery() {
this.returnQuery = { pageNum: 1, pageSize: 10, orderId: null, status: null }
this.loadReturns()
},
openReturnDialog(row) {
if (row) {
this.returnDialog.form = { ...row }
this.returnDialog.title = '编辑退货单'
} else {
this.returnDialog.form = { orderId: '', returnType: 'QUALITY', status: 0 }
this.returnDialog.title = '新增退货单'
}
this.returnDialog.visible = true
this.$nextTick(() => this.$refs.returnForm && this.$refs.returnForm.clearValidate())
},
submitReturn() {
this.$refs.returnForm.validate(valid => {
if (!valid) return
const api = this.returnDialog.form.returnId ? updatePurchaseReturn : addPurchaseReturn
api(this.returnDialog.form).then(() => {
this.$message.success('保存成功')
this.returnDialog.visible = false
this.loadReturns()
})
})
},
handleDeleteReturn(row) {
this.$confirm('确定删除该退货单吗?', '提示').then(() => {
return delPurchaseReturn(row.returnId)
}).then(() => {
this.$message.success('删除成功')
this.loadReturns()
})
},
// 退货明细
openReturnItemDialog() {
this.returnItemDialog.visible = true
this.loadReturnItems()
},
loadReturnItems() {
this.returnItemLoading = true
listPurchaseReturnItem(this.returnItemQuery)
.then(res => {
this.returnItemList = res.rows || []
this.returnItemTotal = res.total || 0
})
.finally(() => {
this.returnItemLoading = false
})
},
resetReturnItemQuery() {
this.returnItemQuery = { pageNum: 1, pageSize: 10, returnId: null, itemId: null }
this.loadReturnItems()
},
openReturnItemForm(row) {
if (row) {
this.returnItemFormDialog.form = { ...row }
this.returnItemFormDialog.title = '编辑退货明细'
} else {
this.returnItemFormDialog.form = { returnId: '', itemId: '', returnQty: 0, orderId: row.orderId }
this.returnItemFormDialog.title = '新增退货明细'
}
this.returnItemFormDialog.visible = true
this.$nextTick(() => this.$refs.returnItemForm && this.$refs.returnItemForm.clearValidate())
},
submitReturnItem() {
this.$refs.returnItemForm.validate(valid => {
if (!valid) return
const api = this.returnItemFormDialog.form.returnItemId ? updatePurchaseReturnItem : addPurchaseReturnItem
api(this.returnItemFormDialog.form).then(() => {
this.$message.success('保存成功')
this.returnItemFormDialog.visible = false
this.loadReturnItems()
})
})
},
handleDeleteReturnItem(row) {
this.$confirm('确定删除该退货明细吗?', '提示').then(() => {
return delPurchaseReturnItem(row.returnItemId)
}).then(() => {
this.$message.success('删除成功')
this.loadReturnItems()
})
}
}
}
</script>
<style lang="scss" scoped>
.erp-purchase-page {
padding: 16px;
min-height: 100%;
display: flex;
flex-direction: column;
gap: 16px;
}
.surface-panel {
background: #fff;
border: 1px solid #d6dce1;
border-radius: 4px;
padding: 16px;
}
.surface-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
color: #1c2b36;
margin-bottom: 12px;
}
.surface-actions {
display: flex;
align-items: center;
gap: 12px;
}
.metrics-panel {
padding: 0;
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 12px;
padding: 16px;
}
.metric {
border: 1px solid #d6dce1;
padding: 12px;
background: #f9fafb;
.label {
font-size: 13px;
color: #6b7785;
margin-bottom: 4px;
}
.value {
font-size: 20px;
font-weight: 600;
color: #16212b;
}
}
.mapping-table {
margin-bottom: 8px;
}
.mapping-toolbar {
margin-bottom: 12px;
}
.analysis-table {
margin-top: 8px;
}
.cell-title {
font-weight: 600;
color: #1f2d3d;
}
.cell-sub {
color: #7c8792;
font-size: 12px;
}
.inline-filter {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
> *:not(.filter-actions) {
flex: 1 1 160px;
}
.filter-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
}
.report-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 12px;
}
.report-card {
border: 1px solid #d6dce1;
padding: 12px;
background: #fdfdfd;
h4 {
margin: 0 0 8px;
font-size: 14px;
color: #2a313c;
}
}
.plain-tabs ::v-deep .el-tabs__header {
border-bottom: 1px solid #d6dce1;
margin-bottom: 12px;
}
.plain-tabs ::v-deep .el-tabs__content {
padding: 0;
}
</style>

View File

@@ -185,28 +185,8 @@
</el-select>
</template>
</el-table-column>
<!-- <el-table-column label="关联信息" align="center" :show-overflow-tooltip="true">
<template slot-scope="scope">
<span v-if="scope.row.parentCoilNos && scope.row.hasMergeSplit === 1 && scope.row.dataType === 1">
<el-tag type="warning" size="mini">来自母卷{{ scope.row.parentCoilNos }}</el-tag>
</span>
<span v-else-if="scope.row.parentCoilNos && scope.row.dataType === 0">
<el-tag type="info" size="mini">分为子卷{{ scope.row.parentCoilNos }}</el-tag>
</span>
<span v-else-if="scope.row.parentCoilNos && scope.row.hasMergeSplit === 2">
<el-tag type="success" size="mini">合并自{{ scope.row.parentCoilNos }}</el-tag>
</span>
<span v-else></span>
</template>
</el-table-column> -->
<el-table-column v-if="showGrade" label="质量状态" align="center" prop="qualityStatus">
<!-- <template slot-scope="scope">
<el-select v-model="scope.row.qualityStatus" placeholder="请选择质量状态" @change="handleGradeChange(scope.row)">
<el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :value="item.value"
:label="item.label" />
</el-select>
</template> -->
</el-table-column>
<el-table-column label="逻辑库位" align="center" prop="warehouseId" v-if="editWarehouse">
<template slot-scope="scope">
@@ -215,6 +195,8 @@
</template>
</el-table-column>
<el-table-column v-if="hasTransferType" label="实际库区" align="center" prop="actualWarehouseName" />
<el-table-column v-if="moreColumn" label="规格" prop="specification"></el-table-column>
@@ -229,6 +211,7 @@
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
<el-table-column v-if="hasTransferType" label="调拨类型" align="center" prop="transferType" />
<el-table-column v-if="hasTransferType" label="改判原因" align="center" prop="rejudgeInfo.rejudgeReason" />
<el-table-column label="钢卷去向" align="center" prop="nextWarehouseId" v-if="editNext" width="150">
<template slot-scope="scope">
@@ -639,7 +622,8 @@ import {
cancelExportCoil,
checkCoilNo,
returnCoil,
getCoilStatisticsList
getCoilStatisticsList,
listWithAdjustRecordCoil
} from "@/api/wms/coil";
import { listBoundCoil } from "@/api/wms/deliveryWaybillDetail";
import { addPendingAction } from "@/api/wms/pendingAction";
@@ -1339,6 +1323,19 @@ export default {
})
return;
}
if (this.hasTransferType) {
listWithAdjustRecordCoil(query).then(response => {
this.materialCoilList = response.rows
this.total = response.total;
this.loading = false;
});
getCoilStatisticsList(statisticQuery).then(res => {
this.statistics = res.data || [];
})
return;
}
listMaterialCoil(query).then(response => {
this.materialCoilList = response.rows
this.total = response.total;
@@ -1744,6 +1741,7 @@ export default {
},
handleExportAll() {
const { orderBy, ...query } = this.queryParams;
query.selectType = query.itemType;
this.download('wms/materialCoil/export', query, `materialCoil_${new Date().getTime()}.xlsx`)
},
handleExportAllProps() {