diff --git a/inventory-web/src/views/stock/stocktake/index.vue b/inventory-web/src/views/stock/stocktake/index.vue index 1fe08b5..92088d2 100644 --- a/inventory-web/src/views/stock/stocktake/index.vue +++ b/inventory-web/src/views/stock/stocktake/index.vue @@ -2,7 +2,7 @@
-
+
@@ -119,21 +119,52 @@
- + +
+
+ +
-
- 已选 {{ checkedLocCount }} 个库位 - 清空 +
+
+ 已选 {{ checkedLocCount }} 个库位 + + 清空 + +
+ +
+ 尚未选择库位
可点「获取推荐」自动勾选,或在左侧树上手动勾选 +
+ + {{ p }} + +
+
@@ -728,7 +759,19 @@ const recLoading = ref(false) // 获取推荐中 const treeLoading = ref(false) // 库位树加载中 const locTreeData = ref([]) // 库位树(已按当前公司前缀过滤) const locTreeRef = ref() // el-tree 实例,用于 setCheckedKeys / getCheckedNodes -const checkedLocCount = ref(0) // 当前勾选的库位数(含父节点,实时展示避免误判规模) +// ★ 当前勾选的库位路径集合 —— 它与提交给后端的 scope_config.locations 完全一致。 +// 刻意**不过滤非末级节点**:后端按 warehouse_location 精确匹配,而库存的库位 +// 可能落在层级 1/2(实测 level1 有 9 个、level2 有 57 个),只提交叶子会把 +// 这些库存静默排除在抽盘范围之外。 +const selectedLocationPaths = ref([]) +const checkedLocCount = computed(() => selectedLocationPaths.value.length) + +// 创建表单当前是否可见(与模板里 v-else 分支的条件保持一致) +const showCreateForm = computed(() => + !!selectedCompany.value && !probeFailed.value && !(activeSession.value && !creatingNew.value) +) +// 抽盘配置区是左右双栏,400px 的窄容器装不下,此时把欢迎页放宽 +const idleWide = computed(() => showCreateForm.value && newScopeType.value === 'active') // 库位树按公司前缀过滤(IRIS 只看 Y,LICA 看 C/L;未配置的公司不过滤) const filterTreeByCompany = (nodes: any[], company: string) => { @@ -755,9 +798,16 @@ const loadLocationTree = async () => { } } -const updateCheckedLocCount = () => { +// 树的勾选状态 → 响应式路径集合。唯一数据源,左右两侧都读它 +const syncSelectedPaths = () => { const nodes = locTreeRef.value?.getCheckedNodes() || [] - checkedLocCount.value = nodes.length + selectedLocationPaths.value = nodes.map((n: any) => n.full_path).filter(Boolean) +} + +// 点右侧标签的 × → 同步取消左侧树上对应的勾选 +const uncheckLocation = (path: string) => { + locTreeRef.value?.setChecked(path, false, true) + syncSelectedPaths() } // 取推荐并把它们在树上自动勾选 @@ -785,13 +835,12 @@ const fetchRecommendLocations = async () => { // false = 不触发子节点级联之外的行为,按 el-tree 默认规则勾选 locTreeRef.value?.setCheckedKeys(locs, false) - updateCheckedLocCount() + // ★ 右侧明细与左侧树同源,勾完立即同步,保证两侧永远一致 + syncSelectedPaths() // 推荐里有、但树上勾不到的(不在当前公司前缀范围内等)要如实告知, // 否则这些库位会静默落选 —— 工人以为盘到了,其实没进范围 - const checkedPaths = new Set( - (locTreeRef.value?.getCheckedNodes() || []).map((n: any) => n.full_path) - ) + const checkedPaths = new Set(selectedLocationPaths.value) const missing = locs.filter(l => !checkedPaths.has(l)) if (missing.length) { ElMessage.warning(`推荐中 ${missing.length} 个库位不在当前公司的库位树上,未能勾选`) @@ -816,10 +865,10 @@ const buildScopeConfig = () => { } } -// 切换回全仓时清空树上的勾选,避免残留到下一次抽盘 +// 清空勾选(切换回全仓、或点「清空」按钮) const resetScopeSelection = () => { locTreeRef.value?.setCheckedKeys([], false) - checkedLocCount.value = 0 + syncSelectedPaths() } watch(newScopeType, (val) => { @@ -1719,6 +1768,8 @@ const goToVarianceReview = () => { border-radius: 0; } .idle-content { width: 100%; max-width: 400px; padding: 20px; } +/* 抽盘配置区是双栏,窄容器装不下,此时放宽欢迎页 */ +.idle-content.is-wide { max-width: 780px; } .app-title { font-size: 24px; margin-bottom: 10px; color: #303133; } .subtitle { color: #909399; margin-bottom: 40px; } .icon-area { margin-bottom: 20px; } @@ -1818,9 +1869,27 @@ const goToVarianceReview = () => { } .scope-recommend-text { font-size: 12px; color: #606266; } .scope-topn-input { width: 100px; } + +/* 左右双栏:左 60% 库位树 | 右 40% 已选明细 */ +.scope-columns { + display: flex; + gap: 10px; + align-items: stretch; +} +.scope-col-left { flex: 0 0 60%; max-width: 60%; min-width: 0; } +.scope-col-right { + flex: 1 1 40%; + min-width: 0; + display: flex; + flex-direction: column; + background: #fff; + border: 1px solid #ebeef5; + border-radius: 4px; + padding: 6px 8px; +} /* 平板大屏:定高 + 滚动,避免树把表单撑得过长 */ .scope-tree { - max-height: 260px; + max-height: 300px; overflow-y: auto; background: #fff; border: 1px solid #ebeef5; @@ -1829,15 +1898,30 @@ const goToVarianceReview = () => { font-size: 14px; --el-tree-node-content-height: 30px; } -.scope-selected { - margin-top: 8px; - font-size: 12px; - color: #606266; +.scope-selected-header { display: flex; align-items: center; - gap: 8px; + justify-content: space-between; + font-size: 12px; + color: #606266; + padding-bottom: 4px; + border-bottom: 1px solid #f2f3f5; + margin-bottom: 6px; +} +.scope-selected-header strong { color: #409eff; font-size: 14px; } +.scope-selected-scroll { flex: 1 1 auto; height: 260px; } +.scope-selected-empty { + font-size: 12px; + color: #c0c4cc; + line-height: 1.8; + text-align: center; + padding-top: 20px; +} +/* 已选库位标签:完整路径平铺,点 × 同步取消左侧勾选 */ +.scope-loc-tag { + margin: 0 4px 4px 0; + font-family: ui-monospace, Menlo, Consolas, monospace; } -.scope-selected strong { color: #409eff; font-size: 14px; } /* 场景 A 的进度提示与次要入口 */ .active-session-tip {