| | |
| | | <div class="main-panel process-panel"> |
| | | <div class="process-panel__header"> |
| | | <div class="section-title">工序数据生产统计明细</div> |
| | | <div style="display: flex; gap: 10px; align-items: center;"> |
| | | <el-button type="primary" size="small" plain icon="Filter" @click="openProcessDialog">选择工序</el-button> |
| | | <el-button type="info" size="small" plain icon="Refresh" @click="resetProcessFilter">重置</el-button> |
| | | <el-radio-group v-model="processRange" size="small" @change="refreshProcessStats"> |
| | | <el-radio-button :value="'week'">按周</el-radio-button> |
| | | <el-radio-button :value="'month'">按月</el-radio-button> |
| | | <el-radio-button :value="'quarter'">按季度</el-radio-button> |
| | | <el-radio-button :value="1">日</el-radio-button> |
| | | <el-radio-button :value="2">周</el-radio-button> |
| | | <el-radio-button :value="3">月</el-radio-button> |
| | | </el-radio-group> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="process-panel__body"> |
| | | <div class="process-panel__chart"> |
| | | <Echarts |
| | | :chartStyle="{ width: '100%', height: '100%' }" |
| | | :grid="processGrid" |
| | | :series="processSeries" |
| | | :tooltip="processTooltip" |
| | | :xAxis="processXAxis" |
| | | :yAxis="processYAxis" |
| | | style="height: 100%" |
| | | /> |
| | | <Echarts :chartStyle="{ width: '100%', height: '100%' }" :grid="processGrid" :series="processSeries" |
| | | :tooltip="processTooltip" :xAxis="processXAxis" :yAxis="processYAxis" style="height: 100%" |
| | | @click="handleChartClick" /> |
| | | </div> |
| | | |
| | | <div class="process-panel__aside"> |
| | |
| | | |
| | | <div class="process-card"> |
| | | <div class="process-card__label">累计总投入</div> |
| | | <div class="process-card__value">{{ formatAmount(processAside.totalInput) }}<span class="unit">万元</span></div> |
| | | <div class="process-card__value">{{ formatAmount(processAside.totalInput) }}<span class="unit">元</span> |
| | | </div> |
| | | </div> |
| | | <div class="process-card"> |
| | | <div class="process-card__label">累计总报废</div> |
| | | <div class="process-card__value">{{ formatAmount(processAside.totalScrap) }}<span class="unit">万元</span></div> |
| | | <div class="process-card__value">{{ formatAmount(processAside.totalScrap) }}<span class="unit">元</span> |
| | | </div> |
| | | </div> |
| | | <div class="process-card"> |
| | | <div class="process-card__label">累计总产出</div> |
| | | <div class="process-card__value">{{ formatAmount(processAside.totalOutput) }}<span class="unit">万元</span></div> |
| | | <div class="process-card__value">{{ formatAmount(processAside.totalOutput) }}<span class="unit">元</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 工序选择弹窗 --> |
| | | <el-dialog v-model="processDialogVisible" title="选择工序" width="500px" append-to-body> |
| | | <div class="process-selection-wrapper"> |
| | | <el-checkbox-group v-model="tempProcessIds"> |
| | | <div class="process-grid"> |
| | | <el-checkbox v-for="item in processOptions" :key="item.id" :label="item.id" border> |
| | | {{ item.name }} |
| | | </el-checkbox> |
| | | </div> |
| | | </el-checkbox-group> |
| | | </div> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="processDialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="handleProcessDialogConfirm">确认</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- 中部横向两栏 --> |
| | | <div class="dashboard-row"> |
| | | <div class="main-panel"> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 180px;margin-top: 20px"> |
| | | <div |
| | | style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 180px;margin-top: 20px"> |
| | | <div> |
| | | <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie" |
| | | :series="materialPieSeries" |
| | | <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie" :series="materialPieSeries" |
| | | :tooltip="pieTooltip"></Echarts> |
| | | </div> |
| | | <ul class="contract-list"> |
| | |
| | | <!-- <el-radio-button label="按季度" :value="3" />--> |
| | | <!-- </el-radio-group>--> |
| | | </div> |
| | | <Echarts ref="chart" |
| | | :color="barColors2" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :series="barSeries" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | style="height: 260px"></Echarts> |
| | | <Echarts ref="chart" :color="barColors2" :chartStyle="chartStyle" :grid="grid" :series="barSeries" |
| | | :tooltip="tooltip" :xAxis="xAxis" :yAxis="yAxis" style="height: 260px"></Echarts> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 底部横向两栏 --> |
| | | <div class="dashboard-row"> |
| | | <div class="main-panel"> |
| | | <div class="section-title">质量统计</div> |
| | | <div style="display: flex;justify-content: space-between;align-items: center;margin-bottom: 10px;"> |
| | | <div class="section-title" style="margin-bottom: 0;">质量统计</div> |
| | | <el-radio-group v-model="qualityRange" size="small" @change="qualityStatisticsInfo"> |
| | | <el-radio-button :value="1">周</el-radio-button> |
| | | <el-radio-button :value="2">月</el-radio-button> |
| | | <el-radio-button :value="3">季度</el-radio-button> |
| | | </el-radio-group> |
| | | </div> |
| | | <div class="quality-cards"> |
| | | <div class="quality-card one">原材料已检测数 <span>{{qualityStatisticsObject.supplierNum}}件</span></div> |
| | | <div class="quality-card two">过程检验数量 <span>{{qualityStatisticsObject.processNum}}件</span></div> |
| | | <div class="quality-card three">出厂已检数量 <span>{{qualityStatisticsObject.factoryNum}}件</span></div> |
| | | </div> |
| | | <Echarts ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="barLegend" |
| | | :series="barSeries1" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | style="height: 260px"></Echarts> |
| | | <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="barLegend" :series="barSeries1" |
| | | :tooltip="tooltip" :xAxis="xAxis1" :yAxis="yAxis1" style="height: 260px"></Echarts> |
| | | </div> |
| | | |
| | | <div class="main-panel"> |
| | | <div class="section-title">回款与开票分析</div> |
| | | <Echarts |
| | | ref="invoiceChart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="lineLegend" |
| | | :series="lineSeries" |
| | | :tooltip="tooltipLine" |
| | | :xAxis="xAxis2" |
| | | :yAxis="yAxis2" |
| | | style="height: 270px;" |
| | | /> |
| | | <Echarts ref="invoiceChart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries" |
| | | :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" style="height: 270px;" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | analysisCustomerContractAmounts, getAmountHalfYear, |
| | | getBusiness, |
| | | homeTodos, |
| | | qualityStatistics, |
| | | statisticsReceivablePayable |
| | | processDataProductionStatistics, |
| | | statisticsReceivablePayable, |
| | | qualityInspectionStatistics |
| | | } from "@/api/viewIndex.js"; |
| | | import { list } from '@/api/productionManagement/productionProcess'; |
| | | |
| | | |
| | | const userStore = useUserStore() |
| | | |
| | | const processOptions = ref([]) |
| | | const selectedProcessIds = ref([]) |
| | | const tempProcessIds = ref([]) |
| | | const processDialogVisible = ref(false) |
| | | const activeProcessIndex = ref(0) |
| | | |
| | | const businessInfo = ref({ |
| | | inventoryNum: 0, |
| | |
| | | // 待办事项 |
| | | const todoList = ref([]) |
| | | const radio1 = ref(1) |
| | | const qualityRange = ref(1) |
| | | |
| | | // 图表引用 |
| | | const barChart = ref(null) |
| | |
| | | statisticsReceivable() |
| | | qualityStatisticsInfo() |
| | | getAmountHalfYearNum() |
| | | getProcessList() |
| | | }) |
| | | // 数据统计 |
| | | const getBusinessData = () => { |
| | |
| | | todoList.value = res.data |
| | | }) |
| | | } |
| | | // 获取工序列表 |
| | | const getProcessList = () => { |
| | | list().then(res => { |
| | | processOptions.value = res.data |
| | | }) |
| | | } |
| | | |
| | | const openProcessDialog = () => { |
| | | tempProcessIds.value = [...selectedProcessIds.value] |
| | | processDialogVisible.value = true |
| | | } |
| | | |
| | | const handleProcessDialogConfirm = () => { |
| | | selectedProcessIds.value = [...tempProcessIds.value] |
| | | processDialogVisible.value = false |
| | | refreshProcessStats() |
| | | } |
| | | |
| | | const resetProcessFilter = () => { |
| | | selectedProcessIds.value = [] |
| | | tempProcessIds.value = [] |
| | | refreshProcessStats() |
| | | } |
| | | |
| | | const handleChartClick = (params) => { |
| | | if (params && params.dataIndex !== undefined) { |
| | | activeProcessIndex.value = params.dataIndex |
| | | } |
| | | } |
| | | // 应付应收统计 |
| | | const statisticsReceivable = () => { |
| | | statisticsReceivablePayable({type: radio1.value}).then((res) => { |
| | |
| | | } |
| | | // 质检统计 |
| | | const qualityStatisticsInfo = () => { |
| | | qualityStatistics().then((res) => { |
| | | qualityInspectionStatistics({ type: qualityRange.value }).then((res) => { |
| | | xAxis1.value[0].data = [] |
| | | barSeries1.value[0].data = [] |
| | | barSeries1.value[1].data = [] |
| | | barSeries1.value[2].data = [] |
| | | res.data.item.forEach(item => { |
| | | xAxis1.value[0].data.push(item.date) |
| | | barSeries1.value[0].data.push(item.supplierNum) |
| | |
| | | } |
| | | |
| | | // 工序数据生产统计明细(假数据 + 图表) |
| | | const processRange = ref('week') |
| | | const processRange = ref(1) |
| | | const processChartData = ref([]) |
| | | |
| | | const processXAxis = ref([ |
| | |
| | | |
| | | const processAside = computed(() => { |
| | | const list = processChartData.value |
| | | const totalInput = list.reduce((s, i) => s + Number(i.input || 0), 0) |
| | | const totalScrap = list.reduce((s, i) => s + Number(i.scrap || 0), 0) |
| | | const totalOutput = list.reduce((s, i) => s + Number(i.output || 0), 0) |
| | | const item = list[activeProcessIndex.value] || {} |
| | | return { |
| | | processName: '工序名称', |
| | | totalInput, |
| | | totalScrap, |
| | | totalOutput, |
| | | processName: item.name || '暂无数据', |
| | | totalInput: item.input || 0, |
| | | totalScrap: item.scrap || 0, |
| | | totalOutput: item.output || 0, |
| | | } |
| | | }) |
| | | |
| | |
| | | return num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) |
| | | } |
| | | |
| | | const buildProcessFake = (range) => { |
| | | // 让不同切换有点差异 |
| | | const factor = range === 'week' ? 1 : range === 'month' ? 1.4 : 1.9 |
| | | const baseNames = ['工序1', '工序2', '工序3', '工序4', '工序5', '工序6', '工序7'] |
| | | return baseNames.map((name, idx) => { |
| | | const seed = (idx + 1) * 120 * factor |
| | | const input = seed + (idx % 2 === 0 ? 80 : 40) |
| | | const scrap = Math.max(20, seed * 0.15) |
| | | const output = Math.max(30, seed * 0.35) |
| | | return { |
| | | name, |
| | | input: Number(input.toFixed(2)), |
| | | scrap: Number(scrap.toFixed(2)), |
| | | output: Number(output.toFixed(2)), |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const refreshProcessStats = () => { |
| | | processChartData.value = buildProcessFake(processRange.value) |
| | | processDataProductionStatistics({ |
| | | type: processRange.value, |
| | | processIds: selectedProcessIds.value.length > 0 ? selectedProcessIds.value.join(',') : null |
| | | }).then(res => { |
| | | processChartData.value = res.data.map(item => ({ |
| | | name: item.processName, |
| | | input: item.totalInput, |
| | | scrap: item.totalScrap, |
| | | output: item.totalOutput |
| | | })) |
| | | processYAxis.value[0].data = processChartData.value.map((i) => i.name) |
| | | activeProcessIndex.value = 0 |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | |
| | | padding: 20px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .dashboard-top { |
| | | display: flex; |
| | | gap: 20px; |
| | |
| | | align-items: flex-start; |
| | | justify-content: space-evenly; |
| | | } |
| | | |
| | | .company-info { |
| | | padding: 0; |
| | | overflow: hidden; |
| | |
| | | object-fit: cover; |
| | | flex: 0 0 auto; |
| | | } |
| | | |
| | | .data-cards { |
| | | width: 50%; |
| | | display: flex; |
| | |
| | | border-radius: 12px; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .data-title { |
| | | font-weight: 700; |
| | | font-size: 26px; |
| | | color: #FFFFFF; |
| | | } |
| | | |
| | | .data-num { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .data-card { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | |
| | | width: 32%; |
| | | height: 140px; |
| | | } |
| | | |
| | | .data-card.sales { |
| | | background-image: url("../assets/images/xioashoushuju.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .data-card.purchase { |
| | | background-image: url("../assets/images/caigou.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .data-card.inventory { |
| | | background-image: url("../assets/images/kucun.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .data-desc { |
| | | font-weight: 500; |
| | | font-size: 13px; |
| | | color: #FFFFFF; |
| | | } |
| | | |
| | | .data-value { |
| | | font-size: 18px; |
| | | font-weight: 500; |
| | | margin: 10px 0; |
| | | color: #FFFFFF; |
| | | } |
| | | |
| | | .top-left { |
| | | display: flex; |
| | | flex-direction: column; |
| | |
| | | height: 180px; |
| | | width: 20%; |
| | | } |
| | | |
| | | .todo-panel { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | |
| | | height: 180px; |
| | | width: 30%; |
| | | } |
| | | |
| | | .todo-list { |
| | | height: 100px; |
| | | list-style: none; |
| | |
| | | font-size: 15px; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .todo-list li { |
| | | border-radius: 8px; |
| | | margin-bottom: 12px; |
| | |
| | | align-items: center; |
| | | background: rgba(225,227,250,0.62); |
| | | } |
| | | |
| | | .todo-title { |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #000000; |
| | | position: relative; |
| | | } |
| | | |
| | | .todo-title::before { |
| | | content: ''; /* 必需,表示这里有一个内容 */ |
| | | content: ''; |
| | | /* 必需,表示这里有一个内容 */ |
| | | position: absolute; |
| | | left: -10px; /* 定位到左侧 */ |
| | | top: 50%; /* 垂直居中 */ |
| | | transform: translateY(-50%); /* 微调垂直居中 */ |
| | | width: 6px; /* 圆的直径 */ |
| | | height: 6px; /* 圆的直径 */ |
| | | left: -10px; |
| | | /* 定位到左侧 */ |
| | | top: 50%; |
| | | /* 垂直居中 */ |
| | | transform: translateY(-50%); |
| | | /* 微调垂直居中 */ |
| | | width: 6px; |
| | | /* 圆的直径 */ |
| | | height: 6px; |
| | | /* 圆的直径 */ |
| | | background: #498CEB; |
| | | border-radius: 50%; /* 让其变成圆形 */ |
| | | border-radius: 50%; |
| | | /* 让其变成圆形 */ |
| | | } |
| | | |
| | | .todo-division { |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #000000; |
| | | } |
| | | |
| | | .todo-time { |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #000000; |
| | | } |
| | | |
| | | .todo-meta { |
| | | color: #888; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .dashboard-row { |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .main-panel { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .section-title { |
| | | position: relative; |
| | | font-size: 18px; |
| | |
| | | background-color: #002FA7; |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | .contract-info { |
| | | display: flex; |
| | | align-items: center; |
| | |
| | | border-radius: 10px; |
| | | padding: 10px 30px; |
| | | } |
| | | |
| | | .contract-summary { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 30px; |
| | | } |
| | | |
| | | .contract-card { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .contract-name { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #050505; |
| | | } |
| | | |
| | | .contract-meta { |
| | | display: flex; |
| | | align-items: center; |
| | | width: 100%; |
| | | gap: 80px; |
| | | } |
| | | |
| | | .main-amount { |
| | | font-size: 24px; |
| | | color: rgba(51,50,50,0.85); |
| | | } |
| | | .up { color: #e57373; } |
| | | |
| | | .up { |
| | | color: #e57373; |
| | | } |
| | | |
| | | .contract-list { |
| | | margin-top: 16px; |
| | | font-size: 14px; |
| | |
| | | overflow-y: auto; |
| | | width: 460px; |
| | | } |
| | | |
| | | .line { |
| | | position: relative; |
| | | width: 230px; |
| | | } |
| | | |
| | | .line::after { |
| | | content: ''; |
| | | position: absolute; |
| | |
| | | background-color: #C9C5C5; |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | .contract-list li { |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .quality-cards { |
| | | display: flex; |
| | | gap: 12px; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .quality-card { |
| | | border-radius: 8px; |
| | | padding: 15px 10px 10px 50px; |
| | |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .quality-card.one { |
| | | background-image: url("../assets/images/yuancailiao.png"); |
| | | } |
| | | |
| | | .quality-card.two { |
| | | background-image: url("../assets/images/guocheng.png"); |
| | | } |
| | | |
| | | .quality-card.three { |
| | | background-image: url("../assets/images/chuchang.png"); |
| | | |
| | | } |
| | | |
| | | .quality-card span { |
| | | color: #4fc3f7; |
| | | font-weight: bold; |
| | | margin-left: 6px; |
| | | } |
| | | |
| | | .chart { |
| | | width: 100%; |
| | | height: 220px; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .process-panel { |
| | | padding-bottom: 10px; |
| | | } |
| | |
| | | .process-panel__body { |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .process-panel__aside { |
| | | width: 100%; |
| | | flex-direction: row; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .process-card { |
| | | flex: 1; |
| | | min-width: 220px; |
| | | } |
| | | } |
| | | |
| | | .process-selection-wrapper { |
| | | max-height: 400px; |
| | | overflow-y: auto; |
| | | padding: 10px; |
| | | } |
| | | |
| | | .process-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); |
| | | gap: 12px; |
| | | } |
| | | |
| | | :deep(.el-checkbox.is-bordered) { |
| | | margin-left: 0 !important; |
| | | width: 100%; |
| | | } |
| | | </style> |