高效菜单管理页面:一键增删改查

发布于:2025-09-05 ⋅ 阅读:(23) ⋅ 点赞:(0)
<template>
  <div class="app-container">
    <el-card>
      <!-- 搜索和操作区域 -->
      <div class="filter-container">
        <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleCreate">新增菜单</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="getMenuList">刷新</el-button>
      </div>

      <!-- 菜单表格 -->
      <el-table
        height="500"
        :data="menuList"
        row-key="id"
        border
        stripe
        default-expand-all
        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
        v-loading="loading">
        <el-table-column prop="menuTitle" label="菜单名称" width="200" fixed="left" />
        <el-table-column prop="menuName" label="路由名称" width="150" />
        <el-table-column prop="menuPath" label="路由路径" width="150" />
        <el-table-column prop="component" label="组件路径" width="200" />
         <el-table-column prop="perm" label="权限标识" width="150" />
        <el-table-column prop="redirect" label="重定向路径" width="200" />
        <el-table-column label="图标" width="100">
          <template slot-scope="scope">
            <i :class="scope.row.iconClass" v-if="scope.row.iconClass"></i>
            <span v-else>/</span>
          </template>
        </el-table-column>
        <el-table-column prop="orderNum" label="排序" width="80" align="center" />
        <el-table-column label="是否可见" width="100" align="center">
          <template slot-scope="scope">
            <el-tag :type="scope.row.visible ? 'success' : 'danger'">
              {{ scope.row.visible ? '是' : '否' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200" fixed="right">
          <template slot-scope="scope">
            <el-button
              type="text"
              icon="el-icon-edit"
              @click="handleEdit(scope.row)" >编辑</el-button>
            <el-button
              type="text"
              icon="el-icon-delete"
              @click="handleDelete(scope.row)" >删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-card>

    <!-- 新增/编辑对话框 -->
    <el-dialog
      :title="dialogTitle"
      :visible.sync="dialogVisible"
      width="600px"
      @close="resetForm">
      <el-form
        ref="menuForm"
        :model="form"
        :rules="rules"
        label-width="100px"
        label-position="right">
        <el-form-item label="父级菜单" prop="parentId">
          <el-select
            filterable
            v-model="form.parentId"
            placeholder="选择父级菜单"
            style="width: 100%" >
            <el-option
              v-for="item in menuTreeOptions"
              :key="item.id"
              :value="item.id"
              :label="item.menuTitle"/>
          </el-select>
        </el-form-item>

        <el-form-item label="菜单类型" prop="types">
          <el-radio-group v-model="form.types">
            <el-radio :label="0">目录</el-radio>
            <el-radio :label="1">菜单</el-radio>
            <el-radio :label="2">按钮</el-radio>
          </el-radio-group>
        </el-form-item>

        <el-form-item label="菜单名称" prop="menuTitle">
          <el-input v-model="form.menuTitle" placeholder="请输入菜单名称" />
          <div class="form-tip">显示在侧边栏的名称</div>
        </el-form-item>

        <el-form-item label="路由名称" prop="menuName">
          <el-input v-model="form.menuName" placeholder="请输入路由名称" />
          <div class="form-tip">菜单的唯一标识名</div>
        </el-form-item>

        <el-form-item label="路由路径" prop="menuPath">
          <el-input v-model="form.menuPath" placeholder="请输入路由路径" />
        </el-form-item>

        <el-form-item
          label="组件路径"
          prop="component"
          v-if="form.types !== 2">
          <el-input v-model="form.component" placeholder="请输入组件路径" />
          <div class="form-tip">例如:@/views/system/user/index</div>
        </el-form-item>

        <el-form-item
          label="重定向路径">
          <el-input v-model="form.redirect" placeholder="请输入重定向路径" />
          <div class="form-tip">例如:/user/index</div>
        </el-form-item>

        <el-form-item label="权限标识">
          <el-input v-model="form.perm" placeholder="请输入权限标识"  />
        </el-form-item>

         <el-form-item label="图标" prop="iconClass">
          <el-input v-model="form.iconClass" placeholder="请输入图标类名">
            <template slot="prepend">
              <i :class="form.iconClass" v-if="form.iconClass"></i>
              <i class="el-icon-picture" v-else></i>
            </template>
          </el-input>
          <div class="form-tip">Element UI图标类名,如:el-icon-user</div>
        </el-form-item>

        <el-form-item label="排序" prop="orderNum">
          <el-input-number v-model="form.orderNum" :min="0" :max="10000" />
        </el-form-item>

        <el-form-item label="是否可见" prop="visible">
          <el-switch v-model="form.visible" :active-value="1" :inactive-value="0" />
        </el-form-item>
      </el-form>

      <span slot="footer" class="dialog-footer">
        <el-button type="primary" @click="handleSubmit">确定</el-button>
        <el-button @click="dialogVisible = false">取消</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import { reqMenuList, addMenu, updateMenu, deleteMenu } from '@/api/menu'
import { generatorTree, buildTreeOptions } from '@/utils/myUtils.js'

export default {
  name: 'MenuManagement',
  data() {
    return {
      loading: false,
      menuList: [],
      menuTreeOptions: [],
      dialogVisible: false,
      dialogTitle: '新增菜单',
      form: {
        id: null,
        parentId: 0,
        types: 1,
        menuTitle: '',
        menuName: '',
        menuPath: '',
        component: '',
        perm: '',
        iconClass: '',
        orderNum: 0,
        visible: 1,
        redirect: '',
      },
      rules: {
        menuTitle: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
        menuName: [{ required: true, message: '请输入路由名称', trigger: 'blur' }],
        menuPath: [{ required: true, message: '请输入路由路径', trigger: 'blur' }],
        component: [{ required: true, message: '请输入组件路径', trigger: 'blur' }]
      }
    }
  },
  mounted() {
    this.getMenuList()
  },
  methods: {
    // 获取菜单列表
    async getMenuList() {
      this.loading = true
      try {
        const res = await reqMenuList()  // 获取菜单
        this.menuList = generatorTree(res.data, 0)   // 菜单列表
        this.menuTreeOptions = buildTreeOptions(res.data)  
      } catch (error) {
        console.error('获取菜单列表失败:', error)
      } finally {
        this.loading = false
      }
    },

    // 新增菜单
    handleCreate() {
      this.dialogTitle = '新增菜单'
      this.dialogVisible = true
    },

    // 编辑菜单
    handleEdit(row) {
      this.dialogTitle = '编辑菜单'
      this.form = { ...row }
      this.dialogVisible = true
    },

    // 删除菜单
    handleDelete(row) {
      this.$confirm(`确定删除菜单 "${row.menuTitle}" 吗?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async() => {
          const {code, message} = await deleteMenu(row.id)
          if (code == 200) {
            this.$message.success(message)
            this.getMenuList()
          } else {
            this.$message.error(message)
          }
      })
    },


    // 提交表单
    handleSubmit() {
      this.$refs.menuForm.validate(async(valid) => {
        if (valid) {
          if (this.form.id) {
            const { code, message } = await updateMenu(this.form)
             if (code == 200) {
              this.$message.success(message)
            } else {
              this.$message.error(message)
            }
          } else {
            const { code, message } = await addMenu(this.form)
             if (code == 200) {
              this.$message.success(message)
            } else {
              this.$message.error(message)
            }
          }
          this.dialogVisible = false
          this.getMenuList()
        }
      })
    },

    // 重置表单
    resetForm() {
      this.$refs.menuForm.resetFields()
      this.form = {
        id: null,
        parentId: 0,
        types: 1,
        menuTitle: '',
        menuName: '',
        menuPath: '',
        component: '',
        iconClass: '',
        perm: '',
        orderNum: 0,
        visible: 1,
        redirect: '',
      }
    }
  }
}
</script>

<style scoped>
.filter-container {
  margin-bottom: 20px;
}

.form-tip {
  font-size: 12px;
  color: #909399;
  margin-top: 4px;
}

.app-container {
  padding: 20px;
}
</style>

工具类,生成树形结构及筛选出父级目录


// 筛选出父级目录
export function  buildTreeOptions(data) {
  const options = [{ id: 0, menuTitle: '根目录' }]
  
  data.forEach(item => {
    if (item.types === 0 || item.types == 1) { // 只显示目录类型或菜单类型
      options.push({ id: item.id, menuTitle: item.menuTitle})
    }
  })
  return options
}

// 扁平化变成结构数据
export function generatorTree(list, rootValue) {
  const arr = []
  list.forEach(item => {
    if (item.parentId == rootValue) {
      // 找到了匹配的节点
      arr.push(item)
      // 当前节点的id 和 当前节点的子节点的pid是想等的
      const children = generatorTree(list, item.id) // 找到的节点的子节点
      if (children.length) { item.children = children } // 将子节点赋值给当前节点
    }
  })
  return arr
}

前端访问后端接口


// 获取菜单列表
export function reqMenuList() {
  return request({
    url: '/menu/getDataList',
    method: 'get'
  })
}

// 新增菜单
export function addMenu(data) {
  return request({
    url: '/menu/addMenu',
    method: 'post',
    data
  })
}

// 更新菜单
export function updateMenu(data) {
  return request({
    url: '/menu/updateMenu',
    method: 'put',
    data
  })
}

// 删除菜单
export function deleteMenu(id) {
  return request({
    url: `/menu/deleteMenuById/${id}`,
    method: 'delete'
  })
}

后端接口

 // 获取菜单和操作权限
    @GetMapping("/getDataList")
    public ResultBean selectMenuList() {
        return ResultBean.success("获取成功", menuService.selectMenuList());
    }

// 添加菜单
    @PostMapping("/addMenu")
    @ResponseBody
    public ResultBean addMenu(@RequestBody Menu menu) {
        menuService.addMenu(menu);
        return ResultBean.success("添加成功");
    }

    // 根据id删除菜单
    @DeleteMapping("/deleteMenuById/{id}")
    public ResultBean deleteMenuById(@PathVariable Integer id) {
        menuService.deleteMenuById(id);
        return ResultBean.success("删除成功");
    }

    // 根据id修改菜单
    @PutMapping("/updateMenu")
    public ResultBean updateMenu(@RequestBody Menu menu) {
        menuService.updateMenu(menu);
        return ResultBean.success("修改成功");
    }

业务逻辑

 // 添加菜单
    @Override
    public void addMenu(Menu menu) {
        menuMapper.addMenu(menu);
    }

    // 根据 id 删除菜单
    @Transactional
    @Override
    public void deleteMenuById(Integer id) {
        menuMapper.deleteMenuById(id);
        menuMapper.delPermByMenuId(id);
    }

    // 修改菜单
    @Transactional
    @Override
    public void updateMenu(Menu menu) {
        menuMapper.updateMenu(menu);
    }

数据库设计

效果如下


网站公告

今日签到

点亮在社区的每一天
去签到