fix(产品需求): 修复产品需求使用状态和终止态字典的问题。
fix(组织): 修复组织编码下拉框的数据显示问题、修复组织编码负责人无法新增的问题。 fix(管理链路): 修复管理链路高度没固定,节点全部收缩等问题。
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
* - 支持节点的展开/折叠
|
||||
* - 支持单选/多选节点
|
||||
* - 提供新增、编辑、删除(单个/批量)功能
|
||||
* - 支持按管理者用户 ID 和被管理用户 ID 搜索
|
||||
* - 支持按上级用户 ID 和下级用户 ID 搜索
|
||||
*
|
||||
* 树形结构特点:
|
||||
* - 根节点:最高领导,没有上级
|
||||
@@ -134,6 +134,10 @@ async function loadTreeData() {
|
||||
|
||||
if (!error) {
|
||||
treeData.value = data || [];
|
||||
|
||||
// 数据加载完成后,展开前两层节点
|
||||
await nextTick();
|
||||
expandFirstTwoLevels();
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -167,6 +171,9 @@ async function loadTreeDataByQuery(query: Api.SystemManage.UserManagementRelatio
|
||||
* 清空选中状态并重新加载数据
|
||||
*/
|
||||
async function reloadTreeData() {
|
||||
// 保存当前展开状态
|
||||
saveExpandedState();
|
||||
|
||||
checkedNodeKeys.value = [];
|
||||
await loadTreeData();
|
||||
await nextTick();
|
||||
@@ -216,15 +223,25 @@ const { bool: operateVisible, setTrue: openOperateModal, setFalse: closeOperateM
|
||||
const operateType = ref<UI.TableOperateType>('add');
|
||||
const editingData = ref<Api.SystemManage.UserManagementRelation | null>(null);
|
||||
|
||||
/**
|
||||
* 是否在管理链路树中选中节点后点击新增
|
||||
* 用于控制新增对话框中上级用户下拉框是否禁用
|
||||
*/
|
||||
const isAddFromTreeNode = ref(false);
|
||||
|
||||
/**
|
||||
* 打开新增对话框
|
||||
*
|
||||
* @param item 当前节点数据,用于设置默认管理者为此节点用户
|
||||
* @param item 当前节点数据,用于设置默认上级为此节点用户
|
||||
*/
|
||||
function openAdd(item?: Api.SystemManage.UserManagementRelationTreeRespVO) {
|
||||
operateType.value = 'add';
|
||||
// 如果是从某一行的新增按钮触发,则默认管理者为当前节点用户
|
||||
// 否则默认管理者为当前登录用户(在对话框组件中处理)
|
||||
|
||||
// 如果是从树节点点击的新增按钮,标记为来自树节点
|
||||
isAddFromTreeNode.value = Boolean(item);
|
||||
|
||||
// 如果是从某一行的新增按钮触发,则默认上级为当前节点用户
|
||||
// 否则默认上级为当前登录用户(在对话框组件中处理)
|
||||
editingData.value = item
|
||||
? {
|
||||
id: null,
|
||||
@@ -309,14 +326,127 @@ function handleNodeCheck(checkedData: any, checkedInfo: any) {
|
||||
.filter((id: string | null): id is string => Boolean(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存当前展开的节点 ID 列表
|
||||
* 用于在刷新数据后恢复展开状态
|
||||
*/
|
||||
const expandedNodeKeys = ref<string[]>([]);
|
||||
|
||||
/**
|
||||
* 保存当前展开的节点状态
|
||||
*/
|
||||
function saveExpandedState() {
|
||||
if (!relationTreeRef.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const store = (relationTreeRef.value as any).store;
|
||||
if (!store) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allNodes = store.nodesMap || {};
|
||||
expandedNodeKeys.value = [];
|
||||
|
||||
Object.keys(allNodes).forEach(key => {
|
||||
const node = allNodes[key];
|
||||
if (node && node.expanded && node.data && node.data.userId) {
|
||||
expandedNodeKeys.value.push(node.data.userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理对话框提交事件
|
||||
*
|
||||
* @param relationId 提交后的关系 ID
|
||||
*/
|
||||
function handleSubmitted(_relationId: string) {
|
||||
async function handleSubmitted(_relationId: string) {
|
||||
closeOperateModal();
|
||||
reloadTreeData();
|
||||
await reloadTreeData();
|
||||
|
||||
// 操作完成后恢复树节点的展开状态
|
||||
await restoreExpandedState();
|
||||
|
||||
// 重置标记
|
||||
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() {
|
||||
const tree = relationTreeRef.value;
|
||||
if (!tree || !treeData.value.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 展开第一层(根节点)
|
||||
for (const rootNode of treeData.value) {
|
||||
const treeNode = tree.getNode(rootNode.userId);
|
||||
if (treeNode) {
|
||||
treeNode.expand();
|
||||
}
|
||||
|
||||
// 展开第二层(根节点的直接子节点)
|
||||
if (rootNode.children && rootNode.children.length > 0) {
|
||||
for (const childNode of rootNode.children) {
|
||||
const childTreeNode = tree.getNode(childNode.userId);
|
||||
if (childTreeNode) {
|
||||
childTreeNode.expand();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复树节点的展开状态
|
||||
*
|
||||
* 根据之前保存的展开状态,恢复对应的节点展开
|
||||
*/
|
||||
async function restoreExpandedState() {
|
||||
await nextTick();
|
||||
|
||||
const tree = relationTreeRef.value;
|
||||
if (!tree || !expandedNodeKeys.value.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 恢复之前展开的节点
|
||||
for (const key of expandedNodeKeys.value) {
|
||||
const node = tree.getNode(key);
|
||||
if (node) {
|
||||
node.expand();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,7 +486,7 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-col-stretch gap-16px overflow-hidden">
|
||||
<div class="flex-col-stretch gap-16px overflow-hidden" style="height: calc(70vh - 120px)">
|
||||
<!-- 搜索区域 -->
|
||||
<RelationSearch
|
||||
v-model:model="searchParams"
|
||||
@@ -366,7 +496,7 @@ onMounted(async () => {
|
||||
/>
|
||||
|
||||
<!-- 树形卡片区域 -->
|
||||
<ElCard class="flex-1-hidden card-wrapper">
|
||||
<ElCard class="flex-1-hidden card-wrapper min-h-0">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between gap-12px">
|
||||
<div class="flex items-center gap-10px">
|
||||
@@ -463,6 +593,7 @@ onMounted(async () => {
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
:user-list="userList"
|
||||
:is-add-from-tree-node="isAddFromTreeNode"
|
||||
@submitted="handleSubmitted"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
* - 表单验证和提交
|
||||
*
|
||||
* 表单字段:
|
||||
* - 管理者用户:必填,下拉选择,默认当前登录用户
|
||||
* - 被管理用户:必填,下拉选择,默认空
|
||||
* - 上级用户:必填,下拉选择,默认当前登录用户
|
||||
* - 下级用户用户:必填,下拉选择,默认空
|
||||
* - 生效开始时间:可选
|
||||
* - 生效结束时间:可选
|
||||
* - 备注:可选
|
||||
@@ -39,6 +39,8 @@ interface Props {
|
||||
rowData?: Api.SystemManage.UserManagementRelation | null;
|
||||
/** 用户列表,由父组件统一提供 */
|
||||
userList: Api.SystemManage.UserSimple[];
|
||||
/** 是否从树节点点击新增(用于控制上级用户下拉框禁用) */
|
||||
isAddFromTreeNode?: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
@@ -115,8 +117,8 @@ function createDefaultModel(): Model {
|
||||
* 表单验证规则
|
||||
*/
|
||||
const rules = {
|
||||
managerUserId: createRequiredRule('请选择管理者用户'),
|
||||
subordinateUserId: createRequiredRule('请选择被管理用户')
|
||||
managerUserId: createRequiredRule('请选择上级用户'),
|
||||
subordinateUserId: createRequiredRule('请选择下级用户')
|
||||
} satisfies Record<string, App.Global.FormRule>;
|
||||
|
||||
/**
|
||||
@@ -162,16 +164,16 @@ async function initModel() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (!isEdit.value) {
|
||||
// 新增模式:设置管理者用户
|
||||
// 优先使用 rowData 中传入的管理者用户 ID(如从树形节点新增)
|
||||
// 新增模式:设置上级用户
|
||||
// 优先使用 rowData 中传入的上级用户 ID(如从树形节点新增)
|
||||
// 否则使用当前登录用户
|
||||
let managerUserIdToSet = resolveDefaultManagerUserId();
|
||||
|
||||
if (props.rowData && props.rowData.managerUserId) {
|
||||
// 从树形节点点击新增,管理者为当前节点用户
|
||||
// 从树形节点点击新增,上级为当前节点用户
|
||||
managerUserIdToSet = props.rowData.managerUserId;
|
||||
} else if (authStore.userInfo.userId) {
|
||||
// 头部新增,管理者为当前登录用户
|
||||
// 头部新增,上级为当前登录用户
|
||||
const currentUserId = authStore.userInfo.userId;
|
||||
const currentUserName = authStore.userInfo.userName;
|
||||
|
||||
@@ -301,18 +303,24 @@ watch(visible, value => {
|
||||
<ElForm ref="formRef" :model="model" :rules="rules" label-position="top">
|
||||
<ElRow :gutter="16">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="管理者用户" prop="managerUserId">
|
||||
<ElSelect v-model="model.managerUserId" class="w-full" placeholder="请选择管理者用户" filterable>
|
||||
<ElFormItem label="上级用户" prop="managerUserId">
|
||||
<ElSelect
|
||||
v-model="model.managerUserId"
|
||||
class="w-full"
|
||||
placeholder="请选择上级用户"
|
||||
filterable
|
||||
:disabled="props.operateType === 'add' && props.isAddFromTreeNode"
|
||||
>
|
||||
<ElOption v-for="user in props.userList" :key="user.id" :label="user.nickname" :value="user.id" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="被管理用户" prop="subordinateUserId">
|
||||
<ElFormItem label="下级用户" prop="subordinateUserId">
|
||||
<ElSelect
|
||||
v-model="model.subordinateUserId"
|
||||
class="w-full"
|
||||
placeholder="请选择被管理用户"
|
||||
placeholder="请选择下级用户"
|
||||
filterable
|
||||
:disabled="isEdit"
|
||||
>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* 用户管理链路搜索组件
|
||||
*
|
||||
* 功能说明:
|
||||
* - 提供管理者和被管理者用户下拉选择
|
||||
* - 提供上级和下级用户下拉选择
|
||||
* - 支持搜索和重置操作
|
||||
* - 与树形结构数据联动
|
||||
*
|
||||
@@ -63,11 +63,11 @@ function search() {
|
||||
<template>
|
||||
<TableSearchPanel :model="model" :action-col-lg="8" @reset="reset" @search="search">
|
||||
<!-- <ElCol :lg="8" :md="12" :sm="12">-->
|
||||
<!-- <ElFormItem label="管理者用户" prop="managerUserId">-->
|
||||
<!-- <ElFormItem label="上级用户" prop="managerUserId">-->
|
||||
<!-- <ElSelect-->
|
||||
<!-- v-model="model.managerUserId"-->
|
||||
<!-- class="w-full"-->
|
||||
<!-- placeholder="请选择管理者用户"-->
|
||||
<!-- placeholder="请选择上级用户"-->
|
||||
<!-- clearable-->
|
||||
<!-- filterable-->
|
||||
<!-- >-->
|
||||
|
||||
@@ -66,7 +66,7 @@ function mapUsersToCandidateUsers(users: Api.SystemManage.User[]): Api.SystemMan
|
||||
return users
|
||||
.filter(item => !item.resignedAt || item.resignedAt > now)
|
||||
.map(item => ({
|
||||
id: item.id,
|
||||
id: String(item.id),
|
||||
nickname: item.nickname?.trim() || item.username,
|
||||
deptId: item.deptId,
|
||||
deptName: item.deptName ?? null
|
||||
|
||||
@@ -44,7 +44,7 @@ const title = computed(() => {
|
||||
});
|
||||
|
||||
type Model = {
|
||||
userId: number | null;
|
||||
userId: string | null;
|
||||
effectiveFrom: Date | null;
|
||||
effectiveUntil: Date | null;
|
||||
remark: string;
|
||||
@@ -119,7 +119,7 @@ async function handleSubmit() {
|
||||
|
||||
const payload: Api.SystemManage.SaveOrgLeaderRelationParams = {
|
||||
deptId: props.dept.id,
|
||||
userId: Number(model.value.userId),
|
||||
userId: model.value.userId,
|
||||
effectiveFrom,
|
||||
effectiveUntil,
|
||||
remark: model.value.remark.trim() || null
|
||||
@@ -129,10 +129,10 @@ async function handleSubmit() {
|
||||
|
||||
const request =
|
||||
isEdit.value && props.rowData
|
||||
? fetchUpdateOrgLeaderRelation({ id: props.rowData.id, ...payload })
|
||||
: fetchCreateOrgLeaderRelation(payload);
|
||||
? await fetchUpdateOrgLeaderRelation({ id: props.rowData.id, ...payload })
|
||||
: await fetchCreateOrgLeaderRelation(payload);
|
||||
|
||||
const { error } = await request;
|
||||
const { error } = request;
|
||||
|
||||
submitting.value = false;
|
||||
|
||||
@@ -186,10 +186,11 @@ watch(visible, async value => {
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem :label="$t('page.system.user.effectiveFrom')" prop="effectiveFrom">
|
||||
<ElFormItem :label="$t('page.system.user.effectiveFrom')" prop="effectiveFrom" style="width: 100%">
|
||||
<ElDatePicker
|
||||
v-model="model.effectiveFrom"
|
||||
class="w-full"
|
||||
style="width: 100%"
|
||||
type="datetime"
|
||||
clearable
|
||||
:placeholder="$t('page.system.user.form.effectiveFrom')"
|
||||
@@ -197,10 +198,11 @@ watch(visible, async value => {
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem :label="$t('page.system.user.effectiveUntil')" prop="effectiveUntil">
|
||||
<ElFormItem :label="$t('page.system.user.effectiveUntil')" prop="effectiveUntil" style="width: 100%">
|
||||
<ElDatePicker
|
||||
v-model="model.effectiveUntil"
|
||||
class="w-full"
|
||||
style="width: 100%"
|
||||
type="datetime"
|
||||
clearable
|
||||
:placeholder="$t('page.system.user.form.effectiveUntil')"
|
||||
|
||||
@@ -232,7 +232,7 @@ watch(visible, async value => {
|
||||
<ElOption
|
||||
v-for="item in props.orgCodeOptions"
|
||||
:key="item.value"
|
||||
:label="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
|
||||
Reference in New Issue
Block a user