From e8f6e97a15b26e03706ce10c15faf1919da38ea4 Mon Sep 17 00:00:00 2001 From: tzy <1042411602@qq.com> Date: Tue, 27 Oct 2026 17:40:10 +0800 Subject: [PATCH] 0811 --- src/api/system/designTask.js | 75 + src/api/system/details.js | 3 +- src/api/system/dingTodo.js | 24 + src/api/system/drawingRouteOperLog.js | 24 + src/api/system/material.js | 18 +- src/api/system/orderPro.js | 37 + src/api/system/orderProProgress.js | 34 + src/api/system/ratio.js | 29 + src/api/system/route.js | 31 +- src/api/system/vmiMaterial.js | 96 + src/api/system/workAlertGroupPush.js | 58 + src/permission.js | 2 +- src/router/index.js | 50 +- src/utils/jsyyOrderNo.js | 8 + src/utils/projectStatusLock.js | 84 + src/views/devstat/index.vue | 628 +++++++ src/views/index.vue | 533 +++--- src/views/system/details/index.vue | 78 +- src/views/system/dingTodo/index.vue | 185 ++ .../system/drawingRouteOperLog/index.vue | 629 +++++++ src/views/system/material/index.vue | 21 +- .../system/operationPlanningMain/index.vue | 106 +- src/views/system/orderPro/index.vue | 1236 +++++++++++-- src/views/system/orderProDesigner/index.vue | 319 ++++ src/views/system/orderProProgress/index.vue | 696 ++++++++ src/views/system/productionBoard/index.vue | 309 +++- src/views/system/ratio/index.vue | 385 +++- src/views/system/route/index.vue | 557 +++++- src/views/system/user/index.vue | 9 + src/views/system/vmiMaterial/index.vue | 679 +++++++ src/views/system/workAlertGroupPush/index.vue | 672 +++++++ src/views/workbench/design/index.vue | 1579 +++++++++++++++++ src/views/workbench/index.vue | 22 +- 33 files changed, 8534 insertions(+), 682 deletions(-) create mode 100644 src/api/system/designTask.js create mode 100644 src/api/system/dingTodo.js create mode 100644 src/api/system/drawingRouteOperLog.js create mode 100644 src/api/system/orderProProgress.js create mode 100644 src/api/system/vmiMaterial.js create mode 100644 src/api/system/workAlertGroupPush.js create mode 100644 src/utils/jsyyOrderNo.js create mode 100644 src/utils/projectStatusLock.js create mode 100644 src/views/devstat/index.vue create mode 100644 src/views/system/dingTodo/index.vue create mode 100644 src/views/system/drawingRouteOperLog/index.vue create mode 100644 src/views/system/orderProDesigner/index.vue create mode 100644 src/views/system/orderProProgress/index.vue create mode 100644 src/views/system/vmiMaterial/index.vue create mode 100644 src/views/system/workAlertGroupPush/index.vue create mode 100644 src/views/workbench/design/index.vue diff --git a/src/api/system/designTask.js b/src/api/system/designTask.js new file mode 100644 index 0000000..cf6eb3a --- /dev/null +++ b/src/api/system/designTask.js @@ -0,0 +1,75 @@ +import request from '@/utils/request' + +export function getDesignTaskSummary() { + return request({ + url: '/system/designTask/summary', + method: 'get' + }) +} + +export function getDesignTaskBoard(query) { + return request({ + url: '/system/designTask/board', + method: 'get', + params: query + }) +} + +export function listMyDesignTask(query) { + return request({ + url: '/system/designTask/mine', + method: 'get', + params: query + }) +} + +export function reportDesignTask(data) { + return request({ + url: '/system/designTask/report', + method: 'post', + data: data + }) +} + +export function listDesignTaskReports(taskId) { + return request({ + url: '/system/designTask/reports/' + taskId, + method: 'get' + }) +} + +export function acceptDesignTask(taskId) { + return request({ + url: '/system/designTask/accept/' + taskId, + method: 'post' + }) +} + +export function updateDesignTaskDim(data) { + return request({ + url: '/system/designTask/designDim', + method: 'put', + data: data + }) +} + +export function deleteDesignTaskReport(reportId) { + return request({ + url: '/system/designTask/reports/' + reportId, + method: 'delete' + }) +} + +export function listDesignTaskByOrder(orderId) { + return request({ + url: '/system/designTask/byOrder/' + orderId, + method: 'get' + }) +} + +export function syncDesignAccounts() { + return request({ + url: '/system/designTask/syncAccounts', + method: 'post' + }) +} diff --git a/src/api/system/details.js b/src/api/system/details.js index 62bc70b..393970f 100644 --- a/src/api/system/details.js +++ b/src/api/system/details.js @@ -76,6 +76,7 @@ export function importElectricalBom(formData) { url: '/system/details/importElectricalBom', method: 'post', data: formData, - headers: { 'Content-Type': 'multipart/form-data' } + timeout: 180000, + headers: { "Content-Type": "multipart/form-data", repeatSubmit: false }, }) } diff --git a/src/api/system/dingTodo.js b/src/api/system/dingTodo.js new file mode 100644 index 0000000..caee85e --- /dev/null +++ b/src/api/system/dingTodo.js @@ -0,0 +1,24 @@ +import request from '@/utils/request' + +export function listDingTodoScenes() { + return request({ + url: '/system/dingTodo/scenes', + method: 'get' + }) +} + +export function updateDingTodoScene(data) { + return request({ + url: '/system/dingTodo/scenes', + method: 'put', + data + }) +} + +export function listDingTodoLogs(query) { + return request({ + url: '/system/dingTodo/logs', + method: 'get', + params: query + }) +} diff --git a/src/api/system/drawingRouteOperLog.js b/src/api/system/drawingRouteOperLog.js new file mode 100644 index 0000000..1426335 --- /dev/null +++ b/src/api/system/drawingRouteOperLog.js @@ -0,0 +1,24 @@ +import request from '@/utils/request' + +export function listDrawingRouteOperLog(query) { + return request({ + url: '/system/drawingRouteOperLog/list', + method: 'get', + params: query + }) +} + +export function getDrawingRouteOperLog(id) { + return request({ + url: '/system/drawingRouteOperLog/' + id, + method: 'get' + }) +} + +export function renotifyDrawingRouteOperLog(id) { + return request({ + url: '/system/drawingRouteOperLog/' + id + '/renotify', + method: 'post', + timeout: 180000 + }) +} diff --git a/src/api/system/material.js b/src/api/system/material.js index 1abd3e3..b1dd8af 100644 --- a/src/api/system/material.js +++ b/src/api/system/material.js @@ -8,11 +8,21 @@ export function listMaterial_index(query) { params: query }) } -// -export function material_inventory(query) { + +/** + * 库存查询(支持单个或多个物料) + * @param {string|string[]} codes 物料编码或编码数组 + */ +export function material_inventory(codes) { + const list = Array.isArray(codes) + ? codes.filter(Boolean) + : String(codes || '').split(',').map(s => s.trim()).filter(Boolean) return request({ - url: '/index/inventory?materialCode=' + query, - method: 'get' + url: '/index/inventory', + method: 'get', + params: { + materialCodes: list.join(',') + } }) } diff --git a/src/api/system/orderPro.js b/src/api/system/orderPro.js index ec97f4b..bcb368c 100644 --- a/src/api/system/orderPro.js +++ b/src/api/system/orderPro.js @@ -66,6 +66,24 @@ export function updateOrderPro(data) { }) } +/** 批量设置项目状态(不走整单编辑,避免设计人必填拦住) */ +export function updateOrderProBomStatus(data) { + return request({ + url: '/system/orderPro/bomStatus', + method: 'put', + data: data + }) +} + +/** 设置出图模式:normal 正常 / part 零部件 */ +export function updateDrawingMode(id, drawingMode) { + return request({ + url: '/system/orderPro/drawingMode/' + id, + method: 'put', + data: { drawingMode } + }) +} + // 删除项目令号 export function delOrderPro(id) { return request({ @@ -126,6 +144,14 @@ export function delSave(ids) { }) } +// 确认图纸已齐:设计人员确认图纸上传完成,之后才允许下载 PLM PDF +export function issueDrawingQty(id) { + return request({ + url: '/system/orderPro/issueDrawingQty/' + id, + method: 'post' + }) +} + // 下载PDF export function uploadPDF(id) { return request({ @@ -223,3 +249,14 @@ export function getKingdeePurchaseOrder(id) { method: 'get' }) } + +// 临时导入:按生产令号更新出图时间、项目完成时间等字段,空单元格不更新 +export function importDrawingByExcel(formData) { + return request({ + url: '/system/orderPro/importDrawingBy', + method: 'post', + data: formData, + timeout: 180000, + headers: { 'Content-Type': 'multipart/form-data', repeatSubmit: false } + }) +} diff --git a/src/api/system/orderProProgress.js b/src/api/system/orderProProgress.js new file mode 100644 index 0000000..7b66d6a --- /dev/null +++ b/src/api/system/orderProProgress.js @@ -0,0 +1,34 @@ +import request from '@/utils/request' + +export function listOrderProProgress(query) { + return request({ + url: '/system/orderProProgress/list', + method: 'get', + params: query + }) +} + +export function getOrderProProgressDesignerSummary(query) { + return request({ + url: '/system/orderProProgress/designerSummary', + method: 'get', + params: query + }) +} + +export function getOrderProProgressOverview(query) { + return request({ + url: '/system/orderProProgress/overview', + method: 'get', + params: query + }) +} + +export function syncOrderProProgressWorkload(ids) { + return request({ + url: '/system/orderProProgress/syncWorkload', + method: 'post', + data: ids, + timeout: 5 * 60 * 1000 + }) +} diff --git a/src/api/system/ratio.js b/src/api/system/ratio.js index 97f6e6e..305653e 100644 --- a/src/api/system/ratio.js +++ b/src/api/system/ratio.js @@ -1,12 +1,41 @@ import request from '@/utils/request' +export function listRatioValues(seriesType) { + return request({ + url: '/system/ratio/ratios', + method: 'get', + params: { seriesType } + }) +} + +export function listRatioModels(params) { + return request({ + url: '/system/ratio/models', + method: 'get', + params: { + seriesType: params.seriesType, + ratioValue: params.ratioValue + } + }) +} + export function getRatioBuildCapacity(params) { return request({ url: '/system/ratio/buildCapacity', method: 'get', params: { + seriesType: params.seriesType, ratioValue: params.ratioValue, + modelId: params.modelId, qty: params.qty } }) +} + +export function importRatioBom(data) { + return request({ + url: '/system/ratio/import', + method: 'post', + data + }) } \ No newline at end of file diff --git a/src/api/system/route.js b/src/api/system/route.js index 229e0c6..7380024 100644 --- a/src/api/system/route.js +++ b/src/api/system/route.js @@ -99,6 +99,17 @@ export function pushRouteBom(rooteProdet, groupName) { }); } +/** 按物料推送工艺:只推所选物料,不改项目整单状态 */ +export function pushRouteBomByMaterial(rooteProdet, groupName, materialCodes) { + const codes = Array.isArray(materialCodes) ? materialCodes.filter(Boolean).join(',') : (materialCodes || ''); + return request({ + url: '/system/route/pushRouteBomByMaterial', + method: 'post', + params: { rooteProdet, groupName, materialCodes: codes }, + timeout: 600000, + }); +} + export function approveRoute(rooteProdet) { return request({ url: '/system/route/approveRoute', @@ -146,12 +157,28 @@ export function getAllRouteAndUse(rooteProdet) { throw new Error('获取全部工艺失败,请重试!'); }); } -export function generatePDFs(rooteProdet) { +/** 按令号 + 图号 + 格式下载 PLM 图纸/模型,后端转发文件服务 */ +export function downloadDrawingModel(productionOrderNo, materialCode, fileType) { + return request({ + url: '/system/route/downloadDrawingModel', + method: 'get', + params: { productionOrderNo, materialCode, fileType }, + responseType: 'blob', + timeout: 300000 + }); +} + +export function generatePDFs(rooteProdet, materialCode) { + const params = { rooteProdet }; + if (materialCode) { + params.materialCode = materialCode; + } return request({ url: '/system/route/generatePDFs', method: 'get', - params: { rooteProdet }, + params, responseType: 'blob', + timeout: materialCode ? 120000 : 300000, headers:{ 'Content-Type': 'application/json; application/octet-stream'}, }); } diff --git a/src/api/system/vmiMaterial.js b/src/api/system/vmiMaterial.js new file mode 100644 index 0000000..66202b1 --- /dev/null +++ b/src/api/system/vmiMaterial.js @@ -0,0 +1,96 @@ +import request from '@/utils/request' + +export function listVmiMaterial(query) { + return request({ + url: '/system/vmiMaterial/list', + method: 'get', + params: query + }) +} + +export function getVmiMaterial(id) { + return request({ + url: '/system/vmiMaterial/' + id, + method: 'get' + }) +} + +export function addVmiMaterial(data) { + return request({ + url: '/system/vmiMaterial', + method: 'post', + data + }) +} + +export function updateVmiMaterial(data) { + return request({ + url: '/system/vmiMaterial', + method: 'put', + data + }) +} + +export function delVmiMaterial(id) { + const ids = Array.isArray(id) ? id.join(',') : id + return request({ + url: '/system/vmiMaterial/' + ids, + method: 'delete' + }) +} + +export function scanVmiMaterial() { + return request({ + url: '/system/vmiMaterial/scan', + method: 'post', + timeout: 180000, + headers: { repeatSubmit: false } + }) +} + +export function confirmVmiReplenish(data) { + return request({ + url: '/system/vmiMaterial/confirm', + method: 'post', + timeout: 120000, + data + }) +} + +export function previewVmiReplenish(data) { + return request({ + url: '/system/vmiMaterial/confirm/preview', + method: 'post', + data + }) +} + +export function ignoreVmiAlert(alertId) { + return request({ + url: '/system/vmiMaterial/ignore/' + alertId, + method: 'post' + }) +} + +export function notifyVmiForecast(id) { + return request({ + url: '/system/vmiMaterial/forecast/notify/' + id, + method: 'post' + }) +} + +export function listVmiAlert(query) { + return request({ + url: '/system/vmiMaterial/alert/list', + method: 'get', + params: query + }) +} + +export function listVmiReq(query) { + return request({ + url: '/system/vmiMaterial/req/list', + method: 'get', + params: query + }) +} diff --git a/src/api/system/workAlertGroupPush.js b/src/api/system/workAlertGroupPush.js new file mode 100644 index 0000000..666bb09 --- /dev/null +++ b/src/api/system/workAlertGroupPush.js @@ -0,0 +1,58 @@ +import request from '@/utils/request' + +export function listGroupPushTypes() { + return request({ + url: '/system/workAlertGroupPush/pushTypes', + method: 'get' + }) +} + +export function sendWorkAlertGroupPush(data) { + return request({ + url: '/system/workAlertGroupPush/send', + method: 'post', + timeout: 180000, + headers: { repeatSubmit: false }, + data + }) +} + +export function listGroupPushSchedule() { + return request({ + url: '/system/workAlertGroupPush/schedule/list', + method: 'get' + }) +} + +export function addGroupPushSchedule(data) { + return request({ + url: '/system/workAlertGroupPush/schedule', + method: 'post', + data + }) +} + +export function updateGroupPushSchedule(data) { + return request({ + url: '/system/workAlertGroupPush/schedule', + method: 'put', + data + }) +} + +export function delGroupPushSchedule(id) { + const ids = Array.isArray(id) ? id.join(',') : id + return request({ + url: '/system/workAlertGroupPush/schedule/' + ids, + method: 'delete' + }) +} + +export function triggerGroupPushSchedule(id) { + return request({ + url: '/system/workAlertGroupPush/schedule/trigger/' + id, + method: 'post', + timeout: 180000, + headers: { repeatSubmit: false } + }) +} diff --git a/src/permission.js b/src/permission.js index 97d20d4..dcbe3b8 100644 --- a/src/permission.js +++ b/src/permission.js @@ -8,7 +8,7 @@ import { isRelogin } from '@/utils/request' NProgress.configure({ showSpinner: false }) -const whiteList = ['/login', '/register', '/index', '/indexDaping', '/mrp2', '/system/cost', '/DataBoard/ProduceKanBan/index'] +const whiteList = ['/login', '/register', '/index', '/indexDaping', '/mrp2', '/system/cost', '/DataBoard/ProduceKanBan/index', '/devstat'] function isWhiteList(path) { return whiteList.indexOf(path) !== -1 diff --git a/src/router/index.js b/src/router/index.js index 5850355..34b8ec1 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -155,6 +155,13 @@ export const constantRoutes = [ hidden: true, meta: { title: '零件成本查询' } }, + { + path: '/devstat', + component: () => import('@/views/devstat/index'), + name: 'DevStatBoard', + hidden: true, + meta: { title: '北通道实验台' } + }, { path: '/system/operationPlanningMain', component: Layout, @@ -246,7 +253,20 @@ export const constantRoutes = [ // component: () => import('@/views/index'), // } // ] - // } + // }, + { + path: '/system/dingTodo', + component: Layout, + hidden: true, + children: [ + { + path: '', + component: () => import('@/views/system/dingTodo/index'), + name: 'DingTodoPage', + meta: { title: '钉钉待办' } + } + ] + } ] // 动态路由,基于用户权限动态去加载 @@ -307,6 +327,34 @@ export const dynamicRoutes = [ } ] }, + { + path: '/system/orderProDesigner', + component: Layout, + hidden: true, + permissions: ['system:orderProProgress:list'], + children: [ + { + path: 'index', + component: () => import('@/views/system/orderProDesigner/index'), + name: 'OrderProDesigner', + meta: { title: '设计人汇总', activeMenu: '/system/orderProProgress' } + } + ] + }, + { + path: '/system/vmiMaterial', + component: Layout, + hidden: true, + permissions: ['system:vmiMaterial:list'], + children: [ + { + path: 'index', + component: () => import('@/views/system/vmiMaterial/index'), + name: 'VmiMaterial', + meta: { title: 'VMI物料安全库存', activeMenu: '/system/vmiMaterial' } + } + ] + }, { path: '/tool/gen-edit', component: Layout, diff --git a/src/utils/jsyyOrderNo.js b/src/utils/jsyyOrderNo.js new file mode 100644 index 0000000..885b151 --- /dev/null +++ b/src/utils/jsyyOrderNo.js @@ -0,0 +1,8 @@ +/** JSYY 技术预研令号:不属于生产令号,禁止更新物料清单、推送工艺 */ + +export const JSYY_DENY_MSG = + "JSYY技术预研令号不属于生产令号,严禁直接用于生产执行,禁止更新物料清单、禁止推送工艺"; + +export function isJsyyOrderNo(orderNo) { + return !!(orderNo && String(orderNo).toUpperCase().indexOf("JSYY") >= 0); +} diff --git a/src/utils/projectStatusLock.js b/src/utils/projectStatusLock.js new file mode 100644 index 0000000..039f91c --- /dev/null +++ b/src/utils/projectStatusLock.js @@ -0,0 +1,84 @@ +/** + * 项目状态(bomStatus)—— 与后端 ProjectStatusLock 保持一致。 + * 页面展示 / 筛选 / 设置状态都用这一套,不再读字典 process_status。 + * + * 8 工艺审核完成:禁止出图、禁止上传工艺路线 + * 6 开始生产 / 7 生产完成:禁止出图、上传工艺路线、推送工艺、更新物料清单 + */ + +export const ProjectStatusAction = { + DRAWING: "DRAWING", + UPLOAD_ROUTE: "UPLOAD_ROUTE", + PUSH_ROUTE: "PUSH_ROUTE", + UPDATE_MATERIAL: "UPDATE_MATERIAL", +}; + +const ACTION_LABEL = { + DRAWING: "出图", + UPLOAD_ROUTE: "上传工艺路线", + PUSH_ROUTE: "推送工艺", + UPDATE_MATERIAL: "更新物料清单", +}; + +/** 与后端写入值一致;5 为历史残留,展示等同开始生产,不出现在下拉 */ +export const PROJECT_STATUS_MAP = { + 0: "方案设计完成", + 1: "设计出图完成", + 2: "工艺待审核", + 3: "已推送BOM", + 4: "已推送工艺", + 5: "开始生产", + 6: "开始生产", + 7: "生产完成", + 8: "工艺审核完成", + 9: "工艺审核拒绝", +}; + +export const PROJECT_STATUS_OPTIONS = [ + { value: 0, label: "方案设计完成" }, + { value: 1, label: "设计出图完成" }, + { value: 2, label: "工艺待审核" }, + { value: 3, label: "已推送BOM" }, + { value: 4, label: "已推送工艺" }, + { value: 6, label: "开始生产" }, + { value: 7, label: "生产完成" }, + { value: 8, label: "工艺审核完成" }, + { value: 9, label: "工艺审核拒绝" }, +]; + +export function projectStatusLabel(bomStatus) { + const s = bomStatus === null || bomStatus === undefined || bomStatus === "" + ? null + : Number(bomStatus); + if (s === null || Number.isNaN(s)) return "未知"; + return PROJECT_STATUS_MAP[s] || String(s); +} + +export function denyIfProjectStatusLocked(bomStatus, action) { + if (bomStatus === null || bomStatus === undefined || bomStatus === "" || !action) { + return null; + } + const s = Number(bomStatus); + if (Number.isNaN(s)) return null; + + const productionLocked = s === 5 || s === 6 || s === 7; + const routeApproved = s === 8; + + if (action === ProjectStatusAction.DRAWING || action === ProjectStatusAction.UPLOAD_ROUTE) { + if (productionLocked || routeApproved) { + return `当前项目状态为「${projectStatusLabel(s)}」,不允许${ACTION_LABEL[action]}`; + } + return null; + } + if (action === ProjectStatusAction.PUSH_ROUTE || action === ProjectStatusAction.UPDATE_MATERIAL) { + if (productionLocked) { + return `当前项目状态为「${projectStatusLabel(s)}」,不允许${ACTION_LABEL[action]}`; + } + return null; + } + return null; +} + +export function isProjectStatusLocked(bomStatus, action) { + return !!denyIfProjectStatusLocked(bomStatus, action); +} diff --git a/src/views/devstat/index.vue b/src/views/devstat/index.vue new file mode 100644 index 0000000..97f1dd2 --- /dev/null +++ b/src/views/devstat/index.vue @@ -0,0 +1,628 @@ + + + + + + + diff --git a/src/views/index.vue b/src/views/index.vue index 5422468..0f4b26b 100644 --- a/src/views/index.vue +++ b/src/views/index.vue @@ -1,10 +1,10 @@ - diff --git a/src/views/system/drawingRouteOperLog/index.vue b/src/views/system/drawingRouteOperLog/index.vue new file mode 100644 index 0000000..64321e4 --- /dev/null +++ b/src/views/system/drawingRouteOperLog/index.vue @@ -0,0 +1,629 @@ + + + + + diff --git a/src/views/system/material/index.vue b/src/views/system/material/index.vue index 5c725fe..0c5e5e5 100644 --- a/src/views/system/material/index.vue +++ b/src/views/system/material/index.vue @@ -368,18 +368,15 @@ export default { this.showInventory(row.materialCode); // 打开弹框 }, showInventory(materialCode) { - const data = { - matrilascode: materialCode - } - // 向后端发送请求获取库存数 - material_inventory(data).then(response => { - this.inventoryData.materialCode = materialCode; // 设置物料编号 - this.inventoryData.inventory = response; // 设置库存数 - this.dialogVisible = true; // 打开弹框显示数据 - }) - .catch(error => { - console.error('获取库存数失败:', error); - }); + material_inventory(materialCode).then(response => { + const list = (response && response.data) || []; + const item = list.find(r => r && r.materialCode === materialCode) || list[0]; + this.inventoryData.materialCode = materialCode; + this.inventoryData.inventory = item ? item.kucun : 0; + this.dialogVisible = true; + }).catch(error => { + console.error('获取库存数失败:', error); + }); }, /** 查询物料检索列表 */ diff --git a/src/views/system/operationPlanningMain/index.vue b/src/views/system/operationPlanningMain/index.vue index ebb859d..a055863 100644 --- a/src/views/system/operationPlanningMain/index.vue +++ b/src/views/system/operationPlanningMain/index.vue @@ -105,26 +105,26 @@ - + - + - + + + + + + + - - - + + + + @@ -417,7 +444,7 @@
将文件拖到此处,或点击上传
@@ -628,17 +655,25 @@ import { addRoute, updateRoute, getDistinctProjectCodes, - updateProcessPlan, generatePDFs, updateBom, addBom, deleteBom, getKindeeExcel, getAllRouteAndUse, updateProcesTime + updateProcessPlan, generatePDFs, downloadDrawingModel as requestDrawingModel, updateBom, addBom, deleteBom, getKindeeExcel, getAllRouteAndUse, updateProcesTime } from "@/api/system/route"; import { getToken } from "../../../utils/auth"; import Fromed from './fromed' import Bomfrom from './bomfrom' -import { getProcessInfoList, pushRouteBom, approveRoute, onSave, getBomInfo, getProcessRouteList } from "@/api/system/route"; +import { getProcessInfoList, pushRouteBom, pushRouteBomByMaterial, approveRoute, onSave, getBomInfo, getProcessRouteList } from "@/api/system/route"; import { listMaterial } from "@/api/system/material"; +import { blobValidate } from "@/utils/ruoyi"; +import { checkPermi } from "@/utils/permission"; import dict from "@/utils/dict"; import DictData from "@/components/DictData"; +import { isJsyyOrderNo, JSYY_DENY_MSG } from "@/utils/jsyyOrderNo"; +import { + ProjectStatusAction, + denyIfProjectStatusLocked, +} from "@/utils/projectStatusLock"; +import { listOrderPro } from "@/api/system/orderPro"; DictData.install(); export default { @@ -698,12 +733,33 @@ export default { //expandRowKeys:[], omdelData: '', routeDescription: '', // 用于存储 productionOrderNo 的变量 + /** 当前令号对应项目状态,用于状态锁 */ + projectBomStatus: undefined, + /** 当前令号来源:手动 / PLM */ + projectSource: undefined, + /** 正在下载图纸模型的物料编码 */ + downloadingDrawingCode: "", // 按钮loading buttonLoading: false, // 遮罩层 loading: true, // 选中数组 ids: [], + selectedRows: [], + /** 正在下载 PDF 的物料编码,用于行内按钮 loading */ + downloadingPdfCode: "", + /** 图纸模型文件服务支持的格式,propertyValue 传物料编码(图号) */ + drawingModelFormats: [ + { type: "sldprt", label: "零件模型 sldprt" }, + { type: "sldasm", label: "装配模型 sldasm" }, + { type: "dwg", label: "DWG" }, + { type: "pdf", label: "PDF" }, + { type: "x_t", label: "x_t" } + ], + /** 正在按物料推送工艺的物料编码 */ + pushingMaterialCode: "", + /** 工具栏「生成PDF」整包 zip 的 loading */ + generatingAllPdf: false, // 非单禁用 single: true, // 非多个禁用 @@ -950,17 +1006,6 @@ export default { // } this.getList(); }, - watch: { - '$route.query.productionOrderNo': function (newVal) { - // 当路由参数 productionOrderNo 变化时,重新获取 - this.setTotalWeight(); - this.getList(); - // 设置查询参数中的 routeDescription - if (newVal) { - this.queryParams.routeDescription = newVal; - } - } - }, mounted() { this.rowDrop(); }, @@ -973,9 +1018,112 @@ export default { String(value).toLowerCase().includes(searchKey) ); }); - } + }, + jsyyBlocked() { + const orderNo = (this.params && this.params.productionOrderNo) || this.queryParams.routeDescription; + return isJsyyOrderNo(orderNo); + }, + /** 当前要导入的项目令号:抽屉传入优先,否则用页面所选令号 */ + currentImportOrderNo() { + const fromParams = this.params && this.params.productionOrderNo; + const fromQuery = this.queryParams && this.queryParams.routeDescription; + return String(fromParams || fromQuery || "").trim(); + }, + /** 上传地址带上当前令号,后端据此与 Excel 内令号核对 */ + importUploadAction() { + let url = this.upload.url + "?updateSupport=" + this.upload.updateSupport; + const orderNo = this.currentImportOrderNo; + if (orderNo) { + url += "&productionOrderNo=" + encodeURIComponent(orderNo); + } + return url; + }, + jsyyDenyMsg() { + return JSYY_DENY_MSG; + }, + resolvedBomStatus() { + if (this.params && this.params.bomStatus !== undefined && this.params.bomStatus !== null && this.params.bomStatus !== "") { + return this.params.bomStatus; + } + return this.projectBomStatus; + }, + uploadRouteStatusLockMsg() { + return denyIfProjectStatusLocked(this.resolvedBomStatus, ProjectStatusAction.UPLOAD_ROUTE); + }, + uploadRouteStatusLocked() { + return !!this.uploadRouteStatusLockMsg; + }, + pushRouteStatusLockMsg() { + return denyIfProjectStatusLocked(this.resolvedBomStatus, ProjectStatusAction.PUSH_ROUTE); + }, + pushRouteStatusLocked() { + return !!this.pushRouteStatusLockMsg; + }, + isPlmProject() { + const source = (this.params && this.params.source) || this.projectSource; + return String(source || "").trim().toUpperCase() === "PLM"; + }, + }, + watch: { + '$route.query.productionOrderNo': function (newVal) { + this.setTotalWeight(); + this.getList(); + if (newVal) { + this.queryParams.routeDescription = newVal; + } + }, + "params.productionOrderNo": { + immediate: true, + handler(val) { + if (val) { + this.refreshProjectBomStatus(val); + } + }, + }, + "queryParams.routeDescription"(val) { + if (val) { + this.refreshProjectBomStatus(val); + } + }, }, methods: { + async refreshProjectBomStatus(orderNo) { + if (!orderNo) { + this.projectBomStatus = undefined; + this.projectSource = undefined; + return; + } + const hasParamStatus = this.params + && this.params.bomStatus !== undefined + && this.params.bomStatus !== null + && this.params.bomStatus !== ""; + if (hasParamStatus) { + this.projectBomStatus = this.params.bomStatus; + } + if (this.params && this.params.source) { + this.projectSource = this.params.source; + } + if (hasParamStatus && this.projectSource) { + return; + } + try { + const res = await listOrderPro({ productionOrderNo: orderNo, pageNum: 1, pageSize: 1 }); + const row = (res.rows && res.rows[0]) || null; + if (!hasParamStatus) { + this.projectBomStatus = row ? row.bomStatus : undefined; + } + if (!(this.params && this.params.source)) { + this.projectSource = row ? row.source : undefined; + } + } catch (e) { + if (!hasParamStatus) { + this.projectBomStatus = undefined; + } + if (!(this.params && this.params.source)) { + this.projectSource = undefined; + } + } + }, // dict, getKindeeExcel() { // 显示加载状态 @@ -1318,7 +1466,16 @@ export default { }, /** 导入钮操作 */ handleImport() { - this.upload.title = "工艺导入"; + if (this.uploadRouteStatusLocked) { + this.$modal.msgError(this.uploadRouteStatusLockMsg); + return; + } + // 未选令号则无法与文件核对,先拦住 + if (!this.currentImportOrderNo) { + this.$modal.msgWarning("请先选择项目令号,再导入工艺"); + return; + } + this.upload.title = "工艺导入(当前令号:" + this.currentImportOrderNo + ")"; this.upload.open = true; }, /** 下载模板操作 */ @@ -1334,12 +1491,20 @@ export default { this.upload.open = false; this.upload.isUploading = false; this.$refs.upload.clearFiles(); + const msg = (response && response.msg) || "导入失败"; + const ok = response && (response.code === 200 || response.code === 0); this.$alert("
" - + response.msg + "
", "导入结果", { dangerouslyUseHTMLString: true }); - this.getList(); + + msg + "", ok ? "导入结果" : "导入失败", { dangerouslyUseHTMLString: true }); + if (ok) { + this.getList(); + } }, // 提交上传文件 submitFileForm() { + if (!this.currentImportOrderNo) { + this.$modal.msgWarning("请先选择项目令号,再导入工艺"); + return; + } this.$refs.upload.submit(); }, // 取消按钮 @@ -1381,6 +1546,7 @@ export default { // 多选框选中数据 handleSelectionChange(selection) { this.ids = selection.map(item => item.id) + this.selectedRows = selection || [] this.single = selection.length !== 1 this.multiple = !selection.length }, @@ -1416,13 +1582,21 @@ export default { }, - /** 推送工艺 / 审核并推送 共用逻辑 */ - async executeRoutePush({ confirmMessage, loadingText, apiFn, errorTitle, successTitle }) { + /** 推送工艺 / 按物料推送 共用逻辑 */ + async executeRoutePush({ confirmMessage, loadingText, apiFn, errorTitle, successTitle, materialCodes }) { const productionOrderNo = this.params.productionOrderNo; if (!productionOrderNo) { this.$modal.msgError("未获取到生产订单号,请检查!"); return; } + if (isJsyyOrderNo(productionOrderNo)) { + this.$modal.msgError(this.jsyyDenyMsg); + return; + } + if (this.pushRouteStatusLocked) { + this.$modal.msgError(this.pushRouteStatusLockMsg); + return; + } if (!this.routeGroupName || !this.routeGroupName.trim()) { this.$modal.msgError("请填写分组名称!"); return; @@ -1448,10 +1622,13 @@ export default { }, timeout); }); const response = await Promise.race([ - apiFn(productionOrderNo, this.routeGroupName.trim()), + apiFn(productionOrderNo, this.routeGroupName.trim(), materialCodes), timeoutPromise ]); - const { duplicateRoutes, failedRoutes, successfulRoutes } = response.data; + const result = (response && response.data) || {}; + const duplicateRoutes = result.duplicateRoutes || []; + const failedRoutes = result.failedRoutes || []; + const successfulRoutes = result.successfulRoutes || []; const duplicateCodes = duplicateRoutes.join(', '); const failedInfo = failedRoutes.map(route => `${route.materialCode} (${route.errorMessage || '未知错误'})` @@ -1470,6 +1647,9 @@ export default { }); await this.getList(); } catch (error) { + if (error === 'cancel') { + return; + } console.error(`[${errorTitle}]:`, error); this.$notify({ title: errorTitle, @@ -1486,6 +1666,25 @@ export default { } }, + collectSelectedMaterialCodes(rows) { + const codes = []; + const seen = new Set(); + const source = rows || this.selectedRows || []; + source.forEach(row => { + const code = row && row.materialCode ? String(row.materialCode).trim() : ""; + if (!code) { + return; + } + const key = code.toUpperCase(); + if (seen.has(key)) { + return; + } + seen.add(key); + codes.push(code); + }); + return codes; + }, + /**推送工艺 */ pushRouteBom() { return this.executeRoutePush({ @@ -1496,6 +1695,43 @@ export default { }); }, + /** 勾选物料后单独推送金蝶 */ + pushSelectedMaterialRoute() { + const codes = this.collectSelectedMaterialCodes(); + if (!codes.length) { + this.$modal.msgError("请先勾选要推送的物料"); + return; + } + return this.executeRoutePush({ + confirmMessage: `将只推送所选物料(${codes.join('、')})的工艺到金蝶,不会整单推送、也不会改项目状态。是否继续?`, + loadingText: '正在推送所选物料工艺,请稍候...', + apiFn: pushRouteBomByMaterial, + errorTitle: '推送选中物料失败', + successTitle: '物料推送完成', + materialCodes: codes + }); + }, + + /** 物料行单独推送 */ + pushMaterialRoute(row) { + const code = row && row.materialCode ? String(row.materialCode).trim() : ""; + if (!code) { + this.$modal.msgError("该行没有物料编码"); + return; + } + this.pushingMaterialCode = code; + return this.executeRoutePush({ + confirmMessage: `将只推送物料 ${code} 的工艺到金蝶,不会整单推送、也不会改项目状态。是否继续?`, + loadingText: `正在推送物料 ${code} 的工艺,请稍候...`, + apiFn: pushRouteBomByMaterial, + errorTitle: '推送该物料工艺失败', + successTitle: '物料推送完成', + materialCodes: [code] + }).finally(() => { + this.pushingMaterialCode = ""; + }); + }, + /** 审核通过:更新状态并发送钉钉通知,不推送金蝶、不发送工艺计划表 */ async approveRoute() { const productionOrderNo = this.params.productionOrderNo; @@ -1597,39 +1833,166 @@ export default { this.loading = false; }); }, - generatePDFs1(row) { - // 显示加载状态 - this.loading = true; - // 调用 reset 方法,重置表单或状态 - this.reset(); - // 获取生产订单号 - const rooteProdet = this.params.productionOrderNo; - console.log(rooteProdet); - - // 调用封装好的 API 方法 + /** + * 工具栏「生成PDF」:当前令号下全部工序计划单打 zip。 + */ + generatePDFs1() { + const rooteProdet = (this.params && this.params.productionOrderNo) + || this.queryParams.routeDescription + || this.$route.query.productionOrderNo; + if (!rooteProdet) { + this.$modal.msgWarning("请先选择项目令号"); + return; + } + this.generatingAllPdf = true; + const loading = this.$loading({ + lock: true, + text: "正在生成全部工序计划单PDF,请稍候...", + spinner: "el-icon-loading", + background: "rgba(0, 0, 0, 0.7)" + }); generatePDFs(rooteProdet) - .then(response => { - - let blob = new Blob([response], { type: 'application/zip' }) - let url = window.URL.createObjectURL(blob) - const link = document.createElement('a') // 创建a标签 - link.href = url - link.download = rooteProdet + '.zip' // 重命名文件 - link.click() - URL.revokeObjectURL(url) // 释放内存 - // 弹出成功消息 - this.$modal.msgSuccess("生成" + rooteProdet + "的PDF 成功 "); - // 调用方法更新列表 - this.getList(); + .then(async (response) => { + const isBlob = blobValidate(response); + if (!isBlob) { + const resText = await response.text(); + const rspObj = JSON.parse(resText); + this.$modal.msgError((rspObj && rspObj.msg) || "生成PDF失败"); + return; + } + const blob = new Blob([response], { type: "application/zip" }); + const url = window.URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = rooteProdet + "_工序计划单.zip"; + link.click(); + URL.revokeObjectURL(url); + this.$modal.msgSuccess("生成" + rooteProdet + "的PDF成功"); }) - .catch(error => { - // 处理错误,弹出错误提示 + .catch((error) => { this.$modal.msgError("生成PDF失败,请重试!"); - console.error('生成PDF失败:', error); + console.error("生成PDF失败:", error); }) .finally(() => { - // 隐藏加载状态 - this.loading = false; + this.generatingAllPdf = false; + loading.close(); + }); + }, + /** + * 物料行「下载PDF」:用当前令号 + 该行物料编码查金蝶生产订单,直接下单份 PDF。 + */ + downloadMaterialPdf(row) { + const materialCode = row && row.materialCode ? String(row.materialCode).trim() : ""; + const rooteProdet = (this.params && this.params.productionOrderNo) + || this.queryParams.routeDescription + || this.$route.query.productionOrderNo; + if (!rooteProdet) { + this.$modal.msgWarning("请先选择项目令号"); + return; + } + if (!materialCode) { + this.$modal.msgWarning("该行没有物料编码"); + return; + } + this.downloadingPdfCode = materialCode; + generatePDFs(rooteProdet, materialCode) + .then(async (response) => { + const isBlob = blobValidate(response); + if (!isBlob) { + const resText = await response.text(); + const rspObj = JSON.parse(resText); + this.$modal.msgError((rspObj && rspObj.msg) || "下载PDF失败"); + return; + } + const filename = `${rooteProdet}_${materialCode}_工序计划单.pdf`; + const blob = new Blob([response], { type: "application/pdf" }); + const url = window.URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = filename; + link.click(); + URL.revokeObjectURL(url); + this.$modal.msgSuccess(`已下载物料 ${materialCode} 的工序计划单`); + }) + .catch((error) => { + this.$modal.msgError("下载PDF失败,请重试!"); + console.error("下载PDF失败:", error); + }) + .finally(() => { + this.downloadingPdfCode = ""; + }); + }, + rowMoreVisible(row) { + if (!row || row.parentId || !row.materialCode) return false + return checkPermi(['system:route:pushRouteBom']) + || checkPermi(['system:route:generatePDFs']) + || checkPermi(['system:route:downloadDrawingModel']) + }, + handleRowMore(row, command) { + if (!command) return + if (command === 'push') { + this.pushMaterialRoute(row) + return + } + if (command === 'pdf') { + this.downloadMaterialPdf(row) + return + } + if (String(command).indexOf('model:') === 0) { + this.downloadDrawingModel(row, String(command).slice(6)) + } + }, + /** + * 物料行「下载图纸模型」:仅 PLM 项目。前端请求本系统,由后端转发文件服务。 + */ + downloadDrawingModel(row, fileType) { + const materialCode = row && row.materialCode ? String(row.materialCode).trim() : ""; + const format = this.drawingModelFormats.find(item => item.type === fileType); + const productionOrderNo = (this.params && this.params.productionOrderNo) + || this.queryParams.routeDescription + || this.$route.query.productionOrderNo; + if (!this.isPlmProject) { + this.$modal.msgWarning("仅 PLM 来源项目可下载图纸模型"); + return; + } + if (!productionOrderNo) { + this.$modal.msgWarning("请先选择项目令号"); + return; + } + if (!materialCode) { + this.$modal.msgWarning("该行没有物料编码"); + return; + } + if (!format) { + this.$modal.msgWarning("请选择图纸格式"); + return; + } + this.downloadingDrawingCode = materialCode; + requestDrawingModel(productionOrderNo, materialCode, fileType) + .then(async (response) => { + const isBlob = blobValidate(response); + if (!isBlob) { + const resText = await response.text(); + const rspObj = JSON.parse(resText); + this.$modal.msgError((rspObj && rspObj.msg) || "下载图纸模型失败"); + return; + } + const filename = `${materialCode}.${fileType}`; + const blob = new Blob([response], { type: "application/octet-stream" }); + const url = window.URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = filename; + link.click(); + URL.revokeObjectURL(url); + this.$modal.msgSuccess(`已下载物料 ${materialCode} 的 ${format.label}`); + }) + .catch((error) => { + this.$modal.msgError("下载图纸模型失败,请重试!"); + console.error("下载图纸模型失败:", error); + }) + .finally(() => { + this.downloadingDrawingCode = ""; }); }, @@ -1979,12 +2342,50 @@ export default { }; + diff --git a/src/views/system/workAlertGroupPush/index.vue b/src/views/system/workAlertGroupPush/index.vue new file mode 100644 index 0000000..b09e67d --- /dev/null +++ b/src/views/system/workAlertGroupPush/index.vue @@ -0,0 +1,672 @@ + + + + + diff --git a/src/views/workbench/design/index.vue b/src/views/workbench/design/index.vue new file mode 100644 index 0000000..4c2f7a6 --- /dev/null +++ b/src/views/workbench/design/index.vue @@ -0,0 +1,1579 @@ + + + + + diff --git a/src/views/workbench/index.vue b/src/views/workbench/index.vue index 34484f4..6897678 100644 --- a/src/views/workbench/index.vue +++ b/src/views/workbench/index.vue @@ -1,5 +1,6 @@