fix(产品需求): 修复产品需求在测试后存在的问题。
This commit is contained in:
@@ -22,7 +22,7 @@ import { useBoolean } from '@sa/hooks';
|
||||
import {
|
||||
fetchBatchDeleteUserManagementRelation,
|
||||
fetchDeleteUserManagementRelation,
|
||||
fetchGetUserListByDeptId,
|
||||
fetchGetUserListByDeptId, fetchGetUserManagementRelation,
|
||||
fetchGetUserManagementRelationQuery,
|
||||
fetchGetUserManagementRelationTree
|
||||
} from '@/service/api';
|
||||
@@ -60,7 +60,7 @@ const { fromUserIndex = false, deptId = 100, orgType = 'company' } = defineProps
|
||||
* 当组织类型为部门、方向或团队时,隐藏母节点的编辑按钮
|
||||
*/
|
||||
const shouldHideRootEdit = computed(() => {
|
||||
return fromUserIndex && orgType !== 'company';
|
||||
return fromUserIndex && orgType;
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -134,10 +134,6 @@ async function loadTreeData() {
|
||||
|
||||
if (!error) {
|
||||
treeData.value = data || [];
|
||||
|
||||
// 数据加载完成后,展开前两层节点
|
||||
await nextTick();
|
||||
expandFirstTwoLevels();
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -168,18 +164,169 @@ async function loadTreeDataByQuery(query: Api.SystemManage.UserManagementRelatio
|
||||
/**
|
||||
* 刷新树形数据
|
||||
*
|
||||
* 清空选中状态并重新加载数据
|
||||
* 从后端重新加载整个树数据
|
||||
*/
|
||||
async function reloadTreeData() {
|
||||
// 保存当前展开状态
|
||||
saveExpandedState();
|
||||
|
||||
|
||||
checkedNodeKeys.value = [];
|
||||
await loadTreeData();
|
||||
|
||||
// 等待 Vue 渲染树节点
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
|
||||
// 数据更新后恢复展开状态
|
||||
await restoreExpandedState();
|
||||
|
||||
await nextTick();
|
||||
relationTreeRef.value?.setCheckedKeys([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在本地树数据中插入新节点
|
||||
*
|
||||
* 当从树节点新增时,直接在对应父节点下插入新节点,避免整棵树重新加载
|
||||
*
|
||||
* @param managerUserId 上级用户 ID(父节点)
|
||||
* @param newRelationId 新增的关系 ID
|
||||
*/
|
||||
async function insertNodeLocally(managerUserId: string, newRelationId: string) {
|
||||
// 从后端获取新增节点的详情
|
||||
const { data: relationDetail, error } = await fetchGetUserManagementRelation(newRelationId);
|
||||
|
||||
if (error || !relationDetail) {
|
||||
// 如果获取详情失败,降级为重新加载整棵树
|
||||
await reloadTreeData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建树节点数据
|
||||
const newNode: Api.SystemManage.UserManagementRelationTreeRespVO = {
|
||||
id: relationDetail.id,
|
||||
userId: relationDetail.subordinateUserId || '',
|
||||
userNickname: '',
|
||||
managerUserId: relationDetail.managerUserId || '',
|
||||
managerNickname: '',
|
||||
children: []
|
||||
};
|
||||
|
||||
// 在用户列表中查找下级用户的昵称
|
||||
const subordinateUser = userList.value.find(u => u.id === relationDetail.subordinateUserId);
|
||||
if (subordinateUser) {
|
||||
newNode.userNickname = subordinateUser.nickname;
|
||||
}
|
||||
|
||||
// 在用户列表中查找上级用户的昵称
|
||||
const managerUser = userList.value.find(u => u.id === relationDetail.managerUserId);
|
||||
if (managerUser) {
|
||||
newNode.managerNickname = managerUser.nickname;
|
||||
}
|
||||
|
||||
// 递归查找并插入节点
|
||||
function insertIntoTree(nodes: Api.SystemManage.UserManagementRelationTreeRespVO[]): boolean {
|
||||
for (const node of nodes) {
|
||||
if (node.userId === managerUserId) {
|
||||
// 找到父节点,插入新节点
|
||||
if (!node.children) {
|
||||
node.children = [];
|
||||
}
|
||||
node.children.push(newNode);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 递归查找子节点
|
||||
if (node.children && node.children.length > 0) {
|
||||
if (insertIntoTree(node.children)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const inserted = insertIntoTree(treeData.value);
|
||||
|
||||
if (!inserted) {
|
||||
// 如果没有找到父节点,说明新增的是根节点,重新加载整棵树
|
||||
await reloadTreeData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 展开父节点
|
||||
await nextTick();
|
||||
const tree = relationTreeRef.value;
|
||||
if (tree) {
|
||||
const treeNode = tree.getNode(managerUserId);
|
||||
if (treeNode && !treeNode.expanded) {
|
||||
treeNode.expand();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从本地树数据中删除节点
|
||||
*
|
||||
* 当删除节点时,直接从本地数据中移除,避免整棵树重新加载
|
||||
*
|
||||
* @param nodeToDelete 要删除的节点
|
||||
*/
|
||||
function removeNodeLocally(nodeToDelete: Api.SystemManage.UserManagementRelationTreeRespVO) {
|
||||
// 递归查找并删除节点
|
||||
function removeFromTree(nodes: Api.SystemManage.UserManagementRelationTreeRespVO[]): boolean {
|
||||
const index = nodes.findIndex(n => n.userId === nodeToDelete.userId);
|
||||
|
||||
if (index !== -1) {
|
||||
nodes.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 递归查找子节点
|
||||
for (const node of nodes) {
|
||||
if (node.children && node.children.length > 0) {
|
||||
if (removeFromTree(node.children)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
removeFromTree(treeData.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 展开树的所有节点
|
||||
*/
|
||||
async function expandAllNodes() {
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
|
||||
const tree = relationTreeRef.value;
|
||||
if (!tree || !treeData.value.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 递归展开所有节点
|
||||
function expandNodes(nodes: Api.SystemManage.UserManagementRelationTreeRespVO[]) {
|
||||
for (const node of nodes) {
|
||||
const treeNode = tree?.getNode(node.userId);
|
||||
if (treeNode && !treeNode.expanded) {
|
||||
treeNode.expand();
|
||||
}
|
||||
|
||||
if (node.children && node.children.length > 0) {
|
||||
expandNodes(node.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expandNodes(treeData.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理搜索
|
||||
*
|
||||
@@ -204,6 +351,9 @@ async function handleSearch() {
|
||||
await loadTreeData();
|
||||
}
|
||||
|
||||
// 搜索后展开所有节点
|
||||
await expandAllNodes();
|
||||
|
||||
await nextTick();
|
||||
relationTreeRef.value?.setCheckedKeys([]);
|
||||
}
|
||||
@@ -213,9 +363,13 @@ async function handleSearch() {
|
||||
*
|
||||
* 清空搜索条件并重新加载数据
|
||||
*/
|
||||
function resetSearchParams() {
|
||||
async function resetSearchParams() {
|
||||
Object.assign(searchParams, getInitSearchParams());
|
||||
reloadTreeData();
|
||||
|
||||
// 清空保存的展开状态,让 reloadTreeData 后展开前两层
|
||||
expandedNodeKeys.value = [];
|
||||
|
||||
await reloadTreeData();
|
||||
}
|
||||
|
||||
// 对话框相关状态
|
||||
@@ -236,10 +390,10 @@ const isAddFromTreeNode = ref(false);
|
||||
*/
|
||||
function openAdd(item?: Api.SystemManage.UserManagementRelationTreeRespVO) {
|
||||
operateType.value = 'add';
|
||||
|
||||
|
||||
// 如果是从树节点点击的新增按钮,标记为来自树节点
|
||||
isAddFromTreeNode.value = Boolean(item);
|
||||
|
||||
|
||||
// 如果是从某一行的新增按钮触发,则默认上级为当前节点用户
|
||||
// 否则默认上级为当前登录用户(在对话框组件中处理)
|
||||
editingData.value = item
|
||||
@@ -291,7 +445,9 @@ async function handleDelete(item: Api.SystemManage.UserManagementRelationTreeRes
|
||||
}
|
||||
|
||||
window.$message?.success('删除成功');
|
||||
await reloadTreeData();
|
||||
|
||||
// 使用局部删除,保持树的展开状态
|
||||
removeNodeLocally(item);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,6 +460,23 @@ async function handleBatchDelete() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存要删除的节点数据(用于局部删除)
|
||||
const nodesToDelete: Api.SystemManage.UserManagementRelationTreeRespVO[] = [];
|
||||
|
||||
// 递归查找选中的节点
|
||||
function collectCheckedNodes(nodes: Api.SystemManage.UserManagementRelationTreeRespVO[], keys: string[]) {
|
||||
for (const node of nodes) {
|
||||
if (keys.includes(node.userId)) {
|
||||
nodesToDelete.push(node);
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
collectCheckedNodes(node.children, keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collectCheckedNodes(treeData.value, checkedNodeKeys.value);
|
||||
|
||||
const { error } = await fetchBatchDeleteUserManagementRelation(checkedNodeKeys.value);
|
||||
|
||||
if (error) {
|
||||
@@ -311,7 +484,11 @@ async function handleBatchDelete() {
|
||||
}
|
||||
|
||||
window.$message?.success('删除成功');
|
||||
await reloadTreeData();
|
||||
|
||||
// 使用局部删除,保持树的展开状态
|
||||
for (const node of nodesToDelete) {
|
||||
removeNodeLocally(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -361,45 +538,24 @@ function saveExpandedState() {
|
||||
*
|
||||
* @param relationId 提交后的关系 ID
|
||||
*/
|
||||
async function handleSubmitted(_relationId: string) {
|
||||
async function handleSubmitted(relationId: string) {
|
||||
closeOperateModal();
|
||||
await reloadTreeData();
|
||||
|
||||
// 操作完成后恢复树节点的展开状态
|
||||
await restoreExpandedState();
|
||||
|
||||
|
||||
// 如果是从树节点新增,使用局部插入
|
||||
if (operateType.value === 'add' && isAddFromTreeNode.value && editingData.value?.managerUserId) {
|
||||
await insertNodeLocally(editingData.value.managerUserId, relationId);
|
||||
} else {
|
||||
// 其他情况(头部新增、编辑)重新加载整棵树
|
||||
await reloadTreeData();
|
||||
}
|
||||
|
||||
// 重置标记
|
||||
isAddFromTreeNode.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 展开所有子节点(递归)
|
||||
*
|
||||
* @param tree 树形组件实例
|
||||
* @param nodes 节点数据数组
|
||||
*/
|
||||
function expandNodes(tree: InstanceType<typeof ElTree>, nodes: Api.SystemManage.UserManagementRelationTreeRespVO[]) {
|
||||
if (!tree || !nodes || !nodes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const node of nodes) {
|
||||
// 展开当前节点
|
||||
const treeNode = tree.getNode(node.userId);
|
||||
if (treeNode) {
|
||||
treeNode.expand();
|
||||
}
|
||||
|
||||
// 递归展开子节点
|
||||
if (node.children && node.children.length > 0) {
|
||||
expandNodes(tree, node.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 展开树的前两层节点
|
||||
*
|
||||
*
|
||||
* 只展开根节点和它们的直接子节点,第三层及更深层保持折叠
|
||||
*/
|
||||
function expandFirstTwoLevels() {
|
||||
@@ -414,7 +570,7 @@ function expandFirstTwoLevels() {
|
||||
if (treeNode) {
|
||||
treeNode.expand();
|
||||
}
|
||||
|
||||
|
||||
// 展开第二层(根节点的直接子节点)
|
||||
if (rootNode.children && rootNode.children.length > 0) {
|
||||
for (const childNode of rootNode.children) {
|
||||
@@ -429,23 +585,29 @@ function expandFirstTwoLevels() {
|
||||
|
||||
/**
|
||||
* 恢复树节点的展开状态
|
||||
*
|
||||
*
|
||||
* 根据之前保存的展开状态,恢复对应的节点展开
|
||||
*/
|
||||
async function restoreExpandedState() {
|
||||
await nextTick();
|
||||
|
||||
await nextTick();
|
||||
|
||||
const tree = relationTreeRef.value;
|
||||
if (!tree || !expandedNodeKeys.value.length) {
|
||||
if (!tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 恢复之前展开的节点
|
||||
for (const key of expandedNodeKeys.value) {
|
||||
const node = tree.getNode(key);
|
||||
if (node) {
|
||||
node.expand();
|
||||
if (expandedNodeKeys.value.length > 0) {
|
||||
// 恢复之前展开的节点
|
||||
for (const key of expandedNodeKeys.value) {
|
||||
const node = tree.getNode(key);
|
||||
if (node && !node.expanded) {
|
||||
node.expand();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果是首次加载或没有保存的状态,展开前两层
|
||||
expandFirstTwoLevels();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import {
|
||||
fetchCreateUserManagementRelation,
|
||||
fetchGetCandidateSubordinateUsers,
|
||||
fetchGetUserManagementRelation,
|
||||
fetchUpdateUserManagementRelation
|
||||
} from '@/service/api';
|
||||
@@ -73,9 +74,40 @@ const authStore = useAuthStore();
|
||||
const detailLoading = ref(false);
|
||||
const submitting = ref(false);
|
||||
|
||||
// 候选下级用户列表
|
||||
const candidateSubordinateUsers = ref<Api.SystemManage.UserSimple[]>([]);
|
||||
|
||||
// 计算属性:是否为编辑模式
|
||||
const isEdit = computed(() => props.operateType === 'edit');
|
||||
|
||||
/**
|
||||
* 加载候选下级用户列表
|
||||
*
|
||||
* 获取所有还未绑定上级的用户,用于新增时的下级用户下拉框
|
||||
*/
|
||||
async function loadCandidateSubordinateUsers() {
|
||||
const { error, data } = await fetchGetCandidateSubordinateUsers();
|
||||
|
||||
if (error) {
|
||||
candidateSubordinateUsers.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
candidateSubordinateUsers.value = data || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算下级用户下拉框选项
|
||||
*
|
||||
* 新增模式使用候选下级用户列表,编辑模式使用所有用户列表
|
||||
*/
|
||||
const subordinateUserOptions = computed<Api.SystemManage.UserSimple[]>(() => {
|
||||
if (isEdit.value) {
|
||||
return props.userList;
|
||||
}
|
||||
return candidateSubordinateUsers.value;
|
||||
});
|
||||
|
||||
/**
|
||||
* 计算对话框标题
|
||||
*/
|
||||
@@ -281,11 +313,16 @@ async function handleSubmit() {
|
||||
/**
|
||||
* 监听对话框可见性
|
||||
*
|
||||
* 打开时初始化表单
|
||||
* 打开时初始化表单并加载候选用户
|
||||
*/
|
||||
watch(visible, value => {
|
||||
watch(visible, async value => {
|
||||
if (value) {
|
||||
initModel();
|
||||
|
||||
// 如果是新增模式,加载候选下级用户(每次打开都重新加载,避免缓存)
|
||||
if (!isEdit.value) {
|
||||
await loadCandidateSubordinateUsers();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -324,7 +361,7 @@ watch(visible, value => {
|
||||
filterable
|
||||
:disabled="isEdit"
|
||||
>
|
||||
<ElOption v-for="user in props.userList" :key="user.id" :label="user.nickname" :value="user.id" />
|
||||
<ElOption v-for="user in subordinateUserOptions" :key="user.id" :label="user.nickname" :value="user.id" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
|
||||
Reference in New Issue
Block a user