diff --git a/src/views/SeikoWorkshop/ProductionPlan/index.vue b/src/views/SeikoWorkshop/ProductionPlan/index.vue index d468c060..e67b97f0 100644 --- a/src/views/SeikoWorkshop/ProductionPlan/index.vue +++ b/src/views/SeikoWorkshop/ProductionPlan/index.vue @@ -12,14 +12,18 @@ - + 查询 + 可视化看板 + + 本月完成率:{{ monthCompletionRate }} + - + @@ -154,29 +224,97 @@ export default { data() { return { monthValue: '', - orderNumberTypeValue: '', // 订单编号 - partNameValue: '', // 零件名称或图号 - productNameValue: '', // 产品名称 - statusValue: '', // 状态 + orderNumberTypeValue: '', + partNameValue: '', + productNameValue: '', + statusValue: '', loading: false, - tableData2: [] + tableData2: [], + dashboardVisible: false, + statusPieChart: null, + completionBarChart: null, + processStackChart: null, + pieViewMode: 'status', + cardViewMode: 'status' } }, computed: { ...mapGetters([ 'id', 'token' - ]) + ]), + monthCompletionRate() { + if (!this.tableData2 || this.tableData2.length === 0) return '' + var completed = 0 + this.tableData2.forEach(function(row) { + if (row.状态 === '已完成') completed++ + }) + var avg = Math.round(completed / this.tableData2.length * 100) + return avg + '%' + }, + filteredTableData() { + if (!this.statusValue) return this.tableData2 + var val = this.statusValue + return this.tableData2.filter(function(row) { + return row.状态 === val + }) + }, + monthCompletionRateTag() { + var rate = parseInt(this.monthCompletionRate) || 0 + if (rate === 100) return 'info' + if (rate >= 50) return 'success' + return 'warning' + }, + dashboardStats() { + var total = this.tableData2.length + var pending = 0 + var producing = 0 + var completed = 0 + this.tableData2.forEach(function(row) { + if (row.状态 === '已完成') completed++ + else if (row.状态 === '生产中') producing++ + else pending++ + }) + return { total: total, pending: pending, producing: producing, completed: completed } + }, + orderTypeStats() { + var salesTotal = 0 + var restockTotal = 0 + var salesPending = 0 + var restockPending = 0 + var salesProducing = 0 + var restockProducing = 0 + var salesCompleted = 0 + var restockCompleted = 0 + this.tableData2.forEach(function(row) { + var isRestock = row.收货人 === null + if (isRestock) { + restockTotal++ + if (row.状态 === '已完成') restockCompleted++ + else if (row.状态 === '生产中') restockProducing++ + else restockPending++ + } else { + salesTotal++ + if (row.状态 === '已完成') salesCompleted++ + else if (row.状态 === '生产中') salesProducing++ + else salesPending++ + } + }) + return { + salesTotal: salesTotal, restockTotal: restockTotal, + salesPending: salesPending, restockPending: restockPending, + salesProducing: salesProducing, restockProducing: restockProducing, + salesCompleted: salesCompleted, restockCompleted: restockCompleted + } + } }, - // updated() { - // this.$nextTick(() => { - // this.$refs['table'].doLayout() - // }) - // }, created() { this.getNowMonth() this.searchTable1() }, + beforeDestroy() { + this.disposeCharts() + }, methods: { getNowMonth() { const nowDate = new Date() @@ -285,6 +423,295 @@ export default { delete group._completedProcesses }) return Object.values(grouped) + }, + openDashboard() { + if (this.tableData2.length === 0) { + this.$message.warning('暂无数据,请先查询') + return + } + this.dashboardVisible = true + this.$nextTick(function() { + this.drawStatusPieChart() + this.drawCompletionBarChart() + this.drawProcessStackChart() + }) + }, + closeDashboard() { + this.dashboardVisible = false + }, + disposeCharts() { + if (this.statusPieChart) { + this.statusPieChart.dispose() + this.statusPieChart = null + } + if (this.completionBarChart) { + this.completionBarChart.dispose() + this.completionBarChart = null + } + if (this.processStackChart) { + this.processStackChart.dispose() + this.processStackChart = null + } + }, + drawStatusPieChart() { + var self = this + this.statusPieChart = this.$echarts.init(document.getElementById('statusPieChart')) + this.statusPieChart.clear() + this.statusPieChart.off('click') + this.statusPieChart.on('click', function() { + self.pieViewMode = self.pieViewMode === 'status' ? 'orderType' : 'status' + self.updatePieChart() + }) + this.updatePieChart() + }, + updatePieChart() { + if (!this.statusPieChart) return + this.statusPieChart.clear() + if (this.pieViewMode === 'status') { + var stats = this.dashboardStats + this.statusPieChart.setOption({ + title: { + text: '订单状态分布(点击切换)', + left: 'center', + textStyle: { fontSize: 14 } + }, + tooltip: { + trigger: 'item', + formatter: '{b}: {c}单 ({d}%)' + }, + legend: { + bottom: 10, + data: ['派工中', '生产中', '已完成'] + }, + color: ['#409EFF', '#67C23A', '#909399'], + series: [{ + type: 'pie', + radius: ['40%', '65%'], + center: ['50%', '45%'], + avoidLabelOverlap: true, + label: { + show: true, + formatter: '{b}\n{c}单' + }, + data: [ + { value: stats.pending, name: '派工中' }, + { value: stats.producing, name: '生产中' }, + { value: stats.completed, name: '已完成' } + ] + }] + }) + } else { + var salesCount = 0 + var restockCount = 0 + this.tableData2.forEach(function(row) { + if (row.收货人 === null) { + restockCount++ + } else { + salesCount++ + } + }) + this.statusPieChart.setOption({ + title: { + text: '订单类型分布(点击切换)', + left: 'center', + textStyle: { fontSize: 14 } + }, + tooltip: { + trigger: 'item', + formatter: '{b}: {c}单 ({d}%)' + }, + legend: { + bottom: 10, + data: ['销售订单', '补货订单'] + }, + color: ['#409EFF', '#E6A23C'], + series: [{ + type: 'pie', + radius: ['40%', '65%'], + center: ['50%', '45%'], + avoidLabelOverlap: true, + label: { + show: true, + formatter: '{b}\n{c}单' + }, + data: [ + { value: salesCount, name: '销售订单' }, + { value: restockCount, name: '补货订单' } + ] + }] + }) + } + }, + drawCompletionBarChart() { + var orderLabels = [] + var completionRates = [] + var planCounts = [] + this.tableData2.forEach(function(row) { + var label = row.MK订单编号 || row.订单号 || '' + if (label.length > 10) label = label.substring(0, 10) + '...' + orderLabels.push(label) + completionRates.push(parseInt(row.完成率) || 0) + planCounts.push(row.计划数 || 0) + }) + this.completionBarChart = this.$echarts.init(document.getElementById('completionBarChart')) + this.completionBarChart.clear() + this.completionBarChart.setOption({ + title: { + text: '各订单完成率', + left: 'center', + textStyle: { fontSize: 14 } + }, + tooltip: { + trigger: 'axis', + axisPointer: { type: 'shadow' }, + formatter: function(params) { + var tip = params[0].axisValue + '
' + params.forEach(function(p) { + tip += p.marker + p.seriesName + ': ' + p.value + (p.seriesName === '完成率' ? '%' : '') + '
' + }) + return tip + } + }, + legend: { + bottom: 0, + data: ['完成率', '计划数'] + }, + grid: { + left: '3%', + right: '4%', + bottom: '15%', + top: '15%', + containLabel: true + }, + dataZoom: [ + { type: 'inside', start: 0, end: 100 }, + { type: 'slider', start: 0, end: 100, height: 20, bottom: 30 } + ], + xAxis: { + type: 'category', + data: orderLabels, + axisLabel: { rotate: 30, fontSize: 10 } + }, + yAxis: [ + { + type: 'value', + name: '完成率(%)', + max: 100, + axisLabel: { formatter: '{value}%' } + }, + { + type: 'value', + name: '计划数', + axisLabel: { formatter: '{value}' } + } + ], + series: [ + { + name: '完成率', + type: 'bar', + yAxisIndex: 0, + data: completionRates, + itemStyle: { + color: function(params) { + var val = params.value + if (val === 100) return '#67C23A' + if (val >= 50) return '#E6A23C' + return '#F56C6C' + } + }, + label: { + show: true, + position: 'top', + formatter: '{c}%' + } + }, + { + name: '计划数', + type: 'line', + yAxisIndex: 1, + data: planCounts, + itemStyle: { color: '#409EFF' }, + lineStyle: { width: 2 }, + symbolSize: 6 + } + ] + }) + }, + drawProcessStackChart() { + var opKeys = ['OP010', 'OP020', 'OP030', 'OP040', 'OP050', 'OP060', 'OP070', 'OP080', 'OP090', 'OP100', 'OP110'] + var opLabels = [] + var hasProcess = [] + var noProcess = [] + var self = this + opKeys.forEach(function(key) { + opLabels.push(key) + var count = 0 + var empty = 0 + self.tableData2.forEach(function(row) { + if (row[key] && row[key] !== '') { + count++ + } else { + empty++ + } + }) + hasProcess.push(count) + noProcess.push(empty) + }) + this.processStackChart = this.$echarts.init(document.getElementById('processStackChart')) + this.processStackChart.clear() + this.processStackChart.setOption({ + title: { + text: '各工序覆盖情况', + left: 'center', + textStyle: { fontSize: 14 } + }, + tooltip: { + trigger: 'axis', + axisPointer: { type: 'shadow' }, + formatter: function(params) { + var tip = params[0].axisValue + '
' + params.forEach(function(p) { + tip += p.marker + p.seriesName + ': ' + p.value + '单
' + }) + return tip + } + }, + legend: { + bottom: 0, + data: ['已安排工序', '未安排工序'] + }, + grid: { + left: '3%', + right: '4%', + bottom: '12%', + top: '15%', + containLabel: true + }, + xAxis: { + type: 'category', + data: opLabels + }, + yAxis: { + type: 'value', + name: '订单数' + }, + series: [ + { + name: '已安排工序', + type: 'bar', + stack: 'total', + data: hasProcess, + itemStyle: { color: '#67C23A' } + }, + { + name: '未安排工序', + type: 'bar', + stack: 'total', + data: noProcess, + itemStyle: { color: '#E6E6E6' } + } + ] + }) } } } diff --git a/src/views/SeikoWorkshop/WorkshopAssignment/index.vue b/src/views/SeikoWorkshop/WorkshopAssignment/index.vue index 64ba30f4..49a9416d 100644 --- a/src/views/SeikoWorkshop/WorkshopAssignment/index.vue +++ b/src/views/SeikoWorkshop/WorkshopAssignment/index.vue @@ -262,7 +262,7 @@ @click="editData(scope.row)">编辑