AI 摘要

本文基于 Solon + EasyQuery + ElementPlus 实现后台管理系统的权限菜单与角色授权功能。后端提供权限的增删改查接口,支持模糊查询、非必填字段、子权限校验等逻辑;前端通过扁平数据转树形工具渲染 ElementPlus 树表,并在角色列表中集成授权弹窗,支持回显与批量更新角色权限,默认角色禁止操作。

权限菜单

后端 API

核心注意事项:

  • 权限管理的后端 API,提供基础增删改查方法。
  • 获取权限列表:可以根据权限名称、权限标识模糊查询,返回全量列表,前端自行根据全量列表解析得到树形结构数据进行渲染。
  • 添加权限/修改权限:权限路由路径、权限路由组件非必填项。
  • 删除权限:删除时权限 ID 下是否有子权限,如有禁止删除。

AI 生成控制器提示词:

基于Solon+EasyQuery,生成权限控制器,要求如下:
1、编码风格,严格参考部门控制器DeptController。
1、获取权限列表:可以根据权限名称name、权限标识identifier模糊查询,返回全量列表,不需要分页。
2、添加权限:前端提交父级权限ID-parentId、权限名称name、权限标识identifier、权限路由路径path(非必填)、权限路由组件component(非必填)、权限类型type、排序ID-sortId、权限状态status,使用注解进行数据校验,校验后插入数据到数据库。 
4、修改权限:流程同添加权限。
5、删除权限:删除时权限ID下是否有子权限,如有禁止删除。

编写权限控制器(cn.duozai.sadmin.controller.PermsController),提供基础方法:

/**
 * 权限控制器
 * @visduo
 */
@Mapping("/perms")
@Controller
public class PermsController {

    @Db
    EasyEntityQuery easyEntityQuery;

    /**
     * 获取权限列表
     * @visduo
     *
     * @param name 查询条件-权限名称
     * @param identifier 查询条件-权限标识
     * @return 权限列表分页结果
     */
    @Get
    @Mapping("/list")
    public ResponseResult list(@Param(required = false) String name,
                               @Param(required = false) String identifier) {
        List<PermsEntity> permsList = easyEntityQuery.queryable(PermsEntity.class)
                .where(p -> {
                    // 权限名称不为空时对权限名称进行模糊查询
                    p.name().like(StrUtil.isNotBlank(name), name);
                    // 权限标识不为空时对权限标识进行模糊查询
                    p.identifier().like(StrUtil.isNotBlank(identifier), identifier);
                })
                .orderBy(p -> {
                    // 根据排序ID进行排序,越小越靠前
                    p.sortId().asc();
                })
                // 查询全量列表数据
                .toList();

        return ResponseResult.success("查询成功", permsList);
    }

    /**
     * 添加权限
     * @visduo
     *
     * @param parentId 父级权限ID
     * @param name 权限名称
     * @param identifier 权限标识
     * @param path 权限路由路径
     * @param component 权限路由组件
     * @param type 权限类型
     * @param sortId 排序ID
     * @param status 权限状态
     * @return 添加结果
     */
    @Post
    @Mapping("/add")
    public ResponseResult add(@NotBlank(message = "父级权限ID不能为空") Integer parentId,
                              @NotBlank(message = "权限名称不能为空") String name,
                              @NotBlank(message = "权限标识不能为空") String identifier,
                              @Param(required = false) String path,
                              @Param(required = false) String component,
                              @NotBlank(message = "权限类型不能为空") String type,
                              @NotBlank(message = "排序ID不能为空") Integer sortId,
                              @NotBlank(message = "权限状态不能为空") Integer status) {
        PermsEntity permsEntity = new PermsEntity();
        permsEntity.setParentId(parentId);
        permsEntity.setName(name);
        permsEntity.setIdentifier(identifier);
        permsEntity.setPath(path);
        permsEntity.setComponent(component);
        permsEntity.setType(Integer.parseInt(type));
        permsEntity.setSortId(sortId);
        permsEntity.setStatus(status);

        // 插入权限数据
        easyEntityQuery.insertable(permsEntity).executeRows();

        return ResponseResult.success("添加成功", null);
    }

    /**
     * 修改权限
     * @visduo
     *
     * @param id 权限id
     * @param name 权限名称
     * @return 修改结果
     */
    @Put
    @Mapping("/update/{id}")
    public ResponseResult update(@Path int id,
                                 @NotBlank(message = "父级权限ID不能为空") Integer parentId,
                                 @NotBlank(message = "权限名称不能为空") String name,
                                 @NotBlank(message = "权限标识不能为空") String identifier,
                                 @Param(required = false) String path,
                                 @Param(required = false) String component,
                                 @NotBlank(message = "权限类型不能为空") String type,
                                 @NotBlank(message = "排序ID不能为空") Integer sortId,
                                 @NotBlank(message = "权限状态不能为空") Integer status) {
        PermsEntity permsEntity = new PermsEntity();
        permsEntity.setId(id);
        permsEntity.setParentId(parentId);
        permsEntity.setName(name);
        permsEntity.setIdentifier(identifier);
        permsEntity.setPath(path);
        permsEntity.setComponent(component);
        permsEntity.setType(Integer.parseInt(type));
        permsEntity.setSortId(sortId);
        permsEntity.setStatus(status);

        // 修改权限数据
        easyEntityQuery.updatable(permsEntity).executeRows();

        return ResponseResult.success("修改成功", null);
    }

    /**
     * 删除权限
     * @visduo
     *
     * @param id 权限id
     * @return 删除结果
     */
    @Delete
    @Mapping("/delete/{id}")
    public ResponseResult delete(@Path int id) {
        // 该权限下有子权限,禁止删除
        long count = easyEntityQuery.queryable(PermsEntity.class)
                .where(p -> {
                    p.parentId().eq(id);
                }).count();

        if (count > 0) {
            return ResponseResult.failure("该权限下有子权限,禁止删除", null);
        }

        // 删除权限数据
        easyEntityQuery.deletable(PermsEntity.class)
                .where(p -> {
                    p.id().eq(id);
                }).executeRows();

        return ResponseResult.success("删除成功", null);
    }

    /**
     * 根据权限ID获取权限信息
     * @visduo
     *
     * @param id 权限ID
     * @return 权限信息
     */
    @Get
    @Mapping("/get/{id}")
    public ResponseResult get(@Path int id) {
        PermsEntity permsEntity = easyEntityQuery.queryable(PermsEntity.class)
                .where(p -> {
                    p.id().eq(id);
                }).firstOrNull();

        return ResponseResult.success("查询成功", permsEntity);
    }

}

前端组件

核心注意事项:

  • 权限管理的前端组件,提供基础增删改查表单,表单相关注意事项和要求同前。
  • 后端 API 返回给前端的权限列表是扁平化列表,前端需要自行编写工具类方法,将其转换为树形结构列表。
  • 权限列表,使用 ElementPlus 树形数据表格组件渲染。
  • 增改表单涉及关联父级权限下拉框,使用 ElementPlus 树形选择下拉框组件渲染。

AI 生成树形结构工具方法提示词:

编写一个工具类src/plugins/TreeUtil,工具类中包含方法toTreeList.js,作用为:
1、将扁平结构的权限数据数组,转换为支持无限级嵌套的树形结构数组。
2、方法入参原始数组flatList,数组中的元素包含id、parentId,转换后的每个树形节点需完整保留原始节点的所有字段,仅新增childre字段以保存树形结构数据。
3、方法返回转换后的树形结构数组。

编写树形结构工具类(plugins/TreeUtil.js),提供树形结构转换方法:

/**
 * 将扁平结构的权限数据数组,转换为支持无限级嵌套的树形结构数组
 * @param {Array} flatList 扁平结构的数据数组,每个元素需要包含id和parentId字段
 * @returns {Array} 转换后的树形结构数组,每个节点包含原节点的所有字段及children字段
 */
export function toTreeList(flatList) {
    // 创建一个映射来存储所有节点
    const nodeMap = new Map();
    // 结果数组
    const result = [];

    // 首先复制所有节点到映射中,并添加children属性
    flatList.forEach(item => {
        nodeMap.set(item.id, { ...item, children: [] });
    });

    // 构建树形结构
    flatList.forEach(item => {
        const node = nodeMap.get(item.id);
        if (item.parentId === null || item.parentId === undefined || item.parentId === 0) {
            // 没有父节点的项作为根节点
            result.push(node);
        } else {
            // 有父节点的项添加到父节点的children中
            const parentNode = nodeMap.get(item.parentId);
            if (parentNode) {
                parentNode.children.push(node);
            } else {
                // 如果找不到父节点,默认作为根节点处理
                result.push(node);
            }
        }
    });

    return result;
}

export default {
    toTreeList
};

AI 生成组件提示词:

基于ElementPlus,生成权限列表页面,要求如下:
1、编码风格,严格参考部门组件Dept.vue。
2、提供搜索表单,搜索条件为权限名称name、权限标识identifier。
3、后端返回给前端的数据是全量列表数据。你需要调用TreeUtil相关方法将其转换为树形结构数组,并使用树形数据表格组件进行渲染。
4、获取权限列表:响应权限数据包含权限ID、父级权限ID-parentId、权限名称name、权限标识identifier、权限路由路径path、权限路由组件component、权限类型type(0目录/1菜单/2操作)、排序ID-sortId、权限状态status。
5、新增/修改表单中,父级权限显示树形选择下拉框,下拉数据即使用TreeUtil处理后的树形结构数组。树形选择下拉框中,默认需要包含一个根权限,id为0。
6、新增/修改表单中,权限路由路径path、权限路由组件component非必填。

编写权限组件(views/Perms.vue),提供权限管理视图:

<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { toTreeList } from '@/plugins/TreeUtil.js'
import qs from "qs";

// 表格数据
const tableData = ref([])

// 搜索表单数据
const searchForm = reactive({
    name: '',
    identifier: ''
})

// 添加权限对话框相关
const addDialogVisible = ref(false)
const addFormRef = ref()
const addFormData = reactive({
    parentId: 0,
    name: '',
    identifier: '',
    path: '',
    component: '',
    type: 1,
    sortId: 0,
    status: 1
})
const addFormRules = {
    parentId: [
        { required: true, message: '请选择父级权限', trigger: 'change' }
    ],
    name: [
        { required: true, message: '请输入权限名称', trigger: 'blur' },
        { min: 1, max: 50, message: '长度应在1到50个字符之间', trigger: 'blur' }
    ],
    identifier: [
        { required: true, message: '请输入权限标识', trigger: 'blur' },
        { min: 1, max: 100, message: '长度应在1到100个字符之间', trigger: 'blur' }
    ],
    type: [
        { required: true, message: '请选择权限类型', trigger: 'change' }
    ],
    sortId: [
        { required: true, message: '请输入排序ID', trigger: 'blur' }
    ],
    status: [
        { required: true, message: '请选择权限状态', trigger: 'change' }
    ]
}

// 编辑权限对话框相关
const editDialogVisible = ref(false)
const editFormRef = ref()
const editFormData = reactive({
    id: '',
    parentId: 0,
    name: '',
    identifier: '',
    path: '',
    component: '',
    type: 1,
    sortId: 0,
    status: 1
})
const editFormRules = {
    parentId: [
        { required: true, message: '请选择父级权限', trigger: 'change' }
    ],
    name: [
        { required: true, message: '请输入权限名称', trigger: 'blur' },
        { min: 1, max: 50, message: '长度应在1到50个字符之间', trigger: 'blur' }
    ],
    identifier: [
        { required: true, message: '请输入权限标识', trigger: 'blur' },
        { min: 1, max: 100, message: '长度应在1到100个字符之间', trigger: 'blur' }
    ],
    type: [
        { required: true, message: '请选择权限类型', trigger: 'change' }
    ],
    sortId: [
        { required: true, message: '请输入排序ID', trigger: 'blur' }
    ],
    status: [
        { required: true, message: '请选择权限状态', trigger: 'change' }
    ]
}

// 父级权限选项(树形结构)
const parentPermsOptions = ref([])

// 权限类型选项
const typeOptions = [
    { label: '目录', value: 0 },
    { label: '菜单', value: 1 },
    { label: '操作', value: 2 }
]

// 权限状态选项
const statusOptions = [
    { label: '启用', value: 1 },
    { label: '禁用', value: 0 }
]

// 获取权限列表
const fetchPermsList = () => {
    // 构造查询参数
    const params = {
        name: searchForm.name || undefined,
        identifier: searchForm.identifier || undefined
    }

    axios.get('/perms/list', { params }).then((res) => {
        // 将扁平数据转换为树形结构
        const treeData = toTreeList(res.data)
        tableData.value = treeData
    })
}

// 获取所有权限列表(用于父级权限选项)
const fetchAllPermsList = () => {
    axios.get('/perms/list').then((res) => {
        // 将扁平数据转换为树形结构
        const treeData = toTreeList(res.data)
        // 更新父级权限选项,添加根权限选项
        parentPermsOptions.value = [{ id: 0, name: '根权限', children: treeData }]
    })
}

// 搜索
const submitSearchForm = () => {
    fetchPermsList()
}

// 重置搜索
const resetSearchForm = () => {
    searchForm.name = ''
    searchForm.identifier = ''
    fetchPermsList()
}

// 显示添加权限对话框
const showAddDialog = () => {
    fetchAllPermsList()
    addDialogVisible.value = true
}

// 提交添加表单
const submitAddForm = () => {
    addFormRef.value.validate((valid) => {
        if (valid) {
            axios.post('/perms/add', qs.stringify(addFormData)).then((res) => {
                ElMessage.success('添加成功')
                addDialogVisible.value = false
                resetAddForm()
                fetchPermsList()
            })
        } else {
            ElMessage.error('请正确填写表单信息')
            return false
        }
    })
}

// 重置添加表单
const resetAddForm = () => {
    addFormRef.value.resetFields()
}

// 显示编辑权限对话框
const showEditDialog = (row) => {
    fetchAllPermsList()

    // 先设置ID,用于请求详细信息
    editFormData.id = row.id
    editDialogVisible.value = true

    // 获取权限详细信息
    axios.get(`/perms/get/${row.id}`).then((res) => {
        editFormData.parentId = res.data.parentId
        editFormData.name = res.data.name
        editFormData.identifier = res.data.identifier
        editFormData.path = res.data.path
        editFormData.component = res.data.component
        editFormData.type = res.data.type
        editFormData.sortId = res.data.sortId
        editFormData.status = res.data.status
    })
}

// 提交编辑表单
const submitEditForm = () => {
    editFormRef.value.validate((valid) => {
        if (valid) {
            axios.put(`/perms/update/${editFormData.id}`, qs.stringify(editFormData)).then((res) => {
                ElMessage.success('修改成功')
                editDialogVisible.value = false
                resetEditForm()
                fetchPermsList()
            })
        } else {
            ElMessage.error('请正确填写表单信息')
            return false
        }
    })
}

// 重置编辑表单
const resetEditForm = () => {
    editFormRef.value.resetFields()
}

// 删除权限
const handleDelete = (id) => {
    ElMessageBox.confirm('确定要删除该权限吗?', '删除确认', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
    }).then(() => {
        axios.delete(`/perms/delete/${id}`)
            .then((res) => {
                ElMessage.success('删除成功')
                fetchPermsList()
            })
    }).catch(() => {
        // 用户取消删除
    })
}

// 初始化加载数据
onMounted(() => {
    fetchPermsList()
})
</script>

<template>
    <el-main class="layout-main">
        <!-- 搜索表单 -->
        <el-card shadow="never" style="margin-bottom: 1rem;" :body-style="{paddingBottom: '2px'}">
            <el-form :model="searchForm" inline>
                <el-form-item label="权限名称">
                    <el-input v-model="searchForm.name" placeholder="请输入权限名称" />
                </el-form-item>
                <el-form-item label="权限标识">
                    <el-input v-model="searchForm.identifier" placeholder="请输入权限标识" />
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="submitSearchForm">搜索</el-button>
                    <el-button @click="resetSearchForm">重置</el-button>
                    <el-button type="warning" @click="showAddDialog">添加权限</el-button>
                </el-form-item>
            </el-form>
        </el-card>

        <!-- 数据表格 -->
        <el-card shadow="never">
            <template #header>
                <div class="card-header">
                    <span>权限列表</span>
                </div>
            </template>
            <el-table :data="tableData" style="width: 100%" row-key="id" default-expand-all :tree-props="{children: 'children'}" stripe>
                <el-table-column prop="name" label="权限名称" />
                <el-table-column prop="identifier" label="权限标识" />
                <el-table-column prop="path" label="路由路径" />
                <el-table-column prop="component" label="组件路径" />
                <el-table-column prop="type" label="权限类型" width="80">
                    <template #default="scope">
                        <el-tag v-if="scope.row.type === 0">目录</el-tag>
                        <el-tag v-else-if="scope.row.type === 1" type="success">菜单</el-tag>
                        <el-tag v-else-if="scope.row.type === 2" type="warning">操作</el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="sortId" label="排序ID" width="80" />
                <el-table-column prop="status" label="状态" width="80">
                    <template #default="scope">
                        <el-tag v-if="scope.row.status === 1" type="success">启用</el-tag>
                        <el-tag v-else type="danger">禁用</el-tag>
                    </template>
                </el-table-column>
                <el-table-column label="操作" width="150">
                    <template #default="scope">
                        <el-button type="primary" size="small" @click="showEditDialog(scope.row)">编辑</el-button>
                        <el-button type="danger" size="small" @click="handleDelete(scope.row.id)">删除</el-button>
                    </template>
                </el-table-column>
            </el-table>
        </el-card>

        <!-- 添加权限对话框 -->
        <el-dialog v-model="addDialogVisible" title="添加权限" width="500px" @close="resetAddForm">
            <el-form ref="addFormRef" :model="addFormData" :rules="addFormRules" label-width="100px" label-position="top">
                <el-form-item label="父级权限" prop="parentId">
                    <el-tree-select
                        v-model="addFormData.parentId"
                        :data="parentPermsOptions"
                        node-key="id"
                        :props="{ label: 'name', children: 'children' }"
                        check-strictly
                        style="width: 100%"
                        placeholder="请选择父级权限"
                    />
                </el-form-item>

                <el-form-item label="权限名称" prop="name">
                    <el-input v-model="addFormData.name" placeholder="请输入权限名称"/>
                </el-form-item>

                <el-form-item label="权限标识" prop="identifier">
                    <el-input v-model="addFormData.identifier" placeholder="请输入权限标识"/>
                </el-form-item>

                <el-form-item label="路由路径">
                    <el-input v-model="addFormData.path" placeholder="请输入路由路径"/>
                </el-form-item>

                <el-form-item label="组件路径">
                    <el-input v-model="addFormData.component" placeholder="请输入组件路径"/>
                </el-form-item>

                <el-form-item label="权限类型" prop="type">
                    <el-select v-model="addFormData.type" placeholder="请选择权限类型" style="width: 100%">
                        <el-option
                            v-for="item in typeOptions"
                            :key="item.value"
                            :label="item.label"
                            :value="item.value">
                        </el-option>
                    </el-select>
                </el-form-item>

                <el-form-item label="排序ID" prop="sortId">
                    <el-input-number v-model="addFormData.sortId" :min="0" controls-position="right" style="width: 100%"/>
                </el-form-item>

                <el-form-item label="权限状态" prop="status">
                    <el-select v-model="addFormData.status" placeholder="请选择权限状态" style="width: 100%">
                        <el-option
                            v-for="item in statusOptions"
                            :key="item.value"
                            :label="item.label"
                            :value="item.value">
                        </el-option>
                    </el-select>
                </el-form-item>
            </el-form>

            <template #footer>
                <div style="width: 100%; text-align: center">
                    <el-button @click="addDialogVisible = false">取消</el-button>
                    <el-button type="primary" @click="submitAddForm">确定</el-button>
                </div>
            </template>
        </el-dialog>

        <!-- 编辑权限对话框 -->
        <el-dialog v-model="editDialogVisible" title="编辑权限" width="500px" @close="resetEditForm">
            <el-form ref="editFormRef" :model="editFormData" :rules="editFormRules" label-width="100px" label-position="top">
                <el-form-item label="父级权限" prop="parentId">
                    <el-tree-select
                        v-model="editFormData.parentId"
                        :data="parentPermsOptions"
                        node-key="id"
                        :props="{ label: 'name', children: 'children' }"
                        check-strictly
                        style="width: 100%"
                        placeholder="请选择父级权限"
                    />
                </el-form-item>

                <el-form-item label="权限名称" prop="name">
                    <el-input v-model="editFormData.name" placeholder="请输入权限名称"/>
                </el-form-item>

                <el-form-item label="权限标识" prop="identifier">
                    <el-input v-model="editFormData.identifier" placeholder="请输入权限标识"/>
                </el-form-item>

                <el-form-item label="路由路径">
                    <el-input v-model="editFormData.path" placeholder="请输入路由路径"/>
                </el-form-item>

                <el-form-item label="组件路径">
                    <el-input v-model="editFormData.component" placeholder="请输入组件路径"/>
                </el-form-item>

                <el-form-item label="权限类型" prop="type">
                    <el-select v-model="editFormData.type" placeholder="请选择权限类型" style="width: 100%">
                        <el-option
                            v-for="item in typeOptions"
                            :key="item.value"
                            :label="item.label"
                            :value="item.value">
                        </el-option>
                    </el-select>
                </el-form-item>

                <el-form-item label="排序ID" prop="sortId">
                    <el-input-number v-model="editFormData.sortId" :min="0" controls-position="right" style="width: 100%"/>
                </el-form-item>

                <el-form-item label="权限状态" prop="status">
                    <el-select v-model="editFormData.status" placeholder="请选择权限状态" style="width: 100%">
                        <el-option
                            v-for="item in statusOptions"
                            :key="item.value"
                            :label="item.label"
                            :value="item.value">
                        </el-option>
                    </el-select>
                </el-form-item>
            </el-form>

            <template #footer>
                <div style="width: 100%; text-align: center">
                    <el-button @click="editDialogVisible = false">取消</el-button>
                    <el-button type="primary" @click="submitEditForm">确定</el-button>
                </div>
            </template>
        </el-dialog>
    </el-main>
</template>

编写路由配置文件(router/index.js),提供权限管理路由信息:

{
    // 权限管理
    path: 'perms/list',
    component: () => import('@/views/Perms.vue'),
},

编写布局组件(views/Layouts.vue),提供权限管理菜单:

{
    id: 20,
    title: '权限管理',
    path: '/perms/list'
},

在浏览器中测试:

角色授权

后端 API

核心注意事项:

  • 修改角色权限:提供修改角色权限方法,传入授权的权限 ID 字符串,并保存到数据库中。
  • 角色 ID 为 1 即最高角色,禁止修改角色权限。

编写角色控制器(cn.duozai.sadmin.controller.RoleController),提供修改角色权限方法:

package cn.duozai.sadmin.controller;

import cn.duozai.sadmin.repository.RoleEntity;
import cn.duozai.sadmin.repository.UsersEntity;
import cn.duozai.sadmin.utils.ResponseResult;
import cn.hutool.core.util.StrUtil;
import com.easy.query.api.proxy.client.EasyEntityQuery;
import com.easy.query.core.api.pagination.EasyPageResult;
import com.easy.query.solon.annotation.Db;
import org.noear.solon.annotation.*;
import org.noear.solon.validation.annotation.NotBlank;

import java.util.List;

/**
 * 角色控制器
 * @visduo
 */
@Mapping("/role")
@Controller
public class RoleController {

    // ...

    /**
     * 修改角色权限
     * @visduo
     *
     * @param id 角色id
     * @param perms 授权的权限菜单列表
     * @return 修改结果
     */
    @Put
    @Mapping("/updatePerms/{id}")
    public ResponseResult updatePerms(@Path int id,
                                      @Param String perms) {
        // 禁止修改ID为1的角色
        if (id == 1) {
            return ResponseResult.failure("默认角色禁止修改", null);
        }

        RoleEntity roleEntity = new RoleEntity();
        roleEntity.setId(id);
        roleEntity.setPerms(perms);

        easyEntityQuery.updatable(roleEntity).executeRows();

        return ResponseResult.success("修改成功", null);
    }

}

前端组件

核心注意事项:

  • 角色列表中,操作列提供授权按钮,点击授权按钮显示授权模态框,回显勾选已授权的权限。
  • 授权模态框中,使用 Tree 树形控件组件展示权限树形结构列表。
  • Tree 树形控件组件支持取消父子节点的勾选关联,让父节点和子节点的勾选状态完全独立,父节点不会因子节点勾选和取消而自动半选或全选,子节点也不受父节点影响。
  • 表单提交时获取选中的权限 ID 数组并转换为字符串,发送给后端 API。

AI 生成组件提示词:

在Role.vue的基础上,实现:
1、在表格操作列添加一个授权按钮,点击授权按钮后,弹出授权模态框。
2、授权模态框展示时,发送请求到/perms/list获取全量权限列表,并使用TreeUtil转换为树形结构。
3、使用Tree树形控件组件展示权限列表。
4、授权模态框展示时,发送请求到/role/get/xxx,获取到角色信息中的perms后,回显勾选权限列表。perms的格式为字符串1,2,3,4。
5、点击确定按钮,获取权限列表中选中项的id数组并将其转换成字符串,发送请求到/role/updatePerms/xxx。
6、ID为1的角色是默认角色,需要禁用授权按钮。

编写角色组件(views/Role.vue),提供角色授权视图:

<script setup>
// ...

// 授权对话框相关
const authDialogVisible = ref(false)
const permTreeData = ref([])
const defaultCheckedKeys = ref([])
const treeRef = ref()

// ...

// 显示授权对话框
const showAuthDialog = (row) => {
    // 保存当前角色ID用于后续操作
    editFormData.id = row.id;

    // 获取全量权限列表
    axios.get('/perms/list').then((res) => {
        // 将扁平数据转换为树形结构
        permTreeData.value = toTreeList(res.data)

        // 获取角色详细信息,包括权限列表
        axios.get(`/role/get/${row.id}`).then((response) => {
            // 设置默认选中的权限
            // 处理perms可能不存在或为逗号分隔字符串的情况
            let permIds = [];
            if (response.data.perms) {
                // perms是逗号分隔的字符串格式,例如"1,2,3,4,5"
                permIds = response.data.perms.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id));
            }
            defaultCheckedKeys.value = permIds;

            // 显示授权对话框
            authDialogVisible.value = true
        })
    })
}

// 提交授权表单
const submitAuthForm = () => {
    // 获取选中的权限ID数组
    const checkedKeys = treeRef.value.getCheckedKeys()

    // 发送请求更新角色权限
    axios.put(`/role/updatePerms/${editFormData.id}`, qs.stringify({ perms: checkedKeys.join(',') })).then((res) => {
        ElMessage.success('授权成功')
        authDialogVisible.value = false
        fetchRoleList()
    })
}

// 重置授权表单
const resetAuthForm = () => {
    defaultCheckedKeys.value = []
    permTreeData.value = []
}

// ...
</script>

<template>
    <el-main class="layout-main">
        <!-- ... -->

        <!-- 数据表格 -->
        <el-card shadow="never">
            <template #header>
                <div class="card-header">
                    <span>角色列表</span>
                </div>
            </template>
            <el-table :data="tableData" style="width: 100%" stripe>
                <!-- ... -->
                <el-table-column label="操作">
                    <template #default="scope">
                        <el-button type="primary" size="small" @click="showEditDialog(scope.row)">编辑</el-button>
                        <el-button type="success" size="small" @click="showAuthDialog(scope.row)" :disabled="scope.row.id === 1">
                            授权
                        </el-button>
                        <el-button type="danger" size="small" @click="handleDelete(scope.row.id)" :disabled="scope.row.id === 1">
                            删除
                        </el-button>
                    </template>
                </el-table-column>
            </el-table>

            <!-- ... -->
        </el-card>

        <!-- ... -->

        <!-- 授权对话框 -->
        <el-dialog v-model="authDialogVisible" title="角色授权" width="500px" @close="resetAuthForm">
            <el-tree
                ref="treeRef"
                :data="permTreeData"
                show-checkbox
                node-key="id"
                :props="{ label: 'name', children: 'children' }"
                :default-checked-keys="defaultCheckedKeys"
                :default-expand-all="false"
                :check-strictly="true"
                style="width: 100%"
            />

            <template #footer>
                <div style="width: 100%; text-align: center">
                    <el-button @click="authDialogVisible = false">取消</el-button>
                    <el-button type="primary" @click="submitAuthForm">确定</el-button>
                </div>
            </template>
        </el-dialog>
    </el-main>
</template>

在浏览器中测试: