refactor: 系统权限模块重构,迁移至PostgreSQL并对齐新设计

- 切换数据库为PostgreSQL,更新依赖与配置
- 重构权限体系,使用sys_user/sys_role/sys_menu等新实体替代旧模型
- 移除旧的用户角色/权限/系统设置相关代码与实体
- 新增字典、菜单管理模块与基础CRUD接口
- 优化鉴权逻辑,基于菜单权限标识进行接口校验
- 前端同步更新API、路由与权限控制逻辑
- 删除冗余的v-permission指令与相关代码
This commit is contained in:
andyesfly committed 2026-08-19 14:11:34 +08:00
1 parent 98c22f6afb
commit 0a8103f33d
67 files changed
+3263 -1924

No files matched your search

+75 -89
View File
@@ -1,64 +1,53 @@
<template>
<div class="user-management">
<a-card :bordered="false">
<a-form layout="inline" :model="searchForm" class="search-form">
<a-form-item label="关键词">
<a-input v-model:value="searchForm.keyword" placeholder="用户名/昵称/手机号" allow-clear style="width: 220px" @pressEnter="handleSearch" />
</a-form-item>
<a-form-item label="状态">
<a-select v-model:value="searchForm.status" placeholder="请选择状态" allow-clear style="width: 120px">
<a-select-option :value="1">启用</a-select-option>
<a-select-option :value="0">禁用</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleSearch">
<template #icon><SearchOutlined /></template>
搜索
</a-button>
<a-button @click="handleReset">
<template #icon><ReloadOutlined /></template>
重置
</a-button>
</a-space>
</a-form-item>
</a-form>
</a-card>
<a-card title="用户管理">
<template #extra>
<a-space>
<a-button type="primary" @click="handleAdd">
<template #icon><PlusOutlined /></template>
新增用户
</a-button>
<a-button danger :disabled="!selectedRowKeys.length" @click="handleBatchDelete">
<template #icon><DeleteOutlined /></template>
批量删除 ({{ selectedRowKeys.length }})
</a-button>
</a-space>
</template>
<div class="table-operations">
<a-space>
<a-button type="primary" v-permission="'user:create'" @click="handleAdd">
<template #icon><PlusOutlined /></template>
新增用户
<div class="search-bar">
<a-input
v-model:value="searchForm.keyword"
placeholder="用户名/姓名/手机号"
allow-clear
style="width: 220px"
@pressEnter="handleSearch"
>
<template #prefix><SearchOutlined /></template>
</a-input>
<a-select v-model:value="searchForm.status" placeholder="状态" allow-clear style="width: 120px">
<a-select-option :value="1">启用</a-select-option>
<a-select-option :value="0">禁用</a-select-option>
</a-select>
<a-button @click="handleReset">
<template #icon><ReloadOutlined /></template>
重置
</a-button>
<a-button danger v-permission="'user:delete'" :disabled="!selectedRowKeys.length" @click="handleBatchDelete">
<template #icon><DeleteOutlined /></template>
批量删除 ({{ selectedRowKeys.length }})
</a-button>
</a-space>
</div>
</div>
<a-card :bordered="false">
<a-table
:columns="columns"
:data-source="dataSource"
:row-selection="rowSelection"
:loading="loading"
:pagination="pagination"
:scroll="{ x: 1200 }"
row-key="id"
@change="handleTableChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'avatar'">
<a-avatar :src="record.avatar" :size="40">{{ record.nickname?.charAt(0) }}</a-avatar>
</template>
<template v-if="column.key === 'roles'">
<a-space>
<a-tag v-for="role in record.userRoles" :key="role.roleId" :color="getRoleColor(role.role?.code)">
{{ role.role?.name }}
<a-tag v-for="role in record.userRoles" :key="role.roleId" :color="getRoleColor(role.role?.roleCode)">
{{ role.role?.roleName }}
</a-tag>
</a-space>
</template>
@@ -69,18 +58,21 @@
checked-children="启用"
un-checked-children="禁用"
@change="(checked: boolean) => handleStatusChange(record.id, checked ? 1 : 0)"
v-permission="'user:update'"
/>
</template>
<template v-if="column.key === 'created_at'">
{{ formatDateTime(record.created_at) }}
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" size="small" v-permission="'user:update'" @click="handleEdit(record)">
<a-button type="link" size="small" @click="handleEdit(record)">
<template #icon><EditOutlined /></template>
编辑
</a-button>
<a-popconfirm title="确定要删除该用户吗?" ok-text="确定" cancel-text="取消" @confirm="handleDelete(record.id)">
<a-button type="link" size="small" danger v-permission="'user:delete'">
<a-button type="link" size="small" danger>
<template #icon><DeleteOutlined /></template>
删除
</a-button>
@@ -126,8 +118,8 @@
<a-button type="link" size="small" @click="showPassword = true">修改密码</a-button>
</a-form-item>
<a-form-item label="昵称" name="nickname">
<a-input v-model:value="formData.nickname" placeholder="请输入昵称" />
<a-form-item label="姓名" name="realName">
<a-input v-model:value="formData.realName" placeholder="请输入姓名" />
</a-form-item>
<a-form-item label="手机号" name="phone">
@@ -169,6 +161,7 @@ import {
EditOutlined,
} from '@ant-design/icons-vue';
import { userApi, type UserInfo, type UserQuery, type RoleInfo } from '@/api/user';
import dayjs from 'dayjs';
const loading = ref(false);
const dataSource = ref<UserInfo[]>([]);
@@ -193,7 +186,7 @@ const formData = reactive({
username: '',
password: '',
confirmPassword: '',
nickname: '',
realName: '',
phone: '',
email: '',
roleIds: [] as number[],
@@ -214,25 +207,25 @@ const formRules = {
{ min: 6, message: '密码长度不能小于6位', trigger: 'blur' },
],
confirmPassword: [{ required: true, message: '请确认密码', trigger: 'blur' }, { validator: validateConfirmPassword, trigger: 'blur' }],
nickname: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
realName: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
phone: [{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }],
email: [{ type: 'email' as const, message: '邮箱格式不正确', trigger: 'blur' }],
roleIds: [{ required: true, message: '请选择角色', trigger: 'change', type: 'array' as const }],
};
const columns = [
{ title: '用户名', dataIndex: 'username', key: 'username', width: 120 },
{ title: '昵称', dataIndex: 'nickname', key: 'nickname', width: 120 },
{ title: '手机号', dataIndex: 'phone', key: 'phone', width: 130 },
{ title: '邮箱', dataIndex: 'email', key: 'email', width: 180 },
{ title: '角色', key: 'roles', width: 200 },
{ title: '用户名', dataIndex: 'username', key: 'username', ellipsis: true },
{ title: '姓名', dataIndex: 'realName', key: 'realName', ellipsis: true },
{ title: '手机号', dataIndex: 'phone', key: 'phone', ellipsis: true },
{ title: '邮箱', dataIndex: 'email', key: 'email', ellipsis: true },
{ title: '角色', key: 'roles' },
{
title: '状态',
key: 'status',
width: 100,
align: 'center' as const,
width: 80,
},
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at', width: 180 },
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at', ellipsis: true },
{ title: '操作', key: 'action', width: 160, fixed: 'right' as const },
];
@@ -277,7 +270,7 @@ async function fetchRoleList() {
const res: any = await userApi.getRoleList();
if (res.code === 200) {
roleOptions.value = res.data.map((item: RoleInfo) => ({
label: item.name,
label: item.roleName,
value: item.id,
}));
}
@@ -321,7 +314,7 @@ function handleEdit(record: UserInfo) {
resetForm();
formData.username = record.username;
formData.nickname = record.nickname;
formData.realName = record.realName;
formData.phone = record.phone || '';
formData.email = record.email || '';
formData.status = record.status;
@@ -334,7 +327,7 @@ function resetForm() {
formData.username = '';
formData.password = '';
formData.confirmPassword = '';
formData.nickname = '';
formData.realName = '';
formData.phone = '';
formData.email = '';
formData.roleIds = [];
@@ -357,7 +350,7 @@ async function handleSubmit() {
let res: any;
if (isEdit.value && editId.value) {
const updateData: any = {
nickname: formData.nickname,
realName: formData.realName,
phone: formData.phone,
email: formData.email,
roleIds: formData.roleIds,
@@ -373,7 +366,7 @@ async function handleSubmit() {
res = await userApi.createUser({
username: formData.username,
password: formData.password,
nickname: formData.nickname,
realName: formData.realName,
phone: formData.phone,
email: formData.email,
roleIds: formData.roleIds,
@@ -463,37 +456,30 @@ function getRoleColor(code?: string): string {
};
return colorMap[code || ''] || 'default';
}
function formatDateTime(dateStr: string) {
if (!dateStr) return '-';
return dayjs(dateStr).format('YYYY-MM-DD HH:mm:ss');
}
</script>
<style scoped lang="less">
<style scoped>
.user-management {
padding: 24px;
padding: 20px;
}
.search-form {
margin-bottom: 16px;
}
.search-bar {
margin-bottom: 16px;
display: flex;
justify-content: flex-end;
gap: 8px;
}
.table-operations {
margin-bottom: 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
:deep(.ant-table) {
font-size: 14px;
}
:deep(.ant-table) {
.ant-table-thead > tr > th {
background-color: #fafafa;
font-weight: 600;
}
.ant-table-tbody > tr:hover > td {
background-color: #e6f7ff;
}
}
:deep(.ant-avatar) {
background-color: #1890ff;
color: #fff;
}
:deep(.ant-btn-link) {
padding: 0 4px;
}
</style>