feat:多项功能优化
This commit is contained in:
@@ -3,16 +3,16 @@
|
||||
// 说明:这里的"数据来源"特指该字段的数据由谁产生、从哪个系统/页面来,帮助跑通真实流程。
|
||||
|
||||
export const helpProductType = {
|
||||
title: '产品型号(档案维护)',
|
||||
title: '物料档案(档案维护)',
|
||||
overview:
|
||||
'产品型号 = 企业要生产哪些产品的分类档案,是整条产线的源头基础数据。\n\n主数据链条:产品型号 → 工单(选产品型号+物料清单) → 物料清单(给该产品编号配料) → 日排产 → 备料单 → 库房出库 → 产线生产 → 完工成品自动回流 WMS 入库。\n\n关系说明(几对几):\n· 产品型号 ↔ 工单:1 对 N。一个产品型号可以建多个工单(同一型号今天做一批、明天做一批,工单号不同、产品编号相同)。\n· 产品型号 ↔ 物料清单:1 对多(选用制)。同一型号可并存多份物料清单(不同厂家供货/含半成品结构),用「物料清单名称」区分,工单建单时选定一份,备料/装机都按工单锁定的那份算。\n· 产品型号 ↔ 备料单:间接。备料单由「日排产 × 工单锁定的物料清单」算出,与产品型号无直接约束。\n· 强约束:工单/物料清单都挂在产品编号下,产品型号编码变了,下游关联会断,建议建好后不要改。\n· 与 WMS 的关系:两套系统解耦。产品/图号主数据以 WMS 为单一来源,MES 实时拉取(WMS 不可达时允许手填、仅提示不强校验);成品完工后由系统自动生成 SN 回流 WMS 入库,无需人工对账。',
|
||||
'物料档案 = 本页是 WMS「基础数据(物料主数据)」中"成品"品类在本系统的视图,与 WMS 指向同一份主数据(以 WMS 为准,实时拉取、不双录)。本页承载的是"要生产哪些成品",是整条产线的源头基础数据。\n\n品类来源(与 WMS 一致):原材料=进货来的(外购);半成品、成品=自己生产的;其他=辅料/工装/试验设备等。成品还可以作为零部件,放进另一个成品的物料清单(BOM)里被再次加工成新的成品。\n\n主数据链条:物料档案(成品) → 工单(选成品+物料清单) → 物料清单(给该产品编号配料) → 日排产 → 备料单 → 库房出库 → 产线生产 → 完工成品自动回流 WMS 入库。\n\n关系说明(几对几):\n· 成品 ↔ 工单:1 对 N。一个成品可以建多个工单(同一型号今天做一批、明天做一批,工单号不同、产品编号相同)。\n· 成品 ↔ 物料清单:1 对多(选用制)。同一成品可并存多份物料清单(不同厂家供货/含半成品结构),用「物料清单名称」区分,工单建单时选定一份,备料/装机都按工单锁定的那份算。\n· 成品 ↔ 备料单:间接。备料单由「日排产 × 工单锁定的物料清单」算出,与成品无直接约束。\n· 强约束:工单/物料清单都挂在产品编号下,成品编码变了,下游关联会断,建议建好后不要改。\n· 与 WMS 的关系:两套系统解耦。物料/图号主数据以 WMS 为单一来源,MES 实时拉取(WMS 不可达时降级为本地只读缓存);成品完工后由系统自动生成 SN 回流 WMS 入库,无需人工对账。',
|
||||
fields: [
|
||||
{ name: '名称', source: '手工填写。用户自定义,无上游来源。', purpose: '产品型号的中文名,用于识别与下拉展示', fill: '如:智能电表、断路器、配电柜' },
|
||||
{ name: '名称', source: '手工填写。用户自定义,无上游来源。', purpose: '成品的中文名,用于识别与下拉展示', fill: '如:智能电表、断路器、配电柜' },
|
||||
{
|
||||
name: '编码',
|
||||
source: '系统自动生成 + 可手动修改。新建时会自动带出(格式 PROD-YYYYMMDD-序号),你可直接使用或改为自定义编码;编辑时保留已存值。一个产品编号可对应多个工单(同一型号今天做一批、明天做一批,工单号不同,产品编号相同)。',
|
||||
purpose: '产品唯一编码。它是 MES 内部识别该产品的标识,决定该产品的物料清单内容,也是工单、备料、拧紧、追溯的挂靠键。',
|
||||
fill: '建议用有意义的编码,如 METER-01。要点:①编码主数据以 WMS(库房) 为单一来源,本系统实时从 WMS 拉取物料/产品档案;WMS 不可达时允许手填、仅提示不强校验;②成品完工后系统自动生成 SN 并回流 WMS 入库,无需人工对账;③如产品编号就是"产品型号",则直接沿用该类型编码即可;④建好后不要随意改,否则下游工单/追溯的关联会断。'
|
||||
fill: '建议用有意义的编码,如 METER-01。要点:①编码主数据以 WMS(库房) 为单一来源,本系统实时从 WMS 拉取物料/成品档案;WMS 不可达时降级为本地只读缓存;②成品完工后系统自动生成 SN 并回流 WMS 入库,无需人工对账;③如产品编号就是成品编码,则直接沿用该编码即可;④建好后不要随意改,否则下游工单/追溯的关联会断。'
|
||||
},
|
||||
{ name: '类别', source: '手工填写。可选,无上游来源。', purpose: '产品所属大类,便于分类统计', fill: '如:计量表 / 开关柜;可空' },
|
||||
{ name: '启用', source: '开关选择。默认开启。', purpose: '启用后才允许在新建工单/备料的下拉里被选到', fill: '默认开启;暂时停产的可关闭(不删除,保留档案)' },
|
||||
@@ -23,13 +23,13 @@ export const helpProductType = {
|
||||
export const helpWorkOrder = {
|
||||
title: '工单管理(新建 / 编辑 / 删除工单)',
|
||||
overview:
|
||||
'工单 = 一个"合同/生产批次"的唯一载体,是生产执行和仓库出库挂靠的核心对象。\n\n全流程:先有产品型号 → 建工单(选产品型号、选定物料清单、定数量、勾选工位组合) → 进"物料清单"给该产品配料 → 到"日排产"排哪天产多少 → 仓库按排产备料出库 → 产线装配/拧紧/报工 → 完工(成品 SN 系统自动生成,并自动回流 WMS 入库为"成品"品类)。\n工单号会贯穿 备料单、拧紧查询、手动报工、工件追溯、操作日志 全链路。\n\n工程编号:一个合同对应一个工程编号,一个工程编号下对应各个产品的产品序号——即「合同号 → 工程编号 → 产品序号」三级归属,用于按合同/工程项目归集与追溯。\n\n查询:支持按 工单号 / 产品编号 / 产品名称(模糊) / 合同号 / 工程编号 / 状态 组合筛选;删除:仅「已创建 / 待排产 / 已发布」且无排产、备料、生产数据的工单可删除(操作列「删除」),生产中或已完成的工单不可删除。',
|
||||
'工单 = 一个"合同/生产批次"的唯一载体,是生产执行和仓库出库挂靠的核心对象。\n\n全流程:先有产品型号(在「物料档案」里建档) → 建工单(选产品型号、选定物料清单、定数量、勾选工位组合) → 进"物料清单"给该产品配料 → 到"日排产"排哪天产多少 → 仓库按排产备料出库 → 产线装配/拧紧/报工 → 完工(成品 SN 系统自动生成,并自动回流 WMS 入库为"成品"品类)。\n工单号会贯穿 备料单、拧紧查询、手动报工、工件追溯、操作日志 全链路。\n\n工程编号:一个合同对应一个工程编号,一个工程编号下对应各个产品的产品序号——即「合同号 → 工程编号 → 产品序号」三级归属,用于按合同/工程项目归集与追溯。\n\n查询:支持按 工单号 / 产品编号 / 产品名称(模糊) / 合同号 / 工程编号 / 状态 组合筛选;删除:仅「已创建 / 待排产 / 已发布」且无排产、备料、生产数据的工单可删除(操作列「删除」),生产中或已完成的工单不可删除。',
|
||||
sections: [
|
||||
{
|
||||
title: '工单基本信息',
|
||||
fields: [
|
||||
{ name: '工单号', source: '系统自动生成(WO+时间戳),可手工改。新建时自动带出。', purpose: '唯一标识一个工单,备料/出库/报工/追溯都挂在此号下', fill: '一般不用改;如改动须保证唯一' },
|
||||
{ name: '产品型号', source: '下拉选择。数据来自"产品型号"页面已启用的记录。', purpose: '说明这批生产的是什么产品,并决定物料清单内容', fill: '先到"产品型号"页面建好记录再选择' },
|
||||
{ name: '产品型号', source: '下拉选择。数据来自「物料档案」页面已启用的记录。', purpose: '说明这批生产的是什么产品,并决定物料清单内容', fill: '先到「物料档案」页面建好记录再选择' },
|
||||
{ name: '合同号', source: '手工填写。', purpose: '标记本工单所属的合同(一个合同对应一个工程编号)', fill: '如 HT-2026-001(可空)' },
|
||||
{ name: '工程编号', source: '手工填写。', purpose: '标记本工单所属的工程编号(一个工程编号下挂多个产品序号)', fill: '如 GC-2026-001(可空)' },
|
||||
{ name: '产品序号', source: '手工填写。', purpose: '本产品在该工程编号下的序号,用于区分同工程下的不同产品', fill: '如 01、02(可空)' },
|
||||
@@ -81,10 +81,10 @@ export const helpDailyPlan = {
|
||||
export const helpBom = {
|
||||
title: '产品物料清单',
|
||||
overview:
|
||||
'物料清单 = 生产 1 台该产品需要哪些物料、各用多少。\n按「产品编号 + 物料清单名称」维护:同一产品编号可并存多份物料清单(选用制)——不同厂家供货、含半成品的结构差异,都用不同的物料清单名称区分(缺省叫「默认」,不用 V1/V2 版本号)。\n工单建单时选定其中一份,之后该工单的备料算料、装机绑定校验都锁定这份物料清单,不同厂家的料单互不串。\n\n与 WMS 的关系:本页(MES)只维护「产品由哪几种料、各用几个」;每种物料叫什么、规格、是电气件(SN)还是结构件(批次)、库存有多少,统一在 WMS(库房)的「物料档案」里维护,两套系统互相解耦、通过图号对齐。\n\n编码对应规则:本页的「图号」必须与 WMS 物料档案里的「图号」完全一致;「管理方式」也要对应(电气件=SN 逐件,结构件=批次)。编码对不上,WMS 将无法为对应工单备料/出库。\n\n保护规则:某份物料清单一旦被工单选用过,就不允许再从中移除物料(防止历史工单追溯断链);新建/改名的物料清单名称在本页「物料清单名称」下拉直接输入即可创建。\n\n数据链路:先建「产品型号」→ 到本页选产品编号 + 命名物料清单,逐条添加物料 → 建工单时选定物料清单 → 仓库才能为对应工单领料。',
|
||||
'物料清单 = 生产 1 台该产品需要哪些物料、各用多少。\n按「产品编号 + 物料清单名称」维护:同一产品编号可并存多份物料清单(选用制)——不同厂家供货、含半成品的结构差异,都用不同的物料清单名称区分(缺省叫「默认」,不用 V1/V2 版本号)。\n工单建单时选定其中一份,之后该工单的备料算料、装机绑定校验都锁定这份物料清单,不同厂家的料单互不串。\n\n与 WMS 的关系:本页(MES)只维护「产品由哪几种料、各用几个」;每种物料叫什么、规格、是电气件(SN)还是结构件(批次)、库存有多少,统一在 WMS(库房)的「物料档案」里维护,两套系统互相解耦、通过图号对齐。\n\n编码对应规则:本页的「图号」必须与 WMS 物料档案里的「图号」完全一致;「管理方式」也要对应(电气件=SN 逐件,结构件=批次)。编码对不上,WMS 将无法为对应工单备料/出库。\n\n保护规则:某份物料清单一旦被工单选用过,就不允许再从中移除物料(防止历史工单追溯断链);新建/改名的物料清单名称在本页「物料清单名称」下拉直接输入即可创建。\n\n数据链路:先建「物料档案」→ 到本页选产品编号 + 命名物料清单,逐条添加物料 → 建工单时选定物料清单 → 仓库才能为对应工单领料。',
|
||||
fields: [
|
||||
{ name: '物料清单名称', source: '下拉选择/输入(可搜索,输入新名即创建)。缺省「默认」。', purpose: '同一产品编号下多份物料清单的区分名,工单建单时按此选定', fill: '如「默认」「A厂料单」「含半成品」;同一型号下名称唯一' },
|
||||
{ name: '产品编号', source: '下拉选择。来自「产品型号」里建的编码。', purpose: '要为哪个产品配置物料清单', fill: '在查询区或「添加物料」弹窗里选择产品编号' },
|
||||
{ name: '产品编号', source: '下拉选择。来自「物料档案」里建的编码。', purpose: '要为哪个产品配置物料清单', fill: '在查询区或「添加物料」弹窗里选择产品编号' },
|
||||
{ name: '图号', source: '手工填写。必须与 WMS(库房) 物料档案的图号一致。', purpose: '该物料在库房的全库唯一编码,两系统靠它关联', fill: '如 螺丝=GJ-LUOSI-M16;图号对不上 WMS 将无法备料' },
|
||||
{ name: '物料名称', source: '手工填写。', purpose: '物料中文名,便于识别', fill: '如 十字螺丝、主控芯片' },
|
||||
{ name: '规格', source: '手工填写。', purpose: '物料规格型号', fill: '如 M16×80 / SMT-48脚' },
|
||||
@@ -184,11 +184,15 @@ export const helpScan = {
|
||||
export const helpStation = {
|
||||
title: '关联工位(工位总览)',
|
||||
overview:
|
||||
'工位数以数据库为准(仅可修改名称,不增删工位)。本页是工位总览:查看每个工位当前绑定了哪个工艺流程,可修改工位名称。\n\n绑定关系由本页单独维护:在下拉中选择「启用」的工艺流程即绑定到该工位;一个工位只能有一个启用的工艺流程(先停用旧流程再绑新流程);流程停用后自动解绑。\n\n数据链路:关联工位页(选启用流程绑定) → 手动报工/工位终端按该流程的工序步骤采集 → 报工判定 合格/不合格。',
|
||||
'工位数以数据库为准。工位分「内置」与「自定义」两类:内置工位由种子/初始化数据产生(数据库 is_builtin=true),对应实体产线位置,需与 PLC 通讯并必须按工位号顺序流转,不允许修改工位类型、不允许删除;自定义工位是在本页点击「新增工位」创建的,可修改类型、可删除。\n\n本页是工位总览:查看每个工位当前绑定了哪个工艺流程,可修改工位名称、删除自定义工位。\n\n绑定关系由本页单独维护:在下拉中选择「启用」的工艺流程即绑定到该工位;一个工位只能有一个启用的工艺流程(先停用旧流程再绑新流程);流程停用后自动解绑。\n\n「是否有接驳台」对应实体位置(1~10 号工位各 2 个接驳台;0/11/12/13 号无),由内置数据给定,页面不可手动更改。\n\n数据链路:关联工位页(选启用流程绑定) → 手动报工/工位终端按该流程的工序步骤采集 → 报工判定 合格/不合格。',
|
||||
fields: [
|
||||
{ name: '工位号', source: '系统固定(工位数以数据库为准),不可增删。', purpose: '产线物理工位编号', fill: '无需填写' },
|
||||
{ name: '工位号', source: '内置工位由系统预置;自定义工位由本页「新增工位」填写。', purpose: '产线物理工位编号', fill: '新增时自定义,可与现有工位不连续(如 14、15…)' },
|
||||
{ name: '工位名称', source: '可编辑。默认如 装配工位1。', purpose: '工位的显示名称', fill: '如 一号装配工位' },
|
||||
{ name: '类型', source: '内置工位由系统给定且不可修改;自定义工位可改。', purpose: '线上位(连 PLC)/线下位(虚拟,不连 PLC)', fill: '内置工位只读;自定义工位可在改名弹窗切换' },
|
||||
{ name: '来源', source: '系统标识(数据库 is_builtin)。', purpose: '区分内置工位与自定义工位', fill: '只读' },
|
||||
{ name: '接驳台', source: '内置数据给定(对应实体位置),不可手动更改。', purpose: '该工位位置是否有接驳台', fill: '只读' },
|
||||
{ name: '绑定工艺流程', source: '自动来自本页:为工位选择「启用」的工艺流程即绑定。', purpose: '该工位执行的工艺流程(决定工序步骤/考核标准/PDF)', fill: '到本页为该工位选择启用流程;本页只读展示' },
|
||||
{ name: '删除', source: '本页操作。', purpose: '删除自定义工位', fill: '仅自定义工位可删除;内置工位不显示删除按钮' },
|
||||
{ name: '虚拟工位 0 / 13', source: '系统预置。工位号 0=上线位、13=下线位。', purpose: '产线进线口建档上线、出料口完工入库,由 PAD 操作,不连 PLC/拧紧枪', fill: '只读;上线位/下线位在工位终端(PAD)用「上线建档」「下线完工」面板操作' }
|
||||
]
|
||||
}
|
||||
@@ -391,7 +395,7 @@ export const helpProcessCard = {
|
||||
overview:
|
||||
'这个菜单做什么用:按**产品型号**全方位展示该型号的完整制造定义,用于工艺交底、审核与客户查阅;与「工件追溯」页里按单个 SN 打印的流程卡互补——后者记的是“这一件实际怎么做的”,本页讲的是“这个型号应该怎么做”。\n\n内容构成(页面按页签展示,打印时全部输出):①工位流程总览——工位组合、每工位绑定的工艺流程、流程状态、步骤数、检验点数、拧紧步骤数、工艺图纸、该型号的历史作业时间;②工艺流程与步骤——逐工位列出每条步骤的采集方式(手工录入/自动采集/无需采集)、是否需检测确认、是否拧紧采集、考核标准(含上下限);③物料清单——该型号的物料清单明细,按物料清单名称分组,含图号/名称/规格/单位/管理方式/装配工位/单台用量/损耗率/需求总量;④检验卡项——把所有「需要检测确认」的步骤汇总成检验项清单,含判定标准。\n\n工位组合从哪来:优先取该产品编号最近一张工单的「工位组合」;若该型号还没有工单,则按全产线工位顺序(升序)展示,页面顶部会标注来源。\n\n打印/导出:点「打印 / 导出 PDF」,系统会在新窗口打开排版好的 A4 卡片版式,用浏览器打印即可(选择“另存为 PDF”即导出电子档)。',
|
||||
fields: [
|
||||
{ name: '产品型号', source: '下拉选择,来自「产品型号」页。', purpose: '要查看哪个型号的全景流程', fill: '选定后自动带出产品编号' },
|
||||
{ name: '产品型号', source: '下拉选择,来自「物料档案」页。', purpose: '要查看哪个型号的全景流程', fill: '选定后自动带出产品编号' },
|
||||
{ name: '产品编号', source: '自动带出(也可手工填写,直接按编号生成)。', purpose: '匹配工单、物料清单的关键字', fill: '一般无需手填' },
|
||||
{ name: '工位组合', source: '只读,自动带出。', purpose: '该型号要走哪些工位、按什么顺序', fill: '-' },
|
||||
{ name: '工位流程总览-工艺图纸', source: '来自「工艺流程」页上传的图纸文件名。', purpose: '该工位是否有可查阅的工艺图', fill: '-' },
|
||||
@@ -401,23 +405,6 @@ export const helpProcessCard = {
|
||||
}
|
||||
|
||||
|
||||
export const helpQtyReport = {
|
||||
title: '数量不符上报',
|
||||
overview:
|
||||
'这个菜单做什么用:记录「工位收料时实收数量 ≠ 备料单应发数量」的差异,形成闭环——上报→落预警→生产/仓库核实处理。\n\n上报入口有两处:①工位终端「巡检/异常」面板——操作工现场发现数量不符时直接上报;②本页(MES 电脑端)——由生产调度/线长代录或复核。\n\n差异怎么算:系统自动按「实收 − 应发」计算差异(正数=多收、负数=少收);上报后同步落一条预警中心消息(type=qty_diff),未读状态可提醒相关人员。\n\n处理流程:仓库/生产核实后在预警中心标记已读,本页记录保持存档,供后续对账与追溯。',
|
||||
fields: [
|
||||
{ name: '工位', source: '工位终端上报自动带入;本页手录时手动选择(工位数以数据库为准)。', purpose: '差异发生在哪个工位', fill: '选工位号' },
|
||||
{ name: '工单号', source: '工位终端上报自动带入;来自【工单管理】。', purpose: '定位是哪张工单的物料', fill: '搜索/输入工单号' },
|
||||
{ name: '产品序列号', source: '工位终端上报自动带入(当前作业件)。', purpose: '追溯到具体那一件', fill: '扫描/输入 SN' },
|
||||
{ name: '图号/名称', source: '工位终端上报自动带入(当前收料物料)。', purpose: '是哪一种物料数量不符', fill: '选择/输入物料' },
|
||||
{ name: '应发数量', source: '手动/自动带入备料单应发数。', purpose: '作为差异计算基准', fill: '填应发数' },
|
||||
{ name: '实收数量', source: '手动填写(现场实际清点结果)。', purpose: '作为差异计算基准', fill: '填实收数' },
|
||||
{ name: '差异', source: '系统自动计算(实收−应发)。', purpose: '一眼看出多收/少收', fill: '只读' },
|
||||
{ name: '原因', source: '手动填写。', purpose: '说明数量不符的原因(如供应商短装/破损/漏发)', fill: '描述原因' },
|
||||
{ name: '状态', source: '系统维护(待处理/已处理)。', purpose: '跟踪闭环进度', fill: '只读,筛选用' }
|
||||
]
|
||||
}
|
||||
|
||||
export const helpQuality = {
|
||||
title: '质量检验(过程检 / 完工检)',
|
||||
overview:
|
||||
@@ -450,9 +437,11 @@ export const helpWip = {
|
||||
export const helpAlertCenter = {
|
||||
title: '预警中心(系统自动触发)',
|
||||
overview:
|
||||
'预警中心汇集全系统自动产生的异常/提醒消息,人工无需配置规则——全部由内置触发源自动落库并推送。顶部铃铛角标显示未读数,点击进入本页处理。\n\n八类内置触发源:\n· 扭紧不合格(torque_ng):拧紧扭矩/角度超考核标准时自动告警;\n· 检验不合格(inspection_ng):质量检验判定 NG 时告警;\n· 备料未齐套(material_shortage):工单装机绑定物料未齐套时告警;\n· 库存偏低(inventory_low):可用量低于安全库存时告警;\n· 工单超时(task_overdue):工单超过完成日期未完工时告警;\n· 缓存位满(cache_full):产线缓存/接驳台占满时告警;\n· 数量不符(qty_diff):工位收料实收≠应发时告警(见「数量不符上报」);\n· 应急呼叫(emergency_call):工位终端现场发起的紧急呼叫。\n\n应急呼叫闭环:操作工在工位终端点「应急呼叫」(设备故障/质量异常/缺料/安全等紧急情形)→ 系统即时落一条 type=emergency_call 的未读预警并通过 SSE 推到大屏/看板 → 线长、维修、仓库等相关人员在本页看到未读告警并到场处置 → 处置完点「标记已读」闭环。\n\n处理流程:按状态(全部/未读/已读)筛选,核实后点行末「标记已读」;已读记录留存备查。',
|
||||
'预警中心汇集全系统自动产生的异常/提醒消息,人工无需配置规则——全部由内置触发源自动落库并推送。顶部铃铛角标显示未读数,点击进入本页处理。\n\n八类内置触发源:\n· 扭紧不合格(torque_ng):拧紧扭矩/角度超考核标准时自动告警;\n· 检验不合格(inspection_ng):质量检验判定 NG 时告警;\n· 备料未齐套(material_shortage):工单装机绑定物料未齐套时告警;\n· 库存偏低(inventory_low):可用量低于安全库存时告警;\n· 工单超时(task_overdue):工单超过完成日期未完工时告警;\n· 缓存位满(cache_full):产线缓存/接驳台占满时告警;\n· 数量不符(qty_diff):工位收料实收≠应发时告警(MES 仅预警,明细由 WMS 处理);\n· 应急呼叫(emergency_call):工位终端现场发起的紧急呼叫。\n\n应急呼叫闭环:操作工在工位终端点「应急呼叫」(设备故障/质量异常/缺料/安全等紧急情形)→ 系统即时落一条 type=emergency_call 的未读预警并通过 SSE 推到大屏/看板 → 线长、维修、仓库等相关人员在本页看到未读告警并到场处置 → 处置完点「标记已读」闭环。\n\n处理流程:按 状态(全部/未读/已读)、预警类型、开始时间(默认当天)、结束时间(默认不限)筛选并分页浏览,核实后点行末「标记已读」;已读记录留存备查。',
|
||||
fields: [
|
||||
{ name: '状态', source: '系统维护(未读/已读)。', purpose: '区分待处理与已处理预警', fill: '顶部单选筛选:全部/未读/已读' },
|
||||
{ name: '状态', source: '系统维护(未读/已读)。', purpose: '区分待处理与已处理预警', fill: '筛选项单选:全部/未读/已读' },
|
||||
{ name: '预警类型', source: '内置触发源自动写入。', purpose: '只关注某一类异常', fill: '下拉选择八类之一,留空=全部' },
|
||||
{ name: '开始时间 / 结束时间', source: '系统记录触发时刻。', purpose: '按时间范围缩小排查范围', fill: '开始时间默认当天;结束时间留空=不限' },
|
||||
{ name: '类型', source: '内置触发源自动写入。', purpose: '区分八类预警(含应急呼叫)', fill: '只读' },
|
||||
{ name: '标题 / 内容', source: '触发时系统自动生成。', purpose: '说明预警的具体对象与原因', fill: '只读' },
|
||||
{ name: '关联ID', source: '系统写入(工单号/SN/工位等业务键)。', purpose: '定位预警来源的具体业务对象', fill: '只读' },
|
||||
|
||||
@@ -151,8 +151,7 @@ const menuGroups = [
|
||||
title: '基础配置', icon: Box, children: [
|
||||
{ code: 'produce.processflow', name: '工艺流程', path: '/process-flow', icon: Operation },
|
||||
{ code: 'produce.station', name: '关联工位', path: '/station', icon: Link },
|
||||
{ code: 'produce.product', name: '产品型号', path: '/product-type', icon: Box },
|
||||
{ code: 'produce.qtyreport', name: '数量不符上报', path: '/qty-report', icon: Warning }
|
||||
{ code: 'produce.product', name: '物料档案', path: '/product-type', icon: Box }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,18 +1,37 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<!-- 预警中心:不设「规则设置」Tab。预警全部由系统内置触发源自动产生,人工无需配置规则。 -->
|
||||
<el-form inline>
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="statusFilter">
|
||||
<el-radio-button label="all">全部</el-radio-button>
|
||||
<el-radio-button label="UNREAD">未读</el-radio-button>
|
||||
<el-radio-button label="READ">已读</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="预警类型">
|
||||
<el-select v-model="typeFilter" clearable style="width:150px" placeholder="全部">
|
||||
<el-option v-for="t in types" :key="t.value" :label="t.label" :value="t.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="开始时间">
|
||||
<el-date-picker v-model="startDate" type="date" value-format="YYYY-MM-DD" placeholder="开始时间" style="width:150px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间">
|
||||
<el-date-picker v-model="endDate" type="date" value-format="YYYY-MM-DD" placeholder="结束时间" style="width:150px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="search">查询</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div style="margin-bottom:12px;display:flex;align-items:center;gap:12px;flex-wrap:wrap">
|
||||
<el-radio-group v-model="statusFilter" @change="loadAlerts">
|
||||
<el-radio-button label="all">全部</el-radio-button>
|
||||
<el-radio-button label="UNREAD">未读</el-radio-button>
|
||||
<el-radio-button label="READ">已读</el-radio-button>
|
||||
</el-radio-group>
|
||||
<span style="color:#888">未读:{{ unread }}</span>
|
||||
<span style="color:#aaa;font-size:12px">
|
||||
系统内置触发源自动产生预警(扭紧不合格 / 检验不合格 / 备料未齐套 / 库存偏低 / 工单超时 / 缓存位满 / 数量不符 / 应急呼叫),无需人工配置
|
||||
</span>
|
||||
</div>
|
||||
<el-table :data="alerts" border size="small">
|
||||
<el-table :data="alerts" border size="small" v-loading="loading">
|
||||
<el-table-column label="状态" width="90">
|
||||
<template #default="{row}"><el-tag :type="row.status==='UNREAD' ? 'danger' : 'info'">{{ row.status==='UNREAD' ? '未读' : '已读' }}</el-tag></template>
|
||||
</el-table-column>
|
||||
@@ -22,13 +41,18 @@
|
||||
<el-table-column prop="title" label="标题" min-width="140" />
|
||||
<el-table-column prop="content" label="内容" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="refId" label="关联ID" width="120" />
|
||||
<el-table-column prop="createdAt" label="时间" min-width="170" />
|
||||
<el-table-column prop="createdAt" label="时间" min-width="170">
|
||||
<template #default="{row}">{{ fmtTime(row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="110" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button size="small" :disabled="row.status==='READ'" @click="markRead(row)">标记已读</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" background layout="total, prev, pager, next, sizes"
|
||||
:total="total" v-model:current-page="page" v-model:page-size="pageSize"
|
||||
:page-sizes="[20, 50, 100]" @current-change="loadAlerts" @size-change="search" />
|
||||
</el-card>
|
||||
<PageHelp :data="helpAlertCenter" page="AlertCenter" />
|
||||
</template>
|
||||
@@ -39,8 +63,28 @@ import PageHelp from '../components/PageHelp.vue'
|
||||
import { helpAlertCenter } from '../help'
|
||||
|
||||
const alerts = ref([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const loading = ref(false)
|
||||
const unread = ref(0)
|
||||
const statusFilter = ref('all')
|
||||
const typeFilter = ref('')
|
||||
const startDate = ref(todayStr()) // 开始时间默认当天
|
||||
const endDate = ref('') // 结束时间默认空
|
||||
|
||||
function todayStr() {
|
||||
const d = new Date()
|
||||
const p = (n) => String(n).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`
|
||||
}
|
||||
function fmtTime(v) {
|
||||
if (!v) return '-'
|
||||
const d = new Date(v)
|
||||
if (Number.isNaN(d.getTime())) return String(v)
|
||||
const p = (n) => String(n).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`
|
||||
}
|
||||
|
||||
// 内置触发源类型(仅用于展示文案,不在前端配置)
|
||||
const types = [
|
||||
@@ -59,10 +103,37 @@ function typeLabel(v) {
|
||||
}
|
||||
|
||||
async function loadAlerts() {
|
||||
const status = statusFilter.value === 'all' ? '' : statusFilter.value
|
||||
alerts.value = (await request.get('/alerts', { params: { status } })) || []
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
status: statusFilter.value === 'all' ? '' : statusFilter.value,
|
||||
type: typeFilter.value,
|
||||
from: startDate.value,
|
||||
to: endDate.value,
|
||||
page: page.value,
|
||||
pageSize: pageSize.value
|
||||
}
|
||||
const data = await request.get('/alerts', { params })
|
||||
alerts.value = data?.list || []
|
||||
total.value = data?.total || 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
// 未读数不带时间/类型筛选,始终反映全局未读,与右上角铃铛一致
|
||||
unread.value = (await request.get('/alerts', { params: { status: 'UNREAD' } }))?.length || 0
|
||||
}
|
||||
function search() {
|
||||
page.value = 1
|
||||
loadAlerts()
|
||||
}
|
||||
function reset() {
|
||||
statusFilter.value = 'all'
|
||||
typeFilter.value = ''
|
||||
startDate.value = todayStr()
|
||||
endDate.value = ''
|
||||
page.value = 1
|
||||
loadAlerts()
|
||||
}
|
||||
async function markRead(row) {
|
||||
await request.post('/alert/read', { id: row.id })
|
||||
loadAlerts()
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<!-- 附件中心:仅作集中查阅(只读)。新增/删除在各业务页面内完成(如检验页、工序步骤行)。
|
||||
本页展示文件路径,点击路径可直接下载查看。 -->
|
||||
<el-alert type="info" :closable="false" show-icon style="margin-bottom:12px"
|
||||
title="附件中心为只读查阅页:新增/删除附件请在各自业务页面(如质量检验、工序步骤)内操作" />
|
||||
<el-form inline style="margin-bottom:12px">
|
||||
<el-form-item label="业务类型">
|
||||
<el-select v-model="form.bizType" style="width:160px">
|
||||
<el-select v-model="form.bizType" clearable style="width:160px" placeholder="全部">
|
||||
<el-option label="工单" value="work_order" />
|
||||
<el-option label="物料" value="material" />
|
||||
<el-option label="工艺流程" value="process_flow" />
|
||||
<el-option label="检验" value="inspection" />
|
||||
<el-option label="质量" value="quality" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="业务ID">
|
||||
<el-input v-model="form.bizId" placeholder="如 工单号 / 图号" style="width:220px" />
|
||||
<el-input v-model="form.bizId" placeholder="如 工单号 / 图号" style="width:220px" @keyup.enter="load" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-upload action="#" :show-file-list="false" :http-request="doUpload">
|
||||
<el-button type="primary">选择文件上传</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item><el-button @click="load">查询</el-button></el-form-item>
|
||||
<el-form-item><el-button type="primary" @click="load">查询</el-button></el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="list" border size="small">
|
||||
<el-table-column prop="fileName" label="文件名" min-width="180" />
|
||||
<el-table-column label="文件路径" min-width="260">
|
||||
<template #default="{row}">
|
||||
<el-link type="primary" :underline="false" @click="download(row)">{{ row.filePath || '-' }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="fileSize" label="大小" width="110">
|
||||
<template #default="{row}">{{ fmtSize(row.fileSize) }}</template>
|
||||
</el-table-column>
|
||||
@@ -28,22 +33,16 @@
|
||||
<el-table-column prop="bizId" label="业务ID" width="140" />
|
||||
<el-table-column prop="uploader" label="上传人" width="100" />
|
||||
<el-table-column prop="createdAt" label="上传时间" min-width="170" />
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template #default="{row}">
|
||||
<el-button size="small" type="primary" @click="download(row)">下载</el-button>
|
||||
<el-button size="small" type="danger" @click="remove(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import request from '../utils/request'
|
||||
import { getToken } from '../utils/auth'
|
||||
|
||||
const form = reactive({ bizType: 'work_order', bizId: '' })
|
||||
const form = reactive({ bizType: '', bizId: '' })
|
||||
const list = ref([])
|
||||
|
||||
async function load() {
|
||||
@@ -55,25 +54,8 @@ function fmtSize(n) {
|
||||
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KB'
|
||||
return (n / 1024 / 1024).toFixed(1) + ' MB'
|
||||
}
|
||||
async function doUpload(option) {
|
||||
if (!form.bizId) {
|
||||
ElMessage.warning('请先填写业务ID再上传')
|
||||
return
|
||||
}
|
||||
const fd = new FormData()
|
||||
fd.append('file', option.file)
|
||||
fd.append('bizType', form.bizType)
|
||||
fd.append('bizId', form.bizId)
|
||||
try {
|
||||
await request.post('/attachment/upload', fd)
|
||||
ElMessage.success('上传成功')
|
||||
if (option.onSuccess) option.onSuccess()
|
||||
load()
|
||||
} catch (e) {
|
||||
if (option.onError) option.onError(e)
|
||||
}
|
||||
}
|
||||
async function download(row) {
|
||||
if (!row.filePath) return
|
||||
const token = getToken()
|
||||
const resp = await fetch('/api/v1/attachment/download?name=' + encodeURIComponent(row.filePath), { headers: { Authorization: 'Bearer ' + token } })
|
||||
if (!resp.ok) { ElMessage.error('下载失败'); return }
|
||||
@@ -85,11 +67,5 @@ async function download(row) {
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
async function remove(row) {
|
||||
await ElMessageBox.confirm('确认删除该附件?', '提示', { type: 'warning' })
|
||||
await request.post('/attachment/delete', { id: row.id })
|
||||
ElMessage.success('已删除')
|
||||
load()
|
||||
}
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
<el-table-column label="事件类型" min-width="170">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="familyTag(row.eventType)" size="small">{{ familyName(row.eventType) }}</el-tag>
|
||||
<span class="evt-code">{{ row.eventType }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="workOrderNo" label="工单号" min-width="140">
|
||||
@@ -161,11 +160,3 @@ function onReset() {
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.evt-code {
|
||||
margin-left: 6px;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<el-form-item label="结束时间"><el-date-picker v-model="filters.endDate" type="date" value-format="YYYY-MM-DD" style="width:140px" /></el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="search">查询</el-button>
|
||||
<el-button v-if="can('produce.product:add')" type="primary" plain @click="openAdd">新增产品型号</el-button>
|
||||
<el-button v-if="can('produce.product:add')" type="primary" plain @click="openAdd">新增物料档案</el-button>
|
||||
<el-button @click="exportXlsx">导出</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -47,7 +47,7 @@
|
||||
:total="total" v-model:current-page="page" v-model:page-size="pageSize"
|
||||
:page-sizes="[20, 50, 100]" @current-change="load" @size-change="search" />
|
||||
|
||||
<el-dialog v-model="dialog.show" :title="dialog.id?'编辑产品型号':'新增产品型号'" width="460px">
|
||||
<el-dialog v-model="dialog.show" :title="dialog.id?'编辑物料档案':'新增物料档案'" width="460px">
|
||||
<el-form :model="dialog" label-width="80px">
|
||||
<el-form-item label="名称"><el-input v-model="dialog.name" /></el-form-item>
|
||||
<el-form-item label="编码">
|
||||
@@ -75,7 +75,7 @@ import PageHelp from '../components/PageHelp.vue'
|
||||
import { helpProductType } from '../help'
|
||||
import { fmtTime } from '../utils/format'
|
||||
|
||||
// 数据源说明:产品型号档案已并入 WMS 物料主数据(item_type=3,品类=成品),
|
||||
// 数据源说明:物料档案已并入 WMS 物料主数据(item_type=3,品类=成品),
|
||||
// 本页经 MES 后端实时代理 WMS;WMS 不可达时降级为本地只读缓存(wmsOnline=false)。
|
||||
const list = ref([])
|
||||
const total = ref(0)
|
||||
@@ -148,7 +148,7 @@ async function exportXlsx() {
|
||||
const blob = await request.get('/product-types/export', { params, responseType: 'blob' })
|
||||
const a = document.createElement('a')
|
||||
a.href = URL.createObjectURL(blob)
|
||||
a.download = '产品型号.xlsx'
|
||||
a.download = '物料档案.xlsx'
|
||||
a.click()
|
||||
URL.revokeObjectURL(a.href)
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:12px"
|
||||
title="数量不符上报:工位收料时发现「实收数量」与备料单「应发数量」不一致,由工位终端或本页上报。系统自动算出差异并落预警,供生产/仓库核实处理。" />
|
||||
|
||||
<el-form inline>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="query.status" clearable placeholder="全部" style="width:130px" @change="load">
|
||||
<el-option label="待处理" value="PENDING" />
|
||||
<el-option label="已处理" value="RESOLVED" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="工单号"><el-input v-model="query.orderNo" clearable style="width:160px" @keyup.enter="load" /></el-form-item>
|
||||
<el-form-item label="图号"><el-input v-model="query.materialCode" clearable style="width:150px" placeholder="不区分大小写" @keyup.enter="load" /></el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="list" border size="small">
|
||||
<el-table-column prop="stationNo" label="工位" width="70" align="center" />
|
||||
<el-table-column prop="orderNo" label="工单号" min-width="130" />
|
||||
<el-table-column prop="sn" label="产品序列号" min-width="140" />
|
||||
<el-table-column prop="materialCode" label="图号" min-width="110" />
|
||||
<el-table-column prop="materialName" label="物料名称" min-width="110" />
|
||||
<el-table-column prop="planQty" label="应发" width="70" align="center" />
|
||||
<el-table-column prop="actualQty" label="实收" width="70" align="center" />
|
||||
<el-table-column label="差异" width="80" align="center">
|
||||
<template #default="{row}">
|
||||
<span :style="{ color: row.diffQty === 0 ? '#909399' : (row.diffQty > 0 ? '#67c23a' : '#f56c6c'), fontWeight: 600 }">
|
||||
{{ row.diffQty > 0 ? '+' + row.diffQty : row.diffQty }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="cause" label="原因" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="状态" width="90" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.status === 'RESOLVED' ? 'success' : 'warning'" size="small">
|
||||
{{ row.status === 'RESOLVED' ? '已处理' : '待处理' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operator" label="上报人" width="90" />
|
||||
<el-table-column label="上报时间" width="160">
|
||||
<template #default="{row}">{{ fmtTime(row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<PageHelp :data="helpQtyReport" page="QtyReport" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import request from '../utils/request'
|
||||
import PageHelp from '../components/PageHelp.vue'
|
||||
import { helpQtyReport } from '../help'
|
||||
|
||||
const query = reactive({ status: '', orderNo: '', materialCode: '' })
|
||||
const list = ref([])
|
||||
|
||||
function fmtTime(t) {
|
||||
if (!t) return '-'
|
||||
const d = new Date(t)
|
||||
if (isNaN(d.getTime())) return t
|
||||
const p = (n) => String(n).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`
|
||||
}
|
||||
|
||||
async function load() {
|
||||
list.value = (await request.get('/qty-reports', { params: query })) || []
|
||||
}
|
||||
onMounted(() => { load() })
|
||||
</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:12px"
|
||||
title="工位数量以数据库为准。本页可新增工位(工位号自定义,可扩展到 14、15…)与改名;每个工位的工艺流程在「关联工位」页维护:在下拉选择启用的工艺流程即绑定到该工位;一个工位只能有一个启用的工艺流程。" />
|
||||
title="工位数量以数据库为准。本页可新增工位(工位号自定义,可扩展到 14、15…)与改名;每个工位的工艺流程在「关联工位」页维护:在下拉选择启用的工艺流程即绑定到该工位;一个工位只能有一个启用的工艺流程。内置工位由种子/初始化数据产生,需与 PLC 通讯、必须按工位号顺序流转,不允许修改工位类型、不允许删除;「是否有接驳台」对应实体位置,由内置数据给定,不可手动更改。页面点击「新增工位」创建的是非内置工位,可改类型、可删除。" />
|
||||
|
||||
<div style="margin-bottom:12px">
|
||||
<el-button v-if="can('produce.station:add')" type="primary" @click="openAdd">新增工位</el-button>
|
||||
@@ -18,6 +18,18 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来源" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="row.isBuiltin ? 'warning' : 'info'">
|
||||
{{ row.isBuiltin ? '内置' : '自定义' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="接驳台" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="row.hasDock ? 'success' : 'info'">{{ row.hasDock ? '有' : '无' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="绑定工艺流程" min-width="240">
|
||||
<template #default="{ row }">
|
||||
<el-select :model-value="row.flowId" size="small" placeholder="未绑定" @change="(v) => bindFlow(row, v)" style="width:100%">
|
||||
@@ -26,9 +38,10 @@
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="170" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="can('produce.station:edit')" size="small" @click="openName(row)">改名</el-button>
|
||||
<el-button v-if="can('produce.station:delete') && !row.isBuiltin" size="small" type="danger" @click="remove(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -45,10 +58,13 @@
|
||||
<el-input v-model="dlg.name" placeholder="如 一号装配工位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工位类型">
|
||||
<el-radio-group v-model="dlg.stationType">
|
||||
<el-radio-group v-model="dlg.stationType" :disabled="dlg.isBuiltin">
|
||||
<el-radio label="LINE">线上位(连 PLC)</el-radio>
|
||||
<el-radio label="OFFLINE">线下位(虚拟,不连 PLC)</el-radio>
|
||||
</el-radio-group>
|
||||
<div v-if="dlg.isBuiltin" style="color:#909399;font-size:12px;line-height:1.5;margin-top:4px">
|
||||
内置工位需与 PLC 通讯并按顺序流转,工位类型不可修改。
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -62,7 +78,7 @@
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, computed, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import request from '../utils/request'
|
||||
import { can } from '../utils/perm'
|
||||
import PageHelp from '../components/PageHelp.vue'
|
||||
@@ -74,10 +90,10 @@ const flows = ref([])
|
||||
const rows = computed(() => {
|
||||
return [...stations.value]
|
||||
.sort((a, b) => (a.stationNo || 0) - (b.stationNo || 0))
|
||||
.map((s) => ({ id: s.id, stationNo: s.stationNo, name: s.name, flowId: s.flowId, flowName: s.flowName, stationType: s.stationType }))
|
||||
.map((s) => ({ id: s.id, stationNo: s.stationNo, name: s.name, flowId: s.flowId, flowName: s.flowName, stationType: s.stationType, isBuiltin: !!s.isBuiltin, hasDock: !!s.hasDock }))
|
||||
})
|
||||
|
||||
const dlg = reactive({ show: false, isAdd: false, title: '', id: 0, stationNo: 1, name: '', stationType: 'LINE' })
|
||||
const dlg = reactive({ show: false, isAdd: false, title: '', id: 0, stationNo: 1, name: '', stationType: 'LINE', isBuiltin: false })
|
||||
|
||||
async function load() {
|
||||
stations.value = (await request.get('/stations')) || []
|
||||
@@ -87,11 +103,11 @@ function openAdd() {
|
||||
const used = new Set(stations.value.map((s) => s.stationNo))
|
||||
let next = 14
|
||||
while (used.has(next)) next++
|
||||
Object.assign(dlg, { show: true, isAdd: true, title: '新增工位', id: 0, stationNo: next, name: '', stationType: 'LINE' })
|
||||
Object.assign(dlg, { show: true, isAdd: true, title: '新增工位', id: 0, stationNo: next, name: '', stationType: 'LINE', isBuiltin: false })
|
||||
}
|
||||
|
||||
function openName(row) {
|
||||
Object.assign(dlg, { show: true, isAdd: false, title: `工位${row.stationNo} - 修改`, id: row.id, stationNo: row.stationNo, name: row.name, stationType: row.stationType || 'LINE' })
|
||||
Object.assign(dlg, { show: true, isAdd: false, title: `工位${row.stationNo} - 修改`, id: row.id, stationNo: row.stationNo, name: row.name, stationType: row.stationType || 'LINE', isBuiltin: !!row.isBuiltin })
|
||||
}
|
||||
|
||||
async function save() {
|
||||
@@ -118,6 +134,22 @@ async function bindFlow(row, fid) {
|
||||
}
|
||||
}
|
||||
|
||||
async function remove(row) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认删除工位${row.stationNo}「${row.name}」?`, '删除工位', { type: 'warning' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await request.delete(`/stations/${row.id}`)
|
||||
ElMessage.success('工位已删除')
|
||||
} catch {
|
||||
// 拦截器已提示(如内置工位不允许删除)
|
||||
} finally {
|
||||
await load()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await load()
|
||||
flows.value = (await request.get('/process-flows', { params: { pageSize: 1000, page: 1 } }))?.list || []
|
||||
|
||||
@@ -23,8 +23,7 @@ const routes = [
|
||||
{ path: 'scan', name: 'Scan', component: () => import('../pages/Scan.vue'), meta: { title: '手动报工' } },
|
||||
{ path: 'trace', name: 'Trace', component: () => import('../pages/Trace.vue'), meta: { title: '工件追溯' } },
|
||||
{ path: 'process-card', name: 'ProcessCard', component: () => import('../pages/ProcessCard.vue'), meta: { title: '生产流程卡' } },
|
||||
{ path: 'product-type', name: 'ProductType', component: () => import('../pages/ProductType.vue'), meta: { title: '产品型号' } },
|
||||
{ path: 'qty-report', name: 'QtyReport', component: () => import('../pages/QtyReport.vue'), meta: { title: '数量不符上报' } },
|
||||
{ path: 'product-type', name: 'ProductType', component: () => import('../pages/ProductType.vue'), meta: { title: '物料档案' } },
|
||||
{ path: 'event-log', name: 'EventLog', component: () => import('../pages/EventLog.vue'), meta: { title: '操作日志' } },
|
||||
{ path: 'change-password', name: 'ChangePassword', component: () => import('../pages/ChangePassword.vue'), meta: { title: '修改密码' } },
|
||||
{ path: 'account', name: 'AccountManage', component: () => import('../pages/AccountManage.vue'), meta: { title: '账号管理' } },
|
||||
|
||||
Reference in New Issue
Block a user