优化用户和角色管理
This commit is contained in:
parent
86ea16ec4b
commit
0b268c65d6
@ -1,18 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog v-model="visible" :title="isEdit ? '编辑角色' : '添加角色'" width="600px" @close="handleClose">
|
||||||
v-model="visible"
|
|
||||||
:title="isEdit ? '编辑角色' : '添加角色'"
|
|
||||||
width="600px"
|
|
||||||
@close="handleClose"
|
|
||||||
>
|
|
||||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
|
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
|
||||||
<el-form-item label="角色名称" prop="name">
|
<el-form-item label="角色名称" prop="name">
|
||||||
<el-input
|
<el-input v-model="form.name" placeholder="请输入角色名称" maxlength="50" show-word-limit />
|
||||||
v-model="form.name"
|
|
||||||
placeholder="请输入角色名称"
|
|
||||||
maxlength="50"
|
|
||||||
show-word-limit
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="状态" prop="status">
|
<el-form-item label="状态" prop="status">
|
||||||
@ -23,15 +13,16 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="权限设置" prop="rights">
|
<el-form-item label="权限设置" prop="rights">
|
||||||
<el-tree
|
<div style="margin-bottom: 10px;">
|
||||||
ref="treeRef"
|
<el-button size="small" @click="toggleSelectAll">
|
||||||
:data="menuTree"
|
{{ isAllSelected ? '全不选' : '全选' }}
|
||||||
show-checkbox
|
</el-button>
|
||||||
node-key="id"
|
<el-button size="small" @click="handleExpandAll">展开/折叠</el-button>
|
||||||
:props="{ children: 'children', label: 'title' }"
|
</div>
|
||||||
:default-checked-keys="form.rights"
|
|
||||||
style="width: 100%; border: 1px solid var(--el-border-color); border-radius: 4px; padding: 10px; max-height: 400px; overflow-y: auto;"
|
<el-tree ref="treeRef" :data="menuTree" show-checkbox node-key="id"
|
||||||
/>
|
:props="{ children: 'children', label: 'title' }" @check="updateSelectStatus"
|
||||||
|
style="width: 100%; border: 1px solid var(--el-border-color); border-radius: 4px; padding: 10px; max-height: 400px; overflow-y: auto;" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
@ -62,12 +53,15 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
|
|
||||||
const emit = defineEmits(["update:modelValue", "success"]);
|
const emit = defineEmits(["update:modelValue", "success"]);
|
||||||
|
|
||||||
|
// --- 状态定义 ---
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const isEdit = ref(false);
|
const isEdit = ref(false);
|
||||||
const submitting = ref(false);
|
const submitting = ref(false);
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
const treeRef = ref();
|
const treeRef = ref();
|
||||||
const menuTree = ref<any[]>([]);
|
const menuTree = ref<any[]>([]);
|
||||||
|
const isAllSelected = ref(false);
|
||||||
|
const isExpandAll = ref(false);
|
||||||
|
|
||||||
const form = ref({
|
const form = ref({
|
||||||
name: "",
|
name: "",
|
||||||
@ -83,40 +77,82 @@ const rules = {
|
|||||||
status: [{ required: true, message: "请选择状态", trigger: "change" }],
|
status: [{ required: true, message: "请选择状态", trigger: "change" }],
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听 modelValue
|
// --- 辅助功能 ---
|
||||||
watch(
|
|
||||||
() => props.modelValue,
|
// 获取树中所有节点的 ID 列表
|
||||||
(val) => {
|
const getAllMenuIds = (data: any[]): number[] => {
|
||||||
visible.value = val;
|
const ids: number[] = [];
|
||||||
if (val) {
|
const traverse = (list: any[]) => {
|
||||||
loadMenus();
|
list.forEach((item) => {
|
||||||
if (props.role) {
|
ids.push(item.id);
|
||||||
isEdit.value = true;
|
if (item.children && item.children.length > 0) traverse(item.children);
|
||||||
form.value = {
|
});
|
||||||
name: props.role.name,
|
};
|
||||||
status: props.role.status,
|
traverse(data);
|
||||||
rights: parseRights(props.role.rights),
|
return ids;
|
||||||
};
|
};
|
||||||
// 等待树加载完成后设置选中状态
|
|
||||||
nextTick(() => {
|
// 展开折叠
|
||||||
if (treeRef.value) {
|
const handleExpandAll = () => {
|
||||||
treeRef.value.setCheckedKeys(form.value.rights);
|
if (!treeRef.value) return;
|
||||||
}
|
|
||||||
});
|
// 切换状态
|
||||||
} else {
|
isExpandAll.value = !isExpandAll.value;
|
||||||
isEdit.value = false;
|
|
||||||
resetForm();
|
// 获取树的所有节点对象
|
||||||
}
|
// nodesMap 包含了树中所有的节点实例
|
||||||
}
|
const nodes = treeRef.value.store.nodesMap;
|
||||||
|
|
||||||
|
for (let id in nodes) {
|
||||||
|
// 强制修改每个节点的 expanded 属性
|
||||||
|
nodes[id].expanded = isExpandAll.value;
|
||||||
}
|
}
|
||||||
);
|
};
|
||||||
|
|
||||||
// 监听 visible
|
// 更新“全选/全不选”按钮的状态
|
||||||
watch(visible, (val) => {
|
const updateSelectStatus = () => {
|
||||||
emit("update:modelValue", val);
|
if (!treeRef.value || menuTree.value.length === 0) {
|
||||||
});
|
isAllSelected.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const checkedKeys = treeRef.value.getCheckedKeys();
|
||||||
|
const allIds = getAllMenuIds(menuTree.value);
|
||||||
|
// 只有当勾选数等于总数时,才显示“全不选”
|
||||||
|
isAllSelected.value = allIds.length > 0 && checkedKeys.length === allIds.length;
|
||||||
|
};
|
||||||
|
|
||||||
// 解析权限字符串
|
// 全选/全不选 切换逻辑
|
||||||
|
const toggleSelectAll = () => {
|
||||||
|
if (!treeRef.value) return;
|
||||||
|
|
||||||
|
if (isAllSelected.value) {
|
||||||
|
// 当前是全选状态 -> 执行清空
|
||||||
|
treeRef.value.setCheckedKeys([]);
|
||||||
|
isAllSelected.value = false;
|
||||||
|
} else {
|
||||||
|
// 当前不是全选状态 -> 执行全选
|
||||||
|
const allIds = getAllMenuIds(menuTree.value);
|
||||||
|
treeRef.value.setCheckedKeys(allIds);
|
||||||
|
isAllSelected.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 数据加载与监听 ---
|
||||||
|
|
||||||
|
// 加载菜单树数据
|
||||||
|
const loadMenus = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getAllMenus();
|
||||||
|
if (res.code === 200) {
|
||||||
|
menuTree.value = res.data || [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("加载菜单失败:", error);
|
||||||
|
ElMessage.error("加载菜单失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 解析权限数据
|
||||||
const parseRights = (rights: any): number[] => {
|
const parseRights = (rights: any): number[] => {
|
||||||
if (!rights) return [];
|
if (!rights) return [];
|
||||||
if (Array.isArray(rights)) return rights;
|
if (Array.isArray(rights)) return rights;
|
||||||
@ -131,48 +167,67 @@ const parseRights = (rights: any): number[] => {
|
|||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载菜单树
|
// 核心监听:处理弹窗打开时的初始化
|
||||||
const loadMenus = async () => {
|
watch(
|
||||||
try {
|
() => props.modelValue,
|
||||||
const res = await getAllMenus();
|
async (val) => {
|
||||||
if (res.code === 200) {
|
visible.value = val;
|
||||||
menuTree.value = res.data || [];
|
if (val) {
|
||||||
}
|
// 先加载菜单数据
|
||||||
} catch (error) {
|
await loadMenus();
|
||||||
console.error("加载菜单失败:", error);
|
|
||||||
ElMessage.error("加载菜单失败");
|
// 判断是编辑还是新增
|
||||||
}
|
if (props.role) {
|
||||||
};
|
isEdit.value = true;
|
||||||
|
form.value = {
|
||||||
|
name: props.role.name,
|
||||||
|
status: props.role.status,
|
||||||
|
rights: parseRights(props.role.rights),
|
||||||
|
};
|
||||||
|
// 设置树的回显
|
||||||
|
nextTick(() => {
|
||||||
|
if (treeRef.value) {
|
||||||
|
treeRef.value.setCheckedKeys(form.value.rights);
|
||||||
|
updateSelectStatus(); // 初始化按钮状态
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
isEdit.value = false;
|
||||||
|
isAllSelected.value = false;
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(visible, (val) => {
|
||||||
|
emit("update:modelValue", val);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- 表单操作 ---
|
||||||
|
|
||||||
// 重置表单
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
form.value = {
|
form.value = {
|
||||||
name: "",
|
name: "",
|
||||||
status: 1,
|
status: 1,
|
||||||
rights: [],
|
rights: [],
|
||||||
};
|
};
|
||||||
if (formRef.value) {
|
if (formRef.value) formRef.value.clearValidate();
|
||||||
formRef.value.clearValidate();
|
if (treeRef.value) treeRef.value.setCheckedKeys([]);
|
||||||
}
|
|
||||||
if (treeRef.value) {
|
|
||||||
treeRef.value.setCheckedKeys([]);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 关闭对话框
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
resetForm();
|
resetForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 提交表单
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!formRef.value) return;
|
if (!formRef.value) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await formRef.value.validate();
|
await formRef.value.validate();
|
||||||
|
|
||||||
// 获取选中的菜单ID
|
// 组合选中节点(全选 + 半选)
|
||||||
const checkedKeys = treeRef.value.getCheckedKeys();
|
const checkedKeys = treeRef.value.getCheckedKeys();
|
||||||
const halfCheckedKeys = treeRef.value.getHalfCheckedKeys();
|
const halfCheckedKeys = treeRef.value.getHalfCheckedKeys();
|
||||||
const allCheckedKeys = [...checkedKeys, ...halfCheckedKeys];
|
const allCheckedKeys = [...checkedKeys, ...halfCheckedKeys];
|
||||||
@ -201,7 +256,6 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error !== false) {
|
if (error !== false) {
|
||||||
// 不是表单验证错误
|
|
||||||
ElMessage.error(error.message || "操作失败");
|
ElMessage.error(error.message || "操作失败");
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -4,11 +4,15 @@
|
|||||||
<h2>用户管理</h2>
|
<h2>用户管理</h2>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<el-button type="primary" @click="handleAddUser">
|
<el-button type="primary" @click="handleAddUser">
|
||||||
<el-icon><Plus /></el-icon>
|
<el-icon>
|
||||||
|
<Plus />
|
||||||
|
</el-icon>
|
||||||
添加用户
|
添加用户
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button @click="refresh">
|
<el-button @click="refresh">
|
||||||
<el-icon><Refresh /></el-icon>
|
<el-icon>
|
||||||
|
<Refresh />
|
||||||
|
</el-icon>
|
||||||
刷新
|
刷新
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@ -18,50 +22,24 @@
|
|||||||
|
|
||||||
<!-- 用户列表表格 -->
|
<!-- 用户列表表格 -->
|
||||||
<el-table :data="users" style="width: 100%" v-loading="loading">
|
<el-table :data="users" style="width: 100%" v-loading="loading">
|
||||||
<el-table-column
|
<el-table-column prop="id" label="ID" align="center" fixed="left" />
|
||||||
prop="id"
|
<el-table-column prop="name" label="姓名" min-width="120" align="center">
|
||||||
label="ID"
|
|
||||||
align="center"
|
|
||||||
fixed="left"
|
|
||||||
/>
|
|
||||||
<el-table-column prop="account" label="账号" align="center" />
|
|
||||||
<el-table-column
|
|
||||||
prop="name"
|
|
||||||
label="姓名"
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span class="name-link" @click="handlePreview(scope.row)">{{ scope.row.name }}</span>
|
<span class="name-link" @click="handlePreview(scope.row)">{{ scope.row.name }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="group_id" label="角色" align="center">
|
<el-table-column prop="group_id" label="角色" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag
|
<el-tag :type="getRoleTagType(scope.row.group_id)" size="small">
|
||||||
:type="getRoleTagType(scope.row.group_id)"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{{ getRoleTagText(roles, scope.row.group_id) }}
|
{{ getRoleTagText(roles, scope.row.group_id) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column prop="account" label="账号" min-width="180" align="center" />
|
||||||
prop="phone"
|
<el-table-column prop="phone" label="手机号" min-width="180" align="center" />
|
||||||
label="手机号"
|
<el-table-column prop="qq" label="QQ" min-width="180" align="center" />
|
||||||
align="center"
|
<el-table-column prop="last_login_ip" label="最后登录IP" width="120" align="center" />
|
||||||
/>
|
<el-table-column prop="login_count" label="登陆次数" width="120" align="center" />
|
||||||
<el-table-column prop="qq" label="QQ" align="center" />
|
|
||||||
<el-table-column
|
|
||||||
prop="last_login_ip"
|
|
||||||
label="最后登录IP"
|
|
||||||
width="120"
|
|
||||||
align="center"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
|
||||||
prop="login_count"
|
|
||||||
label="登陆次数"
|
|
||||||
width="120"
|
|
||||||
align="center"
|
|
||||||
/>
|
|
||||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">{{
|
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">{{
|
||||||
@ -71,65 +49,34 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="240" align="center" fixed="right">
|
<el-table-column label="操作" width="240" align="center" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button size="small" @click="handleEdit(scope.row)"
|
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
|
||||||
>编辑</el-button
|
<el-button size="small" type="warning" @click="handleChangePassword(scope.row)">
|
||||||
>
|
|
||||||
<el-button
|
|
||||||
size="small"
|
|
||||||
type="warning"
|
|
||||||
@click="handleChangePassword(scope.row)"
|
|
||||||
>
|
|
||||||
修改密码
|
修改密码
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button v-if="scope.row.username !== 'admin' && scope.row.id !== 1" size="small" type="danger"
|
||||||
v-if="scope.row.username !== 'admin' && scope.row.id !== 1"
|
@click="handleDelete(scope.row)">删除</el-button>
|
||||||
size="small"
|
|
||||||
type="danger"
|
|
||||||
@click="handleDelete(scope.row)"
|
|
||||||
>删除</el-button
|
|
||||||
>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<div class="pagination-bar">
|
<div class="pagination-bar">
|
||||||
<el-pagination
|
<el-pagination :current-page="page" :page-size="pageSize" :total="total" @current-change="handlePageChange"
|
||||||
:current-page="page"
|
layout="total, prev, pager, next" />
|
||||||
:page-size="pageSize"
|
|
||||||
:total="total"
|
|
||||||
@current-change="handlePageChange"
|
|
||||||
layout="total, prev, pager, next"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 编辑用户对话框组件 -->
|
<!-- 编辑用户对话框组件 -->
|
||||||
<UserEditDialog
|
<UserEditDialog ref="userEditRef" :modelValue="editDialogVisible" @update:modelValue="editDialogVisible = $event"
|
||||||
ref="userEditRef"
|
:is-edit="isEdit" @submit="handleEditSuccess" @close="editDialogVisible = false" />
|
||||||
:modelValue="editDialogVisible"
|
|
||||||
@update:modelValue="editDialogVisible = $event"
|
|
||||||
:is-edit="isEdit"
|
|
||||||
@submit="handleEditSuccess"
|
|
||||||
@close="editDialogVisible = false"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 修改密码对话框组件 -->
|
<!-- 修改密码对话框组件 -->
|
||||||
<ChangePasswordDialog
|
<ChangePasswordDialog ref="changePasswordRef" :modelValue="passwordDialogVisible"
|
||||||
ref="changePasswordRef"
|
@update:modelValue="passwordDialogVisible = $event" :user-id="currentUserId" @submit="handlePasswordChangeSuccess"
|
||||||
:modelValue="passwordDialogVisible"
|
@close="passwordDialogVisible = false" />
|
||||||
@update:modelValue="passwordDialogVisible = $event"
|
|
||||||
:user-id="currentUserId"
|
|
||||||
@submit="handlePasswordChangeSuccess"
|
|
||||||
@close="passwordDialogVisible = false"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 预览用户对话框组件 -->
|
<!-- 预览用户对话框组件 -->
|
||||||
<PreviewDialog
|
<PreviewDialog ref="previewDialogRef" :modelValue="previewDialogVisible"
|
||||||
ref="previewDialogRef"
|
@update:modelValue="previewDialogVisible = $event" :user="currentUser" />
|
||||||
:modelValue="previewDialogVisible"
|
|
||||||
@update:modelValue="previewDialogVisible = $event"
|
|
||||||
:user="currentUser"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user