Browse Source

:recycle: Refactoring code. 使用 element 原生表格树重构菜单管理

:recycle: Refactoring code. 使用 element 表格树重构菜单管理
冷冷 5 years ago
parent
commit
87c7fa91be
4 changed files with 269 additions and 296 deletions
  1. 1 0
      package.json
  2. 0 174
      src/const/crud/admin/menu.js
  3. 95 122
      src/views/admin/menu/index.vue
  4. 173 0
      src/views/admin/menu/menu-form.vue

+ 1 - 0
package.json

@@ -10,6 +10,7 @@
     "analyze": "vue-cli-service build --report"
   },
   "dependencies": {
+    "@riophae/vue-treeselect": "0.4.0",
     "@chenfengyuan/vue-qrcode": "^1.0.1",
     "pig-avue-form-design": "^1.1.6",
     "avue-plugin-ueditor": "^0.0.6",

+ 0 - 174
src/const/crud/admin/menu.js

@@ -1,174 +0,0 @@
-/*
- *    Copyright (c) 2018-2025, lengleng All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * Neither the name of the pig4cloud.com developer nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- * Author: lengleng (wangiegie@gmail.com)
- */
-import iconList from '@/const/iconList'
-
-export const tableOption = {
-  tip: false,
-  lazy: true,
-  dialogWidth: "60%",
-  tree: true,
-  border: true,
-  index: true,
-  viewBtn: true,
-  align: 'center',
-  column: [
-    {
-      label: "名称",
-      prop: "name",
-      width: 180,
-      rules: [
-        {
-          required: true,
-          message: "请输入名称",
-          trigger: "blur"
-        }
-      ]
-    },
-    {
-      label: "类型",
-      prop: "type",
-      type: "select",
-      slot: true,
-      border: true,
-      valueFormat: "string",
-      dicData: [
-        {
-          label: "左菜单",
-          value: "0"
-        },
-        {
-          label: "顶菜单",
-          value: "2"
-        },
-        {
-          label: "按钮",
-          value: "1"
-        },
-      ],
-      rules: [
-        {
-          required: true,
-          message: "请选择菜单类型",
-          trigger: "blur"
-        }
-      ]
-    },
-    {
-      label: "路由路径",
-      prop: "path",
-      width: 180,
-      addDisplay: true,
-      editDisplay: true,
-      overHidden: true,
-      rules: [
-        {
-          required: true,
-          message: "请输入路由地址",
-          trigger: "blur"
-        }
-      ]
-    },
-    {
-      label: "上级菜单",
-      prop: "parentId",
-      type: "tree",
-      dicUrl: "/admin/menu/tree",
-      addDisplay: true,
-      editDisplay: true,
-      hide: true,
-      props: {
-        label: "name",
-        value: "id"
-      },
-      rules: [
-        {
-          required: true,
-          message: "请选择上级菜单",
-          trigger: "click",
-        }
-      ]
-    },
-    {
-      label: "图标",
-      prop: "icon",
-      type: "icon-select",
-      slot: true,
-      addDisplay: true,
-      editDisplay: true,
-      iconList: iconList,
-      rules: [
-        {
-          required: true,
-          message: "请输入图标",
-          trigger: "click"
-        }
-      ]
-    },
-    {
-      label: "权限标识",
-      prop: "permission",
-      addDisplay: true,
-      editDisplay: true,
-      rules: [
-        {
-          required: true,
-          message: "权限标识",
-          trigger: "blur"
-        }
-      ]
-    },
-    {
-      hide: true,
-      addDisplay: true,
-      editDisplay: true,
-      label: "路由缓冲",
-      prop: "keepAlive",
-      type: "radio",
-      border: true,
-      valueFormat: "string",
-      dicData: [
-        {
-          label: "关闭",
-          value: "0"
-        },
-        {
-          label: "打开",
-          value: "1"
-        },
-      ],
-      rules: [
-        {
-          required: true,
-          message: "请选择上级菜单",
-          trigger: "click",
-        }
-      ]
-    },
-    {
-      label: "排序",
-      prop: "sort",
-      type: "number",
-      rules: [
-        {
-          required: true,
-          message: "请输入菜单排序",
-          trigger: "blur"
-        }
-      ]
-    }
-  ]
-}

+ 95 - 122
src/views/admin/menu/index.vue

@@ -1,152 +1,125 @@
 <template>
   <basic-container>
-    <avue-crud :option="option"
-               :data="data"
-               ref="crud"
-               v-model="form"
-               :permission="permissionList"
-               :before-open="beforeOpen"
-               @row-del="rowDel"
-               @row-update="rowUpdate"
-               @row-save="rowSave"
-               @tree-load="treeLoad">
-      <template slot-scope="scope"
-                slot="icon">
-        <div style="text-align:center">
-          <i :class="scope.row.icon"></i>
-        </div>
-      </template>
-      <template slot-scope="scope"
-                slot="type">
-        <div style="text-align:center">
-          <el-tag v-if="scope.row.type !== '1'" size="small" type="success">菜单</el-tag>
-          <el-tag v-else-if="scope.row.type === '1'" size="small" type="info">按钮</el-tag>
-        </div>
-      </template>
-    </avue-crud>
+    <div class="avue-crud">
+      <el-form :inline="true">
+        <el-form-item>
+          <el-button v-if="permissions.sys_menu_add" icon="el-icon-plus" type="primary"
+                     @click="addOrUpdateHandle(false)">
+            添加
+          </el-button>
+        </el-form-item>
+      </el-form>
+
+      <el-table
+        border
+        v-loading="loading"
+        :data="menuList"
+        row-key="id"
+        :tree-props="{children: 'children', hasChildren: 'hasChildrens'}">
+        <el-table-column prop="name" label="菜单名称" :show-overflow-tooltip="true" width="180"></el-table-column>
+        <el-table-column prop="icon" label="图标" align="center" width="100">
+          <template slot-scope="scope">
+            <i :class="scope.row.icon"/>
+          </template>
+        </el-table-column>
+        <el-table-column prop="sort" label="排序" width="60"></el-table-column>
+        <el-table-column prop="path" label="组件路径" :show-overflow-tooltip="true"></el-table-column>
+        <el-table-column prop="type" label="类型" width="80" align="center">
+          <template slot-scope="scope">
+            <el-tag type="success" v-if="scope.row.type === '0'">左菜单</el-tag>
+            <el-tag type="success" v-if="scope.row.type === '2'">顶菜单</el-tag>
+            <el-tag type="info" v-if="scope.row.type === '1'">按钮</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="keepAlive" label="缓冲" width="80" align="center">
+          <template slot-scope="scope">
+            <el-tag type="info" v-if="scope.row.keepAlive === '0'">关闭</el-tag>
+            <el-tag type="success" v-if="scope.row.keepAlive === '1'">开启</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="permission" label="权限标识" :show-overflow-tooltip="true"></el-table-column>
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template slot-scope="scope">
+            <el-button
+              size="small"
+              type="text"
+              icon="el-icon-plus"
+              @click="addOrUpdateHandle(false,scope.row.id)"
+              v-if="permissions.sys_menu_add">新增
+            </el-button>
+            <el-button size="small"
+                       type="text"
+                       icon="el-icon-edit"
+                       @click="addOrUpdateHandle(true,scope.row.id)"
+                       v-if="permissions.sys_menu_edit">修改
+            </el-button>
+            <el-button
+              size="small"
+              type="text"
+              icon="el-icon-delete"
+              @click="handleDelete(scope.row)"
+              v-if="permissions.sys_menu_del">删除
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <table-form v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getList"></table-form>
+    </div>
   </basic-container>
 </template>
 
 <script>
-  import {addObj, delObj, fetchMenuTree, getObj, putObj} from '@/api/admin/menu'
-  import {validatenull} from '@/util/validate'
-  import {mapGetters} from "vuex";
-  import {tableOption} from '@/const/crud/admin/menu'
+  import {delObj, fetchMenuTree} from '@/api/admin/menu'
+  import TableForm from './menu-form'
+  import {mapGetters} from 'vuex'
 
   export default {
+    name: "Menu",
+    components: {TableForm},
     data() {
       return {
-        form: {},
-        option: tableOption,
-        lazy: true,
-        data: []
+        addOrUpdateVisible: false,
+        // 遮罩层
+        loading: true,
+        // 菜单表格树数据
+        menuList: [],
+        // 菜单树选项
+        menuOptions: [],
       };
     },
     created() {
-      this.onLoad()
-    },
-    watch: {
-      'form.type'() {
-        this.showHide()
-      }
+      this.getList();
     },
     computed: {
-      ...mapGetters(["permissions"]),
-      permissionList() {
-        return {
-          addBtn: this.vaildData(this.permissions.sys_menu_add, false),
-          delBtn: this.vaildData(this.permissions.sys_menu_del, false),
-          editBtn: this.vaildData(this.permissions.sys_menu_edit, false)
-        };
-      }
+      ...mapGetters(['permissions']),
     },
     methods: {
-      rowSave(row, done) {
-        if (validatenull(row.parentId)) {
-          row.parentId = -1
-        }
-        addObj(row).then(res => {
-          row.id = res.data.data.menuId;
-          done(row);
-          this.$message.success("添加成功");
-        });
+      addOrUpdateHandle(isEdit, id) {
+        this.addOrUpdateVisible = true
+        this.$nextTick(() => {
+          this.$refs.addOrUpdate.init(isEdit, id)
+        })
       },
-      rowUpdate(row, index, done, loading) {
-        if (validatenull(row.parentId)) {
-          row.parentId = -1
-        }
-        putObj(row).then(() => {
-          done(row)
-          this.$message.success("修改成功");
+      getList() {
+        this.loading = true;
+        fetchMenuTree(false).then(response => {
+          this.menuList = response.data.data
+          this.loading = false;
         });
       },
-      rowDel(row, index, done) {
-        this.$confirm("确定将选择数据删除?", {
+      handleDelete(row) {
+        this.$confirm('是否确认删除名称为"' + row.name + '"的数据项?', "警告", {
           confirmButtonText: "确定",
           cancelButtonText: "取消",
           type: "warning"
-        }).then(() => {
+        }).then(function () {
           return delObj(row.id);
         }).then(() => {
-          done(row);
-          this.$message.success("删除成功");
-        });
-      },
-      beforeOpen(done, type) {
-        if (["edit", "view"].includes(type)) {
-          getObj(this.form.id).then(res => {
-            this.form = res.data.data;
-          });
-        }
-        done();
-      },
-      onLoad() {
-        fetchMenuTree(this.option.lazy).then(res => {
-          this.data = res.data.data;
-        });
-      },
-      treeLoad(node, child, resolve) {
-        fetchMenuTree(this.option.lazy, node.id).then(res => {
-          resolve(res.data.data)
+          this.getList();
+          this.$message.success('删除成功')
         })
-      },
-      showHide() {
-        this.$refs.crud.option.column.filter(item => {
-          // 不是按钮类型 要输入path
-          if (item.prop === "path") {
-            item.addDisplay = this.form.type !== "1"
-            item.editDisplay = this.form.type !== "1"
-          }
-          // 不是按钮类型 要输入path
-          if (item.prop === "icon") {
-            item.addDisplay = this.form.type !== "1"
-            item.editDisplay = this.form.type !== "1"
-          }
-
-          // 是按钮类型输入permission
-          if (item.prop === "permission") {
-            item.addDisplay = this.form.type === "1"
-            item.editDisplay = this.form.type === "1"
-          }
-
-          // 是顶部菜单不显示上级菜单
-          if (item.prop === "parentId") {
-            item.addDisplay = this.form.type !== "2"
-            item.editDisplay = this.form.type !== "2"
-            item.rules[0].required = this.form.type === "1"
-          }
-
-          // 菜单开启 keepAlive 选择
-          if (item.prop === "keepAlive") {
-            item.addDisplay = this.form.type === "0"
-            item.editDisplay = this.form.type === "0"
-            item.rules[0].required = this.form.type === "0"
-          }
-        });
       }
     }
   };
 </script>
-
-<style>
-</style>

+ 173 - 0
src/views/admin/menu/menu-form.vue

@@ -0,0 +1,173 @@
+<template>
+  <!-- 添加或修改菜单对话框 -->
+  <el-dialog :title="!form.menuId ? '新增' : '修改'"
+             :visible.sync="visible">
+    <el-form ref="dataForm" :model="form" :rules="rules" label-width="80px">
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="菜单类型" prop="type">
+            <el-radio-group v-model="form.type" size="small">
+              <el-radio-button label="0">左菜单</el-radio-button>
+              <el-radio-button label="1">按钮</el-radio-button>
+              <el-radio-button label="2">顶菜单</el-radio-button>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="上级菜单">
+            <treeselect v-model="form.parentId"
+                        :options="menuOptions"
+                        :normalizer="normalizer"
+                        :show-count="true"
+                        placeholder="选择上级菜单"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-form-item label="图标" prop="icon" v-if="form.type !== '1'">
+        <avue-icon-select v-model="form.icon" :icon-list="iconList"></avue-icon-select>
+      </el-form-item>
+      <el-form-item label="名称" prop="name">
+        <el-input v-model="form.name" placeholder="请输入菜单名称"/>
+      </el-form-item>
+      <el-form-item label="路由地址" prop="path" v-if="form.type !== '1'">
+        <el-input v-model="form.path" placeholder="请输入路由地址"/>
+      </el-form-item>
+      <el-form-item label="权限标识" prop="permission" v-if="form.type === '1'">
+        <el-input v-model="form.permission" placeholder="请权限标识" maxlength="50"/>
+      </el-form-item>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="排序" prop="sort">
+            <el-input-number v-model="form.sort" controls-position="right" :min="0"/>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="路由缓冲" prop="keepAlive" v-if="form.type !== '1'">
+            <el-radio-group v-model="form.keepAlive">
+              <el-radio-button label="0">否</el-radio-button>
+              <el-radio-button label="1">是</el-radio-button>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <div slot="footer" class="dialog-footer">
+      <el-button type="primary" @click="dataFormSubmit">确 定</el-button>
+      <el-button @click="visible = false">取 消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+  import {addObj, fetchMenuTree, getObj, putObj} from '@/api/admin/menu'
+  import Treeselect from "@riophae/vue-treeselect"
+  import iconList from '@/const/iconList'
+  import TableForm from './'
+  import "@riophae/vue-treeselect/dist/vue-treeselect.css"
+
+  export default {
+    name: "Menu",
+    components: {Treeselect, TableForm},
+    data() {
+      return {
+        // 遮罩层
+        loading: true,
+        // 菜单树选项
+        menuOptions: [],
+        // 是否显示弹出层
+        visible: false,
+        // 图标
+        iconList: iconList,
+        form: {
+          name: undefined,
+          path: undefined,
+          icon: undefined,
+          permission: undefined,
+          type: '0',
+          keepAlive: '0',
+          sort: 999
+        },
+        // 表单校验
+        rules: {
+          name: [
+            {required: true, message: "菜单名称不能为空", trigger: "blur"}
+          ],
+          sort: [
+            {required: true, message: "菜单顺序不能为空", trigger: "blur"}
+          ],
+          path: [
+            {required: true, message: "路由地址不能为空", trigger: "blur"}
+          ],
+          keepAlive: [
+            {required: true, message: "路由缓冲不能为空", trigger: "blur"}
+          ],
+          permission: [
+            {required: true, message: "权限标识不能为空", trigger: "blur"}
+          ]
+        }
+      };
+    },
+    methods: {
+      init(isEdit, id) {
+        if (id != null) {
+          this.form.parentId = id;
+        }
+        this.visible = true
+        this.getTreeselect();
+        this.$nextTick(() => {
+          this.$refs['dataForm'].resetFields()
+          if (isEdit) {
+            getObj(id).then(response => {
+              this.form = response.data.data
+            })
+          }
+        })
+      },
+      // 表单提交
+      dataFormSubmit() {
+        this.$refs['dataForm'].validate((valid) => {
+          if (valid) {
+            if (this.form.parentId === undefined) {
+              this.form.parentId = -1
+            }
+
+            if (this.form.menuId) {
+              putObj(this.form).then(data => {
+                this.$message.success('修改成功')
+                this.visible = false
+                this.$emit('refreshDataList')
+              });
+            } else {
+              addObj(this.form).then(data => {
+                this.$message.success('添加成功')
+                this.visible = false
+                this.$emit('refreshDataList')
+              })
+            }
+          }
+        })
+      },
+      /** 查询菜单下拉树结构 */
+      getTreeselect() {
+        fetchMenuTree().then(response => {
+          this.menuOptions = [];
+          const menu = {id: -1, name: '根菜单', children: []};
+          menu.children = response.data.data;
+          this.menuOptions.push(menu);
+        });
+      },
+      /** 转换菜单数据结构 */
+      normalizer(node) {
+        if (node.children && !node.children.length) {
+          delete node.children;
+        }
+        return {
+          id: node.id,
+          label: node.name,
+          children: node.children
+        };
+      }
+    }
+  };
+</script>