diff --git a/MES_Manage_View_V20/fix_机加设备生产运营看板_加急任务查询.sql b/MES_Manage_View_V20/fix_机加设备生产运营看板_加急任务查询.sql new file mode 100644 index 0000000..55ac683 --- /dev/null +++ b/MES_Manage_View_V20/fix_机加设备生产运营看板_加急任务查询.sql @@ -0,0 +1,69 @@ +USE [YL_MESDB] +GO + +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO + +ALTER PROCEDURE [dbo].[机加设备生产运营看板_加急任务查询] +AS +BEGIN + SET NOCOUNT ON; + + SELECT + [订单编号] + ,[合同号] + ,[订单类型] + ,[订单状态] + ,[物料编号] + ,[物料描述] + ,[计划数量] + ,[计量单位] + ,[下达日期] + ,[计划开始时间] + ,[计划完成] + ,[自制件属性] + ,加急总数 as 加急数量 + ,工序名称 + ,指派对象 + ,外协分组 + ,REPLACE(STUFF(( + SELECT + ' -> ' + ISNULL(d2.工序名称, '') + '(' + ISNULL(d2.指派对象, '') + ')' + + '|' + ISNULL(CAST(d2.加工状态 AS VARCHAR(10)), '0') + + '|' + ISNULL(CAST(CAST(ISNULL(( + SELECT SUM(合格数) + FROM [dbo].[YL_质量检验_质检记录] + WHERE TaskAID = d2.TaskAID + ), 0) AS INT) AS VARCHAR(10)), '0') + + '|' + ISNULL(CONVERT(VARCHAR(2), MONTH(d2.计划开始时间)) + '月' + CONVERT(VARCHAR(2), DAY(d2.计划开始时间)) + '日', '') + + '|' + ISNULL(CAST(d2.订单行号 AS VARCHAR(10)), '') + + '|' + ISNULL(CAST(d2.外协分组 AS NVARCHAR(50)), '') + + '|' + ISNULL(CAST(CAST(ISNULL(( + SELECT SUM(操作数值) + FROM [dbo].[YL_加工中心_操作记录表] + WHERE TaskAID = d2.TaskAID + AND 操作类别 = '收料' + ), 0) AS INT) AS VARCHAR(10)), '0') + + '|' + ISNULL(CAST(CAST(ISNULL(( + SELECT SUM(不合格数) + FROM [dbo].[YL_质量检验_质检记录] + WHERE TaskAID = d2.TaskAID + ), 0) AS INT) AS VARCHAR(10)), '0') + FROM [dbo].[View_生产订单_MES] d2 + WHERE d2.订单编号 = v.订单编号 + ORDER BY d2.订单行号 + FOR XML PATH('') + ), 1, 6, ''), '>', '>') AS 工序进度详情 + ,(SELECT COUNT(DISTINCT 订单编号) FROM [YL_MESDB].[dbo].[View_生产订单_MES] WHERE 加急总数 > 0 AND 指派对象 NOT LIKE '%装配%') AS [加急订单总数] +FROM [YL_MESDB].[dbo].[View_生产订单_MES] v +WHERE 加急总数 > 0 +AND 指派对象 NOT LIKE '%装配%' +AND 订单行号 = ( + SELECT MIN(订单行号) + FROM [YL_MESDB].[dbo].[View_生产订单_MES] + WHERE 订单编号 = v.订单编号 +) + +END diff --git a/MES_Manage_View_V20/src/views/AnomalousManagement/UrgentTask/index.vue b/MES_Manage_View_V20/src/views/AnomalousManagement/UrgentTask/index.vue new file mode 100644 index 0000000..92d7783 --- /dev/null +++ b/MES_Manage_View_V20/src/views/AnomalousManagement/UrgentTask/index.vue @@ -0,0 +1,861 @@ +、 + + + + diff --git a/MES_Manage_View_V20/src/views/PlanManagement/SelfMakePlandone/index.vue b/MES_Manage_View_V20/src/views/PlanManagement/SelfMakePlandone/index.vue index a4315b1..c61f1df 100644 --- a/MES_Manage_View_V20/src/views/PlanManagement/SelfMakePlandone/index.vue +++ b/MES_Manage_View_V20/src/views/PlanManagement/SelfMakePlandone/index.vue @@ -24,7 +24,7 @@ 要求完工日期: 计划开始: 计划完成: +
+ +
+ 工位排产 + 生产订单: + + 销售订单: + + 物料编码: + + 物料名称: + + 工序名称: + +
+
+ 要求完工日期: + + 计划开始: + + 计划完成: + + 查询 +
+ + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
工位 / 日期标准工时生产订单计划号销售订单物料编码物料名称计划数要求完工日期工序名称指派对象计划开始计划完成最晚开始二次派工优先级未完成说明派工特殊备注外协分组占用情况最后编辑人最后编辑时间操作
+
+
+
+ + + + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + diff --git a/MES_Manage_View_V20/src/views/ProductionManagement/ProductionPlanTrack/index.vue b/MES_Manage_View_V20/src/views/ProductionManagement/ProductionPlanTrack/index.vue index 532de55..2822558 100644 --- a/MES_Manage_View_V20/src/views/ProductionManagement/ProductionPlanTrack/index.vue +++ b/MES_Manage_View_V20/src/views/ProductionManagement/ProductionPlanTrack/index.vue @@ -148,7 +148,11 @@ - + + + @@ -284,6 +288,142 @@ export default { if (!date) return '' return date.split(' ').length > 1 ? date.split(' ')[0] : date }, + toProcessNumber(value) { + const num = Number(value) + return Number.isFinite(num) ? num : 0 + }, + escapeProcessHtml(value) { + return String(value == null ? '' : value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + }, + isOutsourceProcess(process) { + return process.指派对象 === '外协' || + String(process.工序名称 || '').includes('(外协)') || + String(process.工序名称 || '').includes('(外协)') + }, + getProcessStatusNum(status) { + const statusNum = parseInt(status) + if (Number.isFinite(statusNum)) { + return statusNum + } + + const statusMap = { + 辅助开始: 2, + 辅助结束: 3, + 开始加工: 4, + 暂停: 5, + 报工: 6, + 装配加工: 4 + } + return statusMap[status] || 0 + }, + isBlueProcess(process) { + const statusNum = this.getProcessStatusNum(process.加工状态) + const countNum = parseInt(process.收检合格数) + return statusNum === 6 || statusNum === 3 || countNum > 0 + }, + isGreenProcess(process) { + return this.isOutsourceProcess(process) && + this.toProcessNumber(process.收料数量) > this.toProcessNumber(process.收检合格数) + this.toProcessNumber(process.收检不合格数) + }, + getProcessBaseClass(process) { + if (this.isGreenProcess(process)) { + return 'process-route-green' + } + + if (this.isBlueProcess(process)) { + return 'process-route-blue' + } + + const statusNum = this.getProcessStatusNum(process.加工状态) + if (statusNum === 2 || statusNum === 4) { + return 'process-route-green' + } + + return 'process-route-red' + }, + getOutsourceGroupClassState(processes) { + const classes = new Map() + let currentIndexes = [] + let lastProcess = null + + const flushOutsourceRun = () => { + if (currentIndexes.length > 0 && lastProcess) { + const className = this.getProcessBaseClass(lastProcess) + currentIndexes.forEach(index => classes.set(index, className)) + } + currentIndexes = [] + lastProcess = null + } + + const isSameContinuousGroup = process => { + if (!lastProcess) { + return true + } + const lineNo = this.toProcessNumber(process.订单行号) + const lastLineNo = this.toProcessNumber(lastProcess.订单行号) + return lineNo === lastLineNo + 1 && + String(process.外协分组 || '') === String(lastProcess.外协分组 || '') + } + + processes.forEach((process, index) => { + if (!this.isOutsourceProcess(process)) { + flushOutsourceRun() + return + } + if (!isSameContinuousGroup(process)) { + flushOutsourceRun() + } + currentIndexes.push(index) + lastProcess = process + }) + flushOutsourceRun() + + return classes + }, + getProcessClass(process, index, outsourceGroupClasses) { + if (this.isOutsourceProcess(process)) { + return outsourceGroupClasses.get(index) || this.getProcessBaseClass(process) + } + return this.getProcessBaseClass(process) + }, + getRouteProcesses(row) { + if (Array.isArray(row.子数据) && row.子数据.length > 0) { + return row.子数据 + } + + return String(row.工艺路线 || '') + .split(',') + .map(name => name.trim()) + .filter(Boolean) + .map(name => ({ + 工序名称: name, + 加工状态: 0, + 收检合格数: 0, + 指派对象: '', + 订单行号: 0, + 收料数量: 0, + 收检不合格数: 0 + })) + }, + formatProcessRoute(row) { + const processes = this.getRouteProcesses(row) + if (processes.length === 0) { + return '' + } + + const outsourceGroupClasses = this.getOutsourceGroupClassState(processes) + return processes.map((process, index) => { + const name = process.工序名称 || '' + const className = this.getProcessClass(process, index, outsourceGroupClasses) + const arrow = index < processes.length - 1 ? '->' : '' + return `${this.escapeProcessHtml(name)}${arrow}` + }).join('') + }, mergeOrderData(dataList) { // 第一步:先按照订单行号对原始数据进行排序 const sortedDataList = [...dataList].sort((a, b) => { @@ -330,7 +470,8 @@ export default { 订单行号: item.订单行号, 加急总数:item.加急总数, 外协结束时间:item.外协结束时间, - 开始时间:item.开始时间 + 开始时间:item.开始时间, + 外协分组: item.外协分组 }] }) } else { @@ -370,6 +511,7 @@ export default { 开始时间:item.开始时间, 结束时间:item.结束时间, 外协结束时间:item.外协结束时间, + 外协分组: item.外协分组 }) // 对子数据按照订单行号排序 @@ -615,6 +757,33 @@ export default { width: 120px; display: inline-flex; } + +.process-route { + line-height: 20px; + white-space: normal; + word-break: break-all; +} + +::v-deep .process-route-green { + color: #28d961; + font-weight: 700; +} + +::v-deep .process-route-blue { + color: #09a8ff; + font-weight: 700; +} + +::v-deep .process-route-red { + color: #ff3f37; + font-weight: 700; +} + +::v-deep .process-route-arrow { + margin: 0 4px; + color: #909399; +} + .search-container { margin-bottom: 20px; } @@ -688,4 +857,4 @@ export default { display: flex; align-items: center; } - \ No newline at end of file + diff --git a/MES_Manage_View_V20/src/views/ProductionManagement/WorkHoursCheck/index.vue b/MES_Manage_View_V20/src/views/ProductionManagement/WorkHoursCheck/index.vue index 70c126c..fae1714 100644 --- a/MES_Manage_View_V20/src/views/ProductionManagement/WorkHoursCheck/index.vue +++ b/MES_Manage_View_V20/src/views/ProductionManagement/WorkHoursCheck/index.vue @@ -59,7 +59,10 @@ 查询 一键校验 - 直接校验 + 直接校验 diff --git a/机加设备生产运营看板/dist.zip b/机加设备生产运营看板/dist.zip index 60fda85..9c01a46 100644 Binary files a/机加设备生产运营看板/dist.zip and b/机加设备生产运营看板/dist.zip differ diff --git a/机加设备生产运营看板/src/views/index.vue b/机加设备生产运营看板/src/views/index.vue index 3681a47..65d8324 100644 --- a/机加设备生产运营看板/src/views/index.vue +++ b/机加设备生产运营看板/src/views/index.vue @@ -164,7 +164,7 @@ let clockTimer = null let resizeHandler = null const summary = reactive({ - equipmentTotal: 18, + equipmentTotal: 23, running: 10, running2: 0, standby: 4, @@ -595,11 +595,14 @@ const searchPlanExecuteData = () => { const applyDashboardData = rows => { const first = getFirstRow(rows) - summary.equipmentTotal = 18 + summary.equipmentTotal = 23 summary.running = first.运行数量 || 0 - summary.standby = first.待机数量 || 0 - summary.alarm = first.报警数量 || 0 - summary.shutdown = first.关机数量 || 0 + // summary.standby = first.待机数量 || 0 + // summary.alarm = first.报警数量 || 0 + // summary.shutdown = first.关机数量 || 0 + summary.standby = 0 + summary.alarm = 0 + summary.shutdown = 0 } const applydeviceData = rows => { @@ -924,6 +927,10 @@ onMounted(() => { initCharts() }) searchDashboardData() + setInterval(function(){ + searchDashboardData() + },600000) + }) onUnmounted(() => { diff --git a/机加设备生产运营看板/src/views/machiningOperationTwo.vue b/机加设备生产运营看板/src/views/machiningOperationTwo.vue index aa1a26e..91431b6 100644 --- a/机加设备生产运营看板/src/views/machiningOperationTwo.vue +++ b/机加设备生产运营看板/src/views/machiningOperationTwo.vue @@ -115,28 +115,28 @@ const summaryCards = ref([ className: 'urgent-card', valueClass: 'blue-text' }, - { - title: '关键工序任务', - value: 14, - unit: '单', - icon: icons.shield, - className: 'key-card', - valueClass: 'purple-text' - }, - { - title: '缺勤人员', - value: 4, - unit: '人', - icon: icons.user, - className: 'absence-card', - valueClass: 'orange-text', - people: [ - { name: '张三', station: '铣床1#' }, - { name: '王五', station: '车床2#' }, - { name: '孙八', station: '磨床1#' }, - { name: '郑十一', station: '检验台1#' } - ] - } + // { + // title: '关键工序任务', + // value: 14, + // unit: '单', + // icon: icons.shield, + // className: 'key-card', + // valueClass: 'purple-text' + // }, + // { + // title: '缺勤人员', + // value: 4, + // unit: '人', + // icon: icons.user, + // className: 'absence-card', + // valueClass: 'orange-text', + // people: [ + // { name: '张三', station: '铣床1#' }, + // { name: '王五', station: '车床2#' }, + // { name: '孙八', station: '磨床1#' }, + // { name: '郑十一', station: '检验台1#' } + // ] + // } ]) const urgentRows = ref([]) @@ -257,12 +257,121 @@ const boardBaseConfig = { // 格式化工序进度详情为HTML // 格式化工序进度详情为HTML // 格式化工序进度详情为HTML +const blueProcessStyle = 'color: #09a8ff; font-weight: 700;' +const greenProcessStyle = 'color: #28d961; font-weight: 700;' + +const toNumber = value => { + const num = Number(value) + return Number.isFinite(num) ? num : 0 +} + +const parseProcessStep = step => { + const parts = step.split('|') + const hasLineNo = parts[4] !== undefined && parts[4] !== '' && !Number.isNaN(Number(parts[4])) + + return { + name: parts[0] || '', + status: parts[1] || '0', + count: parts[2] || '0', + planDate: parts[3] || '', + assignee: hasLineNo ? '' : (parts[4] || ''), + outsourceGroup: hasLineNo ? (parts[5] || '') : (parts[5] || ''), + lineNo: hasLineNo ? toNumber(parts[4]) : null, + issueQty: hasLineNo ? toNumber(parts[6]) : 0, + badQty: hasLineNo ? toNumber(parts[7]) : 0 + } +} + +const isBlueProcess = (status, count) => { + const statusNum = parseInt(status) + const countNum = parseInt(count) + return statusNum === 6 || statusNum === 3 || countNum > 0 +} + +const isOutsourceProcess = process => ( + process.assignee === '外协' || + process.name.includes('(外协)') || + process.name.includes('(外协)') +) + +const isGreenProcess = process => ( + isOutsourceProcess(process) && + process.issueQty > toNumber(process.count) + toNumber(process.badQty) +) + +const getProcessBaseStyle = process => { + if (isGreenProcess(process)) { + return greenProcessStyle + } + + if (isBlueProcess(process.status, process.count)) { + return blueProcessStyle + } + + const statusNum = parseInt(process.status) + if (statusNum === 2 || statusNum === 4) { + return greenProcessStyle + } + + return 'color: #ff3f37; font-weight: 700;' +} + +const getOutsourceGroupStyleState = steps => { + const styles = new Map() + const processes = steps.map(parseProcessStep) + + let currentIndexes = [] + let lastProcess = null + const flushOutsourceRun = () => { + if (currentIndexes.length > 0 && lastProcess) { + const style = getProcessBaseStyle(lastProcess) + currentIndexes.forEach(index => styles.set(index, style)) + } + currentIndexes = [] + lastProcess = null + } + + const isSameContinuousGroup = process => { + if (!lastProcess) { + return true + } + if (process.lineNo === null || lastProcess.lineNo === null) { + return false + } + return process.lineNo === lastProcess.lineNo + 1 && + (process.outsourceGroup || '') === (lastProcess.outsourceGroup || '') + } + + processes.forEach((process, index) => { + if (!isOutsourceProcess(process)) { + flushOutsourceRun() + return + } + if (!isSameContinuousGroup(process)) { + flushOutsourceRun() + } + currentIndexes.push(index) + lastProcess = process + }) + flushOutsourceRun() + + return { styles } +} + +const getOutsourceGroupStyle = (process, index, outsourceGroupStyleState) => { + if (!isOutsourceProcess(process)) { + return '' + } + return outsourceGroupStyleState.styles.get(index) || '' +} + const formatProcessDetail = (processDetailStr) => { if (!processDetailStr || typeof processDetailStr !== 'string') { return '' } const steps = processDetailStr.split(' -> ') + const outsourceGroupStyleState = getOutsourceGroupStyleState(steps) const maxPerRow = 8 const rows = Math.ceil(steps.length / maxPerRow) @@ -281,15 +390,17 @@ const formatProcessDetail = (processDetailStr) => { // 计划开始时间行 tableHtml += '' rowSteps.forEach((step, index) => { - const parts = step.split('|') - const planDate = parts[3] || '' - const status = parts[1] || '0' - const count = parts[2] || '0' + const process = parseProcessStep(step) // 获取颜色 - const colorStyle = getProcessColor(status, count) + const colorStyle = getProcessColor( + process.status, + process.count, + process, + getOutsourceGroupStyle(process, startIndex + index, outsourceGroupStyleState) + ) - tableHtml += `${planDate}` + tableHtml += `${process.planDate}` if (index < rowSteps.length - 1) { tableHtml += '' } @@ -299,14 +410,16 @@ const formatProcessDetail = (processDetailStr) => { // 工序名称行 tableHtml += '' rowSteps.forEach((step, index) => { - const parts = step.split('|') - const name = parts[0] || '' - const status = parts[1] || '0' - const count = parts[2] || '0' + const process = parseProcessStep(step) - const colorStyle = getProcessColor(status, count) + const colorStyle = getProcessColor( + process.status, + process.count, + process, + getOutsourceGroupStyle(process, startIndex + index, outsourceGroupStyleState) + ) - tableHtml += `${name}` + tableHtml += `${process.name}` if (index < rowSteps.length - 1) { tableHtml += '→' } @@ -316,13 +429,16 @@ const formatProcessDetail = (processDetailStr) => { // 完成数量行 tableHtml += '' rowSteps.forEach((step, index) => { - const parts = step.split('|') - const count = parts[2] || '0' - const status = parts[1] || '0' + const process = parseProcessStep(step) - const colorStyle = getProcessColor(status, count) + const colorStyle = getProcessColor( + process.status, + process.count, + process, + getOutsourceGroupStyle(process, startIndex + index, outsourceGroupStyleState) + ) - tableHtml += `合格${count}` + tableHtml += `合格${process.count}` if (index < rowSteps.length - 1) { tableHtml += '' } @@ -336,21 +452,28 @@ const formatProcessDetail = (processDetailStr) => { } // 获取颜色函数 -const getProcessColor = (status, count) => { +const getProcessColor = (status, count, process = null, groupStyle = '') => { + if (groupStyle) { + return groupStyle + } + if (process) { + return getProcessBaseStyle(process) + } + const statusNum = parseInt(status) const countNum = parseInt(count) // 加工状态=2或4 绿色 if (statusNum === 2 || statusNum === 4) { - return 'color: #28d961; font-weight: 700;' + return greenProcessStyle } // 加工状态=6 蓝色 if (statusNum === 6 || statusNum === 3) { - return 'color: #09a8ff; font-weight: 700;' + return blueProcessStyle } // 完成数量>0 蓝色 if (countNum > 0) { - return 'color: #09a8ff; font-weight: 700;' + return blueProcessStyle } // 其余红色 return 'color: #ff3f37; font-weight: 700;' @@ -358,7 +481,7 @@ const getProcessColor = (status, count) => { function makeTaskBoard(rows) { return { ...boardBaseConfig, - rowNum: 12, + rowNum: 8, columnWidth: [150, 132, 200, 82, 750], header: ['生产订单', '物料名称', '物料编码', '加急数量', '进度'], data: extendRows(rows, 12).map(row => [ @@ -932,4 +1055,4 @@ onUnmounted(() => { min-height: 50px; line-height: 1.4 !important; } - \ No newline at end of file + diff --git a/生产任务看板/dist.zip b/生产任务看板/dist.zip index 3f3df69..c21a45c 100644 Binary files a/生产任务看板/dist.zip and b/生产任务看板/dist.zip differ diff --git a/生产任务看板/src/utils/request.js b/生产任务看板/src/utils/request.js index b58efb7..ae2996a 100644 --- a/生产任务看板/src/utils/request.js +++ b/生产任务看板/src/utils/request.js @@ -8,7 +8,7 @@ const request_config = window.g.API_WEB_URL + `/submit/MESCommonBase.ashx` export const name = name_config const service = axios.create({ baseURL: request_config, - timeout: 1000 * 5 // 请求超时时间 + timeout: 300000 // 请求超时时间,300秒 }) // && config.data.name !== '菜单系统模块_通知信息_查询数据' && config.data.name !== '菜单系统模块_首页信息_查询数据1' && config.data.name !== '菜单系统模块_首页信息_查询数据2' diff --git a/生产任务看板/src/views/index.vue b/生产任务看板/src/views/index.vue index 152adaa..fbf33c2 100644 --- a/生产任务看板/src/views/index.vue +++ b/生产任务看板/src/views/index.vue @@ -102,17 +102,20 @@
-
未按期齐套任务
-
- +
加急任务列表
+
+
- + - - - + + +
{{ column.label }}{{ column.label }}
{{ row[column.key] }}
+
+ +
@@ -120,17 +123,17 @@
-
加急件明细
+
未按期齐套任务
- +
- + - - - + + +
{{ column.label }}{{ column.label }}
{{ row[column.key] }}
{{ row[column.key] }}
@@ -150,8 +153,8 @@ const tableData = ref([]) const now = ref(new Date('2026-05-06 10:10:10')) let timer = null let resizeHandler = null -let Urgentnum = null -let notintimenum = null +const Urgentnum = ref(0) +const notintimenum = ref(0) let complete = reactive({ completeset:100, tasknum:0, @@ -200,8 +203,13 @@ onMounted(() => { resizeHandler = () => { setScale() } + window.addEventListener('resize', resizeHandler) searchDashboardData() + // setInterval(function(){ + // searchDashboardData() + // },600000) + }) onUnmounted(() => { @@ -223,7 +231,7 @@ const searchNoProcessData = () => { queryDashboardData('生产任务综合看板_未排产无工艺查询', applyNoProcessData) } const searchUrgentData = () => { - queryDashboardData('生产任务综合看板_加急任务查询', applyUrgentData) + queryDashboardData('机加设备生产运营看板_加急任务查询', applyUrgentData) } const searchNotInTimeData = () => { queryDashboardData('生产任务综合看板_未及时开工查询', applyNotInTimeData) @@ -232,50 +240,43 @@ const searchUnCompleteData = () => { queryDashboardData('生产任务综合看板_未按期齐套任务', applyUnCompleteData) } const applyCompleteSetData = rows => { - complete.completeset = rows[0].齐套率 - complete.untasknum = rows[0].未齐套任务数 - complete.tasknum = rows[0].总任务数 + const first = getFirstRow(rows) + complete.completeset = first.齐套率 || 0 + complete.untasknum = first.未齐套任务数 || 0 + complete.tasknum = first.总任务数 || 0 } const applyNoProcessData = rows => { - const total = rows[0].未排产任务数 - const assembly = rows[0].装配未排产数 + const first = getFirstRow(rows) + const total = first.未排产任务数 || 0 + const assembly = first.装配未排产数 || 0 noProcessStats.total = total noProcessStats.machining = total - assembly noProcessStats.assembly = assembly - topStats[1].value = rows[0].无生产工艺计划数 + topStats[1].value = first.无生产工艺计划数 || 0 } const applyNotInTimeData = rows => { - notintimenum = rows[0].未及时开工总数 + notintimenum.value = getFirstRow(rows).未及时开工总数 || 0 } const applyUrgentData = rows => { - urgentRows = rows.map( item => ({ - salesOrder: item.合同号, - productionOrder: item.订单编号, - productName: item.物料描述, - productCode: item.物料编号, - property: item.自制件属性, - planQty: item.计划数量, - releaseDate: item.下达日期, - startDate: item.计划开始, - finishDate: item.计划完成, - })) - Urgentnum = rows[0].加急订单总数 + const first = getFirstRow(rows) + if (first.加急订单总数 !== undefined) { + Urgentnum.value = first.加急订单总数 + } + + urgentRows.value = rows.map(item => ({ + productionOrder: `${item.订单编号 || ''}-${item.合同号 || ''}`, + materialName: item.物料描述, + materialCode: item.物料编号, + urgentQty: item.加急数量, + progress: decodeHtml(item.工序进度详情 || ''), + })) } const applyUnCompleteData = rows => { - kitRows = rows.map( item => ({ + kitRows.value = rows.map( item => ({ productionOrder: item.订单编号, productName: item.物料描述, productCode: item.物料编号, - property: item.自制件属性, - planQty: item.计划数量, - releaseDate: item.下达日期, - startDate: item.计划开始时间, - finishDate: item.计划完成时间, planNo: item.合同号, - stockQty: '0', - kitDate: item.计划开始时间, - pickStatus: item.拣配状态 == 1 ?'已拣配':'未拣配', - issueStatus: item.发料状态 == 2 ?'发料缺件':'未发料', })) } const queryDashboardData = (procedureName, applyData) => { @@ -297,6 +298,240 @@ const getResponseRows = response => { return [] } +const getFirstRow = rows => { + if (Array.isArray(rows) && rows.length > 0) { + return rows[0] + } + return {} +} + +const escapeHtml = value => String(value == null ? '' : value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + +const decodeHtml = value => { + if (!value || typeof value !== 'string') { + return '' + } + if (typeof document === 'undefined') { + return value + .replace(/>/g, '>') + .replace(/</g, '<') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, "'") + } + const textarea = document.createElement('textarea') + textarea.innerHTML = value + return textarea.value +} + +const blueProcessStyle = 'color: #09a8ff; font-weight: 700;' +const greenProcessStyle = 'color: #28d961; font-weight: 700;' +const redProcessStyle = 'color: #ff3f37; font-weight: 700;' + +const toNumber = value => { + const num = Number(value) + return Number.isFinite(num) ? num : 0 +} + +const parseProcessStep = step => { + const parts = step.split('|') + const hasLineNo = parts[4] !== undefined && parts[4] !== '' && !Number.isNaN(Number(parts[4])) + + return { + name: parts[0] || '', + status: parts[1] || '0', + count: parts[2] || '0', + planDate: parts[3] || '', + assignee: hasLineNo ? '' : (parts[4] || ''), + outsourceGroup: hasLineNo ? (parts[5] || '') : (parts[5] || ''), + lineNo: hasLineNo ? toNumber(parts[4]) : null, + issueQty: hasLineNo ? toNumber(parts[6]) : 0, + badQty: hasLineNo ? toNumber(parts[7]) : 0 + } +} + +const isBlueProcess = (status, count) => { + const statusNum = parseInt(status) + const countNum = parseInt(count) + return statusNum === 6 || statusNum === 3 || countNum > 0 +} + +const isOutsourceProcess = process => ( + process.assignee === '外协' || + process.name.includes('(外协)') || + process.name.includes('(外协)') +) + +const isGreenProcess = process => ( + isOutsourceProcess(process) && + process.issueQty > toNumber(process.count) + toNumber(process.badQty) +) + +const getProcessBaseStyle = process => { + if (isGreenProcess(process)) { + return greenProcessStyle + } + if (isBlueProcess(process.status, process.count)) { + return blueProcessStyle + } + + const statusNum = parseInt(process.status) + if (statusNum === 2 || statusNum === 4) { + return greenProcessStyle + } + + return redProcessStyle +} + +const getOutsourceGroupStyleState = steps => { + const styles = new Map() + const processes = steps.map(parseProcessStep) + + let currentIndexes = [] + let lastProcess = null + const flushOutsourceRun = () => { + if (currentIndexes.length > 0 && lastProcess) { + const style = getProcessBaseStyle(lastProcess) + currentIndexes.forEach(index => styles.set(index, style)) + } + currentIndexes = [] + lastProcess = null + } + + const isSameContinuousGroup = process => { + if (!lastProcess) { + return true + } + if (process.lineNo === null || lastProcess.lineNo === null) { + return false + } + return process.lineNo === lastProcess.lineNo + 1 && + (process.outsourceGroup || '') === (lastProcess.outsourceGroup || '') + } + + processes.forEach((process, index) => { + if (!isOutsourceProcess(process)) { + flushOutsourceRun() + return + } + if (!isSameContinuousGroup(process)) { + flushOutsourceRun() + } + currentIndexes.push(index) + lastProcess = process + }) + flushOutsourceRun() + + return { styles } +} + +const getOutsourceGroupStyle = (process, index, outsourceGroupStyleState) => { + if (!isOutsourceProcess(process)) { + return '' + } + return outsourceGroupStyleState.styles.get(index) || '' +} + +const getProcessColor = (status, count, process = null, groupStyle = '') => { + if (groupStyle) { + return groupStyle + } + if (process) { + return getProcessBaseStyle(process) + } + + const statusNum = parseInt(status) + const countNum = parseInt(count) + if (statusNum === 2 || statusNum === 4) { + return greenProcessStyle + } + if (statusNum === 6 || statusNum === 3) { + return blueProcessStyle + } + if (countNum > 0) { + return blueProcessStyle + } + return redProcessStyle +} + +const formatProcessDetail = processDetailStr => { + if (!processDetailStr || typeof processDetailStr !== 'string') { + return '' + } + + const steps = processDetailStr.split(' -> ') + const outsourceGroupStyleState = getOutsourceGroupStyleState(steps) + const maxPerRow = 8 + const rows = Math.ceil(steps.length / maxPerRow) + let tableHtml = '
' + + for (let row = 0; row < rows; row++) { + const startIndex = row * maxPerRow + const endIndex = Math.min(startIndex + maxPerRow, steps.length) + const rowSteps = steps.slice(startIndex, endIndex) + + if (row > 0) { + tableHtml += '' + } + + tableHtml += '' + rowSteps.forEach((step, index) => { + const process = parseProcessStep(step) + const colorStyle = getProcessColor( + process.status, + process.count, + process, + getOutsourceGroupStyle(process, startIndex + index, outsourceGroupStyleState) + ) + tableHtml += `` + if (index < rowSteps.length - 1) { + tableHtml += '' + } + }) + tableHtml += '' + + tableHtml += '' + rowSteps.forEach((step, index) => { + const process = parseProcessStep(step) + const colorStyle = getProcessColor( + process.status, + process.count, + process, + getOutsourceGroupStyle(process, startIndex + index, outsourceGroupStyleState) + ) + tableHtml += `` + if (index < rowSteps.length - 1) { + tableHtml += '' + } + }) + tableHtml += '' + + tableHtml += '' + rowSteps.forEach((step, index) => { + const process = parseProcessStep(step) + const colorStyle = getProcessColor( + process.status, + process.count, + process, + getOutsourceGroupStyle(process, startIndex + index, outsourceGroupStyleState) + ) + tableHtml += `` + if (index < rowSteps.length - 1) { + tableHtml += '' + } + }) + tableHtml += '' + } + + tableHtml += '
${escapeHtml(process.planDate)}
${escapeHtml(process.name)}
合格${escapeHtml(process.count)}
' + return tableHtml +} + const startClock = () => { timer = window.setInterval(() => { now.value = new Date(now.value.getTime() + 1000) @@ -314,60 +549,55 @@ const destroyDashboard = () => { } } -const topStats = [ +const topStats = reactive([ { title: '待处理销售订单', value: 12, icon: svgIcons.salesOrder }, { title: '工艺BOM待完成', value: 9, icon: svgIcons.bom }, -] +]) const leftColumns = [ - { label: '销售订单', key: 'salesOrder' }, - { label: '计划号', key: 'planNo' }, + { label: '销售订单', key: 'planNo' }, { label: '生产订单', key: 'productionOrder' }, { label: '产品名称', key: 'productName' }, - { label: '产品编码', key: 'productCode' }, - { label: '计划数', key: 'planQty' }, - { label: '入库数', key: 'stockQty' }, - { label: '预计齐套', key: 'kitDate' }, - { label: '拣配状态', key: 'pickStatus' }, - { label: '发料状态', key: 'issueStatus' }, + { label: '产品编码', key: 'productCode' } ] const urgentColumns = [ - { label: '销售订单', key: 'salesOrder' }, { label: '生产订单', key: 'productionOrder' }, - { label: '产品名称', key: 'productName' }, - { label: '产品编码', key: 'productCode' }, - { label: '属性', key: 'property' }, - { label: '计划数', key: 'planQty' }, - { label: '下达日期', key: 'releaseDate' }, - { label: '计划开始', key: 'startDate' }, - { label: '计划完成', key: 'finishDate' }, + { label: '物料名称', key: 'materialName' }, + { label: '物料编码', key: 'materialCode' }, + { label: '加急数量', key: 'urgentQty' }, + { label: '进度', key: 'progress' } ] -let kitRows = Array.from({ length: 9 }, () => ({ - salesOrder: 'SH260014', +const makeScrollRows = (rows, minRows = 10) => { + if (!Array.isArray(rows) || rows.length === 0) { + return [] + } + + const baseRows = [...rows] + while (baseRows.length < minRows) { + baseRows.push(...rows.slice(0, minRows - baseRows.length)) + } + return [...baseRows, ...baseRows] +} + +const kitRows = ref(Array.from({ length: 9 }, () => ({ planNo: '54023', productionOrder: '1075', productName: '垫铁+行程开关部装', - productCode: '213-YGLQ-DF150V1.2.18', - planQty: '1', - stockQty: '0', - kitDate: '2026-01-21', - pickStatus: '已拣配', - issueStatus: '未发料', -})) + productCode: '213-YGLQ-DF150V1.2.18' +}))) -let urgentRows = Array.from({ length: 10 }, () => ({ - salesOrder: 'SH260014', - productionOrder: '1075', - productName: '垫铁+行程开关部装', - productCode: '213-YGLQ-DF150V1.2.18', - property: '机加', - planQty: '1', - releaseDate: '2026-01-21', - startDate: '2026-01-21', - finishDate: '2026-01-21', -})) +const urgentRows = ref([]) + +const displayUrgentRows = computed(() => makeScrollRows(urgentRows.value, 8)) +const displayKitRows = computed(() => makeScrollRows(kitRows.value, 9)) +const getScrollDuration = (rows, minRows, secondsPerRow) => { + const rowCount = Math.max(Array.isArray(rows) ? rows.length : 0, minRows) + return `${Math.max(rowCount * secondsPerRow, 36)}s` +} +const urgentScrollDuration = computed(() => getScrollDuration(urgentRows.value, 8, 4.2)) +const kitScrollDuration = computed(() => getScrollDuration(kitRows.value, 9, 3.5))