diff --git a/inventory-web/src/views/bom/BomManage.vue b/inventory-web/src/views/bom/BomManage.vue index 9cfb83d..5432aa9 100644 --- a/inventory-web/src/views/bom/BomManage.vue +++ b/inventory-web/src/views/bom/BomManage.vue @@ -542,34 +542,16 @@ const getOrGenerateTempBomNo = (): string => { } const handleCreate = async () => { - const tempBomNo = getOrGenerateTempBomNo() - pendingDraftBomNo = tempBomNo - pendingDraftVersion = 'V1.0' - - try { - const res = await getDraftDetail({ bom_no: pendingDraftBomNo, version: pendingDraftVersion }) - if (res.code === 200 && res.data) { - const confirm = await ElMessageBox.confirm( - '检测到未发布的草稿,是否恢复继续编辑?', - '草稿恢复', - { confirmButtonText: '恢复草稿', cancelButtonText: '放弃草稿', type: 'info' } - ).catch(() => null) - - if (confirm) { - restoreDraftToForm(res.data) - dialogTitle.value = '新建 BOM' - isEditMode.value = false - isSaveAsMode.value = false - isReadOnlyMode.value = false - dialogVisible.value = true - return - } else { - resetForm() - } - } - } catch (e) {} + // 1. 全局拦截 + const isIntercepted = await checkAndInterceptGlobalDraft() + if (isIntercepted) return + // 2. 正常的纯新建逻辑 resetForm() + const tempBomNo = getOrGenerateTempBomNo() + form.bom_no = tempBomNo + originalDraftHash.value = getDraftHash() + dialogTitle.value = '新建 BOM' isEditMode.value = false isSaveAsMode.value = false @@ -578,45 +560,16 @@ const handleCreate = async () => { } const saveDraftData = async () => { + // 获取当前正在编辑的单号 const draftBomNo = form.bom_no || getOrGenerateTempBomNo() const currentHash = getDraftHash() - // 场景 A:之前已经存过/恢复过草稿,且没有任何改动 + // 场景 A:防呆拦截。如果数据没有任何改动,直接提示并阻断多余的网络请求 if (originalDraftHash.value && currentHash === originalDraftHash.value) { return ElMessage.warning('草稿数据无变动,无需重复暂存') } - // 场景 B:已经存过草稿,且发生了改动 -> 询问覆盖还是新建 - if (originalDraftHash.value && currentHash !== originalDraftHash.value) { - try { - const action = await ElMessageBox.confirm( - '检测到草稿内容已发生变动,请选择保存方式:', - '草稿变动提示', - { - confirmButtonText: '直接覆盖', - cancelButtonText: '另存为新草稿', - distinguishCancelAndClose: true, - type: 'warning' - } - ) - if (action === 'confirm') { - // 选择覆盖原草稿 - await executeSaveDraftRequest(draftBomNo) - } - } catch (action) { - if (action === 'cancel') { - // 选择另存为新草稿:生成新单号并保存 - const ts = new Date().toISOString().replace(/[-T:.Z]/g, '').slice(0, 14) - const uid = Math.random().toString(36).slice(2, 6) - const newTempNo = `DRAFT-TEMP-${ts}-${uid}` - form.bom_no = newTempNo - await executeSaveDraftRequest(newTempNo) - } - } - return - } - - // 场景 C:第一次暂存 + // 场景 B:执行保存。在"全局唯一草稿"架构下,不再询问,直接实时同步最新状态到数据库 await executeSaveDraftRequest(draftBomNo) } @@ -642,6 +595,7 @@ const executeSaveDraftRequest = async (targetBomNo: string) => { ElMessage.success('草稿暂存成功') form.bom_no = targetBomNo localStorage.setItem('pending_bom_draft_no', targetBomNo) + localStorage.setItem('pending_bom_draft_version', draftVersion) originalDraftHash.value = getDraftHash() } else { ElMessage.error(res.msg || '暂存失败') @@ -677,6 +631,71 @@ const restoreDraftToForm = (draftData: any) => { originalDraftHash.value = getDraftHash() } +const checkUserDraft = async (bomNo: string, version: string) => { + try { + const res = await getDraftDetail({ bom_no: bomNo, version: version }) + if (res.code === 200 && res.data) { + const confirm = await ElMessageBox.confirm( + `检测到未发布的草稿数据,是否恢复继续编辑?`, + '草稿恢复', + { confirmButtonText: '恢复草稿', cancelButtonText: '放弃草稿', type: 'info' } + ).catch(() => 'cancel') + + if (confirm === 'confirm') { + return res.data + } + } + } catch (e) { + // 网络异常或查不到均视为无草稿 + } + return null +} + +const checkAndInterceptGlobalDraft = async (): Promise => { + const pendingNo = localStorage.getItem('pending_bom_draft_no') + const pendingVer = localStorage.getItem('pending_bom_draft_version') || 'V1.0' + + if (pendingNo) { + try { + const res = await getDraftDetail({ bom_no: pendingNo, version: pendingVer }) + if (res.code === 200 && res.data) { + const action = await ElMessageBox.confirm( + `系统检测到您有一个未发布的草稿 [${pendingNo}]。\n\n为防止数据混乱,同一时间只允许保留一份草稿。\n• 点击【恢复草稿】继续编辑\n• 点击【放弃旧草稿】彻底销毁它并开启新任务`, + '全局草稿拦截', + { + confirmButtonText: '恢复草稿', + cancelButtonText: '放弃旧草稿', + distinguishCancelAndClose: true, + type: 'warning' + } + ).catch(action => action) + + if (action === 'confirm') { + resetForm() + restoreDraftToForm(res.data) + originalDraftHash.value = getDraftHash() + dialogTitle.value = '继续编辑草稿' + isEditMode.value = false + isSaveAsMode.value = false + isReadOnlyMode.value = false + dialogVisible.value = true + return true + } else if (action === 'cancel') { + localStorage.removeItem('pending_bom_draft_no') + localStorage.removeItem('pending_bom_draft_version') + return false + } else { + return true + } + } + } catch (e) { + localStorage.removeItem('pending_bom_draft_no') + localStorage.removeItem('pending_bom_draft_version') + } + } + return false +} + const handleView = async (row: BomItem) => { await loadDetail(row.bom_no, row.version) dialogTitle.value = '查看 BOM' @@ -686,54 +705,54 @@ const handleView = async (row: BomItem) => { dialogVisible.value = true } -const handleSaveAs = async (row: BomItem) => { - // 1. 重置 form 基础状态 - resetForm() +const handleSaveAs = async (row: any) => { + // 1. 全局拦截 + const isIntercepted = await checkAndInterceptGlobalDraft() + if (isIntercepted) return - // 2. 获取源 BOM 详情(不通过 loadDetail,显式走"深拷贝+清 ID"路径) - const res = await getBomDetail(row.bom_no, row.version) - if (res.code !== 200) return - const raw = JSON.parse(JSON.stringify(res.data)) + // 2. 正常的纯另存为逻辑 + resetForm() + const res = await getBomDetail(row.bom_no, row.version) + if (res.code !== 200) return + const raw = JSON.parse(JSON.stringify(res.data)) - // 3. ★ 核心:显式深拷贝 + 清除所有主键 ID(防止后端误判为更新操作) - if ('id' in raw) delete raw.id - if ('bom_id' in raw) delete raw.bom_id - if (Array.isArray(raw.children)) { - raw.children.forEach((c: any) => { - if ('id' in c) delete c.id - if ('bom_id' in c) delete c.bom_id - }) - } + if ('id' in raw) delete raw.id + if ('bom_id' in raw) delete raw.bom_id + if (Array.isArray(raw.children)) { + raw.children.forEach((c: any) => { + if ('id' in c) delete c.id + if ('bom_id' in c) delete c.bom_id + }) + } - // 4. 把"已清除 ID 的纯净数据"写入 form - form.children = raw.children.map((child: any, idx: number) => ({ - rowKey: idx, - child_id: child.child_id, - material_name: child.child_name || '未知物料', - material_spec: child.child_spec || '', - dosage: child.dosage, - remark: child.remark || '' - })) - if (raw.parent_id) { - form.parent_id = raw.parent_id - parentNameInput.value = raw.parent_name || '未知产品' - } - form.bom_no = (raw.parent_spec || row.bom_no).split('/')[0].trim() - form.remark = raw.remark || '' + form.children = raw.children.map((child: any, idx: number) => ({ + rowKey: idx, + child_id: child.child_id, + material_name: child.child_name || '未知物料', + material_spec: child.child_spec || '', + dosage: child.dosage, + remark: child.remark || '' + })) + if (raw.parent_id) { + form.parent_id = raw.parent_id + parentNameInput.value = raw.parent_name || '未知产品' + } - // 5. 设置"另存为"模式特有状态(版本升级单选 + 子件变更检测) - originalVersion = raw.version || '' - currentBomNo = row.bom_no - originalChildren = JSON.parse(JSON.stringify(form.children)) - form.versionUpgradeType = 'minor' - form.version = versionOptions.value.minor + form.bom_no = (raw.parent_spec || row.bom_no).split('/')[0].trim() + form.remark = raw.remark || '' + form.versionUpgradeType = 'minor' + form.version = versionOptions.value?.minor || 'V1.1' - // 6. 弹窗状态机:标题"新增 BOM",父件可改,启用版本升级单选 - dialogTitle.value = '新增 BOM' - isEditMode.value = false - isSaveAsMode.value = true - isReadOnlyMode.value = false - dialogVisible.value = true + originalVersion = raw.version || '' + currentBomNo = row.bom_no + originalChildren = JSON.parse(JSON.stringify(form.children)) + originalDraftHash.value = getDraftHash() + + dialogTitle.value = '新增 BOM (版本升级)' + isEditMode.value = false + isSaveAsMode.value = true + isReadOnlyMode.value = false + dialogVisible.value = true } const loadDetail = async (bomNo: string, version: string) => { @@ -844,6 +863,7 @@ const submitForm = async () => { if (res.code === 200) { ElMessage.success('发布成功') localStorage.removeItem('pending_bom_draft_no') + localStorage.removeItem('pending_bom_draft_version') originalDraftHash.value = '' dialogVisible.value = false fetchBomList()