Compare commits

...

36 Commits

Author SHA1 Message Date
shijing ff2e9a49e0 feat:禅道446折线图:X轴展示产品编号,Y轴展示分检的像位移值 2026-07-24 11:29:49 +08:00
shijing f3a92cb296 fix:光芯生产执行中的检验记录可以撤回并删除 2026-07-24 08:59:53 +08:00
shijing 9c302b215b Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory_web 2026-07-23 16:40:07 +08:00
shijing f8e3523568 fix:禅道456 2026-07-23 16:40:05 +08:00
caoqianming a378a39917 fix:工艺画布拉线表单带入残留信息根因修复:禁用VueFlow connectOnClick(单击手柄会挂起待连接状态,之后再点手柄以旧节点为起点触发connect,导致间歇性带入错误输入/输出);预填改为prop挂载时确定;误吸附回起点按拉空白处理;单纯点击手柄不再弹新增表单;节点增减后重新适配视野 2026-07-23 16:06:02 +08:00
caoqianming 494999eb11 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory_web 2026-07-23 15:52:16 +08:00
caoqianming dbd93cb9b9 release: 3.1.2026072315 2026-07-23 15:51:49 +08:00
caoqianming 205768fbc3 fix:工艺路线表单残留问题根治:xtSelect外部清空值时同步清空显示文字(残留主因);画布/右侧新增弹框加key每次打开强制重建实例;route_form编辑时sort不再被count+1覆盖;无routeId不再全量拉辅料
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:50:32 +08:00
shijing ca5d96bd86 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory_web 2026-07-23 15:04:33 +08:00
shijing 1770b10d07 fix:交接扫码时光标丢失问题 2026-07-23 15:04:31 +08:00
caoqianming ec59a0ac98 release: 3.1.2026072314
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:26:56 +08:00
caoqianming d6d382e281 fix:弹框表单残留上次内容问题一批:route_form open时主动resetForm不再依赖父级v-if销毁(关闭动画期间重开复用实例致残留);riskpoint新增未重置saveForm且RiskForm常驻挂载补destroy-on-close;algo页弹框v-model改v-if与algo2对齐;mroom/plat_record新增未重置表单
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:25:25 +08:00
caoqianming 37b5e3f468 fix:工作流配置四页(字段/工作流/流转/状态)新增弹框未重置表单,先编辑再新增会残留上一条内容;默认值提为defaultForm常量供data与新增共用
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:25:25 +08:00
shijing 23313d5ced fix:禅道458 2026-07-23 13:55:20 +08:00
shijing 070b41fd51 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory_web 2026-07-23 13:24:58 +08:00
shijing d3502b4456 fix:陶瓷日志中设备为可选 2026-07-23 13:24:56 +08:00
caoqianming d3ed636393 release: 3.1.2026072309
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 09:21:19 +08:00
caoqianming dea337b632 feat:工艺画布scFlow更名xtFlow;工艺包产品常驻画布为终点节点可直接连线;节点按物料真实类型着色;fix:拖线新增setData覆盖params_json致排一次棒表单报错;perf:vue-flow分包prefetch预取+查看器合并重复布局;style:route_show查看页卡片化紧凑布局
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 09:18:59 +08:00
caoqianming f7fee89290 feat:物料表单新增入库检验方式,入库明细未检免检物料显示免检标识;fix:其他配置页提交未实现导致保存无效
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 16:27:41 +08:00
caoqianming 3ee5403ad9 fix:抽屉内打开的el-dialog统一加append-to-body(24文件28处),避免被pages.scss抽屉工作流表单样式误伤成卡片/灰底/底部留白
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 15:03:55 +08:00
caoqianming 63dfe7e948 refactor:工艺路线查看统一到Vue Flow只读视图scFlowViewer,与编辑器共用MaterialNode节点/dagre布局;route_show与route页弃用dagre-d3版scDegra
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 15:03:54 +08:00
caoqianming 46ed8b0c54 fix:抽屉内el-main/el-aside恢复nopadding约定优先级; route_form弹窗加append-to-body避免被抽屉工作流表单样式误伤成卡片
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 15:03:54 +08:00
caoqianming e50e09bcaf feat:工艺路线新增可视化流程编辑器scFlowEditor(Vue Flow+dagre异步分包); 编辑工艺抽屉压缩布局留白
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 15:03:54 +08:00
shijing 7fccfc9869 fix:分检加人员筛选不生效修改 2026-07-21 14:59:24 +08:00
caoqianming e671393bff fix:table下的tr补上tbody包裹,消除vue3.5模板编译的HTML嵌套规范警告
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 10:29:26 +08:00
caoqianming 87ef0772a4 chore:升级依赖 vue3.2->3.5, element-plus2.8->2.14, sass等; maopi/zhongjian1Total的script声明lang=jsx以兼容新版编译器
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 10:14:54 +08:00
caoqianming c37712d2b5 fix:scScanner内置扫码枪输入框改为opt-in(默认隐藏),消除与调用页自有输入框重复
新增showInput prop默认false,恢复提交前布局:套壳环境仍显示原生扫码按钮,
PC/平板枪照旧走各页自有输入框。需集中式扫码框时显式:showInput="true"。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 16:54:21 +08:00
shijing fe730915f2 fix:禅道452综合查询界面棒数据未显示发货数 2026-07-20 16:26:25 +08:00
shijing 6be1b10736 fix:禅道452综合查询界面棒数据未显示发货数 2026-07-20 13:19:15 +08:00
shijing bec1e9a8a6 feat:禅道444扭转工序,快速报工,扫码框后面加数字识别功能 2026-07-20 11:27:37 +08:00
shijing 6564742644 fix:禅道452综合查询界面棒数据未显示发货数 2026-07-20 11:26:41 +08:00
shijing 564fa2e1c3 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory_web 2026-07-20 09:50:16 +08:00
shijing 04e3a2e18d feat:禅道446深加工统计里加工装tab页面 2026-07-20 09:50:14 +08:00
caoqianming 3e77fb77fb feat:销售发货批次选择隐藏可发数量为0的批次
批次查询改用后端 count_canmio__gt=0 过滤(存量-在途出入库),
只展示可实际发货的批次

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 12:06:41 +08:00
caoqianming c8f38e54b6 feat:合格B类字段文案改为记为合格更准确表达
工序/日志提交处 clear_defect 开关文案由不拆批/合批清除缺陷统一改为合格B类记为合格,与后端同步。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 10:21:10 +08:00
caoqianming 27060f7c54 fix:出入库记录及明细表单新增时清空,避免上次保存数据残留导致"物料找不到"
mio_form/mioitem_form的open()原只设type、不重置form,saveDialog虽用
v-if="dialog.save"+@closed重建,但关闭动画未结束就再点新增时dialog.save
仍为true、组件被复用,导致上次的material/mb/batch/selectObj等残留。
新增resetForm(),add模式打开时强制清空,不依赖v-if重建时序。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 09:54:36 +08:00
78 changed files with 2222 additions and 346 deletions

View File

@ -1,3 +1,34 @@
## 3.1.2026072315
- fix: 问题修复
- 工艺路线表单残留问题根治:xtSelect外部清空值时同步清空显示文字(残留主因);画布/右侧新增弹框加key每次打开强制重建实例;route_form编辑时sort不再被count+1覆盖;无routeId不再全量拉辅料 [caoqianming]
## 3.1.2026072314
- fix: 问题修复
- 弹框表单残留上次内容问题一批:route_form open时主动resetForm不再依赖父级v-if销毁(关闭动画期间重开复用实例致残留);riskpoint新增未重置saveForm且RiskForm常驻挂载补destroy-on-close;algo页弹框v-model改v-if与algo2对齐;mroom/plat_record新增未重置表单 [caoqianming]
- 工作流配置四页(字段/工作流/流转/状态)新增弹框未重置表单,先编辑再新增会残留上一条内容;默认值提为defaultForm常量供data与新增共用 [caoqianming]
- 禅道458 [shijing]
- 陶瓷日志中设备为可选 [shijing]
## 3.1.2026072309
- feat: 新增功能
- 工艺画布scFlow更名xtFlow;工艺包产品常驻画布为终点节点可直接连线;节点按物料真实类型着色;fix:拖线新增setData覆盖params_json致排一次棒表单报错;perf:vue-flow分包prefetch预取+查看器合并重复布局;style:route_show查看页卡片化紧凑布局 [caoqianming]
- 物料表单新增入库检验方式,入库明细未检免检物料显示免检标识;fix:其他配置页提交未实现导致保存无效 [caoqianming]
- 工艺路线新增可视化流程编辑器scFlowEditor(Vue Flow+dagre异步分包); 编辑工艺抽屉压缩布局留白 [caoqianming]
- 禅道444扭转工序快速报工扫码框后面加数字识别功能 [shijing]
- 禅道446深加工统计里加工装tab页面 [shijing]
- 销售发货批次选择隐藏可发数量为0的批次 [caoqianming]
- 合格B类字段文案改为记为合格更准确表达 [caoqianming]
- fix: 问题修复
- 抽屉内打开的el-dialog统一加append-to-body(24文件28处),避免被pages.scss抽屉工作流表单样式误伤成卡片/灰底/底部留白 [caoqianming]
- 抽屉内el-main/el-aside恢复nopadding约定优先级; route_form弹窗加append-to-body避免被抽屉工作流表单样式误伤成卡片 [caoqianming]
- 分检加人员筛选不生效修改 [shijing]
- table下的tr补上tbody包裹,消除vue3.5模板编译的HTML嵌套规范警告 [caoqianming]
- scScanner内置扫码枪输入框改为opt-in(默认隐藏),消除与调用页自有输入框重复 [caoqianming]
- 禅道452综合查询界面棒数据未显示发货数 [shijing]
- 禅道452综合查询界面棒数据未显示发货数 [shijing]
- 禅道452综合查询界面棒数据未显示发货数 [shijing]
- 出入库记录及明细表单新增时清空,避免上次保存数据残留导致"物料找不到" [caoqianming]
## 3.1.2026071608 ## 3.1.2026071608
- feat: 新增功能 - feat: 新增功能

View File

@ -7,11 +7,14 @@
"build": "vue-cli-service build --report" "build": "vue-cli-service build --report"
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "2.0.10", "@element-plus/icons-vue": "^2.3.2",
"@kjgl77/datav-vue3": "^1.7.1", "@kjgl77/datav-vue3": "^1.7.1",
"@tinymce/tinymce-vue": "5.0.0", "@tinymce/tinymce-vue": "5.0.0",
"@vue-flow/background": "^1.3.2",
"@vue-flow/controls": "^1.1.3",
"@vue-flow/core": "^1.48.2",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "1.3.4", "axios": "^1.18.1",
"babylonjs": "^6.46.0", "babylonjs": "^6.46.0",
"babylonjs-gui": "^6.46.0", "babylonjs-gui": "^6.46.0",
"babylonjs-loaders": "^6.46.0", "babylonjs-loaders": "^6.46.0",
@ -19,26 +22,27 @@
"codemirror": "^5.65.17", "codemirror": "^5.65.17",
"codemirror-editor-vue3": "^2.7.0", "codemirror-editor-vue3": "^2.7.0",
"copy-webpack-plugin": "^12.0.2", "copy-webpack-plugin": "^12.0.2",
"core-js": "3.29.0", "core-js": "^3.49.0",
"cropperjs": "1.5.13", "cropperjs": "1.5.13",
"crypto-browserify": "^3.12.0", "crypto-browserify": "^3.12.0",
"crypto-js": "4.1.1", "crypto-js": "^4.2.0",
"d3": "^7.6.1", "d3": "^7.6.1",
"dagre": "^0.8.5",
"dagre-d3": "^0.6.4", "dagre-d3": "^0.6.4",
"dhtmlx-gantt": "^8.0.6", "dhtmlx-gantt": "^8.0.6",
"echarts": "^5.5.1", "echarts": "^5.5.1",
"element-plus": "^2.8.4", "element-plus": "^2.14.3",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"json-editor-vue3": "^1.0.6", "json-editor-vue3": "^1.0.6",
"jspdf": "^2.5.1", "jspdf": "^2.5.1",
"node-polyfill-webpack-plugin": "^4.0.0", "node-polyfill-webpack-plugin": "^4.0.0",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"qrcodejs2": "0.0.2", "qrcodejs2": "0.0.2",
"sortablejs": "1.15.0", "sortablejs": "^1.15.7",
"tinymce": "6.3.2", "tinymce": "6.3.2",
"vue": "3.2.47", "vue": "^3.5.40",
"vue-i18n": "9.2.2", "vue-i18n": "^11.4.7",
"vue-router": "4.1.6", "vue-router": "^4.6.4",
"vuedraggable": "4.0.3", "vuedraggable": "4.0.3",
"vuex": "4.1.0", "vuex": "4.1.0",
"xgplayer": "2.32.2", "xgplayer": "2.32.2",
@ -48,9 +52,9 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.21.00", "@babel/core": "7.21.00",
"@vue/cli-plugin-babel": "5.0.8", "@vue/cli-plugin-babel": "^5.0.9",
"@vue/cli-service": "5.0.8", "@vue/cli-service": "^5.0.9",
"sass": "1.58.3", "sass": "^1.101.0",
"sass-loader": "10.1.1" "sass-loader": "10.1.1"
}, },
"browserslist": [ "browserslist": [

View File

@ -516,6 +516,12 @@ export default {
return await http.get(`${config.API_URL}/mtm/routepack/${id}/final_materials/`); return await http.get(`${config.API_URL}/mtm/routepack/${id}/final_materials/`);
}, },
}, },
validate: {
name: "校验工艺图",
req: async function (id) {
return await http.get(`${config.API_URL}/mtm/routepack/${id}/validate/`);
},
},
update: { update: {
name: "更新", name: "更新",
req: async function (id, data) { req: async function (id, data) {

View File

@ -0,0 +1,106 @@
<template>
<div class="mnode" :class="'mnode--' + (data.kind || 'mid')">
<Handle type="target" :position="Position.Top" class="mnode__handle" />
<span class="mnode__tag">{{ kindLabel }}</span>
<div class="mnode__name" :title="data.label">{{ data.label }}</div>
<Handle type="source" :position="Position.Bottom" class="mnode__handle" />
</div>
</template>
<script>
import { Handle, Position } from "@vue-flow/core";
// xtFlowEditor xtFlowViewer
export default {
name: "MaterialNode",
components: { Handle },
props: {
// { label, kind: raw|mid|final }
data: { type: Object, default: () => ({}) },
},
data() {
return { Position };
},
computed: {
kindLabel() {
return { raw: "原料", final: "成品", mid: "半成品" }[this.data.kind] || "半成品";
},
},
};
</script>
<style scoped>
.mnode {
position: relative;
width: 150px;
padding: 6px 10px 8px;
border-radius: 8px;
border: 1px solid #dcdfe6;
background: #fff;
box-shadow: 0 2px 8px rgba(31, 45, 61, 0.08);
text-align: center;
transition: box-shadow 0.15s, transform 0.15s;
}
.mnode:hover {
box-shadow: 0 4px 14px rgba(31, 45, 61, 0.16);
transform: translateY(-1px);
}
.mnode__tag {
display: inline-block;
font-size: 11px;
line-height: 1;
padding: 2px 6px;
border-radius: 8px;
margin-bottom: 5px;
color: #fff;
}
.mnode__name {
font-size: 12px;
line-height: 1.3;
color: #303133;
word-break: break-all;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.mnode--raw {
border-color: #f0c78a;
background: #fffaf2;
}
.mnode--raw .mnode__tag {
background: #e6a23c;
}
.mnode--mid {
border-color: #a3d0ff;
background: #f5faff;
}
.mnode--mid .mnode__tag {
background: #409eff;
}
.mnode--final {
border-color: #a4da89;
background: #f6fcf1;
}
.mnode--final .mnode__tag {
background: #67c23a;
}
</style>
<style>
/* Vue Flow 内部元素(非 scoped */
.vue-flow__node-material {
background: transparent;
border: none;
padding: 0;
}
.vue-flow__handle {
width: 9px;
height: 9px;
background: #fff;
border: 2px solid #409eff;
}
.vue-flow__handle:hover {
background: #409eff;
}
</style>

View File

@ -0,0 +1,24 @@
import dagre from "dagre";
export const NODE_W = 150;
export const NODE_H = 54;
/**
* dagre 自动布局返回带 position nodes
* @param {Array} nodes Vue Flow 节点id 必须为字符串
* @param {Array} edges Vue Flow
* @param {Object} opts { rankdir: TB|BT|LR|RL, nodeW, nodeH }
*/
export function layoutGraph(nodes, edges, opts = {}) {
const { rankdir = "TB", nodeW = NODE_W, nodeH = NODE_H } = opts;
const g = new dagre.graphlib.Graph();
g.setGraph({ rankdir, nodesep: 30, ranksep: 56, marginx: 20, marginy: 20 });
g.setDefaultEdgeLabel(() => ({}));
nodes.forEach((n) => g.setNode(n.id, { width: nodeW, height: nodeH }));
edges.forEach((e) => g.setEdge(e.source, e.target));
dagre.layout(g);
return nodes.map((n) => {
const gn = g.node(n.id);
return { ...n, position: { x: gn.x - nodeW / 2, y: gn.y - nodeH / 2 } };
});
}

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog <el-dialog append-to-body
title="绑定定位卡" title="绑定定位卡"
destroy-on-close destroy-on-close
v-model="pageShow" v-model="pageShow"

View File

@ -155,6 +155,7 @@
</el-card> </el-card>
<p class="tables-title">作业人员</p> <p class="tables-title">作业人员</p>
<table class="tables" cellspacing="0"> <table class="tables" cellspacing="0">
<tbody>
<tr class="tableTh tableThS"> <tr class="tableTh tableThS">
<td class="tableTd">序号</td> <td class="tableTd">序号</td>
<td class="tableTd" style="width:100px">姓名</td> <td class="tableTd" style="width:100px">姓名</td>
@ -172,9 +173,11 @@
</td> </td>
<td class="tableTd" style="width:180px">{{ item.create_time }}</td> <td class="tableTd" style="width:180px">{{ item.create_time }}</td>
</tr> </tr>
</tbody>
</table> </table>
<p class="tables-title">气体检测记录</p> <p class="tables-title">气体检测记录</p>
<table class="tables" cellspacing="0"> <table class="tables" cellspacing="0">
<tbody>
<tr class="tableTh tableThS"> <tr class="tableTh tableThS">
<td class="tableTd">序号</td> <td class="tableTd">序号</td>
<td class="tableTd">检测部位</td> <td class="tableTd">检测部位</td>
@ -198,9 +201,11 @@
<td class="tableTd">{{ item.check_time }}</td> <td class="tableTd">{{ item.check_time }}</td>
<td class="tableTd">{{ item.create_time }}</td> <td class="tableTd">{{ item.create_time }}</td>
</tr> </tr>
</tbody>
</table> </table>
<p class="tables-title">处理日志</p> <p class="tables-title">处理日志</p>
<table class="tables" cellspacing="0"> <table class="tables" cellspacing="0">
<tbody>
<tr class="tableTh"> <tr class="tableTh">
<td class="tableTd">进行状态</td> <td class="tableTd">进行状态</td>
<td class="tableTd attributeTd">操作类型</td> <td class="tableTd attributeTd">操作类型</td>
@ -228,6 +233,7 @@
</td> </td>
<td class="tableTd" v-if="item.participant_str==null">{{ item.update_time }}</td> <td class="tableTd" v-if="item.participant_str==null">{{ item.update_time }}</td>
</tr> </tr>
</tbody>
</table> </table>
</div> </div>

View File

@ -2,7 +2,9 @@
<template> <template>
<span class="sc-scanner" style="display:inline-flex;align-items:center;gap:4px;vertical-align:middle;"> <span class="sc-scanner" style="display:inline-flex;align-items:center;gap:4px;vertical-align:middle;">
<!-- 扫码枪(蓝牙/USB HID):聚焦后扫码即输入,回车触发平板蓝牙枪PC USB枪都走这里 --> <!-- 扫码枪(蓝牙/USB HID):聚焦后扫码即输入,回车触发平板蓝牙枪PC USB枪都走这里 -->
<!-- 默认隐藏:多数调用页已自带扫码输入框,避免重复;需集中式扫码框时传 :showInput="true" -->
<el-input <el-input
v-if="showInput"
ref="gunInput" ref="gunInput"
v-model="gunValue" v-model="gunValue"
:placeholder="placeholder" :placeholder="placeholder"
@ -21,6 +23,9 @@ export default {
labeltext: { type: String, default: "扫码" }, labeltext: { type: String, default: "扫码" },
placeholder: { type: String, default: "扫码枪扫描" }, placeholder: { type: String, default: "扫码枪扫描" },
gunWidth: { type: String, default: "180px" }, gunWidth: { type: String, default: "180px" },
// false:,
// ; scScanner true
showInput: { type: Boolean, default: false },
}, },
data() { data() {
return { return {

View File

@ -9,11 +9,13 @@
@mouseout="mouseOut" @mouseout="mouseOut"
> >
<table class="totall"> <table class="totall">
<tbody>
<tr class="title"> <tr class="title">
<th v-for="itemx in titleData" :key="itemx"> <th v-for="itemx in titleData" :key="itemx">
{{ itemx }} {{ itemx }}
</th> </th>
</tr> </tr>
</tbody>
</table> </table>
<div <div
:id="refValue" :id="refValue"
@ -22,6 +24,7 @@
> >
<div :id="'scrollBody' + refValue"> <div :id="'scrollBody' + refValue">
<table :class="{ marquee_top: animate }"> <table :class="{ marquee_top: animate }">
<tbody>
<tr <tr
v-for="(itemy, index) in rowData" v-for="(itemy, index) in rowData"
class="rollData" class="rollData"
@ -47,11 +50,13 @@
<span v-else>{{ itemz.value }}</span> <span v-else>{{ itemz.value }}</span>
</td> </td>
</tr> </tr>
</tbody>
</table> </table>
<table <table
:class="{ marquee_top: animate }" :class="{ marquee_top: animate }"
v-show="scrollVivible" v-show="scrollVivible"
> >
<tbody>
<tr <tr
v-for="(itemy, index) in rowData" v-for="(itemy, index) in rowData"
class="rollData" class="rollData"
@ -81,6 +86,7 @@
<span v-else>{{ itemz.value }}</span> <span v-else>{{ itemz.value }}</span>
</td> </td>
</tr> </tr>
</tbody>
</table> </table>
</div> </div>
</div> </div>

View File

@ -0,0 +1,717 @@
<template>
<div class="flow-editor" v-loading="loading">
<!-- 顶部工具栏 -->
<div class="flow-toolbar">
<div class="flow-toolbar__left">
<el-button v-if="!readonly" type="primary" icon="el-icon-plus" size="small" round @click="addStep">新增工序</el-button>
<el-button icon="el-icon-magic-stick" size="small" round @click="relayout">自动布局</el-button>
<el-button icon="el-icon-full-screen" size="small" round @click="doFit">适应画布</el-button>
<el-button icon="el-icon-circle-check" size="small" round @click="doValidate" :loading="validating">校验</el-button>
</div>
<div class="flow-legend">
<span class="lg lg--raw">原料</span>
<span class="lg lg--mid">半成品</span>
<span class="lg lg--final">成品</span>
</div>
</div>
<div class="flow-tip" v-if="!readonly">
从左侧拖入原料作为起点 节点间拉线新建工序拉到空白处自动生成半成品连到产品节点收尾单击线条编辑右键线条删工序 / 右键节点移除物料
</div>
<div class="flow-tip flow-tip--ro" v-else>当前状态不可编辑仅创建中的工艺可编辑</div>
<div class="flow-body">
<!-- 左侧物料面板拖入画布作为节点 -->
<div class="flow-palette" v-if="!readonly">
<div class="palette-head">
<div class="palette-title">物料库</div>
<el-select v-model="matType" size="small" @change="loadMaterials" style="width: 100%">
<el-option label="主要原料" value="30"></el-option>
<el-option label="成品" value="10"></el-option>
<el-option label="半成品" value="20"></el-option>
</el-select>
<el-input
v-model="matKeyword"
size="small"
clearable
prefix-icon="el-icon-search"
placeholder="搜索物料"
style="margin-top: 6px"
></el-input>
</div>
<div class="palette-list" v-loading="matLoading">
<div
v-for="m in filteredMaterials"
:key="m.id"
class="palette-item"
:draggable="true"
@dragstart="onDragStart($event, m)"
:title="m.full_name"
>
<i class="el-icon-rank palette-item__grip"></i>
<span class="palette-item__txt">{{ m.full_name }}</span>
</div>
<div v-if="!matLoading && filteredMaterials.length === 0" class="palette-empty">无匹配物料</div>
</div>
</div>
<!-- 画布 -->
<div class="flow-canvas" @drop="onDrop" @dragover.prevent>
<VueFlow
:id="flowId"
:default-viewport="{ zoom: 0.9 }"
:min-zoom="0.2"
:max-zoom="2"
:connection-radius="8"
:connect-on-click="false"
:nodes-draggable="true"
:nodes-connectable="!readonly"
:elements-selectable="true"
@nodes-initialized="onNodesInit"
@connect-start="onConnectStart"
@connect="onConnect"
@connect-end="onConnectEnd"
@edge-click="onEdgeClick"
@edge-context-menu="onEdgeContextMenu"
@node-context-menu="onNodeContextMenu"
>
<!-- 自定义物料节点 xtFlowViewer 共用 -->
<template #node-material="{ data }">
<material-node :data="data" />
</template>
<Background :gap="18" pattern-color="#e6e9ef" />
<Controls :show-interactive="false" />
</VueFlow>
<div v-if="!loading && nodes.length === 0" class="flow-empty">
<i class="el-icon-position flow-empty__icon"></i>
<div>从左侧把原料拖到这里开始搭建工艺路线</div>
</div>
</div>
</div>
<!-- 复用现有单步表单新增/编辑一条 Routekey 随每次打开递增强制重建实例预填走 prop 在挂载时即确定 -->
<route-form
v-if="dialogVisible"
:key="dialogSeq"
ref="routeForm"
:count="count"
:routepack="routepack"
:prefill="formPrefill"
@success="onFormSuccess"
@closed="dialogVisible = false"
></route-form>
</div>
</template>
<script>
import { VueFlow, useVueFlow } from "@vue-flow/core";
import { Background } from "@vue-flow/background";
import { Controls } from "@vue-flow/controls";
import routeForm from "@/views/mtm/route_form.vue";
import MaterialNode from "./flow/MaterialNode.vue";
import { layoutGraph } from "./flow/dagreLayout";
import "@vue-flow/core/dist/style.css";
import "@vue-flow/core/dist/theme-default.css";
import "@vue-flow/controls/dist/style.css";
// flowId Vue Flow store id
let flowSeq = 0;
export default {
name: "xtFlowEditor",
components: { VueFlow, Background, Controls, routeForm, MaterialNode },
props: {
// id
routepack: { type: String, default: "" },
// 线
productId: { type: [String, Number], default: "" },
productName: { type: String, default: "" },
//
readonly: { type: Boolean, default: false },
},
emits: ["changed"],
setup() {
const flowId = "route-flow-editor-" + ++flowSeq;
const { fitView, screenToFlowCoordinate, project, setNodes, setEdges, addNodes, removeNodes } =
useVueFlow(flowId);
return { flowId, fitView, screenToFlowCoordinate, project, setNodes, setEdges, addNodes, removeNodes };
},
data() {
return {
loading: false,
validating: false,
routes: [],
nodes: [],
edges: [],
count: 0,
dialogVisible: false,
dialogSeq: 0,
formPrefill: null,
//
matType: "30",
matKeyword: "",
materials: [],
matLoading: false,
// Route
seedNodes: [],
// 线
connectSource: null,
connectMade: false,
connectStartPos: null,
//
hasFitted: false,
};
},
computed: {
filteredMaterials() {
const kw = (this.matKeyword || "").trim().toLowerCase();
if (!kw) return this.materials;
return this.materials.filter((m) => (m.full_name || "").toLowerCase().includes(kw));
},
},
watch: {
routepack() {
this.reload();
},
productId() {
if (this.routepack) this.reload();
},
},
mounted() {
this.loadMaterials();
if (this.routepack) this.reload();
},
methods: {
/** 加载左侧物料面板列表 */
loadMaterials() {
this.matLoading = true;
this.$API.mtm.material.list
.req({ page: 0, type__in: this.matType, is_hidden: false, query: "{full_name,id}" })
.then((res) => {
this.materials = Array.isArray(res) ? res : res.results || [];
})
.finally(() => {
this.matLoading = false;
});
},
/** 重新拉取路线并重建图 */
reload() {
if (!this.routepack) return;
this.loading = true;
this.$API.mtm.route.list
.req({ page: 0, routepack: this.routepack })
.then((res) => {
this.routes = Array.isArray(res) ? res : res.results || [];
this.count = this.routes.length;
this.buildGraph();
})
.finally(() => {
this.loading = false;
});
},
/** 物料真实类型 → 节点配色10成品 30原料 其余半成品 */
kindOfType(t) {
const s = String(t);
if (s === "10") return "final";
if (s === "30") return "raw";
return "mid";
},
/** 拓扑推断兜底(后端未返回物料类型时用):起点原料 / 终点成品 / 中间半成品 */
kindOf(id, targetIds) {
const isSrc = this.routes.some((r) => String(r.material_in) === id);
const isTgt = targetIds.has(id);
if (isTgt && !isSrc) return "final";
if (!isTgt && isSrc) return "raw";
return "mid";
},
/** 由 routes 构建 物料节点 + 工序边,并用 dagre 自动布局;合并未落库的种子节点 */
buildGraph() {
const matMap = {};
const matKind = {};
const targetIds = new Set();
const edges = [];
this.routes.forEach((r) => {
if (r.material_in == null || r.material_out == null) return;
const sid = String(r.material_in);
const tid = String(r.material_out);
if (!matMap[sid]) matMap[sid] = r.material_in_name || sid;
if (!matMap[tid]) matMap[tid] = r.material_out_name || tid;
if (r.material_in_type != null) matKind[sid] = this.kindOfType(r.material_in_type);
if (r.material_out_type != null) matKind[tid] = this.kindOfType(r.material_out_type);
targetIds.add(tid);
const locked = !!r.from_route;
edges.push({
id: String(r.id),
source: sid,
target: tid,
label: (r.process_name || "") + (locked ? " 🔒" : ""),
data: { route: r, locked },
type: "smoothstep",
markerEnd: "arrowclosed",
style: {
stroke: locked ? "#c0c4cc" : "#409eff",
strokeWidth: locked ? 1.5 : 2,
strokeDasharray: locked ? "5 4" : undefined,
},
labelStyle: { fill: "#5b6370", fontSize: "12px", fontWeight: 500 },
labelBgStyle: { fill: "#ffffff", stroke: "#e4e7ed", strokeWidth: 1 },
labelBgPadding: [7, 4],
labelBgBorderRadius: 6,
});
});
const routeNodes = Object.keys(matMap).map((id) => ({
id,
type: "material",
data: { label: matMap[id], kind: matKind[id] || this.kindOf(id, targetIds) },
position: { x: 0, y: 0 },
}));
//
const pid = this.productId ? String(this.productId) : "";
if (pid && !matMap[pid]) {
routeNodes.push({
id: pid,
type: "material",
data: { label: this.productName || "产品", kind: "final" },
position: { x: 0, y: 0 },
});
}
//
const routeNodeIds = new Set(Object.keys(matMap));
if (pid) routeNodeIds.add(pid);
this.seedNodes = this.seedNodes.filter((s) => !routeNodeIds.has(s.id));
const prevCount = this.nodes.length;
this.edges = edges;
this.nodes = layoutGraph(routeNodes.concat(this.seedNodes), edges);
this.syncStore();
// dagre 线
if (this.hasFitted && this.nodes.length !== prevCount) this.doFit();
},
/** 把本地 nodes/edges 命令式同步到 Vue Flow store先节点后边避免边找不到端点 */
syncStore() {
this.setEdges([]);
this.setNodes(this.nodes);
this.setEdges(this.edges);
},
/** 重新布局并适应画布 */
relayout() {
this.nodes = layoutGraph([...this.nodes], this.edges);
this.setNodes(this.nodes);
this.doFit();
},
doFit() {
this.$nextTick(() => {
try {
this.fitView({ padding: 0.25, maxZoom: 1, duration: 300 });
} catch (e) {
/* noop */
}
});
},
/** VueFlow 节点尺寸测量完成:仅首次自动适配一次 */
onNodesInit() {
if (this.hasFitted) return;
if (this.nodes.length === 0) return;
this.hasFitted = true;
this.doFit();
},
/* ============ 左侧面板拖拽入画布 ============ */
onDragStart(evt, mat) {
evt.dataTransfer.setData(
"application/xtflow",
JSON.stringify({ id: mat.id, name: mat.full_name, type: this.matType })
);
evt.dataTransfer.effectAllowed = "move";
},
onDrop(evt) {
if (this.readonly) return;
const raw = evt.dataTransfer.getData("application/xtflow");
if (!raw) return;
let mat;
try {
mat = JSON.parse(raw);
} catch (e) {
return;
}
const pos = this.toFlowPos(evt.clientX, evt.clientY);
this.addSeed(mat, pos);
},
toFlowPos(clientX, clientY) {
if (typeof this.screenToFlowCoordinate === "function") {
return this.screenToFlowCoordinate({ x: clientX, y: clientY });
}
if (typeof this.project === "function") {
return this.project({ x: clientX, y: clientY });
}
return { x: clientX, y: clientY };
},
addSeed(mat, position) {
const id = String(mat.id);
if (this.nodes.some((n) => n.id === id)) {
const inUse = this.routes.some(
(r) => String(r.material_in) === id || String(r.material_out) === id
);
this.$message.info(inUse ? "该物料已在工序中使用" : "该物料已在画布中(右键节点可移除)");
return;
}
const node = {
id,
type: "material",
data: { label: mat.name, kind: this.kindOfType(mat.type) },
position,
};
this.seedNodes.push(node);
this.nodes = [...this.nodes, node];
// store Handle dimensions
this.addNodes([node]);
},
/* ============ 连线手势 ============ */
onConnectStart(params) {
this.connectSource = (params && (params.nodeId || params.source)) || null;
this.connectMade = false;
const oe = params && params.event;
this.connectStartPos = oe && oe.clientX != null ? { x: oe.clientX, y: oe.clientY } : null;
},
/** 连到已有节点:新建一条 Route预填输入/输出物料 */
onConnect(conn) {
this.connectMade = true;
if (this.readonly) return;
//
if (!conn.target || conn.target === conn.source) {
this.openAddFrom(conn.source);
return;
}
const src = this.nodes.find((n) => n.id === conn.source);
const tgt = this.nodes.find((n) => n.id === conn.target);
// id Number()
this.openForm("add", {
material_in: conn.source,
material_in_name: src ? src.data.label : "",
material_out: conn.target,
material_out_name: tgt ? tgt.data.label : "",
});
},
/** 拉线到空白处:新建工序,输出物料留空由后端自动生成下一节点 */
onConnectEnd(evt) {
const src = this.connectSource;
const made = this.connectMade;
const startPos = this.connectStartPos;
this.connectSource = null;
this.connectMade = false;
this.connectStartPos = null;
if (this.readonly || made || !src) return;
// ()线
const oe = evt && evt.clientX != null ? evt : evt && evt.event;
if (startPos && oe && oe.clientX != null) {
const dist = Math.hypot(oe.clientX - startPos.x, oe.clientY - startPos.y);
if (dist < 8) return;
}
this.openAddFrom(src);
},
/** 以某节点为输入新建工序:只带输入物料,其余字段一律默认值 */
openAddFrom(srcId) {
const node = this.nodes.find((n) => n.id === srcId);
// id
this.openForm("add", {
material_in: srcId,
material_in_name: node ? node.data.label : "",
});
},
/** 单击边 => 编辑对应 Route复用 route_form */
onEdgeClick(evt) {
const edge = evt.edge || evt;
const route = edge.data && edge.data.route;
if (!route) return;
if (this.readonly) {
this.$message.info("当前状态不可编辑");
return;
}
if (edge.data.locked) {
this.$message.warning("该工序引用了其他步骤,无法编辑");
return;
}
this.openForm("edit", route);
},
/** 右键边 => 删除对应 Route */
onEdgeContextMenu(evt) {
const oe = evt.event || evt;
if (oe && oe.preventDefault) oe.preventDefault();
if (this.readonly) return;
const edge = evt.edge || evt;
const route = edge.data && edge.data.route;
if (!route) return;
if (edge.data.locked) {
this.$message.warning("该工序被其他步骤引用,无法删除");
return;
}
this.$confirm(`确定删除工序「${route.process_name || ""}」吗?`, "提示", {
type: "warning",
})
.then(() => {
this.$API.mtm.route.delete.req(route.id).then(() => {
this.$message.success("删除成功");
this.reload();
this.$emit("changed");
});
})
.catch(() => {});
},
/** 右键节点 => 移除未落库的种子节点(已用于工序的物料需删对应工序) */
onNodeContextMenu(evt) {
const oe = evt.event || evt;
if (oe && oe.preventDefault) oe.preventDefault();
if (this.readonly) return;
const node = evt.node || evt;
const nid = node.id;
if (this.productId && nid === String(this.productId)) {
this.$message.warning("产品节点为工艺终点,不可移除");
return;
}
const inUse = this.routes.some(
(r) => String(r.material_in) === nid || String(r.material_out) === nid
);
if (inUse) {
this.$message.warning("该物料已用于工序,请删除对应工序");
return;
}
const label = (node.data && node.data.label) || "";
this.$confirm(`从画布移除「${label}」?`, "提示", { type: "warning" })
.then(() => {
this.seedNodes = this.seedNodes.filter((s) => s.id !== nid);
this.nodes = this.nodes.filter((n) => n.id !== nid);
this.removeNodes([nid]);
})
.catch(() => {});
},
addStep() {
this.openForm("add", null);
},
/** 打开单步表单prefill 为 null 表示纯新增。预填经 prop 传入,在表单挂载 open 时一次性应用 */
openForm(mode, prefill) {
this.dialogSeq += 1;
this.formPrefill = prefill || null;
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs.routeForm.open(mode);
});
},
onFormSuccess() {
this.dialogVisible = false;
this.reload();
this.$emit("changed");
},
/** 调后端 validate 接口,实时反馈 DAG 是否合法 */
doValidate() {
if (!this.routepack) return;
this.validating = true;
this.$API.mtm.routepack.validate
.req(this.routepack)
.then((res) => {
if (res && res.valid) {
this.$message.success("工艺图校验通过");
} else {
this.$message.error("校验未通过:" + (res ? res.error : "未知错误"));
}
})
.finally(() => {
this.validating = false;
});
},
},
};
</script>
<style scoped>
.flow-editor {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
min-height: 480px;
}
.flow-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 4px 8px;
background: #f7f9fc;
border: 1px solid #eef1f6;
border-radius: 8px;
flex-wrap: wrap;
}
.flow-toolbar__left {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.flow-legend {
display: flex;
align-items: center;
gap: 12px;
}
.lg {
position: relative;
padding-left: 16px;
font-size: 12px;
color: #606266;
}
.lg::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 10px;
height: 10px;
border-radius: 3px;
border: 1px solid;
}
.lg--raw::before {
background: #fdf6ec;
border-color: #e6a23c;
}
.lg--mid::before {
background: #ecf5ff;
border-color: #409eff;
}
.lg--final::before {
background: #f0f9eb;
border-color: #67c23a;
}
.flow-tip {
color: #909399;
font-size: 12px;
padding: 3px 2px 4px;
}
.flow-tip--ro {
color: #e6a23c;
}
.flow-body {
display: flex;
flex: 1;
min-height: 420px;
gap: 6px;
}
.flow-palette {
width: 190px;
flex: 0 0 190px;
display: flex;
flex-direction: column;
border: 1px solid #eef1f6;
border-radius: 8px;
overflow: hidden;
background: #fff;
}
.palette-head {
padding: 8px;
border-bottom: 1px solid #f0f2f5;
background: #fafbfd;
}
.palette-title {
font-size: 13px;
font-weight: 600;
color: #303133;
margin-bottom: 8px;
}
.palette-list {
flex: 1;
overflow-y: auto;
padding: 8px;
}
.palette-item {
display: flex;
align-items: center;
gap: 6px;
padding: 7px 9px;
margin-bottom: 6px;
border: 1px solid #ebeef5;
border-radius: 6px;
background: #fff;
cursor: grab;
font-size: 12px;
color: #303133;
transition: all 0.15s;
}
.palette-item:hover {
border-color: #409eff;
background: #ecf5ff;
box-shadow: 0 2px 6px rgba(64, 158, 255, 0.15);
transform: translateY(-1px);
}
.palette-item:active {
cursor: grabbing;
}
.palette-item__grip {
color: #c0c4cc;
flex-shrink: 0;
}
.palette-item__txt {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.palette-empty {
color: #909399;
font-size: 12px;
text-align: center;
padding: 16px 0;
}
.flow-canvas {
position: relative;
flex: 1;
min-height: 420px;
border: 1px solid #eef1f6;
border-radius: 8px;
overflow: hidden;
background: #fbfcfe;
}
.flow-empty {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #a8abb2;
font-size: 13px;
text-align: center;
pointer-events: none;
}
.flow-empty__icon {
font-size: 34px;
color: #c8ccd4;
display: block;
margin-bottom: 10px;
}
</style>
<style>
/* Vue Flow 内部元素(非 scoped物料节点样式在 flow/MaterialNode.vue */
.vue-flow__controls {
box-shadow: 0 2px 10px rgba(31, 45, 61, 0.12);
border-radius: 6px;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,261 @@
<template>
<div class="xt-flow-viewer">
<!-- 顶部工具栏 xtFlowEditor 视觉一致 -->
<div class="flow-toolbar">
<div class="flow-toolbar__left">
<el-button icon="el-icon-full-screen" size="small" round @click="doFit">适应画布</el-button>
</div>
<div class="flow-legend">
<span class="lg lg--raw">原料</span>
<span class="lg lg--mid">半成品</span>
<span class="lg lg--final">成品</span>
</div>
</div>
<div class="flow-canvas">
<VueFlow
:id="flowId"
:min-zoom="0.2"
:max-zoom="2"
:nodes-draggable="false"
:nodes-connectable="false"
:elements-selectable="false"
@nodes-initialized="onNodesInit"
>
<template #node-material="{ data }">
<material-node :data="data" />
</template>
<Background :gap="18" pattern-color="#e6e9ef" />
<Controls :show-interactive="false" />
</VueFlow>
<div v-if="isEmpty" class="flow-empty">
<i class="el-icon-warning-outline flow-empty__icon"></i>
<div>暂无工艺图数据</div>
</div>
</div>
</div>
</template>
<script>
import { VueFlow, useVueFlow } from "@vue-flow/core";
import { Background } from "@vue-flow/background";
import { Controls } from "@vue-flow/controls";
import MaterialNode from "./flow/MaterialNode.vue";
import { layoutGraph } from "./flow/dagreLayout";
import "@vue-flow/core/dist/style.css";
import "@vue-flow/core/dist/theme-default.css";
import "@vue-flow/controls/dist/style.css";
// flowId Vue Flow store id
let viewerSeq = 0;
/**
* 工艺路线只读流程图Vue Flow xtFlowEditor 共用节点样式与布局
* props scDegra 对齐nodes [{id,label}]edges [{id,source,target,label}]
*/
export default {
name: "xtFlowViewer",
components: { VueFlow, Background, Controls, MaterialNode },
props: {
nodes: { type: Array, default: () => [] },
edges: { type: Array, default: () => [] },
rankdir: { type: String, default: "TB" },
},
setup() {
const flowId = "xt-flow-viewer-" + ++viewerSeq;
const { fitView, setNodes, setEdges } = useVueFlow(flowId);
return { flowId, fitView, setNodes, setEdges };
},
data() {
return {
// fit
hasFitted: false,
rebuildPending: false,
};
},
computed: {
isEmpty() {
return !(this.nodes || []).length;
},
},
watch: {
nodes() {
this.scheduleRebuild();
},
edges() {
this.scheduleRebuild();
},
},
mounted() {
this.rebuild();
},
methods: {
/** nodes/edges 通常被先后赋值,用微任务合并成一次 rebuild避免双次 dagre 布局 */
scheduleRebuild() {
if (this.rebuildPending) return;
this.rebuildPending = true;
Promise.resolve().then(() => {
this.rebuildPending = false;
this.rebuild();
});
},
/** nodes/edges -> Vue Flow 图;节点类型按出入度推断(无入边=原料,无出边=成品) */
rebuild() {
const sourceIds = new Set();
const targetIds = new Set();
const rawEdges = (this.edges || [])
.filter((e) => e.source != null && e.target != null)
.map((e) => {
const sid = String(e.source);
const tid = String(e.target);
sourceIds.add(sid);
targetIds.add(tid);
return {
id: e.id != null ? String(e.id) : sid + "-" + tid,
source: sid,
target: tid,
label: e.label || "",
type: "smoothstep",
markerEnd: "arrowclosed",
style: { stroke: "#409eff", strokeWidth: 2 },
labelStyle: { fill: "#5b6370", fontSize: "12px", fontWeight: 500 },
labelBgStyle: { fill: "#ffffff", stroke: "#e4e7ed", strokeWidth: 1 },
labelBgPadding: [7, 4],
labelBgBorderRadius: 6,
};
});
const vNodes = (this.nodes || [])
.filter((n) => n.id != null && n.label)
.map((n) => {
const id = String(n.id);
const isSrc = sourceIds.has(id);
const isTgt = targetIds.has(id);
let kind = "mid";
if (isTgt && !isSrc) kind = "final";
else if (isSrc && !isTgt) kind = "raw";
return { id, type: "material", data: { label: n.label, kind }, position: { x: 0, y: 0 } };
});
const nodeIds = new Set(vNodes.map((n) => n.id));
const vEdges = rawEdges.filter((e) => nodeIds.has(e.source) && nodeIds.has(e.target));
this.setEdges([]);
this.setNodes(layoutGraph(vNodes, vEdges, { rankdir: this.rankdir }));
this.setEdges(vEdges);
// nodes-initialized fit fit
if (this.hasFitted) this.doFit();
},
onNodesInit() {
this.doFit();
},
doFit() {
this.$nextTick(() => {
try {
this.fitView({ padding: 0.2, maxZoom: 1, duration: this.hasFitted ? 200 : 0 });
this.hasFitted = true;
} catch (e) {
/* noop */
}
});
},
},
};
</script>
<style scoped>
.xt-flow-viewer {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
min-height: 320px;
gap: 6px;
}
.flow-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 4px 8px;
background: #f7f9fc;
border: 1px solid #eef1f6;
border-radius: 8px;
flex-wrap: wrap;
}
.flow-toolbar__left {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.flow-legend {
display: flex;
align-items: center;
gap: 12px;
}
.lg {
position: relative;
padding-left: 16px;
font-size: 12px;
color: #606266;
}
.lg::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 10px;
height: 10px;
border-radius: 3px;
border: 1px solid;
}
.lg--raw::before {
background: #fdf6ec;
border-color: #e6a23c;
}
.lg--mid::before {
background: #ecf5ff;
border-color: #409eff;
}
.lg--final::before {
background: #f0f9eb;
border-color: #67c23a;
}
.flow-canvas {
position: relative;
flex: 1;
min-height: 280px;
border: 1px solid #eef1f6;
border-radius: 8px;
overflow: hidden;
background: #fbfcfe;
}
.flow-empty {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #a8abb2;
font-size: 13px;
text-align: center;
pointer-events: none;
}
.flow-empty__icon {
font-size: 34px;
color: #c8ccd4;
display: block;
margin-bottom: 10px;
}
/* 只读视图:隐藏连接点 */
.xt-flow-viewer :deep(.vue-flow__handle) {
opacity: 0;
pointer-events: none;
}
</style>
<style>
.xt-flow-viewer .vue-flow__controls {
box-shadow: 0 2px 10px rgba(31, 45, 61, 0.12);
border-radius: 6px;
overflow: hidden;
}
</style>

View File

@ -123,7 +123,17 @@ onMounted(() => {
const init = () => { const init = () => {
if (props.label) { if (props.label) {
selectLabel.value = props.label; selectLabel.value = props.label;
} else { return;
}
// (/)
const emptyVal =
props.modelValue == null ||
props.modelValue === "" ||
(Array.isArray(props.modelValue) && props.modelValue.length === 0);
if (emptyVal) {
selectLabel.value = "";
return;
}
if (props.multiple) { if (props.multiple) {
if (isFixOptions.value) { if (isFixOptions.value) {
selectLabel.value = props.options selectLabel.value = props.options
@ -149,7 +159,6 @@ const init = () => {
selectLabel.value = props.obj[props.labelField]; selectLabel.value = props.obj[props.labelField];
} }
} }
}
} }
const isFixOptions = ref(false); const isFixOptions = ref(false);

View File

@ -6,7 +6,7 @@ const DEFAULT_CONFIG = {
DASHBOARD_URL: "/dashboard", DASHBOARD_URL: "/dashboard",
//版本号 //版本号
APP_VER: "3.1.2026071608", APP_VER: "3.1.2026072315",
//内核版本号 //内核版本号
CORE_VER: "1.6.9", CORE_VER: "1.6.9",

View File

@ -62,6 +62,11 @@
position: relative; position: relative;
} }
/* 显式声明 nopadding 的区域不受上面默认内边距影响 */
.el-main.nopadding {
padding: 0;
}
/* 表单卡片化 */ /* 表单卡片化 */
.el-main > .el-form { .el-main > .el-form {
background: var(--el-bg-color); background: var(--el-bg-color);
@ -159,6 +164,11 @@
padding: 14px 10px 14px 14px; padding: 14px 10px 14px 14px;
overflow-y: auto; overflow-y: auto;
} }
.el-aside.nopadding {
background: transparent;
border-left: 0;
padding: 0;
}
} }
/* ============================================================ /* ============================================================

View File

@ -356,7 +356,7 @@
<el-dialog v-model="choseMyScheduleDialog" title="选择策略"> <el-dialog v-model="choseMyScheduleDialog" title="选择策略">
<MySchedule @scheduleChose="scheduleChose"></MySchedule> <MySchedule @scheduleChose="scheduleChose"></MySchedule>
</el-dialog> </el-dialog>
<el-dialog v-model="riskDialog" title="新增/编辑" width="90%"> <el-dialog v-model="riskDialog" title="新增/编辑" width="90%" destroy-on-close>
<RiskForm ref="riskForm" :riskpoint="rowData.id" @riskConfirm="riskConfirm"></RiskForm> <RiskForm ref="riskForm" :riskpoint="rowData.id" @riskConfirm="riskConfirm"></RiskForm>
</el-dialog> </el-dialog>
</el-container> </el-container>
@ -444,6 +444,7 @@ export default {
this.$refs.table.queryData(this.query); this.$refs.table.queryData(this.query);
}, },
table_add() { table_add() {
this.saveForm = Object.assign({}, defaultForm);
this.saveDialog = true; this.saveDialog = true;
}, },
table_edit(row) { table_edit(row) {

View File

@ -128,13 +128,13 @@
</el-container> </el-container>
</el-container> </el-container>
<save-dialog <save-dialog
v-model="dialog.save" v-if="dialog.save"
ref="saveDialog" ref="saveDialog"
@success="handleSaveSuccessss" @success="handleSaveSuccessss"
@closed="dialog.save = false" @closed="dialog.save = false"
></save-dialog> ></save-dialog>
<algo-dialog <algo-dialog
v-model="dialog.algos" v-if="dialog.algos"
ref="algoDialog" ref="algoDialog"
@success="handleSaveSuccessss" @success="handleSaveSuccessss"
@closed="dialog.algos = false" @closed="dialog.algos = false"

View File

@ -208,7 +208,7 @@
> </el-button > </el-button
> >
</el-footer> </el-footer>
<el-dialog title="选择题目" v-model="questionCVisible" width="90%"> <el-dialog append-to-body title="选择题目" v-model="questionCVisible" width="90%">
<Question <Question
style="height: 500px" style="height: 500px"
ref="QuestionChoseRef" ref="QuestionChoseRef"
@ -219,7 +219,7 @@
</template> </template>
</el-dialog> </el-dialog>
<!-- <el-button type="primary" @click="handleChoose1" icon="el-icon-plus" style="margin-right: 10px;">上传试题</el-button> --> <!-- <el-button type="primary" @click="handleChoose1" icon="el-icon-plus" style="margin-right: 10px;">上传试题</el-button> -->
<el-dialog <el-dialog append-to-body
v-model="impDialogVisible" v-model="impDialogVisible"
title="上传试题" title="上传试题"
:left="'200px'" :left="'200px'"

View File

@ -1,18 +1,22 @@
<template> <template>
<div class="wrapper" @mouseover="mouseOver" @mouseout="mouseOut"> <div class="wrapper" @mouseover="mouseOver" @mouseout="mouseOut">
<table class="totall"> <table class="totall">
<tbody>
<tr class="title"> <tr class="title">
<th v-for="itemx in liData" :key="itemx">{{ itemx }}</th> <th v-for="itemx in liData" :key="itemx">{{ itemx }}</th>
</tr> </tr>
</tbody>
</table> </table>
<div ref="moocBox" class="wrapper2"> <div ref="moocBox" class="wrapper2">
<table :class="{ marquee_top: animate }"> <table :class="{ marquee_top: animate }">
<tbody>
<tr v-for="itemy in listData" class="rollData" ref="con1" :key="itemy"> <tr v-for="itemy in listData" class="rollData" ref="con1" :key="itemy">
<td v-for="itemz in itemy" :key="itemz">{{ itemz }}</td> <td v-for="itemz in itemy" :key="itemz">{{ itemz }}</td>
</tr> </tr>
<tr v-for="itemy in listData" class="rollData" ref="con2" :key="itemy"> <tr v-for="itemy in listData" class="rollData" ref="con2" :key="itemy">
<td v-for="itemz in itemy" :key="itemz">{{ itemz }}</td> <td v-for="itemz in itemy" :key="itemz">{{ itemz }}</td>
</tr> </tr>
</tbody>
</table> </table>
</div> </div>
</div> </div>

View File

@ -326,10 +326,17 @@ export default {
// console.log('userOption',userOption); // console.log('userOption',userOption);
}); });
}, },
//,(mioitem_form,v-if)
resetForm() {
this.form = { state: 10, inout_date: this.$TOOL.dateFormat2(new Date()) };
},
// //
open(mode = "add", type = "",htype="") { open(mode = "add", type = "",htype="") {
this.mode = mode; this.mode = mode;
this.htype = htype; this.htype = htype;
if (mode == "add") {
this.resetForm();
}
this.form.type = type; this.form.type = type;
this.init(); this.init();
this.visible = true; this.visible = true;

View File

@ -149,8 +149,9 @@
</el-table-column> </el-table-column>
<el-table-column label="检验提交" prop="count_tested" v-if="cate=='mainso'&&(type == 'pur_in' ||type == 'do_in' ||type == 'other_in')"> <el-table-column label="检验提交" prop="count_tested" v-if="cate=='mainso'&&(type == 'pur_in' ||type == 'do_in' ||type == 'other_in')">
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.test_date == null" type="warning"></el-tag> <el-tag v-if="scope.row.test_date !== null" type="success"></el-tag>
<el-tag v-else type="success"></el-tag> <el-tag v-else-if="scope.row.material_&&scope.row.material_.test_mode_in==10" type="info">免检</el-tag>
<el-tag v-else type="warning"></el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="不合格数量" prop="count_notok" v-if="cate!=='helpso'"> <el-table-column label="不合格数量" prop="count_notok" v-if="cate!=='helpso'">

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog <el-dialog append-to-body
title="检验" title="检验"
v-model="visible" v-model="visible"
:size="1200" :size="1200"

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog <el-dialog append-to-body
:title="titleMap[form.type]" :title="titleMap[form.type]"
v-model="visible" v-model="visible"
:size="1000" :size="1000"
@ -272,7 +272,7 @@
<el-button @click="visible = false">取消</el-button> <el-button @click="visible = false">取消</el-button>
</template> </template>
</el-dialog> </el-dialog>
<el-dialog title="设置发货流水位数" v-model="showDigit"> <el-dialog append-to-body title="设置发货流水位数" v-model="showDigit">
<h4>没有找到该前缀的发货编号请完善发货流水号位数填整数</h4> <h4>没有找到该前缀的发货编号请完善发货流水号位数填整数</h4>
<el-form :model="wprParams" label-width="80px"> <el-form :model="wprParams" label-width="80px">
<el-form-item label="流水位数"> <el-form-item label="流水位数">
@ -507,7 +507,8 @@ export default {
// orderitem_material__order: this.mioObj.order, // orderitem_material__order: this.mioObj.order,
type:10, type:10,
// material__type: 10 // material__type: 10
count__gte: 1, // (-)0
count_canmio__gt: 0,
}; };
this.inputBatchDisable = true; this.inputBatchDisable = true;
@ -735,11 +736,35 @@ export default {
this.form.warehouse = ""; this.form.warehouse = "";
this.inputBatchDisable = false; this.inputBatchDisable = false;
}, },
//,""
resetForm() {
this.form = { count: 1, note: '', pack_index: 1 };
this.selectObj = null;
this.selectBatch = null;
this.mioitems = [''];
this.mioitemw = [];
this.wprList = [];
this.wbatchOptions = [];
this.batchOptions = [];
this.batchOptions_o = [];
this.components = [];
this.assembShow = false;
this.batchcount = null;
this.mTracking = 10;
this.prefix = '';
this.halfgoodprefix = '';
this.digitNum = null;
this.count_send_yp = 0;
this.inputBatchDisable = false;
},
// //
open(mode = "add", type = "",mtype) { open(mode = "add", type = "",mtype) {
let that = this; let that = this;
this.mode = mode; this.mode = mode;
this.mtype = mtype?mtype:null; this.mtype = mtype?mtype:null;
if (mode == "add") {
this.resetForm();
}
this.form.type = this.type = type; this.form.type = this.type = type;
if (mode == "add") { if (mode == "add") {
this.init(); this.init();

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog <el-dialog append-to-body
title="标签信息" title="标签信息"
v-model="visible" v-model="visible"
:size="1000" :size="1000"

View File

@ -132,6 +132,18 @@
<el-switch v-model="form.is_hidden" /> <el-switch v-model="form.is_hidden" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :md="12" :sm="24">
<el-form-item label="入库检验方式" prop="test_mode_in">
<el-select
v-model="form.test_mode_in"
placeholder="入库检验方式"
style="width: 100%"
>
<el-option label="免检" :value="10"></el-option>
<el-option label="必检" :value="20"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :md="12" :sm="24"> <el-col :md="12" :sm="24">
<el-form-item label="安全库存下限" prop="count_safe"> <el-form-item label="安全库存下限" prop="count_safe">
<el-input-number <el-input-number
@ -231,7 +243,7 @@ export default {
show: "查看物料", show: "查看物料",
}, },
brothers: [], brothers: [],
form: {}, form: { test_mode_in: 20 },
rules: { rules: {
name: [ name: [
{ {

View File

@ -157,6 +157,18 @@
<el-switch v-model="form.is_hidden" /> <el-switch v-model="form.is_hidden" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :md="12" :sm="24">
<el-form-item label="入库检验方式" prop="test_mode_in">
<el-select
v-model="form.test_mode_in"
placeholder="入库检验方式"
style="width: 100%"
>
<el-option label="免检" :value="10"></el-option>
<el-option label="必检" :value="20"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :md="12" :sm="24" v-if="form.type == 40"> <el-col :md="12" :sm="24" v-if="form.type == 40">
<el-form-item label="是否进入车间库存"> <el-form-item label="是否进入车间库存">
<el-switch v-model="form.into_wm" /> <el-switch v-model="form.into_wm" />
@ -207,7 +219,7 @@ export default {
edit: "编辑物料", edit: "编辑物料",
show: "查看物料", show: "查看物料",
}, },
form: {}, form: { test_mode_in: 20 },
rules: { rules: {
name: [ name: [
{ {
@ -353,6 +365,7 @@ export default {
that.form.unit = data.unit; that.form.unit = data.unit;
that.form.process = data.process; that.form.process = data.process;
that.form.tracking = data.tracking; that.form.tracking = data.tracking;
that.form.test_mode_in = data.test_mode_in;
that.form.count_safe = data.count_safe; that.form.count_safe = data.count_safe;
that.form.count_safe_upper = data.count_safe_upper; that.form.count_safe_upper = data.count_safe_upper;
that.form.is_hidden = data.is_hidden; that.form.is_hidden = data.is_hidden;

View File

@ -98,7 +98,7 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="合批清除缺陷"> <el-form-item label="合格B类默认记合格">
<el-switch <el-switch
v-model="form.clear_defect" v-model="form.clear_defect"
></el-switch> ></el-switch>

View File

@ -17,14 +17,7 @@
</div> </div>
</el-header> </el-header>
<el-main class="nopadding"> <el-main class="nopadding">
<scDegra <xtFlowViewer :nodes="nodes" :edges="edges"></xtFlowViewer>
style="margin-top: 50px;"
ref="degraDialogs"
:nodes="nodes"
:edges="edges"
:rankdir="'DL'"
>
</scDegra>
</el-main> </el-main>
</el-container> </el-container>
</el-aside> </el-aside>
@ -130,6 +123,9 @@
</el-dialog> </el-dialog>
</template> </template>
<script> <script>
import { defineAsyncComponent } from "vue";
// Vue Flow
const xtFlowViewer = defineAsyncComponent(() => import(/* webpackPrefetch: true */ "@/components/xtFlowViewer.vue"));
const defaultForm = { const defaultForm = {
process: null, process: null,
sort: 1, sort: 1,
@ -139,7 +135,7 @@ const defaultForm = {
}; };
export default { export default {
name: "route", name: "route",
components: {}, components: { xtFlowViewer },
data() { data() {
return { return {
apiObj: null, apiObj: null,

View File

@ -3,6 +3,7 @@
:title="titleMap[mode]" :title="titleMap[mode]"
v-model="visible" v-model="visible"
:size="1000" :size="1000"
append-to-body
destroy-on-close destroy-on-close
@closed="$emit('closed')" @closed="$emit('closed')"
> >
@ -304,6 +305,8 @@ export default {
props: { props: {
count: { type: Number, default : 0 }, count: { type: Number, default : 0 },
routepack: { type: String, default: "" }, routepack: { type: String, default: "" },
// (线)setData
prefill: { type: Object, default: null },
}, },
emits: ["success", "closed"], emits: ["success", "closed"],
data() { data() {
@ -396,6 +399,11 @@ export default {
}, },
getroutemats(){ getroutemats(){
let that = this; let that = this;
// routeId(线)
if (!that.routeId) {
that.routemats = [];
return;
}
that.$API.mtm.routemat.list.req({ route: that.routeId,page: 0}).then((res) => { that.$API.mtm.routemat.list.req({ route: that.routeId,page: 0}).then((res) => {
that.routemats = res; that.routemats = res;
}); });
@ -466,15 +474,45 @@ export default {
that.paramsJsonFileurl = list1; that.paramsJsonFileurl = list1;
that.params_json.fileurl = JSON.stringify(that.paramsJsonFileurl); that.params_json.fileurl = JSON.stringify(that.paramsJsonFileurl);
}, },
//openv-if
resetForm() {
this.form = Object.assign({}, defaultForm);
this.form.routepack = this.routepack;
if (this.project_code == "bxerp" || this.project_code == "tcerp") {
this.form.material_out_tracking = 20;
}
this.params_json = {
duibian1: 0,
duibian2: 0,
changdu1: 0,
changdu2: 0,
pbnum: 0,
pbsigao: 0,
pbbb: "是",
};
this.params_json2 = { dscd: 0, sijing: 0 };
this.fileIds = [];
this.fileurl = [];
this.fileurl_form = [];
this.paramsJsonFileurl = [];
this.routemats = [];
this.routeId = "";
this.processName = "";
this.divDisable = false;
},
// //
open(mode = "add") { open(mode = "add") {
let that = this; let that = this;
that.mode = mode; that.mode = mode;
that.resetForm();
if (that.prefill) that.setData(that.prefill);
that.visible = true; that.visible = true;
that.$nextTick(() => { that.$nextTick(() => {
if(that.count){ // setData sort
if(that.count && mode === "add"){
that.form.sort = that.count+1; that.form.sort = that.count+1;
} }
that.$refs.dialogForm && that.$refs.dialogForm.clearValidate();
}) })
return this; return this;
}, },
@ -484,16 +522,18 @@ export default {
Object.assign(that.form, data); Object.assign(that.form, data);
that.routeId = data.id; that.routeId = data.id;
that.addTemplate.route = data.id; that.addTemplate.route = data.id;
that.params_json=that.form.params_json; // 线(prefill)params_json,undefined
if (that.form.params_json) {
that.params_json = Object.assign({}, that.params_json, that.form.params_json);
}
if((this.project_code=='bxerp'||this.project_code=='tcerp')&&this.form.material_out_tracking==null){ if((this.project_code=='bxerp'||this.project_code=='tcerp')&&this.form.material_out_tracking==null){
this.form.material_out_tracking = 20; this.form.material_out_tracking = 20;
} }
if(data.process_name=='捆棒'){ if(data.process_name=='捆棒'&&that.form.params_json){
that.params_json2=that.form.params_json; that.params_json2 = Object.assign({}, that.params_json2, that.form.params_json);
} }
if(data.process_name=='排一次棒'){ if(data.process_name=='排一次棒'){
that.fileIds= []; that.fileIds= [];
that.params_json=that.form.params_json;
if(that.params_json.fileurl&&that.params_json.fileurl!=="[]"){ if(that.params_json.fileurl&&that.params_json.fileurl!=="[]"){
let fileurl = JSON.parse(that.params_json.fileurl); let fileurl = JSON.parse(that.params_json.fileurl);
fileurl.forEach((item)=>{ fileurl.forEach((item)=>{

View File

@ -1,35 +1,38 @@
<template> <template>
<el-container v-loading="loading"> <el-container class="rs-page" v-loading="loading">
<el-main class="nopadding rs-main">
<!-- 基本信息条 -->
<div class="rs-card rs-head">
<div class="rs-card__title">基本信息</div>
<div class="rs-head__items">
<div class="rs-head__item">
<span class="rs-lbl">工艺名称</span>
<span class="rs-val">{{ form.name || "-" }}</span>
</div>
<div class="rs-head__item">
<span class="rs-lbl">物料名称</span>
<span class="rs-val">{{ form.material_name || "-" }}</span>
</div>
</div>
</div>
<el-main class="nopadding" style="padding-right: 1px;"> <div class="rs-body">
<el-container> <!-- 流程图卡 -->
<el-header style="height: 80px;display: block;padding:0"> <div class="rs-card rs-flow">
<el-card shadow="hover"> <div class="rs-card__title">工艺路线流程图</div>
<el-descriptions :column="3" title="基本信息"> <el-tabs v-if="tabsTitle.length > 1" v-model="activeName" class="rs-tabs"
<el-descriptions-item label="工艺名称:">{{ @tab-click="tabshandleClick">
form.name
}}</el-descriptions-item>
<el-descriptions-item label="物料名称:">{{
form.material_name
}}</el-descriptions-item>
</el-descriptions>
</el-card>
</el-header>
<el-main>
<el-container>
<el-aside style="width: 50%;overflow: scroll;">
<div
style="font-weight: 600;color: #303133;font-size: 16px;padding: 10px 0;position: fixed;">
工艺路线流程图</div>
<el-tabs v-if="tabsTitle.length > 1" v-model="activeName" :tab-position="'left'"
style="flex-direction: row;" @tab-click="tabshandleClick">
<el-tab-pane :label="item" v-for="item in tabsTitle" :key="item"></el-tab-pane> <el-tab-pane :label="item" v-for="item in tabsTitle" :key="item"></el-tab-pane>
</el-tabs> </el-tabs>
<scDegra style="margin-top: 50px;" v-if="limitedWatch" ref="degraDialogs" :nodes="nodes" <xtFlowViewer v-if="limitedWatch" :nodes="nodes" :edges="edges" class="rs-flow__canvas"
:edges="edges" :rankdir="'DL'" @closeDialog="limitedWatch = false"> style="min-height: 0">
</scDegra> </xtFlowViewer>
</el-aside> </div>
<el-main class="padding: 1px">
<!-- 工序表格卡 -->
<div class="rs-card rs-steps">
<div class="rs-card__title">工序步骤</div>
<div class="rs-steps__table">
<scTable ref="tables" :data="tableData" row-key="id" hidePagination hideDo stripe border> <scTable ref="tables" :data="tableData" row-key="id" hidePagination hideDo stripe border>
<el-table-column label="排序" prop="sort" width="50"> <el-table-column label="排序" prop="sort" width="50">
</el-table-column> </el-table-column>
@ -39,41 +42,40 @@
</el-table-column> </el-table-column>
<el-table-column label="输出" prop="material_out_name"> <el-table-column label="输出" prop="material_out_name">
</el-table-column> </el-table-column>
<el-table-column label="追踪方式"> <el-table-column label="追踪方式" width="80">
<template #default="scope"> <template #default="scope">
<span>{{ tracking_[scope.row.material_out_tracking] }}</span> <span>{{ tracking_[scope.row.material_out_tracking] }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="出材率" prop="out_rate"> <el-table-column label="出材率" prop="out_rate" width="70">
</el-table-column> </el-table-column>
<el-table-column label="切分融合数量" prop="div_number"> <el-table-column label="切分融合数量" prop="div_number" width="80">
</el-table-column> </el-table-column>
<el-table-column label="工时" prop="hour_work"> <el-table-column label="工时" prop="hour_work" width="60">
</el-table-column> </el-table-column>
<el-table-column label="批次校验" v-if="project_code !== 'bxerp'"> <el-table-column label="批次校验" width="80" v-if="project_code !== 'bxerp'">
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.batch_bind" type="success"></el-tag> <el-tag v-if="scope.row.batch_bind" type="success"></el-tag>
</template> </template>
</el-table-column> </el-table-column>
</scTable> </scTable>
</div>
</div>
</div>
</el-main> </el-main>
</el-container> <el-aside width="20%" v-if="form.ticket" class="rs-aside">
</el-main>
</el-container>
</el-main>
<el-aside width="20%" v-if="form.ticket">
<ticketd :ticket_="form.ticket_"></ticketd> <ticketd :ticket_="form.ticket_"></ticketd>
</el-aside> </el-aside>
</el-container> </el-container>
</template> </template>
<script> <script>
import { defineAsyncComponent } from "vue";
import ticketd from '@/views/wf/ticketd.vue' import ticketd from '@/views/wf/ticketd.vue'
// Vue Flow
const xtFlowViewer = defineAsyncComponent(() => import(/* webpackPrefetch: true */ "@/components/xtFlowViewer.vue"));
export default { export default {
emits: ["success", "closed"], emits: ["success", "closed"],
components: { ticketd }, components: { ticketd, xtFlowViewer },
props: { props: {
t_id: { type: String, default: null }, t_id: { type: String, default: null },
}, },
@ -178,7 +180,99 @@ export default {
</script> </script>
<style scoped> <style scoped>
.el-transfer { /* 页面底色:浅灰承托白卡 */
--el-transfer-panel-width: 345px !important; .rs-page {
height: 100%;
background: #f5f7fa;
}
.rs-main {
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px;
overflow: hidden;
}
/* 统一卡片语言 */
.rs-card {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 10px;
box-shadow: 0 1px 2px rgba(31, 45, 61, 0.04);
padding: 12px 14px;
}
.rs-card__title {
display: flex;
align-items: center;
gap: 8px;
font-size: 15px;
font-weight: 600;
color: #303133;
margin-bottom: 10px;
}
.rs-card__title::before {
content: "";
width: 3px;
height: 14px;
border-radius: 2px;
background: var(--el-color-primary, #409eff);
}
/* 基本信息条:单行轻量 */
.rs-head {
flex-shrink: 0;
padding-bottom: 10px;
}
.rs-head .rs-card__title {
margin-bottom: 8px;
}
.rs-head__items {
display: flex;
gap: 48px;
flex-wrap: wrap;
}
.rs-lbl {
color: #909399;
font-size: 13px;
margin-right: 10px;
}
.rs-val {
color: #303133;
font-size: 13px;
font-weight: 500;
}
/* 主体:流程图 + 工序表 两卡并排 */
.rs-body {
flex: 1;
min-height: 0;
display: flex;
gap: 10px;
}
.rs-flow {
width: 50%;
display: flex;
flex-direction: column;
overflow: hidden;
}
.rs-flow__canvas {
flex: 1;
min-height: 0;
}
.rs-tabs :deep(.el-tabs__header) {
margin: 0 0 8px;
}
.rs-steps {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.rs-steps__table {
flex: 1;
min-height: 0;
}
/* 右侧工单/审批面板:与主体同底色,间距交给 ticketd 自身的内边距 */
.rs-aside {
background: #f5f7fa;
padding: 0;
} }
</style> </style>

View File

@ -7,7 +7,7 @@
@closed="$emit('closed')" @closed="$emit('closed')"
> >
<el-container> <el-container>
<el-header> <el-header style="height: auto; padding: 6px 20px 0">
<el-steps <el-steps
:active="active" :active="active"
style="width: 100%" style="width: 100%"
@ -93,30 +93,29 @@
<!--工序!--> <!--工序!-->
<el-main class="nopadding" v-if="active === 1"> <el-main class="nopadding" v-if="active === 1">
<el-container> <el-container>
<el-aside style="width: 50%;overflow: scroll;"> <el-aside class="nopadding" style="width: 55%;overflow: hidden;padding-right: 6px;">
<div style="font-weight: 600;color: #303133;font-size: 16px;padding: 10px 0;position: fixed;width: 30px;">工艺路线流程图</div> <xtFlowEditor
<scDegra ref="flowEditor"
style="margin-top: 50px;" :routepack="routepack"
v-if="limitedWatch" :product-id="form.material || ''"
ref="degraDialogs" :product-name="form.material_name || ''"
:nodes="nodes" :readonly="!!(form.state && form.state !== 10)"
:edges="edges" @changed="handleEditorChanged"
:rankdir="'DL'" ></xtFlowEditor>
>
</scDegra>
</el-aside> </el-aside>
<el-main style="width: 50%;overflow: scroll;"> <el-main class="nopadding" style="width: 45%;overflow: auto;">
<el-container> <el-container>
<el-header> <el-header style="height: auto; padding: 4px 0">
<div class="left-panel" style="margin: 10px"> <div class="left-panel">
<el-button <el-button
type="primary" type="primary"
icon="el-icon-plus" icon="el-icon-plus"
size="small"
@click="table_add" @click="table_add"
></el-button> ></el-button>
</div> </div>
</el-header> </el-header>
<el-main> <el-main class="nopadding">
<scTable <scTable
ref="tables" ref="tables"
:apiObj="apiObj" :apiObj="apiObj"
@ -176,7 +175,7 @@
</el-main> </el-main>
</el-container> </el-container>
</el-main> </el-main>
<el-footer v-if="active === 1" style="text-align: center"> <el-footer v-if="active === 1" style="height: 44px; display: flex; align-items: center; justify-content: center; gap: 8px;">
<el-button @click="handleLastStep" style="margin-right: 4px">上一步</el-button> <el-button @click="handleLastStep" style="margin-right: 4px">上一步</el-button>
<ticketd_b :workflow_key="'routepack'" :title="form.name" :t_id="form.id" :ticket_="form.ticket_" <ticketd_b :workflow_key="'routepack'" :title="form.name" :t_id="form.id" :ticket_="form.ticket_"
@success="$emit('closed')"></ticketd_b> @success="$emit('closed')"></ticketd_b>
@ -185,6 +184,7 @@
</el-container> </el-container>
<save-dialog <save-dialog
v-if="dialog.save" v-if="dialog.save"
:key="dialogSeq"
ref="saveDialog" ref="saveDialog"
:count = "count" :count = "count"
:routepack="routepack" :routepack="routepack"
@ -194,11 +194,14 @@
</el-drawer> </el-drawer>
</template> </template>
<script> <script>
import { defineAsyncComponent } from "vue";
import saveDialog from "./route_form.vue"; import saveDialog from "./route_form.vue";
import ticketd_b from "@/views/wf/ticketd_b.vue"; import ticketd_b from "@/views/wf/ticketd_b.vue";
// Vue Flow + dagre
const xtFlowEditor = defineAsyncComponent(() => import(/* webpackPrefetch: true */ "@/components/xtFlowEditor.vue"));
export default { export default {
name: "routepack_form", name: "routepack_form",
components: { saveDialog, ticketd_b }, components: { saveDialog, ticketd_b, xtFlowEditor },
data() { data() {
return { return {
active: 0, active: 0,
@ -215,6 +218,7 @@ export default {
dialog: { dialog: {
save: false, save: false,
}, },
dialogSeq: 0,
fileList:[], fileList:[],
form: { form: {
name: "", name: "",
@ -339,13 +343,14 @@ export default {
table_add() { table_add() {
let that = this; let that = this;
that.count = this.$refs.tables.tableData.length; that.count = this.$refs.tables.tableData.length;
console.log("count",that.count); this.dialogSeq += 1;
this.dialog.save = true; this.dialog.save = true;
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.saveDialog.open("add"); this.$refs.saveDialog.open("add");
}); });
}, },
table_edit(row) { table_edit(row) {
this.dialogSeq += 1;
this.dialog.save = true; this.dialog.save = true;
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.saveDialog.open("edit").setData(row); this.$refs.saveDialog.open("edit").setData(row);
@ -369,7 +374,7 @@ export default {
let that = this; let that = this;
that.$API.mtm.route.delete.req(row.id).then((res) => { that.$API.mtm.route.delete.req(row.id).then((res) => {
that.$message.success("工序步骤删除成功"); that.$message.success("工序步骤删除成功");
that.getDEGdatas(); that.$refs.flowEditor && that.$refs.flowEditor.reload();
that.$refs.tables.refresh(); that.$refs.tables.refresh();
return res; return res;
}).catch((err) => { }).catch((err) => {
@ -378,10 +383,13 @@ export default {
}, },
// //
handleSaveSuccess() { handleSaveSuccess() {
console.log("handleSaveSuccess");
let that = this; let that = this;
that.$refs.tables.refresh(); that.$refs.tables.refresh();
that.getDEGdatas(); that.$refs.flowEditor && that.$refs.flowEditor.reload();
},
// ->
handleEditorChanged() {
this.$refs.tables && this.$refs.tables.refresh();
}, },
}, },
}; };

View File

@ -99,6 +99,11 @@
</template> </template>
<script> <script>
const defaultForm = {
name: "",
location: "",
capacity: "",
};
export default { export default {
name: "mroom", name: "mroom",
data() { data() {
@ -114,11 +119,7 @@ export default {
edit: "编辑", edit: "编辑",
show: "查看", show: "查看",
}, },
addForm: { addForm: Object.assign({}, defaultForm),
name: "",
location: "",
capacity: "",
},
rules: { rules: {
name: [{ required: true, message: "请输入会议室名称", trigger: "blur" }], name: [{ required: true, message: "请输入会议室名称", trigger: "blur" }],
location: [{ required: true, message: "请输入地点", trigger: "blur" }], location: [{ required: true, message: "请输入地点", trigger: "blur" }],
@ -127,9 +128,10 @@ export default {
}; };
}, },
methods: { methods: {
// //
handleAdd() { handleAdd() {
this.type = "add"; this.type = "add";
this.addForm = Object.assign({}, defaultForm);
this.limitedVisible = true; this.limitedVisible = true;
}, },
handleCancel() { handleCancel() {

View File

@ -6,7 +6,7 @@
label-width="auto" label-width="auto"
style="margin-top: 20px" style="margin-top: 20px"
> >
<el-form-item label="领料时校验是否检验"> <el-form-item label="领料时校验是否检验(总开关,物料可单独设为免检)">
<el-switch v-model="form.mes.check_test_when_do_out"></el-switch> <el-switch v-model="form.mes.check_test_when_do_out"></el-switch>
</el-form-item> </el-form-item>
<el-divider /> <el-divider />
@ -51,7 +51,16 @@ export default {
}) })
}, },
submitForm() { submitForm() {
this.saveLoading = true;
this.$API.system.config.updateInfo.req({
mes: this.form.mes,
sms: this.form.sms
}).then(() => {
this.$message.success("保存成功");
this.saveLoading = false;
}).catch(() => {
this.saveLoading = false;
})
} }
} }
} }

View File

@ -100,7 +100,7 @@
</el-main> </el-main>
<ScBind v-model="showBindBlt" :bindBtl="bindBtl" :bindType="bindType" :bindName="bindName" <ScBind v-model="showBindBlt" :bindBtl="bindBtl" :bindType="bindType" :bindName="bindName"
:bindEmployee="bindEmployee" @success="showBindBltSuccess" @closed="showBindBltClose"></ScBind> :bindEmployee="bindEmployee" @success="showBindBltSuccess" @closed="showBindBltClose"></ScBind>
<el-dialog title="提前离厂" v-model="leaveVisible" width="30%"> <el-dialog append-to-body title="提前离厂" v-model="leaveVisible" width="30%">
<el-form ref="dialogForm" label-width="80px"> <el-form ref="dialogForm" label-width="80px">
<el-form-item label="离厂原因"> <el-form-item label="离厂原因">
<el-input v-model="reason" type="text" clearable></el-input> <el-input v-model="reason" type="text" clearable></el-input>
@ -112,7 +112,7 @@
</template> </template>
</el-dialog> </el-dialog>
<!-- 人员添加编辑 --> <!-- 人员添加编辑 -->
<el-dialog v-model="workerVisible" :title="workDialogTitle"> <el-dialog append-to-body v-model="workerVisible" :title="workDialogTitle">
<el-form ref="workerForm" :model="formworker" label-width="120px"> <el-form ref="workerForm" :model="formworker" label-width="120px">
<el-row> <el-row>
<el-col :md="24" :sm="12" :xs="24"> <el-col :md="24" :sm="12" :xs="24">

View File

@ -245,6 +245,7 @@ export default {
methods: { methods: {
handleAdd() { handleAdd() {
this.type = "add"; this.type = "add";
this.addForm = {};
this.limitedVisible = true; this.limitedVisible = true;
}, },
handleCancel() { handleCancel() {

View File

@ -179,8 +179,9 @@ export default {
}, },
handleQuery(){ handleQuery(){
let that = this; let that = this;
let querys0 = [{field:"wm",value:false,compare:"isnull"},{field:"data__分检_日期",compare:"isnull",value:false},{field:"data__has_key",compare:"",value:"分检_批次号"}];
let querys = [[{field:"wm",value:false,compare:"isnull"},{field:"data__分检_日期",compare:"isnull",value:false},{field:"data__has_key",compare:"",value:"分检_批次号"}]]; let querys = [[{field:"wm",value:false,compare:"isnull"},{field:"data__分检_日期",compare:"isnull",value:false},{field:"data__has_key",compare:"",value:"分检_批次号"}]];
let obj = {},obj1 = {},obj2 = {},obj3 = {},obj4 = {},obj5 = {}; let obj = {},obj1 = {},obj2 = {},obj3 = {},obj4 = {},obj5 = {},obj6 = {},obj7 = {};
obj.field = 'data__分检_批次号'; obj.field = 'data__分检_批次号';
obj.value = that.query.batch__contains; obj.value = that.query.batch__contains;
obj.compare = ''; obj.compare = '';
@ -201,27 +202,58 @@ export default {
obj4.value = that.query.number__contains; obj4.value = that.query.number__contains;
obj4.compare = 'contains'; obj4.compare = 'contains';
obj5.field = 'data__分检_操作人'; obj5.field = 'data__喷码_一体刀_操作人';
obj5.value = that.query.handler; obj5.value = that.query.handler;
obj5.compare = ''; obj5.compare = '';
obj6.field = 'data__喷码_磨床开台_操作人';
obj6.value = that.query.handler;
obj6.compare = '';
obj7.field = 'data__光锥一体刀_操作人';
obj7.value = that.query.handler;
obj7.compare = '';
if(that.query.handler!==''&&that.query.handler!==null){
querys[1]=querys0;
querys[2]=querys0;
querys[0].push(obj5);
querys[1].push(obj6);
querys[2].push(obj7);
}
if(that.query.batch__contains!==''&&that.query.batch__contains!==null){ if(that.query.batch__contains!==''&&that.query.batch__contains!==null){
querys[0].push(obj); querys[0].push(obj);
if(that.query.handler!==''&&that.query.handler!==null){
querys[1].push(obj);
querys[2].push(obj);
}
} }
if(that.query.last_time__gte!==''&&that.query.last_time__gte!==null){ if(that.query.last_time__gte!==''&&that.query.last_time__gte!==null){
querys[0].push(obj1); querys[0].push(obj1);
if(that.query.handler!==''&&that.query.handler!==null){
querys[1].push(obj1);
querys[2].push(obj1);
}
} }
if(that.query.last_time__lte!==''&&that.query.last_time__lte!==null){ if(that.query.last_time__lte!==''&&that.query.last_time__lte!==null){
querys[0].push(obj2); querys[0].push(obj2);
if(that.query.handler!==''&&that.query.handler!==null){
querys[1].push(obj2);
querys[2].push(obj2);
}
} }
if(that.query.name__contains!==''&&that.query.name__contains!==null){ if(that.query.name__contains!==''&&that.query.name__contains!==null){
querys[0].push(obj3); querys[0].push(obj3);
if(that.query.handler!==''&&that.query.handler!==null){
querys[1].push(obj3);
querys[2].push(obj3);
}
} }
if(that.query.number__contains!==''&&that.query.number__contains!==null){ if(that.query.number__contains!==''&&that.query.number__contains!==null){
querys[0].push(obj4); querys[0].push(obj4);
}
if(that.query.handler!==''&&that.query.handler!==null){ if(that.query.handler!==''&&that.query.handler!==null){
querys[0].push(obj5); querys[1].push(obj4);
querys[2].push(obj4);
}
} }
that.params.querys = querys; that.params.querys = querys;
this.$refs.table.queryData(that.params); this.$refs.table.queryData(that.params);

View File

@ -195,7 +195,7 @@
</el-container> </el-container>
</template> </template>
<script> <script lang="jsx">
export default { export default {
name: "statistics_guan", name: "statistics_guan",
data() { data() {

View File

@ -4,6 +4,7 @@
<div class="left-panel"></div> <div class="left-panel"></div>
<div class="right-panel"> <div class="right-panel">
<xtSelect <xtSelect
v-if="activeName=='shengchan'"
:apiObj="apiObj" :apiObj="apiObj"
v-model="query.user_name" v-model="query.user_name"
:valueField="'name'" :valueField="'name'"
@ -36,13 +37,16 @@
</div> </div>
</el-header> </el-header>
<el-main class="nopadding"> <el-main class="nopadding">
<el-tabs type="border-card" v-model="activeName" @tab-click="tabsClick" style="height: 100%;" lazy>
<el-tab-pane name="shengchan" label="生产统计" style="height: 100%;" key="shengchan">
<el-container v-if="activeName=='shengchan'">
<el-main>
<scTable <scTable
ref="table" ref="table"
:data="tableData" :data="tableData"
id="exportDiv" id="exportDiv"
row-key="id" row-key="id"
hidePagination hidePagination
hideDo
stripe stripe
> >
<el-table-column label="日期" prop="日期" width="100" fixed="left"> <el-table-column label="日期" prop="日期" width="100" fixed="left">
@ -73,13 +77,149 @@
</template> </template>
</el-table-column> </el-table-column>
</scTable> </scTable>
<el-drawer v-model="visible" :title="userName+'板段列表'" size="50%" direction="rtl" append-to-body destroy-on-close>
<el-card shadow="never">
<div v-for="(item,index) in detailRow" :key="item" style="height:22px;border-bottom: 1px solid #efefef;">
<span style="margin-right:10px;width: 18px;display: inline-block;">{{ index+1 }} </span>
{{ item }}
</div>
</el-card>
</el-drawer>
</el-main>
</el-container>
</el-tab-pane>
<el-tab-pane name="gongzhuang" label="工装统计" style="height: 100%;" key="gongzhuang">
<el-container v-if="activeName=='gongzhuang'">
<el-main>
<scTable
ref="table"
:data="tableData"
id="exportDiv"
row-key="id"
hidePagination
stripe
>
<el-table-column type="index" width="50" fixed="left"/>
<el-table-column label="工装编号" prop="工装编号" :filters="userFilters" :filter-method="filterUser" filter-placement="bottom-end">
</el-table-column>
<el-table-column label="物料名" prop="物料名" :filters="nameFilters" :filter-method="filterName" filter-placement="bottom-end">
</el-table-column>
<el-table-column label="总数">
<template #default="scope">
<span @click="handleClick(scope.row)" style="color:#0052d9;">{{ scope.row.总切片数 }}</span>
</template>
</el-table-column>
</scTable>
</el-main>
</el-container>
</el-tab-pane>
<el-tab-pane name="renyuan" label="开台人员统计" style="height: 100%;" key="renyuan">
<el-container v-if="activeName=='renyuan'">
<el-main>
<scTable
ref="table"
:data="tableData"
id="exportDiv"
row-key="id"
hidePagination
stripe
>
<el-table-column type="index" width="50" fixed="left"/>
<el-table-column label="开台人" prop="开台人员姓名" :filters="userFilters" :filter-method="filterUser3" filter-placement="bottom-end">
</el-table-column>
<el-table-column label="物料名" prop="物料名" :filters="nameFilters" :filter-method="filterName" filter-placement="bottom-end">
</el-table-column>
<el-table-column label="总数" prop="总切片数">
<template #default="scope">
<span @click="handleClick(scope.row)" style="color:#0052d9;">{{ scope.row.总切片数 }}</span>
</template>
</el-table-column>
</scTable>
</el-main>
</el-container>
</el-tab-pane>
</el-tabs>
<el-drawer
v-model="boardVisible"
:title="boardTitle"
size="80%"
direction="rtl"
append-to-body
destroy-on-close
>
<div class="board-drawer-body">
<div class="board-drawer-table">
<el-table :data="boardList" border stripe height="100%" style="width: 100%;">
<el-table-column type="index" label="序号" width="60" />
<el-table-column label="板段号" prop="板段号" />
<el-table-column label="像位移" prop="像位移" />
</el-table>
</div>
<div class="board-drawer-chart">
<div class="board-chart-scroll">
<scEcharts
:option="chartOption"
:style="{ width: chartWidth + 'px', height: '100%' }"
/>
</div>
</div>
</div>
</el-drawer>
</el-main> </el-main>
</el-container> </el-container>
</template> </template>
<script> <script>
import scEcharts from "@/components/scEcharts";
export default { export default {
name: "workerTimes", name: "workerTimes",
components: { scEcharts },
computed: {
chartWidth() {
const perPoint = 22;
const minWidth = 600;
return Math.max(minWidth, (this.boardList.length || 1) * perPoint);
},
chartOption() {
const xs = this.boardList.map(i => i.板段号);
const ys = this.boardList.map(i => (i.像位移 == null ? null : Number(i.像位移)));
return {
tooltip: { trigger: 'axis' },
grid: { left: 50, right: 20, top: 30, bottom: 100 },
xAxis: {
type: 'category',
data: xs,
axisLabel: { rotate: 85, interval: 0, fontSize: 11 }
},
yAxis: { type: 'value', name: '像位移' },
series: [{
name: '像位移',
type: 'line',
data: ys,
connectNulls: false,
symbol: 'circle',
symbolSize: 6,
markLine: {
symbol: 'none',
silent: true,
data: [
{
yAxis: 100,
lineStyle: { color: '#f5a623', width: 1.5, type: 'dashed' },
label: { formatter: '标准 100', position: 'insideEndTop', color: '#f5a623' }
},
{
yAxis: 200,
lineStyle: { color: '#f5222d', width: 1.5, type: 'dashed' },
label: { formatter: '标准 200', position: 'insideEndTop', color: '#f5222d' }
}
]
}
}]
};
}
},
data() { data() {
return { return {
query:{ query:{
@ -93,6 +233,17 @@ export default {
lists:[], lists:[],
mgruops:[], mgruops:[],
tableData:[], tableData:[],
userFilters:[],
filterUsers:[],
nameFilters:[],
filterNames:[],
visible:false,
userName:'',
boardVisible:false,
boardTitle:'',
boardList:[],
code:'lineDay_p',
activeName:'shengchan',
apiObj: this.$API.system.user.list, apiObj: this.$API.system.user.list,
}; };
}, },
@ -104,6 +255,12 @@ export default {
that.getData(); that.getData();
}, },
methods: { methods: {
tabsClick(){
let that = this;
that.$nextTick(()=>{
that.getData();
})
},
getData(){ getData(){
let that = this; let that = this;
that.lists = []; that.lists = [];
@ -113,10 +270,12 @@ export default {
that.coefficient = []; that.coefficient = [];
let params = {}; let params = {};
params.query = that.query; params.query = that.query;
that.$API.bi.dataset.exec.req('lineDay_p', params).then((res) => { let code = that.activeName=='shengchan'?'lineDay_p':that.activeName=='gongzhuang'?'product_toolings':'product_kaitairen';
that.$API.bi.dataset.exec.req(code, params).then((res) => {
let data = res.data2.ds0; let data = res.data2.ds0;
let coefficient = []; let coefficient = [];
if(data.length>0){ if(data.length>0){
if(that.activeName=='shengchan'){
data.forEach(item=>{ data.forEach(item=>{
let obj = item.工序物料生产数?JSON.parse(item.工序物料生产数):{}; let obj = item.工序物料生产数?JSON.parse(item.工序物料生产数):{};
for(let key in obj){ for(let key in obj){
@ -155,9 +314,41 @@ export default {
item1.time_all = all; item1.time_all = all;
}) })
} }
let userList = [],users = [];
let nameList = [],names = [];
data.forEach(item=>{
if(that.activeName=='gongzhuang'){
if(users.indexOf(item.工装编号)>-1){}else{
users.push(item.工装编号);
userList.push({text:item.工装编号,value:item.工装编号})
}
}else if(that.activeName=='renyuan'){
if(users.indexOf(item.开台人员姓名)>-1){}else{
users.push(item.开台人员姓名);
userList.push({text:item.开台人员姓名,value:item.开台人员姓名})
}
}
if(names.indexOf(item.物料名)>-1){}else{
names.push(item.物料名);
nameList.push({text:item.物料名,value:item.物料名})
}
that.userFilters = userList;
that.nameFilters = nameList;
})
}
that.tableData = data; that.tableData = data;
}); });
}, },
filterUser(value, row) {
return row.工装编号 == value;
},
filterUser3(value, row) {
return row.开台人员姓名 == value;
},
filterName(value, row) {
return row.物料名 == value;
},
xishuChange(){ xishuChange(){
let that = this; let that = this;
that.tableData.forEach((item1,index1) => { that.tableData.forEach((item1,index1) => {
@ -186,6 +377,16 @@ export default {
deptChange(){ deptChange(){
this.getData(); this.getData();
}, },
handleClick(row){
let that = this;
let detail = row.板段明细;
if(typeof detail === 'string'){
try{ detail = JSON.parse(detail); }catch(e){ detail = []; }
}
that.boardList = Array.isArray(detail) ? detail : [];
that.boardTitle = (row.工装编号 || row.开台人员姓名 || '') + ' 板段列表';
that.boardVisible = true;
},
handleQuery(){ handleQuery(){
let that = this; let that = this;
that.getData(); that.getData();
@ -195,4 +396,26 @@ export default {
</script> </script>
<style scoped> <style scoped>
.board-drawer-body {
display: flex;
flex-direction: column;
height: 100%;
gap: 8px;
}
.board-drawer-table {
flex: 0 0 60%;
min-height: 0;
overflow: hidden;
}
.board-drawer-chart {
flex: 0 0 40%;
min-height: 0;
overflow: hidden;
}
.board-chart-scroll {
width: 100%;
height: 100%;
overflow-x: auto;
overflow-y: hidden;
}
</style> </style>

View File

@ -327,7 +327,7 @@
</el-container> </el-container>
</template> </template>
<script> <script lang="jsx">
export default { export default {
name: "statistics_guan", name: "statistics_guan",
data() { data() {

View File

@ -0,0 +1,72 @@
<!--
物料仓库合格库存统计
数据来源: BI 数据集 code=materialCount2 (POST api/bi/dataset/{code}/exec)
抽自 factory_web/src/views/inm/good.vue "物料仓库合格库存统计"抽屉内容
-->
<template>
<el-container v-loading="loading">
<el-header style="display: flex; align-items: center;">
<span style="font-weight: 600;">物料仓库合格库存统计</span>
<el-text style="margin-left: 16px;">仓库总数{{ totalCount }}</el-text>
<el-button type="primary" icon="el-icon-refresh" @click="getProductCount" :loading="loading" style="margin-left: auto;">刷新</el-button>
</el-header>
<el-container>
<el-header style="height: 40%;">
<scTable :data="tableData" hideDo hidePagination row-key="material_name" style="width: 100%;">
<el-table-column label="物料名" prop="material_name"></el-table-column>
<el-table-column label="数量" prop="count"></el-table-column>
</scTable>
</el-header>
<el-main>
<scEcharts :option="echartsOptions" height="100%"></scEcharts>
</el-main>
</el-container>
</el-container>
</template>
<script>
import scEcharts from "@/components/scEcharts/index.vue";
export default {
name: "goodStatistic",
components: { scEcharts },
data() {
return {
loading: false,
tableData: [],
totalCount: 0,
echartsOptions: {},
};
},
mounted() {
this.getProductCount();
},
methods: {
getProductCount() {
let that = this;
let query = {
material_types: "10",
select_material: "material.name as material_name",
groupby_material: "material.name",
};
that.loading = true;
that.$API.bi.dataset.exec
.req("materialCount2", { query: query })
.then((res) => {
let data = (that.tableData = res.data2.ds0 || []);
let totalCount = 0;
data.forEach((item) => {
totalCount += Number(item.count) || 0;
});
that.totalCount = totalCount;
if (res.echart_options) {
that.echartsOptions = JSON.parse(res.echart_options);
}
})
.finally(() => {
that.loading = false;
});
},
},
};
</script>

View File

@ -1,7 +1,7 @@
<!-- <!--
物料保障车间-仓库数量汇总表 物料保障车间-仓库数量汇总表
数据来源: BI 数据集 code=wh_material_summary (POST api/bi/dataset/{code}/exec) 数据来源: BI 数据集 code=wh_material_summary (POST api/bi/dataset/{code}/exec)
返回 res.data2.ds0~ds3, 分组维度为 material.model(型号) 返回 res.data2.ds0~ds4, 分组维度为 material.model(型号)
菜单/路由由后端下发, component statistics/material_wh_summary 菜单/路由由后端下发, component statistics/material_wh_summary
--> -->
<template> <template>
@ -25,15 +25,13 @@
<el-main id="elMain" v-loading="loading"> <el-main id="elMain" v-loading="loading">
<!-- KPI 概览 --> <!-- KPI 概览 -->
<el-row :gutter="15" style="margin-bottom: 15px;"> <div class="kpi-grid">
<el-col :md="6" :sm="12" v-for="k in kpiList" :key="k.label"> <el-card v-for="k in kpiList" :key="k.label" shadow="hover" class="kpi-card" :style="{ borderLeft: '4px solid ' + k.color }">
<el-card shadow="hover" class="kpi-card" :style="{ borderLeft: '4px solid ' + k.color }">
<div class="kpi-label">{{ k.label }} · 现有库存合计</div> <div class="kpi-label">{{ k.label }} · 现有库存合计</div>
<div class="kpi-value" :style="{ color: k.color }">{{ fmtNum(k.total) }}</div> <div class="kpi-value" :style="{ color: k.color }">{{ fmtNum(k.total) }}</div>
<div class="kpi-sub">覆盖 {{ k.count }} 个型号</div> <div class="kpi-sub">覆盖 {{ k.count }} 个型号</div>
</el-card> </el-card>
</el-col> </div>
</el-row>
<!-- 库存量对比图 --> <!-- 库存量对比图 -->
<el-card shadow="never" style="margin-bottom: 15px;"> <el-card shadow="never" style="margin-bottom: 15px;">
@ -103,6 +101,18 @@
</el-table> </el-table>
</el-card> </el-card>
</el-col> </el-col>
<el-col :lg="12">
<el-card shadow="never" class="rpt-card">
<template #header><b>半成品库周数据</b><span class="hd-tip">(入库=采购入库+生产入库 / 出库=生产领料)</span></template>
<el-table :data="ds4" border stripe size="small" show-summary :summary-method="sumRow">
<el-table-column label="型号" prop="型号" width="110" />
<el-table-column label="入库数据" prop="入库数据" align="right" :formatter="numCell" />
<el-table-column label="出库数据" prop="出库数据" align="right" :formatter="numCell" />
<el-table-column label="现有库存" prop="现有库存" align="right" :formatter="numCell" />
</el-table>
</el-card>
</el-col>
</el-row> </el-row>
<!-- 设置安全库存上下限 --> <!-- 设置安全库存上下限 -->
@ -136,6 +146,7 @@ import scEcharts from "@/components/scEcharts/index.vue";
const BLOCKS = [ const BLOCKS = [
{ key: "原料", color: "#409EFF" }, { key: "原料", color: "#409EFF" },
{ key: "成品", color: "#67C23A" }, { key: "成品", color: "#67C23A" },
{ key: "半成品", color: "#B37FEB" },
{ key: "白玻", color: "#E6A23C" }, { key: "白玻", color: "#E6A23C" },
{ key: "黑玻", color: "#909399" }, { key: "黑玻", color: "#909399" },
]; ];
@ -151,6 +162,7 @@ export default {
ds1: [], ds1: [],
ds2: [], ds2: [],
ds3: [], ds3: [],
ds4: [],
safeDialog: { safeDialog: {
visible: false, visible: false,
saving: false, saving: false,
@ -267,6 +279,7 @@ export default {
this.ds1 = d.ds1 || []; this.ds1 = d.ds1 || [];
this.ds2 = d.ds2 || []; this.ds2 = d.ds2 || [];
this.ds3 = d.ds3 || []; this.ds3 = d.ds3 || [];
this.ds4 = d.ds4 || [];
}) })
.finally(() => { .finally(() => {
this.loading = false; this.loading = false;
@ -335,8 +348,19 @@ export default {
</script> </script>
<style scoped> <style scoped>
.kpi-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 15px;
margin-bottom: 15px;
}
@media (max-width: 992px) {
.kpi-grid {
grid-template-columns: repeat(2, 1fr);
}
}
.kpi-card { .kpi-card {
margin-bottom: 8px; margin-bottom: 0;
} }
.kpi-label { .kpi-label {
color: #909399; color: #909399;

View File

@ -37,6 +37,7 @@
<el-main id="elMain"> <el-main id="elMain">
<el-row :gutter="15"> <el-row :gutter="15">
<table class="tables" border="1" cellspacing="0"> <table class="tables" border="1" cellspacing="0">
<tbody>
<tr> <tr>
<th class="tableTh tableHead" colspan="15">库存统计</th> <th class="tableTh tableHead" colspan="15">库存统计</th>
</tr> </tr>
@ -204,10 +205,12 @@
<td class="tableTd">外扫未抛光</td> <td class="tableTd">外扫未抛光</td>
<td class="tableTd"></td> <td class="tableTd"></td>
</tr> </tr>
</tbody>
</table> </table>
</el-row> </el-row>
<el-row :gutter="15"> <el-row :gutter="15">
<table class="tables" border="1" cellspacing="0"> <table class="tables" border="1" cellspacing="0">
<tbody>
<tr> <tr>
<th class="tableTh tableHead" colspan="15">扫边车间数据汇总</th> <th class="tableTh tableHead" colspan="15">扫边车间数据汇总</th>
</tr> </tr>
@ -358,6 +361,7 @@
<td class="tableTd">外扫未抛光</td> <td class="tableTd">外扫未抛光</td>
<td class="tableTd"></td> <td class="tableTd"></td>
</tr> </tr>
</tbody>
</table> </table>
</el-row> </el-row>
</el-main> </el-main>

View File

@ -52,7 +52,7 @@
<template #default="scope"> <template #default="scope">
<span <span
v-if="scope.row.week_esitimate_consume && scope.row.week_esitimate_consume !== null"> v-if="scope.row.week_esitimate_consume && scope.row.week_esitimate_consume !== null">
{{ Math.floor(scope.row.count_mb / scope.row.week_esitimate_consume) * 7 }} {{ Math.floor(scope.row.count / scope.row.week_esitimate_consume) * 7 }}
</span> </span>
</template> </template>
</el-table-column> </el-table-column>
@ -337,12 +337,20 @@ export default {
this.echartsOptions = JSON.parse(res.echart_options); this.echartsOptions = JSON.parse(res.echart_options);
} }
}) })
}else if(that.params.type==30){
this.$API.bi.dataset.exec.req("materialCount2", {query:query2 }).then((res) => {
if (res.echart_options) {
this.echartsOptions = JSON.parse(res.echart_options);
}
})
}else if(that.params.type==40){ }else if(that.params.type==40){
this.$API.bi.dataset.exec.req("helpso_cate", {}).then((res) => { this.$API.bi.dataset.exec.req("helpso_cate", {}).then((res) => {
if (res.echart_options) { if (res.echart_options) {
this.echartsOptions = JSON.parse(res.echart_options); this.echartsOptions = JSON.parse(res.echart_options);
} }
}) })
}else{
that.echartsOptions = null;
} }
}, },
// //

View File

@ -33,17 +33,11 @@
<el-table-column label="版型" prop="版型"> <el-table-column label="版型" prop="版型">
</el-table-column> </el-table-column>
<el-table-column label="7号"> <el-table-column label="7号">
<el-table-column <el-table-column label="合格数" prop="合格数">
label="合格数"
prop="合格数"
>
</el-table-column> </el-table-column>
</el-table-column> </el-table-column>
<el-table-column label="8号"> <el-table-column label="8号">
<el-table-column <el-table-column label="仓库库存" prop="仓库库存">
label="仓库库存"
prop="仓库库存"
>
</el-table-column> </el-table-column>
</el-table-column> </el-table-column>
<el-table-column label="6号"> <el-table-column label="6号">
@ -53,10 +47,7 @@
</el-table-column> </el-table-column>
<el-table-column label="粘头" prop="粘头"> <el-table-column label="粘头" prop="粘头">
</el-table-column> </el-table-column>
<el-table-column <el-table-column label="粗中细" prop="粗中细">
label="粗中细"
prop="粗中细"
>
</el-table-column> </el-table-column>
<el-table-column label="抛光" prop="抛光"> <el-table-column label="抛光" prop="抛光">
</el-table-column> </el-table-column>
@ -93,18 +84,12 @@
<el-table-column label="规格" prop="规格"> <el-table-column label="规格" prop="规格">
</el-table-column> </el-table-column>
<el-table-column label="10号"> <el-table-column label="10号">
<el-table-column <el-table-column label="合格数" prop="合格数">
label="合格数"
prop="合格数"
>
</el-table-column> </el-table-column>
</el-table-column> </el-table-column>
<el-table-column label="8号"> <el-table-column label="8号">
<!-- 仓库库存加车间未退火数 --> <!-- 仓库库存加车间未退火数 -->
<el-table-column <el-table-column label="库存" prop="仓库库存">
label="库存"
prop="仓库库存"
>
</el-table-column> </el-table-column>
<el-table-column label="退火" prop="退火"> <el-table-column label="退火" prop="退火">
</el-table-column> </el-table-column>
@ -281,6 +266,7 @@ export default {
// //
getDataB(data,saleData) { getDataB(data,saleData) {
let that = this; let that = this;
that.tableData11 = [];
let models = [],dataArr = []; let models = [],dataArr = [];
// //
data.forEach((item) => { data.forEach((item) => {
@ -362,14 +348,46 @@ export default {
obj.成品库; obj.成品库;
that.tableData11[index] = obj; that.tableData11[index] = obj;
}); });
saleData.forEach((item) => {
let model = "";
if (item.型号.indexOf("+") > -1) {
model = item.型号.split("+")[0];
} else {
model = item.型号;
}
let index = models.indexOf(model);
if (index > -1) {
that.tableData11[index].检验合格数 += item.数量;
} else {
models.push(model);
that.tableData11.push({
版型: model,
合格数: 0,
仓库库存: 0,
库存: 0,
平头: 0,
粘头: 0,
粗中细: 0,
抛光: 0,
开槽: 0,
配管: 0,
成品库: 0,
合计: 0,
中检合格数: 0,
检验合格数: item.数量,
});
}
})
that.models_b = models; that.models_b = models;
that.getCountOk('b',models); that.getCountOk('b',models);
}, },
// //
getDataG(data,saleData) { getDataG(data,saleData) {
let that = this; let that = this;
that.tableData22 = [];
let models = [], let models = [],
modelArr = []; modelArr = [];
let modelsspec = [];
//modelArr //modelArr
data.forEach((item) => { data.forEach((item) => {
let index = 0; let index = 0;
@ -417,7 +435,6 @@ export default {
}); });
//specArr //specArr
specArr.forEach((item3, index3) => { specArr.forEach((item3, index3) => {
let modelsspec = [];
item3.forEach((item4) => { item3.forEach((item4) => {
let obj = { let obj = {
版型: "", 版型: "",
@ -482,19 +499,42 @@ export default {
}); });
}); });
saleData.forEach((item) => { saleData.forEach((item) => {
let index = 0; let model = "", spec = "";
if (item.型号.indexOf("+") > -1) { if (item.型号 && item.型号.indexOf("+") > -1) {
let model = item.型号.split("+")[1]; model = item.型号.split("+")[1];
index = models.indexOf(model);
} else { } else {
index = models.indexOf(item.型号); model = item.型号 || "";
} }
if (item.规格 && item.规格.indexOf("+") > -1) {
spec = item.规格.split("+")[1];
} else {
spec = item.规格 || "";
}
let msitem = model + spec;
let index = modelsspec.indexOf(msitem);
if (index > -1) { if (index > -1) {
that.tableData22[index].检验合格数=item.数量; that.tableData22[index].检验合格数 += item.数量;
} else {
modelsspec.push(msitem);
that.tableData22.push({
版型: model,
规格: spec,
合格数: 0,
仓库库存: 0,
退火: 0,
库存: 0,
开槽: 0,
倒角: 0,
配棒: 0,
成品库: 0,
合计: 0,
中检合格数: 0,
检验合格数: item.数量,
});
} }
}) })
that.models_g = models; that.models_g = modelsspec;
that.getCountOk('g',models); that.getCountOk('g',modelsspec);
}, },
getSaleOutData(data_b, data_g){ getSaleOutData(data_b, data_g){
let that = this; let that = this;

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<el-dialog <el-dialog append-to-body
title="打印物料标签" title="打印物料标签"
v-model="visible" v-model="visible"
destroy-on-close destroy-on-close

View File

@ -117,6 +117,18 @@
</template> </template>
<script> <script>
const defaultForm = {
field_key: '',
field_name: '',
placeholder: '',
field_type: '',
sort: '',
label: 'name',
default_value: '',
field_template: '',
field_choice: [],
is_hidden: false,
};
export default { export default {
name: "field", name: "field",
data() { data() {
@ -130,18 +142,7 @@
show: '查看' show: '查看'
}, },
// //
addForm: { addForm: Object.assign({}, defaultForm, {field_choice: []}),
field_key: '',
field_name: '',
placeholder: '',
field_type: '',
sort: '',
label: 'name',
default_value: '',
field_template: '',
field_choice: [],
is_hidden: false,
},
// //
rules: { rules: {
name: [ name: [
@ -205,6 +206,8 @@
}, },
handleAdd() { handleAdd() {
this.type = 'add'; this.type = 'add';
this.addForm = Object.assign({}, defaultForm, {field_choice: []});
this.choiceOption = [{id: null, name: ""}];
this.dialogVisible = true; this.dialogVisible = true;
}, },
submitHandle() { submitHandle() {

View File

@ -391,6 +391,15 @@
</template> </template>
<script> <script>
const defaultForm = {
name: "",
is_hidden: false,
sort: "",
type: "",
enable_retreat: false,
participant_type: 0,
distribute_type: 2, //
};
export default { export default {
name: "state", name: "state",
data() { data() {
@ -412,17 +421,10 @@ export default {
stateChange: [], stateChange: [],
fieldList: [], fieldList: [],
// //
addForm: { addForm: Object.assign({}, defaultForm, {
name: "",
is_hidden: false,
sort: "",
type: "",
participant_cc: [], participant_cc: [],
enable_retreat: false,
participant_type: 0,
distribute_type: 2, //
state_fields: {}, // state_fields: {}, //
}, }),
// //
rules: { rules: {
name: [{ required: true, message: "请输入工作流名称" }], name: [{ required: true, message: "请输入工作流名称" }],
@ -568,6 +570,11 @@ export default {
handleAdd() { handleAdd() {
this.getField(); this.getField();
this.type = "add"; this.type = "add";
this.addForm = Object.assign({}, defaultForm, {
participant_cc: [],
state_fields: {},
});
this.stateChange = [];
this.dialogVisible = true; this.dialogVisible = true;
// if(this.dialogInitNum === 0){ // if(this.dialogInitNum === 0){
// this.getUsers(); // this.getUsers();

View File

@ -142,6 +142,13 @@
<script> <script>
// import vueJsonEditor from 'vue-json-editor' // import vueJsonEditor from 'vue-json-editor'
import JsonEditorVue from 'json-editor-vue3' import JsonEditorVue from 'json-editor-vue3'
const defaultForm = {
name: '',
timer: 0,
source_state: '',
destination_state: '',
field_require_check: false,
};
export default { export default {
name: "transform", name: "transform",
components: { JsonEditorVue }, components: { JsonEditorVue },
@ -165,14 +172,7 @@
value: 3, value: 3,
label: '其他' label: '其他'
}], }],
addForm: { addForm: Object.assign({}, defaultForm, {condition_expression: {}}),
name: '',
timer: 0,
source_state: '',
destination_state: '',
condition_expression: {},
field_require_check: false,
},
stateList: [], stateList: [],
dialogVisible: false, dialogVisible: false,
} }
@ -200,6 +200,7 @@
handleAdd() { handleAdd() {
this.getStateList(); this.getStateList();
this.type = 'add'; this.type = 'add';
this.addForm = Object.assign({}, defaultForm, {condition_expression: {}});
this.dialogVisible = true; this.dialogVisible = true;
}, },
handleEdit(row) { handleEdit(row) {

View File

@ -207,6 +207,15 @@
<script> <script>
import degraDialog from "./degraD3_2.vue"; import degraDialog from "./degraD3_2.vue";
const defaultForm = {
name: "",
sn_prefix: "",
description: "",
view_permission_check: true,
display_form_str: [],
title_template: "",
content_template: "",
};
export default { export default {
name: "index", name: "index",
components: { components: {
@ -233,15 +242,7 @@ export default {
}, },
wfCateOptions: [], wfCateOptions: [],
// //
addForm: { addForm: Object.assign({}, defaultForm, { display_form_str: [] }),
name: "",
sn_prefix: "",
description: "",
view_permission_check: true,
display_form_str: [],
title_template: "",
content_template: "",
},
// //
rules: { rules: {
name: [{ required: true, message: "请输入工作流名称" }], name: [{ required: true, message: "请输入工作流名称" }],
@ -264,6 +265,7 @@ export default {
// //
handleAdd() { handleAdd() {
this.type = "add"; this.type = "add";
this.addForm = Object.assign({}, defaultForm, { display_form_str: [] });
this.limitedVisible = true; this.limitedVisible = true;
}, },

View File

@ -915,6 +915,7 @@ export default {
this.imputDisable = true; this.imputDisable = true;
await this.getInputText(data); await this.getInputText(data);
this.imputDisable = false; this.imputDisable = false;
await this.$nextTick();
this.$refs.codeInput.focus(); this.$refs.codeInput.focus();
}, },

View File

@ -286,7 +286,7 @@
@success="handleSaveSuccess" @success="handleSaveSuccess"
@closed="dialog.save = false" @closed="dialog.save = false"
></save-dialog> ></save-dialog>
<el-dialog v-model="printVisible" width="1200px"> <el-dialog append-to-body v-model="printVisible" width="1200px">
<print :baseData="form" :tableData="tableData" :type="form.type" mtype="20" @closePrint="printVisible=false"/> <print :baseData="form" :tableData="tableData" :type="form.type" mtype="20" @closePrint="printVisible=false"/>
</el-dialog> </el-dialog>
<print-dialog <print-dialog

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog <el-dialog append-to-body
:title="titleMap[form.type]" :title="titleMap[form.type]"
v-model="visible" v-model="visible"
:size="1000" :size="1000"

View File

@ -60,7 +60,7 @@
</el-descriptions> </el-descriptions>
<div style="padding: 5px 10px;display: flex;justify-content: end;align-items: center;"> <div style="padding: 5px 10px;display: flex;justify-content: end;align-items: center;">
<div v-if="mlogItem.submit_time == null && hasOkBDefect" style="margin-right: 16px;display:flex;align-items:center;"> <div v-if="mlogItem.submit_time == null && hasOkBDefect" style="margin-right: 16px;display:flex;align-items:center;">
<span style="margin-right: 6px;">合格B类不拆批</span> <span style="margin-right: 6px;">合格B类记为合格</span>
<el-switch v-model="mlogItem.clear_defect"></el-switch> <el-switch v-model="mlogItem.clear_defect"></el-switch>
</div> </div>
<el-button <el-button
@ -276,7 +276,7 @@
</scTable> </scTable>
</el-card> </el-card>
<!-- 输入物料编辑 --> <!-- 输入物料编辑 -->
<el-dialog v-model="saveInDialog" title="编辑"> <el-dialog append-to-body v-model="saveInDialog" title="编辑">
<el-form <el-form
:model="saveInForm" :model="saveInForm"
:rules="rules" :rules="rules"
@ -444,7 +444,7 @@
@closed="dialog.edit = false" @closed="dialog.edit = false"
> >
</edit-dialog> </edit-dialog>
<el-dialog v-model="printVisible" width="1200px"> <el-dialog append-to-body v-model="printVisible" width="1200px">
<print :baseData="mlogItem" :tableData="tableData" :tableData2="tableData2" type="102" @closePrint="printVisible=false"/> <print :baseData="mlogItem" :tableData="tableData" :tableData2="tableData2" type="102" @closePrint="printVisible=false"/>
</el-dialog> </el-dialog>
</div> </div>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog <el-dialog append-to-body
:title="titleMap[mode]" :title="titleMap[mode]"
v-model="visible" v-model="visible"
style="width: 80%;" style="width: 80%;"
@ -60,8 +60,8 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :md="12" :sm="12" :xs="24" v-if="route_code=='ladansi'||route_code=='yicifusi'||route_code=='ercifusi'||route_code=='zlybcl'||route_code=='djg'||route_code=='cnc'"> <el-col :md="12" :sm="12" :xs="24" v-if="base_code=='tcerp'||route_code=='ladansi'||route_code=='yicifusi'||route_code=='ercifusi'||route_code=='zlybcl'||route_code=='djg'||route_code=='cnc'">
<el-form-item label="生产设备" prop="equipment"> <el-form-item label="生产设备" :prop="base_code!=='tcerp' ? 'equipment' : null">
<el-select <el-select
v-model="form.equipment" v-model="form.equipment"
placeholder="生产设备" placeholder="生产设备"
@ -315,6 +315,7 @@ export default {
mtaskOptions:[], mtaskOptions:[],
supplierOptions: [], supplierOptions: [],
setFiltersVisible: false, setFiltersVisible: false,
base_code: this.$TOOL.data.get('BASE_INFO').base.base_code,
}; };
}, },
mounted() { mounted() {

View File

@ -18,7 +18,7 @@
style="padding: 0 10px" style="padding: 0 10px"
> >
<el-row> <el-row>
<el-col :span="project_code!=='bxerp'?22:24"> <el-col :span="scanColSpan">
<el-form-item label="扫码:"> <el-form-item label="扫码:">
<el-input <el-input
class="inputWrap" class="inputWrap"
@ -34,6 +34,9 @@
<el-col :span="2" v-if="project_code!=='bxerp'"> <el-col :span="2" v-if="project_code!=='bxerp'">
<scScanner style="top: -7px" @scanResult="codeTextChange"></scScanner> <scScanner style="top: -7px" @scanResult="codeTextChange"></scScanner>
</el-col> </el-col>
<el-col :span="4" v-if="route_code=='niuzhuan'">
<el-button type="success" @click="digital" style="margin-top: 3px;margin-left: 5px;">数字识别</el-button>
</el-col>
</el-row> </el-row>
<el-row> <el-row>
<el-col :md="12" :sm="12" :xs="24"> <el-col :md="12" :sm="12" :xs="24">
@ -63,8 +66,8 @@
<el-input-number v-model="form.count_use" :disabled="materialTracking==20" style="width: 100%;"></el-input-number> <el-input-number v-model="form.count_use" :disabled="materialTracking==20" style="width: 100%;"></el-input-number>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :md="12" :sm="12" :xs="24" v-if="route_code=='ladansi'||route_code=='yicifusi'||route_code=='ercifusi'||route_code=='zlybcl'||route_code=='djg'||route_code=='cnc'"> <el-col :md="12" :sm="12" :xs="24" v-if="base_code=='tcerp'||route_code=='ladansi'||route_code=='yicifusi'||route_code=='ercifusi'||route_code=='zlybcl'||route_code=='djg'||route_code=='cnc'">
<el-form-item label="生产设备" prop="equipment"> <el-form-item label="生产设备" :prop="base_code!=='tcerp' ? 'equipment' : null">
<el-select <el-select
v-model="form.equipment" v-model="form.equipment"
placeholder="生产设备" placeholder="生产设备"
@ -230,7 +233,15 @@ export default {
} }
}, },
emits: ["success", "closed"], emits: ["success", "closed"],
computed: {
scanColSpan(){
let span = this.project_code!=='bxerp' ? 22 : 24;
if(this.route_code=='niuzhuan'){
span -= 4;
}
return span;
},
},
data() { data() {
return { return {
loading: false, loading: false,
@ -266,6 +277,9 @@ export default {
imageUrl:'', imageUrl:'',
test_file:'', test_file:'',
project_code:'', project_code:'',
ocr_ip:"",
ocr_port:"",
ocr_unit:"",
visible: false, visible: false,
isSaveing: false, isSaveing: false,
batchCountCando:0, batchCountCando:0,
@ -278,6 +292,7 @@ export default {
routeOptions: [], routeOptions: [],
materialOptions: [], materialOptions: [],
routeOriginOptions: [], routeOriginOptions: [],
base_code: this.$TOOL.data.get('BASE_INFO').base.base_code,
}; };
}, },
mounted() { mounted() {
@ -309,6 +324,9 @@ export default {
}); });
}); });
},500) },500)
that.ocr_ip = localStorage.getItem("ocr_ip") || "";
that.ocr_port = localStorage.getItem("ocr_port") || "";
that.ocr_unit = localStorage.getItem("ocr_unit") || "";
}, },
methods: { methods: {
// //
@ -351,6 +369,29 @@ export default {
this.wm_in = text; this.wm_in = text;
this.formWminChange(text); this.formWminChange(text);
}, },
digital(){
let that = this;
if(!that.ocr_ip || !that.ocr_port || !that.ocr_unit){
that.$message.warning('请先在右上角"数字识别设置"配置IP、端口号和位号');
return;
}
that.$API.em.equipment.get_svs_char.req({
host: that.ocr_ip,
port: that.ocr_port,
flow_unit: that.ocr_unit
}).then((res) => {
if(res.char){
that.wm_in = res.char;
that.formWminChange(res.char);
}
}).catch((err) => {
that.$notify.error({
title: '数字识别获取失败',
message: err.data
})
return err;
});
},
// //
open(mode = "add") { open(mode = "add") {
let that = this; let that = this;

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog <el-dialog append-to-body
title="过程检验" title="过程检验"
v-model="visible" v-model="visible"
:size="1000" :size="1000"

View File

@ -1,6 +1,6 @@
<!-- 新增mlogbIn --> <!-- 新增mlogbIn -->
<template> <template>
<el-dialog <el-dialog append-to-body
title="添加日志详情" title="添加日志详情"
v-model="visible" v-model="visible"
:size="1000" :size="1000"

View File

@ -274,7 +274,7 @@
</sc-form-table> </sc-form-table>
</el-main> </el-main>
</el-container> </el-container>
<el-dialog <el-dialog append-to-body
title="检验" title="检验"
v-model="checkVisible" v-model="checkVisible"
destroy-on-close destroy-on-close
@ -378,7 +378,7 @@
</el-main> </el-main>
</el-container> </el-container>
</el-dialog> </el-dialog>
<el-dialog <el-dialog append-to-body
title="批量检验" title="批量检验"
v-model="setVisible" v-model="setVisible"
destroy-on-close destroy-on-close

View File

@ -68,7 +68,7 @@
<el-button type="primary" @click="mtaskb_submit">提交</el-button> <el-button type="primary" @click="mtaskb_submit">提交</el-button>
</div> </div>
</div> </div>
<el-dialog v-model="printVisible" width="1200px"> <el-dialog append-to-body v-model="printVisible" width="1200px">
<print :baseData="mtaskObj" type="mtask" @closePrint="printVisible=false"/> <print :baseData="mtaskObj" type="mtask" @closePrint="printVisible=false"/>
</el-dialog> </el-dialog>
</el-drawer> </el-drawer>

View File

@ -87,12 +87,21 @@
> >
<template #default="scope"> <template #default="scope">
<el-button <el-button
v-if="scope.row.submit_time!==null"
link size="small" link size="small"
@click="check_revert(scope.row)" @click="check_revert(scope.row)"
v-auth="'ftestwork.create'" v-auth="'ftestwork.create'"
type="primary" type="primary"
>撤回 >撤回
</el-button> </el-button>
<el-button
v-else
link size="small"
@click="check_delete(scope.row)"
v-auth="'ftestwork.create'"
type="danger"
>删除
</el-button>
</template> </template>
</el-table-column> </el-table-column>
</scTable> </scTable>
@ -159,7 +168,17 @@ export default {
}); });
}).catch(() => {}); }).catch(() => {});
}, },
check_delete(row){
let that = this;
this.$confirm(`确定删除该检验吗?`, "提示", {
type: "warning",
}).then(() => {
that.$API.qm.ftestwork.delete.req(row.id).then((res) => {
this.$refs.table.refresh();
})
})
},
handleQuery() { handleQuery() {
this.$refs.table.queryData(this.query); this.$refs.table.queryData(this.query);
}, },

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog <el-dialog append-to-body
:title="titleMap[mode]" :title="titleMap[mode]"
v-model="visible" v-model="visible"
destroy-on-close destroy-on-close

View File

@ -116,7 +116,7 @@
@closed="dialog.check = false" @closed="dialog.check = false"
> >
</check-dialog> </check-dialog>
<el-dialog v-model="printVisible" width="1200px"> <el-dialog append-to-body v-model="printVisible" width="1200px">
<print :baseData="fmlogItem" :tableData="tableData" type="101" @closePrint="printVisible=false"/> <print :baseData="fmlogItem" :tableData="tableData" type="101" @closePrint="printVisible=false"/>
</el-dialog> </el-dialog>
</div> </div>

View File

@ -310,7 +310,7 @@
@success="handleSaveSuccess" @success="handleSaveSuccess"
@closed="dialog.save = false" @closed="dialog.save = false"
></save-dialog> ></save-dialog>
<el-dialog v-model="printVisible" width="1200px"> <el-dialog append-to-body v-model="printVisible" width="1200px">
<print :baseData="form" :tableData="tableData" :type="form.type" mtype="20" @closePrint="printVisible=false"/> <print :baseData="form" :tableData="tableData" :type="form.type" mtype="20" @closePrint="printVisible=false"/>
</el-dialog> </el-dialog>
<print-dialog <print-dialog

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog <el-dialog append-to-body
:title="titleMap[form.type]" :title="titleMap[form.type]"
v-model="visible" v-model="visible"
style="width: 80%;" style="width: 80%;"

View File

@ -64,7 +64,7 @@
<div style="padding: 5px 10px;display: flex;justify-content: end;align-items: center;"> <div style="padding: 5px 10px;display: flex;justify-content: end;align-items: center;">
<!-- hasOkBDefect --> <!-- hasOkBDefect -->
<div v-if="mlogItem.submit_time == null" style="margin-right: 16px;display:flex;align-items:center;"> <div v-if="mlogItem.submit_time == null" style="margin-right: 16px;display:flex;align-items:center;">
<span style="margin-right: 6px;">合格B类不拆批</span> <span style="margin-right: 6px;">合格B类记为合格</span>
<el-switch v-model="mlogItem.clear_defect"></el-switch> <el-switch v-model="mlogItem.clear_defect"></el-switch>
</div> </div>
<el-button <el-button
@ -168,7 +168,7 @@
</scTable> </scTable>
</el-card> </el-card>
<!-- 输入物料编辑 --> <!-- 输入物料编辑 -->
<el-dialog v-model="saveInDialog" title="编辑"> <el-dialog append-to-body v-model="saveInDialog" title="编辑">
<el-form <el-form
:model="saveInForm" :model="saveInForm"
:rules="rules" :rules="rules"
@ -359,7 +359,7 @@
@closed="dialog.edit = false" @closed="dialog.edit = false"
> >
</edit-dialog> </edit-dialog>
<el-dialog v-model="printVisible" width="1200px"> <el-dialog append-to-body v-model="printVisible" width="1200px">
<print :baseData="mlogItem" :tableData="tableData" :tableData2="tableData2" type="102" @closePrint="printVisible=false"/> <print :baseData="mlogItem" :tableData="tableData" :tableData2="tableData2" type="102" @closePrint="printVisible=false"/>
</el-dialog> </el-dialog>
<print-dialog <print-dialog

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog <el-dialog append-to-body
:title="titleMap[mode]" :title="titleMap[mode]"
v-model="visible" v-model="visible"
destroy-on-close destroy-on-close

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog <el-dialog append-to-body
title="过程检验" title="过程检验"
v-model="visible" v-model="visible"
style="width: 80%;" style="width: 80%;"

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog <el-dialog append-to-body
:title="titleMap[mode]" :title="titleMap[mode]"
v-model="visible" v-model="visible"
style="width: 80%;" style="width: 80%;"

View File

@ -68,7 +68,7 @@
<el-button type="primary" @click="mtaskb_submit">提交</el-button> <el-button type="primary" @click="mtaskb_submit">提交</el-button>
</div> </div>
</div> </div>
<el-dialog v-model="printVisible" width="1200px"> <el-dialog append-to-body v-model="printVisible" width="1200px">
<print :baseData="mtaskObj" type="mtask" @closePrint="printVisible=false"/> <print :baseData="mtaskObj" type="mtask" @closePrint="printVisible=false"/>
</el-dialog> </el-dialog>
</el-drawer> </el-drawer>