| | |
| | | border |
| | | :preserve-expanded-content="false" |
| | | :default-expand-all="true" |
| | | style="width: 100%"> |
| | | style="width: 100%" |
| | | @expand-change="handleOuterExpandChange"> |
| | | <el-table-column type="expand"> |
| | | <template #default="props"> |
| | | <el-form ref="form" |
| | | :model="dataValue"> |
| | | <el-table :data="dataValue.dataList" |
| | | <div v-if="dataValue.isEdit" |
| | | class="drag-tip"> |
| | | <el-icon><Rank /></el-icon> |
| | | <span>拖动手柄调整结构:同层级直接拖动排序;拖到某行正下方成为其子级;拖到列表最前成为一级</span> |
| | | </div> |
| | | <el-table ref="tableRef" |
| | | :data="dataValue.dataList" |
| | | row-key="tempId" |
| | | default-expand-all |
| | | :tree-props="{children: 'children', hasChildren: 'hasChildren'}" |
| | | @expand-change="handleTreeExpandChange" |
| | | style="width: 100%"> |
| | | <el-table-column v-if="dataValue.isEdit" |
| | | width="44" |
| | | align="center"> |
| | | <template #default="{ row }"> |
| | | <div class="drag-handle" |
| | | title="拖动调整顺序:拖到某行正下方成为其子级,拖到列表最前成为一级"> |
| | | <el-icon><Rank /></el-icon> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="productName" |
| | | label="产品" /> |
| | | <el-table-column prop="model" |
| | |
| | | <el-form-item v-if="dataValue.isEdit" |
| | | :rules="dataValue.dataList.some(item => (item as any).tempId === row.tempId) ? [] : [{ required: true, message: '请选择消耗工序', trigger: 'change' }]" |
| | | style="margin: 0"> |
| | | <el-select v-model="row.processId" |
| | | placeholder="请选择" |
| | | filterable |
| | | clearable |
| | | style="width: 100%" |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)"> |
| | | <el-option v-for="item in dataValue.processOptions" |
| | | :key="item.id" |
| | | :label="item.name" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | <el-input v-model="row.processName" |
| | | readonly |
| | | placeholder="请选择消耗工序" |
| | | clearable |
| | | style="width: 100%" |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" |
| | | @click="openProcessDialog(row, $event)" |
| | | @clear="clearProcess(row)"> |
| | | <template #append> |
| | | <el-button icon="Search" |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" |
| | | @click="openProcessDialog(row)" /> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | style="margin: 0"> |
| | | <el-input-number v-model="row.unitQuantity" |
| | | :min="0" |
| | | :precision="2" |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | @change="handleUnitQuantityChange" |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | </el-form-item> |
| | | </template> |
| | |
| | | style="margin: 0"> |
| | | <el-input-number v-model="row.demandedQuantity" |
| | | :min="0" |
| | | :precision="2" |
| | | :step="1" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | :disabled="true" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | <el-input v-model="row.unit" |
| | | placeholder="请输入单位" |
| | | clearable |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | </el-table> |
| | | <product-select-dialog v-if="dataValue.showProductDialog" |
| | | v-model:model-value="dataValue.showProductDialog" |
| | | :single="true" |
| | | @confirm="handleProduct" /> |
| | | <process-select-dialog v-if="dataValue.showProcessDialog" |
| | | v-model:model-value="dataValue.showProcessDialog" |
| | | @confirm="handleProcessConfirm" /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | computed, |
| | | defineAsyncComponent, |
| | | defineComponent, |
| | | nextTick, |
| | | onBeforeUnmount, |
| | | onMounted, |
| | | reactive, |
| | | ref, |
| | | watch, |
| | | } from "vue"; |
| | | import { queryList, add } from "@/api/productionManagement/productStructure.js"; |
| | | import { |
| | | queryList, |
| | | addBomDetail, |
| | | } from "@/api/productionManagement/productStructure.js"; |
| | | import { listProcessBom } from "@/api/productionManagement/productionOrder.js"; |
| | | import { list } from "@/api/productionManagement/productionProcess"; |
| | | import { listAll } from "@/api/productionManagement/productionProcess"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { useRoute, useRouter } from "vue-router"; |
| | | import Sortable from "sortablejs"; |
| | | import { Rank } from "@element-plus/icons-vue"; |
| | | |
| | | defineComponent({ |
| | | name: "StructureEdit", |
| | |
| | | const ProductSelectDialog = defineAsyncComponent( |
| | | () => import("@/views/basicData/product/ProductSelectDialog.vue") |
| | | ); |
| | | const ProcessSelectDialog = defineAsyncComponent( |
| | | () => |
| | | import( |
| | | "@/views/productionManagement/productionProcess/ProcessSelectDialog.vue" |
| | | ) |
| | | ); |
| | | const emit = defineEmits(["update:router"]); |
| | | const form = ref(); |
| | | const tableRef = ref(); |
| | | let tableSortable = null; |
| | | |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | |
| | | productOptions: [], |
| | | processOptions: [], |
| | | showProductDialog: false, |
| | | showProcessDialog: false, |
| | | currentRowIndex: null, |
| | | currentRowName: null, |
| | | currentProcessRow: 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(4)); |
| | | }; |
| | | |
| | | 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); |
| | | }; |
| | | |
| | | // ===== 拖拽排序 ===== |
| | | // 按当前 DOM 行顺序展开成扁平列表(编辑模式下强制全部展开,保证行顺序与数据一致) |
| | | const buildFlatTree = () => { |
| | | const flat = []; |
| | | const walk = (items, depth, parentNode) => { |
| | | items.forEach(node => { |
| | | flat.push({ node, depth, parentArray: items, parentNode }); |
| | | if (Array.isArray(node.children) && node.children.length > 0) { |
| | | walk(node.children, depth + 1, node); |
| | | } |
| | | }); |
| | | }; |
| | | walk(dataValue.dataList, 0, null); |
| | | return flat; |
| | | }; |
| | | |
| | | const removeNodeByTempId = (items, tempId) => { |
| | | for (let i = 0; i < items.length; i++) { |
| | | if (String(items[i].tempId) === String(tempId)) { |
| | | items.splice(i, 1); |
| | | return true; |
| | | } |
| | | if ( |
| | | Array.isArray(items[i].children) && |
| | | removeNodeByTempId(items[i].children, tempId) |
| | | ) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | }; |
| | | |
| | | const updateParentRef = (node, parent) => { |
| | | node.parentTempId = parent?.tempId ?? ""; |
| | | node.parentId = parent?.id ?? ""; |
| | | }; |
| | | |
| | | const handleSortEnd = evt => { |
| | | const { oldIndex, newIndex } = evt; |
| | | if ( |
| | | typeof oldIndex !== "number" || |
| | | typeof newIndex !== "number" || |
| | | oldIndex === newIndex |
| | | ) { |
| | | return; |
| | | } |
| | | const flat = buildFlatTree(); |
| | | if (oldIndex < 0 || oldIndex >= flat.length) return; |
| | | const dragged = flat[oldIndex]; |
| | | |
| | | // 拖放后位于被拖行上方一行的数据(按最终 DOM 顺序换算到原扁平列表) |
| | | let aEntry = null; |
| | | if (newIndex > oldIndex) { |
| | | aEntry = flat[newIndex]; |
| | | } else if (newIndex > 0) { |
| | | aEntry = flat[newIndex - 1]; |
| | | } |
| | | |
| | | if (!aEntry) { |
| | | // 拖到列表最前 → 成为一级 |
| | | removeNodeByTempId(dataValue.dataList, dragged.node.tempId); |
| | | dataValue.dataList.unshift(dragged.node); |
| | | updateParentRef(dragged.node, null); |
| | | } else if (aEntry.depth < dragged.depth) { |
| | | // 拖到某行正下方 → 成为该行的子级 |
| | | removeNodeByTempId(dataValue.dataList, dragged.node.tempId); |
| | | if (!aEntry.node.children) { |
| | | aEntry.node.children = []; |
| | | } |
| | | aEntry.node.children.push(dragged.node); |
| | | updateParentRef(dragged.node, aEntry.node); |
| | | } else { |
| | | // 同级插入:向上找第一个深度不超过被拖行的节点 |
| | | let i = flat.indexOf(aEntry); |
| | | while (i >= 0 && flat[i].depth > dragged.depth) { |
| | | i--; |
| | | } |
| | | const eEntry = flat[i]; |
| | | // 拖回自己的子树内 → 不处理(防止形成循环) |
| | | if (!eEntry || eEntry.node.tempId === dragged.node.tempId) { |
| | | return; |
| | | } |
| | | removeNodeByTempId(dataValue.dataList, dragged.node.tempId); |
| | | const siblings = eEntry.parentArray; |
| | | const idx = siblings.indexOf(eEntry.node); |
| | | siblings.splice(idx + 1, 0, dragged.node); |
| | | updateParentRef(dragged.node, eEntry.parentNode); |
| | | } |
| | | |
| | | recalculateDemandedQuantities(); |
| | | }; |
| | | |
| | | const initSortable = () => { |
| | | if (!tableRef.value) return; |
| | | const tbody = tableRef.value.$el.querySelector(".el-table__body tbody"); |
| | | if (!tbody) return; |
| | | tableSortable?.destroy(); |
| | | tableSortable = new Sortable(tbody, { |
| | | animation: 150, |
| | | handle: ".drag-handle", |
| | | disabled: !dataValue.isEdit, |
| | | ghostClass: "sortable-ghost", |
| | | onEnd: handleSortEnd, |
| | | }); |
| | | }; |
| | | |
| | | const expandAllTree = () => { |
| | | const table = tableRef.value; |
| | | if (!table) return; |
| | | const walk = items => { |
| | | items.forEach(item => { |
| | | if (Array.isArray(item.children) && item.children.length > 0) { |
| | | table.toggleRowExpansion(item, true); |
| | | walk(item.children); |
| | | } |
| | | }); |
| | | }; |
| | | walk(dataValue.dataList); |
| | | }; |
| | | |
| | | // 编辑模式下不允许折叠,保证拖拽时 DOM 行顺序与 dataList 一致 |
| | | const handleTreeExpandChange = (row, expanded) => { |
| | | if (dataValue.isEdit && expanded === false) { |
| | | nextTick(() => { |
| | | tableRef.value?.toggleRowExpansion(row, true); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // 外层行折叠后内容会销毁,重新展开时重新初始化拖拽 |
| | | const handleOuterExpandChange = (row, expandedRows) => { |
| | | if (Array.isArray(expandedRows) && expandedRows.some(r => r === row)) { |
| | | nextTick(() => initSortable()); |
| | | } |
| | | }; |
| | | |
| | | watch( |
| | | () => dataValue.isEdit, |
| | | val => { |
| | | tableSortable?.option("disabled", !val); |
| | | if (val) { |
| | | nextTick(() => { |
| | | initSortable(); |
| | | expandAllTree(); |
| | | }); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | 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 openProcessDialog = (row: any, event?: Event) => { |
| | | // 点击清除按钮时不弹窗 |
| | | const target = event?.target as HTMLElement | undefined; |
| | | if (target && target.closest(".el-input__clear")) { |
| | | return; |
| | | } |
| | | dataValue.currentProcessRow = row; |
| | | dataValue.showProcessDialog = true; |
| | | }; |
| | | |
| | | const clearProcess = (row: any) => { |
| | | row.processId = ""; |
| | | row.operationId = ""; |
| | | row.processName = ""; |
| | | row.operationName = ""; |
| | | }; |
| | | |
| | | const handleProcessConfirm = (processRow: any) => { |
| | | const row = dataValue.currentProcessRow; |
| | | if (!row) { |
| | | return; |
| | | } |
| | | row.processId = processRow.id; |
| | | row.operationId = processRow.id; |
| | | row.processName = processRow.name; |
| | | row.operationName = processRow.name; |
| | | handleProcessChange(row, processRow.id); |
| | | }; |
| | | |
| | | const handleUnitQuantityChange = () => { |
| | | recalculateDemandedQuantities(); |
| | | }; |
| | | |
| | | const tableData = reactive([ |
| | | { |
| | |
| | | // 订单情况:使用订单的产品结构接口 |
| | | 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) || []; |
| | | // 为所有项及其子项设置name属性 |
| | | const setNameRecursively = (items: any[]) => { |
| | | items.forEach((item: any) => { |
| | | item.tempId = item.id; |
| | | item.processName = |
| | | dataValue.processOptions.find(option => option.id === item.processId) |
| | | ?.name || ""; |
| | | if (item.children && item.children.length > 0) { |
| | | setNameRecursively(item.children); |
| | | } |
| | | }); |
| | | }; |
| | | setNameRecursively(dataValue.dataList); |
| | | console.log(dataValue); |
| | | normalizeTreeData(dataValue.dataList); |
| | | console.log(dataValue.dataList, "dataValue.dataList"); |
| | | } |
| | | }; |
| | | |
| | | const fetchProcessOptions = async () => { |
| | | const { data } = await list(); |
| | | dataValue.processOptions = data as any; |
| | | const { data } = await listAll(); |
| | | console.log(data, "dataValue.dataList"); |
| | | dataValue.processOptions = normalizeListData(data); |
| | | }; |
| | | |
| | | const handleProduct = (row: any) => { |
| | | if (row?.length > 1) { |
| | | ElMessage.error("只能选择一个产品"); |
| | | if (!Array.isArray(row) || row.length === 0) { |
| | | ElMessage.warning("请选择一个产品"); |
| | | return; |
| | | } |
| | | const productData = row[0]; |
| | | // 只允许一个:如果上游返回了多个,默认使用最后一次选择并覆盖当前值 |
| | | const productData = row[row.length - 1]; |
| | | |
| | | // 最外层组件中,与当前产品相同的产品只能有一个 |
| | | const isTopLevel = dataValue.dataList.some(item => (item as any).tempId === dataValue.currentRowName); |
| | | 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) { |
| | | 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 |
| | | 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("最外层和当前产品一样的一级只能有一个"); |
| | |
| | | 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; |
| | | |
| | | // 如果有工序ID,检查所有项是否都使用相同的工序 |
| | | 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("请选择规格"); |
| | |
| | | // 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); |
| | | }); |
| | |
| | | |
| | | const submit = () => { |
| | | dataValue.loading = true; |
| | | normalizeTreeData(dataValue.dataList); |
| | | recalculateDemandedQuantities(); |
| | | |
| | | // 先进行表单校验 |
| | | const valid = validateAll(); |
| | | console.log(dataValue.dataList, "dataValue.dataList"); |
| | | if (valid) { |
| | | add({ |
| | | addBomDetail({ |
| | | bomId: routeId.value, |
| | | children: dataValue.dataList || [], |
| | | children: buildSubmitTree(dataValue.dataList || []), |
| | | }) |
| | | .then(res => { |
| | | router.push({ |
| | | path: "/productionManagement/productionManagement/productStructure/index", |
| | | }); |
| | | router.go(-1); |
| | | ElMessage.success("保存成功"); |
| | | dataValue.loading = false; |
| | | }) |
| | |
| | | } |
| | | }; |
| | | |
| | | const removeItem = (tempId:string) => { |
| | | const removeItem = (tempId: string) => { |
| | | // 先尝试从顶层删除 |
| | | const topIndex = dataValue.dataList.findIndex(item => item.tempId === tempId); |
| | | if (topIndex !== -1) { |
| | |
| | | productModelId: undefined, |
| | | processId: "", |
| | | processName: "", |
| | | unitQuantity: 0, |
| | | operationId: "", |
| | | operationName: "", |
| | | unitQuantity: 1, |
| | | demandedQuantity: 0, |
| | | unit: "", |
| | | children: [], |
| | | |
| | | tempId: new Date().getTime(), |
| | | }); |
| | | recalculateDemandedQuantities(); |
| | | return; |
| | | } |
| | | addchildItem(item, tempId); |
| | |
| | | model: undefined, |
| | | productModelId: undefined, |
| | | processId: "", |
| | | unitQuantity: 0, |
| | | processName: "", |
| | | operationId: "", |
| | | operationName: "", |
| | | unitQuantity: 1, |
| | | demandedQuantity: 0, |
| | | children: [], |
| | | unit: "", |
| | | tempId: new Date().getTime(), |
| | | }); |
| | | recalculateDemandedQuantities(); |
| | | return true; |
| | | } |
| | | if (item.children && item.children.length > 0) { |
| | |
| | | // 先加载工序选项,再加载数据,确保el-select能够正确回显 |
| | | await fetchProcessOptions(); |
| | | await fetchData(); |
| | | await nextTick(); |
| | | initSortable(); |
| | | }); |
| | | </script> |
| | | |
| | | onBeforeUnmount(() => { |
| | | tableSortable?.destroy(); |
| | | tableSortable = null; |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .drag-tip { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | margin-bottom: 8px; |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .drag-handle { |
| | | cursor: move; |
| | | color: #909399; |
| | | text-align: center; |
| | | } |
| | | |
| | | .drag-handle:hover { |
| | | color: var(--el-color-primary); |
| | | } |
| | | |
| | | :deep(.sortable-ghost) { |
| | | opacity: 0.6; |
| | | background-color: #f5f7fa !important; |
| | | } |
| | | </style> |