feat: update production kanban details
This commit is contained in:
223
生产任务看板/doc/MES_Kanban_MonthlyRates.sql
Normal file
223
生产任务看板/doc/MES_Kanban_MonthlyRates.sql
Normal file
@@ -0,0 +1,223 @@
|
||||
USE [YL_MESDB]
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[MES_Kanban_MonthlyRates]
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @today date = CONVERT(date, GETDATE());
|
||||
DECLARE @currentStart date = DATEFROMPARTS(YEAR(@today), MONTH(@today), 1);
|
||||
DECLARE @currentEnd date = DATEADD(month, 1, @currentStart);
|
||||
DECLARE @previousStart date = DATEADD(month, -1, @currentStart);
|
||||
DECLARE @previousEnd date = @currentStart;
|
||||
|
||||
DECLARE @rates TABLE (
|
||||
[module] varchar(20) NOT NULL,
|
||||
[currentNumerator] decimal(18, 2) NULL,
|
||||
[currentDenominator] decimal(18, 2) NULL,
|
||||
[previousNumerator] decimal(18, 2) NULL,
|
||||
[previousDenominator] decimal(18, 2) NULL
|
||||
);
|
||||
|
||||
-- Sales and purchase source views contain the exception set and total set.
|
||||
INSERT INTO @rates
|
||||
SELECT
|
||||
'sales',
|
||||
CASE WHEN currentTotal.total > currentException.total THEN currentTotal.total - currentException.total ELSE 0 END,
|
||||
currentTotal.total,
|
||||
CASE WHEN previousTotal.total > previousException.total THEN previousTotal.total - previousException.total ELSE 0 END,
|
||||
previousTotal.total
|
||||
FROM (
|
||||
SELECT COUNT_BIG(1) AS total
|
||||
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_SaleOTRate_m]
|
||||
WHERE [DocDate] >= @currentStart AND [DocDate] < @currentEnd
|
||||
) currentTotal
|
||||
CROSS JOIN (
|
||||
SELECT COUNT_BIG(1) AS total
|
||||
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_SaleOTRate_z]
|
||||
WHERE [DocDate] >= @currentStart AND [DocDate] < @currentEnd
|
||||
) currentException
|
||||
CROSS JOIN (
|
||||
SELECT COUNT_BIG(1) AS total
|
||||
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_SaleOTRate_m]
|
||||
WHERE [DocDate] >= @previousStart AND [DocDate] < @previousEnd
|
||||
) previousTotal
|
||||
CROSS JOIN (
|
||||
SELECT COUNT_BIG(1) AS total
|
||||
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_SaleOTRate_z]
|
||||
WHERE [DocDate] >= @previousStart AND [DocDate] < @previousEnd
|
||||
) previousException;
|
||||
|
||||
INSERT INTO @rates
|
||||
SELECT
|
||||
'research',
|
||||
SUM(CASE WHEN [预计结束时间] >= @currentStart AND [预计结束时间] < @currentEnd AND [单据状态] = N'已完成' THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN [预计结束时间] >= @currentStart AND [预计结束时间] < @currentEnd THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN [预计结束时间] >= @previousStart AND [预计结束时间] < @previousEnd AND [单据状态] = N'已完成' THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN [预计结束时间] >= @previousStart AND [预计结束时间] < @previousEnd THEN 1 ELSE 0 END)
|
||||
FROM [dbo].[MES_设计报工_任务]
|
||||
WHERE [预计结束时间] >= @previousStart AND [预计结束时间] < @currentEnd;
|
||||
|
||||
INSERT INTO @rates
|
||||
SELECT
|
||||
'plan',
|
||||
SUM(CASE WHEN [计划开始时间] >= @currentStart AND [计划开始时间] < @currentEnd AND ISNULL([齐套], 0) = 1 THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN [计划开始时间] >= @currentStart AND [计划开始时间] < @currentEnd THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN [计划开始时间] >= @previousStart AND [计划开始时间] < @previousEnd AND ISNULL([齐套], 0) = 1 THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN [计划开始时间] >= @previousStart AND [计划开始时间] < @previousEnd THEN 1 ELSE 0 END)
|
||||
FROM [dbo].[View_生产订单_MES]
|
||||
WHERE [计划开始时间] >= @previousStart AND [计划开始时间] < @currentEnd
|
||||
AND [自制件属性] LIKE N'%装配%'
|
||||
AND [物料编码] NOT LIKE N'%3420-HT2%'
|
||||
AND [物料编码] NOT LIKE N'%3420-YT2%'
|
||||
AND [物料编码] NOT LIKE N'%3420-YF2%'
|
||||
AND [物料编码] NOT LIKE N'%3420-YP2%';
|
||||
|
||||
INSERT INTO @rates
|
||||
SELECT
|
||||
'purchase',
|
||||
CASE WHEN currentTotal.total > currentException.total THEN currentTotal.total - currentException.total ELSE 0 END,
|
||||
currentTotal.total,
|
||||
CASE WHEN previousTotal.total > previousException.total THEN previousTotal.total - previousException.total ELSE 0 END,
|
||||
previousTotal.total
|
||||
FROM (
|
||||
SELECT COUNT_BIG(1) AS total
|
||||
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_POOnTimeRate_M]
|
||||
WHERE [DocDate] >= @currentStart AND [DocDate] < @currentEnd
|
||||
) currentTotal
|
||||
CROSS JOIN (
|
||||
SELECT COUNT_BIG(1) AS total
|
||||
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_POOnTimeRate]
|
||||
WHERE [DocDate] >= @currentStart AND [DocDate] < @currentEnd
|
||||
) currentException
|
||||
CROSS JOIN (
|
||||
SELECT COUNT_BIG(1) AS total
|
||||
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_POOnTimeRate_M]
|
||||
WHERE [DocDate] >= @previousStart AND [DocDate] < @previousEnd
|
||||
) previousTotal
|
||||
CROSS JOIN (
|
||||
SELECT COUNT_BIG(1) AS total
|
||||
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_POOnTimeRate]
|
||||
WHERE [DocDate] >= @previousStart AND [DocDate] < @previousEnd
|
||||
) previousException;
|
||||
|
||||
INSERT INTO @rates
|
||||
SELECT
|
||||
'machining',
|
||||
SUM(CASE WHEN [计划开始时间] >= @currentStart AND [计划开始时间] < @currentEnd
|
||||
AND [开工时间] IS NOT NULL AND [开工时间] <= DATEADD(day, 1, [计划开始时间]) THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN [计划开始时间] >= @currentStart AND [计划开始时间] < @currentEnd THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN [计划开始时间] >= @previousStart AND [计划开始时间] < @previousEnd
|
||||
AND [开工时间] IS NOT NULL AND [开工时间] <= DATEADD(day, 1, [计划开始时间]) THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN [计划开始时间] >= @previousStart AND [计划开始时间] < @previousEnd THEN 1 ELSE 0 END)
|
||||
FROM [dbo].[View_生产订单_MES]
|
||||
WHERE [计划开始时间] >= @previousStart AND [计划开始时间] < @currentEnd
|
||||
AND [指派对象] <> N'走手续'
|
||||
AND [指派对象] NOT LIKE N'%装配%'
|
||||
AND [指派对象] NOT IN (N'期初物料', N'打磨', N'钻床', N'电火花', N'压力测试', N'酸洗/喷漆', N'抛光', N'外协')
|
||||
AND [排产状态] = 1
|
||||
AND [订单编号] > 5000;
|
||||
|
||||
;WITH assemblyBase AS (
|
||||
SELECT
|
||||
ISNULL([预计送检日期], CASE
|
||||
WHEN [物料描述] LIKE N'%系统%' THEN DATEADD(day, 3, [钣金预达时间])
|
||||
ELSE DATEADD(day, 3, [齐套时间])
|
||||
END) AS expectedInspection,
|
||||
CASE
|
||||
WHEN ISNULL([任务状态], 0) >= 4
|
||||
OR (ISNULL([指派数量], 0) > 0 AND ISNULL([完成数量], 0) >= ISNULL([指派数量], 0))
|
||||
THEN [修改时间]
|
||||
ELSE NULL
|
||||
END AS completionTime,
|
||||
CASE
|
||||
WHEN [物料描述] LIKE N'%系统%' AND [派工时间] IS NOT NULL THEN 1
|
||||
WHEN [物料描述] NOT LIKE N'%系统%' AND ISNULL([发料状态], 0) = 1 THEN 1
|
||||
ELSE 0
|
||||
END AS executable
|
||||
FROM [dbo].[View_生产订单_MES]
|
||||
WHERE [物料类型] = N'pit_Resource'
|
||||
AND [自制件属性] LIKE N'%装配%'
|
||||
AND [物料描述] NOT LIKE N'%软管总成%'
|
||||
AND [物料描述] NOT LIKE N'%发货部件%'
|
||||
AND ISNULL([不装配], N'') <> N'否'
|
||||
)
|
||||
INSERT INTO @rates
|
||||
SELECT
|
||||
'assembly',
|
||||
SUM(CASE WHEN expectedInspection >= @currentStart AND expectedInspection < @currentEnd AND executable = 1
|
||||
AND NOT (completionTime > expectedInspection OR (completionTime IS NULL AND expectedInspection < @today)) THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN expectedInspection >= @currentStart AND expectedInspection < @currentEnd AND executable = 1 THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN expectedInspection >= @previousStart AND expectedInspection < @previousEnd AND executable = 1
|
||||
AND NOT (completionTime > expectedInspection OR completionTime IS NULL) THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN expectedInspection >= @previousStart AND expectedInspection < @previousEnd AND executable = 1 THEN 1 ELSE 0 END)
|
||||
FROM assemblyBase
|
||||
WHERE expectedInspection >= @previousStart AND expectedInspection < @currentEnd;
|
||||
|
||||
;WITH qualitySummary AS (
|
||||
SELECT
|
||||
[TaskAID],
|
||||
SUM(ISNULL([检验数量], 0)) AS inspectedQuantity,
|
||||
MAX([检测时间]) AS lastInspectionTime
|
||||
FROM [dbo].[YL_质量检验_质检记录]
|
||||
WHERE [检测类型编号] = 6 AND [TaskAID] IS NOT NULL
|
||||
GROUP BY [TaskAID]
|
||||
), operationSummary AS (
|
||||
SELECT
|
||||
[TaskAID],
|
||||
SUM(ISNULL([操作数值], 0)) AS completedQuantity,
|
||||
MAX(CASE WHEN ISNULL([操作数值], 0) <> 0 THEN COALESCE([报工日期], [结束时间]) END) AS lastReportTime
|
||||
FROM [dbo].[YL_加工中心_操作记录表]
|
||||
WHERE ISNULL([操作类别编号], 0) IN (3, 6) AND [TaskAID] IS NOT NULL
|
||||
GROUP BY [TaskAID]
|
||||
), qualityBase AS (
|
||||
SELECT
|
||||
o.lastReportTime,
|
||||
DATEADD(day, 2, o.lastReportTime) AS inspectionDeadline,
|
||||
o.completedQuantity,
|
||||
ISNULL(q.inspectedQuantity, 0) AS inspectedQuantity,
|
||||
q.lastInspectionTime
|
||||
FROM operationSummary o
|
||||
LEFT JOIN qualitySummary q ON q.[TaskAID] = o.[TaskAID]
|
||||
WHERE o.lastReportTime >= @previousStart AND o.lastReportTime < @currentEnd
|
||||
AND o.completedQuantity > 0
|
||||
)
|
||||
INSERT INTO @rates
|
||||
SELECT
|
||||
'quality',
|
||||
SUM(CASE WHEN lastReportTime >= @currentStart AND lastReportTime < @currentEnd
|
||||
AND ((inspectedQuantity >= completedQuantity AND lastInspectionTime <= inspectionDeadline)
|
||||
OR (inspectedQuantity < completedQuantity AND inspectionDeadline >= @today)) THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN lastReportTime >= @currentStart AND lastReportTime < @currentEnd THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN lastReportTime >= @previousStart AND lastReportTime < @previousEnd
|
||||
AND inspectedQuantity >= completedQuantity AND lastInspectionTime <= inspectionDeadline THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN lastReportTime >= @previousStart AND lastReportTime < @previousEnd THEN 1 ELSE 0 END)
|
||||
FROM qualityBase;
|
||||
|
||||
-- Temporary placeholder until the warehouse timely-rate formula is confirmed.
|
||||
INSERT INTO @rates
|
||||
VALUES ('warehouse', 95, 100, 95, 100);
|
||||
|
||||
SELECT
|
||||
[module],
|
||||
CAST(CASE WHEN ISNULL([currentDenominator], 0) = 0 THEN 0 ELSE ISNULL([currentNumerator], 0) * 100.0 / [currentDenominator] END AS decimal(18, 2)) AS [currentRate],
|
||||
CAST(CASE WHEN ISNULL([previousDenominator], 0) = 0 THEN 0 ELSE ISNULL([previousNumerator], 0) * 100.0 / [previousDenominator] END AS decimal(18, 2)) AS [previousRate],
|
||||
ISNULL([currentNumerator], 0) AS [currentNumerator],
|
||||
ISNULL([currentDenominator], 0) AS [currentDenominator],
|
||||
ISNULL([previousNumerator], 0) AS [previousNumerator],
|
||||
ISNULL([previousDenominator], 0) AS [previousDenominator]
|
||||
FROM @rates
|
||||
ORDER BY CASE [module]
|
||||
WHEN 'sales' THEN 1
|
||||
WHEN 'research' THEN 2
|
||||
WHEN 'plan' THEN 3
|
||||
WHEN 'purchase' THEN 4
|
||||
WHEN 'machining' THEN 5
|
||||
WHEN 'assembly' THEN 6
|
||||
WHEN 'quality' THEN 7
|
||||
WHEN 'warehouse' THEN 8
|
||||
ELSE 99
|
||||
END;
|
||||
END
|
||||
GO
|
||||
97
生产任务看板/doc/MES_Kanban_ProductionInfo.sql
Normal file
97
生产任务看板/doc/MES_Kanban_ProductionInfo.sql
Normal file
@@ -0,0 +1,97 @@
|
||||
CREATE OR ALTER PROCEDURE [dbo].[MES_Kanban_ProductionInfo]
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @today date = CONVERT(date, GETDATE());
|
||||
DECLARE @tomorrow date = DATEADD(day, 1, @today);
|
||||
DECLARE @monthStart date = DATEFROMPARTS(YEAR(@today), MONTH(@today), 1);
|
||||
|
||||
DECLARE @currentPlan decimal(18, 2) = 0;
|
||||
DECLARE @currentOutput decimal(18, 2) = 0;
|
||||
DECLARE @completionRate decimal(18, 2) = 0;
|
||||
DECLARE @workHours decimal(18, 1) = 0;
|
||||
DECLARE @kitRate decimal(18, 2) = 0;
|
||||
DECLARE @qualityRate decimal(18, 2) = 0;
|
||||
|
||||
;WITH AssemblyOrders AS
|
||||
(
|
||||
SELECT
|
||||
CONVERT(nvarchar(100), [订单编号]) AS [订单编号],
|
||||
MAX(ISNULL(CONVERT(decimal(18, 4), [计划数量订单]), 0)) AS [计划数量],
|
||||
MAX(ISNULL(CONVERT(decimal(18, 4), [完成数量]), 0)) AS [完成数量]
|
||||
FROM [dbo].[View_生产订单_MES]
|
||||
WHERE [计划完成] >= @monthStart
|
||||
AND [计划完成] < @tomorrow
|
||||
AND [自制件属性] LIKE N'%装配%'
|
||||
GROUP BY CONVERT(nvarchar(100), [订单编号])
|
||||
)
|
||||
SELECT
|
||||
@currentPlan = ISNULL(SUM([计划数量]), 0),
|
||||
@currentOutput = ISNULL(SUM([完成数量]), 0)
|
||||
FROM AssemblyOrders;
|
||||
|
||||
SET @completionRate = CAST(
|
||||
CASE WHEN @currentPlan > 0
|
||||
THEN @currentOutput * 100.0 / @currentPlan
|
||||
ELSE 0
|
||||
END AS decimal(18, 2)
|
||||
);
|
||||
|
||||
SELECT @workHours = CAST(ISNULL(SUM(ISNULL([加工工时], 0)), 0) / 3600.0 AS decimal(18, 1))
|
||||
FROM [dbo].[View_生产工时视图全部]
|
||||
WHERE [结束时间] >= @monthStart
|
||||
AND [结束时间] < @tomorrow
|
||||
AND ISNULL([阶段标识], 0) = -1
|
||||
AND ISNULL([加工工时], 0) > 0;
|
||||
|
||||
SELECT @kitRate = CAST(
|
||||
CASE WHEN COUNT_BIG(1) > 0
|
||||
THEN SUM(CASE WHEN ISNULL([齐套], 0) = 1 THEN 1 ELSE 0 END) * 100.0 / COUNT_BIG(1)
|
||||
ELSE 0
|
||||
END AS decimal(18, 2)
|
||||
)
|
||||
FROM [dbo].[View_生产订单_MES]
|
||||
WHERE [计划开始时间] >= @monthStart
|
||||
AND [计划开始时间] < @tomorrow
|
||||
AND [自制件属性] LIKE N'%装配%'
|
||||
AND [物料编码] NOT LIKE N'%3420-HT2%'
|
||||
AND [物料编码] NOT LIKE N'%3420-YT2%'
|
||||
AND [物料编码] NOT LIKE N'%3420-YF2%'
|
||||
AND [物料编码] NOT LIKE N'%3420-YP2%';
|
||||
|
||||
SELECT @qualityRate = CAST(
|
||||
CASE WHEN ISNULL(SUM([检验数量]), 0) > 0
|
||||
THEN ISNULL(SUM([合格数]), 0) * 100.0 / SUM([检验数量])
|
||||
ELSE 0
|
||||
END AS decimal(18, 2)
|
||||
)
|
||||
FROM [dbo].[View_质量检验_质检记录]
|
||||
WHERE [工序名称] = N'装配'
|
||||
AND [检测时间] >= @monthStart
|
||||
AND [检测时间] < @tomorrow;
|
||||
|
||||
SELECT [Zone], [Sort], [TagName], [TagValue], [TagUnit], [Icon], [color]
|
||||
FROM
|
||||
(
|
||||
SELECT N'生产' AS [Zone], 1 AS [Sort], N'当前计划' AS [TagName],
|
||||
CONVERT(nvarchar(50), @currentPlan) AS [TagValue], N'台' AS [TagUnit],
|
||||
N'AssemblyPlan' AS [Icon], N'#0678b4' AS [color]
|
||||
UNION ALL
|
||||
SELECT N'生产', 2, N'当前产量', CONVERT(nvarchar(50), @currentOutput), N'台',
|
||||
N'ManufacturingExecution', N'#68b720'
|
||||
UNION ALL
|
||||
SELECT N'生产', 3, N'计划完成', CONVERT(nvarchar(50), @completionRate), N'%',
|
||||
N'ProductionCheck', N'#e97378'
|
||||
UNION ALL
|
||||
SELECT N'生产', 4, N'工时消耗', CONVERT(nvarchar(50), @workHours), N'h',
|
||||
N'ManHourStatistics', N'#9d6dd5'
|
||||
UNION ALL
|
||||
SELECT N'生产', 5, N'齐套率', CONVERT(nvarchar(50), @kitRate), N'%',
|
||||
N'GroupMatching', N'#dc7e86'
|
||||
UNION ALL
|
||||
SELECT N'生产', 6, N'合格率', CONVERT(nvarchar(50), @qualityRate), N'%',
|
||||
N'QualityBasicData', N'#fafc80'
|
||||
) AS ProductionInfo
|
||||
ORDER BY [Sort];
|
||||
END;
|
||||
@@ -1,5 +1,6 @@
|
||||
window.g = {
|
||||
API_WEB_URL: 'https://192.168.2.92:10101',
|
||||
BOARD_IFRAME_URL: 'http://192.168.2.92:10008/?dmtmode=DT',
|
||||
};
|
||||
window.configData = {
|
||||
MachineToolList: [
|
||||
@@ -202,4 +203,4 @@ window.configData = {
|
||||
{J1:0,J2:50.3,J3:60.0,J4:40,J5:90,J6:153.9},
|
||||
{J1:0,J2:50.3,J3:60.0,J4:40,J5:90,J6:153.9,E1:1200}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
205
生产任务看板/src/views/plan-kanban/components/CenterDetailPanel.vue
Normal file
205
生产任务看板/src/views/plan-kanban/components/CenterDetailPanel.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<template>
|
||||
<section class="center-detail-panel panel">
|
||||
<header class="detail-panel-head">
|
||||
<h2>{{ title }}</h2>
|
||||
<span v-if="status === 'ready'" class="detail-count">{{ rows.length }} 条</span>
|
||||
</header>
|
||||
|
||||
<div class="detail-table-head detail-grid-row" :style="gridStyle">
|
||||
<span v-for="column in columns" :key="column.key">{{ column.label }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-table-body">
|
||||
<div
|
||||
v-if="status === 'ready'"
|
||||
class="detail-table-track"
|
||||
:class="{ scrolling: shouldScroll }"
|
||||
:style="scrollStyle"
|
||||
>
|
||||
<div
|
||||
v-for="(row, index) in displayRows"
|
||||
:key="`${row.id}-${index}`"
|
||||
class="detail-grid-row detail-data-row"
|
||||
:class="{ urgent: row.urgent }"
|
||||
:style="gridStyle"
|
||||
:title="row.title"
|
||||
>
|
||||
<span
|
||||
v-for="column in columns"
|
||||
:key="column.key"
|
||||
:class="[`cell-${column.key}`, { 'category-cell': column.key === 'category' }]"
|
||||
>
|
||||
{{ row[column.key] || '--' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="detail-panel-state">
|
||||
<span v-if="status === 'loading'" class="detail-loading-icon"></span>
|
||||
{{ status === 'loading' ? '加载中' : message }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
title: { type: String, required: true },
|
||||
columns: { type: Array, required: true },
|
||||
rows: { type: Array, default: () => [] },
|
||||
status: { type: String, default: 'loading' },
|
||||
message: { type: String, default: '' },
|
||||
visibleRows: { type: Number, default: 9 }
|
||||
})
|
||||
|
||||
const rowHeight = 34
|
||||
const shouldScroll = computed(() => props.rows.length > props.visibleRows)
|
||||
const displayRows = computed(() => shouldScroll.value ? [...props.rows, ...props.rows] : props.rows)
|
||||
const gridStyle = computed(() => ({
|
||||
gridTemplateColumns: props.columns.map(column => column.width || '1fr').join(' ')
|
||||
}))
|
||||
const scrollStyle = computed(() => ({
|
||||
'--scroll-distance': `${props.rows.length * rowHeight}px`,
|
||||
'--scroll-duration': `${Math.max(props.rows.length * 1.8, 20)}s`
|
||||
}))
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.center-detail-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
color: #dcecff;
|
||||
background: rgba(7, 31, 72, 0.72);
|
||||
}
|
||||
|
||||
.detail-panel-head {
|
||||
display: flex;
|
||||
flex: 0 0 36px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
padding: 0 10px;
|
||||
border-bottom: 1px solid rgba(43, 216, 255, 0.28);
|
||||
}
|
||||
|
||||
.detail-panel-head h2 {
|
||||
margin: 0;
|
||||
color: #8cd3ff;
|
||||
font-size: 14px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.detail-count {
|
||||
color: #1ce6ff;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.detail-grid-row {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.detail-table-head {
|
||||
flex: 0 0 30px;
|
||||
color: #bfe8ff;
|
||||
font-size: 9px;
|
||||
font-weight: 800;
|
||||
background: rgba(10, 75, 145, 0.76);
|
||||
}
|
||||
|
||||
.detail-table-head span,
|
||||
.detail-data-row > span {
|
||||
min-width: 0;
|
||||
padding: 0 2px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.detail-table-body {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-table-track.scrolling {
|
||||
animation: detail-table-scroll var(--scroll-duration) linear infinite;
|
||||
}
|
||||
|
||||
.detail-table-track.scrolling:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
.detail-data-row {
|
||||
height: 34px;
|
||||
color: #dcecff;
|
||||
font-size: 9px;
|
||||
border-bottom: 1px solid rgba(43, 216, 255, 0.1);
|
||||
}
|
||||
|
||||
.detail-data-row:nth-child(odd) {
|
||||
background: rgba(4, 25, 59, 0.38);
|
||||
}
|
||||
|
||||
.detail-data-row:nth-child(even) {
|
||||
background: rgba(4, 18, 47, 0.24);
|
||||
}
|
||||
|
||||
.detail-data-row.urgent,
|
||||
.detail-data-row.urgent .category-cell {
|
||||
color: #ff637a;
|
||||
background: rgba(116, 19, 37, 0.22);
|
||||
}
|
||||
|
||||
.category-cell {
|
||||
color: #1ce6ff;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.cell-materialName {
|
||||
display: -webkit-box;
|
||||
overflow-wrap: anywhere;
|
||||
line-height: 12px;
|
||||
white-space: normal;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.detail-panel-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #78a9ce;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.detail-loading-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid rgba(43, 216, 255, 0.25);
|
||||
border-top-color: #2bd8ff;
|
||||
border-radius: 50%;
|
||||
animation: detail-loading 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes detail-table-scroll {
|
||||
from { transform: translateY(0); }
|
||||
to { transform: translateY(calc(-1 * var(--scroll-distance))); }
|
||||
}
|
||||
|
||||
@keyframes detail-loading {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
278
生产任务看板/src/views/plan-kanban/components/DeviceStatusPanel.vue
Normal file
278
生产任务看板/src/views/plan-kanban/components/DeviceStatusPanel.vue
Normal file
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<section class="device-status-panel panel">
|
||||
<header class="device-panel-head">
|
||||
<h2>设备运行状态明细</h2>
|
||||
<div class="device-legend">
|
||||
<span><i class="status-dot status-run"></i>运行</span>
|
||||
<span><i class="status-dot status-off"></i>停机</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="device-table-head device-grid-row">
|
||||
<span>设备</span>
|
||||
<span>状态</span>
|
||||
<span>自动化</span>
|
||||
<span>辅机</span>
|
||||
<span>利用率</span>
|
||||
<span>开动率</span>
|
||||
<span>合格率</span>
|
||||
<span>OEE</span>
|
||||
</div>
|
||||
|
||||
<div class="device-table-body">
|
||||
<div
|
||||
v-if="status === 'ready'"
|
||||
class="device-table-track"
|
||||
:class="{ scrolling: shouldScroll }"
|
||||
:style="scrollStyle"
|
||||
>
|
||||
<div
|
||||
v-for="(row, index) in displayRows"
|
||||
:key="`${row.id}-${index}`"
|
||||
class="device-grid-row device-data-row"
|
||||
:title="getRowTitle(row)"
|
||||
>
|
||||
<span class="device-name" :title="`${row.name} / ${row.stations}`">{{ row.name }}</span>
|
||||
<span class="device-state"><i class="status-dot" :class="`status-${row.statusTone}`"></i>{{ row.status }}</span>
|
||||
<span><i class="status-dot status-run"></i></span>
|
||||
<span><i class="status-dot status-run"></i></span>
|
||||
<strong :class="rateClass(row.utilization)">{{ formatRate(row.utilization) }}</strong>
|
||||
<strong>{{ formatRate(row.availability) }}</strong>
|
||||
<strong :class="rateClass(row.qualityRate)">{{ formatFixedRate(row.qualityRate) }}</strong>
|
||||
<strong :class="rateClass(row.oee)">{{ formatRate(row.oee) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="device-panel-state">
|
||||
<span v-if="status === 'loading'" class="device-loading-icon"></span>
|
||||
{{ status === 'loading' ? '加载中' : message }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
rows: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: 'loading'
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
visibleRows: {
|
||||
type: Number,
|
||||
default: 8
|
||||
},
|
||||
rowHeight: {
|
||||
type: Number,
|
||||
default: 28
|
||||
}
|
||||
})
|
||||
|
||||
const shouldScroll = computed(() => props.rows.length > props.visibleRows)
|
||||
const displayRows = computed(() => shouldScroll.value ? [...props.rows, ...props.rows] : props.rows)
|
||||
const scrollStyle = computed(() => ({
|
||||
'--row-height': `${props.rowHeight}px`,
|
||||
'--scroll-distance': `${props.rows.length * props.rowHeight}px`,
|
||||
'--scroll-duration': `${Math.max(props.rows.length * 1.6, 18)}s`
|
||||
}))
|
||||
|
||||
const formatRate = value => `${Number(value || 0).toFixed(2).replace(/\.00$/, '')}%`
|
||||
const formatFixedRate = value => `${Number(value || 0).toFixed(2)}%`
|
||||
const rateClass = value => ({
|
||||
'rate-good': Number(value || 0) >= 85,
|
||||
'rate-warning': Number(value || 0) >= 60 && Number(value || 0) < 85,
|
||||
'rate-danger': Number(value || 0) < 60
|
||||
})
|
||||
|
||||
const getRowTitle = row => [
|
||||
`工位: ${row.stations || '--'}`,
|
||||
`本月加工工时: ${Number(row.workHours || 0).toFixed(2)}h`,
|
||||
`报工天数: ${row.reportDays}`,
|
||||
`设备标准开机工时: ${Number(row.standardOpenHours || 0).toFixed(2)}h/天`,
|
||||
`质检不良/计划: ${row.defectiveQty}/${row.qualityPlannedQty}`
|
||||
].join(' | ')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.device-status-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
color: #dcecff;
|
||||
background: rgba(7, 31, 72, 0.62);
|
||||
}
|
||||
|
||||
.device-panel-head {
|
||||
display: flex;
|
||||
flex: 0 0 36px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
padding: 0 10px;
|
||||
border-bottom: 1px solid rgba(43, 216, 255, 0.28);
|
||||
}
|
||||
|
||||
.device-panel-head h2 {
|
||||
margin: 0;
|
||||
color: #8cd3ff;
|
||||
font-size: 14px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.device-legend {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
color: #91b6d3;
|
||||
font-size: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.device-legend span,
|
||||
.device-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.device-grid-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1.15fr 0.8fr 0.72fr 0.62fr 0.9fr 0.9fr 0.88fr 0.78fr;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.device-table-head {
|
||||
flex: 0 0 30px;
|
||||
color: #bfe8ff;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
background: rgba(10, 75, 145, 0.86);
|
||||
}
|
||||
|
||||
.device-table-head span,
|
||||
.device-data-row > * {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.device-table-body {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.device-table-track.scrolling {
|
||||
animation: device-table-scroll var(--scroll-duration) linear infinite;
|
||||
}
|
||||
|
||||
.device-table-track.scrolling:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
.device-data-row {
|
||||
height: var(--row-height, 28px);
|
||||
padding: 0 4px;
|
||||
color: #dcecff;
|
||||
font-size: 10px;
|
||||
border-bottom: 1px solid rgba(43, 216, 255, 0.1);
|
||||
}
|
||||
|
||||
.device-data-row:nth-child(odd) {
|
||||
background: rgba(4, 25, 59, 0.48);
|
||||
}
|
||||
|
||||
.device-data-row:nth-child(even) {
|
||||
background: rgba(4, 18, 47, 0.3);
|
||||
}
|
||||
|
||||
.device-name {
|
||||
color: #ffffff;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.device-data-row strong {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
display: inline-block;
|
||||
flex: 0 0 7px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.status-run {
|
||||
background: #2fd47a;
|
||||
box-shadow: 0 0 7px rgba(47, 212, 122, 0.9);
|
||||
}
|
||||
|
||||
.status-off {
|
||||
background: #78899b;
|
||||
box-shadow: 0 0 5px rgba(120, 137, 155, 0.6);
|
||||
}
|
||||
|
||||
.rate-good {
|
||||
color: #2fd47a;
|
||||
}
|
||||
|
||||
.rate-warning {
|
||||
color: #ffb84d;
|
||||
}
|
||||
|
||||
.rate-danger {
|
||||
color: #ff637a;
|
||||
}
|
||||
|
||||
.device-panel-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #78a9ce;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.device-loading-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid rgba(43, 216, 255, 0.25);
|
||||
border-top-color: #2bd8ff;
|
||||
border-radius: 50%;
|
||||
animation: device-loading 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes device-table-scroll {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
}
|
||||
to {
|
||||
transform: translateY(calc(-1 * var(--scroll-distance)));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes device-loading {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
217
生产任务看板/src/views/plan-kanban/components/MachiningWipPanel.vue
Normal file
217
生产任务看板/src/views/plan-kanban/components/MachiningWipPanel.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<template>
|
||||
<section class="machining-wip-panel panel">
|
||||
<header class="wip-panel-head">
|
||||
<h2>机加在制任务</h2>
|
||||
<span>加急任务红色显示</span>
|
||||
</header>
|
||||
|
||||
<div class="wip-table-head wip-grid-row">
|
||||
<span>设备</span>
|
||||
<span>生产订单</span>
|
||||
<span>物料名称</span>
|
||||
<span>物料编码</span>
|
||||
<span>工序</span>
|
||||
<span>预算工时</span>
|
||||
<span>数量</span>
|
||||
<span>进度</span>
|
||||
</div>
|
||||
|
||||
<div class="wip-table-body">
|
||||
<div
|
||||
v-if="status === 'ready'"
|
||||
class="wip-table-track"
|
||||
:class="{ scrolling: shouldScroll }"
|
||||
:style="scrollStyle"
|
||||
>
|
||||
<div
|
||||
v-for="(row, index) in displayRows"
|
||||
:key="`${row.id}-${index}`"
|
||||
class="wip-grid-row wip-data-row"
|
||||
:class="{ urgent: row.urgent }"
|
||||
:title="getRowTitle(row)"
|
||||
>
|
||||
<strong>{{ row.deviceName || '--' }}</strong>
|
||||
<span>{{ row.productionOrder || '--' }}</span>
|
||||
<span class="material-text">{{ row.materialName || '--' }}</span>
|
||||
<span class="material-text">{{ row.materialCode || '--' }}</span>
|
||||
<span>{{ row.processName || '--' }}</span>
|
||||
<span>{{ formatBudget(row.budgetMinutes) }}</span>
|
||||
<span>{{ formatNumber(row.plannedQty) }}</span>
|
||||
<span>{{ formatNumber(row.reportedQty) }}/{{ formatNumber(row.plannedQty) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="wip-panel-state">
|
||||
<span v-if="status === 'loading'" class="wip-loading-icon"></span>
|
||||
{{ status === 'loading' ? '加载中' : message }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
rows: { type: Array, default: () => [] },
|
||||
status: { type: String, default: 'loading' },
|
||||
message: { type: String, default: '' }
|
||||
})
|
||||
|
||||
const rowHeight = 36
|
||||
const shouldScroll = computed(() => props.rows.length > 7)
|
||||
const displayRows = computed(() => shouldScroll.value ? [...props.rows, ...props.rows] : props.rows)
|
||||
const scrollStyle = computed(() => ({
|
||||
'--scroll-distance': `${props.rows.length * rowHeight}px`,
|
||||
'--scroll-duration': `${Math.max(props.rows.length * 1.8, 20)}s`
|
||||
}))
|
||||
|
||||
const formatNumber = value => Number(value || 0).toLocaleString('zh-CN', { maximumFractionDigits: 2 })
|
||||
const formatBudget = value => value === null || value === undefined ? '--' : Number(value).toFixed(2)
|
||||
const getRowTitle = row => [
|
||||
`设备: ${row.deviceName || '--'}`,
|
||||
`生产订单: ${row.productionOrder || '--'}`,
|
||||
`物料: ${row.materialName || '--'} (${row.materialCode || '--'})`,
|
||||
`工序: ${row.processName || '--'}`,
|
||||
`预算工时: ${formatBudget(row.budgetMinutes)}`,
|
||||
`进度: ${formatNumber(row.reportedQty)}/${formatNumber(row.plannedQty)}`
|
||||
].join(' | ')
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.machining-wip-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
color: #dcecff;
|
||||
background: rgba(7, 31, 72, 0.62);
|
||||
}
|
||||
|
||||
.wip-panel-head {
|
||||
display: flex;
|
||||
flex: 0 0 36px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
padding: 0 10px;
|
||||
border-bottom: 1px solid rgba(43, 216, 255, 0.28);
|
||||
}
|
||||
|
||||
.wip-panel-head h2 {
|
||||
margin: 0;
|
||||
color: #8cd3ff;
|
||||
font-size: 14px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wip-panel-head span {
|
||||
color: #ff637a;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.wip-grid-row {
|
||||
display: grid;
|
||||
grid-template-columns: 0.55fr 1.35fr 1.05fr 1.7fr 0.72fr 0.7fr 0.42fr 0.7fr;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wip-table-head {
|
||||
flex: 0 0 30px;
|
||||
color: #bfe8ff;
|
||||
font-size: 9px;
|
||||
font-weight: 800;
|
||||
background: rgba(10, 75, 145, 0.72);
|
||||
}
|
||||
|
||||
.wip-table-head span,
|
||||
.wip-data-row > * {
|
||||
min-width: 0;
|
||||
padding: 0 2px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wip-data-row .material-text {
|
||||
display: -webkit-box;
|
||||
line-height: 12px;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: normal;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.wip-table-body {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wip-table-track.scrolling {
|
||||
animation: wip-table-scroll var(--scroll-duration) linear infinite;
|
||||
}
|
||||
|
||||
.wip-table-track.scrolling:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
.wip-data-row {
|
||||
height: 36px;
|
||||
color: #dcecff;
|
||||
font-size: 9px;
|
||||
border-bottom: 1px solid rgba(43, 216, 255, 0.1);
|
||||
}
|
||||
|
||||
.wip-data-row:nth-child(odd) {
|
||||
background: rgba(4, 25, 59, 0.34);
|
||||
}
|
||||
|
||||
.wip-data-row:nth-child(even) {
|
||||
background: rgba(4, 18, 47, 0.2);
|
||||
}
|
||||
|
||||
.wip-data-row strong {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.wip-data-row.urgent,
|
||||
.wip-data-row.urgent strong {
|
||||
color: #ff5e6c;
|
||||
background: rgba(116, 19, 37, 0.28);
|
||||
}
|
||||
|
||||
.wip-panel-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #78a9ce;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.wip-loading-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid rgba(43, 216, 255, 0.25);
|
||||
border-top-color: #2bd8ff;
|
||||
border-radius: 50%;
|
||||
animation: wip-loading 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes wip-table-scroll {
|
||||
from { transform: translateY(0); }
|
||||
to { transform: translateY(calc(-1 * var(--scroll-distance))); }
|
||||
}
|
||||
|
||||
@keyframes wip-loading {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
219
生产任务看板/src/views/plan-kanban/composables/useCenterDetailData.js
Normal file
219
生产任务看板/src/views/plan-kanban/composables/useCenterDetailData.js
Normal file
@@ -0,0 +1,219 @@
|
||||
import { inject, ref } from 'vue'
|
||||
|
||||
const PROCEDURES = {
|
||||
shipping: '装配中心任务看板_发货状态查询',
|
||||
assemblyWip: '生产管理_装配状态_获取进行中任务'
|
||||
}
|
||||
|
||||
const SHIPPING_COLUMNS = [
|
||||
{ key: 'productionOrder', label: '生产订单', width: '0.85fr' },
|
||||
{ key: 'salesOrder', label: '销售订单', width: '1fr' },
|
||||
{ key: 'materialName', label: '物料名称', width: '1.55fr' },
|
||||
{ key: 'quantity', label: '数量', width: '0.52fr' },
|
||||
{ key: 'requiredDate', label: '要求发货', width: '0.9fr' },
|
||||
{ key: 'shippingStatus', label: '状态', width: '0.65fr' }
|
||||
]
|
||||
|
||||
const ASSEMBLY_WIP_COLUMNS = [
|
||||
{ key: 'category', label: '分类', width: '0.62fr' },
|
||||
{ key: 'operator', label: '操作人', width: '0.65fr' },
|
||||
{ key: 'productionOrder', label: '生产订单', width: '0.78fr' },
|
||||
{ key: 'materialName', label: '物料名称', width: '1.35fr' },
|
||||
{ key: 'station', label: '工位', width: '0.72fr' },
|
||||
{ key: 'progress', label: '完成/分配', width: '0.75fr' },
|
||||
{ key: 'startTime', label: '开始时间', width: '0.9fr' }
|
||||
]
|
||||
|
||||
const getRows = response => {
|
||||
if (Array.isArray(response?.data)) return response.data
|
||||
if (Array.isArray(response?.data?.rows)) return response.data.rows
|
||||
if (Array.isArray(response?.data?.result)) return response.data.result
|
||||
if (Array.isArray(response?.data?.data)) return response.data.data
|
||||
if (Array.isArray(response?.data?.Table)) return response.data.Table
|
||||
return []
|
||||
}
|
||||
|
||||
const hasValue = value => value !== null && value !== undefined && value !== ''
|
||||
const textValue = value => hasValue(value) ? String(value).trim() : ''
|
||||
const pickValue = (row, keys) => {
|
||||
const key = keys.find(item => hasValue(row?.[item]))
|
||||
return key ? row[key] : ''
|
||||
}
|
||||
const toNumber = value => {
|
||||
const numberValue = Number(value)
|
||||
return Number.isFinite(numberValue) ? numberValue : 0
|
||||
}
|
||||
const formatNumber = value => {
|
||||
if (!hasValue(value)) return ''
|
||||
return toNumber(value).toLocaleString('zh-CN', { maximumFractionDigits: 2 })
|
||||
}
|
||||
const formatDate = (value, includeTime = false) => {
|
||||
if (!hasValue(value)) return ''
|
||||
const text = String(value).replace('T', ' ')
|
||||
if (includeTime) return text.slice(5, 16)
|
||||
return text.slice(0, 10)
|
||||
}
|
||||
const isUrgent = row => toNumber(pickValue(row, ['加急总数', '加急数量'])) > 0 ||
|
||||
['1', '是', '加急'].includes(textValue(pickValue(row, ['加急状态', '加急'])))
|
||||
|
||||
const classifyAssemblyTask = row => {
|
||||
const materialName = textValue(pickValue(row, ['物料描述', '零件名称', '物料名称', '产品名称']))
|
||||
const station = textValue(pickValue(row, ['工位名称', '指派对象', '操作人', '二次派工']))
|
||||
|
||||
if (station.includes('电气') || materialName.includes('电气')) return '电气类'
|
||||
if (materialName.includes('阀')) return '部件类'
|
||||
return '机械类'
|
||||
}
|
||||
|
||||
const formatShippingStatus = row => {
|
||||
const value = textValue(pickValue(row, ['发货单状态', '发货状态']))
|
||||
if (value === 'O' || value === '1' || value.includes('待发')) return '待发货'
|
||||
if (value === 'C' || value === '2' || value.includes('已发')) return '已发货'
|
||||
return value
|
||||
}
|
||||
|
||||
const normalizeShippingRows = rows => rows.map((row, index) => {
|
||||
const productionOrder = textValue(pickValue(row, ['生产单号', '订单编号', '订单号']))
|
||||
const salesOrder = textValue(pickValue(row, ['合同号', '项目号', '项目代码']))
|
||||
const materialName = textValue(pickValue(row, ['物料描述', '物料名称', '产品名称']))
|
||||
const materialCode = textValue(pickValue(row, ['物料编号', '物料编码', '产品编码']))
|
||||
const quantity = formatNumber(pickValue(row, ['交货数量', '计划数量']))
|
||||
const requiredDate = formatDate(pickValue(row, ['要求发货时间', '要求发货日期']))
|
||||
const shippingStatus = formatShippingStatus(row)
|
||||
const workHours = formatNumber(row['合同装配工时'])
|
||||
|
||||
return {
|
||||
id: `shipping-${row.TaskAID || productionOrder || index}-${index}`,
|
||||
productionOrder,
|
||||
salesOrder,
|
||||
materialName,
|
||||
materialCode,
|
||||
quantity,
|
||||
requiredDate,
|
||||
shippingStatus,
|
||||
urgent: isUrgent(row),
|
||||
title: [
|
||||
`生产订单: ${productionOrder || '--'}`,
|
||||
`销售订单: ${salesOrder || '--'}`,
|
||||
`物料: ${materialName || '--'} (${materialCode || '--'})`,
|
||||
`交货数量: ${quantity || '--'}`,
|
||||
`要求发货: ${requiredDate || '--'}`,
|
||||
`累计装配工时: ${workHours || '0'}`
|
||||
].join(' | ')
|
||||
}
|
||||
})
|
||||
|
||||
const normalizeAssemblyWipRows = rows => rows.map((row, index) => {
|
||||
const operator = textValue(pickValue(row, ['UserName', '操作人', '负责人']))
|
||||
const productionOrder = textValue(pickValue(row, ['订单号', '订单编号', '生产订单']))
|
||||
const salesOrder = textValue(pickValue(row, ['合同号', '销售订单']))
|
||||
const materialName = textValue(pickValue(row, ['物料描述', '零件名称', '物料名称', '产品名称']))
|
||||
const materialCode = textValue(pickValue(row, ['物料编号', '零件编码', '物料编码', '产品编码']))
|
||||
const station = textValue(pickValue(row, ['工位名称', '指派对象']))
|
||||
const assignedQty = formatNumber(pickValue(row, ['分配数', '指派数量', '计划数量']))
|
||||
const finishedQty = formatNumber(pickValue(row, ['完成数量', '送检数'])) || '0'
|
||||
const startTime = formatDate(pickValue(row, ['StartTime', '开始时间']), true)
|
||||
const category = classifyAssemblyTask(row)
|
||||
|
||||
return {
|
||||
id: `assembly-${row['操作记录ID'] || row.TaskAID || index}-${index}`,
|
||||
category,
|
||||
operator,
|
||||
productionOrder,
|
||||
salesOrder,
|
||||
materialName,
|
||||
materialCode,
|
||||
station,
|
||||
progress: `${finishedQty}/${assignedQty || '0'}`,
|
||||
startTime,
|
||||
urgent: isUrgent(row),
|
||||
title: [
|
||||
`分类: ${category}`,
|
||||
`操作人: ${operator || '--'}`,
|
||||
`生产/销售订单: ${productionOrder || '--'} / ${salesOrder || '--'}`,
|
||||
`物料: ${materialName || '--'} (${materialCode || '--'})`,
|
||||
`工位: ${station || '--'}`,
|
||||
`完成/分配: ${finishedQty}/${assignedQty || '0'}`,
|
||||
`开始时间: ${startTime || '--'}`
|
||||
].join(' | ')
|
||||
}
|
||||
})
|
||||
|
||||
export function useCenterDetailData() {
|
||||
const CreateData = inject('CreateData', null)
|
||||
const ExecDatabase = inject('ExecDatabase', null)
|
||||
const shippingRows = ref([])
|
||||
const shippingStatus = ref('loading')
|
||||
const shippingMessage = ref('')
|
||||
const assemblyWipRows = ref([])
|
||||
const assemblyWipStatus = ref('loading')
|
||||
const assemblyWipMessage = ref('')
|
||||
let activeLoad = null
|
||||
|
||||
const queryProcedure = async name => {
|
||||
if (!CreateData || !ExecDatabase) throw new Error('公共数据请求方法未注入')
|
||||
return getRows(await ExecDatabase(CreateData('11', name)))
|
||||
}
|
||||
|
||||
const applyResult = (result, rowsRef, statusRef, messageRef, normalize, emptyMessage, errorMessage) => {
|
||||
if (result.status === 'rejected') {
|
||||
console.error(errorMessage, result.reason)
|
||||
rowsRef.value = []
|
||||
statusRef.value = 'error'
|
||||
messageRef.value = errorMessage
|
||||
return
|
||||
}
|
||||
rowsRef.value = normalize(result.value)
|
||||
statusRef.value = rowsRef.value.length ? 'ready' : 'empty'
|
||||
messageRef.value = rowsRef.value.length ? '' : emptyMessage
|
||||
}
|
||||
|
||||
const loadCenterDetailData = async () => {
|
||||
if (activeLoad) return activeLoad
|
||||
|
||||
shippingStatus.value = 'loading'
|
||||
assemblyWipStatus.value = 'loading'
|
||||
shippingMessage.value = ''
|
||||
assemblyWipMessage.value = ''
|
||||
|
||||
activeLoad = Promise.allSettled([
|
||||
queryProcedure(PROCEDURES.shipping),
|
||||
queryProcedure(PROCEDURES.assemblyWip)
|
||||
]).then(([shippingResult, assemblyResult]) => {
|
||||
applyResult(
|
||||
shippingResult,
|
||||
shippingRows,
|
||||
shippingStatus,
|
||||
shippingMessage,
|
||||
normalizeShippingRows,
|
||||
'暂无发货任务',
|
||||
'发货任务加载失败'
|
||||
)
|
||||
applyResult(
|
||||
assemblyResult,
|
||||
assemblyWipRows,
|
||||
assemblyWipStatus,
|
||||
assemblyWipMessage,
|
||||
normalizeAssemblyWipRows,
|
||||
'暂无装配在制任务',
|
||||
'装配在制任务加载失败'
|
||||
)
|
||||
}).finally(() => {
|
||||
activeLoad = null
|
||||
})
|
||||
|
||||
return activeLoad
|
||||
}
|
||||
|
||||
return {
|
||||
shippingColumns: SHIPPING_COLUMNS,
|
||||
shippingRows,
|
||||
shippingStatus,
|
||||
shippingMessage,
|
||||
assemblyWipColumns: ASSEMBLY_WIP_COLUMNS,
|
||||
assemblyWipRows,
|
||||
assemblyWipStatus,
|
||||
assemblyWipMessage,
|
||||
loadCenterDetailData
|
||||
}
|
||||
}
|
||||
@@ -348,6 +348,21 @@ const normalizeOverviewModule = (rows, moduleKey) => {
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeMonthlyRates = rows => rows.reduce((result, row) => {
|
||||
const moduleKey = textValue(row.module)
|
||||
if (moduleKey) {
|
||||
result[moduleKey] = {
|
||||
monthlyRate: toRate(row.currentRate),
|
||||
previousMonthRate: toRate(row.previousRate),
|
||||
monthlyRateNumerator: toNumber(row.currentNumerator),
|
||||
monthlyRateDenominator: toNumber(row.currentDenominator),
|
||||
previousMonthRateNumerator: toNumber(row.previousNumerator),
|
||||
previousMonthRateDenominator: toNumber(row.previousDenominator)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}, {})
|
||||
|
||||
const mergeCards = (moduleState, cards, summary = {}) => {
|
||||
moduleState.status = 'ready'
|
||||
moduleState.message = ''
|
||||
@@ -471,6 +486,15 @@ export function useDashboardData() {
|
||||
})
|
||||
}
|
||||
|
||||
const applyMonthlyRates = rows => {
|
||||
const monthlyRates = normalizeMonthlyRates(rows)
|
||||
Object.entries(monthlyRates).forEach(([moduleKey, rateSummary]) => {
|
||||
if (state.modules[moduleKey]) {
|
||||
Object.assign(state.modules[moduleKey].summary, rateSummary)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const fetchPlan = async () => {
|
||||
const [rateRows, noProcessRows, scheduleTrendRows, urgentRows, lateCountRows, lateRows, notKittedRows] = await Promise.all([
|
||||
safeQuery(PROCEDURES.planRate),
|
||||
@@ -724,6 +748,7 @@ export function useDashboardData() {
|
||||
|
||||
try {
|
||||
const overviewPromise = safeQuery(PROCEDURES.departmentOverview)
|
||||
const monthlyRatesPromise = safeQuery(PROCEDURES.monthlyRates)
|
||||
await Promise.all([
|
||||
fetchPlan(),
|
||||
fetchMachining(),
|
||||
@@ -732,6 +757,7 @@ export function useDashboardData() {
|
||||
fetchWarehouse()
|
||||
])
|
||||
applyDepartmentOverview(await overviewPromise)
|
||||
applyMonthlyRates(await monthlyRatesPromise)
|
||||
updatePanorama()
|
||||
state.lastUpdated = new Date().toLocaleString('zh-CN', { hour12: false })
|
||||
} finally {
|
||||
|
||||
331
生产任务看板/src/views/plan-kanban/composables/useDeviceOeeData.js
Normal file
331
生产任务看板/src/views/plan-kanban/composables/useDeviceOeeData.js
Normal file
@@ -0,0 +1,331 @@
|
||||
import { inject, ref } from 'vue'
|
||||
|
||||
const PROCEDURES = {
|
||||
devices: '设备管理_设备信息_查询',
|
||||
stations: 'MES_登录_工位与名称_查询',
|
||||
workHours: '工时统计_设备工时_查询',
|
||||
quality: '生产管理_产品合格率工位_查询',
|
||||
deviceStatus: '设备管理_设备状态_查询',
|
||||
urgentTasks: '机加设备生产运营看板_加急任务查询'
|
||||
}
|
||||
|
||||
const getRows = response => {
|
||||
if (Array.isArray(response?.data)) {
|
||||
return response.data
|
||||
}
|
||||
if (Array.isArray(response?.data?.rows)) {
|
||||
return response.data.rows
|
||||
}
|
||||
if (Array.isArray(response?.data?.result)) {
|
||||
return response.data.result
|
||||
}
|
||||
if (Array.isArray(response?.data?.data)) {
|
||||
return response.data.data
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
const toNumber = value => {
|
||||
const numberValue = Number(value)
|
||||
return Number.isFinite(numberValue) ? numberValue : 0
|
||||
}
|
||||
|
||||
const roundRate = value => Number(toNumber(value).toFixed(2))
|
||||
const normalizeText = value => String(value ?? '').trim()
|
||||
const normalizeKey = value => normalizeText(value).replace(/\s+/g, '').toLowerCase()
|
||||
const pickValue = (row, keys, fallback = '') => {
|
||||
const key = keys.find(item => row?.[item] !== undefined && row[item] !== null && row[item] !== '')
|
||||
return key ? row[key] : fallback
|
||||
}
|
||||
|
||||
const toDateText = date => {
|
||||
const year = date.getFullYear()
|
||||
const month = `${date.getMonth() + 1}`.padStart(2, '0')
|
||||
const day = `${date.getDate()}`.padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
const getMonthRange = () => {
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
return {
|
||||
start: toDateText(new Date(today.getFullYear(), today.getMonth(), 1)),
|
||||
end: toDateText(today)
|
||||
}
|
||||
}
|
||||
|
||||
const splitBindings = value => normalizeText(value)
|
||||
.split(/[,,;;|/、\s]+/)
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
const isMajorDevice = row => ['是', '1', 'true', 'yes'].includes(normalizeText(row['主要设备']).toLowerCase())
|
||||
|
||||
const mapWithConcurrency = async (items, concurrency, iteratee) => {
|
||||
const results = new Array(items.length)
|
||||
let cursor = 0
|
||||
|
||||
const worker = async () => {
|
||||
while (cursor < items.length) {
|
||||
const index = cursor
|
||||
cursor += 1
|
||||
results[index] = await iteratee(items[index], index)
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, worker))
|
||||
return results
|
||||
}
|
||||
|
||||
const getDeviceState = value => {
|
||||
const status = normalizeText(value)
|
||||
const stopped = ['停', '禁用', '故障', '报废'].some(keyword => status.includes(keyword))
|
||||
return {
|
||||
status: stopped ? '停机' : '运行',
|
||||
statusTone: stopped ? 'off' : 'run'
|
||||
}
|
||||
}
|
||||
|
||||
const summarizeWorkHours = (rows, stationNames, standardHours) => {
|
||||
const stationSet = new Set(stationNames.map(normalizeKey))
|
||||
const stationRows = rows.filter(row => stationSet.has(normalizeKey(row.operator)))
|
||||
const summaryRows = stationRows.filter(row => Number(row.Level) === 1)
|
||||
const dayRows = stationRows.filter(row => Number(row.Level) === 2 && normalizeText(row.dateLabel))
|
||||
const workHours = summaryRows.reduce((total, row) => total + toNumber(row.workHours), 0)
|
||||
const reportDays = new Set(dayRows.map(row => normalizeText(row.dateLabel))).size
|
||||
const standardOpenHours = toNumber(standardHours)
|
||||
const denominator = standardOpenHours * reportDays
|
||||
|
||||
return {
|
||||
workHours,
|
||||
reportDays,
|
||||
standardOpenHours,
|
||||
utilization: denominator > 0 ? roundRate((workHours / denominator) * 100) : 0
|
||||
}
|
||||
}
|
||||
|
||||
const getTaskKey = (orderNo, processName) => [orderNo, processName].map(normalizeKey).join('|')
|
||||
|
||||
const createTaskWorkMap = rows => {
|
||||
const workMap = new Map()
|
||||
rows
|
||||
.filter(row => Number(row.Level) === 3)
|
||||
.forEach(row => {
|
||||
const key = getTaskKey(
|
||||
pickValue(row, ['orderNo', '订单号', '订单编号', '生产订单']),
|
||||
pickValue(row, ['processName', '工序名称', '工序'])
|
||||
)
|
||||
if (key !== '|') {
|
||||
workMap.set(key, (workMap.get(key) || 0) + toNumber(pickValue(row, ['workHours', '加工工时', '加工工时和'])))
|
||||
}
|
||||
})
|
||||
return workMap
|
||||
}
|
||||
|
||||
const isUrgentRow = row => {
|
||||
const urgentQty = toNumber(pickValue(row, ['加急数量', '加急数']))
|
||||
const text = normalizeText(pickValue(row, ['加急', '是否加急', '加急状态', '加急标识', '紧急程度'])).toLowerCase()
|
||||
return urgentQty > 0 || ['是', 'y', 'yes', 'true', '1', '加急'].includes(text) ||
|
||||
(text.includes('加急') && !text.includes('不加急'))
|
||||
}
|
||||
|
||||
const normalizeWipRows = (statusRows, urgentRows, devices, workHourRows) => {
|
||||
const statusByDevice = new Map()
|
||||
statusRows.forEach(row => {
|
||||
const keys = [
|
||||
pickValue(row, ['设备名称', '设备']),
|
||||
pickValue(row, ['指派对象', '工位', '操作人'])
|
||||
].flatMap(splitBindings).map(normalizeKey).filter(Boolean)
|
||||
keys.forEach(key => {
|
||||
if (!statusByDevice.has(key)) {
|
||||
statusByDevice.set(key, row)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const urgentOrders = new Set(
|
||||
urgentRows
|
||||
.map(row => normalizeKey(pickValue(row, ['订单编号', '订单号', '生产订单'])))
|
||||
.filter(Boolean)
|
||||
)
|
||||
const taskWorkMap = createTaskWorkMap(workHourRows)
|
||||
return devices.map(device => {
|
||||
const deviceName = normalizeText(device.source['设备名称'])
|
||||
const matchKeys = [
|
||||
deviceName,
|
||||
...device.stationNames,
|
||||
...splitBindings(device.source['工位绑定'])
|
||||
].map(normalizeKey).filter(Boolean)
|
||||
const row = matchKeys.map(key => statusByDevice.get(key)).find(Boolean)
|
||||
if (!row) {
|
||||
return null
|
||||
}
|
||||
|
||||
const orderNo = normalizeText(pickValue(row, ['订单号', '订单编号', '生产订单']))
|
||||
const contractNo = normalizeText(pickValue(row, ['合同号', '销售订单']))
|
||||
const processName = normalizeText(pickValue(row, ['工序名称', '工序']))
|
||||
const plannedQty = toNumber(pickValue(row, ['分配数', '分配数量', '计划数量']))
|
||||
const reportedQty = toNumber(pickValue(row, ['报工数量', '完成数量', '完工数量']))
|
||||
const sourceWorkMinutes = toNumber(pickValue(row, ['加工工时和', '加工工时M', '加工工时']))
|
||||
const workMinutes = sourceWorkMinutes || (taskWorkMap.get(getTaskKey(orderNo, processName)) || 0) * 60
|
||||
const materialName = normalizeText(pickValue(row, ['零件名称', '物料名称', '物料描述']))
|
||||
const materialCode = normalizeText(pickValue(row, ['零件编码', '物料编码', '物料编号']))
|
||||
|
||||
return {
|
||||
id: device.source['设备流水号'] || normalizeKey(deviceName),
|
||||
deviceName,
|
||||
productionOrder: [orderNo, contractNo].filter(Boolean).join('-'),
|
||||
materialName,
|
||||
materialCode,
|
||||
processName,
|
||||
budgetMinutes: reportedQty > 0 ? Number((workMinutes / reportedQty).toFixed(2)) : null,
|
||||
plannedQty,
|
||||
reportedQty,
|
||||
urgent: urgentOrders.has(normalizeKey(orderNo)) || isUrgentRow(row)
|
||||
}
|
||||
}).filter(Boolean)
|
||||
}
|
||||
|
||||
export function useDeviceOeeData() {
|
||||
const CreateData = inject('CreateData', null)
|
||||
const ExecDatabase = inject('ExecDatabase', null)
|
||||
const rows = ref([])
|
||||
const status = ref('loading')
|
||||
const message = ref('')
|
||||
const wipRows = ref([])
|
||||
const wipStatus = ref('loading')
|
||||
const wipMessage = ref('')
|
||||
let activeLoad = null
|
||||
|
||||
const queryProcedure = async (name, params = []) => {
|
||||
if (!CreateData || !ExecDatabase) {
|
||||
throw new Error('公共数据请求方法未注入')
|
||||
}
|
||||
const payload = CreateData('11', name, params)
|
||||
return getRows(await ExecDatabase(payload))
|
||||
}
|
||||
|
||||
const queryQuality = async stationNames => {
|
||||
const resultSets = await Promise.all(stationNames.map(async stationName => {
|
||||
try {
|
||||
return await queryProcedure(PROCEDURES.quality, [
|
||||
['工位', stationName],
|
||||
['合格状态', '全部']
|
||||
])
|
||||
} catch (error) {
|
||||
console.error(`设备合格率加载失败: ${stationName}`, error)
|
||||
return []
|
||||
}
|
||||
}))
|
||||
const qualityRows = resultSets.flat()
|
||||
const defectiveQty = qualityRows.reduce((total, row) => total + toNumber(row['不良数量']), 0)
|
||||
const plannedQty = qualityRows.reduce((total, row) => total + toNumber(row['计划数量']), 0)
|
||||
|
||||
return {
|
||||
defectiveQty,
|
||||
plannedQty,
|
||||
qualityRate: plannedQty > 0 ? roundRate((1 - defectiveQty / plannedQty) * 100) : 0
|
||||
}
|
||||
}
|
||||
|
||||
const loadDeviceOeeData = async () => {
|
||||
if (activeLoad) {
|
||||
return activeLoad
|
||||
}
|
||||
|
||||
status.value = 'loading'
|
||||
message.value = ''
|
||||
wipStatus.value = 'loading'
|
||||
wipMessage.value = ''
|
||||
activeLoad = (async () => {
|
||||
try {
|
||||
const range = getMonthRange()
|
||||
const [deviceRows, stationRows, workHourRows, deviceStatusRows, urgentRows] = await Promise.all([
|
||||
queryProcedure(PROCEDURES.devices),
|
||||
queryProcedure(PROCEDURES.stations),
|
||||
queryProcedure(PROCEDURES.workHours, [
|
||||
['开始日期', range.start],
|
||||
['结束日期', range.end]
|
||||
]),
|
||||
queryProcedure(PROCEDURES.deviceStatus),
|
||||
queryProcedure(PROCEDURES.urgentTasks)
|
||||
])
|
||||
|
||||
const stationMap = new Map(stationRows.map(row => [
|
||||
normalizeText(row['工位号']),
|
||||
normalizeText(row['指南工位号'] || row['工位名称'] || row['工位号'])
|
||||
]))
|
||||
const devices = deviceRows
|
||||
.filter(row => isMajorDevice(row) && splitBindings(row['工位绑定']).length > 0)
|
||||
.map(row => {
|
||||
const stationNames = splitBindings(row['工位绑定'])
|
||||
.map(binding => stationMap.get(binding) || binding)
|
||||
.filter(Boolean)
|
||||
return {
|
||||
source: row,
|
||||
stationNames,
|
||||
...getDeviceState(row['状态'])
|
||||
}
|
||||
})
|
||||
|
||||
const qualitySummaries = await mapWithConcurrency(devices, 4, device => queryQuality(device.stationNames))
|
||||
rows.value = devices.map((device, index) => {
|
||||
const work = summarizeWorkHours(
|
||||
workHourRows,
|
||||
device.stationNames,
|
||||
device.source['设备标准开机工时']
|
||||
)
|
||||
const quality = qualitySummaries[index]
|
||||
const availability = 100
|
||||
const oee = roundRate((work.utilization / 100) * (availability / 100) * (quality.qualityRate / 100) * 100)
|
||||
|
||||
return {
|
||||
id: device.source['设备流水号'],
|
||||
name: normalizeText(device.source['设备名称']),
|
||||
stations: device.stationNames.join(' / '),
|
||||
status: device.status,
|
||||
statusTone: device.statusTone,
|
||||
automation: 'run',
|
||||
auxiliary: 'run',
|
||||
utilization: work.utilization,
|
||||
availability,
|
||||
qualityRate: quality.qualityRate,
|
||||
oee,
|
||||
workHours: work.workHours,
|
||||
reportDays: work.reportDays,
|
||||
standardOpenHours: work.standardOpenHours,
|
||||
defectiveQty: quality.defectiveQty,
|
||||
qualityPlannedQty: quality.plannedQty
|
||||
}
|
||||
})
|
||||
status.value = rows.value.length ? 'ready' : 'empty'
|
||||
message.value = rows.value.length ? '' : '暂无主要设备'
|
||||
wipRows.value = normalizeWipRows(deviceStatusRows, urgentRows, devices, workHourRows)
|
||||
wipStatus.value = wipRows.value.length ? 'ready' : 'empty'
|
||||
wipMessage.value = wipRows.value.length ? '' : '暂无机加在制任务'
|
||||
} catch (error) {
|
||||
console.error('设备运行状态明细加载失败', error)
|
||||
rows.value = []
|
||||
status.value = 'error'
|
||||
message.value = '设备数据加载失败'
|
||||
wipRows.value = []
|
||||
wipStatus.value = 'error'
|
||||
wipMessage.value = '在制任务加载失败'
|
||||
}
|
||||
})().finally(() => {
|
||||
activeLoad = null
|
||||
})
|
||||
|
||||
return activeLoad
|
||||
}
|
||||
|
||||
return {
|
||||
rows,
|
||||
status,
|
||||
message,
|
||||
wipRows,
|
||||
wipStatus,
|
||||
wipMessage,
|
||||
loadDeviceOeeData
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,7 @@ export const MODULES = [
|
||||
|
||||
export const PROCEDURES = {
|
||||
departmentOverview: 'MES_Kanban_DepartmentOverview',
|
||||
monthlyRates: 'MES_Kanban_MonthlyRates',
|
||||
planRate: '生产任务综合看板_按期齐套率',
|
||||
planNoProcess: '生产任务综合看板_未排产无工艺查询',
|
||||
planScheduleTrend: 'MES_Kanban_PlanScheduleTrend',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user