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

更新领域加载方式

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