This commit is contained in:
tzy 2025-11-11 21:21:48 +08:00
parent eea371a159
commit fa512a8bad
2 changed files with 604 additions and 5 deletions

View File

@ -16,6 +16,23 @@ export function getOrderPro(id) {
method: 'get'
})
}
// 获取工艺上传日志
export function getRouteLog(id) {
return request({
url: '/system/orderPro/getRouteLog/' + id,
method: 'post'
})
}
// 获取BOM上传日志
export function getBomUploadStatus(projectCode) {
return request({
url: '/system/details/viewGetBomUploadStatus',
method: 'post',
params: { rooteProdet: projectCode }
})
}
export function processList(params) {
return request({
url: '/system/orderPro/processlist',

View File

@ -479,6 +479,262 @@
style="margin-top: 20px; text-align: right;"
/>
</el-card>
<!-- 第四部分 工艺上传日志 -->
<el-card shadow="never" style="margin-top: 20px;">
<div slot="header" class="clearfix">
<span style="font-weight: 600; font-size: 16px;">工艺上传日志</span>
</div>
<!-- 概览统计 -->
<div style="margin-bottom: 16px; padding: 12px; background: #f5f7fa; border-radius: 4px; display:flex; gap:24px; align-items:center; flex-wrap: wrap;">
<div style="display:flex; align-items:center; gap:8px;">
<el-tag type="success" size="small">成功</el-tag>
<span style="font-weight: 600; color: #67C23A;">{{ processLogCounts.success }}</span>
</div>
<div style="display:flex; align-items:center; gap:8px;">
<el-tag type="warning" size="small">重复</el-tag>
<span style="font-weight: 600; color: #E6A23C;">{{ processLogCounts.duplicate }}</span>
</div>
<div style="display:flex; align-items:center; gap:8px;">
<el-tag type="danger" size="small">失败</el-tag>
<span style="font-weight: 600; color: #F56C6C;">{{ processLogCounts.failed }}</span>
</div>
<div style="margin-left:auto; color:#606266; font-size:13px;">
<i class="el-icon-time" style="margin-right: 4px;"></i>
<span v-if="processUploadLog.time">{{ processUploadLog.time }}</span>
<span v-else style="color: #909399;">暂无时间信息</span>
</div>
</div>
<!-- 拉取按钮 -->
<div style="margin-bottom: 16px;">
<el-button type="primary" size="small" @click="fetchProcessLog" :loading="processLogLoading" :disabled="!productionObj || !productionObj.id" icon="el-icon-refresh">
拉取最新日志
</el-button>
<el-button size="small" @click="clearProcessLog" icon="el-icon-delete" style="margin-left: 8px;">清空</el-button>
</div>
<!-- 成功上传结构化显示 -->
<div v-if="processUploadLog.successfulRoutes && processUploadLog.successfulRoutes.length" style="margin-bottom: 20px;">
<div style="margin-bottom: 12px; padding: 8px 12px; background: #f0f9ff; border-left: 4px solid #67C23A; border-radius: 4px;">
<span style="font-weight: 600; color:#67C23A; font-size: 14px;">
<i class="el-icon-success" style="margin-right: 6px;"></i>
成功上传{{ processUploadLog.successfulRoutes.length }}
</span>
<span style="margin-left:12px; color:#909399; font-size:12px;">工序行数{{ successFlatRows.length }}</span>
</div>
<el-table :data="successFlatPagedRows" size="small" border stripe style="width: 100%;">
<el-table-column prop="materialCode" label="物料编码" width="160" show-overflow-tooltip/>
<el-table-column prop="materialName" label="物料名称" width="180" show-overflow-tooltip/>
<el-table-column prop="processNo" label="工序号" width="90" align="center"/>
<el-table-column prop="workCenter" label="工作中心" width="120" show-overflow-tooltip/>
<el-table-column prop="processName" label="工序名称" width="120" show-overflow-tooltip/>
<el-table-column prop="activityUnit" label="工序单位" width="80" align="center"/>
<el-table-column prop="processControl" label="工序控制" width="120" show-overflow-tooltip/>
<el-table-column prop="activityDuration" label="工序时长" width="100" align="center"/>
<el-table-column prop="processDescription" label="工序描述" min-width="150" show-overflow-tooltip/>
<el-table-column prop="processNameDescription" label="材质/备注" width="140" show-overflow-tooltip/>
</el-table>
<pagination
v-show="successFlatRows.length > 0"
:total="successFlatRows.length"
:page.sync="processSuccessPage.pageNum"
:limit.sync="processSuccessPage.pageSize"
@pagination="() => {}"
style="margin-top: 12px;"
/>
</div>
<!-- 重复 -->
<div v-if="processUploadLog.duplicateRoutes && processUploadLog.duplicateRoutes.length" style="margin-bottom: 20px;">
<div style="margin-bottom: 12px; padding: 8px 12px; background: #fdf6ec; border-left: 4px solid #E6A23C; border-radius: 4px;">
<span style="font-weight: 600; color:#E6A23C; font-size: 14px;">
<i class="el-icon-warning" style="margin-right: 6px;"></i>
重复{{ processUploadLog.duplicateRoutes.length }}
</span>
</div>
<div style="padding: 12px; background: #fafafa; border-radius: 4px;">
<el-tag
v-for="(dup, idx) in processUploadLog.duplicateRoutes"
:key="idx"
type="warning"
size="small"
style="margin-right:8px; margin-bottom:8px; padding: 4px 12px;">
{{ dup }}
</el-tag>
</div>
</div>
<!-- 失败 -->
<div v-if="processUploadLog.failedRoutes && processUploadLog.failedRoutes.length">
<div style="margin-bottom: 12px; padding: 8px 12px; background: #fef0f0; border-left: 4px solid #F56C6C; border-radius: 4px;">
<span style="font-weight: 600; color:#F56C6C; font-size: 14px;">
<i class="el-icon-error" style="margin-right: 6px;"></i>
失败{{ processUploadLog.failedRoutes.length }}
</span>
</div>
<el-table :data="processFailedPagedItems" size="small" border stripe style="width: 100%;">
<el-table-column prop="materialCode" label="物料编码" width="160" show-overflow-tooltip/>
<el-table-column prop="errorMessage" label="错误信息" min-width="200" show-overflow-tooltip>
<template slot-scope="scope">
<span style="color: #F56C6C;">{{ scope.row.errorMessage }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="processUploadLog.failedRoutes && processUploadLog.failedRoutes.length > 0"
:total="processUploadLog.failedRoutes.length"
:page.sync="processFailedPage.pageNum"
:limit.sync="processFailedPage.pageSize"
@pagination="() => {}"
style="margin-top: 12px;"
/>
</div>
<!-- 空状态 -->
<el-empty
v-if="(!processUploadLog.successfulRoutes || processUploadLog.successfulRoutes.length === 0) && (!processUploadLog.duplicateRoutes || processUploadLog.duplicateRoutes.length === 0) && (!processUploadLog.failedRoutes || processUploadLog.failedRoutes.length === 0)"
description="暂无工艺上传日志数据"
:image-size="100"
style="padding: 40px 0;">
</el-empty>
</el-card>
<!-- 第五部分 BOM上传日志 -->
<el-card shadow="never" style="margin-top: 20px;">
<div slot="header" class="clearfix">
<span style="font-weight: 600; font-size: 16px;">BOM上传日志</span>
</div>
<!-- 概览统计 -->
<div style="margin-bottom: 16px; padding: 12px; background: #f5f7fa; border-radius: 4px; display:flex; gap:24px; align-items:center; flex-wrap: wrap;">
<div style="display:flex; align-items:center; gap:8px;">
<el-tag type="success" size="small">成功</el-tag>
<span style="font-weight: 600; color: #67C23A;">{{ bomLogCounts.success }}</span>
</div>
<div style="display:flex; align-items:center; gap:8px;">
<el-tag type="info" size="small">已存在</el-tag>
<span style="font-weight: 600; color: #909399;">{{ bomLogCounts.exists }}</span>
</div>
<div style="display:flex; align-items:center; gap:8px;">
<el-tag type="danger" size="small">失败</el-tag>
<span style="font-weight: 600; color: #F56C6C;">{{ bomLogCounts.failed }}</span>
</div>
<div style="margin-left:auto; color:#606266; font-size:13px;">
<span>总计{{ bomLogCounts.total }}</span>
</div>
</div>
<!-- 拉取按钮 -->
<div style="margin-bottom: 16px;">
<el-button type="primary" size="small" @click="fetchBomLog" :loading="bomLogLoading" :disabled="!productionObj || !productionObj.productionOrderNo" icon="el-icon-refresh">
拉取最新日志
</el-button>
<el-button size="small" @click="clearBomLog" icon="el-icon-delete" style="margin-left: 8px;">清空</el-button>
</div>
<!-- 成功上传 -->
<div v-if="bomLogCounts.success > 0" style="margin-bottom: 20px;">
<div style="margin-bottom: 12px; padding: 8px 12px; background: #f0f9ff; border-left: 4px solid #67C23A; border-radius: 4px;">
<span style="font-weight: 600; color:#67C23A; font-size: 14px;">
<i class="el-icon-success" style="margin-right: 6px;"></i>
成功上传{{ bomLogCounts.success }}
</span>
</div>
<el-table
:data="bomSuccessPagedItems"
size="small"
border
stripe
style="width: 100%;">
<el-table-column prop="materialCode" label="物料编码" width="160" show-overflow-tooltip/>
<el-table-column prop="reason" label="说明" min-width="200" show-overflow-tooltip>
<template slot-scope="scope">
<span style="color: #67C23A;">{{ scope.row.reason }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="bomLogCounts.success > 0"
:total="bomLogCounts.success"
:page.sync="bomSuccessPage.pageNum"
:limit.sync="bomSuccessPage.pageSize"
@pagination="() => {}"
style="margin-top: 12px;"
/>
</div>
<!-- 已存在 -->
<div v-if="bomLogCounts.exists > 0" style="margin-bottom: 20px;">
<div style="margin-bottom: 12px; padding: 8px 12px; background: #f4f4f5; border-left: 4px solid #909399; border-radius: 4px;">
<span style="font-weight: 600; color:#909399; font-size: 14px;">
<i class="el-icon-info" style="margin-right: 6px;"></i>
已存在且一致{{ bomLogCounts.exists }}
</span>
</div>
<el-table
:data="bomExistsPagedItems"
size="small"
border
stripe
style="width: 100%;">
<el-table-column prop="materialCode" label="物料编码" width="160" show-overflow-tooltip/>
<el-table-column prop="reason" label="说明" min-width="200" show-overflow-tooltip>
<template slot-scope="scope">
<span style="color: #909399;">{{ scope.row.reason }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="bomLogCounts.exists > 0"
:total="bomLogCounts.exists"
:page.sync="bomExistsPage.pageNum"
:limit.sync="bomExistsPage.pageSize"
@pagination="() => {}"
style="margin-top: 12px;"
/>
</div>
<!-- 失败 -->
<div v-if="bomLogCounts.failed > 0" style="margin-bottom: 20px;">
<div style="margin-bottom: 12px; padding: 8px 12px; background: #fef0f0; border-left: 4px solid #F56C6C; border-radius: 4px;">
<span style="font-weight: 600; color:#F56C6C; font-size: 14px;">
<i class="el-icon-error" style="margin-right: 6px;"></i>
失败{{ bomLogCounts.failed }}
</span>
</div>
<el-table
:data="bomFailedPagedItems"
size="small"
border
stripe
style="width: 100%;">
<el-table-column prop="materialCode" label="物料编码" width="160" show-overflow-tooltip/>
<el-table-column prop="reason" label="错误信息" min-width="200" show-overflow-tooltip>
<template slot-scope="scope">
<span style="color: #F56C6C;">{{ formatBomErrorReason(scope.row.reason) }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="bomLogCounts.failed > 0"
:total="bomLogCounts.failed"
:page.sync="bomFailedPage.pageNum"
:limit.sync="bomFailedPage.pageSize"
@pagination="() => {}"
style="margin-top: 12px;"
/>
</div>
<!-- 空状态 -->
<el-empty
v-if="bomLogCounts.total === 0"
description="暂无BOM上传日志数据"
:image-size="100"
style="padding: 40px 0;">
</el-empty>
</el-card>
<!-- 新增/编辑弹窗 -->
<el-dialog
:title="isEditChain ? '编辑销齿链型号' : '新增销齿链型号'"
@ -663,7 +919,9 @@ import {
delSave,
uploadPDF,
exportRoute,
getMRPResults
getMRPResults,
getRouteLog,
getBomUploadStatus
} from "@/api/system/orderPro";
import {processList} from "../../../api/system/orderPro";
import {listRigidChain} from '@/api/system/pcRigidChain';
@ -843,7 +1101,29 @@ export default {
dwgUploadUrl: process.env.VUE_APP_BASE_API + '/system/orderPro/uploadDwg',
uploadHeaders: {
Authorization: 'Bearer ' + getToken()
}
},
// DWG MB
dwgMaxSizeMB: 100,
//
processUploadLog: {
time: null,
successfulRoutes: [],
duplicateRoutes: [],
failedRoutes: []
},
processLogLoading: false,
//
processSuccessPage: { pageNum: 1, pageSize: 10 },
processFailedPage: { pageNum: 1, pageSize: 10 },
// BOM
bomUploadLog: {
items: []
},
bomLogLoading: false,
// BOM
bomSuccessPage: { pageNum: 1, pageSize: 10 },
bomExistsPage: { pageNum: 1, pageSize: 10 },
bomFailedPage: { pageNum: 1, pageSize: 10 }
};
},
created() {
@ -872,7 +1152,294 @@ export default {
// drawingPath
}
},
computed: {
processLogCounts() {
const success = Array.isArray(this.processUploadLog?.successfulRoutes) ? this.processUploadLog.successfulRoutes.length : 0;
const duplicate = Array.isArray(this.processUploadLog?.duplicateRoutes) ? this.processUploadLog.duplicateRoutes.length : 0;
const failed = Array.isArray(this.processUploadLog?.failedRoutes) ? this.processUploadLog.failedRoutes.length : 0;
return {success, duplicate, failed};
},
successFlatRows() {
const rows = [];
const list = this.processUploadLog?.successfulRoutes || [];
list.forEach(item => {
const base = {
materialCode: item.materialCode || '',
materialName: item.materialName || ''
};
const details = Array.isArray(item.processRouteDT) ? item.processRouteDT : [];
details.forEach(dt => {
//
// 1) processNo / workCenter / processName / activityUnit / processControl / activityDuration / processDescription / processNameDescription
// 2) K3FOperNumber / FWorkCenterId.FName / FProcessId.FName / FActivity1UnitID.FName / FOptCtrlCodeId.FName / FActivity1Qty / FOperDescription / FNumber
const processNo = dt.processNo ?? dt.FOperNumber ?? dt['FOperNumber'];
const workCenter = dt.workCenter ?? (dt.FWorkCenterId && dt.FWorkCenterId.FName) ?? dt['FWorkCenterId.FName'];
const processName = dt.processName ?? (dt.FProcessId && dt.FProcessId.FName) ?? dt['FProcessId.FName'];
const activityUnit = dt.activityUnit ?? (dt.FActivity1UnitID && dt.FActivity1UnitID.FName) ?? dt['FActivity1UnitID.FName'];
const processControl = dt.processControl ?? (dt.FOptCtrlCodeId && dt.FOptCtrlCodeId.FName) ?? dt['FOptCtrlCodeId.FName'];
const activityDuration = dt.activityDuration ?? dt.FActivity1Qty ?? dt['FActivity1Qty'];
const processDescription = dt.processDescription ?? dt.FOperDescription ?? dt['FOperDescription'];
const processNameDescription = dt.processNameDescription ?? dt.FNumber ?? dt['FNumber'];
rows.push({
...base,
processNo,
workCenter,
processName,
activityUnit,
processControl,
activityDuration,
processDescription,
processNameDescription
});
});
});
return rows;
},
//
successFlatPagedRows() {
const rows = this.successFlatRows;
const start = (this.processSuccessPage.pageNum - 1) * this.processSuccessPage.pageSize;
const end = start + this.processSuccessPage.pageSize;
return rows.slice(start, end);
},
//
processFailedPagedItems() {
const items = Array.isArray(this.processUploadLog?.failedRoutes) ? this.processUploadLog.failedRoutes : [];
const start = (this.processFailedPage.pageNum - 1) * this.processFailedPage.pageSize;
const end = start + this.processFailedPage.pageSize;
return items.slice(start, end);
},
// BOM
bomLogCounts() {
const items = Array.isArray(this.bomUploadLog?.items) ? this.bomUploadLog.items : [];
const success = items.filter(item => item.code === '200').length;
const exists = items.filter(item => item.code === '100').length;
const failed = items.filter(item => item.code === '300' || (item.code !== '200' && item.code !== '100')).length;
return {
success,
exists,
failed,
total: items.length
};
},
// BOM
bomSuccessItems() {
const items = Array.isArray(this.bomUploadLog?.items) ? this.bomUploadLog.items : [];
return items.filter(item => item.code === '200');
},
// BOM
bomExistsItems() {
const items = Array.isArray(this.bomUploadLog?.items) ? this.bomUploadLog.items : [];
return items.filter(item => item.code === '100');
},
// BOM
bomFailedItems() {
const items = Array.isArray(this.bomUploadLog?.items) ? this.bomUploadLog.items : [];
return items.filter(item => item.code === '300' || (item.code !== '200' && item.code !== '100'));
},
// BOM
bomSuccessPagedItems() {
const items = this.bomSuccessItems;
const start = (this.bomSuccessPage.pageNum - 1) * this.bomSuccessPage.pageSize;
const end = start + this.bomSuccessPage.pageSize;
return items.slice(start, end);
},
// BOM
bomExistsPagedItems() {
const items = this.bomExistsItems;
const start = (this.bomExistsPage.pageNum - 1) * this.bomExistsPage.pageSize;
const end = start + this.bomExistsPage.pageSize;
return items.slice(start, end);
},
// BOM
bomFailedPagedItems() {
const items = this.bomFailedItems;
const start = (this.bomFailedPage.pageNum - 1) * this.bomFailedPage.pageSize;
const end = start + this.bomFailedPage.pageSize;
return items.slice(start, end);
}
},
methods: {
async fetchProcessLog() {
if (!this.productionObj || !this.productionObj.id) {
this.$message.error('请先选择生产令号');
return;
}
this.processLogLoading = true;
try {
const res = await getRouteLog(this.productionObj.id);
if (res && (res.code === 200 || res.success)) {
// msg JSON msg data
const jsonStr = res.msg || res.data || '';
//
if (!jsonStr || !jsonStr.trim()) {
this.$message.info('暂无工艺上传日志');
this.clearProcessLog();
return;
}
// JSON
let parsed = null;
try {
parsed = JSON.parse(jsonStr);
} catch (parseError) {
console.error('JSON 解析失败', parseError);
this.$message.error('日志数据格式错误,无法解析');
this.clearProcessLog();
return;
}
//
let latest = null;
//
if (Array.isArray(parsed)) {
if (parsed.length === 0) {
this.$message.info('暂无工艺上传日志');
this.clearProcessLog();
return;
}
latest = parsed.reduce((acc, cur) => {
const accTime = acc && acc.time ? new Date(acc.time).getTime() : 0;
const curTime = cur && cur.time ? new Date(cur.time).getTime() : 0;
return curTime >= accTime ? cur : acc;
});
}
// 使
else if (parsed && typeof parsed === 'object') {
latest = parsed;
}
else {
this.$message.error('日志数据格式不正确');
this.clearProcessLog();
return;
}
if (!latest) {
this.$message.info('暂无工艺上传日志');
this.clearProcessLog();
return;
}
// 使
// dayjs
this.processUploadLog = {
time: latest.time ? dayjs(latest.time).format('YYYY-MM-DD HH:mm:ss') : null,
successfulRoutes: latest.successfulRoutes || [],
duplicateRoutes: latest.duplicateRoutes || [],
failedRoutes: latest.failedRoutes || []
};
this.$message.success('已拉取最新日志');
} else {
this.$message.error(res && res.msg ? res.msg : '获取工艺上传日志失败');
}
} catch (e) {
console.error('获取工艺上传日志异常', e);
this.$message.error('获取工艺上传日志失败');
} finally {
this.processLogLoading = false;
}
},
clearProcessLog() {
this.processUploadLog = {
time: null,
successfulRoutes: [],
duplicateRoutes: [],
failedRoutes: []
};
//
this.processSuccessPage = { pageNum: 1, pageSize: 10 };
this.processFailedPage = { pageNum: 1, pageSize: 10 };
},
// BOM
async fetchBomLog() {
if (!this.productionObj || !this.productionObj.productionOrderNo) {
this.$message.error('请先选择生产令号');
return;
}
this.bomLogLoading = true;
try {
const res = await getBomUploadStatus(this.productionObj.productionOrderNo);
if (res && (res.code === 200 || res.success)) {
// data JSON data msg
const jsonStr = res.data || res.msg || '';
//
if (!jsonStr || !jsonStr.trim()) {
this.$message.info('暂无BOM上传日志');
this.clearBomLog();
return;
}
// JSON
let parsed = null;
try {
parsed = JSON.parse(jsonStr);
} catch (parseError) {
console.error('JSON 解析失败', parseError);
this.$message.error('BOM日志数据格式错误无法解析');
this.clearBomLog();
return;
}
// 使 items 使 items
let items = [];
if (Array.isArray(parsed)) {
items = parsed;
} else if (parsed && typeof parsed === 'object' && Array.isArray(parsed.items)) {
items = parsed.items;
} else {
this.$message.error('BOM日志数据格式不正确');
this.clearBomLog();
return;
}
this.bomUploadLog = {
items: items
};
this.$message.success('已拉取最新BOM日志');
} else {
this.$message.error(res && res.msg ? res.msg : '获取BOM上传日志失败');
}
} catch (e) {
console.error('获取BOM上传日志异常', e);
this.$message.error('获取BOM上传日志失败');
} finally {
this.bomLogLoading = false;
}
},
// BOM
clearBomLog() {
this.bomUploadLog = {
items: []
};
//
this.bomSuccessPage = { pageNum: 1, pageSize: 10 };
this.bomExistsPage = { pageNum: 1, pageSize: 10 };
this.bomFailedPage = { pageNum: 1, pageSize: 10 };
},
// BOM
formatBomErrorReason(reason) {
if (!reason) return '';
// reason JSON
try {
const parsed = JSON.parse(reason);
if (Array.isArray(parsed)) {
return parsed.map(item => {
const field = item.FieldName || '';
const message = item.Message || '';
return field ? `${field}: ${message}` : message;
}).join('; ');
}
return reason;
} catch (e) {
// JSON
return reason;
}
},
formatFraction(value) {
//
if (typeof value === 'string' && value.includes('/')) {
@ -932,6 +1499,14 @@ export default {
this.drawer = true;
//
this.loadChainTableData();
//
if (row && row.id) {
this.fetchProcessLog();
}
// BOM
if (row && row.productionOrderNo) {
this.fetchBomLog();
}
},
/** 获取图纸类型标签 */
getDrawingTypeLabel(value) {
@ -1560,10 +2135,12 @@ export default {
return false;
}
// 50MB
const maxSize = 50 * 1024 * 1024; // 50MB
// 100MB
const limitMB = Number(this.dwgMaxSizeMB) || 100;
const maxSize = limitMB * 1024 * 1024;
const sizeMB = (file.size / (1024 * 1024)).toFixed(2);
if (file.size > maxSize) {
this.$message.error('文件大小不能超过50MB');
this.$message.error(`文件过大(所选 ${sizeMB}MB限制 ${limitMB}MB。请压缩后重试或联系管理员调整服务器限制`);
return false;
}
@ -1585,6 +2162,11 @@ export default {
},
handleDwgUploadError(error, file, row) {
console.error('图纸上传失败:', error);
const status = (error && (error.status || (error.response && error.response.status))) || 0;
if (status === 413) {
this.$message.error('文件过大(超出服务器限制)。请联系管理员提高上传大小限制或压缩文件后重试');
return;
}
this.$message.error(`图纸上传失败:${file.name}`);
},
downloadPDF() {