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>
|
<template>
|
||||||
<div class="system">
|
<div class="system">
|
||||||
<!-- 缺少通配搜索组件 -->
|
<!-- 菜单列表头 -->
|
||||||
|
<MenuHeader :title="menuTitle" :buttons="menuHeaderButtons" @click="handleAddMenu" />
|
||||||
<div class="header">
|
<!-- 菜单列表表格 -->
|
||||||
<h4>菜单列表</h4>
|
<MenuTable :table-data="menuTree" :columns="MenuTableColumns" row-key="id" :tree-props="treeProps">
|
||||||
<el-button type="primary" :icon="Plus" @click="handleAddMenu">新增菜单</el-button>
|
<template #icon="{ row }">
|
||||||
</div>
|
<el-icon v-if="row.icon && menuiconMap[row.icon]">
|
||||||
|
<component :is="menuiconMap[row.icon]" />
|
||||||
<div class="table-container">
|
</el-icon>
|
||||||
<el-table :data="menuTree" row-key="id" :tree-props="{ children: 'son' }" border highlight-current-row>
|
<span v-else>-</span>
|
||||||
<el-table-column prop="name" label="菜单名称" />
|
</template>
|
||||||
<el-table-column prop="path" label="路由路径" />
|
<template #type="{ row }">
|
||||||
<el-table-column prop="icon" label="图标">
|
{{ row.type === 1 ? '目录' : '按钮' }}
|
||||||
<template #default="{ row }">
|
</template>
|
||||||
<el-icon v-if="row.icon && iconMap[row.icon]">
|
<template #status="{ row }">
|
||||||
<component :is="iconMap[row.icon]" />
|
<el-tag size="small" :type="row.status === 1 ? 'success' : 'danger'">
|
||||||
</el-icon>
|
{{ row.status === 1 ? '启用' : '禁用' }}
|
||||||
<span v-else>-</span>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
<template #action="{ row }">
|
||||||
<el-table-column prop="type" label="类型">
|
<el-button link type="primary" size="small" @click="handleEditMenu(row)">
|
||||||
<template #default="{ row }">
|
编辑
|
||||||
{{ row.type === 1 ? '目录' : '按钮' }}
|
</el-button>
|
||||||
</template>
|
<el-button link type="danger" size="small" @click="handleDeleteMenu(row)">
|
||||||
</el-table-column>
|
删除
|
||||||
<!-- 新增:状态列 -->
|
</el-button>
|
||||||
<el-table-column prop="status" label="状态">
|
<el-button link size="small" :type="row.status === 1 ? 'warning' : 'success'"
|
||||||
<template #default="{ row }">
|
@click="handleToggleStatus(row)">
|
||||||
<el-tag size="small" :type="row.status === 1 ? 'success' : 'danger'">
|
{{ row.status === 1 ? '禁用' : '启用' }}
|
||||||
{{ row.status === 1 ? '启用' : '禁用' }}
|
</el-button>
|
||||||
</el-tag>
|
</template>
|
||||||
</template>
|
</MenuTable>
|
||||||
</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>
|
</div>
|
||||||
<el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增菜单' : '编辑菜单'" width="600px" destroy-on-close>
|
<!-- 菜单新增/编辑弹窗 -->
|
||||||
<el-form ref="menuFormRef" :model="menuForm" :rules="menuFormRules">
|
<MenuDialog :visible="dialogVisible" :title="dialogTitle" @confirm="handleSubmit" @cancel="handleCancel"
|
||||||
<el-form-item label="菜单名称" prop="name">
|
@close="handleClose">
|
||||||
<el-input v-model="menuForm.name" placeholder="请输入菜单名称" />
|
<MenuForm :form-data="menuForm" :form-rules="menuFormRules" :form-items="MenuFormItemOptions"
|
||||||
</el-form-item>
|
:options-map="menuFormOptionsMap" ref="menuFormRef" />
|
||||||
<el-form-item label="父级菜单" prop="parentId">
|
</MenuDialog>
|
||||||
<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>
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
<script setup lang="ts">
|
import type { FormInstance } from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import { Plus } from '@element-plus/icons-vue'
|
||||||
import { Tools, List, HomeFilled, UserFilled, Platform, BellFilled, GoodsFilled, WarningFilled, Plus } from '@element-plus/icons-vue'
|
// 组件配置
|
||||||
|
import MenuHeader from '@/components/common/Header.vue'
|
||||||
import { getMenuApi, createMenuApi, deleteMenuApi, editMenuApi } from '@/api/modules/menu'
|
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'
|
import { rules } from './rules'
|
||||||
|
|
||||||
// 定义图标映射表和选择图标选项列表
|
import { getMenuApi } from '@/api/modules/menu'
|
||||||
const iconMap: Record<string, any> = {
|
// 定义header配置
|
||||||
Tools,
|
const menuTitle = ref('菜单管理')
|
||||||
List,
|
const menuHeaderButtons = [
|
||||||
HomeFilled,
|
{ label: '新增菜单', type: 'primary' as const, icon: Plus, key: 'addMenu' },
|
||||||
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 menuTree = ref<any[]>([])
|
||||||
|
const treeProps = { children: 'son' }
|
||||||
|
|
||||||
|
// 定义弹窗配置
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogTitle = ref('')
|
||||||
|
|
||||||
// 定义菜单表单数据
|
// 定义菜单表单数据
|
||||||
const menuForm = ref({
|
const menuForm = ref({
|
||||||
name: '', // 菜单名称
|
name: '', // 菜单名称
|
||||||
parentId: '0', // 默认一级菜单
|
nameValue: '', // 默认一级菜单
|
||||||
path: '', // 路由路径
|
path: '', // 路由路径
|
||||||
api: '', // 后端路由
|
api: '', // 后端路由
|
||||||
icon: '', // 图标名称
|
icon: '', // 图标名称
|
||||||
sort: 1, // 排序(默认1)
|
sort: null, // 排序(默认1)
|
||||||
type: 1 // 默认是菜单类型
|
type: null // 默认是菜单类型
|
||||||
})
|
})
|
||||||
const menuFormRef = ref<FormInstance>()
|
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 () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getMenuApi()
|
const data = await getMenuApi()
|
||||||
menuTree.value = filterEnabledMenus(res)
|
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) {
|
} catch (err) {
|
||||||
console.error('获取菜单列表失败')
|
console.error('获取菜单列表失败')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 处理菜单状态,只保留 status=1 的菜单
|
/**
|
||||||
const filterEnabledMenus = (menuList: any[]) => {
|
@description: 处理菜单编辑/删除/状态切换
|
||||||
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) => {
|
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
|
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 () => {
|
@description: 处理弹窗提交/取消/关闭事件
|
||||||
try {
|
**/
|
||||||
await menuFormRef.value?.validate()
|
const handleSubmit = () => {
|
||||||
if (dialogType.value === 'add') {
|
console.log('提交菜单表单')
|
||||||
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 handleCancel = () => {
|
||||||
//删除菜单
|
dialogVisible.value = false
|
||||||
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 handleClose = () => {
|
||||||
|
dialogVisible.value = false
|
||||||
// 切换状态
|
|
||||||
const handleToggleStatus = async (row: any) => {
|
|
||||||
row.status = row.status === 1 ? 0 : 1
|
|
||||||
ElMessage.success(`已${row.status === 1 ? '启用' : '禁用'}`)
|
|
||||||
}
|
}
|
||||||
</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