Files
klp-oa/klp-ui/src/views/erp/dashboard/index.vue

804 lines
21 KiB
Vue
Raw Normal View History

<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>