Files
klp-oa/klp-ui/src/views/erp/dashboard/index.vue
砂糖 d09079f4c1 feat(wms): 新增带改判记录的钢卷列表接口及展示
feat(crm): 在订单异议页面添加异议内容和处理结果的HTML展示

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

refactor(wms): 移除钢卷列表中的冗余代码并添加改判原因列
2026-05-09 14:08:11 +08:00

804 lines
21 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="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>