提交 2e773916 authored 作者: liuluyu's avatar liuluyu

更新领域加载方式

上级 f13ac2e2
...@@ -9,8 +9,9 @@ ...@@ -9,8 +9,9 @@
:value="treeValue" :value="treeValue"
:options="treeData" :options="treeData"
:multiple="multiple" :multiple="multiple"
:loadData="asyncLoadTreeData"
v-bind="attrs" v-bind="attrs"
:show-search="{ filterTreeNode }" :show-search="{ filter: filterTreeNode }"
change-on-select change-on-select
@change="onChange" @change="onChange"
@search="searchDebounced" @search="searchDebounced"
...@@ -18,20 +19,20 @@ ...@@ -18,20 +19,20 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Cascader } from 'ant-design-vue'; import { Cascader } from 'ant-design-vue';
import { ref, unref, watch } from 'vue'; import { ref, unref, watch } from 'vue';
import { useAttrs } from '/@/hooks/core/useAttrs'; import { useAttrs } from '/@/hooks/core/useAttrs';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { useDebounceFn } from '@vueuse/core'; import { useDebounceFn } from '@vueuse/core';
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
enum Api { enum Api {
url = '/sys/dict/loadTreeData', url = '/sys/dict/loadTreeData',
view = '/sys/dict/loadDictItem/', view = '/sys/dict/loadDictItem/',
} }
const props = defineProps({ const props = defineProps({
value: propTypes.string.def(''), value: propTypes.string.def(''),
placeholder: propTypes.string.def('请选择'), placeholder: propTypes.string.def('请选择'),
dict: propTypes.string.def('id'), dict: propTypes.string.def('id'),
...@@ -46,37 +47,38 @@ const props = defineProps({ ...@@ -46,37 +47,38 @@ const props = defineProps({
multiple: propTypes.bool.def(false), multiple: propTypes.bool.def(false),
loadTriggleChange: propTypes.bool.def(false), loadTriggleChange: propTypes.bool.def(false),
reload: propTypes.number.def(1), reload: propTypes.number.def(1),
}); });
const attrs = useAttrs(); const attrs = useAttrs();
const emit = defineEmits(['change', 'update:value']); const emit = defineEmits(['change', 'update:value']);
const { createMessage } = useMessage(); const { createMessage } = useMessage();
// 响应式数据 // 响应式数据
const treeData = ref<any[]>([]); const treeData = ref<any[]>([]);
const treeValue = ref<any>(null); const treeValue = ref<any>(null);
const searchValue = ref(''); const searchValue = ref('');
const initialized = ref(false); const initialized = ref(false);
// 非响应式数据 // 非响应式数据
const loadingMap = new Map(); const loadingMap = new Map();
let tableName = ''; const childrenCache = new Map<string, any[]>();
let text = ''; let tableName = '';
let code = ''; let text = '';
let code = '';
// 计算属性
const dropdownStyle = { // 计算属性
const dropdownStyle = {
maxHeight: props.maxHeight, maxHeight: props.maxHeight,
overflow: 'auto', overflow: 'auto',
'virtual-scroll': true, 'virtual-scroll': true,
'item-size': 32, 'item-size': 32,
}; };
// 防抖搜索 // 防抖搜索
const searchDebounced = useDebounceFn(onSearch, 300); const searchDebounced = useDebounceFn(onSearch, 300);
// 合并初始化逻辑的watch // 合并初始化逻辑的watch
watch( watch(
[() => props.dict, () => props.reload], [() => props.dict, () => props.reload],
async () => { async () => {
if (!initialized.value) { if (!initialized.value) {
...@@ -88,73 +90,73 @@ watch( ...@@ -88,73 +90,73 @@ watch(
await loadItemByCode(); await loadItemByCode();
}, },
{ immediate: true } { immediate: true }
); );
// 优化后的value watch // 优化后的value watch
watch( watch(
() => props.value, () => props.value,
(newVal) => { (newVal) => {
if (newVal !== unref(treeValue)?.value) { if (newVal !== unref(treeValue)?.value) {
loadItemByCode(); loadItemByCode();
} }
} }
); );
/** /**
* 根据code获取下拉数据并回显 * 根据code获取下拉数据并回显
*/ */
async function loadItemByCode() { async function loadItemByCode() {
if (!props.value || props.value === '0') { if (!props.value || props.value === '0') {
treeValue.value = props.multiple ? [] : null; treeValue.value = props.multiple ? [] : null;
return; return;
} }
const params = { key: props.value }; const params = { key: props.value };
const result = await defHttp.get( const result = await defHttp.get({ url: `${Api.view}${props.dict}`, params }, { isTransformResponse: false });
{ url: `${Api.view}${props.dict}`, params },
{ isTransformResponse: false }
);
if (result.success && result.result?.length > 0) { if (result.success && result.result?.length > 0) {
console.log(result, 'result');
console.log(result.result, 'result.result');
console.log(props, 'props');
const values = props.value; const values = props.value;
console.log(values, 'values');
const length = values.length; const length = values.length;
console.log(length, 'length');
if (length === 2) { if (length === 2) {
treeValue.value = [values]; treeValue.value = [values];
console.log(treeValue.value, 'treeValue.value2');
} else if (length === 4) { } else if (length === 4) {
treeValue.value = [values.substring(0, 2), values]; treeValue.value = [values.substring(0, 2), values];
console.log(treeValue.value, 'treeValue.value4');
} else if (length === 6) { } else if (length === 6) {
treeValue.value = [ treeValue.value = [values.substring(0, 2), values.substring(0, 4), values];
values.substring(0, 2), console.log(treeValue.value, 'treeValue.value6');
values.substring(0, 4),
values,
];
} }
onLoadTriggleChange(result.result[0]); onLoadTriggleChange(result.result[0]);
} }
} }
function onLoadTriggleChange(text) { function onLoadTriggleChange(text) {
if (!props.multiple && props.loadTriggleChange) { if (!props.multiple && props.loadTriggleChange) {
emit('change', props.value, text); emit('change', props.value, text);
} }
} }
/** /**
* 初始化数据字典信息 * 初始化数据字典信息
*/ */
function initDictInfo() { function initDictInfo() {
const arr = props.dict?.split(','); const arr = props.dict?.split(',');
tableName = arr[0]; tableName = arr[0];
text = arr[1]; text = arr[1];
code = arr[2]; code = arr[2];
} }
/** /**
* 加载根节点数据 * 加载根节点数据
*/ */
async function loadRoot() { async function loadRoot() {
const params = { const params = {
pid: props.pidValue, pid: props.pidValue,
pidField: props.pidField, pidField: props.pidField,
...@@ -164,62 +166,45 @@ async function loadRoot() { ...@@ -164,62 +166,45 @@ async function loadRoot() {
tableName, tableName,
text, text,
code, code,
// 尝试让后端一次性返回完整树(若后端不支持会忽略该参数)
loadAll: 1,
}; };
const res = await defHttp.get({ url: Api.url, params }, { isTransformResponse: false }); const res = await defHttp.get({ url: Api.url, params }, { isTransformResponse: false });
if (res.success && res.result) { if (res.success && res.result) {
res.result.forEach(i => { treeData.value = normalizeNodes(res.result);
i.value = i.key;
if (props.onlyLevel !== '1') {
i.isLeaf = !!i.leaf;
i.label = i.title;
loadTreeData(i);
}
});
treeData.value = res.result;
} else { } else {
console.warn('树根节点查询结果异常', res); console.warn('树根节点查询结果异常', res);
} }
} }
/** /**
* 加载树节点数据 * 规范化节点字段(后端字段 -> Cascader 所需字段)
*/ */
async function loadTreeData(treeNode) { function normalizeNodes(nodes: any[]): any[] {
if (treeNode.children) return; if (!Array.isArray(nodes)) return [];
return nodes.map((n) => {
const pid = treeNode.key; const node = { ...n };
if (loadingMap.has(pid)) return loadingMap.get(pid); // 后端返回一般包含:key/title/leaf/children
node.value = node.key;
const promise = loadTreeNodeData(pid); node.label = node.title;
loadingMap.set(pid, promise); node.isLeaf = !!node.leaf;
try { if (Array.isArray(node.children) && node.children.length > 0) {
const res = await promise; node.children = normalizeNodes(node.children);
if (res.success) { } else {
res.result.forEach(i => { // 未返回 children 时,靠懒加载展开
i.value = i.key; node.children = undefined;
if (props.onlyLevel !== '2') {
i.isLeaf = !!i.leaf;
i.label = i.title;
loadTreeData(i);
} }
return node;
}); });
addChildren(pid, res.result, treeData.value);
updateTreeData();
}
} finally {
loadingMap.delete(pid);
} }
}
/** /**
* 加载单个树节点数据 * 加载单个树节点数据
*/ */
async function loadTreeNodeData(pid) { async function loadTreeNodeData(pid) {
const params = { const params = {
pid, pid,
pidField: props.pidField, pidField: props.pidField,
...@@ -231,48 +216,65 @@ async function loadTreeNodeData(pid) { ...@@ -231,48 +216,65 @@ async function loadTreeNodeData(pid) {
code, code,
}; };
return defHttp.get({ url: Api.url, params }, { isTransformResponse: false }); return defHttp.get({ url: Api.url, params }, { isTransformResponse: false });
} }
/** /**
* 更新树数据(轻量级更新 * Cascader 懒加载子节点(展开时触发
*/ */
function updateTreeData() { async function asyncLoadTreeData(selectedOptions: any[]) {
const targetOption = selectedOptions?.[selectedOptions.length - 1];
if (!targetOption) return;
const pid = String(targetOption.key ?? targetOption.value ?? '');
if (!pid) return;
// 已经加载过
if (Array.isArray(targetOption.children) && targetOption.children.length > 0) return;
// 命中缓存
const cached = childrenCache.get(pid);
if (cached) {
targetOption.children = cached;
treeData.value = treeData.value.slice(); treeData.value = treeData.value.slice();
} return;
}
/** if (loadingMap.has(pid)) {
* 添加子节点(非递归实现) await loadingMap.get(pid);
*/ return;
function addChildren(pid, children, treeArray) {
if (!treeArray || treeArray.length === 0) return;
const stack = [...treeArray];
while (stack.length) {
const item = stack.pop();
if (item.key === pid) {
if (!children || children.length === 0) {
item.isLeaf = true;
} else {
item.children = children;
} }
break;
targetOption.loading = true;
const promise = (async () => {
try {
const res = await loadTreeNodeData(pid);
if (res?.success && Array.isArray(res.result)) {
const children = normalizeNodes(res.result);
childrenCache.set(pid, children);
targetOption.children = children;
} else {
targetOption.children = [];
targetOption.isLeaf = true;
} }
if (item.children) { treeData.value = treeData.value.slice();
stack.push(...item.children); } finally {
targetOption.loading = false;
loadingMap.delete(pid);
} }
})();
loadingMap.set(pid, promise);
await promise;
} }
}
function filterTreeNode(inputValue, path) { function filterTreeNode(inputValue, path) {
return path.some(option => return path.some((option) => option.label.toLowerCase().includes(inputValue.toLowerCase()));
option.label.toLowerCase().includes(inputValue.toLowerCase()) }
);
}
/** /**
* 选中树节点事件 * 选中树节点事件
*/ */
function onChange(value, selectedOptions) { function onChange(value, _selectedOptions) {
if (!value) { if (!value) {
emitValue(''); emitValue('');
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
...@@ -281,25 +283,25 @@ function onChange(value, selectedOptions) { ...@@ -281,25 +283,25 @@ function onChange(value, selectedOptions) {
emitValue(value); emitValue(value);
} }
treeValue.value = value; treeValue.value = value;
} }
function emitValue(value) { function emitValue(value) {
emit('change', value); emit('change', value);
emit('update:value', value); emit('update:value', value);
} }
/** /**
* 搜索事件 * 搜索事件
*/ */
function onSearch(val) { function onSearch(val) {
searchValue.value = val; searchValue.value = val;
// 可以在这里添加实际的搜索逻辑 // 可以在这里添加实际的搜索逻辑
} }
/** /**
* 校验条件配置 * 校验条件配置
*/ */
async function validateProp() { async function validateProp() {
const mycondition = props.condition; const mycondition = props.condition;
if (!mycondition) return; if (!mycondition) return;
...@@ -312,7 +314,7 @@ async function validateProp() { ...@@ -312,7 +314,7 @@ async function validateProp() {
createMessage.error('组件JTreeSelect-condition传值有误,需要一个json字符串!'); createMessage.error('组件JTreeSelect-condition传值有误,需要一个json字符串!');
throw e; throw e;
} }
} }
</script> </script>
<style lang="less"></style> <style lang="less"></style>
...@@ -31,7 +31,6 @@ ...@@ -31,7 +31,6 @@
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
import { useAttrs } from '/@/hooks/core/useAttrs'; import { useAttrs } from '/@/hooks/core/useAttrs';
import { TreeSelect } from 'ant-design-vue';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { isObject, isArray } from '/@/utils/is'; import { isObject, isArray } from '/@/utils/is';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
...@@ -76,6 +75,8 @@ ...@@ -76,6 +75,8 @@
const text = ref<any>(''); const text = ref<any>('');
const code = ref<any>(''); const code = ref<any>('');
const show = ref<boolean>(true); const show = ref<boolean>(true);
const loadingMap = new Map<string, Promise<void>>();
const childrenCache = new Map<string, any[]>();
/** /**
* 监听value数据并初始化 * 监听value数据并初始化
*/ */
...@@ -203,6 +204,8 @@ ...@@ -203,6 +204,8 @@
tableName: unref(tableName), tableName: unref(tableName),
text: unref(text), text: unref(text),
code: unref(code), code: unref(code),
// 若后端支持一次性返回整棵树,可利用该参数减少请求(不支持会忽略)
loadAll: 1,
}; };
let res = await defHttp.get({ url: Api.url, params }, { isTransformResponse: false }); let res = await defHttp.get({ url: Api.url, params }, { isTransformResponse: false });
if (res.success && res.result) { if (res.success && res.result) {
...@@ -234,12 +237,25 @@ ...@@ -234,12 +237,25 @@
*/ */
async function asyncLoadTreeData(treeNode) { async function asyncLoadTreeData(treeNode) {
if (treeNode.dataRef.children) { if (treeNode.dataRef.children) {
return Promise.resolve(); return;
} }
if (props.url) { if (props.url) {
return Promise.resolve(); return;
}
let pid = String(treeNode.dataRef.key);
if (!pid) return;
const cached = childrenCache.get(pid);
if (cached) {
addChildren(pid, cached, treeData.value);
treeData.value = [...treeData.value];
return;
}
if (loadingMap.has(pid)) {
await loadingMap.get(pid);
return;
} }
let pid = treeNode.dataRef.key;
let params = { let params = {
pid: pid, pid: pid,
pidField: props.pidField, pidField: props.pidField,
...@@ -250,6 +266,8 @@ ...@@ -250,6 +266,8 @@
text: unref(text), text: unref(text),
code: unref(code), code: unref(code),
}; };
const p = (async () => {
try {
let res = await defHttp.get({ url: Api.url, params }, { isTransformResponse: false }); let res = await defHttp.get({ url: Api.url, params }, { isTransformResponse: false });
if (res.success) { if (res.success) {
for (let i of res.result) { for (let i of res.result) {
...@@ -259,11 +277,19 @@ ...@@ -259,11 +277,19 @@
} }
// 代码逻辑说明: 【TV360X-87】树表编辑时不可选自己及子孙节点当父节点 // 代码逻辑说明: 【TV360X-87】树表编辑时不可选自己及子孙节点当父节点
handleHiddenNode(res.result); handleHiddenNode(res.result);
childrenCache.set(pid, res.result);
//添加子节点 //添加子节点
addChildren(pid, res.result, treeData.value); addChildren(pid, res.result, treeData.value);
treeData.value = [...treeData.value]; treeData.value = [...treeData.value];
} }
return Promise.resolve(); } finally {
loadingMap.delete(pid);
}
})();
loadingMap.set(pid, p);
await p;
return;
} }
/** /**
...@@ -329,9 +355,9 @@ ...@@ -329,9 +355,9 @@
/** /**
* 校验条件配置是否有误 * 校验条件配置是否有误
*/ */
function validateProp() { function validateProp(): Promise<void> {
let mycondition = props.condition; let mycondition = props.condition;
return new Promise((resolve, reject) => { return new Promise<void>((resolve, reject) => {
if (!mycondition) { if (!mycondition) {
resolve(); resolve();
} else { } else {
...@@ -341,11 +367,11 @@ ...@@ -341,11 +367,11 @@
resolve(); resolve();
} else { } else {
createMessage.error('组件JTreeSelect-condition传值有误,需要一个json字符串!'); createMessage.error('组件JTreeSelect-condition传值有误,需要一个json字符串!');
reject(); reject(new Error('invalid condition'));
} }
} catch (e) { } catch (e) {
createMessage.error('组件JTreeSelect-condition传值有误,需要一个json字符串!'); createMessage.error('组件JTreeSelect-condition传值有误,需要一个json字符串!');
reject(); reject(e as any);
} }
} }
}); });
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
:value="treeValue" :value="treeValue"
:treeData="treeData" :treeData="treeData"
:multiple="multiple" :multiple="multiple"
:loadData="asyncLoadTreeData"
v-bind="attrs" v-bind="attrs"
v-model:searchValue="searchValue" v-model:searchValue="searchValue"
show-search show-search
...@@ -61,6 +62,8 @@ ...@@ -61,6 +62,8 @@
const tableName = ref<any>(''); const tableName = ref<any>('');
const text = ref<any>(''); const text = ref<any>('');
const code = ref<any>(''); const code = ref<any>('');
const loadingMap = new Map<string, Promise<void>>();
const childrenCache = new Map<string, any[]>();
/** /**
* 监听value数据并初始化 * 监听value数据并初始化
*/ */
...@@ -149,14 +152,18 @@ ...@@ -149,14 +152,18 @@
tableName: unref(tableName), tableName: unref(tableName),
text: unref(text), text: unref(text),
code: unref(code), code: unref(code),
// 若后端支持一次性返回整棵树,可利用该参数减少请求(不支持会忽略)
loadAll: 1,
}; };
let res = await defHttp.get({ url: Api.url, params }, { isTransformResponse: false }); let res = await defHttp.get({ url: Api.url, params }, { isTransformResponse: false });
if (res.success && res.result) { if (res.success && res.result) {
for (let i of res.result) { for (let i of res.result) {
i.value = i.key; i.value = i.key;
if (props.onlyLevel != '1') { // onlyLevel=1:仅展示一级,不允许展开
if (props.onlyLevel == '1') {
i.isLeaf = true;
} else {
i.isLeaf = !!i.leaf; i.isLeaf = !!i.leaf;
loadTreeData(i);
} }
} }
treeData.value = [...res.result]; treeData.value = [...res.result];
...@@ -165,45 +172,35 @@ ...@@ -165,45 +172,35 @@
} }
} }
function loadTreeData(treeNode) {
if (treeNode.children) {
return Promise.resolve();
}
let pid = treeNode.key;
let params = {
pid: pid,
pidField: props.pidField,
hasChildField: props.hasChildField,
converIsLeafVal: props.converIsLeafVal,
condition: props.condition,
tableName: unref(tableName),
text: unref(text),
code: unref(code),
};
defHttp.get({ url: Api.url, params }, { isTransformResponse: false }).then((res) => {
if (res.success) {
for (let i of res.result) {
i.value = i.key;
if (props.onlyLevel != '2') {
i.isLeaf = !!i.leaf;
loadTreeData(i);
}
}
//添加子节点
addChildren(pid, res.result, treeData.value);
treeData.value = [...treeData.value];
}
});
}
/** /**
* 异步加载数据 * 异步加载数据
*/ */
async function asyncLoadTreeData(treeNode) { async function asyncLoadTreeData(treeNode) {
if (treeNode.dataRef.children) { if (treeNode.dataRef.children) {
return Promise.resolve(); return;
}
if (props.onlyLevel == '1') {
treeNode.dataRef.isLeaf = true;
return;
}
let pid = String(treeNode.dataRef.key);
if (!pid) return;
// 命中缓存
const cached = childrenCache.get(pid);
if (cached) {
addChildren(pid, cached, treeData.value);
treeData.value = [...treeData.value];
return;
}
// 并发去重
if (loadingMap.has(pid)) {
await loadingMap.get(pid);
return;
} }
let pid = treeNode.dataRef.key;
let params = { let params = {
pid: pid, pid: pid,
pidField: props.pidField, pidField: props.pidField,
...@@ -214,17 +211,28 @@ ...@@ -214,17 +211,28 @@
text: unref(text), text: unref(text),
code: unref(code), code: unref(code),
}; };
const p = (async () => {
try {
let res = await defHttp.get({ url: Api.url, params }, { isTransformResponse: false }); let res = await defHttp.get({ url: Api.url, params }, { isTransformResponse: false });
if (res.success) { if (res.success) {
for (let i of res.result) { for (let i of res.result) {
i.value = i.key; i.value = i.key;
i.isLeaf = !!i.leaf; // onlyLevel=2:只展示到第二级,第二级不允许继续展开
i.isLeaf = props.onlyLevel == '2' ? true : !!i.leaf;
} }
//添加子节点 childrenCache.set(pid, res.result);
addChildren(pid, res.result, treeData.value); addChildren(pid, res.result, treeData.value);
treeData.value = [...treeData.value]; treeData.value = [...treeData.value];
} }
return Promise.resolve(); } finally {
loadingMap.delete(pid);
}
})();
loadingMap.set(pid, p);
await p;
return;
} }
/** /**
...@@ -275,7 +283,7 @@ ...@@ -275,7 +283,7 @@
/** /**
* 文本框值变化 * 文本框值变化
*/ */
function onSearch(val) { function onSearch(_val) {
// if(!!val){ // if(!!val){
// Object.assign(props,{dict:"st_domain,domain_name,domain_code,domain_name like %'+val+'%"}) // Object.assign(props,{dict:"st_domain,domain_name,domain_code,domain_name like %'+val+'%"})
// } // }
...@@ -284,9 +292,9 @@ ...@@ -284,9 +292,9 @@
/** /**
* 校验条件配置是否有误 * 校验条件配置是否有误
*/ */
function validateProp() { function validateProp(): Promise<void> {
let mycondition = props.condition; let mycondition = props.condition;
return new Promise((resolve, reject) => { return new Promise<void>((resolve, reject) => {
if (!mycondition) { if (!mycondition) {
resolve(); resolve();
} else { } else {
...@@ -296,11 +304,11 @@ ...@@ -296,11 +304,11 @@
resolve(); resolve();
} else { } else {
createMessage.error('组件JTreeSelect-condition传值有误,需要一个json字符串!'); createMessage.error('组件JTreeSelect-condition传值有误,需要一个json字符串!');
reject(); reject(new Error('invalid condition'));
} }
} catch (e) { } catch (e) {
createMessage.error('组件JTreeSelect-condition传值有误,需要一个json字符串!'); createMessage.error('组件JTreeSelect-condition传值有误,需要一个json字符串!');
reject(); reject(e as any);
} }
} }
}); });
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论