| | |
| | | /> |
| | | </div> |
| | | <div class="category-cards"> |
| | | <div v-for="(it, idx) in categoryPieData" :key="it.name" class="category-card"> |
| | | <div class="category-name">产å大类{{ idx + 1 }}</div> |
| | | <div v-for="(it, idx) in categoryPieData" :key="idx" class="category-card"> |
| | | <div class="category-name">{{ it.name }}</div> |
| | | <div class="category-val">{{ it.value }}</div> |
| | | </div> |
| | | </div> |
| | |
| | | <div class="bottom-row"> |
| | | <div class="panel panel-full"> |
| | | <div class="panel-header"> |
| | | <div class="panel-title">å·¥åºä¸è¯åå åæ</div> |
| | | <div class="panel-title">å·¥åºä¸è¯çåæ</div> |
| | | <div class="panel-actions"> |
| | | <div class="panel-action">年份</div> |
| | | <div class="panel-action">çéæ¶é´èå´</div> |
| | | <el-date-picker |
| | | v-model="currentYear" |
| | | type="year" |
| | | format="YYYYå¹´" |
| | | value-format="YYYY" |
| | | :clearable="false" |
| | | class="panel-year-picker" |
| | | v-model="dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | :clearable="true" |
| | | class="panel-date-picker" |
| | | size="small" |
| | | @change="handleDateChange" |
| | | /> |
| | | </div> |
| | | </div> |
| | | <div class="panel-body"> |
| | | <div class="bottom-chart-wrap"> |
| | | <div class="bottom-chart-wrap" v-loading="isLoading" element-loading-text="å è½½ä¸..."> |
| | | <div class="chart-unit">åä½ï¼%</div> |
| | | <Echarts |
| | | :chartStyle="{ width: '100%', height: '260px' }" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, ref } from 'vue' |
| | | import { |
| | | findProductQualityStatistics, |
| | | findProductWorkOrderCountStatistics, |
| | | findProductProductionStatistics, |
| | | findProductOutputCategoryPieData, |
| | | findProductDefectReasonAnalysis, |
| | | findProductProcessDefectRateAnalysis |
| | | } from '@/api/productionManagement/productionStatistic.js' |
| | | |
| | | import {computed, onMounted, ref} from 'vue' |
| | | import * as echarts from 'echarts' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import { ElSelect, ElOption, ElDatePicker } from 'element-plus' |
| | |
| | | } |
| | | |
| | | const kpi = ref({ |
| | | workOrderTotal: 378, |
| | | workOrderDoing: 108, |
| | | workOrderDone: 238, |
| | | passRate: 98, |
| | | ngRate: 8, |
| | | scrapTotal: 38, |
| | | productionAmount: 989878, |
| | | productionAmountTrend: 16, |
| | | productionQty: 19878, |
| | | productionQtyTrend: 16, |
| | | productionCost: 69878, |
| | | productionCostTrend: -16, |
| | | supplierCount: 48, |
| | | supplierCountTrend: 16, |
| | | totalOutput: 1000, |
| | | workOrderTotal: 0, |
| | | workOrderDoing: 0, |
| | | workOrderDone: 0, |
| | | passRate: 0, |
| | | ngRate: 0, |
| | | scrapTotal: 0, |
| | | productionAmount: 0, // æ»ç产æ»é |
| | | productionAmountTrend: 0, // æ»ç产æ»éè¾ä¸æè¶å¿ |
| | | productionCost: 0, // çäº§æ»æ¶è |
| | | productionCostTrend: 0, // çäº§æ»æ¶èè¾ä¸æè¶å¿ |
| | | supplierCount: 0, // æ»ä¾åºå |
| | | supplierCountTrend: 0, // æ»ä¾åºåè¾ä¸æè¶å¿ |
| | | }) |
| | | |
| | | const currentYear = ref('2025') |
| | | const dateRange = ref([]) |
| | | |
| | | function formatMoney(n) { |
| | | const num = Number(n || 0) |
| | |
| | | const axisTooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'shadow' }, |
| | | confine: true, // éå¶å¨å¾è¡¨åºåå
æ¾ç¤º |
| | | position: 'top', // åºå®æ¾ç¤ºå¨é¡¶é¨ |
| | | enterable: true, // å
è®¸é¼ æ è¿å
¥æç¤ºæ¡åºå |
| | | extraCssText: 'max-height: 200px; overflow-y: auto; padding: 10px;', // æ·»å æå¤§é«åº¦åæ»å¨æ¡ |
| | | formatter: (params) => { |
| | | const first = params?.[0] |
| | | const x = first?.axisValueLabel || '' |
| | |
| | | |
| | | // ä¸é¨å·¦ä¾§ï¼é¥¼ç¶å¾ï¼æäº§å大类ç»è®¡äº§åºæ°éï¼ |
| | | const categoryPieColors = ['#2D5BFF', '#4E8AFF', '#00A4ED', '#26C6DA', '#7C3AED', '#F59E0B', '#EF4444', '#10B981'] |
| | | const categoryPieData = ref([ |
| | | { name: '产å大类1', value: 600 }, |
| | | { name: '产å大类2', value: 200 }, |
| | | { name: '产å大类3', value: 200 }, |
| | | { name: '产å大类4', value: 200 }, |
| | | { name: '产å大类5', value: 200 }, |
| | | ]) |
| | | const categoryPieData = ref([]) |
| | | |
| | | const categoryPieLegend = computed(() => { |
| | | // 设计å¾ä¸é¥¼å¾æ¬èº«æ¾ç¤ºå æ¯æ ç¾ï¼ä¸é¢å¤å±ç¤ºå¾ä¾ |
| | |
| | | // ä¸é¨å³ä¾§ï¼ç¯å½¢é¥¼å¾ |
| | | const ngColors = ['#2D5BFF', '#26C6DA', '#F59E0B', '#7C3AED', '#60A5FA', '#10B981'] |
| | | |
| | | const ngReasonData = ref([ |
| | | { name: 'ä¸è¯åå 1', value: 300 }, |
| | | { name: 'ä¸è¯åå 3', value: 200 }, |
| | | { name: 'ä¸è¯åå 4', value: 100 }, |
| | | { name: 'ä¸è¯åå 5', value: 100 }, |
| | | { name: 'ä¸è¯åå 6', value: 100 }, |
| | | ]) |
| | | const ngReasonData = ref([]) |
| | | const ngRateData = ref([]) |
| | | const isLoading = ref(false) |
| | | |
| | | const ngTotal = computed(() => ngReasonData.value.reduce((s, it) => s + Number(it.value || 0), 0)) |
| | | const ngRows = computed(() => { |
| | |
| | | // åºé¨ï¼æçº¿å¾ï¼å¹´ä»½èå¨çæ¯ä¸ªå·¥åºä¸è¯çï¼ |
| | | const lineGrid = { left: '4%', right: '4%', top: 60, bottom: 30, containLabel: true } |
| | | const lineLegend = computed(() => { |
| | | const processes = chartData.value.processes || [] |
| | | const processNames = processes.map(p => p.name) |
| | | return { |
| | | show: true, |
| | | top: 8, |
| | | type: 'scroll', |
| | | orient: 'horizontal', |
| | | textStyle: { color: '#666' }, |
| | | data: ['å¹³åä¸è¯ç', 'å·¥åºA', 'å·¥åºB', 'å·¥åºC'], |
| | | data: ['å¹³åä¸è¯ç', ...processNames], |
| | | pageIconSize: 10, |
| | | pageTextStyle: { color: '#666' }, |
| | | pageButtonItemGap: 5, |
| | | pageButtonGap: 10 |
| | | } |
| | | }) |
| | | |
| | | const chartDataByYear = { |
| | | '2023': { |
| | | x: ['2023/02/21', '2023/03/02', '2023/03/13', '2023/03/24', '2023/04/04', '2023/04/15', '2023/04/28'], |
| | | bar: [30, 32, 35, 40, 38, 42, 39], |
| | | lineA: [45, 48, 52, 60, 55, 62, 58], |
| | | lineB: [30, 32, 38, 45, 40, 48, 42], |
| | | lineC: [20, 24, 30, 35, 32, 38, 33], |
| | | }, |
| | | '2024': { |
| | | x: ['2024/02/21', '2024/03/02', '2024/03/13', '2024/03/24', '2024/04/04', '2024/04/15', '2024/04/28'], |
| | | bar: [28, 30, 33, 37, 35, 39, 36], |
| | | lineA: [42, 46, 50, 58, 53, 60, 56], |
| | | lineB: [28, 31, 36, 43, 38, 46, 40], |
| | | lineC: [18, 21, 27, 33, 29, 35, 30], |
| | | }, |
| | | '2025': { |
| | | x: ['2025/02/21', '2025/03/02', '2025/03/13', '2025/03/24', '2025/04/04', '2025/04/15', '2025/04/28'], |
| | | bar: [32, 34, 37, 42, 40, 45, 41], |
| | | lineA: [45, 49, 54, 61, 57, 64, 60], |
| | | lineB: [31, 33, 39, 47, 41, 50, 44], |
| | | lineC: [21, 25, 31, 37, 34, 40, 35], |
| | | }, |
| | | } |
| | | |
| | | const chartData = computed(() => { |
| | | return chartDataByYear[currentYear.value] || chartDataByYear['2025'] |
| | | const data = ngRateData.value |
| | | if (!data || data.length === 0) { |
| | | return { |
| | | x: [], |
| | | bar: [], |
| | | processes: [] |
| | | } |
| | | } |
| | | |
| | | // Extract data from the API response |
| | | const x = data.map(item => item.date) |
| | | const bar = data.map(item => item.averageDefectRate) // Convert to percentage |
| | | |
| | | // Extract process names and their data |
| | | const processNames = new Set() |
| | | data.forEach(item => { |
| | | item.processes.forEach(process => { |
| | | Object.keys(process).forEach(name => processNames.add(name)) |
| | | }) |
| | | }) |
| | | |
| | | const processes = Array.from(processNames).map(name => { |
| | | return { |
| | | name, |
| | | data: data.map(item => { |
| | | const process = item.processes.find(p => p[name] !== undefined) |
| | | return process ? process[name] : 0 // Convert to percentage |
| | | }) |
| | | } |
| | | }) |
| | | |
| | | return { |
| | | x, |
| | | bar, |
| | | processes |
| | | } |
| | | }) |
| | | |
| | | const lineXAxis = computed(() => { |
| | |
| | | ] |
| | | }) |
| | | |
| | | const lineSeries = computed(() => [ |
| | | { |
| | | name: 'å¹³åä¸è¯ç', |
| | | type: 'bar', |
| | | barWidth: 18, |
| | | data: chartData.value.bar, |
| | | itemStyle: { |
| | | color: 'rgba(59, 130, 246, 0.15)', |
| | | borderRadius: [4, 4, 0, 0], |
| | | const lineSeries = computed(() => { |
| | | const series = [ |
| | | { |
| | | name: 'å¹³åä¸è¯ç', |
| | | type: 'bar', |
| | | barWidth: 18, |
| | | data: chartData.value.bar, |
| | | itemStyle: { |
| | | color: 'rgba(59, 130, 246, 0.15)', |
| | | borderRadius: [4, 4, 0, 0], |
| | | }, |
| | | }, |
| | | }, |
| | | { |
| | | name: 'å·¥åºA', |
| | | type: 'line', |
| | | smooth: true, |
| | | data: chartData.value.lineA, |
| | | symbol: 'circle', |
| | | symbolSize: 6, |
| | | lineStyle: { width: 2, color: '#3b82f6' }, |
| | | itemStyle: { color: '#3b82f6' }, |
| | | }, |
| | | { |
| | | name: 'å·¥åºB', |
| | | type: 'line', |
| | | smooth: true, |
| | | data: chartData.value.lineB, |
| | | symbol: 'circle', |
| | | symbolSize: 6, |
| | | lineStyle: { width: 2, color: '#f59e0b' }, |
| | | itemStyle: { color: '#f59e0b' }, |
| | | }, |
| | | { |
| | | name: 'å·¥åºC', |
| | | type: 'line', |
| | | smooth: true, |
| | | data: chartData.value.lineC, |
| | | symbol: 'circle', |
| | | symbolSize: 6, |
| | | lineStyle: { width: 2, color: '#10b981' }, |
| | | itemStyle: { color: '#10b981' }, |
| | | }, |
| | | ]) |
| | | ] |
| | | |
| | | // Add process lines with different colors |
| | | const colors = ['#3b82f6', '#f59e0b', '#10b981', '#8b5cf6', '#ec4899', '#14b8a6', '#f97316'] |
| | | chartData.value.processes.forEach((process, index) => { |
| | | series.push({ |
| | | name: process.name, |
| | | type: 'line', |
| | | smooth: true, |
| | | data: process.data, |
| | | symbol: 'circle', |
| | | symbolSize: 6, |
| | | lineStyle: { width: 2, color: colors[index % colors.length] }, |
| | | itemStyle: { color: colors[index % colors.length] }, |
| | | }) |
| | | }) |
| | | |
| | | return series |
| | | }) |
| | | |
| | | // è·åçäº§å·¥åæ°éç»è®¡æ°æ® |
| | | const fetchProductWorkOrderCountStatistic = () => { |
| | | findProductWorkOrderCountStatistics().then((res) => { |
| | | const data = res.data |
| | | kpi.value.workOrderTotal = data.totalCount |
| | | kpi.value.workOrderDoing = data.inProgressCount |
| | | kpi.value.workOrderDone = data.completedCount |
| | | }) |
| | | } |
| | | |
| | | // è·åç产质éç»è®¡æ°æ® |
| | | const fetchProductQualityStatistics = () => { |
| | | findProductQualityStatistics().then((res) => { |
| | | const data = res.data |
| | | kpi.value.passRate = data.qualifiedRate |
| | | kpi.value.ngRate = data.defectRate |
| | | kpi.value.scrapTotal = data.scrapCount |
| | | }) |
| | | } |
| | | |
| | | // è·åç产æ°éç»è®¡æ°æ® |
| | | const fetchProductProductionStatistics = () => { |
| | | findProductProductionStatistics().then((res) => { |
| | | const data = res.data |
| | | kpi.value.productionAmount = data.productionOutput |
| | | kpi.value.productionAmountTrend = data.productionOutputMonthlyChange |
| | | kpi.value.productionCost = data.productionConsumption |
| | | kpi.value.productionCostTrend = data.productionConsumptionMonthlyChange |
| | | kpi.value.supplierCount = data.supplierCount |
| | | kpi.value.supplierCountTrend = data.supplierCountMonthlyChange |
| | | }) |
| | | } |
| | | |
| | | // è·å产å产åºåææ°æ® |
| | | const fetchProductOutputCategoryPieData = () => { |
| | | findProductOutputCategoryPieData().then((res) => { |
| | | categoryPieData.value = res.data |
| | | }) |
| | | } |
| | | // è·åä¸è¯åå åæç»è®¡æ°æ® |
| | | const fetchProductDefectReasonAnalysis = () => { |
| | | findProductDefectReasonAnalysis().then((res) => { |
| | | ngReasonData.value = res.data |
| | | }) |
| | | } |
| | | |
| | | // è·åå·¥åºä¸è¯çåæ |
| | | const fetchProductProcessDefectRateAnalysis = (dateRange) => { |
| | | isLoading.value = true |
| | | const params = dateRange ? { |
| | | startDate: dateRange[0], |
| | | endDate: dateRange[1] |
| | | } : {} |
| | | findProductProcessDefectRateAnalysis(params).then((res) => { |
| | | ngRateData.value = res.data |
| | | }).finally(() => { |
| | | isLoading.value = false |
| | | }) |
| | | } |
| | | |
| | | // å¤çæ¥æéæ©åå |
| | | const handleDateChange = (range) => { |
| | | fetchProductProcessDefectRateAnalysis(range) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | // åå§åæ¶è·åçäº§å·¥åæ°éç»è®¡æ°æ® |
| | | fetchProductWorkOrderCountStatistic() |
| | | // åå§åæ¶è·åç产质éç»è®¡æ°æ® |
| | | fetchProductQualityStatistics() |
| | | // åå§åæ¶è·åç产æ°éç»è®¡æ°æ® |
| | | fetchProductProductionStatistics() |
| | | // åå§åæ¶è·å产å产åºåææ°æ® |
| | | fetchProductOutputCategoryPieData() |
| | | // åå§åæ¶è·åä¸è¯åå åæç»è®¡æ°æ® |
| | | fetchProductDefectReasonAnalysis() |
| | | // åå§åæ¶è·åå·¥åºä¸è¯çåæ |
| | | fetchProductProcessDefectRateAnalysis() |
| | | }) |
| | | |
| | | </script> |
| | | |
| | | <style scoped> |
| | |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .panel-date-picker { |
| | | width: 240px; |
| | | margin-left: 6px; |
| | | } |
| | | |
| | | .panel-date-picker { |
| | | width: 240px; |
| | | margin-left: 6px; |
| | | } |
| | | |
| | | .panel-title { |
| | | font-weight: 800; |
| | | color: #030303; |
| | |
| | | grid-template-columns: 1fr; |
| | | } |
| | | } |
| | | </style> |
| | | </style> |