Refactor permissions and roles management with comprehensive updates

- Implement advanced permission management with tree-based structure
- Add detailed search, filtering, and tree expansion features
- Enhance role management with permission assignment dialog
- Integrate API calls for CRUD operations on permissions and roles
- Improve form validation and error handling
- Update UI with more informative table columns and icons
This commit is contained in:
wzclm 2025-02-24 13:32:24 +08:00
parent c041ef9671
commit 2545bf43ac
4 changed files with 1050 additions and 301 deletions

View File

@ -0,0 +1,83 @@
import request from '@/utils/request'
/**
* 获取权限树形列表
* @returns {Promise} 返回权限树形数据
*/
export function getPermissionTree() {
return request.get('/api/permissions/tree')
}
/**
* 创建权限
* @param {Object} data - 权限数据
* @param {string} data.name - 权限名称
* @param {string} data.code - 权限代码
* @param {string} [data.description] - 权限描述
* @param {string} [data.category] - 权限分类
* @param {string} data.type - 权限类型menu-菜单 button-按钮 api-接口
* @param {number} [data.parent_id] - 父权限ID
* @param {string} [data.path] - 权限路径菜单权限必填
* @param {string} [data.component] - 前端组件菜单权限必填
* @param {string} [data.icon] - 图标菜单权限可选
* @param {number} [data.sort_order=0] - 排序号
* @param {number} [data.status=1] - 状态0-禁用 1-启用
* @returns {Promise} 返回创建结果
*/
export function createPermission(data) {
return request.post('/api/permissions', {
code: data.code,
name: data.name,
description: data.description,
category: data.category,
type: data.type,
parent_id: data.parent_id,
path: data.path,
component: data.component,
icon: data.icon,
sort_order: data.sort_order || 0,
status: data.status || 1
})
}
/**
* 更新权限
* @param {number} id - 权限ID
* @param {Object} data - 更新数据
* @param {string} data.name - 权限名称
* @param {string} data.code - 权限代码
* @param {string} [data.description] - 权限描述
* @param {string} [data.category] - 权限分类
* @param {string} data.type - 权限类型
* @param {number} [data.parent_id] - 父权限ID
* @param {string} [data.path] - 权限路径菜单权限必填
* @param {string} [data.component] - 前端组件菜单权限必填
* @param {string} [data.icon] - 图标菜单权限可选
* @param {number} [data.sort_order] - 排序号
* @param {number} [data.status] - 状态
* @returns {Promise} 返回更新结果
*/
export function updatePermission(id, data) {
return request.put(`/api/permissions/${id}`, {
name: data.name,
code: data.code,
description: data.description,
category: data.category,
type: data.type,
parent_id: data.parent_id,
path: data.path,
component: data.component,
icon: data.icon,
sort_order: data.sort_order,
status: data.status
})
}
/**
* 删除权限
* @param {number} id - 权限ID
* @returns {Promise} 返回删除结果
*/
export function deletePermission(id) {
return request.delete(`/api/permissions/${id}`)
}

99
src/api/system/roles.js Normal file
View File

@ -0,0 +1,99 @@
import request from '@/utils/request'
/**
* 获取角色列表
* @param {Object} params - 查询参数
* @param {number} [params.page=1] - 页码
* @param {number} [params.page_size=10] - 每页条数
* @param {string} [params.keyword] - 搜索关键词
* @returns {Promise} 返回角色列表数据
*/
export function getRoleList(params = {}) {
return request.get('/api/roles', {
params: {
page: params.page || 1,
page_size: params.page_size || 10,
keyword: params.keyword
}
})
}
/**
* 获取角色详情
* @param {number|string} id - 角色ID
* @returns {Promise} 返回角色详情数据
*/
export function getRoleDetail(id) {
return request.get(`/api/roles/${id}`)
}
/**
* 创建角色
* @param {Object} data - 角色数据
* @param {string} data.name - 角色名称
* @param {string} [data.description] - 角色描述
* @returns {Promise} 返回创建结果
*/
export function createRole(data) {
return request.post('/api/roles', data)
}
/**
* 更新角色
* @param {number|string} id - 角色ID
* @param {Object} data - 更新数据
* @param {string} data.name - 角色名称
* @param {string} [data.description] - 角色描述
* @param {Array<number>} [data.permissions] - 权限ID数组
* @returns {Promise} 返回更新结果
*/
export function updateRole(id, data) {
return request.put(`/api/roles/${id}`, {
name: data.name,
description: data.description,
permissions: data.permissions || []
})
}
/**
* 删除角色
* @param {number|string} id - 角色ID
* @returns {Promise} 返回删除结果
*/
export function deleteRole(id) {
return request.delete(`/api/roles/${id}`)
}
/**
* 获取所有权限列表树形结构
* @returns {Promise} 返回权限树形数据
*/
export function getPermissionTree() {
return request.get('/api/permissions/tree')
}
/**
* 获取角色权限
* @param {number|string} id - 角色ID
* @returns {Promise} 返回角色权限数据
*/
export function getRolePermissions(id) {
return request.get(`/api/roles/${id}`)
}
/**
* 分配角色权限
* @param {number|string} id - 角色ID
* @param {Object} data - 权限数据
* @param {string} data.name - 角色名称
* @param {string} data.description - 角色描述
* @param {Array<number>} data.permissions - 权限ID数组
* @returns {Promise} 返回分配结果
*/
export function assignRolePermissions(id, data) {
return request.put(`/api/roles/${id}`, {
name: data.name,
description: data.description,
permissions: Array.isArray(data.permissions) ? data.permissions.filter(id => id != null) : []
})
}

View File

@ -1,229 +1,482 @@
<script setup>
import { ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Plus, Edit, Delete, Folder, FolderOpened } from '@element-plus/icons-vue'
import { formatDateTime } from '@/utils/format'
import {
getPermissionTree,
createPermission,
updatePermission,
deletePermission
} from '@/api/system/permissions'
const tableData = ref([
{
id: 1,
name: "用户管理",
code: "system:user",
description: "用户的增删改查权限",
type: "菜单权限",
status: true,
createTime: "2024-03-20",
},
{
id: 2,
name: "角色管理",
code: "system:role",
description: "角色的增删改查权限",
type: "菜单权限",
status: true,
createTime: "2024-03-20",
},
{
id: 3,
name: "新增用户",
code: "system:user:add",
description: "新增用户的权限",
type: "操作权限",
status: true,
createTime: "2024-03-20",
},
]);
//
const searchForm = ref({
keyword: '',
type: '',
category: '',
status: ''
})
// /
const dialogVisible = ref(false);
const isEdit = ref(false);
const currentPermission = ref(null);
//
const typeOptions = [
{ label: '菜单权限', value: 'menu' },
{ label: '按钮权限', value: 'button' },
{ label: '接口权限', value: 'api' }
]
//
const categoryOptions = [
{ label: '系统管理', value: 'system' },
{ label: '内容管理', value: 'content' },
{ label: '用户管理', value: 'user' }
]
//
const statusOptions = [
{ label: '启用', value: 1 },
{ label: '禁用', value: 0 }
]
//
const tableData = ref([])
const loading = ref(false)
const permissionTree = ref([])
//
const expandAll = ref(false)
//
const getList = async () => {
loading.value = true
try {
const res = await getPermissionTree()
if (res.success) {
permissionTree.value = res.data
tableData.value = res.data // 使
} else {
ElMessage.error(res.message || '获取权限列表失败')
}
} catch (error) {
console.error('获取权限列表错误:', error)
ElMessage.error('获取权限列表失败')
} finally {
loading.value = false
}
}
//
const handleSearch = () => {
const filterNode = (data) => {
const matchKeyword = !searchForm.value.keyword ||
data.name.toLowerCase().includes(searchForm.value.keyword.toLowerCase()) ||
data.code.toLowerCase().includes(searchForm.value.keyword.toLowerCase())
const matchType = !searchForm.value.type || data.type === searchForm.value.type
const matchCategory = !searchForm.value.category || data.category === searchForm.value.category
const matchStatus = searchForm.value.status === '' || data.status === searchForm.value.status
// true
if (matchKeyword && matchType && matchCategory && matchStatus) {
return true
}
//
if (data.children && data.children.length) {
data.children = data.children.filter(filterNode)
return data.children.length > 0
}
return false
}
//
const filteredData = JSON.parse(JSON.stringify(permissionTree.value))
tableData.value = filteredData.filter(filterNode)
}
//
const resetSearch = () => {
searchForm.value = {
keyword: '',
type: '',
category: '',
status: ''
}
tableData.value = permissionTree.value //
}
//
const dialogVisible = ref(false)
const dialogTitle = ref('新增权限')
const formRef = ref(null)
const formData = ref({
name: "",
code: "",
description: "",
type: "",
status: true,
});
name: '',
code: '',
description: '',
category: '',
type: 'menu',
parent_id: null,
path: '',
component: '',
icon: '',
sort_order: 0,
status: 1
})
//
//
const rules = {
name: [{ required: true, message: "请输入权限名称", trigger: "blur" }],
code: [{ required: true, message: "请输入权限标识", trigger: "blur" }],
type: [{ required: true, message: "请选择权限类型", trigger: "change" }],
};
name: [
{ required: true, message: '请输入权限名称', trigger: 'blur' },
{ min: 2, max: 100, message: '长度在 2 到 100 个字符', trigger: 'blur' }
],
code: [
{ required: true, message: '请输入权限代码', trigger: 'blur' },
{ min: 2, max: 100, message: '长度在 2 到 100 个字符', trigger: 'blur' }
],
type: [
{ required: true, message: '请选择权限类型', trigger: 'change' }
],
category: [
{ required: true, message: '请选择权限分类', trigger: 'change' }
]
}
const formRef = ref();
//
const handleAdd = () => {
isEdit.value = false;
currentPermission.value = null;
resetForm();
dialogVisible.value = true;
};
//
const handleEdit = (row) => {
isEdit.value = true;
currentPermission.value = row;
//
const handleAdd = (parentId = null) => {
dialogTitle.value = '新增权限'
formData.value = {
name: row.name,
code: row.code,
description: row.description,
type: row.type,
status: row.status,
};
dialogVisible.value = true;
};
name: '',
code: '',
description: '',
category: '',
type: 'menu',
parent_id: parentId,
path: '',
component: '',
icon: '',
sort_order: 0,
status: 1
}
dialogVisible.value = true
}
//
//
const handleEdit = (row) => {
dialogTitle.value = '编辑权限'
formData.value = { ...row } // 使
dialogVisible.value = true
}
//
const handleDelete = (row) => {
if (!row.id) {
ElMessage.error('权限ID不存在')
return
}
ElMessageBox.confirm('确认删除该权限吗?删除后将无法恢复!', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deletePermission(row.id)
if (res.success) {
ElMessage.success('删除成功')
getList()
} else {
ElMessage.error(res.message || '删除失败')
}
} catch (error) {
console.error('删除权限错误:', error)
const errorMsg = error.response?.data?.message || error.message || '删除失败'
ElMessage.error(errorMsg)
}
}).catch(() => {
//
})
}
//
const handleSubmit = async () => {
if (!formRef.value) return;
if (!formRef.value) return
try {
await formRef.value.validate();
if (isEdit.value && currentPermission.value) {
//
const index = tableData.value.findIndex(
(item) => item.id === currentPermission.value.id
);
if (index !== -1) {
tableData.value[index] = {
...currentPermission.value,
...formData.value,
};
ElMessage.success("更新成功");
}
await formRef.value.validate()
const submitFunc = formData.value.id ? updatePermission : createPermission
const res = await submitFunc(
formData.value.id,
formData.value
)
if (res.success) {
ElMessage.success(formData.value.id ? '更新成功' : '创建成功')
dialogVisible.value = false
getList()
} else {
//
const newPermission = {
id: tableData.value.length + 1,
...formData.value,
createTime: new Date().toLocaleString(),
};
tableData.value.push(newPermission);
ElMessage.success("添加成功");
ElMessage.error(res.message || (formData.value.id ? '更新失败' : '创建失败'))
}
dialogVisible.value = false;
} catch (error) {
console.error("表单验证失败:", error);
console.error('提交表单错误:', error)
ElMessage.error('提交失败,请检查表单数据')
}
};
}
//
const resetForm = () => {
formData.value = {
name: "",
code: "",
description: "",
type: "",
status: true,
};
if (formRef.value) {
formRef.value.resetFields();
}
};
//
const handleDialogClose = () => {
resetForm();
isEdit.value = false;
currentPermission.value = null;
};
//
const handleDelete = (row) => {
ElMessageBox.confirm("确认删除该权限?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
// /
const handleExpandAll = () => {
expandAll.value = !expandAll.value
const table = document.querySelector('.permission-table')
const expandBtns = table.querySelectorAll('.el-table__expand-icon')
expandBtns.forEach(btn => {
const isExpanded = btn.classList.contains('el-table__expand-icon--expanded')
if (expandAll.value && !isExpanded) {
btn.click()
} else if (!expandAll.value && isExpanded) {
btn.click()
}
})
.then(() => {
//
const index = tableData.value.findIndex((item) => item.id === row.id);
if (index !== -1) {
tableData.value.splice(index, 1);
ElMessage.success("删除成功");
}
})
.catch(() => {
//
});
};
}
onMounted(() => {
getList()
})
</script>
<template>
<div class="permission-container">
<div class="permission-management">
<el-card>
<template #header>
<div class="card-header">
<span>权限管理</span>
<el-button type="primary" @click="handleAdd">新增权限</el-button>
<div class="header-btns">
<el-button
:icon="expandAll ? 'FolderOpened' : 'Folder'"
@click="handleExpandAll"
>{{ expandAll ? '折叠' : '展开' }}所有</el-button>
<el-button type="primary" :icon="Plus" @click="handleAdd">新增权限</el-button>
</div>
</div>
</template>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="权限名称" width="150" />
<el-table-column prop="code" label="权限标识" width="150" />
<el-table-column prop="description" label="描述" min-width="200" />
<el-table-column prop="type" label="类型" width="120" />
<el-table-column prop="status" label="状态" width="100">
<!-- 搜索表单 -->
<el-form :model="searchForm" inline class="search-form">
<el-form-item label="关键词">
<el-input
v-model="searchForm.keyword"
placeholder="请输入权限名称/代码"
clearable
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item label="权限类型">
<el-select
v-model="searchForm.type"
placeholder="请选择类型"
clearable
>
<el-option
v-for="item in typeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="权限分类">
<el-input
v-model="searchForm.category"
placeholder="请输入权限分类"
clearable
/>
</el-form-item>
<el-form-item label="状态">
<el-select
v-model="searchForm.status"
placeholder="请选择状态"
clearable
>
<el-option
v-for="item in statusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<!-- 权限列表 -->
<el-table
v-loading="loading"
:data="tableData"
style="width: 100%"
row-key="id"
border
class="permission-table"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column prop="name" label="权限名称" min-width="200">
<template #default="{ row }">
<el-tag :type="row.status ? 'success' : 'danger'">
{{ row.status ? "启用" : "禁用" }}
<span>{{ row.name }}</span>
<el-tag
v-if="row.type === 'menu'"
size="small"
type="success"
class="ml-2"
>菜单</el-tag>
<el-tag
v-else-if="row.type === 'button'"
size="small"
type="warning"
class="ml-2"
>按钮</el-tag>
<el-tag
v-else
size="small"
type="info"
class="ml-2"
>接口</el-tag>
</template>
</el-table-column>
<el-table-column prop="code" label="权限代码" min-width="150"/>
<el-table-column prop="category" label="权限分类" width="120" align="center"/>
<el-table-column prop="path" label="权限路径" min-width="150" show-overflow-tooltip>
<template #default="{ row }">
{{ row.path || '-' }}
</template>
</el-table-column>
<el-table-column prop="sort_order" label="排序" width="80" align="center"/>
<el-table-column prop="status" label="状态" width="80" align="center">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column label="操作" width="180" fixed="right">
<el-table-column prop="description" label="描述" min-width="150" show-overflow-tooltip/>
<el-table-column prop="created_at" label="创建时间" width="180" align="center">
<template #default="{ row }">
<el-button type="primary" size="small" @click="handleEdit(row)">
编辑
</el-button>
<el-button type="danger" size="small" @click="handleDelete(row)">
删除
</el-button>
{{ formatDateTime(row.created_at) }}
</template>
</el-table-column>
<el-table-column label="操作" width="250" fixed="right" align="center">
<template #default="{ row }">
<el-button
v-if="row.type === 'menu'"
type="primary"
:icon="Plus"
link
@click="handleAdd(row.id)"
>新增子权限</el-button>
<el-button type="primary" :icon="Edit" link @click="handleEdit(row)">编辑</el-button>
<el-button
type="danger"
:icon="Delete"
link
@click="handleDelete(row)"
:disabled="row.children && row.children.length > 0"
>删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 新增/编辑权限弹窗 -->
<!-- 表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="isEdit ? '编辑权限' : '新增权限'"
width="500px"
@close="handleDialogClose"
:title="dialogTitle"
width="600px"
destroy-on-close
>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="权限名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入权限名称" />
<el-input
v-model="formData.name"
placeholder="请输入权限名称"
maxlength="100"
show-word-limit
/>
</el-form-item>
<el-form-item label="权限标识" prop="code">
<el-form-item label="权限代码" prop="code">
<el-input
v-model="formData.code"
placeholder="请输入权限标识"
:disabled="isEdit"
placeholder="请输入权限代码"
maxlength="100"
show-word-limit
:disabled="!!formData.id"
/>
</el-form-item>
<el-form-item label="权限类型" prop="type">
<el-select v-model="formData.type" placeholder="请选择权限类型">
<el-option label="菜单权限" value="菜单权限" />
<el-option label="操作权限" value="操作权限" />
<el-option
v-for="item in typeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="权限分类" prop="category">
<el-select v-model="formData.category" placeholder="请选择权限分类">
<el-option
v-for="item in categoryOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="权限路径" prop="path">
<el-input
v-model="formData.path"
placeholder="请输入权限路径"
/>
</el-form-item>
<el-form-item label="前端组件" prop="component">
<el-input
v-model="formData.component"
placeholder="请输入前端组件路径"
/>
</el-form-item>
<el-form-item label="图标" prop="icon">
<el-input
v-model="formData.icon"
placeholder="请输入图标名称"
/>
</el-form-item>
<el-form-item label="排序号" prop="sort_order">
<el-input-number
v-model="formData.sort_order"
:min="0"
:max="999"
controls-position="right"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch
v-model="formData.status"
:active-value="1"
:inactive-value="0"
/>
</el-form-item>
<el-form-item label="权限描述">
<el-input
v-model="formData.description"
type="textarea"
rows="4"
:rows="4"
placeholder="请输入权限描述"
/>
</el-form-item>
<el-form-item label="状态">
<el-switch v-model="formData.status" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
@ -236,56 +489,28 @@ const handleDelete = (row) => {
</template>
<style lang="scss" scoped>
@use "../../../styles/variables.scss" as *;
.permission-management {
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.permission-container {
.el-card {
background: #ffffff;
border: none;
.search-form {
margin-bottom: 24px;
padding: 24px;
background-color: #f8f9fa;
border-radius: 4px;
box-shadow: $box-shadow;
.el-card__header {
padding: 16px 20px;
border-bottom: 1px solid $border-color;
:deep(.el-form-item) {
margin-bottom: 0;
margin-right: 16px;
}
.el-input,
.el-select {
width: 200px;
}
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
span {
font-size: 16px;
font-weight: 500;
color: $text-primary;
}
}
:deep(.el-table) {
th.el-table__cell {
background-color: #fafafa;
color: $text-primary;
font-weight: 500;
}
.el-button--small {
padding: 6px 16px;
}
}
:deep(.el-form) {
.el-form-item__label {
font-weight: normal;
color: $text-regular;
}
}
.dialog-footer {
.el-button {
margin-left: 12px;
}
}
</style>

View File

@ -1,112 +1,454 @@
<script setup>
import { ref } from "vue";
import { ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Plus, Edit, Delete, Setting } from '@element-plus/icons-vue'
import { formatDateTime } from '@/utils/format'
import {
getRoleList,
getRoleDetail,
createRole,
updateRole,
deleteRole,
getRolePermissions,
assignRolePermissions,
getPermissionTree
} from '@/api/system/roles'
const tableData = ref([
{
id: 1,
name: "超级管理员",
description: "系统最高权限",
permissions: ["all"],
createTime: "2024-03-20",
},
{
id: 2,
name: "管理人员",
description: "日常运维管理",
permissions: ["monitor", "patrol"],
createTime: "2024-03-20",
},
]);
//
const searchForm = ref({
keyword: ''
})
const handleEdit = (row) => {
console.log("编辑角色:", row);
};
//
const tableData = ref([])
const loading = ref(false)
//
const pagination = ref({
page: 1,
page_size: 10,
total: 0
})
//
const getList = async () => {
loading.value = true
try {
const res = await getRoleList({
page: pagination.value.page,
page_size: pagination.value.page_size,
keyword: searchForm.value.keyword
})
if (res.success) {
tableData.value = res.data
pagination.value.total = res.data.length
} else {
ElMessage.error(res.message || '获取角色列表失败')
}
} catch (error) {
console.error('获取角色列表错误:', error)
ElMessage.error('获取角色列表失败')
} finally {
loading.value = false
}
}
//
const handleSearch = () => {
pagination.value.page = 1
getList()
}
//
const resetSearch = () => {
searchForm.value.keyword = ''
pagination.value.page = 1
getList()
}
//
const handleSizeChange = (val) => {
pagination.value.page_size = val
getList()
}
const handleCurrentChange = (val) => {
pagination.value.page = val
getList()
}
//
const dialogVisible = ref(false)
const dialogTitle = ref('新增角色')
const formRef = ref(null)
const formData = ref({
name: '',
description: ''
})
//
const rules = {
name: [
{ required: true, message: '请输入角色名称', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
]
}
//
const handleAdd = () => {
dialogTitle.value = '新增角色'
formData.value = {
name: '',
description: ''
}
dialogVisible.value = true
}
//
const handleEdit = async (row) => {
dialogTitle.value = '编辑角色'
try {
const res = await getRoleDetail(row.id)
if (res.success) {
formData.value = res.data
dialogVisible.value = true
} else {
ElMessage.error(res.message || '获取角色详情失败')
}
} catch (error) {
console.error('获取角色详情错误:', error)
ElMessage.error('获取角色详情失败')
}
}
//
const handleDelete = (row) => {
console.log("删除角色:", row);
};
</script>
ElMessageBox.confirm('确认删除该角色吗?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteRole(row.id)
if (res.success) {
ElMessage.success('删除成功')
getList()
} else {
ElMessage.error(res.message || '删除失败')
}
} catch (error) {
console.error('删除角色错误:', error)
ElMessage.error('删除失败')
}
}).catch(() => {})
}
<template>
<div class="role-container">
<el-card>
<template #header>
<div class="card-header">
<span>角色管理</span>
<el-button type="primary">新增角色</el-button>
</div>
</template>
//
const handleSubmit = async () => {
if (!formRef.value) return
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="角色名称" width="120" />
<el-table-column prop="description" label="描述" />
<el-table-column label="权限" width="200">
<template #default="{ row }">
<el-tag v-for="perm in row.permissions" :key="perm" class="permission-tag">
{{ perm }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column label="操作" width="180">
<template #default="{ row }">
<el-button type="primary" size="small" @click="handleEdit(row)">
编辑
</el-button>
<el-button type="danger" size="small" @click="handleDelete(row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
try {
await formRef.value.validate()
const submitData = {
name: formData.value.name,
description: formData.value.description,
permissions: formData.value.permissions || []
}
<style lang="scss" scoped>
@use "../../../styles/variables.scss" as *;
const submitFunc = formData.value.id ? updateRole : createRole
const res = await submitFunc(
formData.value.id,
submitData
)
.role-container {
.el-card {
background: #ffffff;
border: none;
border-radius: 4px;
box-shadow: $box-shadow;
.el-card__header {
padding: 16px 20px;
border-bottom: 1px solid $border-color;
if (res.success) {
ElMessage.success(formData.value.id ? '更新成功' : '创建成功')
dialogVisible.value = false
getList()
} else {
ElMessage.error(res.message || (formData.value.id ? '更新失败' : '创建失败'))
}
} catch (error) {
console.error('提交表单错误:', error)
if (error.response?.data?.message) {
ElMessage.error(error.response.data.message)
} else {
ElMessage.error(formData.value.id ? '更新失败' : '创建失败')
}
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
//
const permissionDialogVisible = ref(false)
const currentRole = ref(null)
const permissionList = ref([])
const selectedPermissions = ref([])
span {
font-size: 16px;
font-weight: 500;
color: $text-primary;
//
const handleCheckChange = (data, checked) => {
// ID
if (!data.children || data.children.length === 0) {
if (checked) {
if (!selectedPermissions.value.includes(data.id)) {
selectedPermissions.value.push(data.id)
}
} else {
const index = selectedPermissions.value.indexOf(data.id)
if (index > -1) {
selectedPermissions.value.splice(index, 1)
}
}
}
}
.permission-tag {
margin-right: 8px;
margin-bottom: 4px;
//
const handlePermission = async (row) => {
currentRole.value = row
try {
//
const permissionRes = await getPermissionTree()
//
const roleRes = await getRolePermissions(row.id)
if (permissionRes.success && roleRes.success) {
permissionList.value = permissionRes.data
selectedPermissions.value = roleRes.data.permissions?.map(p => p.id) || []
permissionDialogVisible.value = true
} else {
ElMessage.error('获取权限数据失败')
}
} catch (error) {
console.error('获取权限列表错误:', error)
ElMessage.error('获取权限列表失败')
}
}
:deep(.el-table) {
th.el-table__cell {
background-color: #fafafa;
color: $text-primary;
font-weight: 500;
//
const handleSubmitPermissions = async () => {
try {
if (!currentRole.value || !selectedPermissions.value) {
ElMessage.error('数据不完整,请重试')
return
}
const submitData = {
name: currentRole.value.name,
description: currentRole.value.description,
permission_ids: selectedPermissions.value
}
console.log('提交权限数据:', submitData)
const res = await assignRolePermissions(currentRole.value.id, submitData)
if (res.success) {
ElMessage.success('权限分配成功')
permissionDialogVisible.value = false
//
await getList()
} else {
ElMessage.error(res.message || '权限分配失败')
}
} catch (error) {
console.error('分配权限错误:', error)
if (error.response?.data) {
console.error('错误详情:', error.response.data)
ElMessage.error(error.response.data.message || '权限分配失败')
} else {
ElMessage.error('权限分配失败,请检查数据后重试')
}
}
}
onMounted(() => {
getList()
})
</script>
<template>
<div class="role-management">
<el-card>
<template #header>
<div class="card-header">
<span>角色管理</span>
<el-button type="primary" :icon="Plus" @click="handleAdd">新增角色</el-button>
</div>
</template>
<!-- 搜索表单 -->
<el-form :model="searchForm" inline class="search-form">
<el-form-item label="关键词">
<el-input
v-model="searchForm.keyword"
placeholder="请输入角色名称"
clearable
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<!-- 角色列表 -->
<el-table
v-loading="loading"
:data="tableData"
style="width: 100%"
>
<el-table-column type="index" label="序号" width="60" align="center"/>
<el-table-column prop="name" label="角色名称" min-width="150"/>
<el-table-column prop="description" label="角色描述" min-width="200" show-overflow-tooltip/>
<el-table-column prop="created_at" label="创建时间" width="180" align="center">
<template #default="{ row }">
{{ formatDateTime(row.created_at) }}
</template>
</el-table-column>
<el-table-column prop="updated_at" label="更新时间" width="180" align="center">
<template #default="{ row }">
{{ formatDateTime(row.updated_at) }}
</template>
</el-table-column>
<el-table-column label="操作" width="250" fixed="right" align="center">
<template #default="{ row }">
<el-button type="primary" :icon="Setting" link @click="handlePermission(row)">权限设置</el-button>
<el-button type="primary" :icon="Edit" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" :icon="Delete" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页器 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.page_size"
:total="pagination.total"
:page-sizes="[10, 20, 50, 100]"
background
layout="total, sizes, prev, pager, next"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<!-- 表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="500px"
destroy-on-close
>
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="角色名称" prop="name">
<el-input
v-model="formData.name"
placeholder="请输入角色名称"
maxlength="50"
show-word-limit
/>
</el-form-item>
<el-form-item label="角色描述" prop="description">
<el-input
v-model="formData.description"
type="textarea"
placeholder="请输入角色描述"
:rows="4"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</span>
</template>
</el-dialog>
<!-- 权限分配对话框 -->
<el-dialog
v-model="permissionDialogVisible"
title="权限分配"
width="600px"
destroy-on-close
>
<el-form label-width="100px">
<el-form-item label="角色名称">
<span>{{ currentRole?.name }}</span>
</el-form-item>
<el-form-item label="权限列表">
<el-tree
:data="permissionList"
show-checkbox
node-key="id"
:default-checked-keys="selectedPermissions"
:props="{
children: 'children',
label: 'name'
}"
@check="handleCheckChange"
>
<template #default="{ data }">
<span class="custom-tree-node">
<span>{{ data.name }}</span>
<el-tag size="small" type="info" class="permission-tag">
{{ data.type === 'menu' ? '菜单' : '按钮' }}
</el-tag>
</span>
</template>
</el-tree>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="permissionDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmitPermissions">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<style lang="scss" scoped>
.role-management {
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.el-button--small {
padding: 6px 16px;
.search-form {
margin-bottom: 24px;
padding: 24px;
background-color: #f8f9fa;
border-radius: 4px;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.custom-tree-node {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
.permission-tag {
font-size: 12px;
}
}
:deep(.el-tree-node__content) {
height: 32px;
}
}
</style>