feat: 修复订单BOM树节点定位错误问题,添加BASE_DEVICE_PARAM_VAL旁路能力改造方案文档和功能

master
zangch@mesnac.com 6 days ago
parent d5c79f7a12
commit 3ea71df714

@ -34,7 +34,6 @@ export function mixData(params,customType=1) {
}
if (type === 3) {
getFoamingData({
"PRODUCT_LINE_CODE": "CX_02"
}).then(val2 => {
(params?.f || (() => {
}))(screenData(val2) || [])

@ -0,0 +1,39 @@
import request from '@/utils/request'
export function listOrderNote(query) {
return request({
url: '/production/orderNote/list',
method: 'get',
params: query
})
}
export function getOrderNote(objId) {
return request({
url: '/production/orderNote/' + objId,
method: 'get'
})
}
export function addOrderNote(data) {
return request({
url: '/production/orderNote',
method: 'post',
data: data
})
}
export function updateOrderNote(data) {
return request({
url: '/production/orderNote',
method: 'put',
data: data
})
}
export function delOrderNote(objId) {
return request({
url: '/production/orderNote/' + objId,
method: 'delete'
})
}

@ -0,0 +1,39 @@
import request from '@/utils/request'
export function listProcessRoute(query) {
return request({
url: '/production/processRoute/list',
method: 'get',
params: query
})
}
export function getProcessRoute(objId) {
return request({
url: '/production/processRoute/' + objId,
method: 'get'
})
}
export function addProcessRoute(data) {
return request({
url: '/production/processRoute',
method: 'post',
data: data
})
}
export function updateProcessRoute(data) {
return request({
url: '/production/processRoute',
method: 'put',
data: data
})
}
export function delProcessRoute(objId) {
return request({
url: '/production/processRoute/' + objId,
method: 'delete'
})
}

@ -0,0 +1,39 @@
import request from '@/utils/request'
export function listStationCapability(query) {
return request({
url: '/production/stationCapability/list',
method: 'get',
params: query
})
}
export function getStationCapability(objId) {
return request({
url: '/production/stationCapability/' + objId,
method: 'get'
})
}
export function addStationCapability(data) {
return request({
url: '/production/stationCapability',
method: 'post',
data: data
})
}
export function updateStationCapability(data) {
return request({
url: '/production/stationCapability',
method: 'put',
data: data
})
}
export function delStationCapability(objId) {
return request({
url: '/production/stationCapability/' + objId,
method: 'delete'
})
}

@ -0,0 +1,47 @@
import request from '@/utils/request'
export function listTaskPool(query) {
return request({
url: '/production/taskPool/list',
method: 'get',
params: query
})
}
export function getTaskPool(orderCode) {
return request({
url: '/production/taskPool/' + orderCode,
method: 'get'
})
}
export function addTaskPool(data) {
return request({
url: '/production/taskPool',
method: 'post',
data: data
})
}
export function updateTaskPool(data) {
return request({
url: '/production/taskPool',
method: 'put',
data: data
})
}
export function changeTaskStatus(data) {
return request({
url: '/production/taskPool/changeStatus',
method: 'put',
data: data
})
}
export function delTaskPool(orderCode) {
return request({
url: '/production/taskPool/' + orderCode,
method: 'delete'
})
}

@ -0,0 +1,39 @@
import request from '@/utils/request'
export function listTeamShift(query) {
return request({
url: '/production/teamShift/list',
method: 'get',
params: query
})
}
export function getTeamShift(objId) {
return request({
url: '/production/teamShift/' + objId,
method: 'get'
})
}
export function addTeamShift(data) {
return request({
url: '/production/teamShift',
method: 'post',
data: data
})
}
export function updateTeamShift(data) {
return request({
url: '/production/teamShift',
method: 'put',
data: data
})
}
export function delTeamShift(objId) {
return request({
url: '/production/teamShift/' + objId,
method: 'delete'
})
}

@ -333,8 +333,8 @@ export const dynamicRoutes = [
permissions: ["base:orderBomInfo:list"],
children: [
{
// 订单BOM与生产BOM共用物料编码规则这里同样放开参数约束避免同类跳转问题再次出现
path: "index/:materialCode",
// 子BOM页面必须带具体节点主键避免同一物料编码挂在不同父节点下时串树
path: "index/:objId(\\d+)",
component: () => import("@/views/base/orderBomInfo/childIndex"),
name: "childBom",
meta: {title: "查看订单子BOM信息", activeMenu: "/base/orderBomInfo"},

@ -59,7 +59,7 @@
v-if="refreshTable"
v-loading="loading"
:data="orderBomInfoList"
row-key="materialCode"
row-key="objId"
:default-expand-all="isExpandAll"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
>
@ -124,8 +124,8 @@
<!-- 添加或修改订单BOM对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="父级编号" prop="parentId">
<treeselect v-model="form.parentId" :options="orderBomInfoOptions" :normalizer="normalizer" placeholder="请选择父级编号" />
<el-form-item label="父级编号" prop="parentObjId">
<treeselect v-model="form.parentObjId" :options="orderBomInfoOptions" :normalizer="normalizer" placeholder="请选择父级编号" @input="handleParentChange" />
</el-form-item>
<el-form-item label="BOM编号" prop="bomCode">
<el-input v-model="form.bomCode" placeholder="请输入BOM编号" />
@ -220,6 +220,10 @@ export default {
orderBomInfoList: [],
// BOM
orderBomInfoOptions: [],
// right-toolbar undefined
columns: [],
//
allOrderBomNodes: [],
//
materialOptions: [],
//
@ -230,6 +234,12 @@ export default {
isExpandAll: true,
//
refreshTable: true,
//
currentRootObjId: null,
//
currentRootMaterialCode: null,
//
currentRootAncestors: null,
//
queryParams: {
bomCode: null,
@ -237,6 +247,7 @@ export default {
materialName: null,
materialType: null,
standardAmount: null,
parentObjId: null,
parentId: null,
isFlag: null,
createdBy: null,
@ -257,36 +268,102 @@ export default {
};
},
created() {
this.queryParams.ancestors = this.$route.params && this.$route.params.materialCode;
this.currentRootObjId = Number(this.$route.params && this.$route.params.objId) || null;
this.currentRootMaterialCode = this.$route.query && this.$route.query.materialCode;
this.queryParams.ancestors = this.currentRootMaterialCode;
this.loadMaterialOptions();
this.getList();
this.initCurrentRoot();
},
methods: {
/** 先定位当前根节点的完整祖级链,再查询当前这一棵子树,避免同编码分支串树 */
initCurrentRoot() {
if (!this.currentRootObjId) {
this.getList();
return;
}
getOrderBomInfo(this.currentRootObjId).then(response => {
const rootNode = response.data || {};
this.currentRootMaterialCode = rootNode.materialCode || this.currentRootMaterialCode;
this.currentRootAncestors = rootNode.ancestors || rootNode.materialCode || null;
this.queryParams.ancestors = this.currentRootMaterialCode;
this.getList();
});
},
/** 查询订单BOM列表 */
getList() {
this.loading = true;
findOrderBomList(this.queryParams).then(response => {
this.orderBomInfoList = this.handleTree(response.data, "materialCode", "parentId");
const normalizedList = this.decorateTreeNodes(this.filterCurrentSubtree(response.data || []));
this.orderBomInfoList = this.handleTree(normalizedList, "objId", "treeParentObjId");
this.loading = false;
});
},
filterCurrentSubtree(list) {
if (!this.currentRootAncestors) {
return list;
}
return (list || []).filter(item => {
const ancestors = item.ancestors || item.materialCode || "";
return ancestors === this.currentRootAncestors || ancestors.indexOf(this.currentRootAncestors + ",") === 0;
});
},
/** 转换订单BOM数据结构 */
normalizer(node) {
if (node.children && !node.children.length) {
delete node.children;
}
return {
id: node.materialCode,
label: node.materialName,
id: node.objId,
label: (node.materialCode || "") + " / " + (node.materialName || ""),
children: node.children
};
},
/** 查询订单BOM下拉树结构 */
/**
* 把后端返回的编码路径补成节点主键路径
*
* 这里保留后端现有的 ancestors/materialCode 模型同时在前端补出 treeParentObjId
* 让树控件和路由都基于 objId 工作避免同编码物料在不同分支下串树
*/
decorateTreeNodes(list) {
const nodes = (list || []).map(item => ({ ...item }));
const identityMap = new Map();
nodes.forEach(item => {
identityMap.set(this.buildNodeIdentity(item.materialCode, item.ancestors), item.objId);
});
this.allOrderBomNodes = nodes.map(item => ({
...item,
treeParentObjId: this.resolveTreeParentObjId(item, identityMap)
}));
return this.allOrderBomNodes;
},
buildNodeIdentity(materialCode, ancestors) {
return `${materialCode || ""}@@${ancestors || ""}`;
},
resolveTreeParentObjId(item, identityMap) {
if (!item.parentId || item.parentId === "0") {
return 0;
}
if (item.parentObjId && item.parentObjId > 0) {
return item.parentObjId;
}
const parentAncestors = this.resolveParentAncestors(item);
return identityMap.get(this.buildNodeIdentity(item.parentId, parentAncestors)) || 0;
},
resolveParentAncestors(item) {
const ancestors = (item.ancestors || "").split(",").filter(Boolean);
if (ancestors.length <= 1) {
return "";
}
ancestors.pop();
return ancestors.join(",");
},
/** 查询订单BOM下拉树结构 */
getTreeselect() {
findOrderBomList().then(response => {
findOrderBomList().then(response => {
const normalizedList = this.decorateTreeNodes(response.data || []);
this.orderBomInfoOptions = [];
const data = { materialCode: 0, materialName: '顶级节点', children: [] };
data.children = this.handleTree(response.data, "materialCode", "parentId");
const data = { objId: 0, materialCode: "0", materialName: "顶级节点", children: [] };
data.children = this.handleTree(normalizedList, "objId", "treeParentObjId");
this.orderBomInfoOptions.push(data);
});
},
@ -314,6 +391,16 @@ export default {
this.form.bomCode = selectedMaterial.materialCode;
}
},
/** 选择父节点后同步回填父级物料编码,兼容历史接口仍按 parentId 查询 */
handleParentChange(parentObjId) {
if (!parentObjId) {
this.form.parentObjId = 0;
this.form.parentId = "0";
return;
}
const parentNode = this.allOrderBomNodes.find(item => item.objId === parentObjId);
this.form.parentId = parentNode ? parentNode.materialCode : null;
},
//
cancel() {
this.open = false;
@ -328,6 +415,7 @@ export default {
materialName: null,
materialType: null,
standardAmount: null,
parentObjId: 0,
parentId: null,
isFlag: 0,
createdBy: null,
@ -355,10 +443,13 @@ export default {
handleAdd(row) {
this.reset();
this.getTreeselect();
if (row != null && row.materialCode) {
this.form.parentId = row.materialCode;
const parentNode = row && row.objId ? row : this.allOrderBomNodes.find(item => item.objId === this.currentRootObjId);
if (parentNode) {
this.form.parentObjId = parentNode.objId;
this.form.parentId = parentNode.materialCode;
} else {
this.form.parentId = 0;
this.form.parentObjId = 0;
this.form.parentId = "0";
}
this.open = true;
this.title = "添加订单BOM";
@ -380,11 +471,13 @@ export default {
handleUpdate(row) {
this.reset();
this.getTreeselect();
if (row != null) {
this.form.parentId = row.parentId;
}
getOrderBomInfo(row.objId).then(response => {
this.form = response.data;
if ((!this.form.parentObjId || this.form.parentObjId <= 0) && row && row.treeParentObjId >= 0) {
// parentObjId
this.form.parentObjId = row.treeParentObjId || 0;
}
this.handleParentChange(this.form.parentObjId);
this.handleMaterialChange(this.form.materialCode);
this.open = true;
this.title = "修改订单BOM";

@ -1,32 +1,24 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="90px">
<!-- <el-form-item label="BOM编号" prop="bomCode">-->
<!-- <el-input-->
<!-- v-model="queryParams.bomCode"-->
<!-- placeholder="请输入BOM编号"-->
<!-- clearable-->
<!-- @keyup.enter.native="handleQuery"-->
<!-- />-->
<!-- </el-form-item>-->
<el-form-item label="子物料编号" prop="materialCode">
<el-form ref="queryForm" :model="queryParams" size="small" :inline="true" v-show="showSearch" label-width="90px">
<el-form-item label="节点物料编码" prop="materialCode">
<el-input
v-model="queryParams.materialCode"
placeholder="请输入子物料编号"
placeholder="请输入节点物料编码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="物料名称" prop="materialName">
<el-form-item label="节点物料名称" prop="materialName">
<el-input
v-model="queryParams.materialName"
placeholder="请输入物料名称"
placeholder="请输入节点物料名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="物料小类" prop="materialType">
<el-select v-model="queryParams.materialType" placeholder="请选择物料小类" clearable>
<el-form-item label="节点物料小类" prop="materialType">
<el-select v-model="queryParams.materialType" placeholder="请选择节点物料小类" clearable>
<el-option
v-for="dict in dict.type.material_subclass"
:key="dict.value"
@ -34,14 +26,6 @@
:value="dict.value"
/>
</el-select>
<el-form-item label="父物料编号" prop="parentId">
<el-input
v-model="queryParams.parentId"
placeholder="请输入父物料编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
@ -52,167 +36,142 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
v-hasPermi="['base:orderBomInfo:add']"
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['base:orderBomInfo:add']"
>手动新增BOM
</el-button>
@click="handleAdd()"
>新增根节点</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
type="info"
plain
icon="el-icon-edit"
icon="el-icon-sort"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['base:orderBomInfo:edit']"
>修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['base:orderBomInfo:remove']"
>删除
</el-button>
@click="toggleExpandAll"
>展开/折叠</el-button>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['base:orderBomInfo:export']"
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['base:orderBomInfo:export']"
>导出
</el-button>
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
<right-toolbar :showSearch.sync="showSearch" :columns="columns" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="orderBomInfoList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center"/>
<el-table-column label="主键标识" align="center" prop="objId" v-if="columns[0].visible"/>
<el-table-column label="BOM编号" align="center" prop="bomCode" v-if="columns[1].visible"/>
<el-table-column label="子物料编号" align="center" prop="materialCode" v-if="columns[2].visible">
<el-table
v-if="refreshTable"
v-loading="loading"
:data="orderBomInfoList"
row-key="objId"
:default-expand-all="isExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column label="节点物料编码" prop="materialCode" align="left" v-if="columns[0].visible" min-width="180" />
<el-table-column label="节点物料名称" prop="materialName" align="left" v-if="columns[1].visible" min-width="220" />
<el-table-column label="节点物料小类" prop="materialType" align="center" v-if="columns[2].visible" width="130">
<template slot-scope="scope">
<router-link :to="'/base/bom-info/index/' + scope.row.materialCode" class="link-type">
<span>{{ scope.row.materialCode }}</span>
</router-link>
<dict-tag :options="dict.type.material_subclass" :value="scope.row.materialType" />
</template>
</el-table-column>
<el-table-column label="子物料名称" align="center" prop="materialName" v-if="columns[3].visible"/>
<el-table-column label="子物料小类" align="center" prop="materialType" v-if="columns[4].visible">
<template slot-scope="scope">
<dict-tag :options="dict.type.material_subclass" :value="scope.row.materialType"/>
</template>
</el-table-column>
<el-table-column label="标准数量" align="center" prop="standardAmount" v-if="columns[5].visible"/>
<el-table-column label="父级物料编号" align="center" prop="parentId" v-if="columns[6].visible"/>
<el-table-column label="父级物料名称" align="center" prop="parentName" v-if="columns[16].visible"/>
<el-table-column label="父级物料小类" align="center" prop="parentMaterialType" v-if="columns[17].visible">
<template slot-scope="scope">
<dict-tag :options="dict.type.material_subclass" :value="scope.row.parentMaterialType"/>
</template>
</el-table-column>
<el-table-column label="是否标识" align="center" prop="isFlag" v-if="columns[7].visible"/>
<el-table-column label="创建人" align="center" prop="createdBy" v-if="columns[8].visible"/>
<el-table-column label="创建时间" align="center" prop="createdTime" width="180" v-if="columns[9].visible">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createdTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="更新人" align="center" prop="updatedBy" v-if="columns[10].visible"/>
<el-table-column label="更新时间" align="center" prop="updatedTime" width="180" v-if="columns[11].visible">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.updatedTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="工厂编号" align="center" prop="factoryCode" v-if="columns[12].visible"/>
<el-table-column label="排序" align="center" prop="sort" v-if="columns[13].visible"/>
<el-table-column label="销售凭证" align="center" prop="vbeln" v-if="columns[14].visible"/>
<el-table-column label="销售单据项目" align="center" prop="vbpos" v-if="columns[15].visible"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<el-table-column label="标准数量" prop="standardAmount" align="center" v-if="columns[3].visible" width="100" />
<el-table-column label="父节点编码" prop="parentId" align="center" v-if="columns[4].visible" width="160" />
<el-table-column label="父节点名称" prop="parentName" align="left" v-if="columns[5].visible" min-width="180" />
<el-table-column label="工厂编号" prop="factoryCode" align="center" v-if="columns[6].visible" width="120" />
<el-table-column label="排序" prop="sort" align="center" v-if="columns[7].visible" width="80" />
<el-table-column label="销售凭证" prop="vbeln" align="center" v-if="columns[8].visible" width="120" />
<el-table-column label="销售单据项目" prop="vbpos" align="center" v-if="columns[9].visible" width="120" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="260">
<template slot-scope="scope">
<el-button
v-hasPermi="['base:orderBomInfo:add']"
size="mini"
type="text"
icon="el-icon-plus"
@click="handleAdd(scope.row)"
>新增下级</el-button>
<el-button
v-hasPermi="['base:orderBomInfo:edit']"
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['base:orderBomInfo:edit']"
>修改
</el-button>
<!-- <el-button-->
<!-- size="mini"-->
<!-- type="text"-->
<!-- icon="el-icon-delete"-->
<!-- @click="handleDelete(scope.row)"-->
<!-- v-hasPermi="['base:orderBomInfo:remove']"-->
<!-- >删除-->
<!-- </el-button>-->
>修改</el-button>
<el-button
v-hasPermi="['base:orderBomInfo:remove']"
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改订单BOM对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-dialog :title="title" :visible.sync="open" width="560px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<!-- <el-form-item label="BOM编号" prop="bomCode">-->
<!-- <el-input v-model="form.bomCode" placeholder="请输入BOM编号"/>-->
<!-- </el-form-item>-->
<el-form-item label="子物料编号" prop="materialCode">
<el-input v-model="form.materialCode" placeholder="请输入子物料编号"/>
<el-form-item label="父节点" prop="parentObjId">
<treeselect
v-model="form.parentObjId"
:options="orderBomInfoOptions"
:normalizer="normalizer"
placeholder="请选择父节点"
@input="handleParentChange"
/>
</el-form-item>
<el-form-item label="BOM编号" prop="bomCode">
<el-input v-model="form.bomCode" placeholder="请输入BOM编号" />
</el-form-item>
<el-form-item label="节点物料编码" prop="materialCode">
<el-select
v-model="form.materialCode"
filterable
clearable
placeholder="请选择物料编码"
@change="handleMaterialChange"
>
<el-option
v-for="item in materialOptions"
:key="item.materialCode"
:label="item.materialCode + ' / ' + item.materialName"
:value="item.materialCode"
/>
</el-select>
</el-form-item>
<el-form-item label="节点物料名称" prop="materialName">
<el-input v-model="form.materialName" placeholder="选择物料后自动带出" disabled />
</el-form-item>
<el-form-item label="节点物料小类" prop="materialType">
<el-select v-model="form.materialType" placeholder="节点物料小类" disabled>
<el-option
v-for="dict in dict.type.material_subclass"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<!-- <el-form-item label="子物料名称" prop="materialName">-->
<!-- <el-input v-model="form.materialName" placeholder="请输入子物料名称"/>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="子物料小类" prop="materialType">-->
<!-- <el-select v-model="form.materialType" placeholder="请选择子物料小类">-->
<!-- <el-option-->
<!-- v-for="dict in dict.type.material_type"-->
<!-- :key="dict.value"-->
<!-- :label="dict.label"-->
<!-- :value="dict.value"-->
<!-- ></el-option>-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<el-form-item label="标准数量" prop="standardAmount">
<el-input v-model="form.standardAmount" placeholder="请输入标准数量"/>
<el-input v-model="form.standardAmount" placeholder="请输入标准数量" />
</el-form-item>
<el-form-item label="父级物料编号" prop="parentId">
<el-input v-model="form.parentId" placeholder="请输入父级物料编号"/>
</el-form-item>
<!-- <el-form-item label="是否标识" prop="isFlag">-->
<!-- <el-input v-model="form.isFlag" placeholder="请输入是否标识"/>-->
<!-- </el-form-item>-->
<el-form-item label="工厂编号" prop="factoryCode">
<el-input v-model="form.factoryCode" placeholder="请输入工厂编号"/>
<el-input v-model="form.factoryCode" placeholder="选择物料后自动带出" disabled />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="form.sort" placeholder="请输入排序"/>
<el-input v-model="form.sort" placeholder="请输入排序" />
</el-form-item>
<el-form-item label="销售凭证" prop="vbeln">
<el-input v-model="form.vbeln" placeholder="请输入销售凭证"/>
<el-input v-model="form.vbeln" placeholder="请输入销售凭证" />
</el-form-item>
<el-form-item label="销售单据项目" prop="vbpos">
<el-input v-model="form.vbpos" placeholder="请输入销售单据项目"/>
<el-input v-model="form.vbpos" placeholder="请输入销售单据项目" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
@ -225,101 +184,152 @@
<script>
import {
listOrderBomInfo,
getOrderBomInfo,
delOrderBomInfo,
addOrderBomInfo,
updateOrderBomInfo
updateOrderBomInfo,
findOrderBomList
} from '@/api/base/orderBomInfo'
import { listAllMaterialInfo } from '@/api/base/materialInfo'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
export default {
name: 'OrderBomInfo',
dicts: ['material_type','material_subclass'],
dicts: ['material_subclass'],
components: {
Treeselect
},
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
// BOM
orderBomInfoList: [],
//
orderBomInfoOptions: [],
allOrderBomNodes: [],
materialOptions: [],
title: '',
//
open: false,
//
isExpandAll: true,
refreshTable: true,
queryParams: {
pageNum: 1,
pageSize: 10,
bomCode: null,
materialCode: null,
materialName: null,
materialType: null,
standardAmount: null,
parentId: null,
isFlag: null,
createdBy: null,
createdTime: null,
updatedBy: null,
updatedTime: null,
factoryCode: null,
sort: null,
vbeln: null,
vbpos: null
parentId: null
},
//
form: {},
//
rules: {},
columns: [
{ key: 0, label: `主键标识`, visible: false },
{ key: 1, label: `BOM编号`, visible: false },
{ key: 2, label: `子物料编号`, visible: true },
{ key: 3, label: `子物料名称`, visible: true },
{ key: 4, label: `子物料小类`, visible: true },
{ key: 5, label: `标准数量`, visible: true },
{ key: 6, label: `父级物料编号`, visible: true },
{ key: 7, label: `是否标识`, visible: false },
{ key: 8, label: `创建人`, visible: false },
{ key: 9, label: `创建时间`, visible: false },
{ key: 10, label: `更新人`, visible: false },
{ key: 11, label: `更新时间`, visible: false },
{ key: 12, label: `工厂编号`, visible: true },
{ key: 13, label: `排序`, visible: true },
{ key: 14, label: `销售凭证`, visible: false },
{ key: 15, label: `销售单据项目`, visible: false },
{ key: 16, label: `父级物料名称`, visible: true },
{ key: 17, label: `父级物料小类`, visible: true }
{ key: 0, label: '节点物料编码', visible: true },
{ key: 1, label: '节点物料名称', visible: true },
{ key: 2, label: '节点物料小类', visible: true },
{ key: 3, label: '标准数量', visible: true },
{ key: 4, label: '父节点编码', visible: true },
{ key: 5, label: '父节点名称', visible: true },
{ key: 6, label: '工厂编号', visible: true },
{ key: 7, label: '排序', visible: true },
{ key: 8, label: '销售凭证', visible: false },
{ key: 9, label: '销售单据项目', visible: false }
]
}
},
created() {
this.getList();
this.loadMaterialOptions()
this.getList()
},
methods: {
/** 查询订单BOM列表 */
/** 主页面直接展示整棵BOM树不能再按分页平铺否则父子关系会被打散。 */
getList() {
this.loading = true
listOrderBomInfo(this.queryParams).then(response => {
this.orderBomInfoList = response.rows
this.total = response.total
findOrderBomList(this.queryParams).then(response => {
const normalizedList = this.decorateTreeNodes(response.data || [])
this.orderBomInfoList = this.handleTree(normalizedList, 'objId', 'treeParentObjId')
this.loading = false
})
},
//
cancel() {
this.open = false
this.reset()
decorateTreeNodes(list) {
const nodes = (list || []).map(item => ({ ...item }))
const identityMap = new Map()
nodes.forEach(item => {
identityMap.set(this.buildNodeIdentity(item.materialCode, item.ancestors), item.objId)
})
this.allOrderBomNodes = nodes.map(item => ({
...item,
treeParentObjId: this.resolveTreeParentObjId(item, identityMap)
}))
return this.allOrderBomNodes
},
buildNodeIdentity(materialCode, ancestors) {
return `${materialCode || ''}@@${ancestors || ''}`
},
resolveTreeParentObjId(item, identityMap) {
if (!item.parentId || item.parentId === '0') {
return 0
}
if (item.parentObjId && item.parentObjId > 0) {
return item.parentObjId
}
const parentAncestors = this.resolveParentAncestors(item)
return identityMap.get(this.buildNodeIdentity(item.parentId, parentAncestors)) || 0
},
resolveParentAncestors(item) {
const ancestors = (item.ancestors || '').split(',').filter(Boolean)
if (ancestors.length <= 1) {
return ''
}
ancestors.pop()
return ancestors.join(',')
},
normalizer(node) {
if (node.children && !node.children.length) {
delete node.children
}
return {
id: node.objId,
label: (node.materialCode || '') + ' / ' + (node.materialName || ''),
children: node.children
}
},
getTreeselect() {
findOrderBomList().then(response => {
const normalizedList = this.decorateTreeNodes(response.data || [])
this.orderBomInfoOptions = []
const root = { objId: 0, materialCode: '0', materialName: '顶级节点', children: [] }
root.children = this.handleTree(normalizedList, 'objId', 'treeParentObjId')
this.orderBomInfoOptions.push(root)
})
},
loadMaterialOptions() {
listAllMaterialInfo({ isFlag: 0 }).then(response => {
this.materialOptions = response.data || []
})
},
handleMaterialChange(materialCode) {
const selectedMaterial = this.materialOptions.find(item => item.materialCode === materialCode)
if (!selectedMaterial) {
this.form.materialName = null
this.form.materialType = null
this.form.factoryCode = null
return
}
//
this.form.materialName = selectedMaterial.materialName
this.form.materialType = selectedMaterial.materialSubclass
this.form.factoryCode = selectedMaterial.plantCode
if (!this.form.bomCode) {
this.form.bomCode = selectedMaterial.materialCode
}
},
handleParentChange(parentObjId) {
if (!parentObjId) {
this.form.parentObjId = 0
this.form.parentId = '0'
return
}
const parentNode = this.allOrderBomNodes.find(item => item.objId === parentObjId)
this.form.parentId = parentNode ? parentNode.materialCode : null
},
//
reset() {
this.form = {
objId: null,
@ -328,83 +338,86 @@ export default {
materialName: null,
materialType: null,
standardAmount: null,
parentId: null,
isFlag: null,
createdBy: null,
createdTime: null,
updatedBy: null,
updatedTime: null,
parentObjId: 0,
parentId: '0',
factoryCode: null,
sort: null,
vbeln: null,
vbpos: null
vbpos: null,
ancestors: null
}
this.resetForm('form')
},
/** 搜索按钮操作 */
cancel() {
this.open = false
this.reset()
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm('queryForm')
this.handleQuery()
this.getList()
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.objId)
this.single = selection.length !== 1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
handleAdd(row) {
this.reset()
this.getTreeselect()
if (row && row.objId) {
this.form.parentObjId = row.objId
this.form.parentId = row.materialCode
}
this.open = true
this.title = '添加订单BOM'
this.title = row && row.objId ? '添加下级BOM节点' : '添加根BOM节点'
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset()
const objId = row.objId || this.ids
getOrderBomInfo(objId).then(response => {
this.getTreeselect()
getOrderBomInfo(row.objId).then(response => {
this.form = response.data
this.open = true
this.title = '修改订单BOM'
})
},
/** 提交按钮 */
submitForm() {
this.$refs['form'].validate(valid => {
if (valid) {
if (this.form.objId != null) {
updateOrderBomInfo(this.form).then(response => {
this.$modal.msgSuccess('修改成功')
this.open = false
this.getList()
})
} else {
addOrderBomInfo(this.form).then(response => {
this.$modal.msgSuccess('新增成功')
this.open = false
this.getList()
})
}
if ((!this.form.parentObjId || this.form.parentObjId <= 0) && row && row.treeParentObjId >= 0) {
this.form.parentObjId = row.treeParentObjId || 0
}
this.handleParentChange(this.form.parentObjId)
this.handleMaterialChange(this.form.materialCode)
this.open = true
this.title = '修改BOM节点'
})
},
submitForm() {
this.$refs.form.validate(valid => {
if (!valid) {
return
}
if (this.form.objId != null) {
updateOrderBomInfo(this.form).then(() => {
this.$modal.msgSuccess('修改成功')
this.open = false
this.getList()
})
return
}
addOrderBomInfo(this.form).then(() => {
this.$modal.msgSuccess('新增成功')
this.open = false
this.getList()
})
})
},
/** 删除按钮操作 */
handleDelete(row) {
const objIds = row.objId || this.ids
this.$modal.confirm('是否确认删除订单BOM编号为"' + objIds + '"的数据项?').then(function() {
return delOrderBomInfo(objIds)
this.$modal.confirm('是否确认删除该BOM节点').then(() => {
return delOrderBomInfo(row.objId)
}).then(() => {
this.getList()
this.$modal.msgSuccess('删除成功')
}).catch(() => {
this.getList()
}).catch(() => {})
},
toggleExpandAll() {
this.refreshTable = false
this.isExpandAll = !this.isExpandAll
this.$nextTick(() => {
this.refreshTable = true
})
},
/** 导出按钮操作 */
handleExport() {
this.download('base/orderBomInfo/export', {
...this.queryParams

@ -0,0 +1,687 @@
# BASE_DEVICE_PARAM_VAL 旁路能力改造实现方案
## 1. 当前理解
### 1.1 当前参数监控链路已经不是“单表模型”
根据现有实现与说明文档,`BASE_DEVICE_PARAM_VAL` 已经不再代表全部参数数据源,而是进入了如下分层模型:
1. `BASE_DEVICE_PARAM_VAL`
只承载 `OLD-` 设备、PDA 手工录入、人工补录等单表数据。
2. `BASE_DEVICE_PARAM_VAL_YYYYMM`
承载自动采集设备的月分表数据。
3. `RT_DAILY_PROD_STATE`
承载当天实时累计产量事实。
4. `DEVICE_DAILY_PRODUCTION`
承载历史日汇总产量事实。
所以,任何仍然把 `BASE_DEVICE_PARAM_VAL` 当成“唯一参数源表”的功能,都已经与当前真实架构不一致。
### 1.2 当前已经兼容新旧设备的核心能力
当前 `aucma-base` 中与参数监控直接相关的核心实现已经完成了双源/分层兼容:
1. [BaseDeviceParamVal.java](/C:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/domain/BaseDeviceParamVal.java)
已新增 `tableSuffixes` 字段,用于跨月分表读取。
2. [BaseDeviceParamValMapper.xml](/C:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-base/src/main/resources/mapper/base/BaseDeviceParamValMapper.xml)
已具备 `OLD 单表 + NEW 月分表` 的双源查询能力。
3. [BaseDeviceParamValServiceImpl.java](/C:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/impl/BaseDeviceParamValServiceImpl.java)
已具备读分表、写分表、RT 同步、SPC/trace/latest 双源查询能力。
### 1.3 当前不能动的冻结边界
本次方案的强约束如下:
1. `board1`、`board4`、`board5` 这 3 个 Vue 文件不能修改。
2. 这 3 个 Vue 文件当前使用到的接口、方法、Mapper、Mapper SQL 不允许修改。
3. 因此,改造必须通过“新增旁路能力层”或“迁移旁路功能到新服务”来完成,不能破坏现有大屏调用链。
### 1.4 当前仍然存在的旁路问题
当前仍然直接写死或直接依赖 `BASE_DEVICE_PARAM_VAL` 的能力主要有:
1. 工艺快照:
[ProcessSnapshotMapper.xml](/C:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-base/src/main/resources/mapper/base/ProcessSnapshotMapper.xml)
[ProcessSnapshotServiceImpl.java](/C:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/impl/ProcessSnapshotServiceImpl.java)
2. 工艺预警:
[ProcessAlertServiceImpl.java](/C:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/impl/ProcessAlertServiceImpl.java)
3. PDA 写入:
[DmsMobileController.java](/C:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-dms/src/main/java/com/aucma/dms/controller/DmsMobileController.java)
4. 参数分析报表:
[DeviceParamAnalysisMapper.xml](/C:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-report/src/main/resources/mapper/report/DeviceParamAnalysisMapper.xml)
5. 安灯统计:
[AndonDashboardServiceImpl.java](/C:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-production/src/main/java/com/aucma/production/service/impl/AndonDashboardServiceImpl.java)
这些能力虽然不是 `board1/board4/board5` 页面本身,但它们对参数源表的理解仍然停留在旧模型,需要重构。
---
## 2. 具体需求
### 2.1 总体需求
在不修改任何现有大屏前端文件、不修改大屏当前依赖接口与方法的前提下,完成旁路能力的架构收口,使所有非大屏参数能力不再直接写死 `BASE_DEVICE_PARAM_VAL`
### 2.2 分项需求
#### 2.2.1 工艺快照
1. 快照详情查看时,不能只去 `BASE_DEVICE_PARAM_VAL` 单表找“某时刻附近的参数值”。
2. 快照对比时,必须支持:
- `OLD` 单表数据
- `NEW` 月分表数据
- 跨月查询
3. 快照主表 `process_snapshot` 的 CRUD 保持不变。
#### 2.2.2 工艺预警
1. 预警检查最新值时,不能再通过 `selectBaseDeviceParamValList(...)` 默认拿第一条当最新值。
2. 必须统一按双源最新口径获取“设备 + 参数”的最新采集值。
3. 预警去重、冷却时间、未处理更新逻辑保持不变。
#### 2.2.3 PDA 写入
1. `DmsMobileController` 不应继续承担参数语义拼装、参数编码回填、时间补齐、路由判断等参数监控领域职责。
2. PDA 控制器应只保留:
- 入参校验
- 调用参数写入服务
3. 真实落表位置、`OLD/NEW` 分流、RT 覆盖/增量同步应继续下沉到参数监控域能力。
#### 2.2.4 参数分析报表
1. `aucma-report` 中参数异常、SPC、切换追溯报表不能直接扫描 `BASE_DEVICE_PARAM_VAL*`
2. 报表模块应依赖 `aucma-base` 暴露的统一参数读取能力。
3. `report` 模块只负责报表统计逻辑,不负责底层分表路由和源表物理结构。
#### 2.2.5 安灯统计
1. `AndonDashboardServiceImpl` 不能继续直接注入 `BaseDeviceParamValMapper`
2. 所有 latest/history 类参数读取都应转到统一参数能力层。
3. 产量统计继续优先使用 `RT_DAILY_PROD_STATE` / `DEVICE_DAILY_PRODUCTION`,不回退到源表明细。
---
## 3. 需求分析
### 3.1 为什么不能继续直接查 `BASE_DEVICE_PARAM_VAL`
因为当前真实数据源已经分裂为:
1. `OLD` 设备单表
2. `NEW` 设备月分表
3. RT 实时事实层
4. DAY 历史事实层
如果旁路功能继续直接查 `BASE_DEVICE_PARAM_VAL`,会导致:
1. `NEW` 设备数据查不到。
2. 跨月参数历史缺失。
3. 快照、预警、分析报表与大屏口径不一致。
4. 各模块各写一套表路由逻辑,长期不可维护。
### 3.2 为什么不能直接复用现有大屏接口
虽然现有 `BaseDeviceParamValController` / `IBaseDeviceParamValService` 已有双源能力,但这次有明确冻结边界:
1. `board1`、`board4`、`board5` 当前所使用的接口、方法和 mapper 不允许修改。
2. 如果继续在现有接口上叠加旁路逻辑,容易影响大屏现有行为。
所以更稳妥的方式是:
1. 大屏兼容链路保持不变。
2. 新增一套“旁路统一参数能力层”。
3. 工艺快照、预警、PDA、分析报表、安灯逐步迁移到这套新能力。
### 3.3 为什么快照和预警最应该先改
这两个功能目前的问题最典型:
1. 快照:
时间点取值本质上就是“按设备 + 时间窗口”查参数明细,这恰好最依赖双源和跨月能力。
2. 预警:
当前直接查列表再取第一条,既不是统一 latest 口径,也没有保证排序和双源合并逻辑。
如果这两类能力继续各写一套 SQL后续参数监控每次变更都要多处同步风险非常高。
### 3.4 为什么安灯不能直接注入 `BaseDeviceParamValMapper`
`AndonDashboardServiceImpl` 当前直接注入 `BaseDeviceParamValMapper`,说明它已经越过了参数监控的服务边界,直接耦合到底层参数明细读法。
风险在于:
1. 安灯模块需要知道源表物理结构。
2. 安灯模块需要自己判断 latest 口径。
3. 参数监控一旦继续重构,安灯会跟着断裂。
所以安灯应只依赖“参数读取服务”,而不依赖具体 mapper。
### 3.5 为什么 PDA 控制器也属于旁路能力
`DmsMobileController` 当前虽然通过 `IBaseDeviceParamValService` 写入,但控制器中仍然包含大量参数监控域职责:
1. 参数名强制赋值
2. 参数编码补齐
3. 设备标识补齐
4. 参数值语义校验
5. 写入模式路由
这会导致 DMS 控制器和参数监控域高度耦合。
正确做法是:
1. DMS 只负责 PDA 业务动作。
2. 参数监控域负责“把 PDA 业务动作映射为参数写入”。
---
## 4. 注意点
### 4.1 绝对不能修改的内容
以下内容本次方案中必须保持不变:
1. `board1/index.vue`
2. `board4/index.vue`
3. `board5/index.vue`
4. 这 3 个页面当前调用的 URL
5. 这 3 个页面当前依赖的 Controller 方法签名
6. 这 3 个页面当前依赖的 Service 方法签名
7. 这 3 个页面当前依赖的 Mapper 方法名与 SQL
### 4.2 必须新增而不是强改现有大屏链路
建议新增:
1. 统一参数读取 Facade
2. 统一 PDA 参数写入服务
3. 旁路报表/快照/预警专用的服务封装
不建议直接重写:
1. `board1/board4/board5` 已用接口
2. 现有大屏 mapper SQL
### 4.3 参数读取统一能力必须只在一个地方做表路由
不允许以下情况再次出现:
1. 快照自己拼月表
2. 预警自己判断最新值
3. 报表自己 UNION 分表
4. 安灯自己调 mapper 查源表
统一原则:
1. 只有参数监控域负责 `OLD/NEW``跨月 suffix` 路由。
2. 其他模块一律只调服务。
### 4.4 代码示例中的“修改代码”是方案示例,不是本次实际修改
本文件里的代码块只作为后续实施时的建议写法,不代表本次已经改代码。
### 4.5 简体中文行间注释要求
后续真改代码时,关键业务逻辑必须保留简体中文行间注释,且要解释“为什么这样做”,不能只写“做了什么”。
---
## 5. 具体修改代码方案
## 5.1 新增统一旁路参数读取能力层
### 代码文件位置
建议新增:
1. `zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/IDeviceParamReadFacade.java`
2. `zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/impl/DeviceParamReadFacadeImpl.java`
3. `zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/mapper/DeviceParamReadFacadeMapper.java`
4. `zs_aucma-mes-back/aucma-base/src/main/resources/mapper/base/DeviceParamReadFacadeMapper.xml`
### 建议接口职责
1. 查询某设备某参数双源最新值
2. 查询某设备某时刻附近参数集
3. 查询两个时刻的参数差异
4. 查询某设备某参数双源历史数值
5. 查询某设备模具/物料/产品切换记录
### 建议代码
```java
// 文件zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/IDeviceParamReadFacade.java
package com.aucma.base.service;
import com.aucma.base.domain.BaseDeviceParamVal;
import java.util.Date;
import java.util.List;
import java.util.Map;
public interface IDeviceParamReadFacade {
BaseDeviceParamVal getLatestParamValue(String deviceCode, String paramCode, String paramName);
List<BaseDeviceParamVal> listParamsByTimePoint(String deviceCode, Date snapshotTime, int windowSeconds);
List<Map<String, Object>> compareParamsByTimePoint(String deviceCode, Date timePoint1, Date timePoint2, int windowSeconds);
List<Double> listNumericHistoryValues(String deviceCode, String paramCode, Date beginTime, Date endTime);
}
```
```java
// 文件zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/impl/DeviceParamReadFacadeImpl.java
package com.aucma.base.service.impl;
import com.aucma.base.domain.BaseDeviceParamVal;
import com.aucma.base.mapper.DeviceParamReadFacadeMapper;
import com.aucma.base.service.IDeviceParamReadFacade;
import com.aucma.base.support.DeviceParamTableRouter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class DeviceParamReadFacadeImpl implements IDeviceParamReadFacade {
@Autowired
private DeviceParamReadFacadeMapper deviceParamReadFacadeMapper;
@Autowired
private DeviceParamTableRouter deviceParamTableRouter;
@Override
public BaseDeviceParamVal getLatestParamValue(String deviceCode, String paramCode, String paramName) {
Map<String, Object> params = new HashMap<>();
params.put("deviceCode", deviceCode);
params.put("paramCode", paramCode);
params.put("paramName", paramName);
params.put("tableSuffixes", deviceParamTableRouter.resolveReadTableSuffixes(
new Date(System.currentTimeMillis() - 31L * 24 * 60 * 60 * 1000), new Date()));
// 为什么这样做:旁路模块不应该自己知道 OLD 单表和 NEW 月分表的物理结构,
// 所有双源读取都在这里统一吸收。
return deviceParamReadFacadeMapper.selectLatestParamValue(params);
}
@Override
public List<BaseDeviceParamVal> listParamsByTimePoint(String deviceCode, Date snapshotTime, int windowSeconds) {
// 为什么这样做:快照类能力本质上是“按时间点取最近参数”,
// 必须统一走双源时间窗口查询,不能再默认只查 BASE_DEVICE_PARAM_VAL。
return Collections.emptyList();
}
@Override
public List<Map<String, Object>> compareParamsByTimePoint(String deviceCode, Date timePoint1, Date timePoint2, int windowSeconds) {
return Collections.emptyList();
}
@Override
public List<Double> listNumericHistoryValues(String deviceCode, String paramCode, Date beginTime, Date endTime) {
return Collections.emptyList();
}
}
```
---
## 5.2 工艺快照改造方案
### 当前问题文件位置
1. [ProcessSnapshotMapper.xml](/C:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-base/src/main/resources/mapper/base/ProcessSnapshotMapper.xml)
2. [ProcessSnapshotServiceImpl.java](/C:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/impl/ProcessSnapshotServiceImpl.java)
### 当前问题
`selectParamsBySnapshotTime``compareSnapshots` 直接写死 `base_device_param_val`,无法覆盖:
1. 月分表
2. 新设备
3. 跨月
### 建议修改思路
1. `ProcessSnapshotMapper.xml`
仅保留 `process_snapshot` 主表 CRUD。
2. `ProcessSnapshotServiceImpl`
使用 `IDeviceParamReadFacade` 完成参数详情和时间点对比。
### 建议代码
```java
// 文件zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/impl/ProcessSnapshotServiceImpl.java
@Autowired
private IDeviceParamReadFacade deviceParamReadFacade;
@Override
public ProcessSnapshot selectSnapshotDetail(Long snapshotId) {
ProcessSnapshot snapshot = processSnapshotMapper.selectProcessSnapshotBySnapshotId(snapshotId);
if (snapshot != null && snapshot.getDeviceCode() != null && snapshot.getSnapshotTime() != null) {
// 为什么这样做:快照参数详情已经不是单表能力,
// 必须走统一双源读取,才能兼容 OLD 单表与 NEW 月分表。
List<BaseDeviceParamVal> paramList = deviceParamReadFacade.listParamsByTimePoint(
snapshot.getDeviceCode(), snapshot.getSnapshotTime(), 60);
snapshot.setParamList(paramList);
}
return snapshot;
}
@Override
public List<Map<String, Object>> compareSnapshots(Long snapshotId1, Long snapshotId2) {
ProcessSnapshot snapshot1 = processSnapshotMapper.selectProcessSnapshotBySnapshotId(snapshotId1);
ProcessSnapshot snapshot2 = processSnapshotMapper.selectProcessSnapshotBySnapshotId(snapshotId2);
if (snapshot1 == null || snapshot2 == null) {
return null;
}
if (!snapshot1.getDeviceCode().equals(snapshot2.getDeviceCode())) {
return null;
}
// 为什么这样做:快照对比本质是“两个时间点的双源参数差异”,
// 不能再让 Mapper 直接写死 BASE_DEVICE_PARAM_VAL。
return deviceParamReadFacade.compareParamsByTimePoint(
snapshot1.getDeviceCode(), snapshot1.getSnapshotTime(), snapshot2.getSnapshotTime(), 60);
}
```
---
## 5.3 工艺预警改造方案
### 当前问题文件位置
1. [ProcessAlertServiceImpl.java](/C:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/impl/ProcessAlertServiceImpl.java)
### 当前问题
当前逻辑是:
1. 先查参数配置
2. 再用 `BaseDeviceParamValMapper.selectBaseDeviceParamValList(...)`
3. 然后默认拿第一条记录当“最新值”
这在双源模型下不可靠。
### 建议修改思路
1. 保留预警业务逻辑。
2. 替换“查询最新参数值”的获取方式。
### 建议代码
```java
// 文件zs_aucma-mes-back/aucma-base/src/main/java/com/aucma/base/service/impl/ProcessAlertServiceImpl.java
@Autowired
private IDeviceParamReadFacade deviceParamReadFacade;
// 原来这里是:
// List<BaseDeviceParamVal> valList = baseDeviceParamValMapper.selectBaseDeviceParamValList(valQuery);
// BaseDeviceParamVal latestVal = valList.get(0);
BaseDeviceParamVal latestVal = deviceParamReadFacade.getLatestParamValue(
param.getDeviceCode(),
param.getParamCode(),
param.getParamName()
);
// 为什么这样做:预警必须基于“统一最新值口径”做判断,
// 否则 OLD/NEW 混合场景下会出现新设备查不到值、旧设备取值排序不准的问题。
if (latestVal == null) {
continue;
}
```
---
## 5.4 PDA 写入改造方案
### 当前问题文件位置
1. [DmsMobileController.java](/C:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-dms/src/main/java/com/aucma/dms/controller/DmsMobileController.java)
### 当前问题
当前控制器承担了过多参数监控域职责:
1. 参数名选择
2. 参数值语义控制
3. 设备标识补齐
4. 参数对象构造
5. 最终调用 `IBaseDeviceParamValService`
### 建议修改思路
新增一层 PDA 参数写入服务,把控制器里的领域细节迁出。
### 建议新增文件位置
1. `zs_aucma-mes-back/aucma-dms/src/main/java/com/aucma/dms/service/IPdaDeviceParamWriteService.java`
2. `zs_aucma-mes-back/aucma-dms/src/main/java/com/aucma/dms/service/impl/PdaDeviceParamWriteServiceImpl.java`
### 建议代码
```java
// 文件zs_aucma-mes-back/aucma-dms/src/main/java/com/aucma/dms/controller/DmsMobileController.java
@Autowired
private IPdaDeviceParamWriteService pdaDeviceParamWriteService;
@PostMapping("/device/updateDailyOutput")
@Transactional(rollbackFor = Exception.class)
public AjaxResult updateDeviceDailyOutput(@RequestBody DmsPdaDeviceParamUpdateVo request) {
if (request == null) {
throw new ServiceException("请求体不能为空");
}
// 为什么这样做DMS 控制器应该只负责 PDA 业务入口,
// 不应该继续承担参数对象拼装和参数监控域路由逻辑。
return pdaDeviceParamWriteService.updateDailyOutput(request);
}
```
```java
// 文件zs_aucma-mes-back/aucma-dms/src/main/java/com/aucma/dms/service/impl/PdaDeviceParamWriteServiceImpl.java
@Service
public class PdaDeviceParamWriteServiceImpl implements IPdaDeviceParamWriteService {
@Autowired
private IBaseDeviceParamValService baseDeviceParamValService;
@Override
public AjaxResult updateDailyOutput(DmsPdaDeviceParamUpdateVo request) {
BaseDeviceParamVal entity = new BaseDeviceParamVal();
entity.setParamName("生产计数-当前日期生产总数");
entity.setParamValue(request.getParamValue().toString());
// 为什么这样做PDA 上报的是业务动作,
// 由参数监控域统一决定最终如何落库、如何同步 RT控制器和 DMS 不再感知底层细节。
baseDeviceParamValService.upsertTodayParamValue(entity);
return AjaxResult.success();
}
}
```
---
## 5.5 参数分析报表改造方案
### 当前问题文件位置
1. [DeviceParamAnalysisMapper.xml](/C:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-report/src/main/resources/mapper/report/DeviceParamAnalysisMapper.xml)
### 当前问题
报表模块当前直接写源表 SQL会导致
1. report 模块掌握底层表结构
2. report 模块自己承担双源合并
3. 参数监控继续重构时report 需要同步重写
### 建议修改思路
1. `report` 模块不直接查源表。
2. `DeviceParamAnalysisServiceImpl` 调用 `aucma-base` 的统一参数读取能力。
3. Mapper 只保留报表自身非参数事实表查询。
### 建议代码
```java
// 文件zs_aucma-mes-back/aucma-report/src/main/java/com/aucma/report/service/impl/DeviceParamAnalysisServiceImpl.java
@Autowired
private IDeviceParamReadFacade deviceParamReadFacade;
@Override
public DeviceParamSpcReportVo getSpcReport(DeviceParamReportQuery query) {
List<Double> values = deviceParamReadFacade.listNumericHistoryValues(
query.getDeviceCode(),
query.getParamCode(),
parseDate(query.getStartTime()),
parseDate(query.getEndTime())
);
// 为什么这样做SPC 统计逻辑属于报表层,
// 但参数原始点位读取属于参数监控域,必须分层。
return buildSpcReport(values, query);
}
```
---
## 5.6 安灯统计改造方案
### 当前问题文件位置
1. [AndonDashboardServiceImpl.java](/C:/D/WORK/NewP/zs_aucma-mes/zs_aucma-mes-back/aucma-production/src/main/java/com/aucma/production/service/impl/AndonDashboardServiceImpl.java)
### 当前问题
当前实现直接注入:
1. `BaseDeviceParamValMapper`
2. `IBaseDeviceParamValService`
其中直接注入 mapper 的部分需要移除。
### 建议修改思路
1. 安灯保留对 `IBaseDeviceParamValService` 的兼容依赖可以接受。
2. 所有 direct mapper latest 查询改成统一读取 Facade。
3. 产量类继续优先走 RT/DAY 事实层。
### 建议代码
```java
// 文件zs_aucma-mes-back/aucma-production/src/main/java/com/aucma/production/service/impl/AndonDashboardServiceImpl.java
@Autowired
private IDeviceParamReadFacade deviceParamReadFacade;
// 删除
// @Autowired
// private BaseDeviceParamValMapper baseDeviceParamValMapper;
private double queryLatestDoubleValue(String deviceCode, String paramName) {
BaseDeviceParamVal latestVal = deviceParamReadFacade.getLatestParamValue(deviceCode, null, paramName);
if (latestVal == null || latestVal.getParamValue() == null) {
return 0D;
}
return parseDouble(latestVal.getParamValue());
}
// 为什么这样做:安灯统计属于参数消费方,
// 不应该直接依赖 BaseDeviceParamValMapper 去感知底层单表/分表结构。
```
---
## 6. 推荐实施顺序
### 第一阶段:先补能力层
1. 新增 `IDeviceParamReadFacade`
2. 新增 `DeviceParamReadFacadeImpl`
3. 新增独立 mapper 和 xml
### 第二阶段:先迁快照和预警
原因:
1. 风险低
2. 不影响大屏
3. 价值高
### 第三阶段:迁 PDA 控制器写入职责
原因:
1. 控制器解耦后,后续参数语义调整不再污染 DMS
### 第四阶段:迁 report 参数分析
原因:
1. 让 `report` 不再直接掌握底层参数表结构
### 第五阶段:迁安灯直接 mapper 依赖
原因:
1. 安灯逻辑复杂,适合最后迁
2. 但必须完成,以免后续又形成新的旁路
---
## 7. 验收标准
### 7.1 冻结校验
以下文件不能改:
1. `zs_aucma-mes-ui/src/views/board/board1/index.vue`
2. `zs_aucma-mes-ui/src/views/board/board4/index.vue`
3. `zs_aucma-mes-ui/src/views/board/board5/index.vue`
### 7.2 代码依赖校验
整改后应满足:
1. `ProcessSnapshotMapper.xml` 不再直接出现 `base_device_param_val`
2. `ProcessAlertServiceImpl` 不再直接通过 `BaseDeviceParamValMapper` 取 latest
3. `DeviceParamAnalysisMapper.xml` 不再直接查参数源表
4. `AndonDashboardServiceImpl` 不再直接注入 `BaseDeviceParamValMapper`
5. `DmsMobileController` 不再承担参数监控领域对象拼装
### 7.3 功能验收
1. 工艺快照详情可同时查到 OLD / NEW 参数
2. 快照对比支持跨月
3. 预警最新值口径正确
4. PDA 当日产量、运行时间、三色灯写入不受影响
5. 参数异常/SPC/切换追溯报表不再依赖单表
6. 安灯 OEE/利用率等 latest 类指标与双源参数口径一致
---
## 8. 结论
本次重构的正确方向不是继续扩写 `BASE_DEVICE_PARAM_VAL` 旁路 SQL而是
1. 保持大屏链路不动
2. 新增统一旁路参数读取能力层
3. 让工艺快照、预警、PDA、参数分析报表、安灯逐步迁移到统一能力层
这样做的收益是:
1. 不破坏 `board1/board4/board5`
2. 不再让旁路能力感知底层单表/分表结构
3. 后续参数监控再次演进时,只需要改一处参数能力层

@ -1091,7 +1091,6 @@ export default {
RequestDataSet1: [
{
e: 'fp-2-01',
i: "scada_fp_cy_01('CX_02')",
f: (e) => {
this.planNum = e[0].X_VALUE
this.practicalNum = e[0].Y_VALUE_ONE

@ -0,0 +1,239 @@
<template>
<div class="app-container">
<el-form ref="queryForm" :model="queryParams" size="small" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="工单编号" prop="orderCode">
<el-input v-model="queryParams.orderCode" placeholder="请输入工单编号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="物料名称" prop="materialName">
<el-input v-model="queryParams.materialName" placeholder="请输入物料名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="记录类型" prop="noteType">
<el-select v-model="queryParams.noteType" placeholder="请选择记录类型" clearable>
<el-option v-for="item in noteTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['production:orderNote:add']" type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"></el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['production:orderNote:edit']" type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate"></el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['production:orderNote:remove']" type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"></el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['production:orderNote:export']" type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"></el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="orderNoteList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="工单编号" align="center" prop="orderCode" width="160" />
<el-table-column label="物料编码" align="center" prop="materialCode" width="140" />
<el-table-column label="物料名称" align="center" prop="materialName" show-overflow-tooltip />
<el-table-column label="工单状态" align="center" prop="executionStatus">
<template slot-scope="scope">
<el-tag :type="getExecutionTag(scope.row.executionStatus)">{{ getExecutionLabel(scope.row.executionStatus) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="记录类型" align="center" prop="noteType">
<template slot-scope="scope">{{ getNoteTypeLabel(scope.row.noteType) }}</template>
</el-table-column>
<el-table-column label="记录内容" align="center" prop="noteContent" show-overflow-tooltip />
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<el-tag :type="scope.row.status === '0' ? 'success' : 'info'">{{ scope.row.status === '0' ? '有效' : '关闭' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="创建人" align="center" prop="createdBy" width="100" />
<el-table-column label="创建时间" align="center" prop="createdTime" width="160" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button v-hasPermi="['production:orderNote:edit']" size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"></el-button>
<el-button v-hasPermi="['production:orderNote:remove']" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
<el-dialog :title="title" :visible.sync="open" width="720px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="110px">
<el-form-item label="工单编号" prop="orderCode">
<el-input v-model="form.orderCode" placeholder="请输入工单编号" />
</el-form-item>
<el-form-item label="记录类型" prop="noteType">
<el-select v-model="form.noteType" placeholder="请选择记录类型" style="width: 100%">
<el-option v-for="item in noteTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="记录内容" prop="noteContent">
<el-input v-model="form.noteContent" type="textarea" :rows="5" maxlength="1000" show-word-limit placeholder="请输入记录内容" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="item in statusOptions" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listOrderNote, getOrderNote, addOrderNote, updateOrderNote, delOrderNote } from '@/api/production/orderNote'
export default {
name: 'OrderNote',
data() {
return {
loading: false,
showSearch: true,
total: 0,
orderNoteList: [],
ids: [],
single: true,
multiple: true,
open: false,
title: '',
noteTypeOptions: [
{ label: '异常说明', value: 'ABNORMAL' },
{ label: '改线记录', value: 'LINE_CHANGE' },
{ label: '缺料说明', value: 'LACK_MATERIAL' },
{ label: '延期原因', value: 'DELAY' },
{ label: '关闭原因', value: 'CLOSE_REASON' }
],
statusOptions: [
{ label: '有效', value: '0' },
{ label: '关闭', value: '1' }
],
queryParams: {
pageNum: 1,
pageSize: 10,
orderCode: undefined,
materialName: undefined,
noteType: undefined,
status: undefined
},
form: {},
rules: {
orderCode: [{ required: true, message: '工单编号不能为空', trigger: 'blur' }],
noteType: [{ required: true, message: '记录类型不能为空', trigger: 'change' }],
noteContent: [{ required: true, message: '记录内容不能为空', trigger: 'blur' }]
}
}
},
created() {
this.getList()
},
methods: {
getList() {
this.loading = true
listOrderNote(this.queryParams).then(response => {
this.orderNoteList = response.rows
this.total = response.total
this.loading = false
})
},
getNoteTypeLabel(value) {
const match = this.noteTypeOptions.find(item => item.value === value)
return match ? match.label : value
},
getExecutionLabel(value) {
const map = { PENDING: '待执行', RUNNING: '运行中', COMPLETED: '已完成', PAUSED: '已暂停' }
return map[value] || value || '待执行'
},
getExecutionTag(value) {
const map = { PENDING: 'info', RUNNING: 'warning', COMPLETED: 'success', PAUSED: '' }
return map[value] || 'info'
},
cancel() {
this.open = false
this.reset()
},
reset() {
this.form = {
objId: undefined,
orderCode: undefined,
noteType: undefined,
noteContent: undefined,
status: '0',
remark: undefined
}
this.resetForm('form')
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.resetForm('queryForm')
this.handleQuery()
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.objId)
this.single = selection.length !== 1
this.multiple = !selection.length
},
handleAdd() {
this.reset()
this.open = true
this.title = '新增工单备注与异常记录'
},
handleUpdate(row) {
const objId = row.objId || this.ids[0]
this.reset()
getOrderNote(objId).then(response => {
this.form = response.data
this.open = true
this.title = '修改工单备注与异常记录'
})
},
handleDelete(row) {
const objIds = row.objId || this.ids
this.$modal.confirm('是否确认删除选中的工单记录?').then(() => {
return delOrderNote(objIds)
}).then(() => {
this.getList()
this.$modal.msgSuccess('删除成功')
})
},
handleExport() {
this.download('/production/orderNote/export', { ...this.queryParams }, `order_note_${new Date().getTime()}.xlsx`)
},
submitForm() {
this.$refs.form.validate(valid => {
if (!valid) {
return
}
const request = this.form.objId ? updateOrderNote(this.form) : addOrderNote(this.form)
request.then(() => {
this.$modal.msgSuccess(this.form.objId ? '修改成功' : '新增成功')
this.open = false
this.getList()
})
})
}
}
}
</script>

@ -0,0 +1,348 @@
<template>
<div class="app-container">
<el-form ref="queryForm" :model="queryParams" size="small" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="路线编码" prop="routeCode">
<el-input v-model="queryParams.routeCode" placeholder="请输入路线编码" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="路线名称" prop="routeName">
<el-input v-model="queryParams.routeName" placeholder="请输入路线名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="物料编码" prop="materialCode">
<el-input v-model="queryParams.materialCode" placeholder="请输入物料编码" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['production:processRoute:add']" type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"></el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['production:processRoute:edit']" type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate"></el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['production:processRoute:remove']" type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"></el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['production:processRoute:export']" type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"></el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="routeList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="路线编码" align="center" prop="routeCode" width="130" />
<el-table-column label="路线名称" align="center" prop="routeName" />
<el-table-column label="物料编码" align="center" prop="materialCode" width="130" />
<el-table-column label="物料名称" align="center" prop="materialName" show-overflow-tooltip />
<el-table-column label="版本号" align="center" prop="versionNo" width="90" />
<el-table-column label="状态" align="center" prop="status" width="90">
<template slot-scope="scope">
<el-tag :type="scope.row.status === '0' ? 'success' : 'info'">{{ scope.row.status === '0' ? '启用' : '停用' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
<template slot-scope="scope">
<el-button v-hasPermi="['production:processRoute:edit']" size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"></el-button>
<el-button v-hasPermi="['production:processRoute:remove']" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
<el-dialog :title="title" :visible.sync="open" width="980px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="110px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="路线编码" prop="routeCode">
<el-input v-model="form.routeCode" placeholder="请输入路线编码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="路线名称" prop="routeName">
<el-input v-model="form.routeName" placeholder="请输入路线名称" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="物料" prop="materialCode">
<el-select v-model="form.materialCode" placeholder="请选择物料" filterable style="width: 100%" @change="handleMaterialChange">
<el-option v-for="item in materialOptions" :key="item.materialCode" :label="item.materialCode + ' / ' + item.materialName" :value="item.materialCode" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="版本号" prop="versionNo">
<el-input v-model="form.versionNo" placeholder="请输入版本号" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="item in statusOptions" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
<div class="route-detail-header">
<span>工艺路线明细</span>
<el-button type="primary" plain size="mini" icon="el-icon-plus" @click="handleAddDetail"></el-button>
</div>
<el-table :data="form.detailList" border>
<el-table-column label="顺序号" width="90" align="center">
<template slot-scope="scope">
<el-input-number v-model="scope.row.sortNo" :min="1" :precision="0" style="width: 80px" />
</template>
</el-table-column>
<el-table-column label="工序" min-width="240">
<template slot-scope="scope">
<el-select v-model="scope.row.processCode" placeholder="请选择工序" filterable style="width: 100%" @change="value => handleProcessChange(scope.row, value)">
<el-option v-for="item in processOptions" :key="item.processCode" :label="item.processCode + ' / ' + item.processName" :value="item.processCode" />
</el-select>
</template>
</el-table-column>
<el-table-column label="工序名称" prop="processName" min-width="180" />
<el-table-column label="是否必经" width="150" align="center">
<template slot-scope="scope">
<el-radio-group v-model="scope.row.requiredFlag">
<el-radio label="1"></el-radio>
<el-radio label="0"></el-radio>
</el-radio-group>
</template>
</el-table-column>
<el-table-column label="标准工时" width="140" align="center">
<template slot-scope="scope">
<el-input-number v-model="scope.row.standardWorkTime" :min="0" :precision="2" style="width: 120px" />
</template>
</el-table-column>
<el-table-column label="备注" min-width="160" align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.remark" placeholder="请输入备注" />
</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleRemoveDetail(scope.$index)"></el-button>
</template>
</el-table-column>
</el-table>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listProcessRoute, getProcessRoute, addProcessRoute, updateProcessRoute, delProcessRoute } from '@/api/production/processRoute'
import { listAllMaterialInfo } from '@/api/base/materialInfo'
import { listProcessStation } from '@/api/base/processStation'
export default {
name: 'ProcessRoute',
data() {
return {
loading: false,
showSearch: true,
total: 0,
routeList: [],
materialOptions: [],
processOptions: [],
ids: [],
single: true,
multiple: true,
open: false,
title: '',
statusOptions: [
{ label: '启用', value: '0' },
{ label: '停用', value: '1' }
],
queryParams: {
pageNum: 1,
pageSize: 10,
routeCode: undefined,
routeName: undefined,
materialCode: undefined,
status: undefined
},
form: {},
rules: {
routeCode: [{ required: true, message: '路线编码不能为空', trigger: 'blur' }],
routeName: [{ required: true, message: '路线名称不能为空', trigger: 'blur' }],
materialCode: [{ required: true, message: '物料不能为空', trigger: 'change' }]
}
}
},
created() {
this.loadOptions()
this.getList()
},
methods: {
getList() {
this.loading = true
listProcessRoute(this.queryParams).then(response => {
this.routeList = response.rows
this.total = response.total
this.loading = false
})
},
loadOptions() {
listAllMaterialInfo({ isFlag: 0 }).then(response => {
this.materialOptions = response.rows || response.data || []
})
listProcessStation({ pageNum: 1, pageSize: 999, processType: 1 }).then(response => {
this.processOptions = response.rows || response.data || []
})
},
reset() {
this.form = {
objId: undefined,
routeCode: undefined,
routeName: undefined,
materialCode: undefined,
materialName: undefined,
versionNo: 'V1',
status: '0',
remark: undefined,
detailList: []
}
this.resetForm('form')
},
cancel() {
this.open = false
this.reset()
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.resetForm('queryForm')
this.handleQuery()
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.objId)
this.single = selection.length !== 1
this.multiple = !selection.length
},
handleAdd() {
this.reset()
this.handleAddDetail()
this.open = true
this.title = '新增生产工艺路线'
},
handleUpdate(row) {
const objId = row.objId || this.ids[0]
this.reset()
getProcessRoute(objId).then(response => {
this.form = response.data
if (!this.form.detailList || !this.form.detailList.length) {
this.form.detailList = []
this.handleAddDetail()
}
this.open = true
this.title = '修改生产工艺路线'
})
},
handleDelete(row) {
const objIds = row.objId || this.ids
this.$modal.confirm('是否确认删除选中的工艺路线?').then(() => {
return delProcessRoute(objIds)
}).then(() => {
this.getList()
this.$modal.msgSuccess('删除成功')
})
},
handleExport() {
this.download('/production/processRoute/export', { ...this.queryParams }, `process_route_${new Date().getTime()}.xlsx`)
},
handleMaterialChange(value) {
const material = this.materialOptions.find(item => item.materialCode === value)
if (material) {
this.form.materialName = material.materialName
}
},
handleAddDetail() {
if (!this.form.detailList) {
this.form.detailList = []
}
this.form.detailList.push({
sortNo: this.form.detailList.length + 1,
processCode: undefined,
processName: undefined,
requiredFlag: '1',
standardWorkTime: 0,
remark: undefined
})
},
handleRemoveDetail(index) {
this.form.detailList.splice(index, 1)
},
handleProcessChange(row, value) {
const process = this.processOptions.find(item => item.processCode === value)
if (process) {
row.processName = process.processName
}
},
validateDetails() {
if (!this.form.detailList || this.form.detailList.length === 0) {
this.$modal.msgError('请至少维护一条工序明细')
return false
}
const duplicate = new Set()
for (const item of this.form.detailList) {
if (!item.processCode) {
this.$modal.msgError('工序明细不能为空')
return false
}
if (duplicate.has(item.processCode)) {
this.$modal.msgError('工序明细存在重复工序')
return false
}
duplicate.add(item.processCode)
}
return true
},
submitForm() {
this.$refs.form.validate(valid => {
if (!valid || !this.validateDetails()) {
return
}
const request = this.form.objId ? updateProcessRoute(this.form) : addProcessRoute(this.form)
request.then(() => {
this.$modal.msgSuccess(this.form.objId ? '修改成功' : '新增成功')
this.open = false
this.getList()
})
})
}
}
}
</script>
<style scoped>
.route-detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin: 8px 0 12px;
font-weight: 600;
}
</style>

@ -0,0 +1,283 @@
<template>
<div class="app-container">
<el-form ref="queryForm" :model="queryParams" size="small" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="工位" prop="stationCode">
<el-select v-model="queryParams.stationCode" placeholder="请选择工位" clearable filterable>
<el-option v-for="item in stationOptions" :key="item.productLineCode" :label="item.productLineName" :value="item.productLineCode" />
</el-select>
</el-form-item>
<el-form-item label="物料编码" prop="materialCode">
<el-input v-model="queryParams.materialCode" placeholder="请输入物料编码" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="物料名称" prop="materialName">
<el-input v-model="queryParams.materialName" placeholder="请输入物料名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['production:stationCapability:add']" type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"></el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['production:stationCapability:edit']" type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate"></el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['production:stationCapability:remove']" type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"></el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['production:stationCapability:export']" type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"></el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="stationCapabilityList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="工位编码" align="center" prop="stationCode" />
<el-table-column label="工位名称" align="center" prop="stationName" />
<el-table-column label="物料编码" align="center" prop="materialCode" />
<el-table-column label="物料名称" align="center" prop="materialName" show-overflow-tooltip />
<el-table-column label="标准节拍" align="center" prop="standardCt" />
<el-table-column label="班产能" align="center" prop="shiftCap" />
<el-table-column label="日产能" align="center" prop="dayCap" />
<el-table-column label="切换时长(分钟)" align="center" prop="changeoverMin" width="120" />
<el-table-column label="责任班组" align="center" prop="teamName" />
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<el-tag :type="scope.row.status === '0' ? 'success' : 'info'">{{ scope.row.status === '0' ? '启用' : '停用' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button v-hasPermi="['production:stationCapability:edit']" size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"></el-button>
<el-button v-hasPermi="['production:stationCapability:remove']" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
<el-dialog :title="title" :visible.sync="open" width="640px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="110px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="工位" prop="stationCode">
<el-select v-model="form.stationCode" placeholder="请选择工位" filterable style="width: 100%">
<el-option v-for="item in stationOptions" :key="item.productLineCode" :label="item.productLineName" :value="item.productLineCode" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="物料" prop="materialCode">
<el-select v-model="form.materialCode" placeholder="请选择物料" filterable style="width: 100%" @change="handleMaterialChange">
<el-option v-for="item in materialOptions" :key="item.materialCode" :label="item.materialCode + ' / ' + item.materialName" :value="item.materialCode" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="标准节拍" prop="standardCt">
<el-input-number v-model="form.standardCt" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="切换时长(分钟)" prop="changeoverMin">
<el-input-number v-model="form.changeoverMin" :min="0" :precision="0" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="班产能" prop="shiftCap">
<el-input-number v-model="form.shiftCap" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="日产能" prop="dayCap">
<el-input-number v-model="form.dayCap" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="责任班组" prop="teamCode">
<el-select v-model="form.teamCode" placeholder="请选择班组" clearable filterable style="width: 100%">
<el-option v-for="item in teamOptions" :key="item.teamCode" :label="item.teamName" :value="item.teamCode" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="item in statusOptions" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listStationCapability, getStationCapability, addStationCapability, updateStationCapability, delStationCapability } from '@/api/production/stationCapability'
import { findProductLineList } from '@/api/base/productLine'
import { listAllMaterialInfo } from '@/api/base/materialInfo'
import { getTeamMemberList } from '@/api/base/teamMembers'
export default {
name: 'StationCapability',
data() {
return {
loading: false,
showSearch: true,
total: 0,
stationCapabilityList: [],
stationOptions: [],
materialOptions: [],
teamOptions: [],
ids: [],
single: true,
multiple: true,
open: false,
title: '',
statusOptions: [
{ label: '启用', value: '0' },
{ label: '停用', value: '1' }
],
queryParams: {
pageNum: 1,
pageSize: 10,
stationCode: undefined,
materialCode: undefined,
materialName: undefined,
status: undefined
},
form: {},
rules: {
stationCode: [{ required: true, message: '工位不能为空', trigger: 'change' }],
materialCode: [{ required: true, message: '物料不能为空', trigger: 'change' }]
}
}
},
created() {
this.loadOptions()
this.getList()
},
methods: {
getList() {
this.loading = true
listStationCapability(this.queryParams).then(response => {
this.stationCapabilityList = response.rows
this.total = response.total
this.loading = false
})
},
loadOptions() {
findProductLineList({ productLineType: 2 }).then(response => {
this.stationOptions = response.data || response.rows || []
})
listAllMaterialInfo({ isFlag: 0 }).then(response => {
this.materialOptions = response.rows || response.data || []
})
getTeamMemberList({}).then(response => {
this.teamOptions = response.data || response.rows || []
})
},
cancel() {
this.open = false
this.reset()
},
reset() {
this.form = {
objId: undefined,
stationCode: undefined,
materialCode: undefined,
standardCt: 0,
shiftCap: 0,
dayCap: 0,
changeoverMin: 0,
teamCode: undefined,
status: '0',
remark: undefined
}
this.resetForm('form')
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.resetForm('queryForm')
this.handleQuery()
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.objId)
this.single = selection.length !== 1
this.multiple = !selection.length
},
handleAdd() {
this.reset()
this.open = true
this.title = '新增工位能力维护'
},
handleUpdate(row) {
this.reset()
const objId = row.objId || this.ids[0]
getStationCapability(objId).then(response => {
this.form = response.data
this.open = true
this.title = '修改工位能力维护'
})
},
handleDelete(row) {
const objIds = row.objId || this.ids
this.$modal.confirm('是否确认删除选中的工位能力数据?').then(() => {
return delStationCapability(objIds)
}).then(() => {
this.getList()
this.$modal.msgSuccess('删除成功')
})
},
handleExport() {
this.download('/production/stationCapability/export', { ...this.queryParams }, `station_capability_${new Date().getTime()}.xlsx`)
},
handleMaterialChange(value) {
const material = this.materialOptions.find(item => item.materialCode === value)
if (material) {
this.form.materialName = material.materialName
}
},
submitForm() {
this.$refs.form.validate(valid => {
if (!valid) {
return
}
const request = this.form.objId ? updateStationCapability(this.form) : addStationCapability(this.form)
request.then(() => {
this.$modal.msgSuccess(this.form.objId ? '修改成功' : '新增成功')
this.open = false
this.getList()
})
})
}
}
}
</script>

@ -0,0 +1,323 @@
<template>
<div class="app-container">
<el-form ref="queryForm" :model="queryParams" size="small" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="工单编号" prop="orderCode">
<el-input v-model="queryParams.orderCode" placeholder="请输入工单编号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="物料名称" prop="materialName">
<el-input v-model="queryParams.materialName" placeholder="请输入物料名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="任务分组" prop="taskBucket">
<el-select v-model="queryParams.taskBucket" placeholder="请选择任务分组" clearable>
<el-option v-for="item in bucketOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="执行状态" prop="executionStatus">
<el-select v-model="queryParams.executionStatus" placeholder="请选择执行状态" clearable>
<el-option v-for="item in executionOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="优先级" prop="priorityLevel">
<el-select v-model="queryParams.priorityLevel" placeholder="请选择优先级" clearable>
<el-option v-for="item in priorityOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['production:taskPool:add']" type="primary" plain icon="el-icon-plus" size="mini" :disabled="single" @click="handleConfig">/</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['production:taskPool:edit']" type="success" plain icon="el-icon-refresh-right" size="mini" :disabled="single" @click="handleStatus"></el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['production:taskPool:remove']" type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"></el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['production:taskPool:export']" type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"></el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="taskPoolList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="工单编号" align="center" prop="orderCode" width="150" />
<el-table-column label="任务分组" align="center" prop="taskBucket">
<template slot-scope="scope">
<el-tag :type="getBucketType(scope.row.taskBucket)">{{ getBucketLabel(scope.row.taskBucket) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="执行状态" align="center" prop="executionStatus">
<template slot-scope="scope">
<el-tag :type="getExecutionType(scope.row.executionStatus)">{{ getExecutionLabel(scope.row.executionStatus) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="物料名称" align="center" prop="materialName" show-overflow-tooltip />
<el-table-column label="计划数量" align="center" prop="orderAmount" />
<el-table-column label="已完工数量" align="center" prop="completeAmount" />
<el-table-column label="负责人" align="center" prop="ownerUserName" />
<el-table-column label="优先级" align="center" prop="priorityLevel">
<template slot-scope="scope">
<el-tag :type="getPriorityType(scope.row.priorityLevel)">{{ getPriorityLabel(scope.row.priorityLevel) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="设备编码" align="center" prop="deviceCode" />
<el-table-column label="计划结束日期" align="center" prop="endDate" width="110" />
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220">
<template slot-scope="scope">
<el-button v-hasPermi="['production:taskPool:edit']" size="mini" type="text" icon="el-icon-edit" @click="handleConfig(scope.row)"></el-button>
<el-button v-hasPermi="['production:taskPool:edit']" size="mini" type="text" icon="el-icon-refresh-right" @click="handleStatus(scope.row)"></el-button>
<el-button v-hasPermi="['production:taskPool:remove']" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
<el-dialog :title="configTitle" :visible.sync="configOpen" width="620px" append-to-body>
<el-form ref="configForm" :model="form" :rules="rules" label-width="110px">
<el-form-item label="工单编号" prop="orderCode">
<el-input v-model="form.orderCode" :disabled="!!form.objId || !!form.orderCode" />
</el-form-item>
<el-form-item label="负责人" prop="ownerUserId">
<el-select v-model="form.ownerUserId" placeholder="请选择负责人" clearable filterable style="width: 100%">
<el-option v-for="item in userOptions" :key="item.userId" :label="item.nickName || item.userName" :value="item.userId" />
</el-select>
</el-form-item>
<el-form-item label="优先级" prop="priorityLevel">
<el-select v-model="form.priorityLevel" placeholder="请选择优先级" style="width: 100%">
<el-option v-for="item in priorityOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="任务备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="4" placeholder="请输入任务备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitConfigForm"> </el-button>
<el-button @click="configOpen = false"> </el-button>
</div>
</el-dialog>
<el-dialog title="切换任务状态" :visible.sync="statusOpen" width="520px" append-to-body>
<el-form ref="statusForm" :model="statusForm" :rules="statusRules" label-width="110px">
<el-form-item label="工单编号" prop="orderCode">
<el-input v-model="statusForm.orderCode" disabled />
</el-form-item>
<el-form-item label="执行状态" prop="executionStatus">
<el-select v-model="statusForm.executionStatus" placeholder="请选择执行状态" style="width: 100%">
<el-option v-for="item in executionOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitStatusForm"> </el-button>
<el-button @click="statusOpen = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listTaskPool, getTaskPool, addTaskPool, updateTaskPool, changeTaskStatus, delTaskPool } from '@/api/production/taskPool'
import { selectUserList } from '@/api/system/user'
export default {
name: 'TaskPool',
data() {
return {
loading: false,
showSearch: true,
total: 0,
taskPoolList: [],
userOptions: [],
ids: [],
selectionRows: [],
single: true,
multiple: true,
configOpen: false,
statusOpen: false,
configTitle: '维护负责人/优先级',
bucketOptions: [
{ label: '待开工', value: 'PENDING' },
{ label: '生产中', value: 'RUNNING' },
{ label: '已完成', value: 'COMPLETED' },
{ label: '异常任务', value: 'ABNORMAL' },
{ label: '超时任务', value: 'OVERTIME' }
],
executionOptions: [
{ label: '待执行', value: 'PENDING' },
{ label: '运行中', value: 'RUNNING' },
{ label: '已完成', value: 'COMPLETED' },
{ label: '已暂停', value: 'PAUSED' }
],
priorityOptions: [
{ label: '紧急', value: 'URGENT' },
{ label: '高', value: 'HIGH' },
{ label: '普通', value: 'NORMAL' },
{ label: '低', value: 'LOW' }
],
queryParams: {
pageNum: 1,
pageSize: 10,
orderCode: undefined,
materialName: undefined,
taskBucket: undefined,
executionStatus: undefined,
priorityLevel: undefined
},
form: {},
statusForm: {},
rules: {
orderCode: [{ required: true, message: '工单编号不能为空', trigger: 'blur' }],
priorityLevel: [{ required: true, message: '优先级不能为空', trigger: 'change' }]
},
statusRules: {
executionStatus: [{ required: true, message: '执行状态不能为空', trigger: 'change' }]
}
}
},
created() {
this.getList()
selectUserList({}).then(response => {
this.userOptions = response.rows || response.data || []
})
},
methods: {
getList() {
this.loading = true
listTaskPool(this.queryParams).then(response => {
this.taskPoolList = response.rows
this.total = response.total
this.loading = false
})
},
getBucketLabel(value) {
const match = this.bucketOptions.find(item => item.value === value)
return match ? match.label : value
},
getBucketType(value) {
const map = { PENDING: 'info', RUNNING: 'warning', COMPLETED: 'success', ABNORMAL: 'danger', OVERTIME: '' }
return map[value] || 'info'
},
getExecutionLabel(value) {
const match = this.executionOptions.find(item => item.value === value)
return match ? match.label : value
},
getExecutionType(value) {
const map = { PENDING: 'info', RUNNING: 'warning', COMPLETED: 'success', PAUSED: '' }
return map[value] || 'info'
},
getPriorityLabel(value) {
const match = this.priorityOptions.find(item => item.value === value)
return match ? match.label : value
},
getPriorityType(value) {
const map = { URGENT: 'danger', HIGH: 'warning', NORMAL: 'success', LOW: 'info' }
return map[value] || 'info'
},
resetConfigForm() {
this.form = {
objId: undefined,
orderCode: undefined,
ownerUserId: undefined,
priorityLevel: 'NORMAL',
remark: undefined
}
this.resetForm('configForm')
},
resetStatusForm() {
this.statusForm = {
orderCode: undefined,
executionStatus: undefined
}
this.resetForm('statusForm')
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.resetForm('queryForm')
this.handleQuery()
},
handleSelectionChange(selection) {
this.selectionRows = selection
this.ids = selection.map(item => item.orderCode)
this.single = selection.length !== 1
this.multiple = !selection.length
},
handleConfig(row) {
const current = row || this.selectionRows[0]
if (!current) {
return
}
this.resetConfigForm()
getTaskPool(current.orderCode).then(response => {
const data = response.data || {}
this.form = {
objId: data.objId,
orderCode: data.orderCode || current.orderCode,
ownerUserId: data.ownerUserId,
priorityLevel: data.priorityLevel || 'NORMAL',
remark: data.remark
}
this.configOpen = true
})
},
handleStatus(row) {
const current = row || this.selectionRows[0]
if (!current) {
return
}
this.resetStatusForm()
this.statusForm.orderCode = current.orderCode
this.statusForm.executionStatus = current.executionStatus || 'PENDING'
this.statusOpen = true
},
handleDelete(row) {
const orderCodes = row ? row.orderCode : this.ids.join(',')
this.$modal.confirm('是否确认清除所选工单的任务池扩展配置?').then(() => {
return delTaskPool(orderCodes)
}).then(() => {
this.getList()
this.$modal.msgSuccess('清除成功')
})
},
handleExport() {
this.download('/production/taskPool/export', { ...this.queryParams }, `task_pool_${new Date().getTime()}.xlsx`)
},
submitConfigForm() {
this.$refs.configForm.validate(valid => {
if (!valid) {
return
}
const request = this.form.objId ? updateTaskPool(this.form) : addTaskPool(this.form)
request.then(() => {
this.$modal.msgSuccess('保存成功')
this.configOpen = false
this.getList()
})
})
},
submitStatusForm() {
this.$refs.statusForm.validate(valid => {
if (!valid) {
return
}
changeTaskStatus(this.statusForm).then(() => {
this.$modal.msgSuccess('状态切换成功')
this.statusOpen = false
this.getList()
})
})
}
}
}
</script>

@ -0,0 +1,293 @@
<template>
<div class="app-container">
<el-form ref="queryForm" :model="queryParams" size="small" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="班组" prop="teamCode">
<el-select v-model="queryParams.teamCode" placeholder="请选择班组" clearable filterable>
<el-option v-for="item in teamOptions" :key="item.teamCode" :label="item.teamName" :value="item.teamCode" />
</el-select>
</el-form-item>
<el-form-item label="责任产线" prop="lineCode">
<el-select v-model="queryParams.lineCode" placeholder="请选择产线" clearable filterable>
<el-option v-for="item in lineOptions" :key="item.productLineCode" :label="item.productLineName" :value="item.productLineCode" />
</el-select>
</el-form-item>
<el-form-item label="班次" prop="shiftCode">
<el-select v-model="queryParams.shiftCode" placeholder="请选择班次" clearable>
<el-option v-for="item in shiftOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="排班日期">
<el-date-picker v-model="daterangeShiftDate" type="daterange" value-format="yyyy-MM-dd" range-separator="-" start-placeholder="" end-placeholder="" style="width: 240px" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['production:teamShift:add']" type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"></el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['production:teamShift:edit']" type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate"></el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['production:teamShift:remove']" type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"></el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['production:teamShift:export']" type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"></el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="teamShiftList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="班组" align="center" prop="teamName" />
<el-table-column label="排班日期" align="center" prop="shiftDate" width="120" />
<el-table-column label="班次" align="center" prop="shiftCode">
<template slot-scope="scope">{{ getShiftLabel(scope.row.shiftCode) }}</template>
</el-table-column>
<el-table-column label="负责人" align="center" prop="leaderUserName" />
<el-table-column label="责任产线" align="center" prop="lineName" />
<el-table-column label="责任区域" align="center" prop="areaDesc" show-overflow-tooltip />
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<el-tag :type="scope.row.status === '0' ? 'success' : 'info'">{{ scope.row.status === '0' ? '启用' : '停用' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button v-hasPermi="['production:teamShift:edit']" size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"></el-button>
<el-button v-hasPermi="['production:teamShift:remove']" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
<el-dialog :title="title" :visible.sync="open" width="680px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="110px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="班组" prop="teamCode">
<el-select v-model="form.teamCode" placeholder="请选择班组" filterable style="width: 100%">
<el-option v-for="item in teamOptions" :key="item.teamCode" :label="item.teamName" :value="item.teamCode" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="班次" prop="shiftCode">
<el-select v-model="form.shiftCode" placeholder="请选择班次" style="width: 100%">
<el-option v-for="item in shiftOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="排班日期" prop="shiftDate">
<el-date-picker v-model="form.shiftDate" value-format="yyyy-MM-dd" type="date" placeholder="请选择日期" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="负责人" prop="leaderUserId">
<el-select v-model="form.leaderUserId" placeholder="请选择负责人" clearable filterable style="width: 100%">
<el-option v-for="item in userOptions" :key="item.userId" :label="item.nickName || item.userName" :value="item.userId" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="责任产线" prop="lineCode">
<el-select v-model="form.lineCode" placeholder="请选择产线" clearable filterable style="width: 100%">
<el-option v-for="item in lineOptions" :key="item.productLineCode" :label="item.productLineName" :value="item.productLineCode" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="item in statusOptions" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="责任区域" prop="areaDesc">
<el-input v-model="form.areaDesc" placeholder="请输入责任区域" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listTeamShift, getTeamShift, addTeamShift, updateTeamShift, delTeamShift } from '@/api/production/teamShift'
import { getTeamMemberList } from '@/api/base/teamMembers'
import { findProductLineList } from '@/api/base/productLine'
import { selectUserList } from '@/api/system/user'
export default {
name: 'TeamShift',
data() {
return {
loading: false,
showSearch: true,
total: 0,
teamShiftList: [],
teamOptions: [],
lineOptions: [],
userOptions: [],
daterangeShiftDate: [],
ids: [],
single: true,
multiple: true,
open: false,
title: '',
shiftOptions: [
{ label: '白班', value: 'DAY' },
{ label: '夜班', value: 'NIGHT' },
{ label: '中班', value: 'MIDDLE' }
],
statusOptions: [
{ label: '启用', value: '0' },
{ label: '停用', value: '1' }
],
queryParams: {
pageNum: 1,
pageSize: 10,
teamCode: undefined,
lineCode: undefined,
shiftCode: undefined,
status: undefined,
params: {}
},
form: {},
rules: {
teamCode: [{ required: true, message: '班组不能为空', trigger: 'change' }],
shiftDate: [{ required: true, message: '排班日期不能为空', trigger: 'change' }],
shiftCode: [{ required: true, message: '班次不能为空', trigger: 'change' }]
}
}
},
created() {
this.loadOptions()
this.getList()
},
methods: {
getList() {
this.loading = true
this.queryParams.params = {}
if (this.daterangeShiftDate && this.daterangeShiftDate.length === 2) {
this.queryParams.params.beginShiftDate = this.daterangeShiftDate[0]
this.queryParams.params.endShiftDate = this.daterangeShiftDate[1]
}
listTeamShift(this.queryParams).then(response => {
this.teamShiftList = response.rows
this.total = response.total
this.loading = false
})
},
loadOptions() {
getTeamMemberList({}).then(response => {
this.teamOptions = response.data || response.rows || []
})
findProductLineList({ productLineType: 1 }).then(response => {
this.lineOptions = response.data || response.rows || []
})
selectUserList({}).then(response => {
this.userOptions = response.rows || response.data || []
})
},
getShiftLabel(value) {
const match = this.shiftOptions.find(item => item.value === value)
return match ? match.label : value
},
cancel() {
this.open = false
this.reset()
},
reset() {
this.form = {
objId: undefined,
teamCode: undefined,
shiftDate: undefined,
shiftCode: 'DAY',
leaderUserId: undefined,
lineCode: undefined,
areaDesc: undefined,
status: '0',
remark: undefined
}
this.resetForm('form')
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.daterangeShiftDate = []
this.resetForm('queryForm')
this.handleQuery()
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.objId)
this.single = selection.length !== 1
this.multiple = !selection.length
},
handleAdd() {
this.reset()
this.open = true
this.title = '新增班组排班维护'
},
handleUpdate(row) {
const objId = row.objId || this.ids[0]
this.reset()
getTeamShift(objId).then(response => {
this.form = response.data
this.open = true
this.title = '修改班组排班维护'
})
},
handleDelete(row) {
const objIds = row.objId || this.ids
this.$modal.confirm('是否确认删除选中的班组排班数据?').then(() => {
return delTeamShift(objIds)
}).then(() => {
this.getList()
this.$modal.msgSuccess('删除成功')
})
},
handleExport() {
this.download('/production/teamShift/export', { ...this.queryParams }, `team_shift_${new Date().getTime()}.xlsx`)
},
submitForm() {
this.$refs.form.validate(valid => {
if (!valid) {
return
}
const request = this.form.objId ? updateTeamShift(this.form) : addTeamShift(this.form)
request.then(() => {
this.$modal.msgSuccess(this.form.objId ? '修改成功' : '新增成功')
this.open = false
this.getList()
})
})
}
}
}
</script>

@ -205,8 +205,8 @@ export default {
beginBeginTime: null,
endBeginTime: null,
productLineCode: null,
beginStationCode: '109',
endStationCode: '270',
beginStationCode: null,
endStationCode: null,
ORDER_CODE: null,
MATERIAL_CODE: null,
MATERIAL_NAME: null,
@ -244,10 +244,10 @@ export default {
}
},
created() {
findProductLineList({ productLineType: 1 }).then(response => {
findProductLineList({ }).then(response => {
this.productLineList = response.data
}).then(() => {
return findProductLineList({ productLineType: 2, parentId: this.queryParams.productLineCode, executionSort: '1' })
return findProductLineList({ parentId: this.queryParams.productLineCode, executionSort: '1' })
}).then(response => {
this.findStationList = response.data.filter(station => station.executionSort != null)
})

@ -89,7 +89,6 @@ export default {
tableData: [],
form: {
year: new Date().getFullYear().toString(),
productionLine: 'CX_02'
},
tableTimeHead: [],
tableHead: ['今年1月', '去年1月', '今年2月', '去年2月', '今年3月', '去年3月', '今年4月', '去年4月', '今年5月', '去年5月', '今年6月', '去年6月', '今年7月', '去年7月', '今年8月', '去年8月', '今年9月', '去年9月', '今年10月', '去年10月', '今年11月', '去年11月', '今年12月', '去年12月',],

@ -251,7 +251,7 @@ export default {
boxCode: null,
beginBeginTime: null,
endBeginTime: null,
productLineCode: 'CX_02'
productLineCode: null
},
//
queryDetailParams: {},

@ -229,7 +229,7 @@ export default {
beginBeginTime: null,
endBeginTime: null,
BOX_CODE: null,
PRODUCT_LINE_CODE: 'CX_02',
PRODUCT_LINE_CODE: null,
ORDER_CODE: null,
MATERIAL_CODE: null,
MATERIAL_NAME: null,

@ -152,7 +152,7 @@ export default {
stationCodeList: null,
dateType: 10,
alarmType: 1,
PRODUCT_LINE_CODE: 'CX_02',
PRODUCT_LINE_CODE: null,
ORDER_CODE: null,
MATERIAL_CODE: null,
MATERIAL_NAME: null,

@ -146,7 +146,7 @@ export default {
beginBeginTime: null,
endBeginTime: null,
WORK_CENTER_CODE: null,
PRODUCT_LINE_CODE: 'CX_02',
PRODUCT_LINE_CODE: null,
PRODUCT_LINE_NAME: null,
MATERIAL_MODEL: null,
MATERIAL_CODE: null,

@ -105,7 +105,7 @@ export default {
pageNum: 1,
pageSize: 10,
FACTORY_CODE: '1301',
productionLine: 'CX_02',
productionLine: null,
MATERIAL_MODEL: '',
date: []
},

@ -1,16 +1,16 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="产线" prop="PRODUCT_LINE_NAME">
<el-select v-model="queryParams.WORK_CENTER_CODE" placeholder="请选择产线">
<el-option
v-for="item in productLineList"
:key="item.workCenterCode"
:label="item.productLineName"
:value="item.workCenterCode"
></el-option>
</el-select>
</el-form-item>
<!-- <el-form-item label="产线" prop="PRODUCT_LINE_NAME">-->
<!-- <el-select v-model="queryParams.WORK_CENTER_CODE" placeholder="请选择产线">-->
<!-- <el-option-->
<!-- v-for="item in productLineList"-->
<!-- :key="item.workCenterCode"-->
<!-- :label="item.productLineName"-->
<!-- :value="item.workCenterCode"-->
<!-- ></el-option>-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<el-form-item label="产品编码" prop="MATERIAL_CODE">
<el-input
v-model="queryParams.MATERIAL_CODE"
@ -132,7 +132,7 @@ export default {
pageSize: 10,
beginBeginTime: null,
endBeginTime: null,
WORK_CENTER_CODE: '3103',
WORK_CENTER_CODE: null,
PRODUCT_LINE_NAME: null,
ORDER_CODE: null,
MATERIAL_CODE: null,
@ -159,7 +159,7 @@ export default {
}
},
created() {
findProductLineList({ productLineType: 1 }).then(response => {
findProductLineList({}).then(response => {
this.productLineList = response.data
})
const nowDate = parseTime(new Date(), '{y}-{m}-{d}')

@ -11,7 +11,7 @@
></el-option>
</el-select>
</el-form-item>
<!-- <el-form-item label="物料编码" prop="productCode">-->
<!-- <el-input-->
<!-- v-model="queryParams.productCode"-->
@ -20,7 +20,7 @@
<!-- @keyup.enter.native="handleQuery"-->
<!-- />-->
<!-- </el-form-item>-->
<el-form-item label="过点时间">
<el-date-picker
v-model="daterangeBeginTime"
@ -127,7 +127,7 @@ export default {
queryParams: {
pageNum: 1,
pageSize: 10,
productLineName: '二线',
productLineName: null,
orderCode:null,
boxCode:null,
boxName:null,

@ -157,7 +157,7 @@ export default {
updatedBy: null,
updatedTime: null,
perfusionResult: null,
PRODUCT_LINE_CODE: 'CX_02',
PRODUCT_LINE_CODE: null,
yield: null,
finishStatus: null
},

@ -236,7 +236,7 @@ export default {
beginBeginTime: null,
endBeginTime: null,
BOX_CODE: null,
PRODUCT_LINE_CODE: 'CX_02',
PRODUCT_LINE_CODE: null,
ORDER_CODE: null,
MATERIAL_CODE: null,
MATERIAL_NAME: null,

@ -207,7 +207,7 @@ export default {
endBeginTime: null,
STATION_CODE: [],
stationCodeList: null,
PRODUCT_LINE_CODE: 'CX_02',
PRODUCT_LINE_CODE: null,
ORDER_CODE: null,
MATERIAL_CODE: null,
MATERIAL_NAME: null,

@ -161,7 +161,7 @@ export default {
beginBeginTime: null,
endBeginTime: null,
WORK_CENTER_CODE: null,
PRODUCT_LINE_CODE: 'CX_02',
PRODUCT_LINE_CODE: null,
ORDER_CODE: null,
MATERIAL_CODE: null,
MATERIAL_NAME: null,

@ -172,7 +172,7 @@ export default {
endBeginTime: null,
STATION_CODE: [],
stationCodeList: null,
PRODUCT_LINE_CODE: 'CX_02',
PRODUCT_LINE_CODE: null,
ORDER_CODE: null,
MATERIAL_CODE: null,
MATERIAL_NAME: null,

@ -178,7 +178,7 @@ export default {
boxCode: null,
beginBeginTime: null,
endBeginTime: null,
productLineCode: 'CX_02'
productLineCode: null
},
//
daterangeBeginTime: [],

@ -102,7 +102,7 @@ export default {
pageNum: 1,
pageSize: 10,
month:"0",
PRODUCT_LINE_CODE:"CX_02",
PRODUCT_LINE_CODE:null,
},
// 线
productLineList: [],

Loading…
Cancel
Save