菜单和角色

This commit is contained in:
李远 2026-03-11 17:36:25 +08:00
parent 7e8d963095
commit db9b98af8e
9 changed files with 449 additions and 369 deletions

View File

@ -43,15 +43,6 @@ export async function deleteRoleApi(params: DeleteRoleParams): Promise<void> {
return request.post<void>('/roles/del', params);
}
/**
*
* @param params ID + ID列表
* @returns Promise<void>
*/
export async function setRolePermissionApi(params: SetRolePermissionParams): Promise<void> {
return request.post<void>('/roles/setPermission', params);
}
/**
*
* @param params ID
@ -60,3 +51,12 @@ export async function setRolePermissionApi(params: SetRolePermissionParams): Pro
export async function getRolePermissionApi(params: GetRolePermissionParams): Promise<PermissionIdsResponse> {
return request.get<PermissionIdsResponse>('/roles/getPermission', { id: params.id });
}
/**
*
* @param params ID + ID列表
* @returns Promise<void>
*/
export async function setRolePermissionApi(params: SetRolePermissionParams): Promise<void> {
return request.post<void>('/roles/setPermission', params);
}

View File

@ -7,10 +7,6 @@ export interface RoleFormParams {
id?: string; // 仅编辑/删除/权限操作时需要
}
// 删除角色参数
export interface DeleteRoleParams {
id: string;
}
// 设置角色权限参数
export interface SetRolePermissionParams {
@ -22,18 +18,32 @@ export interface SetRolePermissionParams {
export interface GetRolePermissionParams {
id: string;
}
// 删除角色参数
export interface DeleteRoleParams {
id: string;
}
// 角色列表单条数据类型
export interface RoleItem {
id: string;
name: string;
title: string;
create_time?: string;
update_time?: string;
create_at?: string;
}
// 角色列表响应类型
export type RoleListResponse = RoleItem[];
// 定义权限项的结构
interface PermissionInfo {
id: string;
title: string;
}
export interface RolePermissionItem {
all_permission_info: PermissionInfo[];
permission_ids: [];
}
// 权限ID列表响应类型
export type PermissionIdsResponse = string[];
export type PermissionIdsResponse = RolePermissionItem;

View File

@ -10,8 +10,8 @@
<!-- 下拉选择框 -->
<el-select v-else-if="item.type === 'select'" v-model="formData[item.prop]"
:placeholder="item.placeholder" :disabled="item.disabled" clearable style="width: 100%">
<el-option v-for="option in props.optionsMap[item.prop]" :key="option.value" :label="option.label"
:value="option.value">
<el-option v-for="option in props.optionsMap?.[item.prop] || []" :key="option.value"
:label="option.label" :value="option.value">
<template v-if="option.icon" #label>
<span style="display: flex; align-items: center; gap: 8px;">
<el-icon v-if="option.icon">
@ -29,11 +29,19 @@
<!-- 单选框组 -->
<el-radio-group v-else-if="item.type === 'radio'" v-model="formData[item.prop]">
<el-radio v-for="option in props.optionsMap[item.prop]" :key="option.value" :value="option.value">
<el-radio v-for="option in props.optionsMap?.[item.prop] || []" :key="option.value"
:value="option.value">
{{ option.label }}
</el-radio>
</el-radio-group>
<!-- 复选框组 -->
<el-checkbox-group v-else-if="item.type === 'checkbox'" v-model="formData[item.prop]">
<el-checkbox v-for="option in props.optionsMap?.[item.prop] || []" :key="option.value"
:value="option.value">
{{ option.label }}
</el-checkbox>
</el-checkbox-group>
<!-- 自定义插槽 -->
<slot v-else :name="item.slotName" :formData="formData" />
</el-form-item>
@ -46,7 +54,7 @@ import type { FormInstance, FormRules } from 'element-plus'
import type { Component } from 'vue'
interface FormItem {
type: 'input' | 'select' | 'number' | 'radio' | 'slot'
type: 'input' | 'select' | 'number' | 'radio' | 'slot' | 'checkbox'
label: string
prop: string
placeholder?: string
@ -61,7 +69,7 @@ interface Props {
formData: Record<string, any>
formRules: FormRules
formItems: FormItem[]
optionsMap: Record<string, Array<{
optionsMap?: Record<string, Array<{
label: string | undefined
value: string | number
icon?: Component

View File

@ -3,7 +3,7 @@
<!-- 菜单列表头 -->
<MenuHeader :title="menuTitle" :buttons="menuHeaderButtons" @click="handleAddMenu" />
<!-- 菜单列表表格 -->
<MenuTable :table-data="menuTree" :columns="MenuTableColumns" row-key="id" :tree-props="treeProps">
<MenuTable :table-data="menuTree" :columns="menuTableColumns" row-key="id" :tree-props="treeProps">
<template #icon="{ row }">
<el-icon v-if="row.icon && menuiconMap[row.icon]">
<component :is="menuiconMap[row.icon]" />
@ -14,9 +14,8 @@
{{ row.type === 1 ? '目录' : '按钮' }}
</template>
<template #status="{ row }">
<el-tag size="small" :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? '启用' : '禁用' }}
</el-tag>
<el-switch v-model="row.status" :active-value="1" :inactive-value="2" active-text="启用"
inactive-text="禁用" @change="handleStatusChange(row)" />
</template>
<template #action="{ row }">
<el-button link type="primary" size="small" @click="handleEditMenu(row)">
@ -25,17 +24,17 @@
<el-button link type="danger" size="small" @click="handleDeleteMenu(row)">
删除
</el-button>
<el-button link size="small" :type="row.status === 1 ? 'warning' : 'success'"
<!-- <el-button link size="small" :type="row.status === 1 ? 'warning' : 'success'"
@click="handleToggleStatus(row)">
{{ row.status === 1 ? '禁用' : '启用' }}
</el-button>
</el-button> -->
</template>
</MenuTable>
</div>
<!-- 菜单新增/编辑弹窗 -->
<MenuDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" @confirm="handleSubmit"
@cancel="handleCancel" @close="handleClose">
<MenuForm :form-data="menuForm" :form-rules="menuFormRules" :form-items="MenuFormItemOptions"
<MenuForm :form-data="menuForm" :form-rules="menuFormRules" :form-items="menuFormItemOptions"
:options-map="menuFormOptionsMap" ref="menuFormRef" />
</MenuDialog>
</template>
@ -47,7 +46,7 @@ import MenuHeader from '@/components/common/Header.vue'
import MenuTable from '@/components/common/Table.vue'
import MenuDialog from '@/components/common/Dialog.vue'
import MenuForm from '@/components/common/Form.vue'
import { MenuTableColumns, MenuiconOptions, menuiconMap, MenuFormItemOptions } from './options'
import { menuTableColumns, menuiconOptions, menuiconMap, menuFormItemOptions } from './options'
import { rules } from './rules'
import { getMenuApi, createMenuApi, editMenuApi, deleteMenuApi } from '@/api/modules/menu'
@ -79,11 +78,11 @@ const menuForm = ref({
})
const menuFormRef = ref<FormInstance>()
//
const menuFormRules = rules(menuForm.value, MenuiconOptions)
const menuFormRules = rules(menuForm.value, menuiconOptions)
// icon
const menuFormOptionsMap = ref({
'parentName': [] as any[],
'icon': MenuiconOptions,
'icon': menuiconOptions,
'type': [
{ label: '菜单', value: 1 },
{ label: '按钮', value: 2 }
@ -157,8 +156,24 @@ const handleDeleteMenu = async (row: any) => {
console.error('删除菜单失败')
}
}
const handleToggleStatus = (row: any) => {
row.status = row.status === 1 ? 2 : 1
const handleStatusChange = async (row: any) => {
const oldStatus = row.status === 1 ? 2 : 1
try {
//
// await updateMenuStatusApi({
// id: row.id,
// status: row.status
// })
// ElMessage.success(`${row.status === 1 ? '' : ''}`)
//
// await fetchMenuList()
} catch (err) {
//
row.status = oldStatus
ElMessage.error('状态修改失败')
console.error(err)
}
}
/**
@ -192,8 +207,30 @@ const handleSubmit = async () => {
}
const handleCancel = () => {
dialogVisible.value = false
dialogType.value = 'add'
menuForm.value = {
name: '', //
parentName: '', //
path: '', //
api: '', //
icon: '', //
sort: null, // 1
type: null, //
status: null, //
}
}
const handleClose = () => {
dialogVisible.value = false
dialogType.value = 'add'
menuForm.value = {
name: '', //
parentName: '', //
path: '', //
api: '', //
icon: '', //
sort: null, // 1
type: null, //
status: null, //
}
}
</script>

View File

@ -13,7 +13,7 @@ export const menuiconMap: Record<string, any> = {
}
// 定义选择图标选项列表配置
export const MenuiconOptions = [
export const menuiconOptions = [
{ label: "无图标", value: "无图标" },
{ label: "Tools(工具)", value: "Tools", icon: Tools },
{ label: "List(列表)", value: "List", icon: List },
@ -26,7 +26,7 @@ export const MenuiconOptions = [
]
//定义table配置
export const MenuTableColumns = [
export const menuTableColumns = [
{ prop: 'name', label: '菜单名称', align: 'center' as const, width: '120px' },
{ prop: 'path', label: '路由路径', align: 'center' as const, width: '120px' },
{ label: '图标', slotName: 'icon', align: 'center' as const },
@ -36,7 +36,7 @@ export const MenuTableColumns = [
]
//定义menuForm配置
export const MenuFormItemOptions = [
export const menuFormItemOptions = [
{ prop: 'name', label: '菜单名称', type: 'input' as const,placeholder: '请输入菜单名称' },
{ prop: 'parentName', label: '父级菜单', type: 'select' as const, placeholder: '请选择父级菜单' },
{ prop: 'path', label: '路由路径', type: 'input' as const, placeholder: '请输入路由路径' },

View File

@ -1,256 +0,0 @@
<template>
<div class="system">
<!-- 缺少通配搜索组件 -->
<div class="header">
<h4>菜单列表</h4>
<el-button type="primary" :icon="Plus" @click="handleAddMenu">新增菜单</el-button>
</div>
<div class="table-container">
<el-table :data="menuTree" row-key="id" :tree-props="{ children: 'son' }" border highlight-current-row>
<el-table-column prop="name" label="菜单名称" />
<el-table-column prop="path" label="路由路径" />
<el-table-column prop="icon" label="图标">
<template #default="{ row }">
<el-icon v-if="row.icon && iconMap[row.icon]">
<component :is="iconMap[row.icon]" />
</el-icon>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="type" label="类型">
<template #default="{ row }">
{{ row.type === 1 ? '目录' : '按钮' }}
</template>
</el-table-column>
<!-- 新增状态列 -->
<el-table-column prop="status" label="状态">
<template #default="{ row }">
<el-tag size="small" :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="handleEditMenu(row)">
编辑
</el-button>
<el-button link type="danger" size="small" @click="handleDeleteMenu(row)">
删除
</el-button>
<el-button link size="small" :type="row.status === 1 ? 'warning' : 'success'"
@click="handleToggleStatus(row)">
{{ row.status === 1 ? '禁用' : '启用' }}
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增菜单' : '编辑菜单'" width="600px" destroy-on-close>
<el-form ref="menuFormRef" :model="menuForm" :rules="menuFormRules">
<el-form-item label="菜单名称" prop="name">
<el-input v-model="menuForm.name" placeholder="请输入菜单名称" />
</el-form-item>
<el-form-item label="父级菜单" prop="parentId">
<el-select v-model="menuForm.parentId" placeholder="请选择父级菜单" style="width:100%">
<el-option label="主菜单" value="0" />
<el-option v-for="item in menuTree" :key="item.id" :label="item.name" :value="item.id + ''" />
</el-select>
</el-form-item>
<el-form-item label="路由路径" prop="path">
<el-input v-model="menuForm.path" placeholder="例如:/system/menuList" />
</el-form-item>
<el-form-item label="后端路由" prop="api">
<el-input v-model="menuForm.api" placeholder="例如:/api/system/menu/list可选" />
</el-form-item>
<el-form-item label="图标名称" prop="icon">
<el-select v-model="menuForm.icon" placeholder="请选择图标(二级菜单可留空)" style="width: 100%" clearable>
<el-option v-for="item in iconOptions" :key="item.value" :label="item.label" :value="item.value">
<template #label>
<span style="display: flex; align-items: center; gap: 8px;">
<el-icon v-if="item.icon">
<component :is="item.icon" />
</el-icon>
<span>{{ item.label }}</span>
</span>
</template>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="menuForm.sort" :min="0" placeholder="数字越小越靠前默认1" style="width: 100%" />
</el-form-item>
<el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="menuForm.type">
<el-radio :value="1">菜单显示在侧边栏</el-radio>
<el-radio :value="2">按钮权限控制</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="() => { { dialogVisible = false, dialogType = 'add' } }">取消</el-button>
<el-button type="primary" @click="handleSubmitMenu">确定</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus'
import { Tools, List, HomeFilled, UserFilled, Platform, BellFilled, GoodsFilled, WarningFilled, Plus } from '@element-plus/icons-vue'
import { getMenuApi, createMenuApi, deleteMenuApi, editMenuApi } from '@/api/modules/menu'
import { rules } from './rules'
//
const iconMap: Record<string, any> = {
Tools,
List,
HomeFilled,
UserFilled,
Platform,
BellFilled,
GoodsFilled,
WarningFilled
}
//
const iconOptions = [
{ label: "无图标", value: "无图标" },
{ label: "Tools(工具)", value: "Tools", icon: Tools },
{ label: "List(列表)", value: "List", icon: List },
{ label: "HomeFilled(首页)", value: "HomeFilled", icon: HomeFilled },
{ label: "UserFilled(用户)", value: "UserFilled", icon: UserFilled },
{ label: "Platform(平台)", value: "Platform", icon: Platform },
{ label: "BellFilled(铃铛)", value: "BellFilled", icon: BellFilled },
{ label: "GoodsFilled(商品)", value: "GoodsFilled", icon: GoodsFilled },
{ label: "WarningFilled(警告)", value: "WarningFilled", icon: WarningFilled },
]
//menu
const menuTree = ref<any[]>([])
//
const menuForm = ref({
name: '', //
parentId: '0', //
path: '', //
api: '', //
icon: '', //
sort: 1, // 1
type: 1 //
})
const menuFormRef = ref<FormInstance>()
//
const menuFormRules = rules(menuForm.value, iconOptions)
//
const dialogVisible = ref(false)
// /
const dialogType = ref<'add' | 'edit'>('add')
// ID
const currentEditId = ref(null)
//
//
onMounted(async () => {
try {
const res = await getMenuApi()
menuTree.value = filterEnabledMenus(res)
} catch (err) {
console.error('获取菜单列表失败')
}
})
// status=1
const filterEnabledMenus = (menuList: any[]) => {
const enabledMenus = menuList.filter(menu => menu.status === 1)
enabledMenus.forEach(menu => {
if (menu.son && menu.son.length) {
menu.son = filterEnabledMenus(menu.son)
}
})
return enabledMenus
}
//
const handleAddMenu = () => {
//
menuForm.value = {
name: '',
parentId: '0',
path: '',
api: '',
icon: '',
sort: 1,
type: 1
}
dialogVisible.value = true
console.log('dialogType.value', dialogType.value);
}
//
const handleEditMenu = (row: any) => {
dialogType.value = 'edit'
currentEditId.value = row.id
menuForm.value = {
name: row.name,
parentId: row.parent_id,
path: row.path,
api: row.api,
icon: row.icon,
sort: row.sort,
type: row.type
}
dialogVisible.value = true
}
// /
const handleSubmitMenu = async () => {
try {
await menuFormRef.value?.validate()
if (dialogType.value === 'add') {
await createMenuApi({
...menuForm.value,
parent_id: menuForm.value.parentId
})
const res = await getMenuApi()
menuTree.value = filterEnabledMenus(res)
} else {
await editMenuApi({
id: currentEditId.value || 0,
status: menuForm.value.type === 2 ? 0 : 1, //
parent_id: menuForm.value.parentId,
...menuForm.value
})
const res = await getMenuApi()
menuTree.value = filterEnabledMenus(res)
}
dialogVisible.value = false
} catch (err) {
console.log('提交菜单失败', err)
}
}
//
const handleDeleteMenu = async (row: any) => {
try {
await deleteMenuApi({ id: row.id })
const res = await getMenuApi()
menuTree.value = filterEnabledMenus(res)
} catch (err) {
console.log('删除菜单失败', err)
}
}
//
const handleToggleStatus = async (row: any) => {
row.status = row.status === 1 ? 0 : 1
ElMessage.success(`${row.status === 1 ? '启用' : '禁用'}`)
}
</script>

View File

@ -1,92 +1,347 @@
<template>
<div class="system">
<!-- 缺少通配搜索组件 -->
<div class="header">
<h4>菜单列表</h4>
<el-button type="primary" :icon="Plus" @click="handleAddRole">新增角色</el-button>
</div>
<div class="table-container">
<el-table :data="roleTree" row-key="id" border highlight-current-row>
<el-table-column prop="name" label="角色名称" />
<el-table-column prop="title" label="角色描述" />
<el-table-column prop="status" label="角色状态">
<template #default="{ row }">
<el-tag size="small" :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? '启用' : '禁用' }}
</el-tag>
<!-- 角色列表头 -->
<RoleHeader :title="roleTitle" :buttons="roleHeaderButtons" @click="handleAddRole" />
<!-- 角色列表表格 -->
<RoleTable :table-data="roleTree" :columns="roleTableColumns" row-key="id">
<template #status="{ row }">
<el-switch v-model="row.status" :active-value="1" :inactive-value="2" active-text="启用"
inactive-text="禁用" @change="handleStatusChange(row)" />
</template>
</el-table-column>
<el-table-column prop="created_at" label="创建时间" />
<el-table-column label="操作">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="handleEditMenu(row)">
<template #createTime="{ row }">
{{ row.created_at }}
</template>
<template #action="{ row }">
<el-button link type="primary" size="small" @click="handleEditRole(row)">
编辑
</el-button>
<el-button link type="danger" size="small" @click="handleDeleteMenu(row)">
<el-button link type="primary" size="small" @click="handlePermission(row)">
设置权限
</el-button>
<el-button link type="danger" size="small" @click="handleDeleteRole(row)">
删除
</el-button>
<el-button link size="small" :type="row.status === 1 ? 'warning' : 'success'"
@click="handleToggleStatus(row)">
{{ row.status === 1 ? '禁用' : '启用' }}
</el-button>
</template>
</el-table-column>
</el-table>
</RoleTable>
</div>
<!-- 角色新增/编辑弹窗 -->
<RoleDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" @confirm="handleSubmit"
@cancel="handleCancel" @close="handleClose">
<RoleForm :form-data="roleForm" :form-rules="roleFormRules" :form-items="roleFormItemOptions" ref="roleFormRef"
:options-map="roleFormOptionsMap" :dialog-type="dialogType">
<template #permissions="{ formData }">
<div class="permission-group">
<div v-for="(group, module) in groupedPermissions" :key="module" class="permission-module">
<!-- 点击事件的 checked 参数 -->
<el-checkbox :model-value="getModuleCheckedState(module)"
:indeterminate="getModuleIndeterminateState(module)"
@change="(checked: boolean | string | number) => handleModuleCheckChange(module, !!checked)"
class="module-title">
{{ module }}
</el-checkbox>
<!-- 添加子权限 change 事件监听 -->
<el-checkbox-group v-model="formData.permissions" @change="handlePermissionChange">
<el-checkbox v-for="perm in group" :key="perm.id" :label="perm.id">
{{ perm.title }} ({{ perm.name }})
</el-checkbox>
</el-checkbox-group>
</div>
<el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增角色' : '编辑角色'" width="600px"
destroy-on-close>
<el-form>
<el-form-item label="角色名称" prop="name">
<el-input placeholder="请输入角色名称" />
</el-form-item>
<el-form-item label="角色描述" prop="path">
<el-input placeholder="请输入角色描述" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="() => { { dialogVisible = false, dialogType = 'add' } }">取消</el-button>
<el-button type="primary" @click="handleSubmitRole">确定</el-button>
</template>
</el-dialog>
</div>
</template>
</RoleForm>
</RoleDialog>
</template>
<script setup lang="ts">
<script lang="ts" setup>
import type { FormInstance } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { createRoleApi, deleteRoleApi, editRoleApi, getRoleApi } from '@/api/modules/role'
const roleTree = ref<any[]>([
{
id: 1,
name: "admin",
title: "平台",
status: 1,
created_at: "2026-02-05 06:12:03"
}
])
//
const dialogVisible = ref(true)
// /
const dialogType = ref<'add' | 'edit'>('add')
onMounted(async () => {
const res = await getRoleApi()
roleTree.value = res || []
//
import RoleHeader from '@/components/common/Header.vue'
import RoleTable from '@/components/common/Table.vue'
import RoleDialog from '@/components/common/Dialog.vue'
import RoleForm from '@/components/common/Form.vue'
import { roleTableColumns, roleFormItem, permissionsFormItem } from './options'
import { rules } from './rules'
import { getRoleApi, createRoleApi, editRoleApi, deleteRoleApi, getRolePermissionApi, setRolePermissionApi } from '@/api/modules/role'
// header
const roleTitle = ref('角色管理')
const roleHeaderButtons = [
{ label: '新增角色', type: 'primary' as const, icon: Plus, key: 'addRole' },
]
//
const roleTree = ref<any[]>([])
//
const dialogVisible = ref(false)
const dialogTitle = ref('')
const dialogType = ref('add')
//
const roleForm = ref({
name: '', //
title: '', //
permissions: [] as number[], //
})
const roleFormRef = ref<FormInstance>()
//
const roleFormRules = rules()
//
const roleFormItemOptions = ref(roleFormItem)
//
const roleFormOptionsMap = ref({
'permissions': [] as any[],
})
//id
const permissionIds = ref('')
// { : [1,2...] }
const groupedPermissions = ref<Record<string, any[]>>({})
//
const allPermissions = ref<any[]>([])
/**
@description: 新增角色
**/
const handleAddRole = () => {
console.log('新增角色');
console.log('新增角色', dialogType.value)
dialogVisible.value = true
dialogTitle.value = '新增角色'
handleDialogType(dialogType.value)
}
const handleSubmitRole = () => {
console.log('提交角色');
/**
@description: 角色数据处理
**/
// status=1
const filterRoles = (roleList: any[]) => {
const enabledRoles = roleList.filter(role => role.status === 1)
return enabledRoles
}
//
const fetchRoleList = async () => {
try {
const data = await getRoleApi()
roleTree.value = filterRoles(data)
} catch (err) {
console.error('获取角色列表失败', err)
}
}
//
onMounted(async () => {
await fetchRoleList()
})
/**
@description: 处理菜单编辑/删除/状态切换
**/
//
const handleEditRole = (row: any) => {
console.log('编辑角色', row);
dialogVisible.value = true
dialogTitle.value = '编辑角色'
dialogType.value = 'edit'
handleDialogType(dialogType.value)
roleForm.value = {
...row,
id: row.id, // id
}
const handleDeleteRole = (row: any) => {
console.log('删除角色', row);
console.log('编辑角色', dialogType.value)
}
//
const handleDeleteRole = async (row: any) => {
try {
await deleteRoleApi({ id: row.id })
await fetchRoleList()
} catch (err) {
console.error('删除角色失败')
}
}
//
const handleStatusChange = async (row: any) => {
const oldStatus = row.status === 1 ? 2 : 1
try {
// //
// await updateMenuStatusApi({
// id: row.id,
// status: row.status
// })
// ElMessage.success(`${row.status === 1 ? '' : ''}`)
// //
// await fetchMenuList()
} catch (err) {
//
row.status = oldStatus
ElMessage.error('状态修改失败')
console.error(err)
}
}
//
const handleDialogType = (type: string) => {
if (type === 'permissions') {
roleFormItemOptions.value = permissionsFormItem as any;
} else {
roleFormItemOptions.value = roleFormItem as any;
}
}
//
const getModuleCheckedState = (module: string) => {
const group = groupedPermissions.value[module]
if (!group) return false
const groupIds = group.map(perm => perm.id)
return groupIds.every(id => roleForm.value.permissions.includes(id))
}
//
const getModuleIndeterminateState = (module: string) => {
const group = groupedPermissions.value[module]
if (!group) return false
const groupIds = group.map(perm => perm.id)
const selectedCount = groupIds.filter(id => roleForm.value.permissions.includes(id)).length
return selectedCount > 0 && selectedCount < group.length
}
// /
const handleModuleCheckChange = (module: string, checked: boolean) => {
const group = groupedPermissions.value[module]
if (!group) return
const groupIds = group.map(perm => perm.id)
if (checked) {
roleForm.value.permissions = [...new Set([...roleForm.value.permissions, ...groupIds])]
} else {
roleForm.value.permissions = roleForm.value.permissions.filter(id => !groupIds.includes(id))
}
}
//
const handlePermissionChange = () => {
roleForm.value = { ...roleForm.value }
}
// module
const groupPermissionsByModule = (permissions: any[]) => {
const grouped: Record<string, any[]> = {}
permissions.forEach(perm => {
grouped[perm.module] = grouped[perm.module] || []
grouped[perm.module]!.push(perm)
})
Object.values(grouped).forEach(group => group.sort((a, b) => a.id - b.id))
return grouped
}
//
const handlePermission = async (row: any) => {
dialogVisible.value = true
dialogTitle.value = '设置角色权限'
dialogType.value = 'permissions'
handleDialogType(dialogType.value)
permissionIds.value = row.id
try {
const data = await getRolePermissionApi({ id: row.id })
allPermissions.value = data.all_permission_info
groupedPermissions.value = groupPermissionsByModule(data.all_permission_info)
roleForm.value.permissions = data.permission_ids || []
console.log('分组后的权限', groupedPermissions.value)
} catch (err) {
console.error('获取角色权限失败', err)
}
}
/**
@description: 处理弹窗提交/取消/关闭事件
**/
const handleSubmit = async () => {
console.log('提交菜单表单', roleForm.value.permissions) // ID便
try {
await roleFormRef.value?.validate()
if (dialogType.value === 'add') {
const params = {
name: roleForm.value.name,
title: roleForm.value.title,
}
await createRoleApi(params)
await fetchRoleList()
} else if (dialogType.value === 'edit') {
await editRoleApi(roleForm.value)
await fetchRoleList()
} else {
await setRolePermissionApi({
id: permissionIds.value,
permission_ids: roleForm.value.permissions.join(',')
})
await fetchRoleList()
}
dialogVisible.value = false
} catch (err) {
console.log('表单验证失败')
return
}
}
//
const handleCancel = () => {
dialogVisible.value = false
dialogType.value = 'add'
roleForm.value = {
name: '',
title: '',
permissions: [] as number[],
}
}
//
const handleClose = () => {
dialogVisible.value = false
dialogType.value = 'add'
roleForm.value = {
name: '',
title: '',
permissions: [] as number[],
}
const handleToggleStatus = (row: any) => {
console.log('切换状态状态', row);
}
</script>
<style scoped lang="scss">
:deep(.el-form-item__content) {
margin-left: 0 !important;
}
.permission-module {
margin-bottom: 20px;
border: 1px solid #e5e7eb;
border-radius: 6px;
padding: 15px;
&:last-child {
margin-bottom: 0;
}
}
.module-title {
margin: 0 0 10px 0;
font-size: 16px;
font-weight: 600;
border-bottom: 1px solid #e5e7eb;
padding-bottom: 8px;
display: flex; // flex block
}
.permission-list {
display: flex;
flex-wrap: wrap;
gap: 15px;
}
.el-checkbox-group {
label {
margin-right: 15px;
}
}
</style>

View File

@ -0,0 +1,17 @@
//定义table配置
export const roleTableColumns = [
{ prop: 'name', label: '角色名称', align: 'center' as const },
{ prop: 'title', label: '角色标题', align: 'center' as const },
{ prop: 'status', slotName: 'status', label: '状态', align: 'center' as const },
{ prop: 'createTime', slotName: 'createTime', label: '创建时间', align: 'center' as const, width: '260px' },
{ prop: 'action', slotName: 'action', label: '操作', align: 'center' as const, width: '260px' },
]
//定义roleForm配置
export const roleFormItem = [
{ prop: 'name', label: '角色名称', type: 'input' as const, placeholder: '请输入角色名称' },
{ prop: 'title', label: '角色标题', type: 'input' as const, placeholder: '请输入角色标题' },
]
export const permissionsFormItem = [
{ prop: 'permissions', label: '', type: 'slot' as const, slotName: 'permissions' },
]

View File

@ -0,0 +1,9 @@
import type{ FormRules } from 'element-plus'
export const rules = (): FormRules => {
return {
name: [{ required: true, message: '请输入角色名称', trigger: 'blur' }],
title: [{ required: true, message: '请输入角色标题', trigger: 'blur' }],
}
}