feat(production-master-data): 新增生产主数据模块下物料与工序相关功能
Some checks failed
Release pipeline / publish (push) Has been cancelled
Release pipeline / Always run job (push) Has been cancelled

1. 新增物料单位、物料类别、物料信息管理的API与页面
2. 新增工序单元管理的API、页面与弹窗组件
3. 新增可选参数管理组件与相关API
4. 补充对应国际化多语言配置
5. 新增生产主数据模块路由配置
6. 新增计量单位功能测试流程文档
This commit is contained in:
sheng
2026-06-02 11:25:26 +08:00
parent 99b9bc8a5b
commit a0192d9567
17 changed files with 3460 additions and 1 deletions

View File

@@ -0,0 +1,153 @@
# 功能测试流程文档 ——【计量单位】
> 本文档为【计量单位】功能迁移后的独立测试文档。测试人员请按以下流程逐步执行,对通过的用例在"通过"列打勾(`[x]`),未通过的用例在"问题记录"列描述具体现象(包括错误截图编号、操作步骤、实际/预期差异等)。
| 项目名称 | MES-UI生产主数据 → 物料模型 → 计量单位) |
| --- | --- |
| 文档版本 | v1.0 |
| 适用版本 | mes-ui 本次迁移版本 |
| 编写日期 | 2026-06-02 |
| 测试入口 | 菜单:生产主数据 → 物料模型 → 计量单位 |
| 关联文件 | [`src/views/production-master-data/material-model/material-unit/index.vue`](file:///d:/code/mes/mes-ui/src/views/production-master-data/material-model/material-unit/index.vue) [`src/api/production-master-data/material-unit.js`](file:///d:/code/mes/mes-ui/src/api/production-master-data/material-unit.js) [`src/router/modules/production-master-data.js`](file:///d:/code/mes/mes-ui/src/router/modules/production-master-data.js) |
---
## 一、测试环境配置要求
| 项 | 要求 |
| --- | --- |
| 后台环境 | Webman + 已部署 `production_configuration/matetial_model/unit` 路由接口;数据库中存在 `unit` 数据表 |
| 前端环境 | `npm run serve` 启动 mes-ui 工程;浏览器推荐 Chrome 110+ |
| 登录账号 | 拥有 `生产主数据 / 计量单位` 菜单访问权限,且分配以下权限点:<br>· `…/unit/create`(新增)<br>· `…/unit/edit`(编辑)<br>· `…/unit/delete`(删除) |
| 网络要求 | 前后端网络互通;浏览器可访问 `VUE_APP_API` 域名 |
| 数据准备 | 至少 1 条已存在的单位数据;至少 1 个物料已引用本表(用于验证删除联动提示) |
| 浏览器工具 | 打开 DevToolsF12→ Network 与 Console方便抓取接口与异常 |
---
## 二、测试前置条件
1. 已成功登录系统,且侧边栏显示 **生产主数据 → 物料模型 → 计量单位** 菜单。
2. 浏览器语言分别切换为「简体中文 / English」时界面文字均能正确翻译`key is not defined` / 英文缺失等情况。
3. 列表默认展示接口 `production_configuration/matetial_model/unit/list` 返回的记录。
4. 浏览器缩放比例为 100%;分辨率建议 1440×900 及以上。
5. DevTools 关闭缓存Network → Disable cache避免旧 JS 资源影响测试。
---
## 三、测试用例
> **字段说明(数据库与表单共用)**
> - 编码 `code`:必填,长度 1~100文本
> - 名称 `name`:必填,长度 1~100文本
> - 备注 `remark`:选填,文本域(多行)
> - 列表展示字段:序号 / 编码 / 名称 / 备注 / 创建时间 / 操作
### 3.1 列表与搜索
| 用例编号 | 操作步骤 | 预期结果 | 实际结果 | 通过 | 问题记录 |
| --- | --- | --- | --- | --- | --- |
| 3.1.1 | 进入"计量单位"菜单 | 列表正常加载,无报错;表头依次为:序号、单位编码、单位名称、备注、创建时间、操作 | | [ ] | |
| 3.1.2 | 在"单位编码"输入框输入关键字 → 点击「查询」 | 列表仅展示编码包含该关键字的记录 | | [ ] | |
| 3.1.3 | 在"单位名称"输入框输入关键字 → 点击「查询」 | 列表仅展示名称包含该关键字的记录 | | [ ] | |
| 3.1.4 | 编码 + 名称同时输入关键字 → 点击「查询」 | 列表为两条件 AND 过滤结果 | | [ ] | |
| 3.1.5 | 输入查询条件后点击「重置」 | 输入框清空,列表恢复为全量数据 | | [ ] | |
| 3.1.6 | 在搜索输入框按回车键 | 等同于点击「查询」,触发列表刷新 | | [ ] | |
| 3.1.7 | 列表分页:跳转到第 2 页 / 修改每页条数 | 列表请求参数 `page_no` / `page_size` 正确变化,数据正常刷新 | | [ ] | |
| 3.1.8 | 列表为空时(无数据) | 显示空状态占位(如"暂无数据"),不报错 | | [ ] | |
| 3.1.9 | 列表加载中 | 表格区域显示 loading 遮罩 | | [ ] | |
| 3.1.10 | 切换浏览器语言为 English 后刷新 | 表头、操作按钮、占位文字均显示英文 | | [ ] | |
### 3.2 新增
| 用例编号 | 操作步骤 | 预期结果 | 实际结果 | 通过 | 问题记录 |
| --- | --- | --- | --- | --- | --- |
| 3.2.1 | 点击工具栏「新增」按钮 | 弹出标题为「新增单位」的对话框,显示空表单 | | [ ] | |
| 3.2.2 | 不填任何字段,点击「确定」 | 触发表单校验:「单位编码」「单位名称」下方红字提示必填 | | [ ] | |
| 3.2.3 | 仅填写编码(不填名称),点击「确定」 | 仅名称字段提示必填 | | [ ] | |
| 3.2.4 | 输入 101 字符的编码,点击「确定」 | 提示「长度在 1 到 100 个字符」 | | [ ] | |
| 3.2.5 | 正常填写:编码 `PCS`、名称 `个`、备注 `基本计数单位` → 点击「确定」 | 弹出"操作成功"提示,对话框自动关闭,列表自动刷新并出现该条记录 | | [ ] | |
| 3.2.6 | 必填项后存在前后空格时点击「确定」 | 提交数据带空格(视业务策略决定是否 trim列表正常展示 | | [ ] | |
| 3.2.7 | 新增弹框点击「取消」 | 对话框关闭,未提交任何数据 | | [ ] | |
| 3.2.8 | 新增弹框点击右上角 × | 对话框关闭,未提交任何数据 | | [ ] | |
| 3.2.9 | 在无 `unit/create` 权限的账号下访问 | 工具栏不显示「新增」按钮 | | [ ] | |
| 3.2.10 | 后端返回 `code 已存在` 错误 | 页面提示后端错误信息(如 `code 重复`),不崩溃 | | [ ] | |
### 3.3 编辑
| 用例编号 | 操作步骤 | 预期结果 | 实际结果 | 通过 | 问题记录 |
| --- | --- | --- | --- | --- | --- |
| 3.3.1 | 在列表行点击「编辑」 | 弹出标题为「编辑单位」的对话框,且表单字段回显当前行数据 | | [ ] | |
| 3.3.2 | 清空编码,点击「确定」 | 提示编码必填 | | [ ] | |
| 3.3.3 | 修改名称为新值,备注保留为空,点击「确定」 | 提示操作成功,列表对应行的名称更新 | | [ ] | |
| 3.3.4 | 修改编码(与已有数据重复),点击「确定」 | 后端返回重复提示,页面友好显示 | | [ ] | |
| 3.3.5 | 编辑过程中点击「取消」 | 对话框关闭,原数据未发生变化 | | [ ] | |
| 3.3.6 | 无 `unit/edit` 权限的账号 | 行内不显示「编辑」按钮 | | [ ] | |
| 3.3.7 | 同时打开两个浏览器标签 A、B均进入编辑 | B 保存后 A 再次保存时数据为 B 已提交后的最新值(避免脏写) | | [ ] | |
### 3.4 删除
| 用例编号 | 操作步骤 | 预期结果 | 实际结果 | 通过 | 问题记录 |
| --- | --- | --- | --- | --- | --- |
| 3.4.1 | 在列表行点击「删除」 | 弹出确认框,提示「确定要执行该操作吗?」 | | [ ] | |
| 3.4.2 | 确认框点击「取消」 | 关闭确认框,未删除 | | [ ] | |
| 3.4.3 | 确认框点击「确定」 | 提示操作成功,列表自动移除该记录 | | [ ] | |
| 3.4.4 | 删除被物料引用的单位 | 后端返回业务级提示,页面正确展示且不崩溃 | | [ ] | |
| 3.4.5 | 删除最后一页唯一一条记录后 | 列表自动回退到上一页(或显示空状态),无空白页 | | [ ] | |
| 3.4.6 | 无 `unit/delete` 权限的账号 | 行内不显示「删除」按钮 | | [ ] | |
| 3.4.7 | 网络断开时点击「删除」 | 提示网络异常,未误删数据 | | [ ] | |
### 3.5 权限与国际化
| 用例编号 | 操作步骤 | 预期结果 | 实际结果 | 通过 | 问题记录 |
| --- | --- | --- | --- | --- | --- |
| 3.5.1 | 切换为仅有「查询」权限的账号 | 仅能查看列表与查询,无新增/编辑/删除按钮 | | [ ] | |
| 3.5.2 | 切换语言为 English刷新页面 | 表头、按钮、弹框标题、提示语全部为英文 | | [ ] | |
| 3.5.3 | 在中文下打开新增 → 切换为英文 | 弹框标题、表单 label 立即切换为英文(无需关闭弹框) | | [ ] | |
| 3.5.4 | 切换语言后,校验提示(如"长度在 1 到 100 个字符" | 同步显示对应语言 | | [ ] | |
### 3.6 异常与边界
| 用例编号 | 操作步骤 | 预期结果 | 实际结果 | 通过 | 问题记录 |
| --- | --- | --- | --- | --- | --- |
| 3.6.1 | 后端返回 500 错误 | 列表显示空状态或错误提示,不白屏 | | [ ] | |
| 3.6.2 | 后端返回字段缺失 | 缺失字段列显示为空,不抛 JS 异常 | | [ ] | |
| 3.6.3 | 备注字段输入 5000 字符 | 提交成功,列表中可滚动展示(可截断) | | [ ] | |
| 3.6.4 | 在表单输入过程中按 ESC | 弹框关闭,表单状态重置 | | [ ] | |
| 3.6.5 | 列表行内操作按钮溢出 | 操作列固定在右侧且可滚动查看 | | [ ] | |
| 3.6.6 | DevTools Console 检查 | 全程无 `Vue warn`、未捕获 Promise 异常、key 缺失警告 | | [ ] | |
---
## 四、测试结果汇总
| 用例总数 | 通过 | 失败 | 阻塞 | 通过率 |
| --- | --- | --- | --- | --- |
| | | | | |
---
## 五、问题记录区
| 编号 | 用例编号 | 复现步骤 | 实际结果 | 严重程度 | 处理人 | 状态 | 备注 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | | | | | | | |
| 2 | | | | | | | |
| 3 | | | | | | | |
---
## 六、测试结论
| 项目 | 结论 |
| --- | --- |
| 功能完整性 | ☐ 满足 ☐ 部分缺失 ☐ 不满足 |
| 性能表现 | ☐ 良好 ☐ 一般 ☐ 差 |
| 权限控制 | ☐ 正确 ☐ 存在漏洞 |
| 国际化 | ☐ 完整 ☐ 部分缺失 ☐ 缺失 |
| 是否可发布 | ☐ 是 ☐ 否(请说明阻塞问题) |
测试人员签字__________________ 日期__________
---
*本测试流程文档为【计量单位】功能迁移版本专用,请独立归档保存。*

View File

@@ -2847,7 +2847,552 @@
| 产品列表 | 37 | | | |
| 工艺流程类别 | 36 | | | |
| 产线设置 | 30 | | | |
| **合计** | **256** | | | |
| 工序单元 | 36 | | | |
| **合计** | **292** | | | |
---
# 十、工序单元功能测试
> **迁移日期**2026-06-01
> **对应页面**`src/views/production-master-data/process-model/process-step/index.vue`
> **API 文件**`src/api/production-master-data/process-step.js`
> **路由路径**`/production_configuration/technology_model/technology_flow_workingsubclass`
> **页面结构**:搜索区(工序单元编码 + 工序单元名称 + 查询/重置按钮)+ 数据表格 + 新增/编辑弹框(含设备类别下拉选择)
## 10.1 页面加载与数据展示
### TC-STEP-001工序单元页面正常加载
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 登录系统 2. 点击菜单「生产配置 → 工艺模型 → 工序单元」 |
| **预期结果** | 页面正常加载,显示工序单元列表表格,包含序号、工序单元编码、工序单元名称、设备类别、备注、操作列;底部分页组件显示总条数 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-002表格列完整显示
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 观察表格列头 2. 横向滚动查看所有列 |
| **预期结果** | 表格包含以下列:序号、工序单元编码、工序单元名称、设备类别、备注、操作(共 6 列),操作列固定在右侧 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-003空数据状态展示
| 项目 | 内容 |
|------|------|
| **测试前置条件** | 确保数据库中没有工序单元数据(或清空搜索条件后无匹配数据) |
| **测试步骤** | 1. 在搜索框中输入一个不存在的工序单元编码 2. 点击「查询」 |
| **预期结果** | 表格显示空状态提示(如"暂无数据"),分页显示总数为 0 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-004表格序号正确递增
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 加载工序单元列表 2. 观察序号列 |
| **预期结果** | 序号从 1 开始递增,翻页后序号接续正确 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
## 10.2 搜索与分页
### TC-STEP-005按工序单元编码搜索
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 在工序单元编码输入框中输入已知存在的编码 2. 点击「查询」按钮 |
| **预期结果** | 表格仅显示编码匹配的记录 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-006按工序单元名称搜索
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 在工序单元名称输入框中输入已知存在的名称 2. 点击「查询」按钮 |
| **预期结果** | 表格仅显示名称匹配的记录 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-007组合搜索
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 同时填写工序单元编码和名称搜索条件 2. 点击「查询」按钮 |
| **预期结果** | 表格显示同时满足编码和名称条件的记录 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-008重置搜索条件
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 输入搜索条件并查询 2. 点击「重置」按钮 |
| **预期结果** | 搜索条件清空,表格恢复显示全部数据 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-009分页功能
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 切换每页显示条数 2. 点击下一页/上一页 3. 输入页码跳转 |
| **预期结果** | 分页切换正常,表格数据按分页加载,总条数正确显示 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
## 10.3 新增工序单元
### TC-STEP-010打开新增弹框
| 项目 | 内容 |
|------|------|
| **测试步骤** | 点击表格上方工具栏「新增」按钮 |
| **预期结果** | 弹出新增工序单元弹框,标题显示"新增工序单元",表单字段为空:工序单元编码(输入框)、工序单元名称(输入框)、设备类别(下拉选择)、备注(多行文本域) |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-011新增工序单元成功
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开新增弹框 2. 输入工序单元编码(如"PS-001"3. 输入工序单元名称(如"测试工序单元001"4. 选择设备类别 5. 输入备注(如"用于测试"6. 点击「确定」 |
| **预期结果** | 弹框关闭,提示"操作成功",表格刷新并显示新增的工序单元 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-012新增工序单元—仅填写必填项
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开新增弹框 2. 填写编码、名称,选择设备类别 3. 备注留空 4. 点击「确定」 |
| **预期结果** | 弹框关闭,提示"操作成功",备注字段在表格中显示为空 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-013新增工序单元—设备类别下拉选择
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开新增弹框 2. 点击设备类别下拉框 |
| **预期结果** | 下拉列表显示所有设备类别选项,可搜索过滤 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-014取消新增
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开新增弹框 2. 填写部分内容 3. 点击「取消」按钮或弹框右上角 X |
| **预期结果** | 弹框关闭,表格数据不变 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-015连续新增
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 新增工序单元 A 成功后 2. 再次点击「新增」按钮 3. 新增工序单元 B |
| **预期结果** | 弹框表单每次打开时均为空状态,上次填写的内容不残留;两次新增均成功 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
## 10.4 编辑工序单元
### TC-STEP-016打开编辑弹框
| 项目 | 内容 |
|------|------|
| **测试步骤** | 点击工序单元列表中任一工序单元行操作列的「编辑」按钮 |
| **预期结果** | 弹出编辑工序单元弹框,标题显示"编辑工序单元",表单回填该工序单元的编码、名称、设备类别、备注 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-017编辑工序单元—修改名称
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开编辑弹框 2. 修改工序单元名称(如改为"测试工序单元-已修改"3. 点击「确定」 |
| **预期结果** | 弹框关闭,提示"操作成功",表格中该行名称更新 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-018编辑工序单元—修改设备类别
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开编辑弹框 2. 切换选择另一个设备类别 3. 点击「确定」 |
| **预期结果** | 弹框关闭,提示"操作成功",表格中该行设备类别列更新 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-019编辑工序单元—工序单元编码不可修改
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开编辑弹框 2. 查看工序单元编码输入框 |
| **预期结果** | 工序单元编码字段显示为禁用状态(灰色不可编辑) |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-020取消编辑
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开编辑弹框 2. 修改内容 3. 点击「取消」按钮 |
| **预期结果** | 弹框关闭,原数据不变 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
## 10.5 删除工序单元
### TC-STEP-021删除确认弹框
| 项目 | 内容 |
|------|------|
| **测试步骤** | 点击工序单元列表中任一工序单元行操作列的「删除」按钮 |
| **预期结果** | 弹出确认提示框,内容为"确定要执行该操作吗?",有「确定」和「取消」按钮 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-022删除工序单元成功
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 点击工序单元行的「删除」按钮 2. 在确认框中点击「确定」 |
| **预期结果** | 提示"操作成功",该工序单元从列表中消失,分页总数减 1 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-023取消删除
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 点击「删除」按钮 2. 在确认框中点击「取消」 |
| **预期结果** | 确认框关闭,工序单元数据保持不变 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-024删除最后一页唯一数据后的分页处理
| 项目 | 内容 |
|------|------|
| **测试前置条件** | 存在数据,且最后一页只有一条记录 |
| **测试步骤** | 1. 翻到最后一页 2. 删除该页唯一的工序单元 |
| **预期结果** | 提示"操作成功",自动跳转到前一页,分页总数减 1 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
## 10.6 表单校验
### TC-STEP-025新增表单校验—编码必填
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开新增弹框 2. 不填写工序单元编码 3. 填写其他必填项 4. 点击「确定」 |
| **预期结果** | 编码输入框下方显示红色校验提示,弹框不关闭 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-026新增表单校验—名称必填
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开新增弹框 2. 填写编码 3. 不填写工序单元名称 4. 选择设备类别 5. 点击「确定」 |
| **预期结果** | 名称输入框下方显示红色校验提示,弹框不关闭 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-027新增表单校验—设备类别必填
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开新增弹框 2. 填写编码和名称 3. 不选择设备类别 4. 点击「确定」 |
| **预期结果** | 设备类别下方显示红色校验提示"请选择设备类别",弹框不关闭 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-028新增表单校验—编码长度上限
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开新增弹框 2. 在编码输入框中输入超过 100 个字符 3. 点击「确定」 |
| **预期结果** | 编码输入框下方显示红色校验提示"长度在 1 到 100 个字符" |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-029新增表单校验—中文字符正常输入
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开新增弹框 2. 工序单元名称输入中文字符 3. 点击「确定」 |
| **预期结果** | 表单正常提交,中文内容正确保存 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-030编辑表单校验—清空必填项
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开已存在工序单元的编辑弹框 2. 清空工序单元名称 3. 点击「确定」 |
| **预期结果** | 名称输入框下方显示红色校验提示 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
## 10.7 多语言切换
### TC-STEP-031切换到英文—页面标签
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 切换到英文语言 2. 点击菜单进入工序单元页面 |
| **预期结果** | 搜索区标签显示"Process Unit Code"、"Process Unit Name"、"Search"、"Reset";表格列头显示"No."、"Process Unit Code"、"Process Unit Name"、"Device Category"、"Remark"、"Actions";工具栏按钮显示"Add" |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-032切换到英文—新增弹框标签
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 切换到英文 2. 点击新增按钮 3. 查看弹框 |
| **预期结果** | 弹框标题为"Add Process Unit";表单标签为"Process Unit Code"、"Process Unit Name"、"Device Category"、"Remark";底部按钮为"Confirm"、"Cancel" |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-033切换到英文—编辑弹框标签
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 切换到英文 2. 点击某行的编辑按钮 |
| **预期结果** | 弹框标题为"Edit Process Unit",表单回填正确,所有标签为英文 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-034切换到英文—提示信息
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 切换到英文 2. 执行新增/编辑/删除操作 |
| **预期结果** | 操作成功提示为"Operation succeeded";删除确认框内容为"Proceed with this action?";校验提示为英文 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-035切换到英文—设备类别下拉
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 切换到英文 2. 打开新增弹框 3. 点击设备类别下拉框 |
| **预期结果** | 下拉 placeholder 为"Please select device category" |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-036英文下所有操作功能正常
| 项目 | 内容 |
|------|------|
| **测试步骤** | 在英文界面下执行:搜索、新增工序单元、编辑工序单元、删除工序单元、分页切换 |
| **预期结果** | 所有功能正常运行,交互逻辑与中文界面一致 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-037预设设定值按钮权限控制
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 登录系统 2. 进入工序单元页面 3. 查看某行数据的操作列 |
| **预期结果** | 操作列显示:编辑(黄色)、预设设定值(橙色)、预设结果参数(绿色)、删除(红色)四个按钮 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-038打开预设设定值弹框
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 进入工序单元页面 2. 点击某行的"预设设定值"按钮 |
| **预期结果** | 弹出"预设设定值"对话框,标题正确,显示配置区域占位符和提示信息 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-039预设设定值弹框关闭
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开预设设定值弹框 2. 点击取消按钮 |
| **预期结果** | 弹框关闭,无错误 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-040预设设定值提交
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开预设设定值弹框 2. 点击确定按钮 |
| **预期结果** | 显示"操作成功"提示,弹框关闭 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-041预设结果参数按钮存在
| 项目 | 内容 |
|------|------|
| **测试步骤** | 进入工序单元页面,查看操作列按钮 |
| **预期结果** | 显示"预设结果参数"按钮(绿色图标) |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-042打开预设结果参数弹框
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 进入工序单元页面 2. 点击某行的"预设结果参数"按钮 |
| **预期结果** | 弹出"预设结果参数"对话框,显示可选参数列表表格(名称、参数、类别、备注、是否唯一、是否上传) |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-043预设结果参数搜索功能
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开预设结果参数弹框 2. 输入名称和参数进行搜索 |
| **预期结果** | 点击查询按钮后,表格数据根据搜索条件过滤 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-044新增可选参数
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开预设结果参数弹框 2. 点击"新增一行"按钮 3. 填写参数信息 4. 点击确定 |
| **预期结果** | 新增成功,列表刷新显示新数据,弹出成功提示 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-045编辑可选参数
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开预设结果参数弹框 2. 点击某行的编辑图标 3. 修改信息 4. 点击确定 |
| **预期结果** | 编辑成功,列表刷新显示更新后的数据 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-046删除可选参数
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开预设结果参数弹框 2. 点击某行的删除图标 3. 确认删除 |
| **预期结果** | 删除成功,列表刷新,该数据不再显示 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-047预设结果参数分页
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开预设结果参数弹框 2. 当数据较多时切换分页 |
| **预期结果** | 分页切换正常,显示正确的页码和总条数 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-048预设结果参数关闭
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开预设结果参数弹框 2. 点击关闭按钮 |
| **预期结果** | 弹框正常关闭 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-049预设结果参数导入功能入口
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 打开预设结果参数弹框 2. 点击"导入"按钮 |
| **预期结果** | 显示"导入功能开发中"提示 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
### TC-STEP-050预设结果参数英文界面
| 项目 | 内容 |
|------|------|
| **测试步骤** | 1. 切换到英文 2. 打开预设结果参数弹框 |
| **预期结果** | 弹框标题显示"Preset Result Parameters"表格列头显示英文Name、Parameter、Category等 |
| **实际结果** | |
| **测试状态** | ⬜ 通过 / ⬜ 未通过 |
| **问题描述** | |
---
## 测试结果汇总
| 功能模块 | 测试用例数 | 通过 | 未通过 | 通过率 |
|----------|-----------|------|--------|--------|
| 角色管理 | 24 | | | |
| 用户管理 | 33 | | | |
| 菜单配置 | 37 | | | |
| 接口日志 | 20 | | | |
| 操作日志 | 18 | | | |
| 问题帮助 | 21 | | | |
| 产品列表 | 37 | | | |
| 工艺流程类别 | 36 | | | |
| 产线设置 | 30 | | | |
| 工序单元 | 50 | | | |
| **合计** | **306** | | | |
---
@@ -2861,3 +3406,5 @@
> - v1.6 (2026-06-01)追加产品列表功能37 条用例)
> - v1.7 (2026-06-01)追加工艺流程类别功能36 条用例)
> - v1.8 (2026-06-01)追加产线设置功能30 条用例)
> - v1.9 (2026-06-01)追加工序单元功能36 条用例)
> - v1.10 (2026-06-01)追加工序单元预设设定值和预设结果参数功能14 条用例)

View File

@@ -0,0 +1,359 @@
# MES-UI 迁移功能测试流程文档(累积追加版)
> 本文档用于记录每次 MES-UI 迁移完成后的功能测试流程。
> 采用**累积追加**模式:每次完成新功能迁移,将对应测试内容追加到本文件末尾,并在「目录」中补充章节索引。
> 测试人员按章节顺序执行用例,每完成一项需在「[ ]」中改为「[x]」并填写实际结果;对未通过项在「问题记录」区域详细描述。
>
> **测试结果约定**
> - ✅ 通过Pass
> - ❌ 失败Fail
> - ⚠️ 阻塞Block依赖其他功能未通过
>
> **填写示例**
> - `[x] 1.1 列表正常加载 ✅ 实际10 条记录,分页正常`
> - `[ ] 1.2 编码为空校验 ❌ 实际:未拦截空值,提示词缺失`
---
## 目录
- [一、测试环境与通用前置条件](#一测试环境与通用前置条件)
- [二、物料类别列表Material Category](#二物料类别列表material-category)
- [三、物料信息管理Material Master](#三物料信息管理material-master)
---
## 一、测试环境与通用前置条件
> 所有功能测试前,请先确认以下环境和基础数据准备就绪。
### 1.1 测试环境
| 项目 | 要求 |
|------|------|
| 操作系统 | Windows 10 / 11 或 macOS |
| 浏览器 | Chrome ≥ 100推荐、Edge ≥ 100 |
| Node.js | ≥ 16.0(用于本地构建) |
| MES-UI 仓库 | `d:\code\mes\mes-ui`,已安装依赖 `pnpm install` |
| 后端服务 | Webman 后台已启动(地址、端口按部署环境),数据库表 `material_category` 已存在 |
| 启动命令 | `pnpm dev``npm run serve`,默认 `http://localhost:8080` |
| 登录账号 | 具备「生产配置」菜单权限的账号,建议使用 `admin` 角色 |
| 网络 | 能正常访问后端 API |
### 1.2 通用前置条件
1. 已完成 `pnpm install`,依赖安装无错误。
2. `pnpm dev` 启动成功,浏览器可访问 `http://localhost:8080`
3. 使用具有「生产配置 / 物料模型 / 物料类别列表」访问权限的账号登录。
4. 后端数据库中 `material_category` 表至少有 1 条数据,用于列表展示和编辑。
5. 中英文语言切换正常:在右上角下拉框切换「中文 / English」所有标签翻译均能显示。
6. 浏览器 DevTools 的 Network 面板可见请求 URL 类似 `production_configuration/matetial_model/matetial_category/list?...`(迁移后保持旧 URL
### 1.3 测试结果总览(按功能)
| 功能 | 章节 | 用例数 | 通过 | 失败 | 阻塞 | 测试人 / 日期 |
|------|------|:----:|:----:|:----:|:----:|---------------|
| 物料类别列表 | 二 | 22 | | | | |
| 物料信息管理 | 三 | 30 | | | | |
---
## 二、物料类别列表Material Category
### 2.1 功能概述
- **一级模块**生产配置Production Master Data
- **二级模块**物料模型Material Model
- **三级模块**物料类别列表Material Category
- **功能说明**:用于维护物料类别(区分原材料和半成品),支持编码、名称、备注、创建时间等字段的增删改查
- **菜单路径**:生产配置 → 物料模型 → 物料类别列表
- **路由地址**`/production_configuration/matetial_model/matetial_category`(迁移阶段沿用旧 URL
- **涉及文件**
- 页面:[src/views/production-master-data/material-model/material-category/index.vue](file:///d:/code/mes/mes-ui/src/views/production-master-data/material-model/material-category/index.vue)
- API[src/api/production-master-data/material-category.js](file:///d:/code/mes/mes-ui/src/api/production-master-data/material-category.js)
- 路由:[src/router/modules/production-master-data.js](file:///d:/code/mes/mes-ui/src/router/modules/production-master-data.js)
- i18n`src/locales/zh-chs.json` & `en.json`key 前缀 `page.production_master_data.material_model.material_category.*`
### 2.2 字段说明
| 字段 | 类型 | 必填 | 长度限制 | 说明 |
|------|------|:----:|---------|------|
| 序号 | 数字 | — | — | 表格自增序号,由 `useTableColumns` 自动生成 |
| 物料类别编码 | 字符串 | ✅ | 1~45 | 唯一标识 |
| 物料类别名称 | 字符串 | ✅ | 1~45 | 类别中文名 |
| 备注 | 字符串 | ❌ | — | 多行文本 |
| 创建时间 | 日期 | — | — | 后端自动填充 |
| 操作 | — | — | — | 行内「编辑」「删除」按钮 |
### 2.3 前置条件
1. 已登录具备「生产配置 / 物料模型 / 物料类别列表」权限的账号。
2. 后端 `material_category` 表存在;若为空,可通过本测试用例的「新增」步骤补足。
3. 浏览器地址栏输入 `/production_configuration/matetial_model/matetial_category` 可直接访问。
4. 当前页面右上角语言切换为「中文」切换为「English」后再次验证关键标签。
### 2.4 测试用例
#### 2.4.1 列表与搜索
| # | 操作步骤 | 预期结果 | 实际结果 | 通过 |
|---|---------|---------|---------|:----:|
| 1.1 | 打开「物料类别列表」页面 | 表格自动加载,列表正常显示;表头依次为:序号 / 物料类别编码 / 物料类别名称 / 备注 / 创建时间 / 操作 | | ☐ |
| 1.2 | 在「物料类别编码」输入框输入已存在的关键字,点击「查询」 | 列表仅显示编码模糊匹配的数据,页码回到 1URL 不暴露搜索值 | | ☐ |
| 1.3 | 在「物料类别名称」输入框输入已存在的关键字,点击「查询」 | 列表仅显示名称模糊匹配的数据 | | ☐ |
| 1.4 | 同时输入编码 + 名称后点击「查询」 | 列表为 AND 条件过滤结果 | | ☐ |
| 1.5 | 在搜索框内按 `Enter` 键 | 触发查询,效果与点击「查询」一致 | | ☐ |
| 1.6 | 点击「重置」按钮 | 搜索框清空,列表恢复初始数据,页码回到 1 | | ☐ |
| 1.7 | 翻到第 2 页,点击「重置」 | 页码回到 1列表刷新 | | ☐ |
| 1.8 | 列表行数超过 1 页时,切换「每页显示条数」 | 列表按新分页重新加载URL / 请求参数同步变化 | | ☐ |
| 1.9 | 切换右上角语言为「English」 | 表头列名翻译正确No. / Category Code / Category Name / Remark / Create Time / Actions | | ☐ |
| 1.10 | 后端返回空数据 | 表格区域显示空数据占位,分页总数为 0 | | ☐ |
#### 2.4.2 新增物料类别
| # | 操作步骤 | 预期结果 | 实际结果 | 通过 |
|---|---------|---------|---------|:----:|
| 2.1 | 点击工具栏「新增」按钮 | 弹出「新增物料类别」对话框,含编码 / 名称 / 备注三个表单项,编码默认聚焦 | | ☐ |
| 2.2 | 编码留空,名称输入「测试类别 A」备注输入「原材料」点击「确定」 | 弹框内编码字段红色错误提示「请输入物料类别编码」;不会提交成功 | | ☐ |
| 2.3 | 编码输入「TEST_001」名称留空点击「确定」 | 名称字段红色错误提示「请输入物料类别名称」 | | ☐ |
| 2.4 | 编码输入 46 个字符(如 46 个 `a`),名称输入「测试类别 A」点击「确定」 | 编码字段提示「长度在 1 到 45 个字符」 | | ☐ |
| 2.5 | 名称输入 46 个字符编码输入「TEST_002」点击「确定」 | 名称字段提示「长度在 1 到 45 个字符」 | | ☐ |
| 2.6 | 编码输入已存在的编码(如数据库现存 `RAW_001`),名称输入「重复测试」,点击「确定」 | 提交失败,弹出后端返回的错误提示(如「编码已存在」);列表不刷新 | | ☐ |
| 2.7 | 编码输入「TEST_001」名称输入「测试类别 A」备注输入「用于半成品分类」点击「确定」 | 提交成功,弹框关闭,顶部提示「操作成功」;列表新增一行 | | ☐ |
| 2.8 | 在弹框中点击「取消」 | 弹框关闭,不提交任何数据 | | ☐ |
| 2.9 | 打开新增弹框后点击右上角 × 关闭 | 弹框关闭,再次打开时表单已重置 | | ☐ |
| 2.10 | 重复 2.7 步骤的编码「TEST_001」再次提交 | 后端返回「编码已存在」或类似错误,不重复创建 | | ☐ |
#### 2.4.3 编辑物料类别
| # | 操作步骤 | 预期结果 | 实际结果 | 通过 |
|---|---------|---------|---------|:----:|
| 3.1 | 在列表中找到「测试类别 A」2.7 创建),点击行内「编辑」 | 弹出「编辑物料类别」对话框,编码 / 名称 / 备注自动回填 | | ☐ |
| 3.2 | 将名称修改为「测试类别 A1」备注改为「原材料-已更新」,点击「确定」 | 提交成功,弹框关闭,提示「操作成功」;列表行名称 / 备注同步更新 | | ☐ |
| 3.3 | 编辑时清空编码并保存 | 编码字段必填提示「请输入物料类别编码」 | | ☐ |
| 3.4 | 编辑时清空名称并保存 | 名称字段必填提示「请输入物料类别名称」 | | ☐ |
| 3.5 | 编辑时将编码修改为与另一行相同的值(如 `RAW_001`),点击「确定」 | 后端返回「编码已存在」错误 | | ☐ |
| 3.6 | 编辑弹框打开后点击「取消」 | 数据不保存,列表无变化 | | ☐ |
| 3.7 | 编辑后重新打开编辑弹框 | 表单仍展示上一次保存后的数据 | | ☐ |
| 3.8 | 编辑成功后检查「创建时间」字段 | 创建时间未变化(仅首次新增时赋值) | | ☐ |
#### 2.4.4 删除物料类别
| # | 操作步骤 | 预期结果 | 实际结果 | 通过 |
|---|---------|---------|---------|:----:|
| 4.1 | 在列表中找到「测试类别 A1」点击行内「删除」 | 弹出确认框「确定要执行该操作吗?」,标题为「提示」 | | ☐ |
| 4.2 | 在确认框中点击「取消」 | 删除取消,列表无变化 | | ☐ |
| 4.3 | 在确认框中点击「确定」 | 确认框关闭,顶部提示「操作成功」;该行从列表中消失 | | ☐ |
| 4.4 | 在仅剩 1 条数据的列表上点击「删除」并确认 | 删除成功后,列表变为空,分页回到第 1 页 | | ☐ |
| 4.5 | 尝试删除仍被「物料信息」引用的物料类别 | 后端返回「存在引用,无法删除」或类似错误 | | ☐ |
| 4.6 | 删除操作期间切换「每页显示条数」 | 删除后页码自动修正,避免出现空页 | | ☐ |
#### 2.4.5 权限与异常场景
| # | 操作步骤 | 预期结果 | 实际结果 | 通过 |
|---|---------|---------|---------|:----:|
| 5.1 | 使用无「新增」权限的账号登录 | 工具栏「新增」按钮不显示或不可点击 | | ☐ |
| 5.2 | 使用无「编辑」权限的账号登录 | 行内「编辑」按钮不显示 | | ☐ |
| 5.3 | 使用无「删除」权限的账号登录 | 行内「删除」按钮不显示 | | ☐ |
| 5.4 | 断网后刷新页面 | 表格显示加载失败或空态DevTools Network 显示请求失败 | | ☐ |
| 5.5 | 后端返回 500 错误(如临时停服) | 顶部红色错误提示(如「操作失败」),不卡死页面 | | ☐ |
| 5.6 | 同时打开两个浏览器标签页,在 A 标签新增数据后B 标签刷新列表 | B 标签列表应能看到 A 标签新增的数据 | | ☐ |
#### 2.4.6 国际化与界面
| # | 操作步骤 | 预期结果 | 实际结果 | 通过 |
|---|---------|---------|---------|:----:|
| 6.1 | 中文状态下打开新增弹框 | 弹框标题、字段标签、按钮文字均为中文 | | ☐ |
| 6.2 | 切换为「English」后再次打开新增弹框 | 弹框标题「Add Material Category」按钮「Confirm / Cancel」 | | ☐ |
| 6.3 | 中文状态下点击删除,确认弹框标题为「提示」 | 确认框标题与按钮文字中文显示 | | ☐ |
| 6.4 | 英文状态下点击删除确认弹框标题为「Tip」 | 确认框英文显示 | | ☐ |
| 6.5 | 列表右上角「帮助」按钮(? 图标) | 鼠标悬停或点击显示「物料类别用于区分原材料和半成品」 | | ☐ |
| 6.6 | 备注字段为空时点击保存 | 提交成功,列表中备注列空白(无 `null` / `undefined` 文本) | | ☐ |
### 2.5 接口契约(用于前后端联调核对)
| 方法 | URL | 旧 method 名 | 入参关键 key | 备注 |
|------|-----|-------------|-------------|------|
| GET | `production_configuration/matetial_model/matetial_category/list` | `production_configuration_matetial_model_matetial_category_list` | `code / name / page_no / page_size` | 列表查询 |
| GET | `…/all` | `…_all` | — | 全部类别(下拉框用,可选) |
| POST | `…/create` | `…_create` | `code / name / remark` | 新增 |
| PUT | `…/edit` | `…_edit` | `id / code / name / remark` | 编辑(`id` 为行主键) |
| DELETE | `…/delete` | `…_delete` | `id: [rowId]` | 删除,参数名 `id` 数组(与旧项目保持一致) |
### 2.6 问题记录
> 测试人员发现问题时填写。每条问题一行,格式:`[编号] [用例编号] [现象] [复现步骤] [期望] [截图/日志] [负责人] [处理状态]`。
| 编号 | 用例 | 现象 | 复现步骤 | 期望 | 截图/日志 | 负责人 | 状态 |
|:---:|:---:|------|---------|------|----------|:----:|:----:|
| | | | | | | | |
### 2.7 备注
- 本页面沿用旧 URL 与后台 method 名,待后台数据库路由表统一更新后统一修改。
- 涉及 `confirmMixin` / `useTableColumns` / `useTableButtons` / `i18nMixin` 等公共 Composable如其他页面出现类似问题先确认是否在迁移组件时有遗漏。
- 若新增 / 编辑成功后接口返回结构变化(`res.data` 包裹层调整),需要同步更新 `fetchData` 的解析逻辑。
---
## 三、物料信息管理Material Master
### 3.1 功能概述
- **一级模块**生产配置Production Master Data
- **二级模块**物料模型Material Model
- **三级模块**物料信息管理Material Master
- **功能说明**:用于维护物料主数据,包括物料编码、名称、所属物料类别、单位、备注等,支持新增、编辑、删除、批量删除、按编码/名称/类别检索。
- **菜单路径**:生产配置 → 物料模型 → 物料信息管理
- **路由地址**`/production_configuration/matetial_model/matetial_management`(迁移阶段沿用旧 URL
- **涉及文件**
- 页面:[src/views/production-master-data/material-model/material-master/index.vue](file:///d:/code/mes/mes-ui/src/views/production-master-data/material-model/material-master/index.vue)
- API[src/api/production-master-data/material-master.js](file:///d:/code/mes/mes-ui/src/api/production-master-data/material-master.js)
- 路由:[src/router/modules/production-master-data.js](file:///d:/code/mes/mes-ui/src/router/modules/production-master-data.js)
- i18n`src/locales/zh-chs.json` & `en.json`key 前缀 `page.production_master_data.material_model.material_master.*`
### 3.2 字段说明
| 字段 | 类型 | 必填 | 长度限制 | 说明 |
|------|------|:----:|---------|------|
| 序号 | 数字 | — | — | 表格自增序号 |
| 物料编码 | 字符串 | ✅ | 1~100 | 唯一标识 |
| 物料名称 | 字符串 | ✅ | 1~100 | 物料中文名 |
| 物料类别 | 下拉 | ✅ | — | 来源于物料类别下拉(依赖「物料类别列表」已迁移) |
| 单位 | 输入/下拉 | ❌ | — | 当前版本以文本框占位;待「计量单位」模块迁移后接入下拉数据源 |
| 备注 | 字符串 | ❌ | — | 多行文本,超过 20 字符鼠标悬停展示全量 |
| 创建人 | 字符串 | — | — | 后端返回 |
| 创建时间 | 日期 | — | — | 后端自动填充 |
| 操作 | — | — | — | 行内「编辑」「删除」按钮 |
### 3.3 前置条件
1. 已登录具备「生产配置 / 物料模型 / 物料信息管理」权限的账号。
2. 已完成「物料类别列表」迁移,「物料类别」下拉数据正常加载(用于新增/编辑时的类别选择)。
3. 后端 `material` 表存在;若为空,可通过本测试用例的「新增」步骤补足。
4. 浏览器地址栏输入 `/production_configuration/matetial_model/matetial_management` 可直接访问。
5. 当前页面右上角语言切换为「中文」切换为「English」后再次验证关键标签。
### 3.4 测试用例
#### 3.4.1 列表与搜索
| # | 操作步骤 | 预期结果 | 实际结果 | 通过 |
|---|---------|---------|---------|:----:|
| 1.1 | 打开「物料信息管理」页面 | 表格自动加载,列表正常显示;表头依次为:序号 / 物料编码 / 物料名称 / 物料类别 / 单位 / 备注 / 创建人 / 创建时间 / 操作 | | ☐ |
| 1.2 | 物料类别下拉可正常展开,选项来源于物料类别数据 | 下拉选项与「物料类别列表」一致,支持搜索过滤 | | ☐ |
| 1.3 | 在「物料编码」输入框输入已存在的关键字,点击「查询」 | 列表仅显示编码模糊匹配的数据,页码回到 1 | | ☐ |
| 1.4 | 在「物料名称」输入框输入已存在的关键字,点击「查询」 | 列表仅显示名称模糊匹配的数据 | | ☐ |
| 1.5 | 在「物料类别」下拉中选择某一项,点击「查询」 | 列表仅显示该类别下的物料 | | ☐ |
| 1.6 | 同时输入编码 + 名称 + 类别后点击「查询」 | 列表为 AND 条件过滤结果 | | ☐ |
| 1.7 | 在搜索框内按 `Enter` 键 | 触发查询,效果与点击「查询」一致 | | ☐ |
| 1.8 | 点击「重置」按钮 | 搜索框清空,列表恢复初始数据,页码回到 1 | | ☐ |
| 1.9 | 翻到第 2 页,点击「重置」 | 页码回到 1列表刷新 | | ☐ |
| 1.10 | 列表行数超过 1 页时,切换「每页显示条数」 | 列表按新分页重新加载,请求参数 `page_size` 同步变化 | | ☐ |
| 1.11 | 切换右上角语言为「English」 | 表头列名翻译正确No. / Material Code / Material Name / Category / Unit / Remark / Created By / Created At / Action | | ☐ |
| 1.12 | 备注长度 > 20 字符时,鼠标悬停单元格 | 弹层显示完整备注内容 | | ☐ |
| 1.13 | 备注长度 <= 20 字符时 | 单元格直接展示文本,不显示弹层 | | ☐ |
| 1.14 | 后端返回空数据 | 表格区域显示空数据占位,分页总数为 0 | | ☐ |
#### 3.4.2 新增物料信息
| # | 操作步骤 | 预期结果 | 实际结果 | 通过 |
|---|---------|---------|---------|:----:|
| 2.1 | 点击工具栏「新增」按钮 | 弹出「新增物料信息」对话框,依次为:物料编码 / 物料名称 / 物料类别 / 单位 / 备注 | | ☐ |
| 2.2 | 编码留空,名称输入「测试物料 A」类别下拉选择「原材料」点击「确定」 | 编码字段红色错误提示「请输入物料编码」 | | ☐ |
| 2.3 | 编码输入「MAT_001」名称留空点击「确定」 | 名称字段红色错误提示「请输入物料名称」 | | ☐ |
| 2.4 | 编码、名称都填写,类别留空,点击「确定」 | 类别字段红色错误提示「请选择物料类别」 | | ☐ |
| 2.5 | 编码输入 101 个字符(如 101 个 `a`),名称输入「测试物料 A」点击「确定」 | 编码字段提示「长度在 1 到 100 个字符」 | | ☐ |
| 2.6 | 名称输入 101 个字符编码输入「MAT_002」点击「确定」 | 名称字段提示「长度在 1 到 100 个字符」 | | ☐ |
| 2.7 | 编码输入已存在的编码,名称输入「重复测试」,点击「确定」 | 提交失败,弹出后端返回的错误提示(如「编码已存在」);列表不刷新 | | ☐ |
| 2.8 | 编码输入「MAT_001」名称输入「测试物料 A」类别选择「原材料」单位输入「个」备注输入「用于产线领料」点击「确定」 | 提交成功,弹框关闭,顶部提示「操作成功」;列表新增一行且所有字段正确展示 | | ☐ |
| 2.9 | 在弹框中点击「取消」 | 弹框关闭,不提交任何数据 | | ☐ |
| 2.10 | 打开新增弹框后点击右上角 × 关闭,再次点击「新增」 | 弹框再次打开时表单已重置 | | ☐ |
| 2.11 | 重复 2.8 步骤的编码「MAT_001」再次提交 | 后端返回「编码已存在」或类似错误,不重复创建 | | ☐ |
#### 3.4.3 编辑物料信息
| # | 操作步骤 | 预期结果 | 实际结果 | 通过 |
|---|---------|---------|---------|:----:|
| 3.1 | 在列表中找到「MAT_001」2.8 创建),点击行内「编辑」 | 弹出「编辑物料信息」对话框,编码 / 名称 / 类别 / 单位 / 备注自动回填 | | ☐ |
| 3.2 | 将名称修改为「测试物料 A1」类别改为「半成品」单位改为「箱」备注改为「用于产线补料」点击「确定」 | 提交成功,弹框关闭,提示「操作成功」;列表行各字段同步更新 | | ☐ |
| 3.3 | 编辑时清空编码并保存 | 编码字段必填提示「请输入物料编码」 | | ☐ |
| 3.4 | 编辑时清空名称并保存 | 名称字段必填提示「请输入物料名称」 | | ☐ |
| 3.5 | 编辑时清空类别并保存 | 类别字段必填提示「请选择物料类别」 | | ☐ |
| 3.6 | 编辑时将编码修改为与另一行相同的值,点击「确定」 | 后端返回「编码已存在」错误 | | ☐ |
| 3.7 | 编辑弹框打开后点击「取消」 | 数据不保存,列表无变化 | | ☐ |
| 3.8 | 编辑后重新打开编辑弹框 | 表单仍展示上一次保存后的数据 | | ☐ |
| 3.9 | 编辑成功后检查「创建时间」字段 | 创建时间未变化(仅首次新增时赋值) | | ☐ |
| 3.10 | 编辑时清空备注并保存 | 提交成功,列表中备注列空白(无 `null` / `undefined` 文本) | | ☐ |
#### 3.4.4 删除与批量删除
| # | 操作步骤 | 预期结果 | 实际结果 | 通过 |
|---|---------|---------|---------|:----:|
| 4.1 | 在列表中找到「MAT_001」点击行内「删除」 | 弹出确认框「确定要执行该操作吗?」,标题为「提示」 | | ☐ |
| 4.2 | 在确认框中点击「取消」 | 删除取消,列表无变化 | | ☐ |
| 4.3 | 在确认框中点击「确定」 | 确认框关闭,顶部提示「操作成功」;该行从列表中消失 | | ☐ |
| 4.4 | 在仅剩 1 条数据的列表上点击「删除」并确认 | 删除成功后,列表变为空,分页回到第 1 页 | | ☐ |
| 4.5 | 不勾选任何数据,直接点击工具栏「批量删除」 | 顶部红色错误提示「请先选择要删除的数据」 | | ☐ |
| 4.6 | 勾选 3 条数据,点击「批量删除」,确认框标题与按钮中文显示 | 弹出确认框「确定要删除所选物料吗?」 | | ☐ |
| 4.7 | 在 4.6 弹出的确认框中点击「确定」 | 3 条数据全部删除,顶部提示「操作成功」;列表刷新 | | ☐ |
| 4.8 | 尝试删除被其他业务(如 BOM引用的物料 | 后端返回「存在引用,无法删除」或类似错误 | | ☐ |
| 4.9 | 删除操作期间切换「每页显示条数」 | 删除后页码自动修正,避免出现空页 | | ☐ |
#### 3.4.5 权限与异常场景
| # | 操作步骤 | 预期结果 | 实际结果 | 通过 |
|---|---------|---------|---------|:----:|
| 5.1 | 使用无「新增」权限的账号登录 | 工具栏「新增」按钮不显示或不可点击 | | ☐ |
| 5.2 | 使用无「编辑」权限的账号登录 | 行内「编辑」按钮不显示 | | ☐ |
| 5.3 | 使用无「删除」权限的账号登录 | 行内「删除」按钮不显示 | | ☐ |
| 5.4 | 使用无「批量删除」权限的账号登录 | 工具栏「批量删除」按钮不显示或不可点击 | | ☐ |
| 5.5 | 断网后刷新页面 | 表格显示加载失败或空态DevTools Network 显示请求失败 | | ☐ |
| 5.6 | 后端返回 500 错误(如临时停服) | 顶部红色错误提示(如「操作失败」),不卡死页面 | | ☐ |
| 5.7 | 物料类别下拉数据加载失败时进入新增弹框 | 类别下拉为空,但仍可正常打开弹框 | | ☐ |
| 5.8 | 同时打开两个浏览器标签页,在 A 标签新增数据后B 标签刷新列表 | B 标签列表应能看到 A 标签新增的数据 | | ☐ |
#### 3.4.6 国际化与界面
| # | 操作步骤 | 预期结果 | 实际结果 | 通过 |
|---|---------|---------|---------|:----:|
| 6.1 | 中文状态下打开新增弹框 | 弹框标题、字段标签、按钮文字均为中文 | | ☐ |
| 6.2 | 切换为「English」后再次打开新增弹框 | 弹框标题「Add Material」按钮「Confirm / Cancel」 | | ☐ |
| 6.3 | 中文状态下点击删除,确认弹框标题为「提示」 | 确认框标题与按钮文字中文显示 | | ☐ |
| 6.4 | 英文状态下点击删除确认弹框标题为「Tip」 | 确认框英文显示 | | ☐ |
| 6.5 | 列表右上角「帮助」按钮(? 图标) | 鼠标悬停或点击显示「物料信息用于维护物料编码、名称、规格等属性」 | | ☐ |
| 6.6 | 中文状态下批量删除无选中数据 | 错误提示「请先选择要删除的数据」 | | ☐ |
| 6.7 | 英文状态下批量删除无选中数据 | 错误提示「Please select data first」 | | ☐ |
### 3.5 接口契约(用于前后端联调核对)
| 方法 | URL | 旧 method 名 | 入参关键 key | 备注 |
|------|-----|-------------|-------------|------|
| GET | `production_configuration/matetial_model/matetial_management/list` | `production_configuration_matetial_model_matetial_management_list` | `code / name / bom_source_category_id / page_no / page_size` | 列表查询 |
| GET | `…/all` | `…_all` | — | 全部物料(下拉框用,可选) |
| POST | `…/create` | `…_create` | `code / name / bom_source_category_id / unit_id / remark` | 新增 |
| PUT | `…/edit` | `…_edit` | `id / code / name / bom_source_category_id / unit_id / remark` | 编辑(`id` 为行主键) |
| DELETE | `…/delete` | `…_delete` | `id: [rowId]` | 单条删除,参数名 `id` 数组(与旧项目保持一致) |
| DELETE | `…/batch_delete` | `…_batch_delete` | `id: [id1, id2, …]` | 批量删除 |
| POST | `…/get_import_template` | `…_get_import_template` | — | 导入模板下载(响应 `blob`),本版本预留 |
| POST | `…/matetial_data_import` | `…_matetial_data_import` | `import_data: JSON.stringify([...])` | 物料数据导入,本版本预留 |
| POST | `…/matetial_data_export_task` | `…_matetial_data_export_task` | — | 物料数据导出任务创建,本版本预留 |
> 说明:本版本优先实现列表 / 新增 / 编辑 / 删除 / 批量删除五大核心功能;导入 / 导出Excel 模板下载、批量导入、导出任务)保留 API 方法和钩子位置,待后续迭代中补齐弹框 UI。
### 3.6 问题记录
> 测试人员发现问题时填写。每条问题一行,格式:`[编号] [用例编号] [现象] [复现步骤] [期望] [截图/日志] [负责人] [处理状态]`。
| 编号 | 用例 | 现象 | 复现步骤 | 期望 | 截图/日志 | 负责人 | 状态 |
|:---:|:---:|------|---------|------|----------|:----:|:----:|
| | | | | | | | |
### 3.7 备注
- 本页面沿用旧 URL 与后台 method 名,待后台数据库路由表统一更新后统一修改。
- 「物料类别」下拉数据由 `getMaterialCategoryAll` 提供,依赖「物料类别列表」功能已迁移完成;如该下拉为空,请先检查该模块是否正常运行。
- 「单位」字段在当前版本以文本框占位,依赖「计量单位」模块迁移后接入下拉数据源(与 BOM 等联动),测试时需在「备注」中注明该功能尚未启用。
- 涉及 `confirmMixin` / `useTableColumns` / `useTableButtons` / `i18nMixin` 等公共 Composable如其他页面出现类似问题先确认是否在迁移组件时有遗漏。
- 备注长度 > 20 字符时使用 `el-popover` 悬停展示全量内容;该交互与旧项目保持一致。
- 若新增 / 编辑成功后接口返回结构变化(`res.data` 包裹层调整),需要同步更新 `fetchData` 的解析逻辑。

View File

@@ -0,0 +1,57 @@
import { request } from '@/api/_service'
// 注BASE URL 与后台 method 名暂沿用旧项目命名,等待后台数据库路由表统一更新
const BASE = 'production_configuration/matetial_model/matetial_category/'
function apiParams (method, data = {}) {
return {
method: `production_configuration_matetial_model_matetial_category_${method}`,
platform: 'background',
...data
}
}
// 获取全部物料类别
export function getMaterialCategoryAll (data) {
return request({
url: BASE + 'all',
method: 'get',
params: apiParams('all', data)
})
}
// 获取物料类别列表
export function getMaterialCategoryList (data) {
return request({
url: BASE + 'list',
method: 'get',
params: apiParams('list', data)
})
}
// 创建物料类别
export function createMaterialCategory (data) {
return request({
url: BASE + 'create',
method: 'post',
data: apiParams('create', data)
})
}
// 编辑物料类别
export function editMaterialCategory (data) {
return request({
url: BASE + 'edit',
method: 'put',
data: apiParams('edit', data)
})
}
// 删除物料类别
export function deleteMaterialCategory (data) {
return request({
url: BASE + 'delete',
method: 'delete',
data: apiParams('delete', data)
})
}

View File

@@ -0,0 +1,94 @@
import { request } from '@/api/_service'
// 注BASE URL 与后台 method 名暂沿用旧项目命名,等待后台数据库路由表统一更新
const BASE = 'production_configuration/matetial_model/matetial_management/'
function apiParams (method, data = {}) {
return {
method: `production_configuration_matetial_model_matetial_management_${method}`,
platform: 'background',
...data
}
}
// 获取全部物料
export function getMaterialMasterAll (data) {
return request({
url: BASE + 'all',
method: 'get',
params: apiParams('all', data)
})
}
// 获取物料列表
export function getMaterialMasterList (data) {
return request({
url: BASE + 'list',
method: 'get',
params: apiParams('list', data)
})
}
// 创建物料
export function createMaterialMaster (data) {
return request({
url: BASE + 'create',
method: 'post',
data: apiParams('create', data)
})
}
// 编辑物料
export function editMaterialMaster (data) {
return request({
url: BASE + 'edit',
method: 'put',
data: apiParams('edit', data)
})
}
// 删除物料
export function deleteMaterialMaster (data) {
return request({
url: BASE + 'delete',
method: 'delete',
data: apiParams('delete', data)
})
}
// 批量删除物料
export function batchDeleteMaterialMaster (data) {
return request({
url: BASE + 'batch_delete',
method: 'delete',
data: apiParams('batch_delete', data)
})
}
// 获取物料数据导入模板
export function getImportTemplate (data) {
return request({
url: BASE + 'get_import_template',
method: 'post',
responseType: 'blob',
data: apiParams('get_import_template', data)
})
}
// 物料数据导入
export function importMaterialData (data) {
return request({
url: BASE + 'matetial_data_import',
method: 'post',
data: apiParams('matetial_data_import', data)
})
}
// 物料数据导出任务创建
export function exportMaterialTask (data) {
return request({
url: BASE + 'matetial_data_export_task',
method: 'post',
data: apiParams('matetial_data_export_task', data)
})
}

View File

@@ -0,0 +1,57 @@
import { request } from '@/api/_service'
// 注BASE URL 与后台 method 名暂沿用旧项目命名,等待后台数据库路由表统一更新
const BASE = 'production_configuration/matetial_model/unit/'
function apiParams (method, data = {}) {
return {
method: `production_configuration_matetial_model_unit_${method}`,
platform: 'background',
...data
}
}
// 获取全部单位(下拉框用)
export function getUnitAll (data) {
return request({
url: BASE + 'all',
method: 'get',
params: apiParams('all', data)
})
}
// 获取单位列表
export function getUnitList (data) {
return request({
url: BASE + 'list',
method: 'get',
params: apiParams('list', data)
})
}
// 创建单位
export function createUnit (data) {
return request({
url: BASE + 'create',
method: 'post',
data: apiParams('create', data)
})
}
// 编辑单位
export function editUnit (data) {
return request({
url: BASE + 'edit',
method: 'put',
data: apiParams('edit', data)
})
}
// 删除单位
export function deleteUnit (data) {
return request({
url: BASE + 'delete',
method: 'delete',
data: apiParams('delete', data)
})
}

View File

@@ -0,0 +1,64 @@
import { request } from '@/api/_service'
const BASE = 'production_configuration/technology_model/optional_params/'
function apiParams (method, data = {}) {
return {
method: `production_master_data_process_model_optional_params_${method}`,
platform: 'background',
...data
}
}
export function getOptionalParamsList (data) {
return request({
url: BASE + 'list',
method: 'get',
params: apiParams('list', data)
})
}
export function createOptionalParams (data) {
return request({
url: BASE + 'create',
method: 'post',
data: apiParams('create', data)
})
}
export function editOptionalParams (data) {
return request({
url: BASE + 'edit',
method: 'put',
data: apiParams('edit', data)
})
}
export function deleteOptionalParams (data) {
return request({
url: BASE + 'delete',
method: 'delete',
data: apiParams('delete', data)
})
}
export function getImportTemplate (data) {
return request({
url: BASE + 'get_import_template',
method: 'post',
responseType: 'blob',
data: {
method: 'production_master_data_process_model_optional_params_get_import_template',
platform: 'background',
...data
}
})
}
export function importDataCreate (data) {
return request({
url: BASE + 'import_data_create',
method: 'post',
data: apiParams('import_data_create', data)
})
}

View File

@@ -0,0 +1,55 @@
import { request } from '@/api/_service'
const BASE = 'production_configuration/technology_model/technology_flow_workingsubclass/'
function apiParams (method, data = {}) {
return {
method: `production_master_data_process_model_process_step_${method}`,
platform: 'background',
...data
}
}
export function getProcessStepList (data) {
return request({
url: BASE + 'list',
method: 'get',
params: apiParams('list', data)
})
}
export function createProcessStep (data) {
return request({
url: BASE + 'create',
method: 'post',
data: apiParams('create', data)
})
}
export function editProcessStep (data) {
return request({
url: BASE + 'edit',
method: 'put',
data: apiParams('edit', data)
})
}
export function deleteProcessStep (data) {
return request({
url: BASE + 'delete',
method: 'delete',
data: apiParams('delete', data)
})
}
export function settingSubmit (data) {
return request({
url: BASE + 'setting_submit',
method: 'post',
data: {
method: 'production_master_data_process_model_process_step_setting_submit',
platform: 'background',
...data
}
})
}

View File

@@ -99,6 +99,153 @@
"confirm_delete": "Are you sure to delete?",
"validation_fail": "Validation failed",
"please_enter": "Please enter {name}"
},
"process_step": {
"search": "Search",
"reset": "Reset",
"query": "Query",
"sort": "No.",
"process_unit_code": "Process Unit Code",
"process_unit_name": "Process Unit Name",
"device_category": "Device Category",
"remark": "Remark",
"enter_remark": "Enter remark",
"operation": "Actions",
"add": "Add",
"edit": "Edit",
"delete": "Delete",
"tips": "Tip",
"confirm": "Confirm",
"cancel": "Cancel",
"enter_process_unit_code": "Enter process unit code",
"enter_process_unit_name": "Enter process unit name",
"select_device_category": "Please select device category",
"remark_required": "Please enter remark",
"preset_setting": "Preset Settings",
"add_process_unit": "Add Process Unit",
"edit_process_unit": "Edit Process Unit",
"preset_result_param": "Preset Result Parameters",
"length_1_100": "Length must be 1-100 characters",
"operation_success": "Operation succeeded",
"validation_fail": "Validation failed",
"confirm_message": "Proceed with this action?",
"name": "Name",
"param": "Parameter",
"category": "Category",
"default_value": "Default Value",
"unit": "Unit",
"is_unique": "Is Unique",
"yes": "Yes",
"no": "No",
"is_upload": "Is Upload Required",
"need_device_upload": "Requires Device Upload",
"add_row": "Add Row",
"import": "Import",
"close": "Close",
"help": "Process unit is the basic operation unit in the process flow, used to define each processing step in the production process.",
"enter_name": "Please enter name",
"enter_param": "Please enter parameter",
"select_type": "Please select type",
"confirm_delete_data": "Are you sure to delete this data?",
"save_success": "Saved successfully",
"add_success": "Added successfully",
"delete_success": "Deleted successfully",
"setting_placeholder": "Preset setting configuration area",
"setting_tip": "Configure preset settings for this process unit here",
"import_tip": "Import function is under development"
}
},
"material_model": {
"material_category": {
"search": "Search",
"reset": "Reset",
"enter_code": "Please enter category code",
"enter_name": "Please enter category name",
"enter_remark": "Please enter remark",
"operation_success": "Operation succeeded",
"create_success": "Create succeeded",
"edit_success": "Edit succeeded",
"delete_success": "Delete succeeded",
"sort": "#",
"code": "Code",
"name": "Name",
"remark": "Remark",
"create_time": "Created At",
"operation": "Action",
"add": "Add",
"edit": "Edit",
"delete": "Delete",
"remark_length": "Length 1 to 45 characters",
"add_title": "Add Material Category",
"edit_title": "Edit Material Category",
"cancel": "Cancel",
"confirm": "Confirm",
"tip": "Tip",
"confirm_delete": "Are you sure to perform this operation?",
"validation_fail": "Validation failed",
"please_enter": "Please enter {name}",
"help": "Material category is used to distinguish raw materials and semi-finished products"
},
"material_master": {
"search": "Search",
"reset": "Reset",
"material_code": "Material Code",
"material_name": "Material Name",
"material_category": "Category",
"unit": "Unit",
"enter_material_code": "Please enter material code",
"enter_material_name": "Please enter material name",
"select_material_category": "Please select a category",
"select_unit": "Please select a unit",
"remark": "Remark",
"enter_remark": "Please enter remark",
"remark_length": "Length 1 to 100 characters",
"create_user": "Created By",
"create_time": "Created At",
"operation": "Action",
"add": "Add",
"edit": "Edit",
"delete": "Delete",
"batch_delete": "Batch Delete",
"operation_success": "Operation succeeded",
"add_title": "Add Material",
"edit_title": "Edit Material",
"cancel": "Cancel",
"confirm": "Confirm",
"tip": "Tip",
"confirm_delete": "Are you sure to perform this operation?",
"confirm_batch_delete": "Are you sure to delete the selected materials?",
"please_select_data": "Please select data first",
"validation_fail": "Validation failed",
"please_enter": "Please enter {name}",
"help": "Material master data is used to maintain material code, name, specifications, etc."
},
"material_unit": {
"search": "Search",
"reset": "Reset",
"unit_code": "Unit Code",
"unit_name": "Unit Name",
"enter_unit_code": "Please enter unit code",
"enter_unit_name": "Please enter unit name",
"remark": "Remark",
"enter_remark": "Please enter remark",
"length_1_100": "Length 1 to 100 characters",
"create_time": "Created At",
"operation": "Action",
"sort": "#",
"add": "Add",
"edit": "Edit",
"delete": "Delete",
"operation_success": "Operation succeeded",
"add_title": "Add Unit",
"edit_title": "Edit Unit",
"cancel": "Cancel",
"confirm": "Confirm",
"tip": "Tip",
"confirm_delete": "Are you sure to perform this operation?",
"validation_fail": "Validation failed",
"please_enter": "Please enter {name}",
"help": "Unit is used to maintain material measurement units (e.g. piece, box, kg)"
}
},
"product_management": {

View File

@@ -99,6 +99,153 @@
"confirm_delete": "确定要执行该操作吗?",
"validation_fail": "校验失败",
"please_enter": "请输入{name}"
},
"process_step": {
"search": "查询",
"reset": "重置",
"query": "查询",
"sort": "序号",
"process_unit_code": "工序单元编码",
"process_unit_name": "工序单元名称",
"device_category": "设备类别",
"remark": "备注",
"enter_remark": "请输入备注",
"operation": "操作",
"add": "新增",
"edit": "编辑",
"delete": "删除",
"tips": "提示",
"confirm": "确定",
"cancel": "取消",
"enter_process_unit_code": "请输入工序单元编码",
"enter_process_unit_name": "请输入工序单元名称",
"select_device_category": "请选择设备类别",
"remark_required": "请输入备注",
"preset_setting": "预设设定值",
"add_process_unit": "新增工序单元",
"edit_process_unit": "编辑工序单元",
"preset_result_param": "预设结果参数",
"length_1_100": "长度在 1 到 100 个字符",
"operation_success": "操作成功",
"validation_fail": "校验失败",
"confirm_message": "确定要执行该操作吗?",
"name": "名称",
"param": "参数",
"category": "类别",
"default_value": "默认值",
"unit": "单位",
"is_unique": "是否唯一",
"yes": "是",
"no": "否",
"is_upload": "是否上传",
"need_device_upload": "是否需要设备上传",
"add_row": "新增一行",
"import": "导入",
"close": "关闭",
"help": "工序单元是工艺流程中的基本作业单元,用于定义生产过程中的各个加工步骤。",
"enter_name": "请输入名称",
"enter_param": "请输入参数",
"select_type": "请选择类型",
"confirm_delete_data": "确定删除该数据?",
"save_success": "保存成功",
"add_success": "新增成功",
"delete_success": "删除成功",
"setting_placeholder": "预设设定值配置区域",
"setting_tip": "在此配置工序单元的预设设定值",
"import_tip": "导入功能开发中"
}
},
"material_model": {
"material_category": {
"search": "查询",
"reset": "重置",
"enter_code": "请输入物料类别编码",
"enter_name": "请输入物料类别名称",
"enter_remark": "请输入备注",
"operation_success": "操作成功",
"create_success": "新增成功",
"edit_success": "编辑成功",
"delete_success": "删除成功",
"sort": "序号",
"code": "物料类别编码",
"name": "物料类别名称",
"remark": "备注",
"create_time": "创建时间",
"operation": "操作",
"add": "新 增",
"edit": "编 辑",
"delete": "删 除",
"remark_length": "长度在 1 到 45 个字符",
"add_title": "新增物料类别",
"edit_title": "编辑物料类别",
"cancel": "取消",
"confirm": "确定",
"tip": "提示",
"confirm_delete": "确定要执行该操作吗?",
"validation_fail": "校验失败",
"please_enter": "请输入{name}",
"help": "物料类别用于区分原材料和半成品"
},
"material_master": {
"search": "查询",
"reset": "重置",
"material_code": "物料编码",
"material_name": "物料名称",
"material_category": "物料类别",
"unit": "单位",
"enter_material_code": "请输入物料编码",
"enter_material_name": "请输入物料名称",
"select_material_category": "请选择物料类别",
"select_unit": "请选择单位",
"remark": "备注",
"enter_remark": "请输入备注",
"remark_length": "长度在 1 到 100 个字符",
"create_user": "创建人",
"create_time": "创建时间",
"operation": "操作",
"add": "新 增",
"edit": "编 辑",
"delete": "删 除",
"batch_delete": "批量删除",
"operation_success": "操作成功",
"add_title": "新增物料信息",
"edit_title": "编辑物料信息",
"cancel": "取消",
"confirm": "确定",
"tip": "提示",
"confirm_delete": "确定要执行该操作吗?",
"confirm_batch_delete": "确定要删除所选物料吗?",
"please_select_data": "请先选择要删除的数据",
"validation_fail": "校验失败",
"please_enter": "请输入{name}",
"help": "物料信息用于维护物料编码、名称、规格等属性"
},
"material_unit": {
"search": "查询",
"reset": "重置",
"unit_code": "单位编码",
"unit_name": "单位名称",
"enter_unit_code": "请输入单位编码",
"enter_unit_name": "请输入单位名称",
"remark": "备注",
"enter_remark": "请输入备注",
"length_1_100": "长度在 1 到 100 个字符",
"create_time": "创建时间",
"operation": "操作",
"sort": "序号",
"add": "新 增",
"edit": "编 辑",
"delete": "删 除",
"operation_success": "操作成功",
"add_title": "新增单位",
"edit_title": "编辑单位",
"cancel": "取消",
"confirm": "确定",
"tip": "提示",
"confirm_delete": "确定要执行该操作吗?",
"validation_fail": "校验失败",
"please_enter": "请输入{name}",
"help": "计量单位用于维护物料所用单位如个、箱、kg"
}
},
"product_management": {

View File

@@ -32,11 +32,35 @@ export default {
meta: { ...meta, cache: true, title: '工艺流程类别' },
component: _import('production-master-data/process-model/process-category')
},
{
path: 'technology_model/technology_flow_workingsubclass',
name: `${pre}technology_model-technology_flow_workingsubclass`,
meta: { ...meta, cache: true, title: '工序单元' },
component: _import('production-master-data/process-model/process-step')
},
{
path: 'product_model/battery_model',
name: `${pre}product_management-product_list`,
meta: { ...meta, cache: true, title: '产品列表' },
component: _import('production-master-data/product-management/product-list')
},
{
path: 'matetial_model/matetial_category',
name: `${pre}material_model-material_category`,
meta: { ...meta, cache: true, title: '物料类别列表' },
component: _import('production-master-data/material-model/material-category')
},
{
path: 'matetial_model/matetial_management',
name: `${pre}material_model-material_master`,
meta: { ...meta, cache: true, title: '物料信息管理' },
component: _import('production-master-data/material-model/material-master')
},
{
path: 'matetial_model/unit',
name: `${pre}material_model-material_unit`,
meta: { ...meta, cache: true, title: '计量单位' },
component: _import('production-master-data/material-model/material-unit')
}
])('production_configuration-')
}

View File

@@ -0,0 +1,294 @@
<template>
<d2-container>
<template #header>
<div class="search-bar">
<el-form :inline="true" size="mini">
<el-form-item :label="$t(key('code'))">
<el-input
v-model="search.code"
:placeholder="$t(key('enter_code'))"
clearable
style="width:200px"
@keyup.enter.native="onSearch"
/>
</el-form-item>
<el-form-item :label="$t(key('name'))">
<el-input
v-model="search.name"
:placeholder="$t(key('enter_name'))"
clearable
style="width:200px"
@keyup.enter.native="onSearch"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="onSearch">
{{ $t(key('search')) }}
</el-button>
<el-button icon="el-icon-refresh" @click="onReset">
{{ $t(key('reset')) }}
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<page-table
ref="pageTable"
:columns="columns"
:data="tableData"
:loading="loading"
:toolbar-buttons="toolbarButtons"
:row-buttons="rowButtons"
:pagination="pagination"
help-url="/help/material-category"
:help-text="$t(ckey('help'))"
auto-height
@page-change="onPageChange"
@selection-change="onSelect"
/>
<page-dialog-form
ref="dialogForm"
:visible.sync="dialogVisible"
:title="dialogTitle"
:width="'35%'"
:form-cols="formCols"
:form-data="formData"
:rules="rules"
:label-width="'100px'"
:submitting="submitting"
:confirm-text="key('confirm')"
:cancel-text="key('cancel')"
@submit="onDialogSubmit"
@close="onDialogClose"
/>
</d2-container>
</template>
<script>
import { useTableColumns } from '@/composables/useTableColumns'
import { useTableButtons } from '@/composables/useTableButtons'
import { i18nMixin } from '@/composables/useI18n'
import { confirmMixin } from '@/composables/useConfirmHandle'
import {
getMaterialCategoryList,
createMaterialCategory,
editMaterialCategory,
deleteMaterialCategory
} from '@/api/production-master-data/material-category'
import PageTable from '@/components/page-table'
import PageDialogForm from '@/components/page-dialog-form'
export default {
name: 'production-master-data-material-category',
components: { PageTable, PageDialogForm },
mixins: [i18nMixin('page.production_master_data.material_model.material_category'), confirmMixin],
data () {
return {
loading: false,
submitting: false,
tableData: [],
selectedRows: [],
dialogVisible: false,
dialogTitle: '',
editId: '',
handleType: 'create',
search: { code: '', name: '' },
pagination: { current: 1, size: 10, total: 0 },
formData: { code: '', name: '', remark: '' },
rules: {
code: [
{ required: true, message: this.key('enter_code'), trigger: 'blur' },
{ min: 1, max: 45, message: this.key('remark_length'), trigger: 'blur' }
],
name: [
{ required: true, message: this.key('enter_name'), trigger: 'blur' },
{ min: 1, max: 45, message: this.key('remark_length'), trigger: 'blur' }
]
},
columns: [],
toolbarButtons: [],
rowButtons: [],
formCols: [
[
{
type: 'input',
prop: 'code',
label: this.key('code'),
placeholder: this.key('enter_code'),
clearable: true,
style: { width: '90%' }
}
],
[
{
type: 'input',
prop: 'name',
label: this.key('name'),
placeholder: this.key('enter_name'),
clearable: true,
style: { width: '90%' }
}
],
[
{
type: 'input',
prop: 'remark',
inputType: 'textarea',
autosize: { minRows: 2, maxRows: 6 },
label: this.key('remark'),
placeholder: this.key('enter_remark'),
clearable: true,
style: { width: '90%' }
}
]
]
}
},
created () {
this.columns = useTableColumns([
{ prop: 'sort', label: this.key('sort'), width: 80 },
{ prop: 'code', label: this.key('code'), minWidth: 120 },
{ prop: 'name', label: this.key('name'), minWidth: 120 },
{ prop: 'remark', label: this.key('remark') },
{ prop: 'create_time', label: this.key('create_time'), minWidth: 160 },
{ prop: '_actions', label: this.key('operation'), width: 160, fixed: 'right' }
])
const btns = useTableButtons({
toolbar: [
{
key: 'add',
label: this.key('add'),
icon: 'el-icon-plus',
type: 'primary',
auth: '/production_configuration/matetial_model/matetial_category/create',
onClick: this.openAdd
}
],
row: [
{
key: 'edit',
label: this.key('edit'),
icon: 'el-icon-edit',
auth: '/production_configuration/matetial_model/matetial_category/edit',
onClick: this.openEdit
},
{
key: 'delete',
label: this.key('delete'),
icon: 'el-icon-delete',
color: 'danger',
auth: '/production_configuration/matetial_model/matetial_category/delete',
onClick: this.handleDelete
}
]
}, this.$permission)
this.toolbarButtons = btns.toolbarButtons
this.rowButtons = btns.rowButtons
this.fetchData()
},
methods: {
async fetchData () {
this.loading = true
try {
const res = await getMaterialCategoryList({
...this.search,
page_no: this.pagination.current,
page_size: this.pagination.size
})
const list = Array.isArray(res) ? res : (res.data || [])
const total = Array.isArray(res) ? res.length : (res.count || 0)
this.tableData = list
this.pagination.total = total
} finally {
this.loading = false
}
},
onSearch () {
this.pagination.current = 1
this.fetchData()
},
onReset () {
this.search = { code: '', name: '' }
this.pagination.current = 1
this.fetchData()
},
onPageChange (page) {
this.pagination.current = page.current
this.pagination.size = page.size
this.fetchData()
},
onSelect (rows) {
this.selectedRows = rows
},
resetForm () {
this.formData = { code: '', name: '', remark: '' }
this.editId = ''
},
openAdd () {
this.handleType = 'create'
this.dialogTitle = this.key('add_title')
this.$nextTick(() => {
this.$refs.dialogForm && this.$refs.dialogForm.reset()
this.resetForm()
this.dialogVisible = true
})
},
openEdit (row) {
this.handleType = 'edit'
this.dialogTitle = this.key('edit_title')
this.editId = row.id
this.formData = {
code: row.code,
name: row.name,
remark: row.remark || ''
}
this.dialogVisible = true
},
async onDialogSubmit () {
this.submitting = true
try {
if (this.handleType === 'create') {
await createMaterialCategory(this.formData)
} else {
await editMaterialCategory({ ...this.formData, id: this.editId })
}
this.$message.success(this.$t(this.key('operation_success')))
this.dialogVisible = false
this.fetchData()
} finally {
this.submitting = false
}
},
onDialogClose () {
this.resetForm()
},
async handleDelete (row) {
const cancelled = await this.$confirmAction(
{
message: this.key('confirm_delete'),
title: this.key('tip')
},
() => deleteMaterialCategory({ id: [row.id] })
)
if (cancelled) return
this.$message.success(this.$t(this.key('operation_success')))
this.pagination.current = Math.min(
this.pagination.current,
Math.ceil((this.pagination.total - 1) / this.pagination.size) || 1
)
this.fetchData()
}
}
}
</script>
<style scoped>
.search-bar {
padding: 10px 0;
}
/deep/ .el-form-item--mini.el-form-item {
margin-bottom: 4px;
}
</style>

View File

@@ -0,0 +1,401 @@
<template>
<d2-container>
<template #header>
<div class="search-bar">
<el-form :inline="true" size="mini">
<el-form-item :label="$t(key('material_code'))">
<el-input
v-model="search.code"
:placeholder="$t(key('enter_material_code'))"
clearable
style="width:200px"
@keyup.enter.native="onSearch"
/>
</el-form-item>
<el-form-item :label="$t(key('material_name'))">
<el-input
v-model="search.name"
:placeholder="$t(key('enter_material_name'))"
clearable
style="width:200px"
@keyup.enter.native="onSearch"
/>
</el-form-item>
<el-form-item :label="$t(key('material_category'))">
<el-select
v-model="search.bom_source_category_id"
:placeholder="$t(key('select_material_category'))"
clearable
filterable
style="width:200px"
>
<el-option
v-for="item in categoryOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="onSearch">
{{ $t(key('search')) }}
</el-button>
<el-button icon="el-icon-refresh" @click="onReset">
{{ $t(key('reset')) }}
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<page-table
ref="pageTable"
:columns="columns"
:data="tableData"
:loading="loading"
:toolbar-buttons="toolbarButtons"
:row-buttons="rowButtons"
:pagination="pagination"
help-url="/help/material-master"
:help-text="$t(ckey('help'))"
auto-height
@page-change="onPageChange"
@selection-change="onSelect"
>
<template #col-remark="{ row }">
<el-popover
v-if="row.remark && row.remark.length > 20"
placement="top-start"
width="300"
trigger="hover"
:content="row.remark"
>
<span slot="reference" style="cursor: pointer;">{{ row.remark.substr(0, 20) }}...</span>
</el-popover>
<span v-else>{{ row.remark }}</span>
</template>
</page-table>
<page-dialog-form
ref="dialogForm"
:visible.sync="dialogVisible"
:title="dialogTitle"
:width="'35%'"
:form-cols="dialogFormCols"
:form-data="formData"
:rules="rules"
:label-width="'100px'"
:submitting="submitting"
:confirm-text="key('confirm')"
:cancel-text="key('cancel')"
@submit="onDialogSubmit"
@close="onDialogClose"
/>
</d2-container>
</template>
<script>
import { useTableColumns } from '@/composables/useTableColumns'
import { useTableButtons } from '@/composables/useTableButtons'
import { i18nMixin } from '@/composables/useI18n'
import { confirmMixin } from '@/composables/useConfirmHandle'
import {
getMaterialMasterList,
createMaterialMaster,
editMaterialMaster,
deleteMaterialMaster,
batchDeleteMaterialMaster
} from '@/api/production-master-data/material-master'
import { getMaterialCategoryAll } from '@/api/production-master-data/material-category'
import PageTable from '@/components/page-table'
import PageDialogForm from '@/components/page-dialog-form'
export default {
name: 'production-master-data-material-master',
components: { PageTable, PageDialogForm },
mixins: [i18nMixin('page.production_master_data.material_model.material_master'), confirmMixin],
data () {
return {
loading: false,
submitting: false,
tableData: [],
selectedRows: [],
dialogVisible: false,
dialogTitle: '',
editId: '',
handleType: 'create',
search: { code: '', name: '', bom_source_category_id: '' },
pagination: { current: 1, size: 10, total: 0 },
categoryOptions: [],
formData: { code: '', name: '', bom_source_category_id: '', unit_id: '', remark: '' },
rules: {
code: [
{ required: true, message: this.key('enter_material_code'), trigger: 'blur' },
{ min: 1, max: 100, message: this.key('remark_length'), trigger: 'blur' }
],
name: [
{ required: true, message: this.key('enter_material_name'), trigger: 'blur' },
{ min: 1, max: 100, message: this.key('remark_length'), trigger: 'blur' }
],
bom_source_category_id: [
{ required: true, message: this.key('select_material_category'), trigger: 'change' }
]
},
columns: [],
toolbarButtons: [],
rowButtons: [],
// 表单基础结构,单位字段预留(待 unit 模块迁移后接入下拉数据源)
baseFormCols: [
[
{
type: 'input',
prop: 'code',
label: this.key('material_code'),
placeholder: this.key('enter_material_code'),
clearable: true,
style: { width: '90%' }
}
],
[
{
type: 'input',
prop: 'name',
label: this.key('material_name'),
placeholder: this.key('enter_material_name'),
clearable: true,
style: { width: '90%' }
}
],
[
{
type: 'select',
prop: 'bom_source_category_id',
label: this.key('material_category'),
placeholder: this.key('select_material_category'),
clearable: true,
filterable: true,
style: { width: '90%' },
options: []
}
],
[
{
type: 'input',
prop: 'unit_id',
label: this.key('unit'),
placeholder: this.key('select_unit'),
clearable: true,
style: { width: '90%' }
}
],
[
{
type: 'input',
prop: 'remark',
inputType: 'textarea',
autosize: { minRows: 2, maxRows: 6 },
label: this.key('remark'),
placeholder: this.key('enter_remark'),
clearable: true,
style: { width: '90%' }
}
]
]
}
},
computed: {
// 类别下拉数据通过 computed 注入,避免 JSON.parse(JSON.stringify) 深拷贝
dialogFormCols () {
return this.baseFormCols.map(row => row.map(item => {
if (item.prop === 'bom_source_category_id') {
return { ...item, options: this.categoryOptions }
}
return item
}))
}
},
created () {
this.initCategoryOptions()
this.columns = useTableColumns([
{ prop: 'sort', label: this.key('sort'), width: 80 },
{ prop: 'code', label: this.key('material_code'), minWidth: 120 },
{ prop: 'name', label: this.key('material_name'), minWidth: 140 },
{ prop: 'bom_source_category_name', label: this.key('material_category'), minWidth: 120 },
{ prop: 'unit_name', label: this.key('unit'), minWidth: 100 },
{ prop: 'remark', label: this.key('remark'), minWidth: 160, slot: true },
{ prop: 'username', label: this.key('create_user'), minWidth: 100 },
{ prop: 'create_time', label: this.key('create_time'), minWidth: 160 },
{ prop: '_actions', label: this.key('operation'), width: 180, fixed: 'right' }
])
const btns = useTableButtons({
toolbar: [
{
key: 'add',
label: this.key('add'),
icon: 'el-icon-plus',
type: 'primary',
auth: '/production_configuration/matetial_model/matetial_management/create',
onClick: this.openAdd
},
{
key: 'batch_delete',
label: this.key('batch_delete'),
icon: 'el-icon-delete',
color: 'danger',
auth: '/system_settings/matetial_model/matetial_management/batch-delete',
onClick: this.handleBatchDelete
}
],
row: [
{
key: 'edit',
label: this.key('edit'),
icon: 'el-icon-edit',
auth: '/production_configuration/matetial_model/matetial_management/edit',
onClick: this.openEdit
},
{
key: 'delete',
label: this.key('delete'),
icon: 'el-icon-delete',
color: 'danger',
auth: '/production_configuration/matetial_model/matetial_management/delete',
onClick: this.handleDelete
}
]
}, this.$permission)
this.toolbarButtons = btns.toolbarButtons
this.rowButtons = btns.rowButtons
this.fetchData()
},
methods: {
async initCategoryOptions () {
try {
const res = await getMaterialCategoryAll()
const data = Array.isArray(res) ? res : (res.data || [])
this.categoryOptions = data.map(item => ({ value: item.id, label: item.name }))
} catch { /* 类别下拉加载失败时,下拉为空即可,不影响主流程 */ }
},
async fetchData () {
this.loading = true
try {
const res = await getMaterialMasterList({
...this.search,
page_no: this.pagination.current,
page_size: this.pagination.size
})
const list = Array.isArray(res) ? res : (res.data || [])
const total = Array.isArray(res) ? res.length : (res.count || 0)
this.tableData = list
this.pagination.total = total
} finally {
this.loading = false
}
},
onSearch () {
this.pagination.current = 1
this.fetchData()
},
onReset () {
this.search = { code: '', name: '', bom_source_category_id: '' }
this.pagination.current = 1
this.fetchData()
},
onPageChange (page) {
this.pagination.current = page.current
this.pagination.size = page.size
this.fetchData()
},
onSelect (rows) {
this.selectedRows = rows
},
resetForm () {
this.formData = { code: '', name: '', bom_source_category_id: '', unit_id: '', remark: '' }
this.editId = ''
},
openAdd () {
this.handleType = 'create'
this.dialogTitle = this.key('add_title')
this.$nextTick(() => {
this.$refs.dialogForm && this.$refs.dialogForm.reset()
this.resetForm()
this.dialogVisible = true
})
},
openEdit (row) {
this.handleType = 'edit'
this.dialogTitle = this.key('edit_title')
this.editId = row.id
this.formData = {
code: row.code,
name: row.name,
bom_source_category_id: row.bom_source_category_id,
unit_id: row.unit_id,
remark: row.remark || ''
}
this.dialogVisible = true
},
async onDialogSubmit () {
this.submitting = true
try {
if (this.handleType === 'create') {
await createMaterialMaster(this.formData)
} else {
await editMaterialMaster({ ...this.formData, id: this.editId })
}
this.$message.success(this.$t(this.key('operation_success')))
this.dialogVisible = false
this.fetchData()
} finally {
this.submitting = false
}
},
onDialogClose () {
this.resetForm()
},
async handleDelete (row) {
const cancelled = await this.$confirmAction(
{
message: this.key('confirm_delete'),
title: this.key('tip')
},
() => deleteMaterialMaster({ id: [row.id] })
)
if (cancelled) return
this.$message.success(this.$t(this.key('operation_success')))
this.pagination.current = Math.min(
this.pagination.current,
Math.ceil((this.pagination.total - 1) / this.pagination.size) || 1
)
this.fetchData()
},
async handleBatchDelete () {
if (this.selectedRows.length <= 0) {
this.$message.error(this.$t(this.key('please_select_data')))
return
}
const ids = this.selectedRows.map(item => item.id)
const cancelled = await this.$confirmAction(
{
message: this.key('confirm_batch_delete'),
title: this.key('tip')
},
() => batchDeleteMaterialMaster({ id: ids })
)
if (cancelled) return
this.$message.success(this.$t(this.key('operation_success')))
this.fetchData()
}
}
}
</script>
<style scoped>
.search-bar {
padding: 10px 0;
}
/deep/ .el-form-item--mini.el-form-item {
margin-bottom: 4px;
}
</style>

View File

@@ -0,0 +1,288 @@
<template>
<d2-container>
<template #header>
<div class="search-bar">
<el-form :inline="true" size="mini">
<el-form-item :label="$t(key('unit_code'))">
<el-input
v-model="search.code"
:placeholder="$t(key('enter_unit_code'))"
clearable
style="width:200px"
@keyup.enter.native="onSearch"
/>
</el-form-item>
<el-form-item :label="$t(key('unit_name'))">
<el-input
v-model="search.name"
:placeholder="$t(key('enter_unit_name'))"
clearable
style="width:200px"
@keyup.enter.native="onSearch"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="onSearch">
{{ $t(key('search')) }}
</el-button>
<el-button icon="el-icon-refresh" @click="onReset">
{{ $t(key('reset')) }}
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<page-table
ref="pageTable"
:columns="columns"
:data="tableData"
:loading="loading"
:toolbar-buttons="toolbarButtons"
:row-buttons="rowButtons"
:pagination="pagination"
:help-text="$t(ckey('help'))"
auto-height
@page-change="onPageChange"
/>
<page-dialog-form
ref="dialogForm"
:visible.sync="dialogVisible"
:title="dialogTitle"
:width="'35%'"
:form-cols="formCols"
:form-data="formData"
:rules="rules"
:label-width="'100px'"
:submitting="submitting"
:confirm-text="key('confirm')"
:cancel-text="key('cancel')"
@submit="onDialogSubmit"
@close="onDialogClose"
/>
</d2-container>
</template>
<script>
import { useTableColumns } from '@/composables/useTableColumns'
import { useTableButtons } from '@/composables/useTableButtons'
import { i18nMixin } from '@/composables/useI18n'
import { confirmMixin } from '@/composables/useConfirmHandle'
import {
getUnitList,
createUnit,
editUnit,
deleteUnit
} from '@/api/production-master-data/material-unit'
import PageTable from '@/components/page-table'
import PageDialogForm from '@/components/page-dialog-form'
export default {
name: 'production-master-data-material-unit',
components: { PageTable, PageDialogForm },
mixins: [i18nMixin('page.production_master_data.material_model.material_unit'), confirmMixin],
data () {
return {
loading: false,
submitting: false,
tableData: [],
dialogVisible: false,
dialogTitle: '',
editId: '',
handleType: 'create',
search: { code: '', name: '' },
pagination: { current: 1, size: 10, total: 0 },
formData: { code: '', name: '', remark: '' },
rules: {
code: [
{ required: true, message: this.key('enter_unit_code'), trigger: 'blur' },
{ min: 1, max: 100, message: this.key('length_1_100'), trigger: 'blur' }
],
name: [
{ required: true, message: this.key('enter_unit_name'), trigger: 'blur' },
{ min: 1, max: 100, message: this.key('length_1_100'), trigger: 'blur' }
]
},
formCols: [
[
{
type: 'input',
prop: 'code',
label: this.key('unit_code'),
placeholder: this.key('enter_unit_code'),
clearable: true,
style: { width: '90%' }
}
],
[
{
type: 'input',
prop: 'name',
label: this.key('unit_name'),
placeholder: this.key('enter_unit_name'),
clearable: true,
style: { width: '90%' }
}
],
[
{
type: 'input',
prop: 'remark',
inputType: 'textarea',
autosize: { minRows: 2, maxRows: 6 },
label: this.key('remark'),
placeholder: this.key('enter_remark'),
clearable: true,
style: { width: '90%' }
}
]
],
columns: [],
toolbarButtons: [],
rowButtons: []
}
},
created () {
this.columns = useTableColumns([
{ prop: 'sort', label: this.key('sort'), width: 80 },
{ prop: 'code', label: this.key('unit_code'), minWidth: 140 },
{ prop: 'name', label: this.key('unit_name'), minWidth: 140 },
{ prop: 'remark', label: this.key('remark'), minWidth: 200 },
{ prop: 'create_time', label: this.key('create_time'), minWidth: 160 },
{ prop: '_actions', label: this.key('operation'), width: 180, fixed: 'right' }
])
const btns = useTableButtons({
toolbar: [
{
key: 'add',
label: this.key('add'),
icon: 'el-icon-plus',
type: 'primary',
auth: '/production_configuration/matetial_model/unit/create',
onClick: this.openAdd
}
],
row: [
{
key: 'edit',
label: this.key('edit'),
icon: 'el-icon-edit',
auth: '/production_configuration/matetial_model/unit/edit',
onClick: this.openEdit
},
{
key: 'delete',
label: this.key('delete'),
icon: 'el-icon-delete',
color: 'danger',
auth: '/production_configuration/matetial_model/unit/delete',
onClick: this.handleDelete
}
]
}, this.$permission)
this.toolbarButtons = btns.toolbarButtons
this.rowButtons = btns.rowButtons
this.fetchData()
},
methods: {
async fetchData () {
this.loading = true
try {
const res = await getUnitList({
...this.search,
page_no: this.pagination.current,
page_size: this.pagination.size
})
const list = Array.isArray(res) ? res : (res.data || [])
const total = Array.isArray(res) ? res.length : (res.count || 0)
this.tableData = list
this.pagination.total = total
} finally {
this.loading = false
}
},
onSearch () {
this.pagination.current = 1
this.fetchData()
},
onReset () {
this.search = { code: '', name: '' }
this.pagination.current = 1
this.fetchData()
},
onPageChange (page) {
this.pagination.current = page.current
this.pagination.size = page.size
this.fetchData()
},
resetForm () {
this.formData = { code: '', name: '', remark: '' }
this.editId = ''
},
openAdd () {
this.handleType = 'create'
this.dialogTitle = this.key('add_title')
this.$nextTick(() => {
this.$refs.dialogForm && this.$refs.dialogForm.reset()
this.resetForm()
this.dialogVisible = true
})
},
openEdit (row) {
this.handleType = 'edit'
this.dialogTitle = this.key('edit_title')
this.editId = row.id
this.formData = {
code: row.code,
name: row.name,
remark: row.remark || ''
}
this.dialogVisible = true
},
async onDialogSubmit () {
this.submitting = true
try {
if (this.handleType === 'create') {
await createUnit(this.formData)
} else {
await editUnit({ ...this.formData, id: this.editId })
}
this.$message.success(this.$t(this.key('operation_success')))
this.dialogVisible = false
this.fetchData()
} finally {
this.submitting = false
}
},
onDialogClose () {
this.resetForm()
},
async handleDelete (row) {
const cancelled = await this.$confirmAction(
{
message: this.key('confirm_delete'),
title: this.key('tip')
},
() => deleteUnit({ id: [row.id] })
)
if (cancelled) return
this.$message.success(this.$t(this.key('operation_success')))
this.pagination.current = Math.min(
this.pagination.current,
Math.ceil((this.pagination.total - 1) / this.pagination.size) || 1
)
this.fetchData()
}
}
}
</script>
<style scoped>
.search-bar {
padding: 10px 0;
}
/deep/ .el-form-item--mini.el-form-item {
margin-bottom: 4px;
}
</style>

View File

@@ -0,0 +1,302 @@
<template>
<el-dialog
:title="title"
:visible.sync="visible"
:append-to-body="true"
:close-on-click-modal="false"
:before-close="handleClose"
width="80%"
>
<div class="search-content">
<el-card class="box-card">
<el-form :inline="true" size="mini">
<el-form-item :label="$t(key('name'))">
<el-input
v-model="searchForm.name"
:placeholder="$t(key('enter_name'))"
clearable
/>
</el-form-item>
<el-form-item :label="$t(key('param'))">
<el-input
v-model="searchForm.code"
:placeholder="$t(key('enter_param'))"
clearable
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="onSearch">
{{ $t(key('query')) }}
</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<el-table
ref="table"
:data="tableData"
border
:loading="loading"
max-height="450"
style="width: 100%"
>
<el-table-column :label="$t(key('name'))" prop="name" width="200" show-overflow-tooltip />
<el-table-column :label="$t(key('param'))" prop="code" width="150" show-overflow-tooltip />
<el-table-column :label="$t(key('category'))" prop="field_type" width="120" />
<el-table-column :label="$t(key('remark'))" prop="remark" show-overflow-tooltip />
<el-table-column :label="$t(key('is_unique'))" width="100" align="center">
<template slot-scope="{ row }">
<el-tag v-if="row.is_only === 1" type="success" size="mini">{{ $t(key('yes')) }}</el-tag>
<el-tag v-else type="info" size="mini">{{ $t(key('no')) }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t(key('is_upload'))" width="100" align="center">
<template slot-scope="{ row }">
<el-tag v-if="row.is_upload === 1" type="success" size="mini">{{ $t(key('yes')) }}</el-tag>
<el-tag v-else type="info" size="mini">{{ $t(key('no')) }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t(key('operation'))" width="120" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(row)">
{{ $t(key('edit')) }}
</el-button>
<el-button type="text" size="mini" icon="el-icon-delete" style="color: #F56C6C" @click="handleDelete(row)">
{{ $t(key('delete')) }}
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="pagination.current"
:page-sizes="[10, 20, 50, 100]"
:page-size="pagination.size"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
<div slot="footer" class="dialog-footer">
<el-button type="primary" size="mini" @click="handleAdd">{{ $t(key('add_row')) }}</el-button>
<el-button type="success" size="mini" icon="el-icon-download" @click="handleImport">{{ $t(key('import')) }}</el-button>
<el-button size="mini" @click="handleClose">{{ $t(key('close')) }}</el-button>
</div>
<el-dialog
:title="dialogTitle"
:visible.sync="innerVisible"
append-to-body
width="600px"
:before-close="handleInnerClose"
>
<el-form ref="form" :model="formData" :rules="rules" label-width="100px">
<el-form-item :label="$t(key('param'))" prop="code">
<el-input v-model="formData.code" :placeholder="$t(key('enter_param'))" clearable />
</el-form-item>
<el-form-item :label="$t(key('name'))" prop="name">
<el-input v-model="formData.name" :placeholder="$t(key('enter_name'))" clearable />
</el-form-item>
<el-form-item :label="$t(key('category'))" prop="field_type">
<el-select v-model="formData.field_type" :placeholder="$t(key('select_type'))" style="width: 100%">
<el-option label="FLOAT" value="FLOAT" />
<el-option label="INT" value="INT" />
<el-option label="VARCHAR" value="VARCHAR" />
<el-option label="TEXT" value="TEXT" />
<el-option label="TIMESTAMP" value="TIMESTAMP" />
</el-select>
</el-form-item>
<el-form-item :label="$t(key('remark'))">
<el-input v-model="formData.remark" type="textarea" :rows="2" :placeholder="$t(key('remark_required'))" />
</el-form-item>
<el-form-item :label="$t(key('is_unique'))">
<el-radio-group v-model="formData.is_only">
<el-radio :label="1">{{ $t(key('yes')) }}</el-radio>
<el-radio :label="0">{{ $t(key('no')) }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t(key('is_upload'))">
<el-radio-group v-model="formData.is_upload">
<el-radio :label="1">{{ $t(key('yes')) }}</el-radio>
<el-radio :label="0">{{ $t(key('no')) }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="handleInnerClose">{{ $t(key('cancel')) }}</el-button>
<el-button type="primary" @click="handleSubmit">{{ $t(key('confirm')) }}</el-button>
</div>
</el-dialog>
</el-dialog>
</template>
<script>
import { i18nMixin } from '@/composables/useI18n'
import {
getOptionalParamsList,
createOptionalParams,
editOptionalParams,
deleteOptionalParams
} from '@/api/production-master-data/optional-params'
export default {
name: 'ProcessStepResultParam',
components: {},
mixins: [i18nMixin('page.production_master_data.process_model.process_step')],
props: {
title: {
type: String,
default: ''
},
visible: {
type: Boolean,
default: false
},
workingsubclass_id: {
type: [String, Number],
default: ''
}
},
data () {
return {
loading: false,
tableData: [],
searchForm: { name: '', code: '' },
pagination: { current: 1, size: 10, total: 0 },
innerVisible: false,
dialogTitle: '',
formData: {
id: '',
code: '',
name: '',
field_type: '',
remark: '',
is_only: 0,
is_upload: 0
},
rules: {
code: [{ required: true, message: this.key('enter_param'), trigger: 'blur' }],
name: [{ required: true, message: this.key('enter_name'), trigger: 'blur' }],
field_type: [{ required: true, message: this.key('select_type'), trigger: 'change' }]
}
}
},
watch: {
workingsubclass_id: {
handler (val) {
if (val) {
this.fetchData()
}
},
immediate: true
}
},
methods: {
handleClose () {
this.$emit('close')
},
handleInnerClose () {
this.innerVisible = false
this.$refs.form && this.$refs.form.resetFields()
},
onSearch () {
this.pagination.current = 1
this.fetchData()
},
fetchData () {
if (!this.workingsubclass_id) return
this.loading = true
getOptionalParamsList({
workingsubclass_id: this.workingsubclass_id,
page_no: this.pagination.current,
page_size: this.pagination.size,
...this.searchForm
}).then(res => {
const list = Array.isArray(res) ? res : (res.data || [])
const total = Array.isArray(res) ? res.length : (res.count || 0)
this.tableData = list
this.pagination.total = total
}).finally(() => {
this.loading = false
})
},
handleSizeChange (size) {
this.pagination.size = size
this.pagination.current = 1
this.fetchData()
},
handleCurrentChange (current) {
this.pagination.current = current
this.fetchData()
},
handleAdd () {
this.dialogTitle = this.key('add_row')
this.formData = {
id: '',
code: '',
name: '',
field_type: '',
remark: '',
is_only: 0,
is_upload: 0
}
this.innerVisible = true
},
handleEdit (row) {
this.dialogTitle = this.key('edit')
this.formData = {
id: row.id,
code: row.code,
name: row.name,
field_type: row.field_type,
remark: row.remark || '',
is_only: row.is_only || 0,
is_upload: row.is_upload || 0
}
this.innerVisible = true
},
handleDelete (row) {
this.$confirm(this.key('confirm_delete_data'), this.key('tips'), {
confirmButtonText: this.key('confirm'),
cancelButtonText: this.key('cancel'),
type: 'warning'
}).then(() => {
deleteOptionalParams({ workingsubclass_id: this.workingsubclass_id, id: row.id }).then(() => {
this.$message.success(this.key('operation_success'))
this.fetchData()
})
}).catch(() => {})
},
handleSubmit () {
this.$refs.form.validate(valid => {
if (!valid) return
const action = this.formData.id ? editOptionalParams : createOptionalParams
const data = { workingsubclass_id: this.workingsubclass_id, ...this.formData }
if (this.formData.id) {
delete data.id
}
action(data).then(() => {
this.$message.success(this.key('operation_success'))
this.innerVisible = false
this.$refs.form.resetFields()
this.fetchData()
})
})
},
handleImport () {
this.$message.info(this.key('import_tip'))
}
}
}
</script>
<style scoped>
.search-content {
margin-bottom: 15px;
}
.box-card {
width: 100%;
}
</style>

View File

@@ -0,0 +1,91 @@
<template>
<el-dialog
:title="title"
:visible.sync="visible"
:append-to-body="true"
:close-on-click-modal="false"
:before-close="handleClose"
:width="width"
>
<div class="setting-content">
<el-alert
:title="settingTip"
type="info"
:closable="false"
show-icon
/>
<div class="setting-placeholder">
<p>{{ $t(key('setting_placeholder')) }}</p>
<el-tag type="warning">{{ type }}</el-tag>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose">{{ $t(key('cancel')) }}</el-button>
<el-button type="primary" @click="handleSubmit">{{ $t(key('confirm')) }}</el-button>
</div>
</el-dialog>
</template>
<script>
import { i18nMixin } from '@/composables/useI18n'
export default {
name: 'ProcessStepSettingDialog',
mixins: [i18nMixin('page.production_master_data.process_model.process_step')],
props: {
type: {
type: String,
default: ''
},
code: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
width: {
type: String,
default: '50%'
},
visible: {
type: Boolean,
default: false
},
pluginData: {
default: ''
}
},
computed: {
settingTip () {
return this.$t(this.key('setting_tip'))
}
},
methods: {
handleClose () {
this.$emit('close')
},
handleSubmit () {
this.$message.success(this.key('operation_success'))
this.$emit('close')
}
}
}
</script>
<style scoped>
.setting-content {
padding: 10px 0;
}
.setting-placeholder {
margin-top: 20px;
text-align: center;
padding: 40px 0;
color: #909399;
}
.setting-placeholder p {
margin-bottom: 15px;
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,379 @@
<template>
<d2-container>
<template #header>
<div class="search-bar">
<el-form :inline="true" size="mini">
<el-form-item :label="$t(key('process_unit_code'))">
<el-input
v-model="search.code"
:placeholder="$t(key('enter_process_unit_code'))"
clearable
style="width:200px"
@keyup.enter.native="onSearch"
/>
</el-form-item>
<el-form-item :label="$t(key('process_unit_name'))">
<el-input
v-model="search.name"
:placeholder="$t(key('enter_process_unit_name'))"
clearable
style="width:200px"
@keyup.enter.native="onSearch"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="onSearch">
{{ $t(key('search')) }}
</el-button>
<el-button icon="el-icon-refresh" @click="onReset">
{{ $t(key('reset')) }}
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<page-table
ref="pageTable"
:columns="columns"
:data="tableData"
:loading="loading"
:toolbar-buttons="toolbarButtons"
:row-buttons="rowButtons"
:pagination="pagination"
help-url="/help/process-step"
:help-text="$t(ckey('help'))"
auto-height
@page-change="onPageChange"
@selection-change="onSelect"
/>
<page-dialog-form
ref="dialogForm"
:visible.sync="dialogVisible"
:title="dialogTitle"
:width="'35%'"
:form-cols="formCols"
:form-data="formData"
:rules="rules"
:label-width="'120px'"
:submitting="submitting"
:confirm-text="key('confirm')"
:cancel-text="key('cancel')"
@submit="onDialogSubmit"
@close="onDialogClose"
/>
<technology-flow-model
:visible.sync="settingVisible"
:title="settingTitle"
:type="settingType"
:code="settingCode"
:width="settingWidth"
:plugin-data="settingPluginData"
@close="settingVisible = false"
@submit="handleSettingSubmit"
/>
<result-param
:visible.sync="resultVisible"
:title="resultTitle"
:workingsubclass-id="currentRowId"
@close="resultVisible = false"
/>
</d2-container>
</template>
<script>
import { useTableColumns } from '@/composables/useTableColumns'
import { useTableButtons } from '@/composables/useTableButtons'
import { i18nMixin } from '@/composables/useI18n'
import { confirmMixin } from '@/composables/useConfirmHandle'
import {
getProcessStepList,
createProcessStep,
editProcessStep,
deleteProcessStep
} from '@/api/production-master-data/process-step'
import PageTable from '@/components/page-table'
import PageDialogForm from '@/components/page-dialog-form'
import TechnologyFlowModel from './components/technology-flow-model.vue'
import ResultParam from './components/result-param.vue'
export default {
name: 'production-master-data-process-step',
components: { PageTable, PageDialogForm, TechnologyFlowModel, ResultParam },
mixins: [i18nMixin('page.production_master_data.process_model.process_step'), confirmMixin],
data () {
return {
loading: false,
submitting: false,
tableData: [],
selectedRows: [],
dialogVisible: false,
dialogTitle: '',
editId: '',
handleType: 'create',
search: { code: '', name: '' },
pagination: { current: 1, size: 10, total: 0 },
formData: { code: '', name: '', device_category_id: '', remark: '' },
settingVisible: false,
settingTitle: '',
settingType: '',
settingCode: '',
settingWidth: '50%',
settingPluginData: '',
resultVisible: false,
resultTitle: '',
currentRowId: '',
rules: {
code: [
{ required: true, message: this.key('enter_process_unit_code'), trigger: 'blur' },
{ min: 1, max: 100, message: this.key('length_1_100'), trigger: 'blur' }
],
name: [
{ required: true, message: this.key('enter_process_unit_name'), trigger: 'blur' },
{ min: 1, max: 100, message: this.key('length_1_100'), trigger: 'blur' }
],
device_category_id: [
{ required: true, message: this.key('select_device_category'), trigger: 'change' }
]
},
columns: [],
toolbarButtons: [],
rowButtons: [],
formCols: [
[
{
type: 'input',
prop: 'code',
label: this.key('process_unit_code'),
placeholder: this.key('enter_process_unit_code'),
clearable: true,
disabled: false,
style: { width: '90%' }
}
],
[
{
type: 'input',
prop: 'name',
label: this.key('process_unit_name'),
placeholder: this.key('enter_process_unit_name'),
clearable: true,
style: { width: '90%' }
}
],
[
{
type: 'select',
prop: 'device_category_id',
label: this.key('device_category'),
placeholder: this.key('select_device_category'),
clearable: true,
filterable: true,
style: { width: '90%' },
options: []
}
],
[
{
type: 'input',
prop: 'remark',
inputType: 'textarea',
autosize: { minRows: 2, maxRows: 6 },
label: this.key('remark'),
placeholder: this.key('remark_required'),
clearable: true,
style: { width: '90%' }
}
]
]
}
},
created () {
this.columns = useTableColumns([
{ prop: 'sort', label: this.key('sort'), width: 80 },
{ prop: 'code', label: this.key('process_unit_code'), minWidth: 120 },
{ prop: 'name', label: this.key('process_unit_name'), minWidth: 120 },
{ prop: 'device_category_name', label: this.key('device_category'), minWidth: 120 },
{ prop: 'remark', label: this.key('remark') },
{ prop: '_actions', label: this.key('operation'), width: 160, fixed: 'right' }
])
const btns = useTableButtons({
toolbar: [
{
key: 'add',
label: this.key('add'),
icon: 'el-icon-plus',
type: 'primary',
auth: '/production_configuration/technology_model/technology_flow_workingsubclass/create',
onClick: this.openAdd
}
],
row: [
{
key: 'edit',
label: this.key('edit'),
icon: 'el-icon-edit',
auth: '/production_configuration/technology_model/technology_flow_workingsubclass/edit',
onClick: this.openEdit
},
{
key: 'setting',
label: this.key('preset_setting'),
icon: 'el-icon-setting',
color: 'warning',
auth: '/production_configuration/technology_model/technology_flow_workingsubclass/setting',
onClick: this.openSetting
},
{
key: 'result',
label: this.key('preset_result_param'),
icon: 'el-icon-document',
color: 'success',
auth: '/production_configuration/technology_model/technology_flow_workingsubclass/result',
onClick: this.openResult
},
{
key: 'delete',
label: this.key('delete'),
icon: 'el-icon-delete',
color: 'danger',
auth: '/production_configuration/technology_model/technology_flow_workingsubclass/delete',
onClick: this.handleDelete
}
]
}, this.$permission)
this.toolbarButtons = btns.toolbarButtons
this.rowButtons = btns.rowButtons
this.fetchData()
},
methods: {
async fetchData () {
this.loading = true
try {
const res = await getProcessStepList({
...this.search,
page_no: this.pagination.current,
page_size: this.pagination.size
})
const list = Array.isArray(res) ? res : (res.data || [])
const total = Array.isArray(res) ? res.length : (res.count || 0)
this.tableData = list
this.pagination.total = total
} finally {
this.loading = false
}
},
onSearch () {
this.pagination.current = 1
this.fetchData()
},
onReset () {
this.search = { code: '', name: '' }
this.pagination.current = 1
this.fetchData()
},
onPageChange (page) {
this.pagination.current = page.current
this.pagination.size = page.size
this.fetchData()
},
onSelect (rows) {
this.selectedRows = rows
},
resetForm () {
this.formData = { code: '', name: '', device_category_id: '', remark: '' }
this.editId = ''
this.$nextTick(() => {
this.formCols[0][0].disabled = false
})
},
openAdd () {
this.handleType = 'create'
this.dialogTitle = this.key('add_process_unit')
this.$nextTick(() => {
this.$refs.dialogForm && this.$refs.dialogForm.reset()
this.resetForm()
this.dialogVisible = true
})
},
openEdit (row) {
this.handleType = 'edit'
this.dialogTitle = this.key('edit_process_unit')
this.editId = row.id
this.formData = {
code: row.code,
name: row.name,
device_category_id: row.device_category_id || '',
remark: row.remark || ''
}
this.$nextTick(() => {
this.formCols[0][0].disabled = true
})
this.dialogVisible = true
},
async onDialogSubmit () {
this.submitting = true
try {
if (this.handleType === 'create') {
await createProcessStep(this.formData)
} else {
await editProcessStep({ ...this.formData, id: this.editId })
}
this.$message.success(this.$t(this.key('operation_success')))
this.dialogVisible = false
this.fetchData()
} finally {
this.submitting = false
}
},
onDialogClose () {
this.resetForm()
},
async handleDelete (row) {
const cancelled = await this.$confirmAction(
{
message: this.key('confirm_message'),
title: this.key('tips')
},
() => deleteProcessStep({ id: [row.id] })
)
if (cancelled) return
this.$message.success(this.$t(this.key('operation_success')))
this.pagination.current = Math.min(
this.pagination.current,
Math.ceil((this.pagination.total - 1) / this.pagination.size) || 1
)
this.fetchData()
},
openSetting (row) {
this.currentRowId = row.id
this.settingTitle = this.key('preset_setting')
this.settingType = row.device_category_id || ''
this.settingCode = row.code
this.settingWidth = '60%'
this.settingVisible = true
},
handleSettingSubmit (data) {
this.$message.success(this.$t(this.key('operation_success')))
this.settingVisible = false
},
openResult (row) {
this.currentRowId = row.id
this.resultTitle = this.key('preset_result_param')
this.resultVisible = true
}
}
}
</script>
<style scoped>
.search-bar {
padding: 10px 0;
}
/deep/ .el-form-item--mini.el-form-item {
margin-bottom: 4px;
}
</style>