refactor(bom): 重构BOM管理功能优化物料选择和订单维护流程

- 注释掉废弃的自动同步BOM接口定义
- 将物料编码输入框改为下拉选择支持模糊搜索
- 实现选择物料后自动填充物料名称、类别和工厂编号
- 新增PDA订单维护卡片支持按物料关键字查询工单
- 为工单信息页面添加物料选择器组件和相关校验规则
- 重构参数追溯页面为设备参数分析中心包含异常、趋势和切换三大模块
- 在物料信息页面增加导入功能支持Excel批量导入物料数据
- 统一工单维护权限检查并优化批量操作按钮布局
master
zangch@mesnac.com 1 week ago
parent bd059b7e7c
commit 28423e07f0

@ -11,8 +11,13 @@ export function listFactory(query) {
// 查询工厂下拉框列表
export function findFactoryList(query) {
return listAllFactory(query)
}
// 查询工厂全量列表(不分页)
export function listAllFactory(query) {
return request({
url: '/base/factory/findFactoryList',
url: '/base/factory/all',
method: 'get',
params: query
})

@ -9,6 +9,15 @@ export function listMaterialInfo(query) {
})
}
// 查询全部物料信息列表
export function listAllMaterialInfo(query) {
return request({
url: '/base/materialInfo/all',
method: 'get',
params: query
})
}
// 查询物料信息详细
export function getMaterialInfo(objId) {
return request({

@ -44,7 +44,6 @@ export function releaseOrderPlan(data) {
})
}
// 删除工单信息
export function delOrderInfo(objId) {
return request({
@ -139,3 +138,21 @@ export function batchCompleteProduction(data) {
data: data
})
}
// PDA按物料关键字查询订单
export function listPdaOrdersByMaterial(keyword) {
return request({
url: '/dms/mobile/order/listByMaterial',
method: 'get',
params: { keyword }
})
}
// PDA维护订单状态与数量
export function updatePdaOrderStatusAndQty(data) {
return request({
url: '/dms/mobile/order/updateStatusAndQty',
method: 'post',
data: data
})
}

@ -10,14 +10,15 @@ export function listBaseBomInfo(query) {
}
// 自动同步生产BOM
export function addAutomaticSynchronizationBOM(query) {
return request({
url: '/production/baseBomInfo/addAutomaticSynchronizationBOM',
method: 'get',
params: query,
timeout: 60000
})
}
// 当前业务已经改为全部手动维护树形BOM这里注释掉历史同步接口定义避免前端继续调用。
// export function addAutomaticSynchronizationBOM(query) {
// return request({
// url: '/production/baseBomInfo/addAutomaticSynchronizationBOM',
// method: 'get',
// params: query,
// timeout: 60000
// })
// }
// 查询生产BOM列表
export function listTreeBaseBomInfo(query) {
return request({

@ -128,6 +128,16 @@
v-hasPermi="['base:materialInfo:export']"
>导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="el-icon-upload2"
size="mini"
@click="handleImport"
v-hasPermi="['base:materialInfo:add']"
>导入</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row>
@ -305,6 +315,47 @@
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<el-dialog :title="upload.title" :visible.sync="upload.open" width="420px" append-to-body>
<el-upload
ref="upload"
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
:action="upload.url + '?updateSupport=' + upload.updateSupport"
:data="{ plantCode: upload.plantCode }"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:on-error="handleFileError"
:auto-upload="false"
drag
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip text-center" slot="tip">
<div class="el-upload__tip" slot="tip">
<el-checkbox v-model="upload.updateSupport"></el-checkbox>
</div>
<div style="margin-top: 8px;">
<el-select v-model="upload.plantCode" placeholder="请选择所属工厂" size="mini" style="width: 220px;">
<el-option
v-for="item in factoryList"
:key="item.factoryCode"
:label="item.factoryName"
:value="item.factoryCode"
></el-option>
</el-select>
</div>
<div style="margin-top: 8px;">仅允许导入xlsxlsx格式文件</div>
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate"></el-link>
</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
@ -312,6 +363,7 @@
import { listMaterialInfo, getMaterialInfo, delMaterialInfo, addMaterialInfo, updateMaterialInfo } from "@/api/base/materialInfo";
import { findFactoryList } from "@/api/base/factory";
import { findProductLineList } from "@/api/base/productLine";
import { getToken } from "@/utils/auth";
export default {
name: "MaterialInfo",
@ -389,12 +441,26 @@ export default {
factoryList: [],
// 线
productLineList: [],
//
upload: {
open: false,
title: "",
isUploading: false,
updateSupport: false,
plantCode: null,
headers: { Authorization: "Bearer " + getToken() },
url: process.env.VUE_APP_BASE_API + "/base/materialInfo/importData"
}
};
},
created() {
this.getList();
findFactoryList().then(response => {
this.factoryList = response.data;
if (!this.upload.plantCode && this.factoryList.length > 0) {
const defaultFactory = this.factoryList.find(item => item.factoryCode === '2010') || this.factoryList[0];
this.upload.plantCode = defaultFactory.factoryCode;
}
});
findProductLineList().then(response => {
this.productLineList = response.data;
@ -507,6 +573,46 @@ export default {
this.download('base/materialInfo/export', {
...this.queryParams
}, `materialInfo_${new Date().getTime()}.xlsx`)
},
/** 导入按钮操作 */
handleImport() {
this.upload.title = "物料导入";
this.upload.open = true;
},
/** 下载模板 */
importTemplate() {
this.download('base/materialInfo/importTemplate', {}, `materialInfo_template_${new Date().getTime()}.xlsx`)
},
//
handleFileUploadProgress() {
this.upload.isUploading = true;
},
//
handleFileSuccess(response) {
this.upload.open = false;
this.upload.isUploading = false;
this.$refs.upload.clearFiles();
if (response.code === 200) {
this.$alert("<div style='overflow:auto;overflow-x:hidden;max-height:70vh;padding:10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true });
this.getList();
} else {
this.$modal.msgError(response.msg || "导入失败");
}
},
//
handleFileError() {
this.upload.open = false;
this.upload.isUploading = false;
this.$refs.upload.clearFiles();
this.$modal.msgError("文件上传失败,请重试");
},
//
submitFileForm() {
if (!this.upload.plantCode) {
this.$modal.msgWarning("请选择所属工厂");
return;
}
this.$refs.upload.submit();
}
}
};

@ -131,13 +131,26 @@
<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-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="请输入物料名称" />
<el-input v-model="form.materialName" placeholder="请选择物料后自动带出" disabled />
</el-form-item>
<el-form-item label="物料类别" prop="materialType">
<el-select v-model="form.materialType" placeholder="请选择物料类别">
<el-select v-model="form.materialType" placeholder="请选择物料类别" disabled>
<el-option
v-for="dict in dict.type.material_type"
:key="dict.value"
@ -150,7 +163,7 @@
<el-input v-model="form.standardAmount" 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="请输入排序" />
@ -187,6 +200,7 @@ import {
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";
@ -206,6 +220,8 @@ export default {
orderBomInfoList: [],
// BOM
orderBomInfoOptions: [],
//
materialOptions: [],
//
title: "",
//
@ -242,7 +258,7 @@ export default {
},
created() {
this.queryParams.ancestors = this.$route.params && this.$route.params.materialCode;
console.log( this.queryParams.ancestors)
this.loadMaterialOptions();
this.getList();
},
methods: {
@ -274,6 +290,30 @@ export default {
this.orderBomInfoOptions.push(data);
});
},
/** 加载全部物料下拉数据 */
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) {
// BOMBOM
this.form.bomCode = selectedMaterial.materialCode;
}
},
//
cancel() {
this.open = false;
@ -345,6 +385,7 @@ export default {
}
getOrderBomInfo(row.objId).then(response => {
this.form = response.data;
this.handleMaterialChange(this.form.materialCode);
this.open = true;
this.title = "修改订单BOM";
});

@ -1,7 +1,7 @@
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" class="search-form">
<el-form ref="queryForm" :model="queryParams" size="small" :inline="true" class="search-form">
<el-form-item label="设备编号" prop="deviceCode">
<el-input v-model="queryParams.deviceCode" placeholder="请输入设备编号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
@ -19,39 +19,96 @@
</el-form-item>
</el-form>
<el-card class="maintenance-card" shadow="never">
<div slot="header">
<span>PDA订单维护</span>
</div>
<!--
该区域用于模拟/承接PDA订单维护链路
1. 先按物料关键字查询候选订单
2. 再从下拉框中明确选中一条工单
3. 最后只维护执行状态与完工数量避免一次表单承载过多业务字段
-->
<el-form :model="pdaMaintainForm" size="small" :inline="true" label-width="80px">
<el-form-item label="物料关键字">
<el-input
v-model="pdaMaintainForm.materialKeyword"
placeholder="请输入物料编码或名称"
clearable
style="width: 220px"
@keyup.enter.native="handlePdaOrderSearch"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" :loading="pdaOrderLoading" @click="handlePdaOrderSearch"></el-button>
</el-form-item>
<el-form-item label="订单选择">
<el-select
v-model="pdaMaintainForm.objId"
placeholder="请先查询再选择订单"
filterable
clearable
style="width: 360px"
@change="handlePdaOrderChange"
>
<el-option
v-for="item in pdaOrderOptions"
:key="item.objId"
:label="buildPdaOrderOptionLabel(item)"
:value="item.objId"
/>
</el-select>
</el-form-item>
<el-form-item label="执行状态">
<el-select v-model="pdaMaintainForm.executionStatus" placeholder="请选择执行状态" clearable style="width: 140px">
<el-option label="待执行" value="PENDING" />
<el-option label="运行中" value="RUNNING" />
<el-option label="已完成" value="COMPLETED" />
<el-option label="已暂停" value="PAUSED" />
</el-select>
</el-form-item>
<el-form-item label="完工数量">
<el-input-number v-model="pdaMaintainForm.actualCompleteQty" :min="0" :precision="0" style="width: 140px" />
</el-form-item>
<el-form-item>
<el-button type="success" icon="el-icon-check" size="mini" :loading="pdaMaintainSubmitting" @click="submitPdaOrderMaintain"></el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 批量操作按钮 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
v-hasPermi="['base:orderInfo:edit']"
type="primary"
plain
icon="el-icon-video-play"
size="mini"
:disabled="multiple"
@click="handleBatchStart"
v-hasPermi="['base:orderInfo:edit']"
>批量开始生产</el-button>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['base:orderInfo:edit']"
type="warning"
plain
icon="el-icon-edit"
size="mini"
:disabled="multiple"
@click="handleBatchUpdateQty"
v-hasPermi="['base:orderInfo:edit']"
>批量更新数量</el-button>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['base:orderInfo:edit']"
type="success"
plain
icon="el-icon-check"
size="mini"
:disabled="multiple"
@click="handleBatchComplete"
v-hasPermi="['base:orderInfo:edit']"
>批量完工提报</el-button>
</el-col>
</el-row>
@ -263,15 +320,18 @@
</template>
<script>
import { listOrderInfo, startProduction, completeProduction, updateQuantity, batchStartProduction, batchUpdateQuantity, batchCompleteProduction } from "@/api/base/orderInfo";
import { listOrderInfo, startProduction, completeProduction, updateQuantity, batchStartProduction, batchUpdateQuantity, batchCompleteProduction, listPdaOrdersByMaterial, updatePdaOrderStatusAndQty } from '@/api/base/orderInfo'
export default {
name: "OrderExecution",
name: 'OrderExecution',
data() {
return {
loading: false,
total: 0,
orderList: [],
pdaOrderOptions: [],
pdaOrderLoading: false,
pdaMaintainSubmitting: false,
selectedIds: [],
multiple: true,
queryParams: {
@ -315,68 +375,138 @@ export default {
completeQty: 0,
defectQty: 0
},
detailOrder: {}
};
detailOrder: {},
pdaMaintainForm: {
materialKeyword: '',
objId: null,
orderCode: '',
executionStatus: 'PENDING',
actualCompleteQty: 0
}
}
},
created() {
this.getList();
this.getList()
},
methods: {
getList() {
this.loading = true;
this.loading = true
listOrderInfo(this.queryParams)
.then(response => {
const data = response && response.data ? response.data : response;
const data = response && response.data ? response.data : response
if (Array.isArray(data)) {
this.orderList = data;
this.total = data.length;
this.orderList = data
this.total = data.length
} else {
this.orderList = (data && data.rows) ? data.rows : [];
this.total = (data && data.total) ? data.total : this.orderList.length;
this.orderList = (data && data.rows) ? data.rows : []
this.total = (data && data.total) ? data.total : this.orderList.length
}
})
.catch(() => {
this.orderList = [];
this.total = 0;
this.orderList = []
this.total = 0
})
.finally(() => {
this.loading = false;
});
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
this.resetForm('queryForm')
this.handleQuery()
},
getStatusType(status) {
const map = { 'RUNNING': 'warning', 'COMPLETED': 'success', 'PAUSED': 'info', 'PENDING': '' };
return map[status] || '';
const map = { 'RUNNING': 'warning', 'COMPLETED': 'success', 'PAUSED': 'info', 'PENDING': '' }
return map[status] || ''
},
getStatusText(status) {
const map = { 'RUNNING': '运行中', 'COMPLETED': '已完成', 'PAUSED': '已暂停', 'PENDING': '待执行' };
return map[status] || '待执行';
const map = { 'RUNNING': '运行中', 'COMPLETED': '已完成', 'PAUSED': '已暂停', 'PENDING': '待执行' }
return map[status] || '待执行'
},
buildPdaOrderOptionLabel(row) {
//
return `${row.materialName || '-'} / ${row.orderCode || '-'} / 当前状态:${this.getStatusText(row.executionStatus)}`
},
handlePdaOrderSearch() {
const keyword = this.pdaMaintainForm.materialKeyword && this.pdaMaintainForm.materialKeyword.trim()
if (!keyword) {
this.$modal.msgWarning('请输入物料关键字')
return
}
this.pdaOrderLoading = true
listPdaOrdersByMaterial(keyword).then(response => {
const data = response && response.data ? response.data : response
this.pdaOrderOptions = Array.isArray(data) ? data : []
if (!this.pdaOrderOptions.length) {
this.$modal.msgWarning('未查询到关联工单')
this.pdaMaintainForm.objId = null
this.pdaMaintainForm.orderCode = ''
return
}
if (!this.pdaMaintainForm.objId && this.pdaOrderOptions.length === 1) {
// PDA
this.handlePdaOrderChange(this.pdaOrderOptions[0].objId)
}
}).finally(() => {
this.pdaOrderLoading = false
})
},
handlePdaOrderChange(objId) {
const current = this.pdaOrderOptions.find(item => item.objId === objId)
if (!current) {
this.pdaMaintainForm.orderCode = ''
return
}
//
this.pdaMaintainForm.orderCode = current.orderCode
this.pdaMaintainForm.executionStatus = current.executionStatus || 'PENDING'
this.pdaMaintainForm.actualCompleteQty = current.actualCompleteQty != null ? current.actualCompleteQty : (current.completeAmount || 0)
},
submitPdaOrderMaintain() {
if (!this.pdaMaintainForm.objId) {
this.$modal.msgWarning('请选择订单')
return
}
if (!this.pdaMaintainForm.executionStatus) {
this.$modal.msgWarning('请选择执行状态')
return
}
this.pdaMaintainSubmitting = true
updatePdaOrderStatusAndQty({
objId: this.pdaMaintainForm.objId,
orderCode: this.pdaMaintainForm.orderCode,
executionStatus: this.pdaMaintainForm.executionStatus,
actualCompleteQty: this.pdaMaintainForm.actualCompleteQty
}).then(() => {
//
this.$modal.msgSuccess('订单维护成功')
this.getList()
this.handlePdaOrderSearch()
}).finally(() => {
this.pdaMaintainSubmitting = false
})
},
handleStart(row) {
this.startForm = {
orderCode: row.orderCode,
materialName: row.materialName,
operator: this.$store.state.user.name || ''
};
this.startDialogVisible = true;
}
this.startDialogVisible = true
},
confirmStart() {
if (!this.startForm.operator) {
this.$modal.msgWarning("请输入操作员");
return;
this.$modal.msgWarning('请输入操作员')
return
}
startProduction(this.startForm.orderCode, this.startForm.operator).then(response => {
this.$modal.msgSuccess("开始生产成功");
this.startDialogVisible = false;
this.getList();
});
this.$modal.msgSuccess('开始生产成功')
this.startDialogVisible = false
this.getList()
})
},
handleUpdateQty(row) {
this.updateForm = {
@ -384,15 +514,15 @@ export default {
planQty: row.orderAmount,
completeQty: row.actualCompleteQty || 0,
defectQty: row.actualDefectQty || 0
};
this.updateQtyDialogVisible = true;
}
this.updateQtyDialogVisible = true
},
confirmUpdateQty() {
updateQuantity(this.updateForm.orderCode, this.updateForm.completeQty, this.updateForm.defectQty).then(response => {
this.$modal.msgSuccess("更新数量成功");
this.updateQtyDialogVisible = false;
this.getList();
});
this.$modal.msgSuccess('更新数量成功')
this.updateQtyDialogVisible = false
this.getList()
})
},
handleComplete(row) {
this.completeForm = {
@ -400,84 +530,84 @@ export default {
planQty: row.orderAmount,
completeQty: row.actualCompleteQty || 0,
defectQty: row.actualDefectQty || 0
};
this.completeDialogVisible = true;
}
this.completeDialogVisible = true
},
confirmComplete() {
this.$modal.confirm('确认完工提报?完工后工单状态将变为已完成').then(() => {
completeProduction(this.completeForm.orderCode, this.completeForm.completeQty, this.completeForm.defectQty).then(response => {
this.$modal.msgSuccess("完工提报成功");
this.completeDialogVisible = false;
this.getList();
});
});
this.$modal.msgSuccess('完工提报成功')
this.completeDialogVisible = false
this.getList()
})
})
},
handleDetail(row) {
this.detailOrder = row;
this.detailDialogVisible = true;
this.detailOrder = row
this.detailDialogVisible = true
},
handleSelectionChange(selection) {
this.selectedIds = selection.map(item => item.objId);
this.multiple = !selection.length;
this.selectedIds = selection.map(item => item.objId)
this.multiple = !selection.length
},
handleBatchStart() {
if (this.selectedIds.length === 0) {
this.$modal.msgWarning("请至少选择一条工单");
return;
this.$modal.msgWarning('请至少选择一条工单')
return
}
this.batchStartForm = {
operator: this.$store.state.user.name || ''
};
this.batchStartDialogVisible = true;
}
this.batchStartDialogVisible = true
},
confirmBatchStart() {
if (!this.batchStartForm.operator) {
this.$modal.msgWarning("请输入操作员");
return;
this.$modal.msgWarning('请输入操作员')
return
}
const data = {
objIds: this.selectedIds,
operator: this.batchStartForm.operator
};
}
batchStartProduction(data).then(response => {
this.$modal.msgSuccess("批量开始生产成功");
this.batchStartDialogVisible = false;
this.getList();
});
this.$modal.msgSuccess('批量开始生产成功')
this.batchStartDialogVisible = false
this.getList()
})
},
handleBatchUpdateQty() {
if (this.selectedIds.length === 0) {
this.$modal.msgWarning("请至少选择一条工单");
return;
this.$modal.msgWarning('请至少选择一条工单')
return
}
this.batchUpdateForm = {
completeQty: 0,
defectQty: 0
};
this.batchUpdateQtyDialogVisible = true;
}
this.batchUpdateQtyDialogVisible = true
},
confirmBatchUpdateQty() {
const data = {
objIds: this.selectedIds,
completeQty: this.batchUpdateForm.completeQty,
defectQty: this.batchUpdateForm.defectQty
};
}
batchUpdateQuantity(data).then(response => {
this.$modal.msgSuccess("批量更新数量成功");
this.batchUpdateQtyDialogVisible = false;
this.getList();
});
this.$modal.msgSuccess('批量更新数量成功')
this.batchUpdateQtyDialogVisible = false
this.getList()
})
},
handleBatchComplete() {
if (this.selectedIds.length === 0) {
this.$modal.msgWarning("请至少选择一条工单");
return;
this.$modal.msgWarning('请至少选择一条工单')
return
}
this.batchCompleteForm = {
completeQty: 0,
defectQty: 0
};
this.batchCompleteDialogVisible = true;
}
this.batchCompleteDialogVisible = true
},
confirmBatchComplete() {
this.$modal.confirm('确认批量完工提报?完工后工单状态将变为已完成').then(() => {
@ -485,20 +615,24 @@ export default {
objIds: this.selectedIds,
completeQty: this.batchCompleteForm.completeQty,
defectQty: this.batchCompleteForm.defectQty
};
}
batchCompleteProduction(data).then(response => {
this.$modal.msgSuccess("批量完工提报成功");
this.batchCompleteDialogVisible = false;
this.getList();
});
});
this.$modal.msgSuccess('批量完工提报成功')
this.batchCompleteDialogVisible = false
this.getList()
})
})
}
}
};
}
</script>
<style scoped>
.search-form {
margin-bottom: 15px;
}
.maintenance-card {
margin-bottom: 15px;
}
</style>

@ -249,7 +249,7 @@
/>
<!-- 添加或修改工单信息对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-dialog :title="title" :visible.sync="open" width="620px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-form-item label="SAP计划编号" prop="orderCode">
<el-input v-model="form.orderCode" placeholder="请输入SAP计划编号"/>
@ -271,14 +271,16 @@
<el-input v-model="form.saleOrderLineNumber" placeholder="请输入销售订单行号"/>
</el-form-item>
<el-form-item label="物料编码" prop="materialCode">
<el-input v-model="form.materialCode" placeholder="请输入物料编码"/>
<el-input v-model="form.materialCode" placeholder="请选择物料编码" readonly>
<el-button slot="append" icon="el-icon-search" @click="openMaterialSelector"></el-button>
</el-input>
</el-form-item>
<el-form-item label="物料名称" prop="materialName">
<el-input v-model="form.materialName" placeholder="请输入物料名称"/>
<el-input v-model="form.materialName" placeholder="请选择物料名称" readonly/>
</el-form-item>
<el-form-item label="物料组" prop="matkl">
<el-input v-model="form.matkl" placeholder="选择物料后自动带出" readonly/>
</el-form-item>
<!-- <el-form-item label="物料组" prop="matkl">-->
<!-- <el-input v-model="form.matkl" placeholder="请输入物料组"/>-->
<!-- </el-form-item>-->
<el-form-item label="订单计划数量" prop="orderAmount">
<el-input v-model="form.orderAmount" placeholder="请输入订单计划数量"/>
</el-form-item>
@ -370,6 +372,12 @@
</div>
</el-dialog>
<material-selector
:visible.sync="materialSelector.open"
:selected-material-code="form.materialCode"
@confirm="handleMaterialSelected"
/>
</div>
</template>
@ -384,9 +392,13 @@ import {
} from '@/api/base/orderInfo'
import { findProductLineList } from '@/api/base/productLine'
import { addSAPCalendar } from '@/api/production/calendarInfo'
import MaterialSelector from '@/components/MaterialSelector'
export default {
name: 'OrderInfo',
components: {
MaterialSelector
},
dicts: ['order_status', 'order_type', 'is_flag', 'is_release'],
data() {
return {
@ -441,7 +453,20 @@ export default {
//
form: {},
//
rules: {},
rules: {
orderCode: [
{ required: true, message: 'SAP计划编号不能为空', trigger: 'blur' }
],
workCenterCode: [
{ required: true, message: '请选择工作中心', trigger: 'change' }
],
materialCode: [
{ required: true, message: '请选择物料', trigger: 'change' }
],
orderAmount: [
{ required: true, message: '请输入订单计划数量', trigger: 'blur' }
]
},
columns: [
{ key: 0, label: `主键标识`, visible: false },
{ key: 1, label: `SAP计划编号`, visible: true },
@ -467,7 +492,10 @@ export default {
{ key: 21, label: `工作中心编号`, visible: true }
],
// 线
productLineList: []
productLineList: [],
materialSelector: {
open: false
}
}
},
created() {
@ -569,6 +597,17 @@ export default {
this.title = '修改工单信息'
})
},
openMaterialSelector() {
this.materialSelector.open = true
},
handleMaterialSelected(material) {
this.form.materialCode = material.materialCode
this.form.materialName = material.materialName
this.form.matkl = material.materialMatkl
if (this.$refs.form) {
this.$refs.form.clearValidate(['materialCode'])
}
},
/** 修改按钮操作 */
ReplaceProductionLine(row) {
this.reset()

@ -1,318 +1,89 @@
<template>
<div class="app-container">
<!-- 返回导航栏 -->
<div class="app-container trace-analysis-page">
<div class="breadcrumb-nav">
<el-page-header @back="goBack" content="参数追溯">
<el-page-header content="参数分析与追溯" @back="goBack">
<template slot="title">
<span style="cursor: pointer;" @click="goBack"></span>
</template>
</el-page-header>
</div>
<!-- 搜索表单 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" class="search-form">
<el-form-item label="设备" prop="deviceCode">
<el-select v-model="queryParams.deviceCode" placeholder="请选择设备" clearable filterable style="width: 200px;">
<el-option
v-for="item in deviceList"
:key="item.deviceCode"
:label="item.deviceName || item.deviceCode"
:value="item.deviceCode"
>
<span style="float: left">{{ item.deviceName || item.deviceCode }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.deviceCode }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="参数" prop="paramCode">
<el-select v-model="queryParams.paramCode" placeholder="请选择参数(可选)" clearable filterable style="width: 200px;">
<el-option
v-for="item in paramList"
:key="item.paramCode"
:label="item.paramName || item.paramCode"
:value="item.paramCode"
<el-card shadow="never" class="trace-card">
<div slot="header" class="trace-card-header">
<span>设备参数分析中心</span>
<span class="trace-card-tip">围绕异常趋势和切换三类业务场景统一分析</span>
</div>
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="参数异常报表" name="anomaly">
<anomaly-report
ref="anomalyReport"
:device-list="deviceList"
:default-device-code="defaultDeviceCode"
/>
</el-select>
</el-form-item>
<el-form-item label="时间范围" prop="dateRange">
<el-date-picker
v-model="dateRange"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="yyyy-MM-dd HH:mm:ss"
:default-time="['00:00:00', '23:59:59']"
style="width: 360px;"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" @click="resetQuery"></el-button>
<el-button type="warning" icon="el-icon-download" @click="handleExport" :loading="exportLoading">导出</el-button>
<el-button type="success" icon="el-icon-s-data" @click="handleSPC" :disabled="!queryParams.paramCode">SPC分析</el-button>
</el-form-item>
</el-form>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="traceList" border stripe>
<el-table-column label="设备编号" prop="deviceCode" width="120" />
<el-table-column label="参数编号" prop="paramCode" width="100" />
<el-table-column label="参数名称" prop="paramName" min-width="180" show-overflow-tooltip />
<el-table-column label="参数值" prop="paramValue" width="120" align="center">
<template slot-scope="scope">
<span class="param-value">{{ scope.row.paramValue }}</span>
</template>
</el-table-column>
<el-table-column label="采集时间" prop="collectTime" width="180">
<template slot-scope="scope">
{{ parseTime(scope.row.collectTime) }}
</template>
</el-table-column>
<el-table-column label="记录时间" prop="recordTime" width="180">
<template slot-scope="scope">
{{ parseTime(scope.row.recordTime) }}
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- SPC分析对话框 -->
<el-dialog title="SPC分析" :visible.sync="spcDialogVisible" width="800px" append-to-body>
<div v-loading="spcLoading" class="spc-content">
<div v-if="spcData" class="spc-result">
<div class="spc-header">
<div class="spc-info">
<span><b>设备:</b> {{ spcData.deviceCode }}</span>
<span><b>参数:</b> {{ spcData.paramName }}</span>
<span><b>样本数:</b> {{ spcData.sampleSize }}</span>
</div>
</div>
<el-divider />
<el-row :gutter="20">
<el-col :span="8">
<el-card shadow="hover" class="spc-card">
<div slot="header">统计指标</div>
<div class="spc-stat">
<div class="stat-item"><span class="label">均值 (X̄)</span><span class="value">{{ spcData.mean }}</span></div>
<div class="stat-item"><span class="label">标准差 (σ)</span><span class="value">{{ spcData.stdDev }}</span></div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" class="spc-card">
<div slot="header">控制限</div>
<div class="spc-stat">
<div class="stat-item"><span class="label">UCL (上控制限)</span><span class="value ucl">{{ spcData.ucl }}</span></div>
<div class="stat-item"><span class="label">CL (中心线)</span><span class="value">{{ spcData.cl }}</span></div>
<div class="stat-item"><span class="label">LCL (下控制限)</span><span class="value lcl">{{ spcData.lcl }}</span></div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" class="spc-card">
<div slot="header">过程能力</div>
<div class="spc-stat">
<div class="stat-item"><span class="label">CPK</span><span :class="['value', 'cpk', getCPKClass(spcData.cpk)]">{{ spcData.cpk }}</span></div>
<div class="stat-item"><span class="label">USL (上规格限)</span><span class="value">{{ spcData.usl }}</span></div>
<div class="stat-item"><span class="label">LSL (下规格限)</span><span class="value">{{ spcData.lsl }}</span></div>
</div>
</el-card>
</el-col>
</el-row>
<el-divider />
<div class="cpk-legend">
<span class="legend-item cpk-good">CPK 1.33: 优秀</span>
<span class="legend-item cpk-ok">1.0 CPK < 1.33: 合格</span>
<span class="legend-item cpk-bad">CPK < 1.0: 需改进</span>
</div>
</div>
<div v-else class="no-data">
<i class="el-icon-warning-outline"></i>
<span>暂无SPC数据请确保查询条件正确</span>
</div>
</div>
<div slot="footer">
<el-button @click="spcDialogVisible = false">关闭</el-button>
</div>
</el-dialog>
</el-tab-pane>
<el-tab-pane label="参数趋势/SPC报表" name="spc">
<spc-report
ref="spcReport"
:device-list="deviceList"
:default-device-code="defaultDeviceCode"
/>
</el-tab-pane>
<el-tab-pane label="模具/物料/产品切换追溯" name="switch">
<switch-trace-report
ref="switchTraceReport"
:device-list="deviceList"
:default-device-code="defaultDeviceCode"
/>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
<script>
import { getTraceList, getSPCData, getLatestVal } from "@/api/baseDeviceParamVal/val";
import { getDeviceLedgerList } from "@/api/base/deviceLedger";
import { getDeviceLedgerList } from '@/api/base/deviceLedger'
import AnomalyReport from './AnomalyReport.vue'
import SpcReport from './SpcReport.vue'
import SwitchTraceReport from './SwitchTrace.vue'
export default {
name: "DeviceParamValTrace",
name: 'DeviceParamTraceIndex',
components: {
AnomalyReport,
SpcReport,
SwitchTraceReport
},
data() {
return {
loading: false,
exportLoading: false,
spcLoading: false,
spcDialogVisible: false,
total: 0,
traceList: [],
activeTab: 'anomaly',
deviceList: [],
paramList: [],
dateRange: [],
spcData: null,
queryParams: {
pageNum: 1,
pageSize: 20,
deviceCode: '',
paramCode: '',
startTime: '',
endTime: ''
}
};
defaultDeviceCode: ''
}
},
created() {
this.loadDeviceList();
//
if (this.$route.query.deviceCode) {
this.queryParams.deviceCode = this.$route.query.deviceCode;
this.loadParamList(this.queryParams.deviceCode);
}
// 1
const now = new Date();
const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
this.dateRange = [this.formatDate(oneHourAgo), this.formatDate(now)];
},
watch: {
'queryParams.deviceCode'(val) {
if (val) {
this.loadParamList(val);
} else {
this.paramList = [];
this.queryParams.paramCode = '';
}
}
this.defaultDeviceCode = this.$route.query.deviceCode || ''
this.loadDeviceList()
},
methods: {
formatDate(date) {
const y = date.getFullYear();
const m = (date.getMonth() + 1).toString().padStart(2, '0');
const d = date.getDate().toString().padStart(2, '0');
const h = date.getHours().toString().padStart(2, '0');
const i = date.getMinutes().toString().padStart(2, '0');
const s = date.getSeconds().toString().padStart(2, '0');
return `${y}-${m}-${d} ${h}:${i}:${s}`;
},
goBack() {
this.$router.push('/device/Monitor/val');
this.$router.push('/device/Monitor/val')
},
loadDeviceList() {
getDeviceLedgerList().then(response => {
this.deviceList = response.data || response.rows || [];
});
this.deviceList = response.data || response.rows || []
})
},
loadParamList(deviceCode) {
getLatestVal({ deviceCode }).then(response => {
const rows = response.data || response.rows || [];
//
const paramMap = {};
rows.forEach(item => {
if (!paramMap[item.paramCode]) {
paramMap[item.paramCode] = { paramCode: item.paramCode, paramName: item.paramName };
}
});
this.paramList = Object.values(paramMap);
});
},
getList() {
if (!this.queryParams.deviceCode) {
this.$message.warning('请选择设备');
return;
}
if (!this.dateRange || this.dateRange.length !== 2) {
this.$message.warning('请选择时间范围');
return;
}
this.queryParams.startTime = this.dateRange[0];
this.queryParams.endTime = this.dateRange[1];
this.loading = true;
getTraceList(this.queryParams).then(response => {
this.traceList = response.rows || [];
this.total = response.total || 0;
this.loading = false;
}).catch(() => {
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.queryParams = {
pageNum: 1,
pageSize: 20,
deviceCode: '',
paramCode: '',
startTime: '',
endTime: ''
};
this.traceList = [];
this.total = 0;
},
handleExport() {
if (!this.queryParams.deviceCode || !this.dateRange || this.dateRange.length !== 2) {
this.$message.warning('请先设置查询条件');
return;
}
this.exportLoading = true;
const params = {
deviceCode: this.queryParams.deviceCode,
paramCode: this.queryParams.paramCode,
startTime: this.dateRange[0],
endTime: this.dateRange[1]
};
this.download('/baseDeviceParamVal/val/trace/export', params, `参数追溯_${new Date().getTime()}.xlsx`);
setTimeout(() => { this.exportLoading = false; }, 1000);
},
handleSPC() {
if (!this.queryParams.deviceCode || !this.queryParams.paramCode) {
this.$message.warning('请选择设备和参数');
return;
}
if (!this.dateRange || this.dateRange.length !== 2) {
this.$message.warning('请选择时间范围');
return;
}
this.spcDialogVisible = true;
this.spcLoading = true;
this.spcData = null;
getSPCData({
deviceCode: this.queryParams.deviceCode,
paramCode: this.queryParams.paramCode,
startTime: this.dateRange[0],
endTime: this.dateRange[1]
}).then(response => {
this.spcData = response.data;
this.spcLoading = false;
}).catch(() => {
this.spcLoading = false;
});
},
getCPKClass(cpk) {
if (cpk >= 1.33) return 'cpk-good';
if (cpk >= 1.0) return 'cpk-ok';
return 'cpk-bad';
handleTabClick() {
// resizeECharts
this.$nextTick(() => {
if (this.activeTab === 'spc' && this.$refs.spcReport) {
this.$refs.spcReport.handleTabActivated()
}
})
}
}
};
}
</script>
<style scoped>
@ -323,133 +94,21 @@ export default {
border-radius: 4px;
}
.search-form {
padding: 16px;
background: #fff;
.trace-card {
border-radius: 4px;
margin-bottom: 16px;
}
.param-value {
font-weight: 600;
color: #1890ff;
}
.spc-content {
min-height: 200px;
}
.spc-header {
margin-bottom: 16px;
}
.spc-info {
display: flex;
gap: 24px;
font-size: 14px;
}
.spc-info b {
color: #666;
margin-right: 4px;
}
.spc-card {
height: 180px;
}
.spc-card .el-card__header {
padding: 10px 16px;
font-weight: 600;
background: #fafafa;
}
.spc-stat {
padding: 8px 0;
}
.stat-item {
.trace-card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
}
.stat-item:last-child {
border-bottom: none;
}
.stat-item .label {
color: #666;
font-size: 13px;
}
.stat-item .value {
font-size: 15px;
font-weight: 600;
font-size: 16px;
color: #333;
}
.stat-item .value.ucl {
color: #f56c6c;
}
.stat-item .value.lcl {
color: #67c23a;
}
.stat-item .value.cpk-good {
color: #67c23a;
}
.stat-item .value.cpk-ok {
color: #e6a23c;
}
.stat-item .value.cpk-bad {
color: #f56c6c;
}
.cpk-legend {
display: flex;
justify-content: center;
gap: 24px;
margin-top: 16px;
}
.legend-item {
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
}
.legend-item.cpk-good {
background: #f0f9eb;
color: #67c23a;
}
.legend-item.cpk-ok {
background: #fdf6ec;
color: #e6a23c;
}
.legend-item.cpk-bad {
background: #fef0f0;
color: #f56c6c;
}
.no-data {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
color: #999;
}
.no-data i {
font-size: 48px;
margin-bottom: 12px;
.trace-card-tip {
font-size: 13px;
color: #909399;
font-weight: 400;
}
</style>

@ -331,6 +331,7 @@ export default {
createTime: null,
updateBy: null,
updateTime: null,
productIds: [],
baseStationProjectList: [],
};
this.resetForm("form");
@ -385,15 +386,16 @@ export default {
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
const checkedProjectIds = this.getMenuAllCheckedKeys();
// null
this.form.productIds = Array.isArray(checkedProjectIds) ? checkedProjectIds : [];
if (this.form.maintStationId != null) {
this.form.productIds = this.getMenuAllCheckedKeys();
updateStation(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
this.form.productIds = this.getMenuAllCheckedKeys();
addStation(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;

@ -129,7 +129,6 @@
<el-table-column type="selection" width="55" align="center" />
<!-- <el-table-column label="主键标识" align="center" prop="maintInstanceId" />-->
<!-- <el-table-column label="保养计划ID,关联dms_plan_maint的plan_maint_id" align="center" prop="planMaintId" />-->
<!-- <el-table-column label="工单流程ID关联wf_process的wf_process_id" align="center" prop="wfProcessId" />-->
<el-table-column label="保养单号" align="center" prop="billsMaintCode" />
<el-table-column label="计划保养时间" align="center" prop="planBeginTime" width="180">
<template slot-scope="scope">
@ -289,7 +288,6 @@ export default {
pageNum: 1,
pageSize: 10,
planMaintId: null,
wfProcessId: null,
billsMaintCode: null,
planBeginTime: null,
realBeginTime: null,
@ -303,9 +301,6 @@ export default {
form: {},
//
rules: {
wfProcessId: [
{ required: true, message: "工单流程ID关联wf_process的wf_process_id不能为空", trigger: "blur" }
],
billsMaintCode: [
{ required: true, message: "保养单号不能为空", trigger: "blur" }
],
@ -359,7 +354,6 @@ export default {
this.form = {
maintInstanceId: null,
planMaintId: null,
wfProcessId: null,
billsMaintCode: null,
planBeginTime: null,
realBeginTime: null,
@ -419,8 +413,6 @@ export default {
this.getList();
});
} else {
this.form.wfProcessId = 103;
addDmsBillsMaintInstance(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;

@ -8,11 +8,6 @@
<el-input v-model="this.dmsBillsMaintInstanceList[0].billsMaintCode" disabled />
</el-form-item>
</el-col>
<!-- <el-col :span="8" :offset="2">-->
<!-- <el-form-item label="当前流程" >-->
<!-- <el-input v-model="this.dmsBillsMaintInstanceList[0].wfProcessId" disabled />-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<el-col :span="8" :offset="2">
<el-form-item label="工单状态" >
<el-input v-model="this.billsStatusCheck" disabled />
@ -257,7 +252,6 @@ export default {
pageNum: 1,
pageSize: 10,
maintInstanceId: null,
processActivityId: null,
maintLevel: null,
maintGroup: null,
maintSupervisor: null,
@ -381,7 +375,6 @@ export default {
this.form = {
instanceActivityId: null,
maintInstanceId: null,
processActivityId: null,
maintLevel: null,
maintGroup: null,
maintSupervisor: null,

@ -216,7 +216,7 @@
type="text"
icon="el-icon-success"
@click="handleUpdate(scope.row)"
>处理
>处理
</el-button>
<!-- <el-button-->
<!-- size="mini"-->

@ -76,15 +76,15 @@
v-hasPermi="['production:baseBomInfo:export']"
>导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="AutomaticSynchronizationBOM"
>自动同步生产BOM</el-button>
</el-col>
<!-- <el-col :span="1.5">-->
<!-- <el-button-->
<!-- type="primary"-->
<!-- plain-->
<!-- icon="el-icon-plus"-->
<!-- size="mini"-->
<!-- @click="AutomaticSynchronizationBOM"-->
<!-- >自动同步生产BOM</el-button>-->
<!-- </el-col>-->
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row>
@ -231,8 +231,8 @@ import {
getBaseBomInfo,
delBaseBomInfo,
addBaseBomInfo,
updateBaseBomInfo,
addAutomaticSynchronizationBOM
updateBaseBomInfo
// addAutomaticSynchronizationBOM
} from '@/api/production/baseBomInfo'
import {findFactoryList} from "@//api/base/factory";
import addBom from '@//views/base/orderBomInfo/addBom.vue';
@ -425,12 +425,13 @@ export default {
}
});
},
AutomaticSynchronizationBOM(){
addAutomaticSynchronizationBOM(this.form).then(response => {
this.$modal.msgSuccess('同步成功')
this.getList()
})
},
// AutomaticSynchronizationBOM(){
// // BOM
// addAutomaticSynchronizationBOM(this.form).then(response => {
// this.$modal.msgSuccess('')
// this.getList()
// })
// },
/** 删除按钮操作 */
handleDelete(row) {
const objIds = row.objId || this.ids;

Loading…
Cancel
Save