3.10号提交
This commit is contained in:
parent
39596f3224
commit
d03552b416
|
|
@ -0,0 +1,52 @@
|
|||
<!-- src/components/CommonDialog.vue -->
|
||||
<template>
|
||||
<el-dialog :model-value="visible" :title="title" :width="width" :destroy-on-close="destroyOnClose" @close="handleClose"
|
||||
v-bind="$attrs">
|
||||
<!-- 对话框内容插槽 -->
|
||||
<slot />
|
||||
|
||||
<!-- 底部按钮插槽 -->
|
||||
<template #footer>
|
||||
<slot name="footer">
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">确定</el-button>
|
||||
</slot>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
visible: boolean
|
||||
title: string
|
||||
width?: string | number
|
||||
destroyOnClose?: boolean
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'confirm'): void
|
||||
(e: 'cancel'): void
|
||||
(e: 'close'): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
width: '400px',
|
||||
destroyOnClose: true,
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const handleConfirm = () => {
|
||||
emit('confirm')
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false)
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-bind="$attrs">
|
||||
<!-- 动态表单项 -->
|
||||
<template v-for="(item, index) in formItems" :key="index">
|
||||
<el-form-item :label="item.label" :prop="item.prop" :required="item.required">
|
||||
<!-- 输入框 -->
|
||||
<el-input v-if="item.type === 'input'" v-model="formData[item.prop]" :placeholder="item.placeholder"
|
||||
:disabled="item.disabled" clearable />
|
||||
|
||||
<!-- 下拉选择框 -->
|
||||
<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">
|
||||
<template v-if="option.icon" #label>
|
||||
<span style="display: flex; align-items: center; gap: 8px;">
|
||||
<el-icon v-if="option.icon">
|
||||
<component :is="option.icon" />
|
||||
</el-icon>
|
||||
<span>{{ option.label }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<!-- 数字输入框 -->
|
||||
<el-input-number v-else-if="item.type === 'number'" v-model="formData[item.prop]" :min="item.min"
|
||||
:max="item.max" :placeholder="item.placeholder" :disabled="item.disabled" style="width: 100%" />
|
||||
|
||||
<!-- 单选框组 -->
|
||||
<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">
|
||||
{{ option.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
|
||||
<!-- 自定义插槽 -->
|
||||
<slot v-else :name="item.slotName" :formData="formData" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { Component } from 'vue'
|
||||
|
||||
interface FormItem {
|
||||
type: 'input' | 'select' | 'number' | 'radio' | 'slot'
|
||||
label: string
|
||||
prop: string
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
disabled?: boolean
|
||||
min?: number
|
||||
max?: number
|
||||
slotName?: string // 自定义插槽名称
|
||||
}
|
||||
|
||||
interface Props {
|
||||
formData: Record<string, any>
|
||||
formRules: FormRules
|
||||
formItems: FormItem[]
|
||||
optionsMap: Record<string, Array<{
|
||||
label: string | undefined
|
||||
value: string | number
|
||||
icon?: Component
|
||||
}>>
|
||||
}
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// 暴露表单验证方法
|
||||
defineExpose({
|
||||
validate: () => formRef.value?.validate(),
|
||||
resetFields: () => formRef.value?.resetFields()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<!-- src/components/CommonTableHeader.vue -->
|
||||
<template>
|
||||
<div class="header">
|
||||
<h4>{{ title }}</h4>
|
||||
<div class="header-actions">
|
||||
<el-button v-for="(btn, index) in buttons" :key="btn.key || index" :type="btn.type || 'default'"
|
||||
:icon="btn.icon" @click="handleButtonClick(btn)" :disabled="btn.disabled">
|
||||
{{ btn.label }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ButtonProps } from 'element-plus'
|
||||
import type { Component } from 'vue'
|
||||
|
||||
// 定义按钮接口
|
||||
interface HeaderButton {
|
||||
label: string
|
||||
type?: ButtonProps['type']
|
||||
icon?: Component
|
||||
disabled?: boolean
|
||||
key?: string | number // 可选的唯一标识
|
||||
}
|
||||
|
||||
// 定义组件属性接口
|
||||
interface Props {
|
||||
title: string
|
||||
buttons: HeaderButton[]
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
// 定义自定义事件,向外暴露按钮点击事件
|
||||
const emit = defineEmits<{
|
||||
// 事件名:button-click,参数为按钮对象
|
||||
'button-click': [button: HeaderButton]
|
||||
}>()
|
||||
|
||||
// 统一处理按钮点击
|
||||
const handleButtonClick = (button: HeaderButton) => {
|
||||
// 触发自定义事件,把点击的按钮对象传出去
|
||||
emit('button-click', button)
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<div class="table-container">
|
||||
<el-table :data="tableData" :row-key="rowKey" :tree-props="treeProps" :border="border" :highlight-current-row="highlightCurrentRow"
|
||||
v-bind="$attrs">
|
||||
<!-- 动态表头列 -->
|
||||
<template v-for="(column, index) in columns" :key="index">
|
||||
<el-table-column v-if="!column.slotName" :prop="column.prop" :label="column.label" :width="column.width"
|
||||
:align="column.align || 'left'" />
|
||||
|
||||
<!-- 具名插槽列 -->
|
||||
<el-table-column v-else :label="column.label" :width="column.width" :align="column.align || 'left'">
|
||||
<template #default="{ row }">
|
||||
<slot :name="column.slotName" :row="row" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 定义表格列配置接口
|
||||
interface TableColumn {
|
||||
prop?: string
|
||||
label: string
|
||||
width?: string | number
|
||||
align?: 'left' | 'center' | 'right'
|
||||
slotName?: string // 具名插槽名称
|
||||
}
|
||||
|
||||
// 定义表格组件属性接口
|
||||
interface Props {
|
||||
tableData: any[]
|
||||
rowKey?: string
|
||||
treeProps?: {
|
||||
children: string
|
||||
}
|
||||
columns?: TableColumn[],
|
||||
border?: boolean // 是否显示边框
|
||||
highlightCurrentRow?: boolean // 是否高亮当前行
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
border: true,
|
||||
highlightCurrentRow: true,
|
||||
})
|
||||
</script>
|
||||
|
|
@ -1,256 +1,163 @@
|
|||
<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>
|
||||
<!-- 菜单列表头 -->
|
||||
<MenuHeader :title="menuTitle" :buttons="menuHeaderButtons" @click="handleAddMenu" />
|
||||
<!-- 菜单列表表格 -->
|
||||
<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]" />
|
||||
</el-icon>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #type="{ row }">
|
||||
{{ row.type === 1 ? '目录' : '按钮' }}
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-tag size="small" :type="row.status === 1 ? 'success' : 'danger'">
|
||||
{{ row.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #action="{ 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>
|
||||
</MenuTable>
|
||||
</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>
|
||||
<!-- 菜单新增/编辑弹窗 -->
|
||||
<MenuDialog :visible="dialogVisible" :title="dialogTitle" @confirm="handleSubmit" @cancel="handleCancel"
|
||||
@close="handleClose">
|
||||
<MenuForm :form-data="menuForm" :form-rules="menuFormRules" :form-items="MenuFormItemOptions"
|
||||
:options-map="menuFormOptionsMap" ref="menuFormRef" />
|
||||
</MenuDialog>
|
||||
</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'
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
// 组件配置
|
||||
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 { 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 },
|
||||
import { getMenuApi } from '@/api/modules/menu'
|
||||
// 定义header配置
|
||||
const menuTitle = ref('菜单管理')
|
||||
const menuHeaderButtons = [
|
||||
{ label: '新增菜单', type: 'primary' as const, icon: Plus, key: 'addMenu' },
|
||||
]
|
||||
|
||||
//menu数据
|
||||
// 定义表格配置
|
||||
const menuTree = ref<any[]>([])
|
||||
const treeProps = { children: 'son' }
|
||||
|
||||
// 定义弹窗配置
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
|
||||
// 定义菜单表单数据
|
||||
const menuForm = ref({
|
||||
name: '', // 菜单名称
|
||||
parentId: '0', // 默认一级菜单
|
||||
nameValue: '', // 默认一级菜单
|
||||
path: '', // 路由路径
|
||||
api: '', // 后端路由
|
||||
icon: '', // 图标名称
|
||||
sort: 1, // 排序(默认1)
|
||||
type: 1 // 默认是菜单类型
|
||||
sort: null, // 排序(默认1)
|
||||
type: null // 默认是菜单类型
|
||||
})
|
||||
const menuFormRef = ref<FormInstance>()
|
||||
// 核心:表单验证规则
|
||||
const menuFormRules = rules(menuForm.value, iconOptions)
|
||||
// 表单验证规则
|
||||
const menuFormRules = rules(menuForm.value, MenuiconOptions)
|
||||
// 菜单表单选项映射
|
||||
const menuFormOptionsMap = ref({
|
||||
'parentName': [
|
||||
{ label: '一级菜单', value: '1' },
|
||||
],
|
||||
'icon': MenuiconOptions,
|
||||
'type': [
|
||||
{ label: '菜单', value: 1 },
|
||||
{ label: '按钮', value: 2 }
|
||||
]
|
||||
})
|
||||
|
||||
// 定义弹窗显示状态
|
||||
const dialogVisible = ref(false)
|
||||
// 新增:区分新增/编辑模式
|
||||
const dialogType = ref<'add' | 'edit'>('add')
|
||||
// 编辑菜单ID
|
||||
const currentEditId = ref(null)
|
||||
|
||||
//所有的方法
|
||||
|
||||
/**
|
||||
@description: 新增菜单
|
||||
**/
|
||||
const handleAddMenu = () => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = '新增菜单'
|
||||
console.log('新增菜单')
|
||||
}
|
||||
|
||||
/**
|
||||
@description: 菜单数据处理
|
||||
**/
|
||||
// 处理菜单状态,只保留 status=1 的菜单
|
||||
const filterMenus = (menuList: any[]) => {
|
||||
const enabledMenus = menuList.filter(menu => menu.status === 1)
|
||||
enabledMenus.forEach(menu => {
|
||||
if (menu.son && menu.son.length) {
|
||||
menu.son = filterMenus(menu.son)
|
||||
}
|
||||
})
|
||||
return enabledMenus
|
||||
}
|
||||
// 获取菜单列表
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const res = await getMenuApi()
|
||||
menuTree.value = filterEnabledMenus(res)
|
||||
const data = await getMenuApi()
|
||||
menuTree.value = filterMenus(data)
|
||||
// 处理parentName选项,添加一级菜单选项
|
||||
const parentNameOptions = data.map(item => ({
|
||||
label: item.name,
|
||||
value: item.id ?? ''
|
||||
}))
|
||||
menuFormOptionsMap.value.parentName = [
|
||||
{ label: '一级菜单', value: '1' },
|
||||
...parentNameOptions
|
||||
]
|
||||
} 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);
|
||||
}
|
||||
|
||||
//打开编辑弹窗
|
||||
/**
|
||||
@description: 处理菜单编辑/删除/状态切换
|
||||
**/
|
||||
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
|
||||
dialogTitle.value = '编辑菜单'
|
||||
console.log('编辑菜单', row)
|
||||
}
|
||||
const handleDeleteMenu = (row: any) => {
|
||||
console.log('删除菜单', row)
|
||||
}
|
||||
const handleToggleStatus = (row: any) => {
|
||||
console.log('切换菜单状态', row)
|
||||
}
|
||||
|
||||
// 提交表单(新增/编辑)
|
||||
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)
|
||||
}
|
||||
/**
|
||||
@description: 处理弹窗提交/取消/关闭事件
|
||||
**/
|
||||
const handleSubmit = () => {
|
||||
console.log('提交菜单表单')
|
||||
}
|
||||
|
||||
//删除菜单
|
||||
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 handleCancel = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
|
||||
// 切换状态
|
||||
const handleToggleStatus = async (row: any) => {
|
||||
row.status = row.status === 1 ? 0 : 1
|
||||
ElMessage.success(`已${row.status === 1 ? '启用' : '禁用'}`)
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,256 @@
|
|||
<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>
|
||||
Loading…
Reference in New Issue