| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495 |
- <!-- 模板区域 -->
- <template>
- <div>
- <PageMain class="vh100">
- <el-form :inline="true" :model="formInline" class="demo-form-inline">
- <el-form-item label="菜单名称">
- <el-input v-model="formInline.user" placeholder="请输入菜单名称" clearable />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="Menutree">
- <template #icon>
- <SvgIcon name="i-ep:search" />
- </template>
- 查询</el-button>
- </el-form-item>
- </el-form>
- <el-button type="primary" @click="menuAdd">
- <template #icon>
- <el-icon>
- <Plus />
- </el-icon>
- </template>
- 添加菜单</el-button>
- <div :style="{ height: tableHeight, marginTop: '20px' }">
- <el-auto-resizer>
- <template #default="{ height, width }">
- <el-table-v2 :columns="columns" :data="tableData" :width="width" :height="height" v-loading="loading"
- ref="multipleTableRef" :expand-column-key="expandColumnKey" header-class="header-class">
- <template #cell="scope">
- <template v-if="scope.column.key == 'option'" class="flex j-s a-c">
- <el-button type="primary" link @click="menuAddSub(scope.rowData)"> 添加子菜单
- </el-button>
- <el-dropdown placement="bottom-start">
- <span class="el-dropdown-link">
- 更 多
- </span>
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item @click="menuEdit(scope.rowData)">编辑</el-dropdown-item>
- <el-dropdown-item @click="menuDel(scope.rowData)">删除</el-dropdown-item>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- </template>
- <template v-if="scope.column.key == 'type'">
- <span>{{typelist.find(item => item.value == scope.rowData.type)?.label || ''}}</span>
- </template>
- <template v-if="scope.column.key == 'icon'">
- <SvgIcon :name="scope.rowData.icon || ''" />
- </template>
- <template v-if="scope.column.key == 'menu'">
- <el-tag v-if="scope.rowData.menu == 1" type="primary">显示</el-tag>
- <el-tag v-if="scope.rowData.menu == 0" type="danger">隐藏</el-tag>
- </template>
- </template>
- </el-table-v2>
- </template>
- </el-auto-resizer>
- </div>
- </PageMain>
- <!-- 添加菜单弹窗 -->
- <el-drawer v-model="menuAddShow" direction="rtl" size="800px">
- <template #header>
- <h4>菜单配置</h4>
- </template>
- <template #default>
- <el-form :model="menuAddForm" :rules="menuAddRules" ref="menuAddFormRef" label-width="120px">
- <el-form-item label="菜单类型" prop="type">
- <el-radio-group v-model="menuAddForm.type" fill="#409eff">
- <el-radio-button v-for="item in typelist" :label="item.label" :value="item.value"
- :key="item.value"></el-radio-button>
- </el-radio-group>
- </el-form-item>
- <el-form-item :label="`${typeLabel}名称`" prop="title">
- <el-input v-model="menuAddForm.title" :placeholder="`请输入${typeLabel}名称`" clearable />
- </el-form-item>
- <el-form-item label="上级菜单" prop="parentId" v-if="menuAddForm.type != 0">
- <el-tree-select v-model="menuAddForm.parentId" :data="tableData" node-key="id" :props="defaultProps"
- check-strictly :render-after-expand="false" />
- </el-form-item>
- <el-form-item label="路由标识" prop="name" v-if="menuAddForm.type == 1">
- <el-input v-model="menuAddForm.name" placeholder="请输入路由标识" clearable />
- </el-form-item>
- <el-form-item label="是否缓存页面" prop="keepAlive" v-if="menuAddForm.type == 1">
- <el-switch v-model="menuAddForm.keepAlive" active-value="1" inactive-value="0" />
- </el-form-item>
- <el-form-item label="访问路径" prop="path" v-if="menuAddForm.type == 1">
- <el-input v-model="menuAddForm.path" placeholder="请输入访问路径" clearable
- @blur="menuAddForm.name = generateRouteName(menuAddForm.path)" />
- </el-form-item>
- <el-form-item label="组件" prop="component" v-if="menuAddForm.type == 1">
- <el-input v-model="menuAddForm.component" placeholder="请输入组件" clearable />
- </el-form-item>
- <el-form-item label="菜单图标" v-if="menuAddForm.type != 2">
- <el-input v-model="menuAddForm.icon" placeholder="请选择菜单图标" clearable >
- <template #append>
- <el-button :icon="Search" @click="iconVisible = true" />
- </template>
- </el-input>
- </el-form-item>
- <el-form-item label="按钮权限标识" prop="perms" v-if="menuAddForm.type == 2">
- <el-input v-model="menuAddForm.perms" placeholder="请输入按钮权限标识" clearable />
- </el-form-item>
- <el-form-item label="菜单高亮地址" v-if="menu">
- <el-input v-model="menuAddForm.activeMenu" placeholder="请输入菜单高亮地址" clearable />
- </el-form-item>
- <el-form-item label="隐藏路由" prop="menu" v-if="menuAddForm.type != 2">
- <el-switch v-model="menu" @change="handleMenuChange" />
- </el-form-item>
- </el-form>
- </template>
- <template #footer>
- <div style="flex: auto;">
- <el-button @click="menuAddShow = false">取消</el-button>
- <el-button type="primary" @click="confirmClick(menuAddFormRef)">确认</el-button>
- </div>
- </template>
- </el-drawer>
- <!-- 图标弹窗 -->
- <el-dialog v-model="iconVisible" title="选择图标" width="1200px" height="800px " top="8vh">
- <div>
- <!-- 图标搜索框 -->
- <el-input v-model="iconSearchQuery" placeholder="搜索图标名称" class="mb-4" clearable @input="currentPage = 1">
- <template #prefix>
- <el-icon>
- <Search />
- </el-icon>
- </template>
- </el-input>
- <el-tabs v-model="activeName" class="demo-tabs" @tab-change="currentPage = 1">
- <el-tab-pane :label="icon.info.name + '图标'" :name="icon.prefix" v-for="(icon, key) in icons" :key="key">
- <!-- 计算当前标签页的过滤和分页后的图标 -->
- <div v-if="getFilteredIcons(icon).length === 0" class="text-center py-10 text-gray-500">
- 未找到匹配的图标
- </div>
- <div v-else>
- <div class="icon-container flex f-wrap">
- <div class="icon-item flex a-c j-c " v-for="(item, index) in getPaginatedIcons(getFilteredIcons(icon))"
- :key="index" :class="{ 'icon-selected': selectedIcon === `${icon.prefix}:${item}` }"
- @click="selectedIcon = `${icon.prefix}:${item}`">
- <SvgIcon :name="`${icon.prefix}:${item}`" class="icon" />
- </div>
- </div>
- <!-- 分页组件 -->
- <div class="flex justify-center mt-4">
- <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize"
- :page-sizes="[20, 50, 100]" layout="total, sizes, prev, pager, next, jumper"
- :total="getFilteredIcons(icon).length" @size-change="currentPage = 1" />
- </div>
- </div>
- </el-tab-pane>
- </el-tabs>
- </div>
- <template #footer>
- <div class="dialog-footer">
- <el-button @click="iconVisible = false">取消</el-button>
- <el-button type="primary" @click="confirmSelectIcon">
- 确定
- </el-button>
- </div>
- </template>
- </el-dialog>
- </div>
- </template>
- <!-- 行为区域 -->
- <script lang='ts' setup>
- import { ref, reactive, computed } from 'vue'
- import { FormRules, FormInstance, ComponentSize, ElMessageBox } from 'element-plus';
- import api from '@/api'
- import { ElTag, ElMessage } from 'element-plus';
- import { icons } from '@/iconify'
- import { Search, Plus } from '@element-plus/icons-vue'
- interface Column {
- key?: string,
- dataKey?: string,
- title: string,
- width: number,
- class?: string,
- cellRenderer?: any
- align?: "left" | "center" | "right"; // align 是可选的,且只能是这三个值之一
- }
- const defaultProps = {
- children: 'children',
- label: 'title',
- }
- onMounted(() => {
- setTimeout(() => {
- const tableElement = multipleTableRef.value?.$el as HTMLElement;
- if (tableElement) {
- const offsetTop = tableElement.offsetTop; // 获取 offsetTop
- let height = window.innerHeight;
- tableHeight.value = height - offsetTop - 190 + "px";
- }
- }, 10);
- })
- onBeforeMount(() => {
- Menutree()//获取菜单树
- })
- const menuAddFormRef = ref<FormInstance>()
- const multipleTableRef = ref();//多个表格引用
- const iconVisible = ref(false);//图标弹窗显示
- const menuAddShow = ref(false);//添加菜单弹窗显示
- const activeName = ref('ep');//当前选中的tab
- const selectedIcon = ref('');//当前选中的图标
- const iconSearchQuery = ref('');//图标搜索关键词
- const currentPage = ref(1);//当前页码
- const pageSize = ref(50);//每页显示图标数量
- const tableHeight = ref(''); // 初始化表格高度
- const loading = ref(false)
- const expandColumnKey = 'title'//标记可展开列
- const menu = ref(false);
- // 定义查询表单数据
- const formInline = reactive({
- user: '',
- });
- // 定义添加菜单表单数据
- const menuAddForm = reactive({
- path: '',//路径
- keepAlive: '0',//是否缓存页面
- component: '',//组件
- parentId: '',//上级菜单
- name: '',//菜单名称
- title: '',//菜单标题
- icon: '',//图标
- type: 0,//菜单类型
- activeMenu: '',//高亮菜单路由地址
- perms: '',//按钮权限标识
- menu: 1,
- });
- // 定义添加菜单表单验证规则
- const menuAddRules = reactive({
- path: [{ required: true, message: '请输入路径', trigger: 'blur' }],
- component: [{ required: true, message: '请输入组件', trigger: 'blur' }],
- redirect: [{ required: false, message: '请输入重定向路径', trigger: 'blur' }],
- name: [{ required: true, message: '路由标识必填', trigger: 'blur' }],
- title: [{ required: true, message: '请输入菜单标题', trigger: 'blur' }],
- perms: [{ required: true, message: '请输入按钮权限标识', trigger: 'blur' }],
- type: [{ required: true, message: '请选择菜单类型', trigger: 'change' }],
- parentId: [{ required: true, message: '请选择上级菜单', trigger: 'change' }],
- });
- const columns: Column[] = [
- { key: 'title', dataKey: "title", title: "菜单名称", width: 200, },
- { key: 'type', dataKey: "type", title: "菜单类型", width: 100, align: "center", },
- { key: 'icon', dataKey: "icon", title: "图标", width: 100, align: "center" },
- { key: 'component', dataKey: "component", title: "组件", width: 300, align: "center" },
- { key: 'path', dataKey: "path", title: "页面路径", width: 300, align: "center" },
- {
- key: 'menu', dataKey: "menu", title: "路由是否隐藏", width: 150, align: "center",
- },
- {
- key: 'option',
- title: "操作",
- width: 250,
- align: 'center',
- },
- ]
- const tableData = ref([]);//菜单列表
- const typeLabel = ref('');//菜单类型标签
- const typelist = [
- { label: '目录', value: 0 },
- { label: '菜单', value: 1 },
- { label: '按钮', value: 2 },
- ]
- // 根据path生成驼峰命名的路由标识
- const generateRouteName = (path: string): string => {
- if (!path || path === '/') return ''
- // 移除开头的斜杠和所有非字母数字字符
- const cleanPath = path.replace(/^\/+/, '').replace(/[^a-zA-Z0-9_\-\/]/g, '')
- // 按斜杠分割并转换为驼峰命名
- return cleanPath.split('/')
- .map(segment => {
- // 处理下划线或连字符分隔的单词
- return segment.split(/[-_]/)
- .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
- .join('')
- })
- .join('')
- }
- // 处理菜单隐藏路由切换
- const handleMenuChange = (val: any) => {
- console.log(val)
- if (val) {
- menuAddForm.menu = 0;
- } else {
- menuAddForm.menu = 1;
- }
- }
- // 菜单添加子菜单
- const menuAddSub = (item: any) => {
- menuAddForm.parentId = item.id;
- menuAddForm.type = item.type;
- menuAddShow.value = true;
- }
- //编辑菜单
- const menuEdit = (item: any) => {
- console.log(item)
- const info = item;
- Object.assign(menuAddForm, info)
- console.log(menuAddForm)
- if (menuAddForm.menu == 1) {
- menu.value = false
- } else {
- menu.value = true
- }
- menuAddShow.value = true
- }
- // 删除菜单
- const menuDel = (item: any) => {
- ElMessageBox.confirm('确定删除该菜单吗?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- }).then(() => {
- api.menuApi.deleteMenu(item.id).then((res: any) => {
- if (res.code === 200) {
- ElMessage({
- type: 'success',
- message: '删除成功',
- plain: true,
- })
- Menutree()//获取菜单树
- }
- })
- }).catch(() => {
- ElMessage({
- type: 'info',
- message: '已取消删除',
- plain: true,
- })
- })
- }
- // 过滤图标
- const getFilteredIcons = (icon: any) => {
- if (!iconSearchQuery.value) {
- return icon.icons;
- }
- const query = iconSearchQuery.value.toLowerCase();
- return icon.icons.filter((item: string) =>
- item.toLowerCase().includes(query)
- );
- }
- // 分页处理
- const getPaginatedIcons = (icons: string[]) => {
- const start = (currentPage.value - 1) * pageSize.value;
- const end = start + pageSize.value;
- return icons.slice(start, end);
- }
- // 确认选择图标
- const confirmSelectIcon = () => {
- if (selectedIcon.value) {
- menuAddForm.icon = selectedIcon.value;
- }
- iconVisible.value = false;
- }
- // 菜单类型改变时处理
- // watch(
- // () => menuAddForm.type,
- // (newType) => {
- // console.log(newType);
- // typeLabel.value = typelist.find(item => item.value === newType)?.label || '';
- // switch (newType) {
- // case 0:
- // menuAddForm.component = '';
- // break;
- // case 1:
- // menuAddForm.component = '';
- // break;
- // case 2:
- // menuAddForm.component = '';
- // break;
- // default:
- // }
- // },
- // { immediate: true } // 初始化时立即执行一次
- // )
- // 添加菜单
- const menuAdd = () => {
- menuAddShow.value = true;
- }
- // 获取菜单树
- const Menutree = () => {
- loading.value = true;
- api.menuApi.findMenuTree().then((res: any) => {
- if (res.code === 200) {
- tableData.value = res.data
- loading.value = false;
- }
- })
- }
- // 确认添加菜单
- const confirmClick = async (formEl: FormInstance | undefined) => {
- if (!formEl) return
- await formEl.validate((valid) => {
- if (valid) {
- // 提交表单数据
- api.menuApi.addMenu(menuAddForm).then((res: any) => {
- if (res.code === 200) {
- ElMessage({
- type: 'success',
- message: '添加成功',
- plain: true,
- })
- menuAddShow.value = false;
- Menutree();//刷新菜单树
- }
- })
- } else {
- ElMessage({
- type: 'error',
- message: '信息不完整',
- plain: true,
- })
- }
- })
- }
- </script>
- <!-- 样式区域 -->
- <style lang='scss' scoped>
- :deep(.header-class .el-table-v2__header-cell) {
- padding: 10px 20px;
- font-size: 15px;
- color: #333;
- background-color: #eef1f6;
- border-bottom: 1px solid #eef1f6;
- }
- :deep(.el-table-v2__header-row) {
- justify-content: space-between;
- background-color: #eef1f6;
- }
- :deep(.el-table-v2__row) {
- justify-content: space-between;
- }
- :deep(.el-table-v2__expand-icon) {
- margin: 0;
- }
- :deep(.el-table-v2__cell-text) {
- margin-left: 10px;
- }
- :deep(.el-dropdown-link) {
- display: flex;
- align-items: center;
- margin-left: 15px;
- color: var(--el-color-primary);
- cursor: pointer;
- }
- .disabled :deep(.el-upload--picture-card) {
- opacity: 0;
- }
- .icon-container {
- max-height: 400px;
- padding: 10px;
- overflow-y: auto;
- }
- .icon-item {
- width: 60px;
- height: 60px;
- margin: 8px;
- cursor: pointer;
- border: 2px solid transparent;
- border-radius: 4px;
- transition: all 0.3s;
- }
- .icon-selected {
- background-color: rgb(64 158 255 / 10%);
- border-color: #409eff;
- }
- .icon {
- font-size: 24px;
- }
- </style>
|