| | |
| | | <div class="panel-title-row"> |
| | | <div class="panel-title">生产订单进度</div> |
| | | <el-radio-group v-model="orderFilter" size="small"> |
| | | <el-radio-button label="all">全部</el-radio-button> |
| | | <el-radio-button label="in_progress">进行中</el-radio-button> |
| | | <el-radio-button label="completed">已完成</el-radio-button> |
| | | <el-radio-button label="paused">已暂停</el-radio-button> |
| | | <el-radio-button label="all">全部({{ orderProgressMeta.total }})</el-radio-button> |
| | | <el-radio-button label="inProgress">进行中({{ orderProgressMeta.inProgressCount }})</el-radio-button> |
| | | <el-radio-button label="completed">已完成({{ orderProgressMeta.completedCount }})</el-radio-button> |
| | | <el-radio-button label="paused">已暂停({{ orderProgressMeta.pausedCount }})</el-radio-button> |
| | | </el-radio-group> |
| | | </div> |
| | | <el-table :data="filteredOrders" stripe> |
| | |
| | | <el-table-column label="状态" min-width="90"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getOrderStatusType(row.status)" effect="light"> |
| | | {{ getOrderStatusText(row.status) }} |
| | | {{ row.statusLabel || getOrderStatusText(row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | <div v-if="visiblePanels.plan" class="cockpit-panel plan-panel"> |
| | | <div class="panel-title-row"> |
| | | <div class="panel-title">今日生产计划</div> |
| | | <span class="panel-more">{{ todayPlanList.length }}项</span> |
| | | <span class="panel-more">{{ todayPlanTotal }}项</span> |
| | | </div> |
| | | <ul class="plan-list"> |
| | | <li v-for="item in todayPlanList" :key="item.orderNo" class="plan-item"> |
| | |
| | | getBusiness, |
| | | homeTodos, |
| | | processDataProductionStatistics, |
| | | productionOrderProgress, |
| | | productionOverview, |
| | | productionRealtimeBoard, |
| | | qualityInspectionStatistics, |
| | | statisticsReceivablePayable, |
| | | todayProductionPlan, |
| | | } from "@/api/viewIndex.js"; |
| | | import { list } from "@/api/productionManagement/productionProcess"; |
| | | |
| | |
| | | processNum: 0, |
| | | factoryNum: 0, |
| | | }); |
| | | |
| | | const productionOverviewData = ref({ |
| | | totalOutput: 0, |
| | | totalScrap: 0, |
| | | yieldRate: 0, |
| | | }); |
| | | |
| | | const realtimeBoardData = ref({ |
| | | deviceOee: { value: 0, compareYesterday: 0 }, |
| | | orderAchievementRate: { value: 0, compareYesterday: 0 }, |
| | | defectRate: { value: 0, compareYesterday: 0 }, |
| | | }); |
| | | |
| | | const orderProgressMeta = ref({ |
| | | tab: "all", |
| | | total: 0, |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | inProgressCount: 0, |
| | | completedCount: 0, |
| | | pausedCount: 0, |
| | | }); |
| | | |
| | | const todayPlanList = ref([]); |
| | | const todayPlanTotal = ref(0); |
| | | |
| | | const sum = ref(0); |
| | | const yny = ref(0); |
| | |
| | | key: "production", |
| | | title: "生产总览", |
| | | desc: "累计产出(件)", |
| | | value: formatNumber(processTotals.value.output), |
| | | value: formatNumber(productionOverviewData.value.totalOutput), |
| | | subLabel: "累计报废", |
| | | subValue: formatNumber(processTotals.value.scrap), |
| | | trend: `良率 ${ratioText(processTotals.value.output, processTotals.value.input)}`, |
| | | subValue: formatNumber(productionOverviewData.value.totalScrap), |
| | | trend: `良率 ${Number(productionOverviewData.value.yieldRate || 0).toFixed(2)}%`, |
| | | icon: Operation, |
| | | visible: visibleModules.value.production, |
| | | }, |
| | | ].filter((item) => item.visible)); |
| | | |
| | | const productionOrders = ref([ |
| | | { |
| | | orderNo: "MO-20260518-001", |
| | | productName: "智能控制器", |
| | | planQty: 1000, |
| | | completedQty: 860, |
| | | completionRate: 86, |
| | | deliveryDate: "2026-05-20", |
| | | status: "in_progress", |
| | | }, |
| | | { |
| | | orderNo: "MO-20260518-002", |
| | | productName: "电源模块", |
| | | planQty: 800, |
| | | completedQty: 640, |
| | | completionRate: 80, |
| | | deliveryDate: "2026-05-22", |
| | | status: "in_progress", |
| | | }, |
| | | { |
| | | orderNo: "MO-20260518-003", |
| | | productName: "传感器组件", |
| | | planQty: 500, |
| | | completedQty: 150, |
| | | completionRate: 30, |
| | | deliveryDate: "2026-05-25", |
| | | status: "paused", |
| | | }, |
| | | { |
| | | orderNo: "MO-20260518-004", |
| | | productName: "结构件A", |
| | | planQty: 1200, |
| | | completedQty: 1200, |
| | | completionRate: 100, |
| | | deliveryDate: "2026-05-15", |
| | | status: "completed", |
| | | }, |
| | | ]); |
| | | const productionOrders = ref([]); |
| | | |
| | | const orderFilter = ref("all"); |
| | | const filteredOrders = computed(() => { |
| | | if (orderFilter.value === "all") return productionOrders.value; |
| | | return productionOrders.value.filter((item) => item.status === orderFilter.value); |
| | | }); |
| | | const filteredOrders = computed(() => productionOrders.value); |
| | | |
| | | const todayPlanList = computed(() => |
| | | productionOrders.value |
| | | .slice() |
| | | .sort((a, b) => dayjs(a.deliveryDate).valueOf() - dayjs(b.deliveryDate).valueOf()) |
| | | .slice(0, 5) |
| | | ); |
| | | const getCompareTrend = (value) => { |
| | | const num = Number(value || 0); |
| | | if (num > 0) return "up"; |
| | | if (num < 0) return "down"; |
| | | return "flat"; |
| | | }; |
| | | |
| | | const avgCompletionRate = computed(() => { |
| | | if (!productionOrders.value.length) return 0; |
| | | const total = productionOrders.value.reduce((acc, cur) => acc + Number(cur.completionRate || 0), 0); |
| | | return Number((total / productionOrders.value.length).toFixed(1)); |
| | | }); |
| | | const getCompareText = (value) => { |
| | | const num = Number(value || 0); |
| | | const abs = Math.abs(num).toFixed(2); |
| | | if (num > 0) return `较昨日 ↑ ${abs}%`; |
| | | if (num < 0) return `较昨日 ↓ ${abs}%`; |
| | | return "较昨日 持平"; |
| | | }; |
| | | |
| | | const realtimeBoard = computed(() => { |
| | | const oee = ratioNumber(processTotals.value.output, processTotals.value.input); |
| | | const defectRate = ratioNumber(processTotals.value.scrap, processTotals.value.input); |
| | | const oee = Number(realtimeBoardData.value.deviceOee?.value || 0); |
| | | const orderAchievement = Number(realtimeBoardData.value.orderAchievementRate?.value || 0); |
| | | const defectRate = Number(realtimeBoardData.value.defectRate?.value || 0); |
| | | const oeeCompare = Number(realtimeBoardData.value.deviceOee?.compareYesterday || 0); |
| | | const orderCompare = Number(realtimeBoardData.value.orderAchievementRate?.compareYesterday || 0); |
| | | const defectCompare = Number(realtimeBoardData.value.defectRate?.compareYesterday || 0); |
| | | return [ |
| | | { |
| | | key: "oee", |
| | | label: "设备 OEE", |
| | | percent: clampPercent(oee), |
| | | display: `${oee.toFixed(1)}%`, |
| | | delta: "较昨日 ↑ 4.0%", |
| | | trend: "up", |
| | | display: `${oee.toFixed(2)}%`, |
| | | delta: getCompareText(oeeCompare), |
| | | trend: getCompareTrend(oeeCompare), |
| | | color: "#2d8cff", |
| | | }, |
| | | { |
| | | key: "order", |
| | | label: "订单达成率", |
| | | percent: clampPercent(avgCompletionRate.value), |
| | | display: `${avgCompletionRate.value.toFixed(1)}%`, |
| | | delta: "较昨日 ↑ 2.6%", |
| | | trend: "up", |
| | | percent: clampPercent(orderAchievement), |
| | | display: `${orderAchievement.toFixed(2)}%`, |
| | | delta: getCompareText(orderCompare), |
| | | trend: getCompareTrend(orderCompare), |
| | | color: "#31d2ff", |
| | | }, |
| | | { |
| | | key: "defect", |
| | | label: "不良率", |
| | | percent: clampPercent(defectRate), |
| | | display: `${defectRate.toFixed(1)}%`, |
| | | delta: "较昨日 ↓ 0.5%", |
| | | trend: "down", |
| | | display: `${defectRate.toFixed(2)}%`, |
| | | delta: getCompareText(defectCompare), |
| | | trend: getCompareTrend(defectCompare), |
| | | color: "#f6a23f", |
| | | }, |
| | | ]; |
| | |
| | | |
| | | const getOrderStatusText = (status) => { |
| | | const mapping = { |
| | | in_progress: "进行中", |
| | | completed: "已完成", |
| | | paused: "暂停", |
| | | 1: "待开始", |
| | | 2: "进行中", |
| | | 3: "已完成", |
| | | 4: "已暂停", |
| | | }; |
| | | return mapping[status] || "未知"; |
| | | }; |
| | | |
| | | const getOrderStatusType = (status) => { |
| | | const mapping = { |
| | | in_progress: "success", |
| | | completed: "primary", |
| | | paused: "warning", |
| | | 1: "info", |
| | | 2: "success", |
| | | 3: "primary", |
| | | 4: "warning", |
| | | }; |
| | | return mapping[status] || "info"; |
| | | }; |
| | | |
| | | const formatDueDate = (value) => { |
| | | if (!value) return "--"; |
| | | const date = dayjs(value); |
| | | return date.isValid() ? date.format("YYYY-MM-DD") : "--"; |
| | | }; |
| | | |
| | | const mapOrderProgressRecord = (item = {}) => ({ |
| | | orderNo: item.orderNo || "--", |
| | | productName: item.productName || "--", |
| | | planQty: Number(item.plannedQuantity || 0), |
| | | completedQty: Number(item.completedQuantity || 0), |
| | | completionRate: clampPercent(Number(item.completionRate || 0)), |
| | | deliveryDate: formatDueDate(item.dueDate), |
| | | status: Number(item.status || 0), |
| | | statusLabel: item.statusLabel || "", |
| | | }); |
| | | |
| | | const mapTodayPlanRecord = (item = {}) => ({ |
| | | orderNo: item.orderNo || "--", |
| | | productName: item.productName || "--", |
| | | planQty: Number(item.plannedQuantity || 0), |
| | | deliveryDate: formatDueDate(item.dueDate), |
| | | status: Number(item.status || 0), |
| | | statusLabel: item.statusLabel || "", |
| | | }); |
| | | |
| | | const refreshProductionOverview = async () => { |
| | | try { |
| | | const res = await productionOverview(); |
| | | const data = res?.data || {}; |
| | | productionOverviewData.value = { |
| | | totalOutput: Number(data.totalOutput || 0), |
| | | totalScrap: Number(data.totalScrap || 0), |
| | | yieldRate: Number(data.yieldRate || 0), |
| | | }; |
| | | } catch { |
| | | productionOverviewData.value = { |
| | | totalOutput: 0, |
| | | totalScrap: 0, |
| | | yieldRate: 0, |
| | | }; |
| | | } |
| | | }; |
| | | |
| | | const refreshProductionRealtimeBoard = async () => { |
| | | try { |
| | | const res = await productionRealtimeBoard(); |
| | | const data = res?.data || {}; |
| | | realtimeBoardData.value = { |
| | | deviceOee: { |
| | | value: Number(data.deviceOee?.value || 0), |
| | | compareYesterday: Number(data.deviceOee?.compareYesterday || 0), |
| | | }, |
| | | orderAchievementRate: { |
| | | value: Number(data.orderAchievementRate?.value || 0), |
| | | compareYesterday: Number(data.orderAchievementRate?.compareYesterday || 0), |
| | | }, |
| | | defectRate: { |
| | | value: Number(data.defectRate?.value || 0), |
| | | compareYesterday: Number(data.defectRate?.compareYesterday || 0), |
| | | }, |
| | | }; |
| | | } catch { |
| | | realtimeBoardData.value = { |
| | | deviceOee: { value: 0, compareYesterday: 0 }, |
| | | orderAchievementRate: { value: 0, compareYesterday: 0 }, |
| | | defectRate: { value: 0, compareYesterday: 0 }, |
| | | }; |
| | | } |
| | | }; |
| | | |
| | | const refreshProductionOrderProgress = async () => { |
| | | try { |
| | | const res = await productionOrderProgress({ |
| | | tab: orderFilter.value, |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | }); |
| | | const data = res?.data || {}; |
| | | orderProgressMeta.value = { |
| | | tab: data.tab || orderFilter.value, |
| | | total: Number(data.total || 0), |
| | | pageNum: Number(data.pageNum || 1), |
| | | pageSize: Number(data.pageSize || 10), |
| | | inProgressCount: Number(data.inProgressCount || 0), |
| | | completedCount: Number(data.completedCount || 0), |
| | | pausedCount: Number(data.pausedCount || 0), |
| | | }; |
| | | productionOrders.value = (data.records || []).map(mapOrderProgressRecord); |
| | | } catch { |
| | | orderProgressMeta.value = { |
| | | tab: orderFilter.value, |
| | | total: 0, |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | inProgressCount: 0, |
| | | completedCount: 0, |
| | | pausedCount: 0, |
| | | }; |
| | | productionOrders.value = []; |
| | | } |
| | | }; |
| | | |
| | | const refreshTodayProductionPlan = async () => { |
| | | try { |
| | | const res = await todayProductionPlan({ limit: 5 }); |
| | | const data = res?.data || {}; |
| | | todayPlanTotal.value = Number(data.total || 0); |
| | | todayPlanList.value = (data.records || []).map(mapTodayPlanRecord); |
| | | } catch { |
| | | todayPlanTotal.value = 0; |
| | | todayPlanList.value = []; |
| | | } |
| | | }; |
| | | |
| | | const getBusinessData = async () => { |
| | |
| | | router.push(path).catch(() => {}); |
| | | }; |
| | | |
| | | watch(orderFilter, () => { |
| | | if (visiblePanels.value.order) { |
| | | refreshProductionOrderProgress(); |
| | | } |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | updateNowTime(); |
| | | clockTimer = setInterval(updateNowTime, 1000); |
| | | if (dashboardCards.value.length > 0) { |
| | | getBusinessData(); |
| | | } |
| | | if (visibleModules.value.production) { |
| | | refreshProductionOverview(); |
| | | } |
| | | if (visiblePanels.value.contract) { |
| | | analysisCustomer(); |
| | |
| | | if (visiblePanels.value.process) { |
| | | getProcessList(); |
| | | refreshProcessStats(); |
| | | } |
| | | if (visiblePanels.value.order) { |
| | | refreshProductionOrderProgress(); |
| | | } |
| | | if (visiblePanels.value.realtime) { |
| | | refreshProductionRealtimeBoard(); |
| | | } |
| | | if (visiblePanels.value.plan) { |
| | | refreshTodayProductionPlan(); |
| | | } |
| | | }); |
| | | |
| | |
| | | color: #f59e0b; |
| | | } |
| | | |
| | | .realtime-delta.flat { |
| | | color: #64748b; |
| | | } |
| | | |
| | | .warning-list { |
| | | margin-top: 10px; |
| | | display: flex; |