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