From 2fd817ab50faa08111ce6e64c6d22a54807d08a4 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 09 六月 2026 15:16:48 +0800
Subject: [PATCH] pro 1.bom编辑页面样式优化
---
src/views/productionManagement/productStructure/DetailNew/MaterialCard.vue | 224 ++++++++++++++
src/views/productionManagement/productStructure/DetailNew/index.vue | 617 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 841 insertions(+), 0 deletions(-)
diff --git a/src/views/productionManagement/productStructure/DetailNew/MaterialCard.vue b/src/views/productionManagement/productStructure/DetailNew/MaterialCard.vue
new file mode 100644
index 0000000..038d3e8
--- /dev/null
+++ b/src/views/productionManagement/productStructure/DetailNew/MaterialCard.vue
@@ -0,0 +1,224 @@
+<template>
+ <div class="material-node">
+ <!-- 褰撳墠鑺傜偣鍗$墖 -->
+ <div :class="['node-card', isRoot ? 'root-card' : 'child-card']">
+ <div class="node-header">
+ <div class="node-label">
+ <el-tag :type="isRoot ? '' : 'success'" size="small" effect="dark">
+ {{ isRoot ? '鎴愬搧' : '鍘熸枡' }}
+ </el-tag>
+ <span class="node-title">{{ row.productName || '鏈�夋嫨浜у搧' }}</span>
+ <span v-if="row.model" class="node-sub">瑙勬牸: {{ row.model }}</span>
+ <span v-if="row.unit" class="node-sub">鍗曚綅: {{ row.unit }}</span>
+ </div>
+ <div class="node-actions">
+ <el-button v-if="editable"
+ type="primary"
+ text
+ size="small"
+ @click="handleAdd">
+ + 娣诲姞{{ isRoot ? '鍘熸枡' : '瀛愮骇鍘熸枡' }}
+ </el-button>
+ <el-button v-if="editable"
+ type="danger"
+ text
+ size="small"
+ @click="$emit('remove', row.tempId)">
+ 鍒犻櫎
+ </el-button>
+ </div>
+ </div>
+
+ <!-- 缂栬緫妯″紡涓嬬殑琛ㄥ崟 -->
+ <div v-if="editable" class="node-body">
+ <el-row :gutter="12">
+ <el-col :span="7">
+ <el-form-item label="浜у搧" :rules="[{ required: true, message: '璇烽�夋嫨浜у搧' }]" style="margin:0">
+ <el-input :model-value="row.productName || ''"
+ readonly
+ placeholder="鐐瑰嚮閫夋嫨浜у搧"
+ @click="openSelect"
+ style="width:100%">
+ <template #suffix>
+ <el-icon><component :is="SearchIcon" /></el-icon>
+ </template>
+ </el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="5">
+ <el-form-item label="瑙勬牸" style="margin:0">
+ <el-select v-model="row.model"
+ placeholder="璇烽�夋嫨瑙勬牸"
+ clearable
+ style="width:100%"
+ @visible-change="(v:boolean) => { if (v) openSelect() }">
+ <el-option v-if="row.model" :label="row.model" :value="row.model" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col v-if="!isRoot" :span="5">
+ <el-form-item label="宸ュ簭" :rules="[{ required: true, message: '璇烽�夋嫨宸ュ簭' }]" style="margin:0">
+ <el-select v-model="row.processId"
+ placeholder="璇烽�夋嫨"
+ filterable
+ clearable
+ style="width:100%"
+ @change="(v:any) => $emit('processChange', row, v)">
+ <el-option v-for="item in processOptions"
+ :key="item.id"
+ :label="item.name"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="鏁伴噺" :rules="[{ required: true, message: '璇峰~鍐欐暟閲�' }]" style="margin:0">
+ <el-input-number v-model="row.unitQuantity"
+ :min="0"
+ :precision="2"
+ :step="1"
+ controls-position="right"
+ style="width:100%"
+ @change="$emit('quantityChange')" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="3">
+ <el-form-item label="鍗曚綅" style="margin:0">
+ <el-input v-model="row.unit" placeholder="鍗曚綅" clearable style="width:100%" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </div>
+
+ <!-- 闈炵紪杈戞ā寮忥細绠�娲佹樉绀� -->
+ <div v-else class="node-view">
+ <span v-if="!isRoot && row.processName">宸ュ簭: {{ row.processName }} | </span>
+ <span>鏁伴噺: {{ row.unitQuantity || '-' }}</span>
+ </div>
+ </div>
+
+ <!-- 閫掑綊娓叉煋瀛愯妭鐐� -->
+ <div v-if="row.children && row.children.length > 0" class="node-children">
+ <MaterialCard
+ v-for="child in row.children"
+ :key="child.tempId"
+ :row="child"
+ :depth="depth + 1"
+ :editable="editable"
+ :process-options="processOptions"
+ @remove="(id:string) => $emit('remove', id)"
+ @add="(id:string) => $emit('add', id)"
+ @select-product="(tempId: string, data: any) => $emit('selectProduct', tempId, data)"
+ @process-change="(row: any, v: any) => $emit('processChange', row, v)"
+ @quantity-change="$emit('quantityChange')"
+ />
+ </div>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import { Search } from '@element-plus/icons-vue'
+
+const SearchIcon = Search
+
+const props = defineProps<{
+ row: any
+ depth: number
+ editable: boolean
+ processOptions: any[]
+}>()
+
+const emit = defineEmits<{
+ remove: [tempId: string]
+ add: [tempId: string]
+ selectProduct: [tempId: string, data: any]
+ processChange: [row: any, value: any]
+ quantityChange: []
+}>()
+
+const isRoot = computed(() => props.depth === 0)
+
+const openSelect = () => {
+ emit('selectProduct', props.row.tempId, null)
+}
+
+const handleAdd = () => {
+ emit('add', props.row.tempId)
+}
+</script>
+
+<script lang="ts">
+export default { name: 'MaterialCard' }
+</script>
+
+<style scoped>
+.material-node {
+ margin: 4px 0;
+}
+
+.node-card {
+ border: 1px solid #e4e7ed;
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.root-card {
+ border-color: #409eff;
+ border-left: 4px solid #409eff;
+ background-color: #f0f5ff;
+}
+
+.child-card {
+ border-left: 4px solid #67c23a;
+ background-color: #f0f9eb;
+}
+
+.node-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px 12px;
+ background-color: rgba(0,0,0,0.03);
+ flex-wrap: wrap;
+ gap: 4px;
+}
+
+.node-label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex-wrap: wrap;
+}
+
+.node-title {
+ font-weight: 600;
+ color: #303133;
+}
+
+.node-sub {
+ font-size: 12px;
+ color: #909399;
+}
+
+.node-actions {
+ display: flex;
+ gap: 4px;
+}
+
+.node-body {
+ padding: 10px 12px;
+}
+
+.node-view {
+ padding: 6px 12px;
+ font-size: 13px;
+ color: #606266;
+}
+
+.node-children {
+ margin-left: 36px;
+ padding-left: 16px;
+ border-left: 2px dashed #dcdfe6;
+}
+</style>
diff --git a/src/views/productionManagement/productStructure/DetailNew/index.vue b/src/views/productionManagement/productStructure/DetailNew/index.vue
new file mode 100644
index 0000000..33b31f7
--- /dev/null
+++ b/src/views/productionManagement/productStructure/DetailNew/index.vue
@@ -0,0 +1,617 @@
+<template>
+ <div class="app-container">
+ <PageHeader content="浜у搧缁撴瀯璇︽儏">
+ <template #right-button>
+ <el-button v-if="!dataValue.isEdit && !isOrderPage"
+ type="primary"
+ @click="dataValue.isEdit = true">缂栬緫
+ </el-button>
+ <el-button v-if="dataValue.isEdit && !isOrderPage"
+ type="primary"
+ @click="cancelEdit">鍙栨秷
+ </el-button>
+ <el-button v-if="!isOrderPage"
+ type="primary"
+ :loading="dataValue.loading"
+ @click="submit"
+ :disabled="!dataValue.isEdit">纭
+ </el-button>
+ </template>
+ </PageHeader>
+ <el-table :data="tableData"
+ border
+ :preserve-expanded-content="false"
+ :default-expand-all="true"
+ style="width: 100%">
+ <el-table-column type="expand">
+ <template #default="props">
+ <el-form ref="form" :model="dataValue">
+ <div class="tree-container">
+ <div class="tree-legend">
+ <el-tag type="" size="small" effect="dark">鎴愬搧</el-tag>
+ <span style="margin:0 4px">鈫� 鏈�涓婂眰锛堜骇鍑虹墿锛�</span>
+ <el-divider direction="vertical" />
+ <span style="margin:0 4px">鏈�涓嬪眰锛堟姇鍏ョ墿锛夆啋</span>
+ <el-tag type="success" size="small" effect="dark">鍘熸枡</el-tag>
+ </div>
+
+ <div v-if="dataValue.dataList.length === 0 && dataValue.isEdit" class="empty-hint">
+ 璇风偣鍑讳笅鏂规寜閽坊鍔犳垚鍝�
+ </div>
+
+ <MaterialCard
+ v-for="(item, index) in dataValue.dataList"
+ :key="item.tempId"
+ :row="item"
+ :depth="0"
+ :editable="dataValue.isEdit"
+ :process-options="dataValue.processOptions"
+ @remove="(id: string) => removeItem(id)"
+ @add="(id: string) => addChildItem(id)"
+ @select-product="(tempId: string, _data: any) => { dataValue.currentRowName = tempId; dataValue.showProductDialog = true }"
+ @process-change="(row: any, v: any) => handleProcessChange(row, v)"
+ @quantity-change="handleUnitQuantityChange"
+ />
+
+ <el-button v-if="dataValue.isEdit"
+ type="primary"
+ plain
+ style="margin-top:12px"
+ @click="addRootItem">
+ + 娣诲姞鎴愬搧
+ </el-button>
+ </div>
+ </el-form>
+ </template>
+ </el-table-column>
+ <el-table-column label="BOM缂栧彿"
+ prop="bomNo" />
+ <el-table-column label="浜у搧鍚嶇О"
+ prop="productName" />
+ <el-table-column label="瑙勬牸鍨嬪彿"
+ prop="model" />
+ </el-table>
+ <product-select-dialog v-if="dataValue.showProductDialog"
+ v-model:model-value="dataValue.showProductDialog"
+ :single="true"
+ @confirm="handleProduct" />
+ </div>
+</template>
+
+<script setup lang="ts">
+ import {
+ computed,
+ defineAsyncComponent,
+ defineComponent,
+ onMounted,
+ reactive,
+ ref,
+ } from "vue";
+ import {
+ queryList,
+ addBomDetail,
+ } from "@/api/productionManagement/productStructure.js";
+ import { listProcessBom } from "@/api/productionManagement/productionOrder.js";
+ import { list } from "@/api/productionManagement/productionProcess";
+ import { ElMessage } from "element-plus";
+ import { useRoute, useRouter } from "vue-router";
+
+ defineComponent({
+ name: "StructureEdit",
+ });
+
+ const ProductSelectDialog = defineAsyncComponent(
+ () => import("@/views/basicData/product/ProductSelectDialog.vue")
+ );
+ import MaterialCard from "./MaterialCard.vue";
+ const emit = defineEmits(["update:router"]);
+ const form = ref();
+
+ const route = useRoute();
+ const router = useRouter();
+ const routeId = computed({
+ get() {
+ return route.query.id;
+ },
+
+ set(val) {
+ emit("update:router", val);
+ },
+ });
+
+ // 浠庤矾鐢卞弬鏁拌幏鍙栦骇鍝佷俊鎭�
+ const routeBomNo = computed(() => route.query.bomNo || "");
+ const routeProductName = computed(() => route.query.productName || "");
+ const routeProductModelName = computed(
+ () => route.query.productModelName || ""
+ );
+ const routeOrderId = computed(() => route.query.orderId);
+ const pageType = computed(() => route.query.type);
+ const isOrderPage = computed(
+ () => pageType.value === "order" && routeOrderId.value
+ );
+
+ const dataValue = reactive({
+ dataList: [],
+ productOptions: [],
+ processOptions: [],
+ showProductDialog: false,
+ currentRowIndex: null,
+ currentRowName: null,
+ loading: false,
+ isEdit: false,
+ });
+
+ const normalizeListData = (source: any) => {
+ if (Array.isArray(source)) {
+ return source;
+ }
+ if (Array.isArray(source?.records)) {
+ return source.records;
+ }
+ return [];
+ };
+
+ const getProcessOptionById = (id: any) => {
+ if (id === undefined || id === null || id === "") {
+ return null;
+ }
+ return (
+ normalizeListData(dataValue.processOptions).find(
+ option => String(option.id) === String(id)
+ ) || null
+ );
+ };
+
+ const syncProcessOperationFields = (item: any) => {
+ const processId = item.processId ?? item.operationId ?? "";
+ if (!processId) {
+ item.processId = "";
+ item.operationId = "";
+ item.processName = "";
+ item.operationName = "";
+ return;
+ }
+
+ const option = getProcessOptionById(processId);
+ const processName =
+ option?.name || item.processName || item.operationName || "";
+
+ item.processId = processId;
+ item.operationId = processId;
+ item.processName = processName;
+ item.operationName = processName;
+ };
+
+ const normalizeTreeData = (items: any[]) => {
+ items.forEach((item: any) => {
+ item.tempId = item.tempId || item.id || `${Date.now()}_${Math.random()}`;
+ syncProcessOperationFields(item);
+ if (Array.isArray(item.children) && item.children.length > 0) {
+ normalizeTreeData(item.children);
+ }
+ });
+ };
+
+ const toQuantityNumber = (value: any) => {
+ const numberValue = Number(value);
+ if (!Number.isFinite(numberValue)) {
+ return 0;
+ }
+ return Number(numberValue.toFixed(2));
+ };
+
+ const syncDemandedQuantityTree = (
+ items: any[],
+ parentDemandedQuantity: number | null = null
+ ) => {
+ items.forEach((item: any) => {
+ if (parentDemandedQuantity !== null) {
+ item.demandedQuantity = toQuantityNumber(
+ parentDemandedQuantity * toQuantityNumber(item.unitQuantity)
+ );
+ }
+
+ if (Array.isArray(item.children) && item.children.length > 0) {
+ syncDemandedQuantityTree(
+ item.children,
+ toQuantityNumber(item.demandedQuantity)
+ );
+ }
+ });
+ };
+
+ const recalculateDemandedQuantities = () => {
+ if (!isOrderPage.value) {
+ return;
+ }
+
+ syncDemandedQuantityTree(dataValue.dataList);
+ };
+
+ const buildSubmitTree = (items: any[]) => {
+ return items.map((item: any) => {
+ const current = { ...item };
+ syncProcessOperationFields(current);
+ current.children = Array.isArray(current.children)
+ ? buildSubmitTree(current.children)
+ : [];
+ return current;
+ });
+ };
+
+ const findSiblings = (items: any[], tempId: string): any[] | null => {
+ if (!items || items.length === 0) return null;
+ // 妫�鏌ュ綋鍓嶅眰绾�
+ if (items.some(item => item.tempId === tempId)) {
+ return items;
+ }
+ // 閫掑綊鏌ユ壘瀛愮骇
+ for (const item of items) {
+ if (item.children && item.children.length > 0) {
+ const result = findSiblings(item.children, tempId);
+ if (result) return result;
+ }
+ }
+ return null;
+ };
+
+ const handleProcessChange = (row: any, value: any) => {
+ row.processId = value || "";
+ syncProcessOperationFields(row);
+
+ // 妫�鏌ュ悓涓�灞傜骇鏄惁宸茬粡鏈夊叾浠栦笉鍚岀殑宸ュ簭琚�変腑
+ const siblings = findSiblings(dataValue.dataList, row.tempId);
+ if (siblings && value) {
+ const hasDifferentProcess = siblings.some(sibling => {
+ return sibling.tempId !== row.tempId && sibling.processId && sibling.processId !== value;
+ });
+ if (hasDifferentProcess) {
+ ElMessage.warning("鍚屼竴灞傜骇宸插瓨鍦ㄤ笉鍚岀殑宸ュ簭锛岃鍏堢粺涓�宸ュ簭鍚庡啀杩涜淇敼");
+ }
+ }
+ };
+
+ const handleUnitQuantityChange = () => {
+ recalculateDemandedQuantities();
+ };
+
+ const tableData = reactive([
+ {
+ productName: "",
+ model: "",
+ bomNo: "",
+ },
+ ]);
+
+ const openDialog = (tempId: any) => {
+ console.log(tempId, "tempId");
+ dataValue.currentRowName = tempId;
+ dataValue.showProductDialog = true;
+ };
+
+ const fetchData = async () => {
+ if (isOrderPage.value) {
+ // 璁㈠崟鎯呭喌锛氫娇鐢ㄨ鍗曠殑浜у搧缁撴瀯鎺ュ彛
+ const { data } = await listProcessBom({ orderId: routeOrderId.value });
+ dataValue.dataList = (data as any) || [];
+ normalizeTreeData(dataValue.dataList);
+ recalculateDemandedQuantities();
+ } else {
+ // 闈炶鍗曟儏鍐碉細浣跨敤鍘熸潵鐨勬帴鍙�
+ const { data } = await queryList(routeId.value);
+ dataValue.dataList = (data as any) || [];
+ console.log(dataValue);
+ normalizeTreeData(dataValue.dataList);
+ console.log(dataValue.dataList, "dataValue.dataList");
+ }
+ };
+
+ const fetchProcessOptions = async () => {
+ const { data } = await list({});
+ console.log(data, "dataValue.dataList");
+ dataValue.processOptions = normalizeListData(data);
+ };
+
+ const handleProduct = (row: any) => {
+ if (!Array.isArray(row) || row.length === 0) {
+ ElMessage.warning("璇烽�夋嫨涓�涓骇鍝�");
+ return;
+ }
+ // 鍙厑璁镐竴涓細濡傛灉涓婃父杩斿洖浜嗗涓紝榛樿浣跨敤鏈�鍚庝竴娆¢�夋嫨骞惰鐩栧綋鍓嶅��
+ const productData = row[row.length - 1];
+
+ // 鏈�澶栧眰缁勪欢涓紝涓庡綋鍓嶄骇鍝佺浉鍚岀殑浜у搧鍙兘鏈変竴涓�
+ const isTopLevel = dataValue.dataList.some(
+ item => (item as any).tempId === dataValue.currentRowName
+ );
+ if (isTopLevel) {
+ if (
+ productData.productName === tableData[0].productName &&
+ productData.model === tableData[0].model
+ ) {
+ // 鏌ユ壘鏄惁宸茬粡鏈夊叾浠栭《灞傝宸茬粡鏄繖涓骇鍝�
+ const hasOther = dataValue.dataList.some(
+ item =>
+ (item as any).tempId !== dataValue.currentRowName &&
+ (item as any).productName === tableData[0].productName &&
+ (item as any).model === tableData[0].model
+ );
+ if (hasOther) {
+ ElMessage.warning("鏈�澶栧眰鍜屽綋鍓嶄骇鍝佷竴鏍风殑涓�绾у彧鑳芥湁涓�涓�");
+ return;
+ }
+ }
+ }
+ // dataValue.dataList[dataValue.currentRowIndex].productName =
+ // row[0].productName;
+ // dataValue.dataList[dataValue.currentRowIndex].model = row[0].model;
+ // dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id;
+ // dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || "";
+ dataValue.dataList.map(item => {
+ if (item.tempId === dataValue.currentRowName) {
+ item.productName = productData.productName;
+ item.model = productData.model;
+ item.productModelId = productData.id;
+ item.unit = productData.unit || "";
+ return;
+ }
+ childItem(item, dataValue.currentRowName, productData);
+ });
+ dataValue.showProductDialog = false;
+ };
+ const childItem = (item: any, tempId: any, productData: any) => {
+ if (item.tempId === tempId) {
+ item.productName = productData.productName;
+ item.model = productData.model;
+ item.productModelId = productData.id;
+ item.unit = productData.unit || "";
+ return true;
+ }
+ if (item.children && item.children.length > 0) {
+ for (let child of item.children) {
+ if (childItem(child, tempId, productData)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ // 閫掑綊鏍¢獙鎵�鏈夊眰绾х殑琛ㄥ崟鏁版嵁
+ const validateAll = () => {
+ let isValid = true;
+
+ // 鏍¢獙涓�缁勫厔寮熻妭鐐圭殑宸ュ簭鏄惁閮界浉鍚�
+ const checkProcessUniqueness = (items: any[]) => {
+ if (!items || items.length === 0 || !isValid) return;
+
+ // 鑾峰彇绗竴涓潪绌虹殑宸ュ簭ID浣滀负鍙傝��
+ const firstProcessId = items.find(item => item.processId)?.processId;
+
+ // 濡傛灉鏈夊伐搴廔D锛屾鏌ユ墍鏈夐」鏄惁閮戒娇鐢ㄧ浉鍚岀殑宸ュ簭
+ if (firstProcessId) {
+ for (const item of items) {
+ if (item.processId && item.processId !== firstProcessId) {
+ const option1 = getProcessOptionById(firstProcessId);
+ const option2 = getProcessOptionById(item.processId);
+ const processName1 = option1?.name || "鏈煡宸ュ簭";
+ const processName2 = option2?.name || "鏈煡宸ュ簭";
+ ElMessage.error(
+ `褰撳墠灞傜骇涓嬪伐搴忎笉涓�鑷达紝璇蜂娇鐢ㄧ浉鍚岀殑宸ュ簭銆傚瓨鍦ㄣ��${processName1}銆嶅拰銆�${processName2}銆峘
+ );
+ isValid = false;
+ return;
+ }
+ }
+ }
+
+ // 閫掑綊鏍¢獙瀛愮骇鐨勫厔寮熻妭鐐�
+ for (const item of items) {
+ if (item.children && item.children.length > 0) {
+ checkProcessUniqueness(item.children);
+ }
+ }
+ };
+
+ // 鏍¢獙鍑芥暟
+ const validateItem = (item: any, isTopLevel = false) => {
+ if (!isValid) return;
+ // 鏍¢獙褰撳墠椤圭殑蹇呭~瀛楁
+ if (!item.model) {
+ ElMessage.error("璇烽�夋嫨瑙勬牸");
+ isValid = false;
+ return;
+ }
+ if (!isTopLevel && !item.processId) {
+ ElMessage.error("璇烽�夋嫨娑堣�楀伐搴�");
+ isValid = false;
+ return;
+ }
+ if (!item.unitQuantity) {
+ ElMessage.error("璇疯緭鍏ュ崟浣嶄骇鍑烘墍闇�鏁伴噺");
+ isValid = false;
+ return;
+ }
+ if (isOrderPage.value && !item.demandedQuantity) {
+ ElMessage.error("璇疯緭鍏ラ渶姹傛�婚噺");
+ isValid = false;
+ return;
+ }
+ // if (!item.unit) {
+ // ElMessage.error("璇疯緭鍏ュ崟浣�");
+ // isValid = false;
+ // return;
+ // }
+
+ // 閫掑綊鏍¢獙瀛愰」瀛楁
+ if (item.children && item.children.length > 0) {
+ item.children.forEach(child => {
+ validateItem(child, false);
+ });
+ }
+ };
+
+ // 1. 棣栧厛鏍¢獙鍚屼竴鐖剁骇涓嬬殑鍚屽眰娑堣�楀伐搴忔槸鍚﹀敮涓�
+ checkProcessUniqueness(dataValue.dataList);
+ if (!isValid) return false;
+
+ // 2. 鐒跺悗閬嶅巻鏍¢獙鎵�鏈夐《灞傞」鐨勫瓧娈靛繀濉儏鍐�
+ dataValue.dataList.forEach(item => {
+ validateItem(item, true);
+ });
+
+ return isValid;
+ };
+
+ const submit = () => {
+ dataValue.loading = true;
+ normalizeTreeData(dataValue.dataList);
+ recalculateDemandedQuantities();
+
+ // 鍏堣繘琛岃〃鍗曟牎楠�
+ const valid = validateAll();
+ console.log(dataValue.dataList, "dataValue.dataList");
+ if (valid) {
+ addBomDetail({
+ bomId: routeId.value,
+ children: buildSubmitTree(dataValue.dataList || []),
+ })
+ .then(res => {
+ router.go(-1);
+ ElMessage.success("淇濆瓨鎴愬姛");
+ dataValue.loading = false;
+ })
+ .catch(() => {
+ dataValue.loading = false;
+ });
+ } else {
+ dataValue.loading = false;
+ }
+ };
+
+ const removeItem = (tempId: string) => {
+ const topIndex = dataValue.dataList.findIndex(item => item.tempId === tempId);
+ if (topIndex !== -1) {
+ dataValue.dataList.splice(topIndex, 1);
+ return;
+ }
+
+ const delchildItem = (items: any[], tempId: any) => {
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ if (item.tempId === tempId) {
+ items.splice(i, 1);
+ return true;
+ }
+ if (item.children && item.children.length > 0) {
+ if (delchildItem(item.children, tempId)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ dataValue.dataList.forEach(item => {
+ if (item.children && item.children.length > 0) {
+ delchildItem(item.children, tempId);
+ }
+ });
+ };
+
+ const newChildNode = (parentItem: any) => ({
+ parentId: parentItem.id || "",
+ parentTempId: parentItem.tempId || "",
+ productName: "",
+ productId: "",
+ model: undefined,
+ productModelId: undefined,
+ processId: "",
+ processName: "",
+ operationId: "",
+ operationName: "",
+ unitQuantity: 1,
+ demandedQuantity: 0,
+ unit: "",
+ children: [],
+ tempId: new Date().getTime(),
+ });
+
+ const addRootItem = () => {
+ dataValue.dataList.push(newChildNode({ id: "", tempId: "" }));
+ };
+
+ const addChildItem = (parentTempId: string) => {
+ const addToItem = (items: any[]): boolean => {
+ for (const item of items) {
+ if (item.tempId === parentTempId) {
+ if (!item.children) item.children = [];
+ item.children.push(newChildNode(item));
+ recalculateDemandedQuantities();
+ return true;
+ }
+ if (item.children?.length > 0) {
+ if (addToItem(item.children)) return true;
+ }
+ }
+ return false;
+ };
+ addToItem(dataValue.dataList);
+ };
+
+ const getPropPath = (row, field) => {
+ // 涓烘瘡涓猺ow鐢熸垚鍞竴鐨勮矾寰�
+ // 浣跨敤row.id鎴栫储寮曚綔涓哄敮涓�鏍囪瘑
+ let path = "dataList";
+
+ // 绠�鍗曞疄鐜帮細浣跨敤row鐨刬d鎴栦竴涓敮涓�鏍囪瘑
+ const uniqueId = row.id || Math.floor(Math.random() * 10000);
+ path += `.${uniqueId}`;
+
+ return path + `.${field}`;
+ };
+
+ const cancelEdit = () => {
+ dataValue.isEdit = false;
+ // dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined);
+ fetchData();
+ };
+
+ onMounted(async () => {
+ // 浠庤矾鐢卞弬鏁板洖鏄炬暟鎹�
+ tableData[0].productName = routeProductName.value as string;
+ tableData[0].model = routeProductModelName.value as string;
+ tableData[0].bomNo = routeBomNo.value as string;
+
+ // 璁㈠崟鎯呭喌涓嬬鐢ㄧ紪杈�
+ if (isOrderPage.value) {
+ dataValue.isEdit = false;
+ }
+
+ // 鍏堝姞杞藉伐搴忛�夐」锛屽啀鍔犺浇鏁版嵁锛岀‘淇漞l-select鑳藉姝g‘鍥炴樉
+ await fetchProcessOptions();
+ await fetchData();
+ });
+</script>
+
+<style scoped>
+.tree-container {
+ padding: 8px 0;
+}
+.tree-legend {
+ display: flex;
+ align-items: center;
+ margin-bottom: 12px;
+ padding: 8px 12px;
+ background: #f5f7fa;
+ border-radius: 6px;
+ font-size: 13px;
+ color: #606266;
+}
+.empty-hint {
+ text-align: center;
+ color: #909399;
+ padding: 24px 0;
+}
+</style>
\ No newline at end of file
--
Gitblit v1.9.3