Commit b5ffdbbe by 周海峰

no message

parent f0e1db21
<template> <template>
<div style="padding:1vh;"> <div style="position: relative;">
<div v-show="getTreeData.length > 0"> <!-- Loading遮罩层 - 覆盖整个组件 -->
<div
class="component-loading-overlay"
:style="{ display: checkLoading ? 'flex' : 'none' }"
>
处理中...
</div>
<div v-show="treeData.length > 0">
<div class="search-container"> <div class="search-container">
<div class="search-wrapper"> <div class="search-wrapper">
<q-input <q-input
...@@ -83,21 +91,16 @@ ...@@ -83,21 +91,16 @@
</div> </div>
</div> </div>
</div> </div>
<div style="padding-top: 6vh"> <div style="padding-top: 6vh;">
<Tree <Tree
v-if="getTreeData.length > 0" v-if="treeData.length > 0"
ref="tree" ref="tree"
checkable checkable
:checkedKeys="checkedKeys"
@expand="onExpand"
:expandedKeys="expandedKeys"
:autoExpandParent="autoExpandParent"
:treeData="getTreeData"
:defaultExpandedKeys="defaultExpandedKeys" :defaultExpandedKeys="defaultExpandedKeys"
:checkedKeys="checkedKeys"
:treeData="treeData"
@check="onCheck" @check="onCheck"
@select="onSelect"
> >
<template slot="title" slot-scope="node"> <template slot="title" slot-scope="node">
<span style="color: rgb(33, 186, 69);" v-if="checkedKeys.indexOf(node.key)>-1" ></span> <span style="color: rgb(33, 186, 69);" v-if="checkedKeys.indexOf(node.key)>-1" ></span>
<span v-if="node.title.indexOf(searchValue) > -1"> <span v-if="node.title.indexOf(searchValue) > -1">
...@@ -114,7 +117,7 @@ ...@@ -114,7 +117,7 @@
</Tree> </Tree>
</div> </div>
</div> </div>
<div v-show="!getTreeData.length > 0"> <div v-show="!treeData.length > 0">
正在加载组织机构组件... 正在加载组织机构组件...
</div> </div>
</div> </div>
...@@ -186,7 +189,11 @@ ...@@ -186,7 +189,11 @@
nodeCache: null, nodeCache: null,
// 搜索相关 // 搜索相关
searchOptions: [], searchOptions: [],
showSearchMenu: false showSearchMenu: false,
// 勾选操作loading状态
checkLoading: false,
// 保存body原始样式
originalBodyStyle: null
}; };
}, },
props: { props: {
...@@ -236,33 +243,76 @@ ...@@ -236,33 +243,76 @@
onCheck(checkedKeys, e) { onCheck(checkedKeys, e) {
console.log('onCheck', checkedKeys, e); console.log('onCheck', checkedKeys, e);
let finalCheckedKeys = [...checkedKeys]; // 显示loading
this.checkLoading = true;
console.log('Loading set to true:', this.checkLoading);
// 当勾选父节点时,自动勾选所有叶子节点 // 防止页面滚动
if (e.checked && e.node.children && e.node.children.length > 0) { this.preventBodyScroll();
const leafKeys = this.getAllLeafKeys(e.node.children);
finalCheckedKeys = [...new Set([...finalCheckedKeys, ...leafKeys])];
}
// 当取消勾选父节点时,取消勾选所有叶子节点 // 强制Vue重新渲染
if (!e.checked && e.node.children && e.node.children.length > 0) { this.$forceUpdate();
const leafKeys = this.getAllLeafKeys(e.node.children);
finalCheckedKeys = finalCheckedKeys.filter(key => !leafKeys.includes(key));
}
// 只在最终结果不同时才更新,避免不必要的触发 // 使用setTimeout强制分离到下一个事件循环,确保loading先显示
if (JSON.stringify(finalCheckedKeys) !== JSON.stringify(this.checkedKeys)) { setTimeout(() => {
this.checkedKeys = finalCheckedKeys; console.log('Processing check operation...');
this.processCheckOperation(checkedKeys, e);
}, 0);
},
// 分离的处理逻辑
processCheckOperation(checkedKeys, e) {
try {
// 更新本地状态
this.checkedKeys = checkedKeys;
// 更新选中的人员列表 // 更新选中的人员列表
this.updateSelectedStaff(); this.updateSelectedStaff();
// 双向绑定会通过watch自动处理
// 处理完成后,延迟一点时间再隐藏loading,确保用户能看到
setTimeout(() => {
this.checkLoading = false;
// 恢复页面滚动
this.restoreBodyScroll();
}, 200); // 最少显示200ms,让用户看到反馈
} catch (error) {
console.error('勾选处理出错:', error);
this.checkLoading = false; // 出错时也要隐藏loading
this.restoreBodyScroll(); // 恢复页面滚动
}
},
// 防止body滚动
preventBodyScroll() {
// 保存原始样式
if (!this.originalBodyStyle) {
this.originalBodyStyle = {
overflow: document.body.style.overflow,
position: document.body.style.position
};
}
// 防止滚动
document.body.style.overflow = 'hidden';
document.body.style.position = 'fixed';
document.body.style.width = '100%';
},
// 恢复body滚动
restoreBodyScroll() {
// 恢复原始样式
if (this.originalBodyStyle) {
document.body.style.overflow = this.originalBodyStyle.overflow || '';
document.body.style.position = this.originalBodyStyle.position || '';
document.body.style.width = '';
this.originalBodyStyle = null;
} }
}, },
onSelect(selectedKeys, info) { onSelect(selectedKeys, info) {
// 在复选模式下,onSelect 可以用来处理一些辅助功能,比如展开/折叠 // 在复选模式下,onSelect 可以用来处理一些辅助功能,比如展开/折叠
// console.log('onSelect', selectedKeys, info); // console.log('onSelect', selectedKeys, info);
}, },
// 搜索输入处理 // 搜索输入处理
onSearchInput(value) { onSearchInput(value) {
this.searchValue = value || ''; this.searchValue = value || '';
...@@ -285,14 +335,12 @@ ...@@ -285,14 +335,12 @@
this.showSearchMenu = false; this.showSearchMenu = false;
} }
}, },
// 手动搜索 // 手动搜索
manualSearch() { manualSearch() {
if (this.searchText && this.searchText.length > 0) { if (this.searchText && this.searchText.length > 0) {
this.onSearchInput(this.searchText); this.onSearchInput(this.searchText);
} }
}, },
// 隐藏搜索菜单 // 隐藏搜索菜单
hideSearchMenu() { hideSearchMenu() {
// 延迟隐藏,以便点击选项时能正常触发 // 延迟隐藏,以便点击选项时能正常触发
...@@ -300,25 +348,27 @@ ...@@ -300,25 +348,27 @@
this.showSearchMenu = false; this.showSearchMenu = false;
}, 500); }, 500);
}, },
// 选择搜索选项 // 选择搜索选项
selectSearchOption(option) { selectSearchOption(option) {
this.checkLoading = true;
// 强制Vue重新渲染
this.$forceUpdate();
console.log(option,'selectSearchOption=======') console.log(option,'selectSearchOption=======')
// 勾选选中的节点 // 清空搜索状态
if (!this.checkedKeys.includes(option.key)) {
this.checkedKeys = [...this.checkedKeys, option.key];
this.updateSelectedStaff();
}
// 展开到选中节点的路径
const parentKeys = this.getParentKeys(option.key, this.treeData);
this.expandedKeys = [...new Set([...this.expandedKeys, ...parentKeys])];
this.autoExpandParent = false;
// 清空搜索状态
this.searchText = ''; this.searchText = '';
this.searchOptions = []; this.searchOptions = [];
this.showSearchMenu = false; this.showSearchMenu = false;
setTimeout(() => {
// 勾选选中的节点
if (!this.checkedKeys.includes(option.key)) {
this.checkedKeys = [option.key, ...this.checkedKeys];
}else{
//根据 key 移除
this.checkedKeys = this.checkedKeys.filter(item => item !== option.key);
}
this.updateSelectedStaff();
this.checkLoading = false;
}, 0);
}, },
// 获取节点到根节点的路径 // 获取节点到根节点的路径
...@@ -346,17 +396,17 @@ ...@@ -346,17 +396,17 @@
this.searchValue = value; this.searchValue = value;
// 展开包含搜索结果的节点 // 展开包含搜索结果的节点
const expandedKeys = dataList.map((item) => { // const expandedKeys = dataList.map((item) => {
if (item.title.indexOf(value) > -1) { // if (item.title.indexOf(value) > -1) {
return getParentKey(item.key, this.getTreeData) // return getParentKey(item.key, this.getTreeData)
} // }
return null // return null
}).filter((item, i, self) => item && self.indexOf(item) === i) // }).filter((item, i, self) => item && self.indexOf(item) === i)
if (expandedKeys.length > 0) { // if (expandedKeys.length > 0) {
this.expandedKeys = [...new Set([...this.expandedKeys, ...expandedKeys])]; // this.expandedKeys = [...new Set([...this.expandedKeys, ...expandedKeys])];
this.autoExpandParent = true; // this.autoExpandParent = true;
} // }
}, },
// 获取节点下的所有叶子节点key // 获取节点下的所有叶子节点key
getAllLeafKeys(nodes) { getAllLeafKeys(nodes) {
...@@ -377,27 +427,7 @@ ...@@ -377,27 +427,7 @@
// 更新选中的人员列表 // 更新选中的人员列表
updateSelectedStaff() { updateSelectedStaff() {
this.checkedPeople = []; this.checkedPeople = dataList.filter(node => node.key < 1000000 && this.checkedKeys.includes(node.key))
this.checkedDepartment = [];
// 使用 Map 缓存节点查找结果,避免重复遍历
if (!this.nodeCache) {
this.nodeCache = new Map();
this.buildNodeCache(this.treeData);
}
// 遍历所有选中的key,区分人员和部门
this.checkedKeys.forEach(key => {
const node = this.nodeCache.get(key);
if (node) {
if (key < 1000000) { // 人员
this.checkedPeople.push(node);
} else { // 部门
this.checkedDepartment.push(node);
}
}
});
// 向父组件发送选中的人员列表 // 向父组件发送选中的人员列表
this.$emit('staffNode', this.checkedPeople); this.$emit('staffNode', this.checkedPeople);
}, },
...@@ -467,7 +497,7 @@ ...@@ -467,7 +497,7 @@
position: fixed; position: fixed;
width: 17vw; width: 17vw;
z-index: 9999; z-index: 9999;
padding: 1vh; padding: 0 1vh;
} }
.search-wrapper { .search-wrapper {
...@@ -684,6 +714,23 @@ ...@@ -684,6 +714,23 @@
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.3);
} }
/* 组件Loading遮罩层样式 - 覆盖整个组件 */
.component-loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(3px);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
overflow: hidden; /* 防止遮罩层内部滚动 */
}
/* 响应式设计 */ /* 响应式设计 */
@media (max-width: 768px) { @media (max-width: 768px) {
.search-container { .search-container {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论