|
|
@@ -19,7 +19,23 @@
|
|
|
</el-col>
|
|
|
<el-col :span="6">
|
|
|
<el-form-item label="合同号">
|
|
|
- <el-input v-model="form.contractNo" :disabled="isView" placeholder="请输入合同号" clearable />
|
|
|
+ <el-select
|
|
|
+ v-model="form.contractNo"
|
|
|
+ :disabled="isView"
|
|
|
+ filterable
|
|
|
+ remote
|
|
|
+ :remote-method="loadContractOptions"
|
|
|
+ placeholder="请输入合同号"
|
|
|
+ clearable
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in contractOptions"
|
|
|
+ :key="item.value"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="6">
|
|
|
@@ -32,12 +48,29 @@
|
|
|
</el-col>
|
|
|
<el-col :span="6">
|
|
|
<el-form-item label="设计负责人">
|
|
|
- <el-input v-model="form.designLeadName" :disabled="isView" placeholder="姓名" clearable />
|
|
|
+ <el-select
|
|
|
+ v-model="form.designLeadAccount"
|
|
|
+ :disabled="isView"
|
|
|
+ filterable
|
|
|
+ remote
|
|
|
+ :remote-method="loadUserOptions"
|
|
|
+ placeholder="请选择负责人"
|
|
|
+ clearable
|
|
|
+ style="width: 100%"
|
|
|
+ @change="onDesignLeadChange"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in userOptions"
|
|
|
+ :key="item.value"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="6">
|
|
|
<el-form-item label="负责人账号">
|
|
|
- <el-input v-model="form.designLeadAccount" :disabled="isView" placeholder="可选" clearable />
|
|
|
+ <el-input :model-value="form.designLeadAccount" disabled placeholder="自动填入" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="6">
|
|
|
@@ -67,6 +100,19 @@
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-form-item label="图纸设计周期(h)">
|
|
|
+ <el-input-number
|
|
|
+ v-model="form.drawingDesignCycle"
|
|
|
+ :min="0"
|
|
|
+ :step="1"
|
|
|
+ :disabled="isView"
|
|
|
+ placeholder="小时"
|
|
|
+ style="width: 100%"
|
|
|
+ controls-position="right"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
<el-col :span="6">
|
|
|
<el-form-item label="实际开始">
|
|
|
<el-date-picker
|
|
|
@@ -112,16 +158,18 @@
|
|
|
<div class="pd-section-main">
|
|
|
<div class="cr-info-table">
|
|
|
<div class="cr-row">
|
|
|
- <div class="cr-label">序号</div>
|
|
|
- <div class="cr-value"><el-input model-value="1" disabled /></div>
|
|
|
+ <div class="cr-label">产品编码</div>
|
|
|
+ <div class="cr-value pd-with-suffix">
|
|
|
+ <el-input v-model="form.itemNum" :disabled="isView" placeholder="产品编码" clearable />
|
|
|
+ <el-button v-if="!isView" :icon="Search" @click="openItemPick('main')" />
|
|
|
+ </div>
|
|
|
<div class="cr-label">产品型号</div>
|
|
|
<div class="cr-value">
|
|
|
<el-input v-model="form.productModel" :disabled="isView" placeholder="产品型号" />
|
|
|
</div>
|
|
|
- <div class="cr-label">物料编码</div>
|
|
|
- <div class="cr-value pd-with-suffix">
|
|
|
- <el-input v-model="form.itemNum" :disabled="isView" placeholder="物料编码" clearable />
|
|
|
- <el-button v-if="!isView" :icon="Search" @click="openItemPick('main')" />
|
|
|
+ <div class="cr-label">产品名称</div>
|
|
|
+ <div class="cr-value">
|
|
|
+ <el-input v-model="form.productName" :disabled="isView" placeholder="产品名称" />
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="cr-row">
|
|
|
@@ -146,7 +194,7 @@
|
|
|
<div class="pd-vside">物料</div>
|
|
|
<div class="pd-section-main">
|
|
|
<div class="pd-subhead">
|
|
|
- <span class="pd-subtitle">标准 BOM</span>
|
|
|
+ <span class="pd-subtitle">制造 BOM</span>
|
|
|
<el-button v-if="!isView" type="primary" size="small" @click="addRootBom">
|
|
|
<el-icon><Plus /></el-icon> 新增根级
|
|
|
</el-button>
|
|
|
@@ -187,6 +235,19 @@
|
|
|
<el-input v-model="row.processCode" size="small" :disabled="isView" placeholder="工序" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
+ <el-table-column label="单位用量" width="110">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-input-number
|
|
|
+ v-model="row.qty"
|
|
|
+ size="small"
|
|
|
+ :disabled="isView"
|
|
|
+ :controls="true"
|
|
|
+ :precision="5"
|
|
|
+ :placeholder="'用量'"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
<el-table-column label="固定损耗数量" width="110">
|
|
|
<template #default="{ row }">
|
|
|
<el-input-number
|
|
|
@@ -273,8 +334,12 @@ import type { ItemRow } from '../api/universalSelector';
|
|
|
import {
|
|
|
fetchProductDesignDetail,
|
|
|
saveProductDesign,
|
|
|
+ fetchBomAndRouting,
|
|
|
+ fetchContractOptions,
|
|
|
+ fetchUserOptions,
|
|
|
type ProductDesignBomLine,
|
|
|
type ProductDesignRoutingLine,
|
|
|
+ type DropdownOption,
|
|
|
} from '../api/productDesign';
|
|
|
|
|
|
const props = withDefaults(
|
|
|
@@ -308,10 +373,12 @@ const form = reactive({
|
|
|
drawingPlanEnd: '' as string | undefined,
|
|
|
drawingActualStart: '' as string | undefined,
|
|
|
drawingActualEnd: '' as string | undefined,
|
|
|
+ drawingDesignCycle: undefined as number | undefined,
|
|
|
applicant: '',
|
|
|
applyDate: '' as string | undefined,
|
|
|
productModel: '',
|
|
|
itemNum: '',
|
|
|
+ productName: '',
|
|
|
language: 'zh',
|
|
|
lineRemark: '',
|
|
|
boms: [] as ProductDesignBomLine[],
|
|
|
@@ -372,6 +439,46 @@ function bomRowTreeDepth(row: ProductDesignBomLine): number {
|
|
|
const itemDlgVisible = ref(false);
|
|
|
const pickTarget = ref<'main' | ProductDesignBomLine>('main');
|
|
|
|
|
|
+// ── 合同下拉 ──
|
|
|
+const contractOptions = ref<DropdownOption[]>([]);
|
|
|
+
|
|
|
+async function loadContractOptions(keyword: string) {
|
|
|
+ try {
|
|
|
+ contractOptions.value = await fetchContractOptions(keyword || undefined);
|
|
|
+ } catch {
|
|
|
+ contractOptions.value = [];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 打开下拉时预载(keyword 为空)
|
|
|
+loadContractOptions('');
|
|
|
+
|
|
|
+// ── 用户下拉 ──
|
|
|
+const userOptions = ref<DropdownOption[]>([]);
|
|
|
+
|
|
|
+async function loadUserOptions(keyword: string) {
|
|
|
+ try {
|
|
|
+ userOptions.value = await fetchUserOptions(keyword || undefined);
|
|
|
+ } catch {
|
|
|
+ userOptions.value = [];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 打开下拉时预载
|
|
|
+loadUserOptions('');
|
|
|
+
|
|
|
+/** 选择用户后自动填入姓名 */
|
|
|
+function onDesignLeadChange(account: string) {
|
|
|
+ const opt = userOptions.value.find((u) => u.value === account);
|
|
|
+ if (opt) {
|
|
|
+ // label 格式: "RealName (Account)"
|
|
|
+ const match = opt.label.match(/^(.+?)\s*\(/);
|
|
|
+ form.designLeadName = match ? match[1] : opt.label;
|
|
|
+ } else {
|
|
|
+ form.designLeadName = '';
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
function emptyBomLine(id: string, parentBomId: string | null | undefined): ProductDesignBomLine {
|
|
|
return {
|
|
|
id: bomRowIdKey(id),
|
|
|
@@ -379,6 +486,7 @@ function emptyBomLine(id: string, parentBomId: string | null | undefined): Produ
|
|
|
itemNum: '',
|
|
|
itemName: '',
|
|
|
processCode: '',
|
|
|
+ qty: undefined,
|
|
|
fixedLossQty: undefined,
|
|
|
batchNo: '',
|
|
|
};
|
|
|
@@ -445,16 +553,83 @@ function openItemPick(target: 'main' | ProductDesignBomLine) {
|
|
|
itemDlgVisible.value = true;
|
|
|
}
|
|
|
|
|
|
-function onItemPicked(v: ItemRow) {
|
|
|
+async function onItemPicked(v: ItemRow) {
|
|
|
const num = v.itemNum ?? '';
|
|
|
const name = v.descr ?? '';
|
|
|
if (pickTarget.value === 'main') {
|
|
|
form.itemNum = num;
|
|
|
- } else if (pickTarget.value !== 'main') {
|
|
|
+ form.productName = name;
|
|
|
+ itemDlgVisible.value = false;
|
|
|
+ if (num) await loadBomAndRouting(num);
|
|
|
+ } else {
|
|
|
pickTarget.value.itemNum = num;
|
|
|
pickTarget.value.itemName = name;
|
|
|
+ itemDlgVisible.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function loadBomAndRouting(itemNum: string) {
|
|
|
+ try {
|
|
|
+ const result = await fetchBomAndRouting(itemNum);
|
|
|
+
|
|
|
+ // 构建 BOM 树:扁平行 → 父子层级结构
|
|
|
+ const bomMap = new Map<string, { id: string; itemNum: string }>();
|
|
|
+ const newBoms: ProductDesignBomLine[] = [];
|
|
|
+ let seq = 0;
|
|
|
+
|
|
|
+ for (const row of result.boms) {
|
|
|
+ seq++;
|
|
|
+ const tempId = String(nextBomTempId());
|
|
|
+ const itemNumVal = row.itemNum ?? '';
|
|
|
+ const parentItemVal = row.parentItem ?? '';
|
|
|
+
|
|
|
+ // 父行 ID:查找 parentItem 对应行的临时 ID(仅存首次出现,保证引用根层)
|
|
|
+ let parentBomId: string | null = null;
|
|
|
+ if (parentItemVal && parentItemVal !== itemNum) {
|
|
|
+ const parentEntry = bomMap.get(parentItemVal);
|
|
|
+ parentBomId = parentEntry?.id ?? null;
|
|
|
+ }
|
|
|
+
|
|
|
+ const bomLine: ProductDesignBomLine = {
|
|
|
+ id: tempId,
|
|
|
+ parentBomId,
|
|
|
+ seq,
|
|
|
+ itemNum: itemNumVal,
|
|
|
+ itemName: row.itemName ?? '',
|
|
|
+ processCode: row.op ?? '',
|
|
|
+ qty: row.qty ?? undefined,
|
|
|
+ fixedLossQty: row.qtyConsumed ?? undefined,
|
|
|
+ batchNo: '',
|
|
|
+ };
|
|
|
+
|
|
|
+ newBoms.push(bomLine);
|
|
|
+ // 仅存首次出现,保证子行引用到最浅层父节点
|
|
|
+ if (!bomMap.has(itemNumVal)) {
|
|
|
+ bomMap.set(itemNumVal, { id: tempId, itemNum: itemNumVal });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ form.boms = newBoms;
|
|
|
+
|
|
|
+ // 设置图纸设计周期
|
|
|
+ if (result.drawingDesignCycle != null) {
|
|
|
+ form.drawingDesignCycle = result.drawingDesignCycle;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建工艺路线
|
|
|
+ let routingSeq = 0;
|
|
|
+ form.routings = result.routings.map((r) => ({
|
|
|
+ id: undefined,
|
|
|
+ seq: ++routingSeq,
|
|
|
+ opName: r.descr ?? '',
|
|
|
+ opCode: r.op != null ? String(r.op) : '',
|
|
|
+ isKeyProcess: r.milestoneOp === '1' ? 1 : undefined,
|
|
|
+ productionLine: '',
|
|
|
+ routeCode: '',
|
|
|
+ }));
|
|
|
+ } catch (e) {
|
|
|
+ console.error('获取BOM/工艺失败:', e);
|
|
|
}
|
|
|
- itemDlgVisible.value = false;
|
|
|
}
|
|
|
|
|
|
function resetFormForCreate() {
|
|
|
@@ -469,10 +644,12 @@ function resetFormForCreate() {
|
|
|
form.drawingPlanEnd = undefined;
|
|
|
form.drawingActualStart = undefined;
|
|
|
form.drawingActualEnd = undefined;
|
|
|
+ form.drawingDesignCycle = undefined;
|
|
|
form.applicant = userInfo.userInfos?.realName || userInfo.userInfos?.account || '管理员';
|
|
|
form.applyDate = new Date().toISOString().slice(0, 10);
|
|
|
form.productModel = '';
|
|
|
form.itemNum = '';
|
|
|
+ form.productName = '';
|
|
|
form.language = 'zh';
|
|
|
form.lineRemark = '';
|
|
|
form.boms = [];
|
|
|
@@ -501,10 +678,12 @@ async function loadDetail() {
|
|
|
form.drawingPlanEnd = d.drawingPlanEnd ?? undefined;
|
|
|
form.drawingActualStart = d.drawingActualStart ?? undefined;
|
|
|
form.drawingActualEnd = d.drawingActualEnd ?? undefined;
|
|
|
+ form.drawingDesignCycle = d.drawingDesignCycle ?? undefined;
|
|
|
form.applicant = d.applicant ?? '';
|
|
|
form.applyDate = d.applyDate ?? undefined;
|
|
|
form.productModel = d.productModel ?? '';
|
|
|
form.itemNum = d.itemNum ?? '';
|
|
|
+ form.productName = d.productName ?? '';
|
|
|
form.language = d.language || 'zh';
|
|
|
form.lineRemark = d.lineRemark ?? '';
|
|
|
const bomRows = Array.isArray(d.boms) ? d.boms : [];
|
|
|
@@ -517,6 +696,7 @@ async function loadDetail() {
|
|
|
itemNum: b.itemNum ?? '',
|
|
|
itemName: b.itemName ?? '',
|
|
|
processCode: b.processCode ?? '',
|
|
|
+ qty: b.qty ?? undefined,
|
|
|
fixedLossQty: b.fixedLossQty ?? undefined,
|
|
|
batchNo: b.batchNo ?? '',
|
|
|
};
|
|
|
@@ -556,11 +736,27 @@ onMounted(() => {
|
|
|
if (props.mode === 'create' && !props.designId) resetFormForCreate();
|
|
|
});
|
|
|
|
|
|
+// 图纸设计周期变化时,自动刷新图纸计划结束时间
|
|
|
+watch(
|
|
|
+ () => [form.drawingPlanStart, form.drawingDesignCycle] as const,
|
|
|
+ ([planStart, cycle]) => {
|
|
|
+ if (planStart && cycle != null && cycle > 0) {
|
|
|
+ const d = new Date(planStart);
|
|
|
+ if (!isNaN(d.getTime())) {
|
|
|
+ d.setHours(d.getHours() + cycle);
|
|
|
+ // 格式化为 YYYY-MM-DD HH:mm:ss
|
|
|
+ const pad = (n: number) => String(n).padStart(2, '0');
|
|
|
+ form.drawingPlanEnd = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+);
|
|
|
+
|
|
|
async function onSave() {
|
|
|
saving.value = true;
|
|
|
try {
|
|
|
await saveProductDesign({
|
|
|
- id: form.id,
|
|
|
+ id: form.id != null ? Number(form.id) : undefined,
|
|
|
contractNo: form.contractNo || undefined,
|
|
|
productKind: form.productKind,
|
|
|
designLeadAccount: form.designLeadAccount || undefined,
|
|
|
@@ -570,10 +766,12 @@ async function onSave() {
|
|
|
drawingPlanEnd: form.drawingPlanEnd || undefined,
|
|
|
drawingActualStart: form.drawingActualStart || undefined,
|
|
|
drawingActualEnd: form.drawingActualEnd || undefined,
|
|
|
+ drawingDesignCycle: form.drawingDesignCycle,
|
|
|
applicant: form.applicant || undefined,
|
|
|
applyDate: form.applyDate || undefined,
|
|
|
productModel: form.productModel || undefined,
|
|
|
itemNum: form.itemNum || undefined,
|
|
|
+ productName: form.productName || undefined,
|
|
|
language: form.language || undefined,
|
|
|
lineRemark: form.lineRemark || undefined,
|
|
|
boms: form.boms,
|