前后端修改
This commit is contained in:
@@ -1,156 +1,189 @@
|
||||
<template>
|
||||
<div style="width: 100%; max-width: 300px;">
|
||||
<treeselect
|
||||
:max-height="200"
|
||||
<div :class="['actual-warehouse-select', { 'is-block': block }]" :style="wrapperStyle">
|
||||
<el-select
|
||||
v-model="innerValue"
|
||||
:options="warehouseOptions"
|
||||
:normalizer="normalizer"
|
||||
:placeholder="placeholder"
|
||||
:clearable="clearable"
|
||||
search-nested
|
||||
:load-options="loadOptions"
|
||||
@input="onInput"
|
||||
/>
|
||||
filterable
|
||||
:filter-method="handleFilter"
|
||||
:disabled="disabled"
|
||||
size="small"
|
||||
class="actual-warehouse-select__inner"
|
||||
@change="handleChange"
|
||||
@visible-change="handleVisibleChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in displayOptions"
|
||||
:key="option.actualWarehouseId"
|
||||
:label="option.fullLabel"
|
||||
:value="option.actualWarehouseId"
|
||||
>
|
||||
<div class="option-content">
|
||||
<span class="level-tag">{{ levelLabels[option.level] }}</span>
|
||||
<span class="option-text">{{ option.fullLabel }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Treeselect from '@riophae/vue-treeselect';
|
||||
import '@riophae/vue-treeselect/dist/vue-treeselect.css';
|
||||
import { listActualWarehouse } from '@/api/wms/actualWarehouse';
|
||||
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect';
|
||||
import { listActualWarehouseTree } from '@/api/wms/actualWarehouse';
|
||||
|
||||
const LEVEL_LABELS = {
|
||||
1: '一级',
|
||||
2: '二级',
|
||||
3: '三级'
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'ActualWarehouseSelect',
|
||||
components: { Treeselect },
|
||||
props: {
|
||||
value: {
|
||||
type: [Number, String, null], // 仅保留单选类型
|
||||
type: [Number, String, null],
|
||||
default: null
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择库区/仓库/库位'
|
||||
default: '请选择实际库区/库位'
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showTop: {
|
||||
block: {
|
||||
type: Boolean,
|
||||
default: true // 是否显示顶级节点(actualWarehouseId=0)
|
||||
default: false
|
||||
},
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: 240
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
innerValue: this.value,
|
||||
warehouseOptions: [], // 初始选项(含未加载子节点的节点,标记 children: null)
|
||||
loadedChildren: {}, // 缓存已加载的子节点:key=父节点ID,value=子节点数组
|
||||
allLoadedNodes: [] // 存储所有已加载的节点,用于快速查找完整对象
|
||||
flatOptions: [],
|
||||
displayOptions: [],
|
||||
levelLabels: LEVEL_LABELS
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
wrapperStyle() {
|
||||
const widthValue = this.block ? '100%' : (typeof this.width === 'number' ? `${this.width}px` : this.width);
|
||||
return { width: widthValue };
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.innerValue = val;
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initOptions();
|
||||
created() {
|
||||
this.fetchTree();
|
||||
},
|
||||
methods: {
|
||||
/** 初始化顶级选项 */
|
||||
initOptions() {
|
||||
// 重置状态
|
||||
this.loadedChildren = {};
|
||||
this.allLoadedNodes = [];
|
||||
this.warehouseOptions = [];
|
||||
|
||||
if (this.showTop) {
|
||||
// 显示顶级节点:标记 children: null 表示需要懒加载子节点
|
||||
const topNode = {
|
||||
actualWarehouseId: 0,
|
||||
actualWarehouseName: '顶级节点',
|
||||
children: null, // 关键:标记为未加载子节点
|
||||
isDisabled: false
|
||||
};
|
||||
this.warehouseOptions.push(topNode);
|
||||
this.allLoadedNodes.push(topNode);
|
||||
} else {
|
||||
// 不显示顶级节点:直接加载 parentId=0 的节点作为顶级(初始标记为未加载)
|
||||
this.warehouseOptions.push({
|
||||
actualWarehouseId: 'temp-parent-0', // 临时父节点ID(仅用于触发首次加载)
|
||||
actualWarehouseName: '加载中...',
|
||||
children: null,
|
||||
isDisabled: true
|
||||
});
|
||||
// 触发首次加载 parentId=0 的节点
|
||||
this.loadOptions({
|
||||
action: LOAD_CHILDREN_OPTIONS,
|
||||
parentNode: { id: 0 },
|
||||
callback: (children) => {
|
||||
this.warehouseOptions = children;
|
||||
}
|
||||
});
|
||||
async fetchTree() {
|
||||
try {
|
||||
const res = await listActualWarehouseTree();
|
||||
this.flatOptions = this.flattenTree(res.data || []);
|
||||
this.displayOptions = [...this.flatOptions];
|
||||
} catch (error) {
|
||||
console.error('获取实际库区树失败', error);
|
||||
}
|
||||
},
|
||||
|
||||
/** 懒加载核心方法 */
|
||||
loadOptions({ action, parentNode, callback, instanceId }) {
|
||||
// 仅处理 "加载子节点" 动作
|
||||
if (action !== LOAD_CHILDREN_OPTIONS) {
|
||||
callback();
|
||||
flattenTree(nodes, parentPath = [], level = 1) {
|
||||
const result = [];
|
||||
nodes.forEach(node => {
|
||||
const path = [...parentPath, node.actualWarehouseName];
|
||||
const current = {
|
||||
actualWarehouseId: node.actualWarehouseId,
|
||||
parentId: node.parentId || 0,
|
||||
level,
|
||||
fullLabel: path.join(' / '),
|
||||
descendantIds: [],
|
||||
children: node.children || []
|
||||
};
|
||||
result.push(current);
|
||||
if (node.children && node.children.length) {
|
||||
const children = this.flattenTree(node.children, path, level + 1);
|
||||
children.forEach(child => {
|
||||
current.descendantIds.push(child.actualWarehouseId);
|
||||
current.descendantIds.push(...child.descendantIds);
|
||||
});
|
||||
result.push(...children);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
handleFilter(keyword) {
|
||||
const text = (keyword || '').trim();
|
||||
if (!text) {
|
||||
this.displayOptions = [...this.flatOptions];
|
||||
return;
|
||||
}
|
||||
console.log('加载子节点请求参数:', parentNode);
|
||||
|
||||
const parentId = parentNode.actualWarehouseId; // 当前父节点ID(对应 actualWarehouseId)
|
||||
|
||||
// 2. 调用接口加载子节点(parentId 作为查询条件)
|
||||
listActualWarehouse({ parentId }).then(response => {
|
||||
const children = response.data.map(item => ({
|
||||
...item,
|
||||
isDisabled: !item.isEnabled, // 禁用未启用的节点
|
||||
children: item.hasChildren ?? true ? null : [] // 有子节点则标记 children: null(需懒加载),否则空数组
|
||||
}));
|
||||
|
||||
// 如果没有子节点了,则不添加children属性
|
||||
if (children.length === 0) {
|
||||
delete parentNode.children;
|
||||
callback()
|
||||
return;
|
||||
const matchedIds = new Set();
|
||||
this.flatOptions.forEach(option => {
|
||||
if (option.fullLabel.includes(text)) {
|
||||
matchedIds.add(option.actualWarehouseId);
|
||||
option.descendantIds.forEach(id => matchedIds.add(id));
|
||||
}
|
||||
|
||||
// 5. 给父节点赋值子节点(treeselect 自动渲染)
|
||||
parentNode.children = children;
|
||||
|
||||
// 6. 回调通知加载成功
|
||||
callback();
|
||||
}).catch((error) => {
|
||||
console.error('加载子节点失败:', error);
|
||||
callback(new Error('加载失败,请重试'));
|
||||
});
|
||||
this.displayOptions = this.flatOptions.filter(option => matchedIds.has(option.actualWarehouseId));
|
||||
},
|
||||
|
||||
/** 节点格式标准化 */
|
||||
normalizer(node) {
|
||||
return {
|
||||
id: node.actualWarehouseId, // 节点唯一ID
|
||||
label: node.actualWarehouseName, // 显示文本
|
||||
children: node.children, // 子节点(null=未加载,[]=无节点)
|
||||
isDisabled: node.isDisabled, // 是否禁用
|
||||
raw: node // 保留原始节点数据,方便后续查找
|
||||
};
|
||||
handleVisibleChange(visible) {
|
||||
if (!visible) {
|
||||
this.displayOptions = [...this.flatOptions];
|
||||
}
|
||||
},
|
||||
|
||||
/** 选中值变化时触发(仅单选) */
|
||||
onInput(val) {
|
||||
handleChange(val) {
|
||||
this.$emit('input', val);
|
||||
const node = this.flatOptions.find(option => option.actualWarehouseId === val);
|
||||
this.$emit('select', node);
|
||||
},
|
||||
|
||||
/** 外部刷新方法(如需重新加载数据可调用) */
|
||||
refresh() {
|
||||
this.initOptions();
|
||||
this.fetchTree();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.actual-warehouse-select {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.actual-warehouse-select.is-block {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.actual-warehouse-select__inner {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.option-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.level-tag {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
width: 34px;
|
||||
}
|
||||
|
||||
.option-text {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user