fix(产品需求): 修复产品需求在测试后存在的问题。

This commit is contained in:
dk
2026-05-09 13:42:04 +08:00
parent f4f43814b3
commit f0ea903d59
13 changed files with 706 additions and 384 deletions

View File

@@ -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();
}
}

View File

@@ -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>