菜单和角色
This commit is contained in:
parent
7e8d963095
commit
db9b98af8e
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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: '请输入路由路径' },
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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' },
|
||||
]
|
||||
|
|
@ -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' }],
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue