feat(user-management-relation): 在用户管理页面集成用户带人关系组件,并修复相关的诸多BUG和样式问题

This commit is contained in:
dk
2026-04-14 16:33:47 +08:00
parent 9b6f5955c3
commit a6fc7b48dc
7 changed files with 187 additions and 98 deletions

View File

@@ -318,6 +318,15 @@ export function fetchGetUserPage(params?: Api.SystemManage.UserSearchParams) {
/** 为兼容旧代码保留原函数名 */ /** 为兼容旧代码保留原函数名 */
export const fetchGetUserList = fetchGetUserPage; export const fetchGetUserList = fetchGetUserPage;
/** 通过部门id获取用户详情 */
export function fetchGetUserListByDeptId(deptId: any) {
return request<Api.SystemManage.UserSimple[]>({
url: `${USER_PREFIX}/list-by-dept-id`,
method: 'get',
params: { deptId }
});
}
/** 获取用户详情 */ /** 获取用户详情 */
export function fetchGetUser(id: number) { export function fetchGetUser(id: number) {
return request<Api.SystemManage.User>({ return request<Api.SystemManage.User>({
@@ -487,10 +496,11 @@ export function fetchAssignUserRoles(data: Api.SystemManage.AssignUserRoleParams
* - 中间节点:有上级也有下级 * - 中间节点:有上级也有下级
* - 叶子节点:基层员工,没有下级 * - 叶子节点:基层员工,没有下级
*/ */
export function fetchGetUserManagementRelationTree() { export function fetchGetUserManagementRelationTree(query: UserManagementRelationQueryReqVO) {
return request<Api.SystemManage.UserManagementRelationTreeRespVO[]>({ return request<Api.SystemManage.UserManagementRelationTreeRespVO[]>({
url: `${USER_MANAGEMENT_RELATION_PREFIX}/tree`, url: `${USER_MANAGEMENT_RELATION_PREFIX}/tree`,
method: 'get' method: 'get',
params: query
}); });
} }

View File

@@ -40,8 +40,8 @@ declare namespace Api {
type RoleSearchParams = CommonType.RecordNullable<Pick<Role, 'name' | 'code' | 'status'>> & type RoleSearchParams = CommonType.RecordNullable<Pick<Role, 'name' | 'code' | 'status'>> &
PageParams & { PageParams & {
createTime?: string[]; createTime?: string[];
}; };
type SaveRoleParams = Pick<Role, 'name' | 'code' | 'sort' | 'status'> & { type SaveRoleParams = Pick<Role, 'name' | 'code' | 'sort' | 'status'> & {
remark?: string | null; remark?: string | null;
@@ -143,12 +143,12 @@ declare namespace Api {
type UserSearchParams = CommonType.RecordNullable< type UserSearchParams = CommonType.RecordNullable<
Pick<User, 'status'> & Pick<User, 'status'> &
Pick<PageParams, 'pageNo' | 'pageSize'> & { Pick<PageParams, 'pageNo' | 'pageSize'> & {
username?: string; username?: string;
mobile?: string; mobile?: string;
deptId?: number; deptId?: number;
roleId?: number; roleId?: number;
} }
>; >;
type UserList = PageResult<User>; type UserList = PageResult<User>;
@@ -387,7 +387,12 @@ declare namespace Api {
* 对应后端 UserManagementRelationQueryReqVO * 对应后端 UserManagementRelationQueryReqVO
*/ */
type UserManagementRelationQueryReqVO = CommonType.RecordNullable< type UserManagementRelationQueryReqVO = CommonType.RecordNullable<
Pick<UserManagementRelation, 'managerUserId' | 'subordinateUserId'> Pick<UserManagementRelation, 'managerUserId' | 'subordinateUserId'> & {
/** 是否来自带人关系的index组件 */
fromUserIndex: boolean;
/** 部门ID */
deptId?: number | null;
}
>; >;
/** /**

View File

@@ -325,4 +325,8 @@ getMenuTreeData();
:deep(.el-table__row.current-row > td.el-table__cell) { :deep(.el-table__row.current-row > td.el-table__cell) {
background-color: rgb(64 158 255 / 8%); background-color: rgb(64 158 255 / 8%);
} }
:deep(.el-row) {
margin: 0 0 -15px 0;
}
</style> </style>

View File

@@ -15,21 +15,33 @@
* - 叶子节点:基层员工,没有下级 * - 叶子节点:基层员工,没有下级
*/ */
import { nextTick, onMounted, reactive, ref } from 'vue'; import {nextTick, onMounted, reactive, ref} from 'vue';
import type { ElTree } from 'element-plus'; import type {ElTree} from 'element-plus';
import { ElButton, ElPopconfirm, ElTag } from 'element-plus'; import {ElButton, ElPopconfirm, ElTag} from 'element-plus';
import { useBoolean } from '@sa/hooks'; import {useBoolean} from '@sa/hooks';
import { import {
fetchBatchDeleteUserManagementRelation, fetchBatchDeleteUserManagementRelation,
fetchDeleteUserManagementRelation, fetchDeleteUserManagementRelation, fetchGetUserListByDeptId,
fetchGetUserManagementRelationQuery, fetchGetUserManagementRelationQuery,
fetchGetUserManagementRelationTree, fetchGetUserManagementRelationTree,
fetchGetUserSimpleList
} from '@/service/api'; } from '@/service/api';
import RelationOperateDialog from './modules/relation-operate-dialog.vue'; import RelationOperateDialog from './modules/relation-operate-dialog.vue';
import RelationSearch from './modules/relation-search.vue'; import RelationSearch from './modules/relation-search.vue';
defineOptions({ name: 'UserManagementRelation' }); defineOptions({name: 'UserManagementRelation'});
/**
* 组件 userQuery 定义
*
* @param fromUserIndex 是否不是从带人关系 index 页面访问(从 user 页面访问时为 true
*/
interface userQuery {
fromUserIndex?: boolean;
deptId?: number | null;
}
//从user的index组件访问带人关系fromUserIndex为true否则false; dept=100是灿能电力的id
const {fromUserIndex = false, deptId = 100} = defineProps<userQuery>()
/** /**
* 初始化搜索参数 * 初始化搜索参数
@@ -74,7 +86,7 @@ const treeProps: any = {
* 获取用户简单列表,供搜索组件和对话框组件共享使用 * 获取用户简单列表,供搜索组件和对话框组件共享使用
*/ */
async function loadUserList() { async function loadUserList() {
const { data, error } = await fetchGetUserSimpleList(); const {data, error} = await fetchGetUserListByDeptId(deptId);
if (!error) { if (!error) {
userList.value = data || []; userList.value = data || [];
} }
@@ -89,7 +101,12 @@ async function loadTreeData() {
loading.value = true; loading.value = true;
try { try {
const { data, error } = await fetchGetUserManagementRelationTree(); // 默认不是来自user的index组件访问且deptId=100查询灿能电力及其以下所有部门的用户的带人关系
const query: Api.SystemManage.UserManagementRelationQueryReqVO = {
fromUserIndex: fromUserIndex,
deptId: deptId
};
const {data, error} = await fetchGetUserManagementRelationTree(query);
if (!error) { if (!error) {
treeData.value = data || []; treeData.value = data || [];
@@ -110,7 +127,7 @@ async function loadTreeDataByQuery(query: Api.SystemManage.UserManagementRelatio
loading.value = true; loading.value = true;
try { try {
const { data, error } = await fetchGetUserManagementRelationQuery(query); const {data, error} = await fetchGetUserManagementRelationQuery(query);
if (!error) { if (!error) {
treeData.value = data || []; treeData.value = data || [];
@@ -143,13 +160,13 @@ async function handleSearch() {
// 判断是否有搜索条件 // 判断是否有搜索条件
const hasSearchCondition = searchParams.subordinateUserId !== undefined && searchParams.subordinateUserId !== null; const hasSearchCondition = searchParams.subordinateUserId !== undefined && searchParams.subordinateUserId !== null;
if (hasSearchCondition) { if (hasSearchCondition) {
// 有搜索条件,调用查询接口 // 有搜索条件,调用查询接口
const query: Api.SystemManage.UserManagementRelationQueryReqVO = { const query: Api.SystemManage.UserManagementRelationQueryReqVO = {
subordinateUserId: searchParams.subordinateUserId subordinateUserId: searchParams.subordinateUserId,
fromUserIndex: fromUserIndex,
deptId: deptId
}; };
console.log('查询参数 query:', query);
await loadTreeDataByQuery(query); await loadTreeDataByQuery(query);
} else { } else {
// 无搜索条件,加载完整树 // 无搜索条件,加载完整树
@@ -171,7 +188,7 @@ function resetSearchParams() {
} }
// 对话框相关状态 // 对话框相关状态
const { bool: operateVisible, setTrue: openOperateModal, setFalse: closeOperateModal } = useBoolean(); const {bool: operateVisible, setTrue: openOperateModal, setFalse: closeOperateModal} = useBoolean();
const operateType = ref<UI.TableOperateType>('add'); const operateType = ref<UI.TableOperateType>('add');
const editingData = ref<Api.SystemManage.UserManagementRelation | null>(null); const editingData = ref<Api.SystemManage.UserManagementRelation | null>(null);
@@ -186,14 +203,14 @@ function openAdd(item?: Api.SystemManage.UserManagementRelationTreeRespVO) {
// 否则默认管理者为当前登录用户(在对话框组件中处理) // 否则默认管理者为当前登录用户(在对话框组件中处理)
editingData.value = item editingData.value = item
? { ? {
id: null, id: null,
managerUserId: item.userId, managerUserId: item.userId,
subordinateUserId: null, subordinateUserId: null,
effectiveFrom: null, effectiveFrom: null,
effectiveUntil: null, effectiveUntil: null,
remark: null, remark: null,
createTime: Date.now() createTime: Date.now()
} }
: null; : null;
openOperateModal(); openOperateModal();
} }
@@ -208,14 +225,14 @@ function openEdit(item: Api.SystemManage.UserManagementRelationTreeRespVO) {
// 构建树节点数据为编辑所需格式 // 构建树节点数据为编辑所需格式
editingData.value = item.id editingData.value = item.id
? { ? {
id: item.id, id: item.id,
managerUserId: item.managerUserId, managerUserId: item.managerUserId,
subordinateUserId: item.userId, subordinateUserId: item.userId,
effectiveFrom: null, effectiveFrom: null,
effectiveUntil: null, effectiveUntil: null,
remark: null, remark: null,
createTime: Date.now() createTime: Date.now()
} }
: null; : null;
openOperateModal(); openOperateModal();
} }
@@ -226,7 +243,7 @@ function openEdit(item: Api.SystemManage.UserManagementRelationTreeRespVO) {
* @param item 要删除的关系记录 * @param item 要删除的关系记录
*/ */
async function handleDelete(item: Api.SystemManage.UserManagementRelationTreeRespVO) { async function handleDelete(item: Api.SystemManage.UserManagementRelationTreeRespVO) {
const { error } = await fetchDeleteUserManagementRelation(item.id); const {error} = await fetchDeleteUserManagementRelation(item.id);
if (error) { if (error) {
return; return;
@@ -246,7 +263,7 @@ async function handleBatchDelete() {
return; return;
} }
const { error } = await fetchBatchDeleteUserManagementRelation(checkedNodeKeys.value); const {error} = await fetchBatchDeleteUserManagementRelation(checkedNodeKeys.value);
if (error) { if (error) {
return; return;
@@ -287,6 +304,25 @@ function hasChildren(node: Api.SystemManage.UserManagementRelationTreeRespVO): b
return Boolean(node.children && node.children.length > 0); return Boolean(node.children && node.children.length > 0);
} }
/**
* 计算树形数据中所有节点的数量
*
* 递归遍历树形结构,统计所有节点总数
*
* @param nodes 树形数据数组
* @returns 节点总数
*/
function countTreeNodes(nodes: Api.SystemManage.UserManagementRelationTreeRespVO[]): number {
let count = 0;
for (const node of nodes) {
count += 1;
if (node.children && node.children.length > 0) {
count += countTreeNodes(node.children);
}
}
return count;
}
onMounted(async () => { onMounted(async () => {
await loadUserList(); await loadUserList();
await reloadTreeData(); await reloadTreeData();
@@ -309,28 +345,28 @@ onMounted(async () => {
<div class="flex items-center justify-between gap-12px"> <div class="flex items-center justify-between gap-12px">
<div class="flex items-center gap-10px"> <div class="flex items-center gap-10px">
<p>用户带人关系树</p> <p>用户带人关系树</p>
<ElTag effect="plain">{{ treeData.length }}</ElTag> <ElTag effect="plain">{{ countTreeNodes(treeData) }}</ElTag>
</div> </div>
<div class="flex items-center gap-10px"> <div class="flex items-center gap-10px">
<ElButton plain type="primary" @click="openAdd()"> <ElButton plain type="primary" @click="openAdd()">
<template #icon> <template #icon>
<icon-ic-round-plus class="text-icon" /> <icon-ic-round-plus class="text-icon"/>
</template> </template>
新增 新增
</ElButton> </ElButton>
<ElPopconfirm title="确认删除选中的关系吗?" @confirm="handleBatchDelete"> <!-- <ElPopconfirm title="确认删除选中的关系吗?" @confirm="handleBatchDelete">-->
<template #reference> <!-- <template #reference>-->
<ElButton type="danger" plain :disabled="checkedNodeKeys.length === 0"> <!-- <ElButton type="danger" plain :disabled="checkedNodeKeys.length === 0">-->
<template #icon> <!-- <template #icon>-->
<icon-ic-round-delete class="text-icon" /> <!-- <icon-ic-round-delete class="text-icon"/>-->
</template> <!-- </template>-->
批量删除 <!-- 批量删除-->
</ElButton> <!-- </ElButton>-->
</template> <!-- </template>-->
</ElPopconfirm> <!-- </ElPopconfirm>-->
<ElButton @click="reloadTreeData"> <ElButton @click="reloadTreeData">
<template #icon> <template #icon>
<icon-ic-round-refresh class="text-icon" /> <icon-ic-round-refresh class="text-icon"/>
</template> </template>
刷新 刷新
</ElButton> </ElButton>
@@ -346,7 +382,6 @@ onMounted(async () => {
:data="treeData" :data="treeData"
:props="treeProps" :props="treeProps"
node-key="userId" node-key="userId"
show-checkbox
default-expand-all default-expand-all
:expand-on-click-node="false" :expand-on-click-node="false"
@check="handleNodeCheck" @check="handleNodeCheck"
@@ -355,18 +390,18 @@ onMounted(async () => {
<div class="flex flex-1 items-center justify-between"> <div class="flex flex-1 items-center justify-between">
<span class="flex items-center gap-8px"> <span class="flex items-center gap-8px">
<span>{{ node.label }}</span> <span>{{ node.label }}</span>
<ElTag v-if="data.managerNickname" size="small" type="info">上级{{ data.managerNickname }}</ElTag> <!-- <ElTag v-if="data.managerNickname" size="small" type="info">上级{{ data.managerNickname }}</ElTag>-->
</span> </span>
<div class="flex items-center"> <div class="flex items-center">
<ElButton link type="primary" size="default" @click.stop="openAdd(data)"> <ElButton link type="primary" size="default" @click.stop="openAdd(data)">
<template #icon> <template #icon>
<icon-ic-round-plus class="text-icon" /> <icon-ic-round-plus class="text-icon"/>
</template> </template>
新增 新增
</ElButton> </ElButton>
<ElButton link type="primary" size="small" @click.stop="openEdit(data)"> <ElButton link type="primary" size="small" @click.stop="openEdit(data)">
<template #icon> <template #icon>
<icon-ic-round-edit class="text-icon" /> <icon-ic-round-edit class="text-icon"/>
</template> </template>
编辑 编辑
</ElButton> </ElButton>
@@ -378,14 +413,13 @@ onMounted(async () => {
<template #reference> <template #reference>
<ElButton link type="danger" size="small"> <ElButton link type="danger" size="small">
<template #icon> <template #icon>
<icon-ic-round-delete class="text-icon" /> <icon-ic-round-delete class="text-icon"/>
</template> </template>
删除 删除
</ElButton> </ElButton>
</template> </template>
</ElPopconfirm> </ElPopconfirm>
<ElTag v-else-if="hasChildren(data)" size="small" type="info" style="margin-left: 6px">有下级</ElTag> <span v-else-if="hasChildren(data)" style="margin-left: 56px"></span>
<ElTag v-else size="small" type="warning">不可删除</ElTag>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -281,28 +281,34 @@ watch(visible, value => {
</ElSelect> </ElSelect>
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
</ElRow>
<ElRow :gutter="16">
<ElCol :span="12"> <ElCol :span="12">
<ElFormItem label="生效开始时间" prop="effectiveFrom"> <ElFormItem label="生效开始时间" prop="effectiveFrom" style="width:100%">
<ElDatePicker <ElDatePicker
v-model="model.effectiveFrom" v-model="model.effectiveFrom"
class="w-full" class="w-full"
type="datetime" type="datetime"
placeholder="请选择生效开始时间" placeholder="请选择生效开始时间"
value-format="x" value-format="x"
style="width:100%"
/> />
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
<ElCol :span="12"> <ElCol :span="12">
<ElFormItem label="生效结束时间" prop="effectiveUntil"> <ElFormItem label="生效结束时间" prop="effectiveUntil" style="width:100%">
<ElDatePicker <ElDatePicker
v-model="model.effectiveUntil" v-model="model.effectiveUntil"
class="w-full" class="w-full"
type="datetime" type="datetime"
placeholder="请选择生效结束时间" placeholder="请选择生效结束时间"
value-format="x" value-format="x"
style="width:100%"
/> />
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
</ElRow>
<ElRow :gutter="16">
<ElCol :span="24"> <ElCol :span="24">
<ElFormItem label="备注" prop="remark"> <ElFormItem label="备注" prop="remark">
<ElInput v-model="model.remark" type="textarea" :rows="4" placeholder="请输入备注" /> <ElInput v-model="model.remark" type="textarea" :rows="4" placeholder="请输入备注" />
@@ -313,4 +319,6 @@ watch(visible, value => {
</BusinessFormDialog> </BusinessFormDialog>
</template> </template>
<style scoped></style> <style scoped>
</style>

View File

@@ -14,9 +14,9 @@
* - 用户列表通过 props 传入,由父组件统一管理 * - 用户列表通过 props 传入,由父组件统一管理
*/ */
import { defineEmits, defineModel, defineOptions } from 'vue'; import {defineEmits, defineModel, defineOptions} from 'vue';
defineOptions({ name: 'RelationSearch' }); defineOptions({name: 'RelationSearch'});
/** /**
* 组件 Emits 定义 * 组件 Emits 定义
@@ -43,7 +43,7 @@ defineProps<Props>();
/** /**
* 搜索参数模型,支持双向绑定 * 搜索参数模型,支持双向绑定
*/ */
const model = defineModel<Api.SystemManage.UserManagementRelationQueryReqVO>('model', { required: true }); const model = defineModel<Api.SystemManage.UserManagementRelationQueryReqVO>('model', {required: true});
/** /**
* 重置搜索 * 重置搜索
@@ -81,9 +81,9 @@ function search() {
<!-- </ElFormItem>--> <!-- </ElFormItem>-->
<!-- </ElCol>--> <!-- </ElCol>-->
<ElCol :lg="8" :md="12" :sm="12"> <ElCol :lg="8" :md="12" :sm="12">
<ElFormItem label="用户名" prop="subordinateUserId"> <ElFormItem label="用户名" prop="subordinateUserId" style="margin-left: -50px">
<ElSelect v-model="model.subordinateUserId" class="w-full" placeholder="请选择用户名" clearable filterable> <ElSelect v-model="model.subordinateUserId" class="w-full" placeholder="请选择用户名" clearable filterable>
<ElOption v-for="user in userList" :key="user.id" :label="user.nickname" :value="user.id" /> <ElOption v-for="user in userList" :key="user.id" :label="user.nickname" :value="user.id"/>
</ElSelect> </ElSelect>
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
@@ -94,4 +94,8 @@ function search() {
:deep(.el-form-item__label) { :deep(.el-form-item__label) {
width: 100px !important; width: 100px !important;
} }
:deep(.el-row) {
margin: 0 0 -15px 0;
}
</style> </style>

View File

@@ -1,10 +1,10 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'; import {computed, nextTick, onMounted, reactive, ref, watch} from 'vue';
import type { TableInstance } from 'element-plus'; import type {TableInstance} from 'element-plus';
import { ElButton, ElPopconfirm, ElSwitch, ElTag } from 'element-plus'; import {ElButton, ElPopconfirm, ElSwitch, ElTag} from 'element-plus';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import type { FlatResponseData } from '@sa/axios'; import type {FlatResponseData} from '@sa/axios';
import { userGenderRecord } from '@/constants/business'; import {userGenderRecord} from '@/constants/business';
import { import {
fetchBatchDeleteUser, fetchBatchDeleteUser,
fetchDeleteDept, fetchDeleteDept,
@@ -17,10 +17,11 @@ import {
fetchUpdateUser, fetchUpdateUser,
fetchUpdateUserStatus fetchUpdateUserStatus
} from '@/service/api'; } from '@/service/api';
import { useUIPaginatedTable } from '@/hooks/common/table'; import {useUIPaginatedTable} from '@/hooks/common/table';
import BusinessTableActionCell from '@/components/custom/business-table-action-cell'; import BusinessTableActionCell from '@/components/custom/business-table-action-cell';
import { $t } from '@/locales'; import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
import { buildMenuTree } from '@/views/system/shared/menu-tree'; import {$t} from '@/locales';
import {buildMenuTree} from '@/views/system/shared/menu-tree';
import UserOperateDialog from './modules/user-operate-dialog.vue'; import UserOperateDialog from './modules/user-operate-dialog.vue';
import UserOrgLeaderDialog from './modules/user-org-leader-dialog.vue'; import UserOrgLeaderDialog from './modules/user-org-leader-dialog.vue';
import UserOrgOperateDialog from './modules/user-org-operate-dialog.vue'; import UserOrgOperateDialog from './modules/user-org-operate-dialog.vue';
@@ -28,8 +29,9 @@ import UserOrgPanel from './modules/user-org-panel.vue';
import UserResignedDialog from './modules/user-resigned-dialog.vue'; import UserResignedDialog from './modules/user-resigned-dialog.vue';
import UserResetPasswordDialog from './modules/user-reset-password-dialog.vue'; import UserResetPasswordDialog from './modules/user-reset-password-dialog.vue';
import UserSearch from './modules/user-search.vue'; import UserSearch from './modules/user-search.vue';
import UserManagementRelation from '@/views/system/user-management-relation/index.vue';
defineOptions({ name: 'UserManage' }); defineOptions({name: 'UserManage'});
function getInitSearchParams(): Api.SystemManage.UserSearchParams { function getInitSearchParams(): Api.SystemManage.UserSearchParams {
return { return {
@@ -146,12 +148,13 @@ const editingDeptData = ref<Api.SystemManage.Dept | null>(null);
const orgParentId = ref<number | null>(0); const orgParentId = ref<number | null>(0);
const orgLeaderVisible = ref(false); const orgLeaderVisible = ref(false);
const leaderDeptData = ref<Api.SystemManage.Dept | null>(null); const leaderDeptData = ref<Api.SystemManage.Dept | null>(null);
const userManagementRelationVisible = ref(false);
const deptTree = computed(() => buildMenuTree(deptList.value)); const deptTree = computed(() => buildMenuTree(deptList.value));
const currentDept = computed(() => deptList.value.find(item => item.id === currentDeptId.value) ?? null); const currentDept = computed(() => deptList.value.find(item => item.id === currentDeptId.value) ?? null);
const deptCount = computed(() => deptList.value.length); const deptCount = computed(() => deptList.value.length);
const { columns, columnChecks, data, loading, getDataByPage, mobilePagination } = useUIPaginatedTable< const {columns, columnChecks, data, loading, getDataByPage, mobilePagination} = useUIPaginatedTable<
FlatResponseData<any, Api.SystemManage.UserList>, FlatResponseData<any, Api.SystemManage.UserList>,
Api.SystemManage.User Api.SystemManage.User
>({ >({
@@ -175,9 +178,9 @@ const { columns, columnChecks, data, loading, getDataByPage, mobilePagination }
searchParams.pageSize = params.pageSize ?? 10; searchParams.pageSize = params.pageSize ?? 10;
}, },
columns: () => [ columns: () => [
{ prop: 'selection', type: 'selection', width: 48 }, {prop: 'selection', type: 'selection', width: 48},
{ prop: 'index', type: 'index', label: $t('common.index'), width: 64 }, {prop: 'index', type: 'index', label: $t('common.index'), width: 64},
{ prop: 'username', label: $t('page.system.user.userName'), minWidth: 140, showOverflowTooltip: true }, {prop: 'username', label: $t('page.system.user.userName'), minWidth: 140, showOverflowTooltip: true},
{ {
prop: 'nickname', prop: 'nickname',
label: $t('page.system.user.nickName'), label: $t('page.system.user.nickName'),
@@ -257,9 +260,9 @@ const { columns, columnChecks, data, loading, getDataByPage, mobilePagination }
formatter: row => { formatter: row => {
const state = getUserResignedState(row); const state = getUserResignedState(row);
const stateMap: Record<UserResignedState, { type: UI.ThemeColor; label: App.I18n.I18nKey }> = { const stateMap: Record<UserResignedState, { type: UI.ThemeColor; label: App.I18n.I18nKey }> = {
active: { type: 'success', label: 'page.system.user.resignedStateEnum.active' }, active: {type: 'success', label: 'page.system.user.resignedStateEnum.active'},
pending: { type: 'warning', label: 'page.system.user.resignedStateEnum.pending' }, pending: {type: 'warning', label: 'page.system.user.resignedStateEnum.pending'},
resigned: { type: 'info', label: 'page.system.user.resignedStateEnum.resigned' } resigned: {type: 'info', label: 'page.system.user.resignedStateEnum.resigned'}
}; };
return <ElTag type={stateMap[state].type}>{$t(stateMap[state].label)}</ElTag>; return <ElTag type={stateMap[state].type}>{$t(stateMap[state].label)}</ElTag>;
@@ -320,7 +323,7 @@ const { columns, columnChecks, data, loading, getDataByPage, mobilePagination }
async function loadDeptTree() { async function loadDeptTree() {
deptLoading.value = true; deptLoading.value = true;
const { error, data: deptItems } = await fetchGetDeptList({ const {error, data: deptItems} = await fetchGetDeptList({
status: 0 status: 0
}); });
@@ -422,7 +425,7 @@ function openOrgLeader(row: Api.SystemManage.Dept) {
} }
async function handleDeleteDeptAction(row: Api.SystemManage.Dept) { async function handleDeleteDeptAction(row: Api.SystemManage.Dept) {
const { error } = await fetchDeleteDept(row.id); const {error} = await fetchDeleteDept(row.id);
if (error) { if (error) {
return; return;
@@ -447,7 +450,7 @@ async function handleDeleteAction(row: Api.SystemManage.User) {
return; return;
} }
const { error } = await fetchDeleteUser(row.id); const {error} = await fetchDeleteUser(row.id);
if (error) { if (error) {
return; return;
@@ -466,7 +469,7 @@ async function updateUserResignedAt(userId: number, value: number | null) {
const user = detailResult.data; const user = detailResult.data;
const { error } = await fetchUpdateUser({ const {error} = await fetchUpdateUser({
id: userId, id: userId,
username: user.username, username: user.username,
nickname: user.nickname ?? null, nickname: user.nickname ?? null,
@@ -518,7 +521,7 @@ async function handleBatchDelete() {
return; return;
} }
const { error } = await fetchBatchDeleteUser(userCheckedRowKeys.value); const {error} = await fetchBatchDeleteUser(userCheckedRowKeys.value);
if (error) { if (error) {
return; return;
@@ -531,7 +534,7 @@ async function handleBatchDelete() {
async function handleToggleStatus(row: Api.SystemManage.User, enabled: boolean) { async function handleToggleStatus(row: Api.SystemManage.User, enabled: boolean) {
statusLoadingIds.value = [...statusLoadingIds.value, row.id]; statusLoadingIds.value = [...statusLoadingIds.value, row.id];
const { error } = await fetchUpdateUserStatus({ const {error} = await fetchUpdateUserStatus({
id: row.id, id: row.id,
status: enabled ? 0 : 1 status: enabled ? 0 : 1
}); });
@@ -640,15 +643,21 @@ onMounted(async () => {
<template #default> <template #default>
<ElButton plain type="primary" :disabled="!currentDept" @click="openAdd"> <ElButton plain type="primary" :disabled="!currentDept" @click="openAdd">
<template #icon> <template #icon>
<icon-ic-round-plus class="text-icon" /> <icon-ic-round-plus class="text-icon"/>
</template> </template>
{{ $t('common.add') }} {{ $t('common.add') }}
</ElButton> </ElButton>
<ElButton plain type="primary" :disabled="!currentDept" @click="userManagementRelationVisible = true">
<template #icon>
<icon-ic-round-plus class="text-icon"/>
</template>
带人关系
</ElButton>
<ElPopconfirm :title="$t('common.confirmDelete')" @confirm="handleBatchDelete"> <ElPopconfirm :title="$t('common.confirmDelete')" @confirm="handleBatchDelete">
<template #reference> <template #reference>
<ElButton type="danger" plain :disabled="userCheckedRowKeys.length === 0"> <ElButton type="danger" plain :disabled="userCheckedRowKeys.length === 0">
<template #icon> <template #icon>
<icon-ic-round-delete class="text-icon" /> <icon-ic-round-delete class="text-icon"/>
</template> </template>
{{ $t('common.batchDelete') }} {{ $t('common.batchDelete') }}
</ElButton> </ElButton>
@@ -670,7 +679,7 @@ onMounted(async () => {
:data="data" :data="data"
@selection-change="handleUserSelectionChange" @selection-change="handleUserSelectionChange"
> >
<ElTableColumn v-for="col in columns" :key="String(col.prop)" v-bind="col" /> <ElTableColumn v-for="col in columns" :key="String(col.prop)" v-bind="col"/>
</ElTable> </ElTable>
</div> </div>
<div class="mt-20px flex justify-end"> <div class="mt-20px flex justify-end">
@@ -685,7 +694,7 @@ onMounted(async () => {
</template> </template>
<div v-else class="h-full flex items-center justify-center"> <div v-else class="h-full flex items-center justify-center">
<ElEmpty :description="$t('page.system.user.emptyOrg')" /> <ElEmpty :description="$t('page.system.user.emptyOrg')"/>
</div> </div>
</ElCard> </ElCard>
</div> </div>
@@ -724,7 +733,17 @@ onMounted(async () => {
@submitted="handleDeptSubmitted" @submitted="handleDeptSubmitted"
/> />
<UserOrgLeaderDialog v-model:visible="orgLeaderVisible" :dept="leaderDeptData" /> <UserOrgLeaderDialog v-model:visible="orgLeaderVisible" :dept="leaderDeptData"/>
<BusinessFormDialog
v-model="userManagementRelationVisible"
title="用户带人关系"
preset="lg"
:show-footer="false"
max-body-height="70vh"
>
<UserManagementRelation :fromUserIndex="true" :deptId="currentDeptId"/>
</BusinessFormDialog>
</div> </div>
</template> </template>
@@ -734,4 +753,9 @@ onMounted(async () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
// 带人关系对话框内的搜索框样式优化
:deep(.business-form-dialog) {
width: 800px;
}
</style> </style>