feat: add production task overview dashboard
This commit is contained in:
323
生产任务看板/doc/MES_Kanban_DepartmentOverview.sql
Normal file
323
生产任务看板/doc/MES_Kanban_DepartmentOverview.sql
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
USE [YL_MESDB]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE [dbo].[MES_Kanban_DepartmentOverview]
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
DECLARE @today date = CONVERT(date, GETDATE());
|
||||||
|
DECLARE @tomorrow date = DATEADD(day, 1, @today);
|
||||||
|
DECLARE @start date = DATEADD(day, -4, @today);
|
||||||
|
DECLARE @monthStart date = DATEFROMPARTS(YEAR(@today), MONTH(@today), 1);
|
||||||
|
|
||||||
|
CREATE TABLE #result (
|
||||||
|
[module] varchar(20) NOT NULL,
|
||||||
|
[chart] varchar(20) NOT NULL,
|
||||||
|
[series] nvarchar(30) NOT NULL,
|
||||||
|
[label] nvarchar(50) NOT NULL,
|
||||||
|
[sort] int NOT NULL,
|
||||||
|
[value] decimal(18, 2) NOT NULL,
|
||||||
|
[extraValue] decimal(18, 2) NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO #result ([module], [chart], [series], [label], [sort], [value], [extraValue])
|
||||||
|
SELECT 'sales', 'bar', N'任务数', category, sort, COUNT_BIG(1), NULL
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN [销售产品名称] = N'同轴阀' THEN N'同轴阀'
|
||||||
|
WHEN [销售产品名称] = N'同轴阀组' THEN N'同轴阀组'
|
||||||
|
WHEN [销售产品名称] LIKE N'%系统%' THEN N'系统'
|
||||||
|
ELSE N'其他'
|
||||||
|
END AS category,
|
||||||
|
CASE
|
||||||
|
WHEN [销售产品名称] = N'同轴阀' THEN 1
|
||||||
|
WHEN [销售产品名称] = N'同轴阀组' THEN 2
|
||||||
|
WHEN [销售产品名称] LIKE N'%系统%' THEN 3
|
||||||
|
ELSE 4
|
||||||
|
END AS sort
|
||||||
|
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_TransitTaskClass]
|
||||||
|
) source
|
||||||
|
GROUP BY category, sort;
|
||||||
|
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT 'sales', 'summary', N'滞留', N'销售滞留', 1, COUNT_BIG(1), NULL
|
||||||
|
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_SaleBlgDet];
|
||||||
|
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT
|
||||||
|
'sales', 'rate', N'准交率', N'准交率', 1,
|
||||||
|
CAST(CASE WHEN denominator.total = 0 THEN 0 ELSE numerator.total * 100.0 / denominator.total END AS decimal(18, 2)),
|
||||||
|
denominator.total
|
||||||
|
FROM (SELECT COUNT_BIG(1) AS total FROM [SAP].[SBO_YL].[dbo].[SAP_MES_SaleOTRate_z]) numerator
|
||||||
|
CROSS JOIN (SELECT COUNT_BIG(1) AS total FROM [SAP].[SBO_YL].[dbo].[SAP_MES_SaleOTRate_m]) denominator;
|
||||||
|
|
||||||
|
;WITH dates AS (
|
||||||
|
SELECT DATEADD(day, number, @start) AS dt, number + 1 AS sort
|
||||||
|
FROM (VALUES (0), (1), (2), (3), (4)) source(number)
|
||||||
|
), added AS (
|
||||||
|
SELECT CONVERT(date, [DocDate]) AS dt, COUNT(DISTINCT [单据编号]) AS total
|
||||||
|
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_TransitTaskClass]
|
||||||
|
WHERE [DocDate] >= @start AND [DocDate] < @tomorrow
|
||||||
|
GROUP BY CONVERT(date, [DocDate])
|
||||||
|
), reduced AS (
|
||||||
|
SELECT CONVERT(date, [DocDate]) AS dt, COUNT(DISTINCT [单据号]) AS total
|
||||||
|
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_BizIncDec]
|
||||||
|
WHERE [DocDate] >= @start AND [DocDate] < @tomorrow
|
||||||
|
GROUP BY CONVERT(date, [DocDate])
|
||||||
|
)
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT 'sales', 'trend', N'增减', CONVERT(char(10), dates.dt, 120), dates.sort,
|
||||||
|
ISNULL(added.total, 0), ISNULL(reduced.total, 0)
|
||||||
|
FROM dates
|
||||||
|
LEFT JOIN added ON added.dt = dates.dt
|
||||||
|
LEFT JOIN reduced ON reduced.dt = dates.dt;
|
||||||
|
|
||||||
|
;WITH categories AS (
|
||||||
|
SELECT
|
||||||
|
CASE WHEN [类别] IN (N'分类一', N'分类二', N'分类三', N'分类四') THEN [类别] ELSE N'其他' END AS category,
|
||||||
|
CASE [类别] WHEN N'分类一' THEN 1 WHEN N'分类二' THEN 2 WHEN N'分类三' THEN 3 WHEN N'分类四' THEN 4 ELSE 5 END AS sort
|
||||||
|
FROM [dbo].[MES_设计报工_任务]
|
||||||
|
WHERE ISNULL([单据状态], N'') <> N'已完成'
|
||||||
|
)
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT 'research', 'bar', N'任务数', category, sort, COUNT_BIG(1), NULL
|
||||||
|
FROM categories
|
||||||
|
GROUP BY category, sort;
|
||||||
|
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT 'research', 'summary', N'滞留', N'研发滞留', 1, COUNT_BIG(1), NULL
|
||||||
|
FROM [dbo].[MES_设计报工_任务]
|
||||||
|
WHERE ISNULL([单据状态], N'') <> N'已完成'
|
||||||
|
AND [预计结束时间] < GETDATE();
|
||||||
|
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT
|
||||||
|
'research', 'rate', N'完成率', N'按期完成率', 1,
|
||||||
|
CAST(CASE WHEN COUNT_BIG(1) = 0 THEN 0 ELSE SUM(CASE WHEN [单据状态] = N'已完成' THEN 1 ELSE 0 END) * 100.0 / COUNT_BIG(1) END AS decimal(18, 2)),
|
||||||
|
COUNT_BIG(1)
|
||||||
|
FROM [dbo].[MES_设计报工_任务]
|
||||||
|
WHERE [预计结束时间] >= @monthStart AND [预计结束时间] < @tomorrow;
|
||||||
|
|
||||||
|
;WITH dates AS (
|
||||||
|
SELECT DATEADD(day, number, @start) AS dt, number + 1 AS sort
|
||||||
|
FROM (VALUES (0), (1), (2), (3), (4)) source(number)
|
||||||
|
), added AS (
|
||||||
|
SELECT CONVERT(date, [创建日期]) AS dt, COUNT_BIG(1) AS total
|
||||||
|
FROM [dbo].[MES_设计报工_任务]
|
||||||
|
WHERE [创建日期] >= @start AND [创建日期] < @tomorrow
|
||||||
|
GROUP BY CONVERT(date, [创建日期])
|
||||||
|
), reduced AS (
|
||||||
|
SELECT CONVERT(date, [最后编辑时间]) AS dt, COUNT_BIG(1) AS total
|
||||||
|
FROM [dbo].[MES_设计报工_任务]
|
||||||
|
WHERE [单据状态] = N'已完成' AND [最后编辑时间] >= @start AND [最后编辑时间] < @tomorrow
|
||||||
|
GROUP BY CONVERT(date, [最后编辑时间])
|
||||||
|
)
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT 'research', 'trend', N'增减', CONVERT(char(10), dates.dt, 120), dates.sort,
|
||||||
|
ISNULL(added.total, 0), ISNULL(reduced.total, 0)
|
||||||
|
FROM dates
|
||||||
|
LEFT JOIN added ON added.dt = dates.dt
|
||||||
|
LEFT JOIN reduced ON reduced.dt = dates.dt;
|
||||||
|
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT 'purchase', 'bar', N'任务数', N'申请', 1, COUNT_BIG(1), NULL
|
||||||
|
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_InTransitTask]
|
||||||
|
UNION ALL
|
||||||
|
SELECT 'purchase', 'bar', N'任务数', N'订单', 2, COUNT_BIG(1), NULL
|
||||||
|
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_POAddDet];
|
||||||
|
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT
|
||||||
|
'purchase', 'rate', N'及时率', N'外购及时率', 1,
|
||||||
|
CAST(CASE WHEN denominator.total = 0 THEN 0 ELSE numerator.total * 100.0 / denominator.total END AS decimal(18, 2)),
|
||||||
|
denominator.total
|
||||||
|
FROM (SELECT COUNT_BIG(1) AS total FROM [SAP].[SBO_YL].[dbo].[SAP_MES_POOnTimeRate]) numerator
|
||||||
|
CROSS JOIN (SELECT COUNT_BIG(1) AS total FROM [SAP].[SBO_YL].[dbo].[SAP_MES_POOnTimeRate_M]) denominator;
|
||||||
|
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT
|
||||||
|
'purchase', 'summary', N'滞留', N'采购滞留', 1,
|
||||||
|
CASE WHEN denominator.total > numerator.total THEN denominator.total - numerator.total ELSE 0 END,
|
||||||
|
NULL
|
||||||
|
FROM (SELECT COUNT_BIG(1) AS total FROM [SAP].[SBO_YL].[dbo].[SAP_MES_POOnTimeRate]) numerator
|
||||||
|
CROSS JOIN (SELECT COUNT_BIG(1) AS total FROM [SAP].[SBO_YL].[dbo].[SAP_MES_POOnTimeRate_M]) denominator;
|
||||||
|
|
||||||
|
;WITH dates AS (
|
||||||
|
SELECT DATEADD(day, number, @start) AS dt, number + 1 AS sort
|
||||||
|
FROM (VALUES (0), (1), (2), (3), (4)) source(number)
|
||||||
|
), added AS (
|
||||||
|
SELECT dt, SUM(total) AS total
|
||||||
|
FROM (
|
||||||
|
SELECT CONVERT(date, [DocDate]) AS dt, COUNT_BIG(1) AS total
|
||||||
|
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_PRQAddList]
|
||||||
|
WHERE [DocDate] >= @start AND [DocDate] < @tomorrow
|
||||||
|
GROUP BY CONVERT(date, [DocDate])
|
||||||
|
UNION ALL
|
||||||
|
SELECT CONVERT(date, [DocDate]), COUNT_BIG(1)
|
||||||
|
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_POAddDet]
|
||||||
|
WHERE [DocDate] >= @start AND [DocDate] < @tomorrow
|
||||||
|
GROUP BY CONVERT(date, [DocDate])
|
||||||
|
) source
|
||||||
|
GROUP BY dt
|
||||||
|
), reduced AS (
|
||||||
|
SELECT dt, SUM(total) AS total
|
||||||
|
FROM (
|
||||||
|
SELECT CONVERT(date, [DocDate]) AS dt, COUNT_BIG(1) AS total
|
||||||
|
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_PRQClearList]
|
||||||
|
WHERE [DocDate] >= @start AND [DocDate] < @tomorrow
|
||||||
|
GROUP BY CONVERT(date, [DocDate])
|
||||||
|
UNION ALL
|
||||||
|
SELECT CONVERT(date, [DocDate]), COUNT_BIG(1)
|
||||||
|
FROM [SAP].[SBO_YL].[dbo].[SAP_MES_POClearList]
|
||||||
|
WHERE [DocDate] >= @start AND [DocDate] < @tomorrow
|
||||||
|
GROUP BY CONVERT(date, [DocDate])
|
||||||
|
) source
|
||||||
|
GROUP BY dt
|
||||||
|
)
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT 'purchase', 'trend', N'增减', CONVERT(char(10), dates.dt, 120), dates.sort,
|
||||||
|
ISNULL(added.total, 0), ISNULL(reduced.total, 0)
|
||||||
|
FROM dates
|
||||||
|
LEFT JOIN added ON added.dt = dates.dt
|
||||||
|
LEFT JOIN reduced ON reduced.dt = dates.dt;
|
||||||
|
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT 'machining', 'summary', N'任务', N'未完成任务', 0, COUNT(DISTINCT [TaskAID]), NULL
|
||||||
|
FROM [dbo].[View_生产订单_MES]
|
||||||
|
WHERE ISNULL([任务状态], 0) < 4
|
||||||
|
AND [指派对象] <> N'外协' AND [指派对象] NOT LIKE N'%装配%';
|
||||||
|
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT 'machining', 'summary', N'加急', N'加急任务', 1, COUNT(DISTINCT [订单编号]), NULL
|
||||||
|
FROM [dbo].[View_生产订单_MES]
|
||||||
|
WHERE ISNULL([加急总数], 0) > 0 AND [指派对象] NOT LIKE N'%装配%';
|
||||||
|
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT 'machining', 'summary', N'滞留', N'未及时开工', 2, COUNT_BIG(1), NULL
|
||||||
|
FROM [dbo].[View_生产订单_MES]
|
||||||
|
WHERE [计划开始时间] < GETDATE() AND [开工时间] IS NULL
|
||||||
|
AND [排产状态] = 1
|
||||||
|
AND [指派对象] <> N'外协' AND [指派对象] NOT LIKE N'%装配%';
|
||||||
|
|
||||||
|
;WITH dates AS (
|
||||||
|
SELECT DATEADD(day, number, @start) AS dt, number + 1 AS sort
|
||||||
|
FROM (VALUES (0), (1), (2), (3), (4)) source(number)
|
||||||
|
), added AS (
|
||||||
|
SELECT CONVERT(date, [派工时间]) AS dt, COUNT(DISTINCT [TaskAID]) AS total
|
||||||
|
FROM [dbo].[View_生产订单_MES]
|
||||||
|
WHERE [派工时间] >= @start AND [派工时间] < @tomorrow
|
||||||
|
AND [指派对象] <> N'外协' AND [指派对象] NOT LIKE N'%装配%'
|
||||||
|
GROUP BY CONVERT(date, [派工时间])
|
||||||
|
), reduced AS (
|
||||||
|
SELECT CONVERT(date, [修改时间]) AS dt, COUNT(DISTINCT [TaskAID]) AS total
|
||||||
|
FROM [dbo].[View_生产订单_MES]
|
||||||
|
WHERE [修改时间] >= @start AND [修改时间] < @tomorrow
|
||||||
|
AND ISNULL([任务状态], 0) = 4
|
||||||
|
AND [指派对象] <> N'外协' AND [指派对象] NOT LIKE N'%装配%'
|
||||||
|
GROUP BY CONVERT(date, [修改时间])
|
||||||
|
)
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT 'machining', 'trend', N'增减', CONVERT(char(10), dates.dt, 120), dates.sort,
|
||||||
|
ISNULL(added.total, 0), ISNULL(reduced.total, 0)
|
||||||
|
FROM dates
|
||||||
|
LEFT JOIN added ON added.dt = dates.dt
|
||||||
|
LEFT JOIN reduced ON reduced.dt = dates.dt;
|
||||||
|
|
||||||
|
;WITH dates AS (
|
||||||
|
SELECT DATEADD(day, number, @start) AS dt, number + 1 AS sort
|
||||||
|
FROM (VALUES (0), (1), (2), (3), (4)) source(number)
|
||||||
|
), added AS (
|
||||||
|
SELECT CONVERT(date, [派工时间]) AS dt, COUNT(DISTINCT [TaskAID]) AS total
|
||||||
|
FROM [dbo].[View_生产订单_MES]
|
||||||
|
WHERE [派工时间] >= @start AND [派工时间] < @tomorrow
|
||||||
|
AND [自制件属性] LIKE N'%装配%'
|
||||||
|
GROUP BY CONVERT(date, [派工时间])
|
||||||
|
), reduced AS (
|
||||||
|
SELECT CONVERT(date, [修改时间]) AS dt, COUNT(DISTINCT [TaskAID]) AS total
|
||||||
|
FROM [dbo].[View_生产订单_MES]
|
||||||
|
WHERE [修改时间] >= @start AND [修改时间] < @tomorrow
|
||||||
|
AND [自制件属性] LIKE N'%装配%'
|
||||||
|
AND ISNULL([指派数量], 0) > 0
|
||||||
|
AND ISNULL([完成数量], 0) >= ISNULL([指派数量], 0)
|
||||||
|
GROUP BY CONVERT(date, [修改时间])
|
||||||
|
)
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT 'assembly', 'trend', N'增减', CONVERT(char(10), dates.dt, 120), dates.sort,
|
||||||
|
ISNULL(added.total, 0), ISNULL(reduced.total, 0)
|
||||||
|
FROM dates
|
||||||
|
LEFT JOIN added ON added.dt = dates.dt
|
||||||
|
LEFT JOIN reduced ON reduced.dt = dates.dt;
|
||||||
|
|
||||||
|
;WITH dates AS (
|
||||||
|
SELECT DATEADD(day, number, @start) AS dt, number + 1 AS sort
|
||||||
|
FROM (VALUES (0), (1), (2), (3), (4)) source(number)
|
||||||
|
), added AS (
|
||||||
|
SELECT CONVERT(date, [开始时间]) AS dt, COUNT(DISTINCT [TaskAID]) AS total
|
||||||
|
FROM [dbo].[YL_加工中心_操作记录表]
|
||||||
|
WHERE [开始时间] >= @start AND [开始时间] < @tomorrow
|
||||||
|
AND [操作类别] IN (N'报工', N'辅助结束')
|
||||||
|
GROUP BY CONVERT(date, [开始时间])
|
||||||
|
), reduced AS (
|
||||||
|
SELECT CONVERT(date, [检测时间]) AS dt, COUNT(DISTINCT [TaskAID]) AS total
|
||||||
|
FROM [dbo].[YL_质量检验_质检记录]
|
||||||
|
WHERE [检测时间] >= @start AND [检测时间] < @tomorrow
|
||||||
|
GROUP BY CONVERT(date, [检测时间])
|
||||||
|
)
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT 'quality', 'trend', N'增减', CONVERT(char(10), dates.dt, 120), dates.sort,
|
||||||
|
ISNULL(added.total, 0), ISNULL(reduced.total, 0)
|
||||||
|
FROM dates
|
||||||
|
LEFT JOIN added ON added.dt = dates.dt
|
||||||
|
LEFT JOIN reduced ON reduced.dt = dates.dt;
|
||||||
|
|
||||||
|
;WITH dates AS (
|
||||||
|
SELECT DATEADD(day, number, @start) AS dt, number + 1 AS sort
|
||||||
|
FROM (VALUES (0), (1), (2), (3), (4)) source(number)
|
||||||
|
), added AS (
|
||||||
|
SELECT CONVERT(date, [doc_date]) AS dt, COUNT_BIG(1) AS total
|
||||||
|
FROM [SAP].[SBO_YL].[dbo].[UBT_MES_DP_receipttask_material_2Days]
|
||||||
|
WHERE [doc_date] >= @start AND [doc_date] < @tomorrow
|
||||||
|
GROUP BY CONVERT(date, [doc_date])
|
||||||
|
), reduced AS (
|
||||||
|
SELECT dt, SUM(total) AS total
|
||||||
|
FROM (
|
||||||
|
SELECT TRY_CONVERT(date, [拣配日期]) AS dt, COUNT_BIG(1) AS total
|
||||||
|
FROM [SAP].[SBO_YL].[dbo].[UBT_MES_DP_OpenOPKL_material_101]
|
||||||
|
WHERE TRY_CONVERT(date, [拣配日期]) >= @start AND TRY_CONVERT(date, [拣配日期]) < @tomorrow
|
||||||
|
GROUP BY TRY_CONVERT(date, [拣配日期])
|
||||||
|
UNION ALL
|
||||||
|
SELECT TRY_CONVERT(date, [拣配日期]), COUNT_BIG(1)
|
||||||
|
FROM [SAP].[SBO_YL].[dbo].[UBT_MES_DP_OpenOPKL_material_103]
|
||||||
|
WHERE TRY_CONVERT(date, [拣配日期]) >= @start AND TRY_CONVERT(date, [拣配日期]) < @tomorrow
|
||||||
|
GROUP BY TRY_CONVERT(date, [拣配日期])
|
||||||
|
) source
|
||||||
|
GROUP BY dt
|
||||||
|
)
|
||||||
|
INSERT INTO #result
|
||||||
|
SELECT 'warehouse', 'trend', N'增减', CONVERT(char(10), dates.dt, 120), dates.sort,
|
||||||
|
ISNULL(added.total, 0), ISNULL(reduced.total, 0)
|
||||||
|
FROM dates
|
||||||
|
LEFT JOIN added ON added.dt = dates.dt
|
||||||
|
LEFT JOIN reduced ON reduced.dt = dates.dt;
|
||||||
|
|
||||||
|
INSERT INTO #result
|
||||||
|
VALUES ('warehouse', 'rate', N'及时率', N'及时率', 1, 95, NULL);
|
||||||
|
|
||||||
|
SELECT [module], [chart], [series], [label], [sort], [value], [extraValue]
|
||||||
|
FROM #result
|
||||||
|
ORDER BY
|
||||||
|
CASE [module]
|
||||||
|
WHEN 'sales' THEN 1
|
||||||
|
WHEN 'research' THEN 2
|
||||||
|
WHEN 'purchase' THEN 3
|
||||||
|
WHEN 'machining' THEN 4
|
||||||
|
WHEN 'assembly' THEN 5
|
||||||
|
WHEN 'quality' THEN 6
|
||||||
|
WHEN 'warehouse' THEN 7
|
||||||
|
ELSE 99
|
||||||
|
END,
|
||||||
|
[chart], [sort];
|
||||||
|
END
|
||||||
|
GO
|
||||||
@@ -1,24 +1,28 @@
|
|||||||
import { createRouter, createWebHashHistory } from "vue-router";
|
import { createRouter, createWebHashHistory } from "vue-router";
|
||||||
|
|
||||||
// cookies
|
|
||||||
|
|
||||||
// 创建routes
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
// 重定向
|
|
||||||
path: "/home",
|
|
||||||
redirect: "/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
//前台首页
|
|
||||||
path: "/",
|
path: "/",
|
||||||
name: "root",
|
name: "root",
|
||||||
component: () => import("@/views/index.vue"),
|
component: () => import("@/views/index.vue"),
|
||||||
children: [],
|
children: [],
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
path: "/home",
|
||||||
|
redirect: "/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/plan-kanban",
|
||||||
|
name: "planKanban",
|
||||||
|
component: () => import("@/views/plan-kanban/index.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/production-dashboard",
|
||||||
|
name: "productionDashboard",
|
||||||
|
redirect: "/",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
//History路由模型
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||||
routes,
|
routes,
|
||||||
|
|||||||
261
生产任务看板/src/views/plan-kanban/components/ChartPanel.vue
Normal file
261
生产任务看板/src/views/plan-kanban/components/ChartPanel.vue
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
<template>
|
||||||
|
<article class="chart-panel panel">
|
||||||
|
<div class="panel-head">
|
||||||
|
<div class="panel-title">
|
||||||
|
<h2>{{ title }}</h2>
|
||||||
|
<p v-if="subtitle">{{ subtitle }}</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="stats.length" class="stat-box" :class="`stat-${resolvedStatsVariant}`">
|
||||||
|
<template v-if="resolvedStatsVariant === 'summary'">
|
||||||
|
<div class="summary-total">
|
||||||
|
<b>{{ stats[0].value }}</b>
|
||||||
|
<span>{{ stats[0].unit || '' }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="stats[1]" class="summary-urgent">
|
||||||
|
<span>{{ stats[1].label }}</span>
|
||||||
|
<b>{{ stats[1].value }}</b>
|
||||||
|
<em>{{ stats[1].unit || '' }}</em>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div v-for="item in stats" :key="item.label" class="stat-line">
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
<b>{{ item.value }}</b>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<span v-else-if="badge" class="badge">{{ badge }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="chart-body" :style="{ height: normalizedHeight }">
|
||||||
|
<div ref="elRef" class="chart-canvas"></div>
|
||||||
|
<div v-if="showOverlay" class="state-overlay">
|
||||||
|
<div class="state-icon"></div>
|
||||||
|
<div>{{ overlayText }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useEchart } from '../composables/useEchart'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
badge: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
stats: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
statsVariant: {
|
||||||
|
type: String,
|
||||||
|
default: 'compact'
|
||||||
|
},
|
||||||
|
option: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
default: 'ready'
|
||||||
|
},
|
||||||
|
emptyText: {
|
||||||
|
type: String,
|
||||||
|
default: '暂无数据'
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 260
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const optionRef = computed(() => props.option)
|
||||||
|
const { elRef } = useEchart(optionRef)
|
||||||
|
|
||||||
|
const normalizedHeight = computed(() => (typeof props.height === 'number' ? `${props.height}px` : props.height))
|
||||||
|
const resolvedStatsVariant = computed(() => (props.statsVariant === 'compact' && props.stats.length === 2 ? 'summary' : props.statsVariant))
|
||||||
|
const showOverlay = computed(() => props.status !== 'ready')
|
||||||
|
const overlayText = computed(() => {
|
||||||
|
if (props.status === 'loading') {
|
||||||
|
return '加载中'
|
||||||
|
}
|
||||||
|
if (props.status === 'error') {
|
||||||
|
return '数据加载失败'
|
||||||
|
}
|
||||||
|
if (props.status === 'pending') {
|
||||||
|
return '待接口接入'
|
||||||
|
}
|
||||||
|
return props.emptyText
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.chart-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
min-height: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
color: #8cd3ff;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 7px 0 0;
|
||||||
|
color: #78a9ce;
|
||||||
|
font-size: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding: 4px 10px;
|
||||||
|
color: #eaf7ff;
|
||||||
|
font-size: 13px;
|
||||||
|
border: 1px solid rgba(43, 216, 255, 0.46);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(22, 141, 255, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-box {
|
||||||
|
flex: 0 0 168px;
|
||||||
|
min-height: 52px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid rgba(43, 216, 255, 0.46);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(6, 19, 56, 0.72);
|
||||||
|
box-shadow: inset 0 0 12px rgba(43, 216, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-summary {
|
||||||
|
flex-basis: 138px;
|
||||||
|
min-height: 66px;
|
||||||
|
padding: 5px 10px 7px;
|
||||||
|
text-align: right;
|
||||||
|
border-color: transparent;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-total {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 5px;
|
||||||
|
color: #eaf7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-total b {
|
||||||
|
color: #38c8ff;
|
||||||
|
font-size: 42px;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 0.9;
|
||||||
|
text-shadow: 0 0 12px rgba(56, 200, 255, 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-total span {
|
||||||
|
padding-bottom: 2px;
|
||||||
|
color: #eaf7ff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-urgent {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: #eaf7ff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-urgent b {
|
||||||
|
margin: 0 4px;
|
||||||
|
color: #ff3d57;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-urgent em {
|
||||||
|
color: #eaf7ff;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-line {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
color: #78a9ce;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-line b {
|
||||||
|
color: #eaf7ff;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-body {
|
||||||
|
position: relative;
|
||||||
|
min-height: 160px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
color: #78a9ce;
|
||||||
|
font-size: 16px;
|
||||||
|
border: 1px dashed rgba(43, 216, 255, 0.28);
|
||||||
|
background: rgba(7, 31, 72, 0.78);
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-icon {
|
||||||
|
width: 54px;
|
||||||
|
height: 32px;
|
||||||
|
border: 1px solid rgba(43, 216, 255, 0.42);
|
||||||
|
border-top-width: 5px;
|
||||||
|
box-shadow: inset 0 0 16px rgba(43, 216, 255, 0.18);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
149
生产任务看板/src/views/plan-kanban/components/DashboardHeader.vue
Normal file
149
生产任务看板/src/views/plan-kanban/components/DashboardHeader.vue
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
<template>
|
||||||
|
<header class="dashboard-header">
|
||||||
|
<div class="brand">
|
||||||
|
<img class="brand-logo" :src="logoUrl" alt="RENLY" />
|
||||||
|
</div>
|
||||||
|
<div class="title-wrap">
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<div class="subtitle">{{ subtitle }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="header-meta">
|
||||||
|
<div class="current-time">{{ currentTime }}</div>
|
||||||
|
<div class="refresh-row">
|
||||||
|
<span v-if="lastUpdated" class="updated">更新 {{ lastUpdated }}</span>
|
||||||
|
<button class="refresh-btn" type="button" :disabled="refreshing" @click="$emit('refresh')">
|
||||||
|
<el-icon><Refresh /></el-icon>
|
||||||
|
<span>{{ refreshing ? '刷新中' : '刷新' }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { Refresh } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '计划看板'
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
type: String,
|
||||||
|
default: '生产计划综合监控'
|
||||||
|
},
|
||||||
|
currentTime: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
lastUpdated: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
refreshing: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
logoUrl: {
|
||||||
|
type: String,
|
||||||
|
default: './public/ClientLogo.png'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
defineEmits(['refresh'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.dashboard-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 20;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 340px 1fr 460px;
|
||||||
|
align-items: center;
|
||||||
|
height: 96px;
|
||||||
|
border-bottom: 1px solid rgba(43, 216, 255, 0.62);
|
||||||
|
background: rgba(7, 0, 31, 0.96);
|
||||||
|
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.26);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-logo {
|
||||||
|
width: 224px;
|
||||||
|
height: 58px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
object-fit: contain;
|
||||||
|
object-position: left center;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 6px;
|
||||||
|
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.28));
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-wrap {
|
||||||
|
min-width: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
color: #f3f9ff;
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1.05;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin-top: 8px;
|
||||||
|
color: #8cd3ff;
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-meta {
|
||||||
|
padding-right: 18px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-time {
|
||||||
|
color: #eef7ff;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.updated {
|
||||||
|
color: #78a9ce;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
color: #eaf7ff;
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid rgba(43, 216, 255, 0.62);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(14, 75, 156, 0.62);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.62;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
191
生产任务看板/src/views/plan-kanban/components/DataTable.vue
Normal file
191
生产任务看板/src/views/plan-kanban/components/DataTable.vue
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
<template>
|
||||||
|
<article class="data-table panel">
|
||||||
|
<div class="table-head">
|
||||||
|
<h2>{{ title }}</h2>
|
||||||
|
<span class="row-count">{{ rows.length }} 条</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="table-wrap"
|
||||||
|
:style="{ height: normalizedMaxHeight, '--scroll-duration': scrollDuration }"
|
||||||
|
>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th v-for="column in columns" :key="column.key" :style="{ width: column.width || 'auto' }">
|
||||||
|
{{ column.label }}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody :class="{ 'auto-scroll': shouldAutoScroll }">
|
||||||
|
<tr v-if="rows.length === 0">
|
||||||
|
<td class="empty-cell" :colspan="columns.length">{{ emptyText }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(row, index) in displayRows" v-else :key="row.id || `${title}-${index}`">
|
||||||
|
<td v-for="column in columns" :key="column.key" :style="{ width: column.width || 'auto' }">
|
||||||
|
<span>{{ row[column.key] || '--' }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
rows: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
emptyText: {
|
||||||
|
type: String,
|
||||||
|
default: '暂无数据'
|
||||||
|
},
|
||||||
|
maxHeight: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 360
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const normalizedMaxHeight = computed(() => (typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight))
|
||||||
|
const visibleRows = computed(() => {
|
||||||
|
const height = Number.parseFloat(props.maxHeight)
|
||||||
|
if (!Number.isFinite(height)) {
|
||||||
|
return 7
|
||||||
|
}
|
||||||
|
return Math.max(Math.floor((height - 42) / 42), 1)
|
||||||
|
})
|
||||||
|
const shouldAutoScroll = computed(() => props.rows.length > visibleRows.value)
|
||||||
|
const displayRows = computed(() => (shouldAutoScroll.value ? [...props.rows, ...props.rows] : props.rows))
|
||||||
|
const scrollDuration = computed(() => `${Math.max(props.rows.length * 3, 24)}s`)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.data-table {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 16px 16px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
color: #8cd3ff;
|
||||||
|
font-size: 21px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-count {
|
||||||
|
color: #78a9ce;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrap {
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(53, 149, 217, 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
table-layout: fixed;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr {
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody.auto-scroll {
|
||||||
|
animation: table-scroll-up var(--scroll-duration, 24s) linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrap:hover tbody.auto-scroll {
|
||||||
|
animation-play-state: paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes table-scroll-up {
|
||||||
|
from {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
height: 42px;
|
||||||
|
padding: 0 8px;
|
||||||
|
color: #f1f8ff;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.25;
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid rgba(53, 149, 217, 0.42);
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
color: #cceeff;
|
||||||
|
font-weight: 800;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: normal;
|
||||||
|
background: #082654;
|
||||||
|
box-shadow: inset 0 0 12px rgba(27, 130, 214, 0.32);
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
background: rgba(7, 0, 31, 0.62);
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:nth-child(even) td {
|
||||||
|
background: rgba(8, 38, 84, 0.38);
|
||||||
|
}
|
||||||
|
|
||||||
|
td span {
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-cell {
|
||||||
|
height: 120px;
|
||||||
|
color: #78a9ce;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
128
生产任务看板/src/views/plan-kanban/components/MetricCard.vue
Normal file
128
生产任务看板/src/views/plan-kanban/components/MetricCard.vue
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<template>
|
||||||
|
<article class="metric-card" :class="[`status-${status}`, { compact }]">
|
||||||
|
<div class="metric-label">{{ label }}</div>
|
||||||
|
<div class="metric-value" :class="{ rate: valueType === 'rate' }">{{ value }}</div>
|
||||||
|
<div v-if="subValue" class="metric-sub">{{ subValue }}</div>
|
||||||
|
<div v-else class="metric-sub muted">{{ placeholder }}</div>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '--'
|
||||||
|
},
|
||||||
|
subValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
default: 'ready'
|
||||||
|
},
|
||||||
|
valueType: {
|
||||||
|
type: String,
|
||||||
|
default: 'number'
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '--'
|
||||||
|
},
|
||||||
|
compact: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.metric-card {
|
||||||
|
position: relative;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 112px;
|
||||||
|
padding: 18px 18px 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(43, 216, 255, 0.45);
|
||||||
|
border-radius: 6px;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(19, 111, 190, 0.24), rgba(4, 20, 62, 0.18)),
|
||||||
|
rgba(7, 31, 72, 0.74);
|
||||||
|
box-shadow: inset 0 0 16px rgba(27, 130, 214, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-card::before {
|
||||||
|
position: absolute;
|
||||||
|
right: -18px;
|
||||||
|
bottom: -28px;
|
||||||
|
width: 92px;
|
||||||
|
height: 92px;
|
||||||
|
content: '';
|
||||||
|
border: 1px solid rgba(43, 216, 255, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-label {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
color: #8cd3ff;
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
margin-top: 12px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-shadow: 0 0 12px rgba(28, 230, 255, 0.32);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value.rate {
|
||||||
|
color: #1ce6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-sub {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
min-height: 18px;
|
||||||
|
margin-top: 9px;
|
||||||
|
color: #d8f4ff;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-sub.muted {
|
||||||
|
color: rgba(120, 169, 206, 0.62);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-empty,
|
||||||
|
.status-pending {
|
||||||
|
border-style: dashed;
|
||||||
|
background: rgba(7, 31, 72, 0.48);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-empty .metric-value,
|
||||||
|
.status-pending .metric-value {
|
||||||
|
color: #78a9ce;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact {
|
||||||
|
min-height: 92px;
|
||||||
|
padding: 14px 14px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact .metric-value {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
753
生产任务看板/src/views/plan-kanban/composables/useDashboardData.js
Normal file
753
生产任务看板/src/views/plan-kanban/composables/useDashboardData.js
Normal file
@@ -0,0 +1,753 @@
|
|||||||
|
import { inject, reactive } from 'vue'
|
||||||
|
import { PROCEDURES } from '../config/modules'
|
||||||
|
|
||||||
|
const emptyModule = (status = 'loading') => ({
|
||||||
|
status,
|
||||||
|
message: '',
|
||||||
|
cards: [],
|
||||||
|
summary: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const qualityParams = [
|
||||||
|
['@任务类型', '', 'nvarchar', false],
|
||||||
|
['@生产订单', '', 'nvarchar', false],
|
||||||
|
['@销售订单', '', 'nvarchar', false],
|
||||||
|
['@物料名称', '', 'nvarchar', false],
|
||||||
|
['@物料编码', '', 'nvarchar', false]
|
||||||
|
]
|
||||||
|
|
||||||
|
const emptyRows = Object.freeze([])
|
||||||
|
|
||||||
|
const getRows = response => {
|
||||||
|
if (!response) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
if (Array.isArray(response.data)) {
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
if (response.data && Array.isArray(response.data.result)) {
|
||||||
|
return response.data.result
|
||||||
|
}
|
||||||
|
if (response.data && Array.isArray(response.data.data)) {
|
||||||
|
return response.data.data
|
||||||
|
}
|
||||||
|
if (response.data && Array.isArray(response.data.Table)) {
|
||||||
|
return response.data.Table
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstRow = rows => (Array.isArray(rows) && rows.length > 0 ? rows[0] : {})
|
||||||
|
|
||||||
|
const toNumber = value => {
|
||||||
|
if (value === null || value === undefined || value === '') {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
const numberValue = Number(value)
|
||||||
|
return Number.isFinite(numberValue) ? numberValue : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const toRate = value => {
|
||||||
|
const numberValue = toNumber(value)
|
||||||
|
if (numberValue > 0 && numberValue <= 1) {
|
||||||
|
return Number((numberValue * 100).toFixed(2))
|
||||||
|
}
|
||||||
|
return Number(numberValue.toFixed(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatPercent = value => {
|
||||||
|
const rate = toRate(value)
|
||||||
|
return rate ? `${rate}%` : '--'
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatNumber = value => {
|
||||||
|
const numberValue = toNumber(value)
|
||||||
|
if (!numberValue) {
|
||||||
|
return '0'
|
||||||
|
}
|
||||||
|
if (Number.isInteger(numberValue)) {
|
||||||
|
return `${numberValue}`
|
||||||
|
}
|
||||||
|
return `${Number(numberValue.toFixed(2))}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = value => {
|
||||||
|
if (!value) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
const date = new Date(value)
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return String(value).slice(0, 10)
|
||||||
|
}
|
||||||
|
const month = `${date.getMonth() + 1}`.padStart(2, '0')
|
||||||
|
const day = `${date.getDate()}`.padStart(2, '0')
|
||||||
|
return `${date.getFullYear()}-${month}-${day}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const toDateKey = value => {
|
||||||
|
if (!value) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
const date = new Date(value)
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return String(value).slice(0, 10)
|
||||||
|
}
|
||||||
|
const month = `${date.getMonth() + 1}`.padStart(2, '0')
|
||||||
|
const day = `${date.getDate()}`.padStart(2, '0')
|
||||||
|
return `${date.getFullYear()}-${month}-${day}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const toShortDate = value => {
|
||||||
|
const dateKey = toDateKey(value)
|
||||||
|
if (!dateKey) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return dateKey.slice(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasValue = value => value !== null && value !== undefined && value !== ''
|
||||||
|
|
||||||
|
const pickValue = (row, keys) => {
|
||||||
|
for (const key of keys) {
|
||||||
|
if (hasValue(row[key])) {
|
||||||
|
return row[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowKeys = Object.keys(row || {})
|
||||||
|
for (const key of keys) {
|
||||||
|
const hitKey = rowKeys.find(rowKey => rowKey === key || rowKey.includes(key))
|
||||||
|
if (hitKey && hasValue(row[hitKey])) {
|
||||||
|
return row[hitKey]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const textValue = value => (hasValue(value) ? String(value).trim() : '')
|
||||||
|
|
||||||
|
const sumRows = (rows, key) => rows.reduce((total, row) => total + toNumber(row[key]), 0)
|
||||||
|
|
||||||
|
const recentDateBuckets = (days = 5) => {
|
||||||
|
const today = new Date()
|
||||||
|
today.setHours(0, 0, 0, 0)
|
||||||
|
|
||||||
|
return Array.from({ length: days }, (_, index) => {
|
||||||
|
const date = new Date(today)
|
||||||
|
date.setDate(today.getDate() - (days - 1 - index))
|
||||||
|
const dateKey = toDateKey(date)
|
||||||
|
return {
|
||||||
|
date: dateKey,
|
||||||
|
label: toShortDate(dateKey),
|
||||||
|
taskCount: 0,
|
||||||
|
workloadCount: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSummaryName = value => {
|
||||||
|
const text = String(value || '')
|
||||||
|
return text.includes('汇总') || text.includes('合计')
|
||||||
|
}
|
||||||
|
|
||||||
|
const createCard = (label, value, extra = {}) => ({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
...extra
|
||||||
|
})
|
||||||
|
|
||||||
|
const normalizeUrgentRows = rows => rows.map(row => ({
|
||||||
|
orderNo: textValue(pickValue(row, ['订单编号', '订单号', '生产订单'])),
|
||||||
|
contractNo: textValue(pickValue(row, ['合同号', '销售订单'])),
|
||||||
|
materialName: textValue(pickValue(row, ['物料名称', '物料描述', '零件名称', 'U_Name', 'ItemName'])),
|
||||||
|
materialCode: textValue(pickValue(row, ['物料编号', '物料编码', '零件编码', 'ItemCode'])),
|
||||||
|
urgentQty: formatNumber(pickValue(row, ['加急数量', '加急总数'])),
|
||||||
|
property: textValue(pickValue(row, ['自制件属性', '属性'])),
|
||||||
|
planFinish: formatDate(pickValue(row, ['计划完成', '计划完成时间']))
|
||||||
|
}))
|
||||||
|
|
||||||
|
const normalizeNotKittedRows = rows => rows.map(row => ({
|
||||||
|
orderNo: textValue(pickValue(row, ['订单编号', '订单号', '生产订单'])),
|
||||||
|
contractNo: textValue(pickValue(row, ['合同号', '销售订单'])),
|
||||||
|
materialName: textValue(pickValue(row, ['物料名称', '物料描述', '零件名称', 'U_Name', 'ItemName'])),
|
||||||
|
materialCode: textValue(pickValue(row, ['物料编码', '物料编号', '零件编码', 'ItemCode'])),
|
||||||
|
planQty: formatNumber(pickValue(row, ['计划数量订单', '计划数量', '工序计划数量'])),
|
||||||
|
processName: textValue(pickValue(row, ['工序名称'])),
|
||||||
|
issue: textValue(pickValue(row, ['未完成说明', '发料状态', '拣配状态']))
|
||||||
|
}))
|
||||||
|
|
||||||
|
const normalizeLateStartRows = rows => rows.map(row => ({
|
||||||
|
orderNo: textValue(pickValue(row, ['订单号', '订单编号', '生产订单'])),
|
||||||
|
contractNo: textValue(pickValue(row, ['合同号', '销售订单'])),
|
||||||
|
partName: textValue(pickValue(row, ['零件名称', '物料名称', '物料描述'])),
|
||||||
|
partCode: textValue(pickValue(row, ['零件编码', '物料编码', '物料编号'])),
|
||||||
|
processName: textValue(pickValue(row, ['工序名称'])),
|
||||||
|
assignee: textValue(pickValue(row, ['指派对象', '负责人', '操作人'])),
|
||||||
|
planStart: formatDate(pickValue(row, ['计划开始', '计划开始时间'])),
|
||||||
|
taskStatus: textValue(pickValue(row, ['任务状态']))
|
||||||
|
}))
|
||||||
|
|
||||||
|
const normalizeMachiningRows = rows => rows.map(row => ({
|
||||||
|
orderNo: textValue(pickValue(row, ['订单号', '订单编号', '生产订单'])),
|
||||||
|
partName: textValue(pickValue(row, ['零件名称', '物料名称', '物料描述'])),
|
||||||
|
partCode: textValue(pickValue(row, ['零件编码', '物料编码', '物料编号'])),
|
||||||
|
assignee: textValue(pickValue(row, ['指派对象', '工位'])),
|
||||||
|
processName: textValue(pickValue(row, ['工序名称'])),
|
||||||
|
assignedQty: formatNumber(pickValue(row, ['分配数', '计划数量'])),
|
||||||
|
finishedQty: formatNumber(pickValue(row, ['完成数量', '合格数'])),
|
||||||
|
machineStatus: textValue(pickValue(row, ['加工状态', '任务状态']))
|
||||||
|
}))
|
||||||
|
|
||||||
|
const normalizeAssemblyRows = rows => rows.map(row => ({
|
||||||
|
operator: textValue(pickValue(row, ['操作人', '负责人'])),
|
||||||
|
orderNo: textValue(pickValue(row, ['订单号', '订单编号', '生产订单'])),
|
||||||
|
contractNo: textValue(pickValue(row, ['合同号', '销售订单'])),
|
||||||
|
partName: textValue(pickValue(row, ['零件名称', '物料名称', '物料描述'])),
|
||||||
|
partCode: textValue(pickValue(row, ['零件编码', '物料编码', '物料编号'])),
|
||||||
|
assignedQty: formatNumber(pickValue(row, ['分配数', '计划数量'])),
|
||||||
|
finishedQty: formatNumber(pickValue(row, ['完成数量'])),
|
||||||
|
materialStatus: textValue(pickValue(row, ['发料状态', '拣配状态']))
|
||||||
|
}))
|
||||||
|
|
||||||
|
const normalizeWarehouseRows = rows => rows.map(row => ({
|
||||||
|
category: textValue(pickValue(row, ['类别', '分类'])),
|
||||||
|
docNo: textValue(pickValue(row, ['单号', '生产订单', '合同号'])),
|
||||||
|
materialName: textValue(pickValue(row, ['物料名称', '物料描述', '零件名称', 'U_Name', 'ItemName'])),
|
||||||
|
qty: textValue(pickValue(row, ['加急数量', '数量', '状态', '加急状态']))
|
||||||
|
}))
|
||||||
|
|
||||||
|
const countUrgentRows = rows => {
|
||||||
|
const urgentOrders = new Set()
|
||||||
|
let fallbackCount = 0
|
||||||
|
|
||||||
|
rows.forEach((row, index) => {
|
||||||
|
const urgentQty = toNumber(pickValue(row, ['加急数量', '加急数']))
|
||||||
|
const urgentText = textValue(pickValue(row, ['加急', '是否加急', '加急状态', '加急标识', '紧急程度']))
|
||||||
|
const normalizedUrgentText = urgentText.toLowerCase()
|
||||||
|
const isUrgent = urgentQty > 0 ||
|
||||||
|
['是', 'y', 'yes', 'true', '1', '加急'].some(flag => normalizedUrgentText === flag) ||
|
||||||
|
(urgentText.includes('加急') && !urgentText.includes('不加急'))
|
||||||
|
|
||||||
|
if (!isUrgent) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderNo = textValue(pickValue(row, ['订单编号', '订单号', '生产订单']))
|
||||||
|
if (orderNo) {
|
||||||
|
urgentOrders.add(orderNo)
|
||||||
|
} else {
|
||||||
|
fallbackCount += 1
|
||||||
|
urgentOrders.add(`__row_${index}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return urgentOrders.size || fallbackCount
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizePlanScheduleTrend = rows => {
|
||||||
|
const buckets = recentDateBuckets(5)
|
||||||
|
const bucketMap = new Map(buckets.map(item => [item.date, item]))
|
||||||
|
const taskOrderSets = new Map(buckets.map(item => [item.date, new Set()]))
|
||||||
|
const workloadOrderSets = new Map(buckets.map(item => [item.date, new Set()]))
|
||||||
|
|
||||||
|
rows.forEach(row => {
|
||||||
|
const orderNo = textValue(pickValue(row, ['订单编号', '订单号', '生产订单']))
|
||||||
|
const syncDate = toDateKey(pickValue(row, ['同步时间', '同步日期']))
|
||||||
|
const modifyDate = toDateKey(pickValue(row, ['修改时间', '修改日期']))
|
||||||
|
const syncBucket = bucketMap.get(syncDate)
|
||||||
|
const modifyBucket = bucketMap.get(modifyDate)
|
||||||
|
|
||||||
|
if (orderNo && syncBucket) {
|
||||||
|
taskOrderSets.get(syncDate).add(orderNo)
|
||||||
|
}
|
||||||
|
if (orderNo && modifyBucket) {
|
||||||
|
workloadOrderSets.get(modifyDate).add(orderNo)
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = toDateKey(pickValue(row, ['日期', '统计日期', '时间', 'date']))
|
||||||
|
const bucket = bucketMap.get(date)
|
||||||
|
if (!bucket) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskCount = pickValue(row, ['taskCount', '任务量', '计划任务量', '同步订单数', '同步订单个数', '计划订单数'])
|
||||||
|
const workloadCount = pickValue(row, ['workloadCount', '工作量', '生产任务量', '修改订单数', '修改订单个数', '工作订单数', '订单编号数'])
|
||||||
|
const metricName = textValue(pickValue(row, ['指标', '类型', '名称']))
|
||||||
|
const metricValue = pickValue(row, ['数量', '个数', '订单数', '订单个数', '值'])
|
||||||
|
|
||||||
|
if (metricName.includes('工作') || metricName.includes('生产任务') || metricName.includes('修改')) {
|
||||||
|
if (hasValue(workloadCount)) {
|
||||||
|
bucket.workloadCount = toNumber(workloadCount)
|
||||||
|
} else if (hasValue(metricValue)) {
|
||||||
|
bucket.workloadCount = toNumber(metricValue)
|
||||||
|
}
|
||||||
|
} else if (metricName.includes('任务') || metricName.includes('计划') || metricName.includes('同步')) {
|
||||||
|
if (hasValue(taskCount)) {
|
||||||
|
bucket.taskCount = toNumber(taskCount)
|
||||||
|
} else if (hasValue(metricValue)) {
|
||||||
|
bucket.taskCount = toNumber(metricValue)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (hasValue(taskCount)) {
|
||||||
|
bucket.taskCount = toNumber(taskCount)
|
||||||
|
}
|
||||||
|
if (hasValue(workloadCount)) {
|
||||||
|
bucket.workloadCount = toNumber(workloadCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
buckets.forEach(bucket => {
|
||||||
|
const taskOrderCount = taskOrderSets.get(bucket.date).size
|
||||||
|
const workloadOrderCount = workloadOrderSets.get(bucket.date).size
|
||||||
|
if (taskOrderCount) {
|
||||||
|
bucket.taskCount = taskOrderCount
|
||||||
|
}
|
||||||
|
if (workloadOrderCount) {
|
||||||
|
bucket.workloadCount = workloadOrderCount
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return buckets
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeOverviewModule = (rows, moduleKey) => {
|
||||||
|
const moduleRows = rows.filter(row => row.module === moduleKey)
|
||||||
|
const barItems = moduleRows
|
||||||
|
.filter(row => row.chart === 'bar')
|
||||||
|
.sort((left, right) => toNumber(left.sort) - toNumber(right.sort))
|
||||||
|
.map(row => ({
|
||||||
|
name: textValue(row.label),
|
||||||
|
value: toNumber(row.value)
|
||||||
|
}))
|
||||||
|
const trend = moduleRows
|
||||||
|
.filter(row => row.chart === 'trend')
|
||||||
|
.sort((left, right) => toNumber(left.sort) - toNumber(right.sort))
|
||||||
|
.map(row => ({
|
||||||
|
date: textValue(row.label),
|
||||||
|
label: toShortDate(row.label),
|
||||||
|
taskCount: toNumber(row.value),
|
||||||
|
workloadCount: toNumber(row.extraValue)
|
||||||
|
}))
|
||||||
|
const rateRow = moduleRows.find(row => row.chart === 'rate') || {}
|
||||||
|
const summary = moduleRows
|
||||||
|
.filter(row => row.chart === 'summary')
|
||||||
|
.reduce((result, row) => {
|
||||||
|
result[textValue(row.series)] = toNumber(row.value)
|
||||||
|
return result
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
return {
|
||||||
|
barItems,
|
||||||
|
trend,
|
||||||
|
trendLoaded: trend.length > 0,
|
||||||
|
rate: toRate(rateRow.value),
|
||||||
|
rateTotal: toNumber(rateRow.extraValue),
|
||||||
|
overviewSummary: summary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergeCards = (moduleState, cards, summary = {}) => {
|
||||||
|
moduleState.status = 'ready'
|
||||||
|
moduleState.message = ''
|
||||||
|
moduleState.cards = cards
|
||||||
|
moduleState.summary = summary
|
||||||
|
}
|
||||||
|
|
||||||
|
const markModule = (moduleState, status, message = '') => {
|
||||||
|
moduleState.status = status
|
||||||
|
moduleState.message = message
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDashboardData() {
|
||||||
|
const CreateData = inject('CreateData', null)
|
||||||
|
const ExecDatabase = inject('ExecDatabase', null)
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
loading: false,
|
||||||
|
refreshing: false,
|
||||||
|
lastUpdated: '',
|
||||||
|
errors: [],
|
||||||
|
modules: {
|
||||||
|
sales: {
|
||||||
|
...emptyModule('empty'),
|
||||||
|
cards: [
|
||||||
|
createCard('准交率', '--'),
|
||||||
|
createCard('今日新增', '--'),
|
||||||
|
createCard('售中', '--')
|
||||||
|
],
|
||||||
|
message: '暂无数据'
|
||||||
|
},
|
||||||
|
research: {
|
||||||
|
...emptyModule('empty'),
|
||||||
|
cards: [
|
||||||
|
createCard('执行率', '--'),
|
||||||
|
createCard('立项数', '--'),
|
||||||
|
createCard('研发任务', '--')
|
||||||
|
],
|
||||||
|
message: '暂无数据'
|
||||||
|
},
|
||||||
|
plan: emptyModule(),
|
||||||
|
purchase: {
|
||||||
|
...emptyModule('pending'),
|
||||||
|
cards: [
|
||||||
|
createCard('准供率', '--'),
|
||||||
|
createCard('外购', '--'),
|
||||||
|
createCard('外协', '--'),
|
||||||
|
createCard('采购执行', '--')
|
||||||
|
],
|
||||||
|
message: 'SAP 采购指标待接口接入'
|
||||||
|
},
|
||||||
|
machining: emptyModule(),
|
||||||
|
assembly: emptyModule(),
|
||||||
|
panorama: emptyModule(),
|
||||||
|
quality: emptyModule(),
|
||||||
|
warehouse: emptyModule()
|
||||||
|
},
|
||||||
|
tables: {
|
||||||
|
urgent: [],
|
||||||
|
notKitted: [],
|
||||||
|
lateStart: [],
|
||||||
|
machiningWip: [],
|
||||||
|
assemblyWip: [],
|
||||||
|
warehouse: []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const queryProcedure = async (name, params = []) => {
|
||||||
|
if (!CreateData || !ExecDatabase) {
|
||||||
|
throw new Error('公共数据请求方法未注入')
|
||||||
|
}
|
||||||
|
const payload = CreateData('11', name, params)
|
||||||
|
const response = await ExecDatabase(payload)
|
||||||
|
return getRows(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeQuery = async (name, params = []) => {
|
||||||
|
try {
|
||||||
|
return await queryProcedure(name, params)
|
||||||
|
} catch (error) {
|
||||||
|
state.errors.push({
|
||||||
|
procedure: name,
|
||||||
|
message: error?.message || '请求失败'
|
||||||
|
})
|
||||||
|
return emptyRows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyDepartmentOverview = rows => {
|
||||||
|
if (!rows.length) {
|
||||||
|
;['sales', 'research', 'purchase'].forEach(key => {
|
||||||
|
markModule(state.modules[key], 'empty', '暂无数据')
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const sales = normalizeOverviewModule(rows, 'sales')
|
||||||
|
const research = normalizeOverviewModule(rows, 'research')
|
||||||
|
const purchase = normalizeOverviewModule(rows, 'purchase')
|
||||||
|
|
||||||
|
mergeCards(state.modules.sales, [
|
||||||
|
createCard('准交率', formatPercent(sales.rate), { valueType: 'rate' }),
|
||||||
|
createCard('在途订单', formatNumber(sales.barItems.reduce((total, item) => total + item.value, 0))),
|
||||||
|
createCard('销售滞留', formatNumber(sales.overviewSummary['滞留']))
|
||||||
|
], sales)
|
||||||
|
|
||||||
|
mergeCards(state.modules.research, [
|
||||||
|
createCard('按期完成率', formatPercent(research.rate), { valueType: 'rate' }),
|
||||||
|
createCard('未完成任务', formatNumber(research.barItems.reduce((total, item) => total + item.value, 0))),
|
||||||
|
createCard('研发滞留', formatNumber(research.overviewSummary['滞留']))
|
||||||
|
], research)
|
||||||
|
|
||||||
|
mergeCards(state.modules.purchase, [
|
||||||
|
createCard('外购及时率', formatPercent(purchase.rate), { valueType: 'rate' }),
|
||||||
|
createCard('采购任务', formatNumber(purchase.barItems.reduce((total, item) => total + item.value, 0))),
|
||||||
|
createCard('采购滞留', formatNumber(purchase.overviewSummary['滞留']))
|
||||||
|
], purchase)
|
||||||
|
|
||||||
|
;['machining', 'assembly', 'quality', 'warehouse'].forEach(key => {
|
||||||
|
Object.assign(state.modules[key].summary, normalizeOverviewModule(rows, key))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchPlan = async () => {
|
||||||
|
const [rateRows, noProcessRows, scheduleTrendRows, urgentRows, lateCountRows, lateRows, notKittedRows] = await Promise.all([
|
||||||
|
safeQuery(PROCEDURES.planRate),
|
||||||
|
safeQuery(PROCEDURES.planNoProcess),
|
||||||
|
safeQuery(PROCEDURES.planScheduleTrend),
|
||||||
|
safeQuery(PROCEDURES.urgentTasks),
|
||||||
|
safeQuery(PROCEDURES.lateStartCount),
|
||||||
|
safeQuery(PROCEDURES.lateStartRows),
|
||||||
|
safeQuery(PROCEDURES.notKittedRows)
|
||||||
|
])
|
||||||
|
|
||||||
|
const rate = firstRow(rateRows)
|
||||||
|
const noProcess = firstRow(noProcessRows)
|
||||||
|
const lateCount = firstRow(lateCountRows)
|
||||||
|
const totalTasks = toNumber(rate['总任务数'])
|
||||||
|
const kittedTasks = toNumber(rate['齐套任务数'])
|
||||||
|
const unKittedTasks = toNumber(rate['未齐套任务数'])
|
||||||
|
const kitRate = toRate(rate['齐套率'])
|
||||||
|
const urgentTotal = toNumber(firstRow(urgentRows)['加急订单总数']) || urgentRows.length
|
||||||
|
const urgentSelf = urgentRows.filter(row => String(row['自制件属性'] || '').includes('自制')).length
|
||||||
|
const urgentPurchase = urgentRows.filter(row => String(row['自制件属性'] || '').includes('外购')).length
|
||||||
|
const lateStartTotal = toNumber(lateCount['未及时开工总数']) || sumRows(lateRows, '未及时开工总数')
|
||||||
|
const pendingSchedule = toNumber(noProcess['未排产任务数'])
|
||||||
|
const assemblyPending = toNumber(noProcess['装配未排产数'])
|
||||||
|
const machiningPending = Math.max(pendingSchedule - assemblyPending, 0)
|
||||||
|
const pendingUrgent = toNumber(pickValue(noProcess, [
|
||||||
|
'未排产加急数',
|
||||||
|
'未排产加急任务数',
|
||||||
|
'未排产加急订单数',
|
||||||
|
'未排产加急件数',
|
||||||
|
'加急未排产数',
|
||||||
|
'加急未排产任务数',
|
||||||
|
'加急数',
|
||||||
|
'加急数量',
|
||||||
|
'加急任务数',
|
||||||
|
'加急订单数'
|
||||||
|
])) || countUrgentRows(noProcessRows)
|
||||||
|
const scheduleTrend = normalizePlanScheduleTrend(scheduleTrendRows)
|
||||||
|
const scheduleTrendLoaded = scheduleTrendRows.length > 0
|
||||||
|
|
||||||
|
state.tables.urgent = normalizeUrgentRows(urgentRows)
|
||||||
|
state.tables.notKitted = normalizeNotKittedRows(notKittedRows)
|
||||||
|
state.tables.lateStart = normalizeLateStartRows(lateRows)
|
||||||
|
|
||||||
|
mergeCards(state.modules.plan, [
|
||||||
|
createCard('按期齐套率', formatPercent(kitRate), { valueType: 'rate' }),
|
||||||
|
createCard('未齐套', formatNumber(unKittedTasks), { subValue: `应齐套 ${formatNumber(totalTasks)}` }),
|
||||||
|
createCard('待排产', formatNumber(noProcess['未排产任务数'])),
|
||||||
|
createCard('无生产工艺', formatNumber(noProcess['无生产工艺计划数'])),
|
||||||
|
createCard('加急件', formatNumber(urgentTotal), { subValue: `自制 ${urgentSelf} / 外购 ${urgentPurchase}` }),
|
||||||
|
createCard('未及时开工', formatNumber(lateStartTotal))
|
||||||
|
], {
|
||||||
|
kitRate,
|
||||||
|
totalTasks,
|
||||||
|
kittedTasks,
|
||||||
|
unKittedTasks,
|
||||||
|
feedKitted: toNumber(rate['发料齐套任务数']),
|
||||||
|
feedMissing: toNumber(rate['发料缺件任务数']),
|
||||||
|
notIssued: toNumber(rate['未发料任务数']),
|
||||||
|
pendingSchedule,
|
||||||
|
machiningPending,
|
||||||
|
assemblyPending,
|
||||||
|
pendingUrgent,
|
||||||
|
noProcessPlan: toNumber(noProcess['无生产工艺计划数']),
|
||||||
|
urgentTotal,
|
||||||
|
urgentSelf,
|
||||||
|
urgentPurchase,
|
||||||
|
lateStartTotal,
|
||||||
|
scheduleTrend,
|
||||||
|
scheduleTrendLoaded
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchMachining = async () => {
|
||||||
|
const [rateRows, deviceRows, wipRows] = await Promise.all([
|
||||||
|
safeQuery(PROCEDURES.machiningRate),
|
||||||
|
safeQuery(PROCEDURES.machiningDevice),
|
||||||
|
safeQuery(PROCEDURES.machiningWip)
|
||||||
|
])
|
||||||
|
const rate = firstRow(rateRows)
|
||||||
|
const device = firstRow(deviceRows)
|
||||||
|
const executeRate = toRate(rate['计划执行率_百分数'])
|
||||||
|
const shouldStart = toNumber(rate['应开工任务数'])
|
||||||
|
const onTimeStart = toNumber(rate['按期开始任务数'])
|
||||||
|
const wipTotal = toNumber(firstRow(wipRows)['总数']) || wipRows.length
|
||||||
|
|
||||||
|
state.tables.machiningWip = normalizeMachiningRows(wipRows)
|
||||||
|
|
||||||
|
mergeCards(state.modules.machining, [
|
||||||
|
createCard('执行率', formatPercent(executeRate), { valueType: 'rate' }),
|
||||||
|
createCard('及时开工', formatNumber(onTimeStart), { subValue: `应开工 ${formatNumber(shouldStart)}` }),
|
||||||
|
createCard('在制任务', formatNumber(wipTotal)),
|
||||||
|
createCard('设备开动', formatNumber(device['运行数量']), { subValue: `设备总数 ${formatNumber(device['总数'])}` })
|
||||||
|
], {
|
||||||
|
executeRate,
|
||||||
|
shouldStart,
|
||||||
|
onTimeStart,
|
||||||
|
wipTotal,
|
||||||
|
device: {
|
||||||
|
off: toNumber(device['关机数量']),
|
||||||
|
standby: toNumber(device['待机数量']),
|
||||||
|
running: toNumber(device['运行数量']),
|
||||||
|
alarm: toNumber(device['报警数量']),
|
||||||
|
total: toNumber(device['总数'])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchAssembly = async () => {
|
||||||
|
const [rateRows, executableRows, wipRows, abnormalRows] = await Promise.all([
|
||||||
|
safeQuery(PROCEDURES.assemblyRate),
|
||||||
|
safeQuery(PROCEDURES.assemblyExecutable),
|
||||||
|
safeQuery(PROCEDURES.assemblyWip),
|
||||||
|
safeQuery(PROCEDURES.assemblyAbnormal)
|
||||||
|
])
|
||||||
|
const rate = firstRow(rateRows)
|
||||||
|
const executable = firstRow(executableRows)
|
||||||
|
const abnormal = firstRow(abnormalRows)
|
||||||
|
const executableTotal = toNumber(executable['阀类可执行任务数']) + toNumber(executable['系统类可执行任务数'])
|
||||||
|
const wipTotal = wipRows.length
|
||||||
|
|
||||||
|
state.tables.assemblyWip = normalizeAssemblyRows(wipRows)
|
||||||
|
|
||||||
|
mergeCards(state.modules.assembly, [
|
||||||
|
createCard('装配合格率', formatPercent(rate['总合格率']), { valueType: 'rate' }),
|
||||||
|
createCard('可执行任务', formatNumber(executableTotal), {
|
||||||
|
subValue: `阀类 ${formatNumber(executable['阀类可执行任务数'])} / 系统 ${formatNumber(executable['系统类可执行任务数'])}`
|
||||||
|
}),
|
||||||
|
createCard('及时送检', formatNumber(executable['及时送检数'])),
|
||||||
|
createCard('在制任务', formatNumber(wipTotal)),
|
||||||
|
createCard('异常未关闭', formatNumber(abnormal['装配异常未关闭总数']))
|
||||||
|
], {
|
||||||
|
qualityRate: toRate(rate['总合格率']),
|
||||||
|
inspectionQty: toNumber(rate['总检验数量']),
|
||||||
|
passQty: toNumber(rate['总合格数']),
|
||||||
|
badQty: toNumber(rate['总不合格数']),
|
||||||
|
executableTotal,
|
||||||
|
valveExecutable: toNumber(executable['阀类可执行任务数']),
|
||||||
|
systemExecutable: toNumber(executable['系统类可执行任务数']),
|
||||||
|
electricTasks: toNumber(executable['系统类电气任务数']),
|
||||||
|
mechanicalTasks: toNumber(executable['系统类机械任务数']),
|
||||||
|
timelyInspection: toNumber(executable['及时送检数']),
|
||||||
|
wipTotal,
|
||||||
|
abnormalTotal: toNumber(abnormal['装配异常未关闭总数'])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchQuality = async () => {
|
||||||
|
const [timelyRows, unfinishedRows, testTaskRows] = await Promise.all([
|
||||||
|
safeQuery(PROCEDURES.qualityTimely, qualityParams),
|
||||||
|
safeQuery(PROCEDURES.qualityUnfinished),
|
||||||
|
safeQuery(PROCEDURES.qualityTestTasks, qualityParams)
|
||||||
|
])
|
||||||
|
const timely = firstRow(timelyRows)
|
||||||
|
const detailRows = unfinishedRows.filter(row => !isSummaryName(row['质检类型']))
|
||||||
|
const unfinishedTotal = sumRows(detailRows, '未完成数量')
|
||||||
|
const urgentTotal = sumRows(detailRows, '加急数量')
|
||||||
|
|
||||||
|
mergeCards(state.modules.quality, [
|
||||||
|
createCard('质检及时率', formatPercent(timely['质检及时率']), { valueType: 'rate' }),
|
||||||
|
createCard('报检任务数', formatNumber(timely['报检任务数'])),
|
||||||
|
createCard('超期未检验', formatNumber(timely['超期未检验数'])),
|
||||||
|
createCard('未完成任务', formatNumber(unfinishedTotal), { subValue: `加急 ${formatNumber(urgentTotal)}` })
|
||||||
|
], {
|
||||||
|
timelyRate: toRate(timely['质检及时率']),
|
||||||
|
inspectionTasks: toNumber(timely['报检任务数']),
|
||||||
|
overdueTasks: toNumber(timely['超期未检验数']),
|
||||||
|
unfinishedTotal,
|
||||||
|
urgentTotal,
|
||||||
|
systemTasks: testTaskRows.filter(row => textValue(row['任务类型']).includes('系统')).length,
|
||||||
|
componentTasks: testTaskRows.filter(row => !textValue(row['任务类型']).includes('系统')).length,
|
||||||
|
unfinishedTypes: detailRows.map(row => ({
|
||||||
|
name: row['质检类型'],
|
||||||
|
value: toNumber(row['未完成数量']),
|
||||||
|
urgent: toNumber(row['加急数量'])
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchWarehouse = async () => {
|
||||||
|
const [summaryRows, urgentInboundRows, urgentPickingRows, pickingRows] = await Promise.all([
|
||||||
|
safeQuery(PROCEDURES.warehouseSummary),
|
||||||
|
safeQuery(PROCEDURES.warehouseUrgentInbound),
|
||||||
|
safeQuery(PROCEDURES.warehouseUrgentPicking),
|
||||||
|
safeQuery(PROCEDURES.warehousePicking)
|
||||||
|
])
|
||||||
|
const totalTasks = sumRows(summaryRows, '任务数')
|
||||||
|
const totalQty = sumRows(summaryRows, '数量')
|
||||||
|
const urgentQty = sumRows(summaryRows, '加急数量')
|
||||||
|
const byStage = summaryRows.map(row => ({
|
||||||
|
name: `${row['环节'] || ''}${row['分类'] ? `-${row['分类']}` : ''}`,
|
||||||
|
task: toNumber(row['任务数']),
|
||||||
|
qty: toNumber(row['数量']),
|
||||||
|
urgent: toNumber(row['加急数量'])
|
||||||
|
}))
|
||||||
|
|
||||||
|
state.tables.warehouse = [
|
||||||
|
...normalizeWarehouseRows(urgentInboundRows),
|
||||||
|
...normalizeWarehouseRows(urgentPickingRows),
|
||||||
|
...normalizeWarehouseRows(pickingRows)
|
||||||
|
]
|
||||||
|
|
||||||
|
mergeCards(state.modules.warehouse, [
|
||||||
|
createCard('仓储任务', formatNumber(totalTasks)),
|
||||||
|
createCard('任务数量', formatNumber(totalQty)),
|
||||||
|
createCard('加急数量', formatNumber(urgentQty)),
|
||||||
|
createCard('拣配明细', formatNumber(pickingRows.length))
|
||||||
|
], {
|
||||||
|
totalTasks,
|
||||||
|
totalQty,
|
||||||
|
urgentQty,
|
||||||
|
byStage,
|
||||||
|
urgentInboundCount: urgentInboundRows.length,
|
||||||
|
urgentPickingCount: urgentPickingRows.length,
|
||||||
|
pickingCount: pickingRows.length
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePanorama = () => {
|
||||||
|
const machining = state.modules.machining.summary
|
||||||
|
const assembly = state.modules.assembly.summary
|
||||||
|
const quality = state.modules.quality.summary
|
||||||
|
const warehouse = state.modules.warehouse.summary
|
||||||
|
mergeCards(state.modules.panorama, [
|
||||||
|
createCard('机加在制', formatNumber(machining.wipTotal)),
|
||||||
|
createCard('装配在制', formatNumber(assembly.wipTotal)),
|
||||||
|
createCard('装配可执行', formatNumber(assembly.executableTotal)),
|
||||||
|
createCard('测试/质检', formatNumber(quality.unfinishedTotal)),
|
||||||
|
createCard('仓储流转', formatNumber(warehouse.totalTasks))
|
||||||
|
], {
|
||||||
|
stations: [
|
||||||
|
{ name: '机加在制', value: toNumber(machining.wipTotal), status: '加工中' },
|
||||||
|
{ name: '装配在制', value: toNumber(assembly.wipTotal), status: '加工中' },
|
||||||
|
{ name: '装配可执行', value: toNumber(assembly.executableTotal), status: '待执行' },
|
||||||
|
{ name: '测试/质检', value: toNumber(quality.unfinishedTotal), status: '检验' },
|
||||||
|
{ name: '仓储流转', value: toNumber(warehouse.totalTasks), status: '流转' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadDashboardData = async (options = {}) => {
|
||||||
|
const silent = options.silent === true
|
||||||
|
state.errors = []
|
||||||
|
state.loading = !silent
|
||||||
|
state.refreshing = silent
|
||||||
|
|
||||||
|
;['sales', 'research', 'plan', 'purchase', 'machining', 'assembly', 'quality', 'warehouse'].forEach(key => {
|
||||||
|
markModule(state.modules[key], 'loading')
|
||||||
|
})
|
||||||
|
markModule(state.modules.panorama, 'loading')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const overviewPromise = safeQuery(PROCEDURES.departmentOverview)
|
||||||
|
await Promise.all([
|
||||||
|
fetchPlan(),
|
||||||
|
fetchMachining(),
|
||||||
|
fetchAssembly(),
|
||||||
|
fetchQuality(),
|
||||||
|
fetchWarehouse()
|
||||||
|
])
|
||||||
|
applyDepartmentOverview(await overviewPromise)
|
||||||
|
updatePanorama()
|
||||||
|
state.lastUpdated = new Date().toLocaleString('zh-CN', { hour12: false })
|
||||||
|
} finally {
|
||||||
|
state.loading = false
|
||||||
|
state.refreshing = false
|
||||||
|
;['sales', 'research', 'plan', 'purchase', 'machining', 'assembly', 'quality', 'warehouse', 'panorama'].forEach(key => {
|
||||||
|
const moduleState = state.modules[key]
|
||||||
|
if (moduleState.status === 'loading') {
|
||||||
|
markModule(moduleState, 'error', '数据加载失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
loadDashboardData
|
||||||
|
}
|
||||||
|
}
|
||||||
45
生产任务看板/src/views/plan-kanban/composables/useEchart.js
Normal file
45
生产任务看板/src/views/plan-kanban/composables/useEchart.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import * as echarts from 'echarts'
|
||||||
|
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
export function useEchart(optionRef) {
|
||||||
|
const elRef = ref(null)
|
||||||
|
let chart = null
|
||||||
|
|
||||||
|
const render = async () => {
|
||||||
|
await nextTick()
|
||||||
|
if (!elRef.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!chart) {
|
||||||
|
chart = echarts.init(elRef.value)
|
||||||
|
}
|
||||||
|
chart.setOption(optionRef.value || {}, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resize = () => {
|
||||||
|
if (chart) {
|
||||||
|
chart.resize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
render()
|
||||||
|
window.addEventListener('resize', resize)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(optionRef, render, { deep: true })
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', resize)
|
||||||
|
if (chart) {
|
||||||
|
chart.dispose()
|
||||||
|
chart = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
elRef,
|
||||||
|
resize,
|
||||||
|
render
|
||||||
|
}
|
||||||
|
}
|
||||||
148
生产任务看板/src/views/plan-kanban/config/modules.js
Normal file
148
生产任务看板/src/views/plan-kanban/config/modules.js
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
export const BLUE_COLORS = {
|
||||||
|
background: '#07001f',
|
||||||
|
panel: '#071f48',
|
||||||
|
panelDeep: '#061338',
|
||||||
|
border: '#2bd8ff',
|
||||||
|
title: '#8cd3ff',
|
||||||
|
text: '#eaf7ff',
|
||||||
|
muted: '#78a9ce',
|
||||||
|
cyan: '#1ce6ff',
|
||||||
|
blue: '#168dff',
|
||||||
|
lightBlue: '#6fd8ff',
|
||||||
|
deepBlue: '#0b4fba',
|
||||||
|
warning: '#ffb84d',
|
||||||
|
danger: '#ff5c7a'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MODULES = [
|
||||||
|
{
|
||||||
|
key: 'sales',
|
||||||
|
title: '销售',
|
||||||
|
subtitle: '准交率 / 今日新增 / 售中订单',
|
||||||
|
emptyText: '暂无数据'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'research',
|
||||||
|
title: '研发',
|
||||||
|
subtitle: '执行率 / 立项 / 研发任务',
|
||||||
|
emptyText: '暂无数据'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'plan',
|
||||||
|
title: '计划',
|
||||||
|
subtitle: '按期齐套 / 生产计划 / 加急件'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'purchase',
|
||||||
|
title: '采购',
|
||||||
|
subtitle: '准供率 / 外购 / 外协',
|
||||||
|
emptyText: '待接口接入'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'machining',
|
||||||
|
title: '机加车间',
|
||||||
|
subtitle: '执行率 / 开工 / 在制 / 设备'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'assembly',
|
||||||
|
title: '装配车间',
|
||||||
|
subtitle: '合格率 / 可执行 / 在制'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'panorama',
|
||||||
|
title: '车间全景',
|
||||||
|
subtitle: '机加 / 机械 / 电气 / 测试在产'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'quality',
|
||||||
|
title: '质量',
|
||||||
|
subtitle: '质检及时率 / 未完成任务'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'warehouse',
|
||||||
|
title: '仓储',
|
||||||
|
subtitle: '入库 / 拣配 / 发货'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const PROCEDURES = {
|
||||||
|
departmentOverview: 'MES_Kanban_DepartmentOverview',
|
||||||
|
planRate: '生产任务综合看板_按期齐套率',
|
||||||
|
planNoProcess: '生产任务综合看板_未排产无工艺查询',
|
||||||
|
planScheduleTrend: 'MES_Kanban_PlanScheduleTrend',
|
||||||
|
urgentTasks: '生产任务综合看板_加急任务查询',
|
||||||
|
lateStartCount: '生产任务综合看板_未及时开工查询_Count',
|
||||||
|
lateStartRows: '生产任务综合看板_未及时开工查询',
|
||||||
|
notKittedRows: '生产任务综合看板_未按期齐套任务',
|
||||||
|
machiningRate: '机加设备生产运营看板_计划执行率',
|
||||||
|
machiningDevice: '机加设备生产运营看板_设备运行状态查询',
|
||||||
|
machiningWip: '机加设备生产运营看板_机加在制查询',
|
||||||
|
assemblyRate: '装配中心任务看板_装配合格率',
|
||||||
|
assemblyExecutable: '装配中心任务看板_可执行任务数',
|
||||||
|
assemblyWip: '装配中心任务看板_装配在制任务查询',
|
||||||
|
assemblyAbnormal: '装配中心任务看板_异常未关闭总数',
|
||||||
|
qualityTimely: '质量管理_测试装配任务_质检及时率',
|
||||||
|
qualityUnfinished: '质量任务看板_未完成统计',
|
||||||
|
qualityTestTasks: '质检测试看板_测试装配任务_查询',
|
||||||
|
warehouseSummary: '仓库大屏_汇总查询',
|
||||||
|
warehouseUrgentInbound: '仓库大屏_加急入库列表',
|
||||||
|
warehouseUrgentPicking: '仓库大屏_加急拣配列表',
|
||||||
|
warehousePicking: '仓库大屏_拣配任务列表'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TABLE_COLUMNS = {
|
||||||
|
urgent: [
|
||||||
|
{ key: 'orderNo', label: '生产订单', width: '12%' },
|
||||||
|
{ key: 'contractNo', label: '销售订单', width: '13%' },
|
||||||
|
{ key: 'materialName', label: '物料名称', width: '22%' },
|
||||||
|
{ key: 'materialCode', label: '物料编码', width: '22%' },
|
||||||
|
{ key: 'urgentQty', label: '加急数量', width: '10%' },
|
||||||
|
{ key: 'property', label: '属性', width: '8%' },
|
||||||
|
{ key: 'planFinish', label: '计划完成', width: '13%' }
|
||||||
|
],
|
||||||
|
notKitted: [
|
||||||
|
{ key: 'orderNo', label: '生产订单', width: '12%' },
|
||||||
|
{ key: 'contractNo', label: '销售订单', width: '13%' },
|
||||||
|
{ key: 'materialName', label: '物料名称', width: '20%' },
|
||||||
|
{ key: 'materialCode', label: '物料编码', width: '21%' },
|
||||||
|
{ key: 'planQty', label: '计划数量', width: '10%' },
|
||||||
|
{ key: 'processName', label: '工序', width: '10%' },
|
||||||
|
{ key: 'issue', label: '未完成说明', width: '14%' }
|
||||||
|
],
|
||||||
|
lateStart: [
|
||||||
|
{ key: 'orderNo', label: '生产订单', width: '11%' },
|
||||||
|
{ key: 'contractNo', label: '销售订单', width: '12%' },
|
||||||
|
{ key: 'partName', label: '零件名称', width: '18%' },
|
||||||
|
{ key: 'partCode', label: '零件编码', width: '20%' },
|
||||||
|
{ key: 'processName', label: '工序', width: '10%' },
|
||||||
|
{ key: 'assignee', label: '负责人', width: '10%' },
|
||||||
|
{ key: 'planStart', label: '计划开始', width: '11%' },
|
||||||
|
{ key: 'taskStatus', label: '任务状态', width: '8%' }
|
||||||
|
],
|
||||||
|
machiningWip: [
|
||||||
|
{ key: 'orderNo', label: '生产订单', width: '11%' },
|
||||||
|
{ key: 'partName', label: '零件名称', width: '19%' },
|
||||||
|
{ key: 'partCode', label: '零件编码', width: '21%' },
|
||||||
|
{ key: 'assignee', label: '工位', width: '9%' },
|
||||||
|
{ key: 'processName', label: '工序', width: '10%' },
|
||||||
|
{ key: 'assignedQty', label: '分配数', width: '9%' },
|
||||||
|
{ key: 'finishedQty', label: '完成数', width: '9%' },
|
||||||
|
{ key: 'machineStatus', label: '加工状态', width: '12%' }
|
||||||
|
],
|
||||||
|
assemblyWip: [
|
||||||
|
{ key: 'operator', label: '负责人', width: '10%' },
|
||||||
|
{ key: 'orderNo', label: '生产订单', width: '11%' },
|
||||||
|
{ key: 'contractNo', label: '销售订单', width: '12%' },
|
||||||
|
{ key: 'partName', label: '零件名称', width: '18%' },
|
||||||
|
{ key: 'partCode', label: '零件编码', width: '21%' },
|
||||||
|
{ key: 'assignedQty', label: '分配数', width: '9%' },
|
||||||
|
{ key: 'finishedQty', label: '完成数', width: '9%' },
|
||||||
|
{ key: 'materialStatus', label: '发料状态', width: '10%' }
|
||||||
|
],
|
||||||
|
warehouse: [
|
||||||
|
{ key: 'category', label: '类别', width: '12%' },
|
||||||
|
{ key: 'docNo', label: '单号', width: '24%' },
|
||||||
|
{ key: 'materialName', label: '物料名称', width: '44%' },
|
||||||
|
{ key: 'qty', label: '数量/状态', width: '20%' }
|
||||||
|
]
|
||||||
|
}
|
||||||
1368
生产任务看板/src/views/plan-kanban/index.vue
Normal file
1368
生产任务看板/src/views/plan-kanban/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user