feat(stocktake): 抽盘库位选择改为左右双栏,已选明细可逐个移除
原库位树是单列、层级很深,用户勾完看不出到底选了哪些库位。 现改为左右双栏: - 左 60%:el-tree(勾选),@check 触发 syncSelectedPaths - 右 40%:已选明细面板 —— 顶部「已选 N 个库位」+ 清空按钮, el-scrollbar 内平铺 el-tag 展示完整路径(等宽字体) 双向互动:点击右侧标签的 × 会调 treeRef.setChecked(path, false, true) 同步取消左侧勾选后重算列表。注意 el-tree 的 setCheckedKeys / setChecked 都**不会**触发 @check 事件,因此这两处都显式调用了 syncSelectedPaths(), 保证左右状态强同步(获取推荐后右侧也会立刻渲染)。 ★ 右侧刻意**不过滤非末级节点**,展示的就是提交给后端的 scope_config.locations 全集。原因:后端按 warehouse_location 精确匹配,而库存的库位可能落在层级 1/2 (实测 level1 有 9 个、level2 有 57 个),只提交叶子路径会把这些库存静默排除 在抽盘范围之外。面板所见 = 实际范围,才谈得上「强同步」。 配套:抽盘配置区是双栏、400px 窄容器装不下,故在展开该配置时把欢迎页 放宽到 780px(.idle-content.is-wide),其他状态维持原宽度。
This commit is contained in:
@ -2,7 +2,7 @@
|
||||
<div class="app-container mobile-optimized">
|
||||
|
||||
<el-card v-if="!isSessionActive" class="main-card idle-card" shadow="never">
|
||||
<div class="idle-content">
|
||||
<div class="idle-content" :class="{ 'is-wide': idleWide }">
|
||||
<div class="icon-area">
|
||||
<el-icon :size="80" color="#409EFF"><VideoPlay /></el-icon>
|
||||
</div>
|
||||
@ -119,21 +119,52 @@
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-tree
|
||||
ref="locTreeRef"
|
||||
v-loading="treeLoading"
|
||||
class="scope-tree"
|
||||
:data="locTreeData"
|
||||
node-key="full_path"
|
||||
show-checkbox
|
||||
default-expand-all
|
||||
:props="{ label: 'name', children: 'children' }"
|
||||
@check="updateCheckedLocCount"
|
||||
/>
|
||||
<!-- 左:库位树(勾选) | 右:已选明细(可关闭,与左侧强同步) -->
|
||||
<div class="scope-columns">
|
||||
<div class="scope-col-left">
|
||||
<el-tree
|
||||
ref="locTreeRef"
|
||||
v-loading="treeLoading"
|
||||
class="scope-tree"
|
||||
:data="locTreeData"
|
||||
node-key="full_path"
|
||||
show-checkbox
|
||||
default-expand-all
|
||||
:props="{ label: 'name', children: 'children' }"
|
||||
@check="syncSelectedPaths"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="scope-selected">
|
||||
已选 <strong>{{ checkedLocCount }}</strong> 个库位
|
||||
<el-button link type="primary" size="small" @click="resetScopeSelection">清空</el-button>
|
||||
<div class="scope-col-right">
|
||||
<div class="scope-selected-header">
|
||||
<span>已选 <strong>{{ checkedLocCount }}</strong> 个库位</span>
|
||||
<el-button
|
||||
v-if="checkedLocCount"
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="resetScopeSelection"
|
||||
>
|
||||
清空
|
||||
</el-button>
|
||||
</div>
|
||||
<el-scrollbar class="scope-selected-scroll">
|
||||
<div v-if="!selectedLocationPaths.length" class="scope-selected-empty">
|
||||
尚未选择库位<br />可点「获取推荐」自动勾选,或在左侧树上手动勾选
|
||||
</div>
|
||||
<el-tag
|
||||
v-for="p in selectedLocationPaths"
|
||||
:key="p"
|
||||
closable
|
||||
size="small"
|
||||
type="info"
|
||||
class="scope-loc-tag"
|
||||
@close="uncheckLocation(p)"
|
||||
>
|
||||
{{ p }}
|
||||
</el-tag>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="creatingNew" class="task-form-warn">
|
||||
@ -728,7 +759,19 @@ const recLoading = ref(false) // 获取推荐中
|
||||
const treeLoading = ref(false) // 库位树加载中
|
||||
const locTreeData = ref<any[]>([]) // 库位树(已按当前公司前缀过滤)
|
||||
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<string[]>([])
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user