工序计划。作业人员推送
This commit is contained in:
parent
1a454695ed
commit
4b5ee02ba3
@ -50,3 +50,11 @@ export function cascadeListOperationPlanningMain(query) {
|
|||||||
params: query
|
params: query
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateOperationPlanningMainOperators(data) {
|
||||||
|
return request({
|
||||||
|
url: '/system/operationPlanningMain/updateOperators',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
<div :class="{'hidden':hidden}" class="pagination-container">
|
<div :class="{'hidden':hidden}" class="pagination-container">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
:background="background"
|
:background="background"
|
||||||
:current-page.sync="currentPage"
|
:current-page="currentPage"
|
||||||
:page-size.sync="pageSize"
|
:page-size="pageSize"
|
||||||
:layout="layout"
|
:layout="layout"
|
||||||
:page-sizes="pageSizes"
|
:page-sizes="pageSizes"
|
||||||
:pager-count="pagerCount"
|
:pager-count="pagerCount"
|
||||||
@ -85,6 +85,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleSizeChange(val) {
|
handleSizeChange(val) {
|
||||||
|
this.pageSize = val
|
||||||
if (this.currentPage * val > this.total) {
|
if (this.currentPage * val > this.total) {
|
||||||
this.currentPage = 1
|
this.currentPage = 1
|
||||||
}
|
}
|
||||||
@ -94,6 +95,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleCurrentChange(val) {
|
handleCurrentChange(val) {
|
||||||
|
this.currentPage = val
|
||||||
this.$emit('pagination', { page: val, limit: this.pageSize })
|
this.$emit('pagination', { page: val, limit: this.pageSize })
|
||||||
if (this.autoScroll) {
|
if (this.autoScroll) {
|
||||||
scrollTo(0, 800)
|
scrollTo(0, 800)
|
||||||
|
|||||||
@ -123,15 +123,15 @@
|
|||||||
<el-tag :type="typeTagType(scope.row)" size="mini" effect="plain">{{ typeText(scope.row) }}</el-tag>
|
<el-tag :type="typeTagType(scope.row)" size="mini" effect="plain">{{ typeText(scope.row) }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="产品主数据" min-width="220">
|
<el-table-column label="产品主数据" min-width="320">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<div class="cell-main">
|
<div class="cell-main">
|
||||||
<div class="cell-title">{{ scope.row.productCode }}<span v-if="scope.row.productName"> | {{ scope.row.productName }}</span></div>
|
<div class="cell-title cell-title--wrap">{{ scope.row.productCode }}<span v-if="scope.row.productName"> | {{ scope.row.productName }}</span></div>
|
||||||
<div class="cell-sub">{{ scope.row.proSpecification || '-' }}</div>
|
<div class="cell-sub">{{ scope.row.proSpecification || '-' }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="产线" min-width="180">
|
<el-table-column label="产线" width="120">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<div class="cell-main">
|
<div class="cell-main">
|
||||||
<div class="cell-title">{{ scope.row.lineText || '-' }}</div>
|
<div class="cell-title">{{ scope.row.lineText || '-' }}</div>
|
||||||
@ -159,26 +159,24 @@
|
|||||||
<template v-if="!(scope.row.processSteps || []).length">
|
<template v-if="!(scope.row.processSteps || []).length">
|
||||||
<span class="cell-sub">-</span>
|
<span class="cell-sub">-</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="(scope.row.processSteps || []).length <= 2">
|
|
||||||
<div class="proc-mini">
|
|
||||||
<div v-for="(p, idx) in scope.row.processSteps" :key="idx" class="proc-mini-item" @click="openProcessDetail(scope.row, p)">
|
|
||||||
<el-progress
|
|
||||||
type="circle"
|
|
||||||
:percentage="p.percent"
|
|
||||||
:width="30"
|
|
||||||
:stroke-width="4"
|
|
||||||
:color="p.done ? '#16c784' : '#dcdfe6'"
|
|
||||||
/>
|
|
||||||
<div class="proc-mini-name">{{ p.processName || '-' }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="proc-steps">
|
<div class="proc-steps">
|
||||||
<div v-for="(p, idx) in scope.row.processSteps" :key="idx" class="proc-step">
|
<div v-for="(p, idx) in scope.row.processSteps" :key="idx" class="proc-step">
|
||||||
<div class="proc-step-top">
|
<div class="proc-step-top">
|
||||||
<span class="proc-node" :class="{ done: p.done, clickable: !!p.raw }" @click="openProcessDetail(scope.row, p)">
|
<span class="proc-circle" :class="{ clickable: !!p.raw, ellipsis: !p.raw && p.processName === '…' }" @click="openProcessDetail(scope.row, p)">
|
||||||
<i v-if="p.done" class="el-icon-check"></i>
|
<el-progress
|
||||||
|
type="circle"
|
||||||
|
:percentage="p.done ? 100 : p.percent"
|
||||||
|
:width="32"
|
||||||
|
:stroke-width="4"
|
||||||
|
:show-text="false"
|
||||||
|
:color="p.done ? '#16c784' : '#00b4ad'"
|
||||||
|
/>
|
||||||
|
<span class="proc-circle__text" :class="{ done: p.done }">
|
||||||
|
<i v-if="p.done" class="el-icon-check"></i>
|
||||||
|
<span v-else-if="p.raw">{{ Math.max(0, Math.min(99, Number(p.percent) || 0)) }}%</span>
|
||||||
|
<span v-else>…</span>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="idx < scope.row.processSteps.length - 1" class="proc-line" :class="{ done: p.done }"></span>
|
<span v-if="idx < scope.row.processSteps.length - 1" class="proc-line" :class="{ done: p.done }"></span>
|
||||||
</div>
|
</div>
|
||||||
@ -230,18 +228,46 @@
|
|||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<el-dialog :title="processDialogTitle" :visible.sync="processDialogOpen" width="1200px" top="6vh" append-to-body>
|
<el-drawer
|
||||||
<div v-if="processDialogMeta" class="cell-sub" style="margin-bottom: 10px;">
|
class="process-drawer"
|
||||||
{{ processDialogMeta }}
|
:title="processDialogTitle"
|
||||||
|
:visible.sync="processDialogOpen"
|
||||||
|
direction="btt"
|
||||||
|
size="70%"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<template slot="title">
|
||||||
|
<div class="process-drawer__title-line">
|
||||||
|
<span class="process-drawer__title-main">工序详情</span>
|
||||||
|
<span v-if="processDialogBillNo" class="process-drawer__title-kv">工序单号:{{ processDialogBillNo }}</span>
|
||||||
|
<span v-if="processDialogBatchNo" class="process-drawer__title-kv">生产号:{{ processDialogBatchNo }}</span>
|
||||||
|
<span v-if="processDialogProduct" class="process-drawer__title-kv process-drawer__title-product">产品:{{ processDialogProduct }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="process-drawer__toolbar">
|
||||||
|
<div class="process-drawer__toolbar-left">
|
||||||
|
<span v-if="processDialogDirtyCount" class="process-drawer__dirty">已修改{{ processDialogDirtyCount }}项</span>
|
||||||
|
</div>
|
||||||
|
<div class="process-drawer__toolbar-right">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="mini"
|
||||||
|
:loading="processDialogSaving"
|
||||||
|
:disabled="!processDialogDirtyCount"
|
||||||
|
@click="submitOperatorUpdates"
|
||||||
|
>保存</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-table
|
<div class="process-drawer__table">
|
||||||
ref="processTable"
|
<el-table
|
||||||
:data="processDialogList"
|
ref="processTable"
|
||||||
size="mini"
|
:data="processDialogList"
|
||||||
border
|
size="mini"
|
||||||
highlight-current-row
|
border
|
||||||
:row-key="p => p.entityEntryId || p.operNo"
|
highlight-current-row
|
||||||
>
|
height="100%"
|
||||||
|
:row-key="p => p.entityEntryId || p.operNo"
|
||||||
|
>
|
||||||
<el-table-column label="工序号" prop="operNo" width="80" align="center" />
|
<el-table-column label="工序号" prop="operNo" width="80" align="center" />
|
||||||
<el-table-column label="工序名称" prop="processName" min-width="140" />
|
<el-table-column label="工序名称" prop="processName" min-width="140" />
|
||||||
<el-table-column label="工作中心" prop="workCenter" min-width="120" />
|
<el-table-column label="工作中心" prop="workCenter" min-width="120" />
|
||||||
@ -249,20 +275,52 @@
|
|||||||
<el-table-column label="转入数量" prop="inQty" width="90" align="center" />
|
<el-table-column label="转入数量" prop="inQty" width="90" align="center" />
|
||||||
<el-table-column label="合格数量" prop="okQty" width="90" align="center" />
|
<el-table-column label="合格数量" prop="okQty" width="90" align="center" />
|
||||||
<el-table-column label="废品数量" prop="ngQty" width="90" align="center" />
|
<el-table-column label="废品数量" prop="ngQty" width="90" align="center" />
|
||||||
<el-table-column label="状态" prop="status" width="100" align="center" />
|
<el-table-column label="状态" width="100" align="center">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag size="mini" effect="plain" :class="processStatusClass(scope.row)">{{ processStatusText(scope.row) }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="转出数量" prop="transOutQty" width="90" align="center" />
|
<el-table-column label="转出数量" prop="transOutQty" width="90" align="center" />
|
||||||
<el-table-column label="损耗数量" prop="wastageQty" width="90" align="center" />
|
<el-table-column label="损耗数量" prop="wastageQty" width="90" align="center" />
|
||||||
<el-table-column label="加工车间" prop="department" min-width="120" />
|
<el-table-column label="加工车间" prop="department" min-width="120" />
|
||||||
<el-table-column label="作业人员" prop="operator" min-width="100" />
|
<el-table-column label="作业人员" min-width="220">
|
||||||
<el-table-column label="计划开始" prop="seqPlanStartTime" width="160" />
|
<template slot-scope="scope">
|
||||||
<el-table-column label="计划完成" prop="seqPlanFinishTime" width="160" />
|
<div class="operator-cell">
|
||||||
|
<el-input
|
||||||
|
v-model="scope.row.__operatorDraft"
|
||||||
|
size="mini"
|
||||||
|
clearable
|
||||||
|
placeholder="填写作业人员"
|
||||||
|
@input="markOperatorDirty(scope.row)"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
v-if="scope.row.__operatorDirty"
|
||||||
|
type="text"
|
||||||
|
size="mini"
|
||||||
|
:disabled="scope.row.__operatorSaving"
|
||||||
|
@click="resetOperatorDraft(scope.row)"
|
||||||
|
>取消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="计划开始" width="160">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span>{{ getProcessPlanStartTime(scope.row) || '-' }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="计划完成" width="160">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span>{{ getProcessPlanFinishTime(scope.row) || '-' }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="控制码" prop="optCtrlCode" min-width="120" />
|
<el-table-column label="控制码" prop="optCtrlCode" min-width="120" />
|
||||||
<el-table-column label="关键工序" prop="keyOper" width="90" align="center" />
|
<el-table-column label="关键工序" prop="keyOper" width="90" align="center" />
|
||||||
<el-table-column label="活动一" prop="activity1Name" min-width="120" />
|
<el-table-column label="活动一" prop="activity1Name" min-width="120" />
|
||||||
<el-table-column label="工序说明" prop="operDescription" min-width="140" />
|
<el-table-column label="工序说明" prop="operDescription" min-width="140" />
|
||||||
<el-table-column label="单据体内码" prop="entityEntryId" width="120" align="center" />
|
<el-table-column label="单据体内码" prop="entityEntryId" width="120" align="center" />
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-dialog>
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
<!-- 添加或修改工序计划主对话框 -->
|
<!-- 添加或修改工序计划主对话框 -->
|
||||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||||||
@ -314,7 +372,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { listOperationPlanningMain, cascadeListOperationPlanningMain, getOperationPlanningMain, delOperationPlanningMain, addOperationPlanningMain, updateOperationPlanningMain } from "@/api/system/operationPlanningMain";
|
import { listOperationPlanningMain, cascadeListOperationPlanningMain, getOperationPlanningMain, delOperationPlanningMain, addOperationPlanningMain, updateOperationPlanningMain, updateOperationPlanningMainOperators } from "@/api/system/operationPlanningMain";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "OperationPlanningMain",
|
name: "OperationPlanningMain",
|
||||||
@ -331,6 +389,8 @@ export default {
|
|||||||
processDialogTitle: "工序详情",
|
processDialogTitle: "工序详情",
|
||||||
processDialogMeta: "",
|
processDialogMeta: "",
|
||||||
processDialogList: [],
|
processDialogList: [],
|
||||||
|
processDialogOrder: null,
|
||||||
|
processDialogSaving: false,
|
||||||
// 非单个禁用
|
// 非单个禁用
|
||||||
single: true,
|
single: true,
|
||||||
// 非多个禁用
|
// 非多个禁用
|
||||||
@ -399,6 +459,23 @@ export default {
|
|||||||
created() {
|
created() {
|
||||||
this.getList();
|
this.getList();
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
processDialogBillNo() {
|
||||||
|
const o = this.processDialogOrder || {};
|
||||||
|
return o.billNo || o.moNumber || '';
|
||||||
|
},
|
||||||
|
processDialogBatchNo() {
|
||||||
|
const o = this.processDialogOrder || {};
|
||||||
|
return o.batchNo || o.hbytSclh || '';
|
||||||
|
},
|
||||||
|
processDialogProduct() {
|
||||||
|
const o = this.processDialogOrder || {};
|
||||||
|
return [o.productCode, o.productName].filter(Boolean).join(' | ');
|
||||||
|
},
|
||||||
|
processDialogDirtyCount() {
|
||||||
|
return (this.processDialogList || []).filter(p => p && p.__operatorDirty).length;
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/** 查询工序计划主列表 */
|
/** 查询工序计划主列表 */
|
||||||
getList() {
|
getList() {
|
||||||
@ -470,8 +547,12 @@ export default {
|
|||||||
const processSteps = processList.map(p => {
|
const processSteps = processList.map(p => {
|
||||||
const planQty = this.normalizeNumber(p.planQty || overallPlanQty);
|
const planQty = this.normalizeNumber(p.planQty || overallPlanQty);
|
||||||
const okQty = this.normalizeNumber(p.okQty);
|
const okQty = this.normalizeNumber(p.okQty);
|
||||||
const percent = planQty > 0 ? Math.min(100, Math.round((okQty / planQty) * 100)) : 0;
|
const inQty = this.normalizeNumber(p.inQty);
|
||||||
const done = this.isProcessDone(p, planQty, okQty);
|
const backendProgress = this.normalizePercent(p.progress);
|
||||||
|
const baseQty = p.inQty != null ? inQty : okQty;
|
||||||
|
const fallbackProgress = planQty > 0 ? (baseQty / planQty) * 100 : 0;
|
||||||
|
const percent = backendProgress != null ? backendProgress : this.normalizePercent(fallbackProgress) || 0;
|
||||||
|
const done = this.isProcessDone(p, planQty, okQty, inQty, percent);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
operNo: p.operNo,
|
operNo: p.operNo,
|
||||||
@ -499,12 +580,25 @@ export default {
|
|||||||
lineText
|
lineText
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
isProcessDone(process, planQty, okQty) {
|
isProcessDone(process, planQty, okQty, inQty, percent) {
|
||||||
|
const derived = this.calcProcessStatus(process);
|
||||||
|
if (derived === '工序完成' || derived === '已关闭') return true;
|
||||||
|
|
||||||
const status = (process && process.status) || '';
|
const status = (process && process.status) || '';
|
||||||
if (String(status).includes('完工')) return true;
|
if (String(status).includes('完工')) return true;
|
||||||
|
if (percent != null && Number(percent) >= 100) return true;
|
||||||
if (planQty > 0 && okQty >= planQty) return true;
|
if (planQty > 0 && okQty >= planQty) return true;
|
||||||
|
if (planQty > 0 && inQty >= planQty) return true;
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
normalizePercent(val) {
|
||||||
|
if (val == null || val === '') return null;
|
||||||
|
const n = Number(val);
|
||||||
|
if (!Number.isFinite(n)) return null;
|
||||||
|
if (n < 0) return 0;
|
||||||
|
if (n > 100) return 100;
|
||||||
|
return n;
|
||||||
|
},
|
||||||
buildLineText(processList) {
|
buildLineText(processList) {
|
||||||
const p0 = Array.isArray(processList) && processList.length ? processList[0] : undefined;
|
const p0 = Array.isArray(processList) && processList.length ? processList[0] : undefined;
|
||||||
if (p0 && p0.workCenter) return p0.workCenter;
|
if (p0 && p0.workCenter) return p0.workCenter;
|
||||||
@ -538,14 +632,76 @@ export default {
|
|||||||
},
|
},
|
||||||
statusText(row) {
|
statusText(row) {
|
||||||
if (!row) return '-';
|
if (!row) return '-';
|
||||||
if (row.overallProgress >= 100) return '完工';
|
return this.calcOrderStatus(row) || '-';
|
||||||
if (row.overallProgress > 0) return '已开工';
|
|
||||||
return '备料中';
|
|
||||||
},
|
},
|
||||||
statusTagClass(row) {
|
statusTagClass(row) {
|
||||||
const text = this.statusText(row);
|
const text = this.statusText(row);
|
||||||
if (text === '完工' || text === '已开工') return 'tag-purple';
|
if (text === '未开始') return 'tag-gray';
|
||||||
return 'tag-green';
|
if (text === '进行中') return 'tag-green';
|
||||||
|
if (text === '预警') return 'tag-orange';
|
||||||
|
if (text === '延期') return 'tag-red';
|
||||||
|
if (text === '已完成') return 'tag-blue';
|
||||||
|
return 'tag-gray';
|
||||||
|
},
|
||||||
|
calcOrderStatus(row) {
|
||||||
|
if (!row) return '';
|
||||||
|
const overallProgress = Number(row.overallProgress) || 0;
|
||||||
|
if (overallProgress >= 100) return '已完成';
|
||||||
|
|
||||||
|
const risk = this.calcOrderRisk(row);
|
||||||
|
if (risk) return risk;
|
||||||
|
|
||||||
|
if (overallProgress > 0) return '进行中';
|
||||||
|
return '未开始';
|
||||||
|
},
|
||||||
|
calcOrderRisk(row) {
|
||||||
|
if (!row) return '';
|
||||||
|
if (Number(row.overallProgress) >= 100) return '';
|
||||||
|
|
||||||
|
const warnDays = 1;
|
||||||
|
const now = Date.now();
|
||||||
|
const orderDue = this.toTimestamp(row.planFinishTime);
|
||||||
|
const orderRisk = this.calcRiskByDue(orderDue, now, warnDays);
|
||||||
|
|
||||||
|
let processRisk = '';
|
||||||
|
const list = Array.isArray(row.processList) ? row.processList : [];
|
||||||
|
for (const p of list) {
|
||||||
|
const base = this.calcProcessStatus(p);
|
||||||
|
if (base === '工序完成' || base === '已转出' || base === '已关闭') continue;
|
||||||
|
const due = this.toTimestamp(this.getProcessPlanFinishTime(p));
|
||||||
|
const r = this.calcRiskByDue(due, now, warnDays);
|
||||||
|
if (r === '延期') {
|
||||||
|
processRisk = '延期';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (r === '预警') processRisk = '预警';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderRisk === '延期' || processRisk === '延期') return '延期';
|
||||||
|
if (orderRisk === '预警' || processRisk === '预警') return '预警';
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
calcRiskByDue(dueTs, nowTs, warnDays) {
|
||||||
|
if (!dueTs) return '';
|
||||||
|
const diffDays = (dueTs - nowTs) / 86400000;
|
||||||
|
if (diffDays < 0) return '延期';
|
||||||
|
if (diffDays <= warnDays) return '预警';
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
toTimestamp(val) {
|
||||||
|
if (val == null || val === '') return null;
|
||||||
|
if (typeof val === 'number' && Number.isFinite(val)) return val;
|
||||||
|
const s = String(val);
|
||||||
|
const ts = new Date(s.replace(/-/g, '/')).getTime();
|
||||||
|
return Number.isFinite(ts) ? ts : null;
|
||||||
|
},
|
||||||
|
getProcessPlanStartTime(process) {
|
||||||
|
if (!process) return '';
|
||||||
|
return process.operPlanStartTime || process.fOperPlanStartTime || process.seqPlanStartTime || process.fSeqPlanStartTime || '';
|
||||||
|
},
|
||||||
|
getProcessPlanFinishTime(process) {
|
||||||
|
if (!process) return '';
|
||||||
|
return process.operPlanFinishTime || process.fOperPlanFinishTime || process.seqPlanFinishTime || process.fSeqPlanFinishTime || '';
|
||||||
},
|
},
|
||||||
typeText(row) {
|
typeText(row) {
|
||||||
return (row && row.typeText) || '正常';
|
return (row && row.typeText) || '正常';
|
||||||
@ -617,16 +773,149 @@ export default {
|
|||||||
|
|
||||||
this.processDialogTitle = code ? `工序详情 - ${code}` : '工序详情';
|
this.processDialogTitle = code ? `工序详情 - ${code}` : '工序详情';
|
||||||
this.processDialogMeta = batchNo ? `生产令号:${batchNo}` : '';
|
this.processDialogMeta = batchNo ? `生产令号:${batchNo}` : '';
|
||||||
this.processDialogList = list;
|
this.processDialogOrder = orderRow || null;
|
||||||
|
this.processDialogList = this.prepareProcessDialogList(list);
|
||||||
this.processDialogOpen = true;
|
this.processDialogOpen = true;
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (!this.$refs.processTable || !step || !step.raw) return;
|
if (!this.$refs.processTable || !step || !step.raw) return;
|
||||||
const target = this.processDialogList.find(p => p.entityEntryId === step.raw.entityEntryId)
|
const entryId = step.raw.entityEntryId != null ? String(step.raw.entityEntryId) : '';
|
||||||
|| this.processDialogList.find(p => p.operNo === step.operNo);
|
const operNo = step.operNo != null ? String(step.operNo) : '';
|
||||||
|
const target = this.processDialogList.find(p => p.entityEntryId != null && String(p.entityEntryId) === entryId)
|
||||||
|
|| this.processDialogList.find(p => p.operNo != null && String(p.operNo) === operNo);
|
||||||
if (target) this.$refs.processTable.setCurrentRow(target);
|
if (target) this.$refs.processTable.setCurrentRow(target);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
prepareProcessDialogList(list) {
|
||||||
|
const safeList = Array.isArray(list) ? list : [];
|
||||||
|
return safeList.map(p => {
|
||||||
|
const operator = p && p.operator != null ? String(p.operator) : '';
|
||||||
|
return {
|
||||||
|
...p,
|
||||||
|
__operatorDraft: operator,
|
||||||
|
__operatorDirty: false,
|
||||||
|
__operatorSaving: false
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
processStatusText(process) {
|
||||||
|
return this.calcProcessDisplayStatus(process) || '-';
|
||||||
|
},
|
||||||
|
processStatusClass(process) {
|
||||||
|
const text = this.calcProcessDisplayStatus(process);
|
||||||
|
if (text === '未开工') return 'tag-gray';
|
||||||
|
if (text === '生产中') return 'tag-green';
|
||||||
|
if (text === '质检中') return 'tag-blue';
|
||||||
|
if (text === '预警') return 'tag-orange';
|
||||||
|
if (text === '延期') return 'tag-red';
|
||||||
|
if (text === '工序完成' || text === '已转出') return 'tag-blue';
|
||||||
|
if (text === '已关闭') return 'tag-gray';
|
||||||
|
return 'tag-gray';
|
||||||
|
},
|
||||||
|
calcProcessDisplayStatus(process) {
|
||||||
|
const base = this.calcProcessStatus(process);
|
||||||
|
if (base === '工序完成' || base === '已转出' || base === '已关闭') return base;
|
||||||
|
|
||||||
|
const warnDays = 1;
|
||||||
|
const now = Date.now();
|
||||||
|
const due = this.toTimestamp(this.getProcessPlanFinishTime(process));
|
||||||
|
const risk = this.calcRiskByDue(due, now, warnDays);
|
||||||
|
if (risk) return risk;
|
||||||
|
|
||||||
|
if (base === '未开工' || base === '生产中' || base === '质检中') return base;
|
||||||
|
return base || '';
|
||||||
|
},
|
||||||
|
calcProcessStatus(process) {
|
||||||
|
if (!process) return '';
|
||||||
|
|
||||||
|
const rawK3 = process.k3Status ?? process.operStatus ?? process.k3OperStatus;
|
||||||
|
const k3 = rawK3 == null || rawK3 === '' ? 0 : Number(rawK3);
|
||||||
|
if (k3 === 5 || k3 === 6) return '已关闭';
|
||||||
|
|
||||||
|
const inQty = this.normalizeNumber(process.inQty);
|
||||||
|
const okQty = this.normalizeNumber(process.okQty);
|
||||||
|
const ngQty = this.normalizeNumber(process.ngQty);
|
||||||
|
const transOutQty = this.normalizeNumber(process.transOutQty);
|
||||||
|
|
||||||
|
if (inQty === 0) return '未开工';
|
||||||
|
if (transOutQty > 0) return '已转出';
|
||||||
|
if (okQty + ngQty >= inQty) return '工序完成';
|
||||||
|
if (okQty + ngQty > 0) return '质检中';
|
||||||
|
return '生产中';
|
||||||
|
},
|
||||||
|
markOperatorDirty(row) {
|
||||||
|
if (!row) return;
|
||||||
|
const origin = row.operator != null ? String(row.operator) : '';
|
||||||
|
const draft = row.__operatorDraft != null ? String(row.__operatorDraft) : '';
|
||||||
|
row.__operatorDirty = origin !== draft;
|
||||||
|
},
|
||||||
|
resetOperatorDraft(row) {
|
||||||
|
if (!row) return;
|
||||||
|
row.__operatorDraft = row.operator != null ? String(row.operator) : '';
|
||||||
|
row.__operatorDirty = false;
|
||||||
|
},
|
||||||
|
buildOperatorUpdatePayload(rows) {
|
||||||
|
const order = this.processDialogOrder || {};
|
||||||
|
const list = Array.isArray(rows) ? rows : [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
orderId: order.id ?? null,
|
||||||
|
fid: order.fid ?? null,
|
||||||
|
billNo: order.billNo ?? null,
|
||||||
|
moNumber: order.moNumber ?? null,
|
||||||
|
batchNo: (order.batchNo || order.hbytSclh) ?? null,
|
||||||
|
updates: list.map(p => ({
|
||||||
|
k3DetailId: p.k3DetailId ?? null,
|
||||||
|
entityEntryId: p.entityEntryId ?? null,
|
||||||
|
operNo: p.operNo ?? null,
|
||||||
|
processName: p.processName ?? null,
|
||||||
|
operator: p.__operatorDraft != null ? String(p.__operatorDraft).trim() : ''
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
},
|
||||||
|
submitOperatorUpdates(targetRows) {
|
||||||
|
if (this.processDialogSaving) return;
|
||||||
|
const rows = Array.isArray(targetRows)
|
||||||
|
? targetRows
|
||||||
|
: (this.processDialogList || []).filter(p => p && p.__operatorDirty);
|
||||||
|
|
||||||
|
const realRows = rows.filter(r => {
|
||||||
|
const origin = r && r.operator != null ? String(r.operator) : '';
|
||||||
|
const draft = r && r.__operatorDraft != null ? String(r.__operatorDraft).trim() : '';
|
||||||
|
return origin !== draft;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!realRows.length) return;
|
||||||
|
|
||||||
|
this.processDialogSaving = true;
|
||||||
|
realRows.forEach(r => {
|
||||||
|
r.__operatorSaving = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const payload = this.buildOperatorUpdatePayload(realRows);
|
||||||
|
updateOperationPlanningMainOperators(payload)
|
||||||
|
.then(() => {
|
||||||
|
realRows.forEach(r => {
|
||||||
|
const next = r.__operatorDraft != null ? String(r.__operatorDraft).trim() : '';
|
||||||
|
r.operator = next;
|
||||||
|
r.__operatorDraft = next;
|
||||||
|
r.__operatorDirty = false;
|
||||||
|
});
|
||||||
|
this.$modal.msgSuccess('保存成功');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
realRows.forEach(r => {
|
||||||
|
r.__operatorSaving = false;
|
||||||
|
});
|
||||||
|
this.processDialogSaving = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
saveOperator(row) {
|
||||||
|
if (!row || row.__operatorSaving) return;
|
||||||
|
this.markOperatorDirty(row);
|
||||||
|
if (!row.__operatorDirty) return;
|
||||||
|
this.$modal.msgWarning('已暂存,点击上方“保存”统一提交');
|
||||||
|
},
|
||||||
formatProcessText(row) {
|
formatProcessText(row) {
|
||||||
const list = (row && Array.isArray(row.processList) ? row.processList : []).map(p => {
|
const list = (row && Array.isArray(row.processList) ? row.processList : []).map(p => {
|
||||||
const operNo = p.operNo != null ? String(p.operNo) : '-';
|
const operNo = p.operNo != null ? String(p.operNo) : '-';
|
||||||
@ -742,6 +1031,30 @@ export default {
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag-gray {
|
||||||
|
background: #909399;
|
||||||
|
border-color: #909399;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-orange {
|
||||||
|
background: #f59e0b;
|
||||||
|
border-color: #f59e0b;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-red {
|
||||||
|
background: #ef4444;
|
||||||
|
border-color: #ef4444;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-blue {
|
||||||
|
background: #3b82f6;
|
||||||
|
border-color: #3b82f6;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
.cell-main {
|
.cell-main {
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
@ -754,6 +1067,14 @@ export default {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cell-title--wrap {
|
||||||
|
white-space: normal;
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
.cell-sub {
|
.cell-sub {
|
||||||
color: #909399;
|
color: #909399;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@ -768,36 +1089,6 @@ export default {
|
|||||||
min-height: 46px;
|
min-height: 46px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.proc-mini {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proc-mini-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
width: 56px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .proc-mini .el-progress-circle__text {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proc-mini-name {
|
|
||||||
margin-top: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #303133;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
width: 56px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proc-steps {
|
.proc-steps {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@ -814,33 +1105,46 @@ export default {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.proc-node {
|
|
||||||
width: 18px;
|
.proc-circle {
|
||||||
height: 18px;
|
position: relative;
|
||||||
border-radius: 50%;
|
width: 32px;
|
||||||
border: 2px solid #dcdfe6;
|
height: 32px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: #ffffff;
|
|
||||||
background: #ffffff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.proc-node.done {
|
.proc-circle.clickable {
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
border-color: #16c784;
|
|
||||||
color: #16c784;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proc-node.clickable {
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.proc-node.done i {
|
.proc-circle__text {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #00b4ad;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proc-circle__text.done {
|
||||||
|
color: #16c784;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.proc-circle.ellipsis .proc-circle__text {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1;
|
||||||
|
color: #c0c4cc;
|
||||||
|
}
|
||||||
|
|
||||||
.proc-line {
|
.proc-line {
|
||||||
width: 26px;
|
width: 26px;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
@ -883,4 +1187,62 @@ export default {
|
|||||||
.is-row-checked td {
|
.is-row-checked td {
|
||||||
background: #e8faf8 !important;
|
background: #e8faf8 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.process-drawer ::v-deep .el-drawer__body {
|
||||||
|
padding: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-drawer__toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-drawer__title-main {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-drawer__title-line {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 14px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-drawer__title-kv {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-drawer__title-product {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-drawer__dirty {
|
||||||
|
color: #606266;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-drawer__table {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user