2 Commits e12139b7b1 ... 818f432de5

Autor SHA1 Mensagem Data
  liceal 818f432de5 增加自定义指令权限控制DOM 4 anos atrás
  liceal ef13ccc3d1 添加异步路由功能,添加岗位下用户table,修复菜单复点报错BUG todo:权限管理 4 anos atrás

+ 54 - 0
src/Api/dept.js

@@ -0,0 +1,54 @@
+import axios from '@/axios/http';
+
+const URL = {
+    curdDept: 'dept/', //部门增删查改
+}
+
+/**
+ * 得到树
+ * @param {Object} params {parent_id:父级节点id(可空,空则返回顶级树)} 
+ */
+export function getNode(params) {
+    return axios({
+        url: URL.curdDept,
+        method: 'get',
+        params
+    })
+}
+
+/**
+ * 新增叶子
+ * 如果父级id空,则创建为顶级树
+ * @param {Object} data {name:名字,parent_id:父级id(可空)} 
+ */
+export function addNode(data) {
+    return axios({
+        url: URL.curdDept,
+        method: 'post',
+        data
+    })
+}
+
+/**
+ * 删除节点
+ * @param {Object} data {id:节点id}
+ */
+export function delNode(data) {
+    return axios({
+        url: URL.curdDept,
+        method: 'delete',
+        data
+    })
+}
+
+/**
+ * 更新节点
+ * @param {Object} data {id:节点id,name:节点名字,parent_id:父级id(可空)}
+ */
+export function putNode(data) {
+    return axios({
+        url: URL.curdDept,
+        method: 'put',
+        data
+    })
+}

+ 38 - 0
src/Api/jobs.js

@@ -0,0 +1,38 @@
+import axios from '@/axios/http';
+
+const URL = {
+    job: 'jobs/'
+}
+
+/** 得到岗位
+ * @param {Object} params {dept_id:父级部门id} 
+ */
+export function getJob(params) {
+    return axios({
+        url: URL.job,
+        method: 'get',
+        params
+    })
+}
+
+/** 插入岗位
+ * @param {Object} data {name:岗位名字,dept_id:父级部门id}
+ */
+export function addJob(data) {
+    return axios({
+        url: URL.job,
+        method: 'post',
+        data
+    })
+}
+
+/** 删除岗位
+ * @param {Object} data {id:岗位id} 
+ */
+export function delJob(data) {
+    return axios({
+        url: URL.job,
+        method: 'delete',
+        data
+    })
+}

+ 5 - 4
src/Api/tree.js

@@ -5,6 +5,7 @@ const URL = {
     addTree: 'tree/',
     delTree: 'tree/',
     putTree: 'tree/',
+    curdDept: 'dept/', //部门增删查改
 }
 
 /**
@@ -13,7 +14,7 @@ const URL = {
  */
 export function getNode(params) {
     return axios({
-        url: URL.getTree,
+        url: URL.curdDept,
         method: 'get',
         params
     })
@@ -26,7 +27,7 @@ export function getNode(params) {
  */
 export function addNode(data) {
     return axios({
-        url: URL.addTree,
+        url: URL.curdDept,
         method: 'post',
         data
     })
@@ -38,7 +39,7 @@ export function addNode(data) {
  */
 export function delNode(data) {
     return axios({
-        url: URL.delTree,
+        url: URL.curdDept,
         method: 'delete',
         data
     })
@@ -50,7 +51,7 @@ export function delNode(data) {
  */
 export function putNode(data) {
     return axios({
-        url: URL.putTree,
+        url: URL.curdDept,
         method: 'put',
         data
     })

+ 12 - 0
src/Api/user.js

@@ -1,6 +1,7 @@
 import axios from '@/axios/http';
 
 const URL = {
+    user: 'user',
     login: 'login/',
     logout: 'logout/',
     report: 'dailyreport/'
@@ -21,6 +22,17 @@ export function logout() {
     })
 }
 
+/**
+ * 得到所有用户
+ */
+export function allUser() {
+    return axios({
+        url: URL.user,
+        method: 'get'
+    })
+}
+
+
 export function getReport(id) {
     return axios({
         url: `${URL.report}${id}/`,

+ 49 - 17
src/App.vue

@@ -1,36 +1,38 @@
 <template>
-  <div>
+  <div id="app">
     <el-menu
-      :default-active="$route.path"
+      :default-active="$route.name"
       background-color="#545c64"
       text-color="#fff"
       mode="horizontal"
       class="el-menu-demo nav"
       active-text-color="#ffd04b"
-      router
+      @select="menuSelect"
     >
-      <template v-for="(item, index) in routers">
+      <template v-for="(item, index) in getterPermission.routes">
         <!-- 把:id给截掉 -->
+        <!-- :index="item.path.split(':')[0]" -->
         <el-menu-item
-          :index="item.path.split(':')[0]"
+          :index="item.name"
           v-if="!item.hasOwnProperty('children')"
           :key="index"
         >{{item.alias}}</el-menu-item>
         <el-submenu :index="$route.path" v-else :key="index">
           <template slot="title">{{item.alias}}</template>
+          <!-- :index="item.path+'/'+child.path.split(':')[0]" -->
           <el-menu-item
             v-for="(child,index_child) in item.children"
             :key="index_child"
-            :index="item.path+'/'+child.path.split(':')[0]"
+            :index="child.name"
           >{{child.alias}}</el-menu-item>
         </el-submenu>
       </template>
     </el-menu>
 
-    <hr />
-
     <router-view />
 
+    <hr />
+
     <div class="buttons">
       <!-- :disabled="!isLogin" -->
       <el-button type="primary" @click="dialogVisible=true" :disabled="!isLogin">登陆</el-button>
@@ -58,10 +60,12 @@
 <script>
 import router from "./router";
 import { login, logout } from "@/Api/user";
+import { mapActions, mapGetters } from "vuex";
 export default {
   data() {
     return {
-      routers: this.$router.options.routes,
+      // routers: this.$router.options.routes,
+      routers: this.getterPermission ? this.getterPermission.routes : [],
       dialogVisible: false,
       user: {
         username: "",
@@ -73,7 +77,20 @@ export default {
     isLogin() {
       console.log("csrftoken", this.$Cookie.get("csrftoken"));
       return this.$Cookie.get("csrftoken") === undefined;
-    }
+    },
+    ...mapGetters(["getterPermission"])
+  },
+  created() {
+    //加载异步路由
+    let asyncRouters = [
+      {
+        path: "/asyncTest",
+        name: "asyncTest",
+        alias: "异步菜单",
+        component: () => import("@/views/asyncTest/asyncTest.vue")
+      }
+    ];
+    this.asyncAddRouters(asyncRouters);
   },
   methods: {
     login() {
@@ -109,19 +126,34 @@ export default {
       });
       this.$Cookie.remove("csrftoken");
       console.log("登出");
-    }
+    },
+    //选择菜单
+    menuSelect(index, indexPath) {
+      // console.log(indexPath);
+      let pathName = indexPath[indexPath.length - 1];
+      // console.log(this.$route.name, pathName);
+      if (this.$route.name !== pathName) {
+        this.$router.push({
+          name: indexPath[indexPath.length - 1]
+        });
+      }
+    },
+    ...mapActions(["asyncAddRouters"])
   }
 };
 </script>
 
 <style lang="less">
+body,
 #app {
-  font-family: Avenir, Helvetica, Arial, sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  text-align: center;
-  color: #2c3e50;
-  margin-top: 60px;
+  // font-family: Avenir, Helvetica, Arial, sans-serif;
+  // -webkit-font-smoothing: antialiased;
+  // -moz-osx-font-smoothing: grayscale;
+  // text-align: center;
+  // color: #2c3e50;
+  // margin-top: 60px;
+  padding: 0px;
+  margin: 0px;
 }
 .buttons {
   display: flex;

+ 6 - 6
src/router/index.js

@@ -3,7 +3,7 @@ import VueRouter from 'vue-router'
 
 Vue.use(VueRouter)
 
-const routes = [{
+export const constantRoutes = [{
         path: '/',
         name: 'home',
         alias: '主页',
@@ -48,18 +48,18 @@ const routes = [{
         ]
     },
     {
-        path: '/tree',
-        name: 'tree',
-        alias: '动态加载树',
+        path: '/dynamicTable',
+        name: 'dynamicTable',
+        alias: '动态表单',
         component: () =>
-            import ('@/views/tree/tree.vue')
+            import ('@/views/dynamicTable/dynamicTable.vue')
     }
 ]
 
 const router = new VueRouter({
     mode: 'history',
     base: process.env.BASE_URL,
-    routes
+    routes: constantRoutes
 })
 
 export default router

+ 5 - 0
src/store/getter.js

@@ -0,0 +1,5 @@
+const getters = {
+    permission_role: state => state.permission.role
+}
+
+export default getters

+ 4 - 2
src/store/index.js

@@ -1,9 +1,11 @@
 import Vue from 'vue'
 import Vuex from 'vuex'
 import modules from './modules'
+import getters from './getter'
 
 Vue.use(Vuex)
 
 export default new Vuex.Store({
-	modules
-})
+    modules,
+    getters
+})

+ 4 - 2
src/store/modules/index.js

@@ -1,4 +1,6 @@
 import * as bill from './bill'
+import * as permission from './permission'
 export default {
-	bill
-}
+    bill,
+    permission
+}

+ 54 - 0
src/store/modules/permission.js

@@ -0,0 +1,54 @@
+import router, { constantRoutes } from '@/router'
+
+/**
+ * 变量存储
+ */
+export const state = {
+    routes: [], //最后改变完呈现的路由
+    role: 'admin', //权限admin 目前先写死了
+}
+
+/**
+ * 获取数据
+ */
+export const getters = {
+    getterPermission(state) {
+        return state
+    }
+}
+
+/**
+ * 改变数据事件
+ */
+export const mutations = {
+    addRouter(state) {
+        router.options.routes = state.routes //手动改变路由数组
+    },
+    setRouter(state, asyncRouter) {
+        router.addRoutes(asyncRouter) //注册路由
+        state.routes = constantRoutes.concat(asyncRouter) //拼接原来的和异步的路由得到最新路由
+    },
+    setRole(state, role) {
+        state.role = role
+    }
+}
+
+/**
+ * 使用改变数据事件
+ */
+export const actions = {
+    /** 异步添加路由 
+     * @param {Array} asyncRouter 异步路由 
+     */
+    async asyncAddRouters({ commit }, asyncRouter = []) {
+        await commit('setRouter', asyncRouter)
+        await commit('addRouter')
+    },
+
+    /** 设置角色
+     * @param {String} role 角色名 
+     */
+    setPermissionRole({ commit }, role) {
+        commit('setRole', role)
+    }
+}

+ 56 - 0
src/views/asyncTest/asyncTest.vue

@@ -0,0 +1,56 @@
+<template>
+  <div>
+    <el-button v-permission="['admin']">admin按钮</el-button>
+    <el-button v-permission="['zayu']">zayu按钮</el-button>
+    <el-button v-permission="['zayu1']">zayu1按钮</el-button>
+    <el-button v-permission="['zayu1','zayu2']">zaui1按钮zayu2按钮</el-button>
+    <hr />
+    <el-input v-model="role" placeholder="输入角色名字"></el-input>
+    <el-button @click="setRole">设置角色</el-button>
+  </div>
+</template>
+
+<script>
+import store from "@/store";
+import { mapActions } from "vuex";
+export default {
+  name: "asyncTest",
+  data() {
+    return {
+      role: ""
+    };
+  },
+  directives: {
+    permission: {
+      inserted(el, binding) {
+        //非管理员的都会被删除
+        let roles = binding.value;
+        let nowRole = store.getters.permission_role; //获取当前权限
+        if (roles.indexOf(nowRole) == -1) {
+          el.parentNode && el.parentNode.removeChild(el); //删除自己
+        }
+      }
+    }
+  },
+  methods: {
+    getRole() {
+      return {
+        role: "admin"
+      };
+    },
+    ...mapActions(["setPermissionRole"]),
+    setRole() {
+      this.setPermissionRole(this.role);
+      console.log(store.getters.permission_role);
+      this.$message({
+          type:'success',
+          message:"更新成功,请切换一下页面,查看更新状态",
+          duration:1000
+      })
+    }
+  }
+};
+</script>
+
+<style>
+</style>

+ 51 - 16
src/views/tree/tree.vue → src/views/dynamicTable/components/tree.vue

@@ -1,11 +1,11 @@
 <template>
   <div>
     <!-- 
-			拖拽
-			draggable
-			@node-drop="handleDrop"
-			@node-drag-enter="handleDragEnter"
-			:allow-drop="allowDrop"
+		拖拽
+		draggable
+		@node-drop="handleDrop"
+		@node-drag-enter="handleDragEnter"
+		:allow-drop="allowDrop"
     -->
     <el-tree
       :props="props"
@@ -16,14 +16,25 @@
       :expand-on-click-node="expand"
       ref="tree"
       v-loading="loading"
+      @check-change="handleCheckChange"
     >
       <template slot-scope="{data,node}">
         <div class="row">
           <span class="content">{{data.name}}</span>
           <span class="handle">
             <el-button type="text" size="mini" @click.stop.prevent="addNode(data,node)">新增</el-button>
-            <el-button v-if="data.id!=-1" type="text" size="mini" @click.stop.prevent="delNode(data,node)">删除</el-button>
-            <el-button v-if="data.id!=-1" type="text" size="mini" @click.stop.prevent="putNode(data,node)">更新</el-button>
+            <el-button
+              v-if="data.id!=-1"
+              type="text"
+              size="mini"
+              @click.stop.prevent="delNode(data,node)"
+            >删除</el-button>
+            <el-button
+              v-if="data.id!=-1"
+              type="text"
+              size="mini"
+              @click.stop.prevent="putNode(data,node)"
+            >更新</el-button>
           </span>
         </div>
       </template>
@@ -32,7 +43,8 @@
 </template>
 
 <script>
-import { getNode, addNode, delNode, putNode } from "@/Api/tree";
+// import { getNode, addNode, delNode, putNode } from "@/Api/tree";
+import { getNode, addNode, delNode, putNode } from "@/Api/dept";
 export default {
   name: "tree",
   data() {
@@ -59,7 +71,7 @@ export default {
   methods: {
     //加载节点
     loadNode(node, resolve) {
-      // console.log(node);
+      // console.log(resolve);
       if (node.level === 0) {
         let data = [
           {
@@ -67,12 +79,19 @@ export default {
             id: -1
           }
         ];
-        return resolve(data);
+        resolve(data);
+        this.$nextTick(() => {
+          this.$emit("check-node", this.$refs.tree.getCheckedNodes()); //返回所有节点
+        });
+        // return resolve(data);
       } else if (node.level === 1) {
         getNode().then(res => {
           // console.log(res.data);
           // res.data.leaf = false
-          return resolve(res.data);
+          resolve(res.data);
+          this.$nextTick(() => {
+            this.$emit("check-node", this.$refs.tree.getCheckedNodes()); //返回所有节点
+          });
         });
       } else {
         // console.log(node);
@@ -82,6 +101,9 @@ export default {
           .then(res => {
             // console.log(res);
             resolve(res.data);
+            this.$nextTick(() => {
+              this.$emit("check-node", this.$refs.tree.getCheckedNodes()); //返回所有节点
+            });
           })
           .catch(e => {
             // console.log(e);
@@ -92,7 +114,7 @@ export default {
     //添加节点
     addNode(data, node) {
       // this.expand = false;
-      console.log(data, node);
+      // console.log(data, node);
       this.$prompt("请输入名字", "插入节点", {
         confirmButtonText: "确定",
         cancelButtonText: "取消"
@@ -140,6 +162,9 @@ export default {
           console.log("删除节点", res);
           this.$refs.tree.remove(node);
           this.loading = false;
+          this.$nextTick(() => {
+            this.$emit("check-node", this.$refs.tree.getCheckedNodes()); //返回所有节点
+          });
           // this.expand = true;
         })
         .catch(e => {
@@ -166,8 +191,11 @@ export default {
               console.log(data.id, data);
               data.name = value;
               this.$refs.tree.updateKeyChildren(data.id, data);
-              this.$forceUpdate();
+              // this.$forceUpdate();
               this.loading = false;
+              this.$nextTick(() => {
+                this.$emit("check-node", this.$refs.tree.getCheckedNodes()); //返回所有节点
+              });
             })
             .catch(e => {
               this.loading = false;
@@ -181,6 +209,15 @@ export default {
           console.log("取消修改", e);
         });
     },
+    /** 选择状态改变
+     * @param {Object} data 节点详细数据
+     * @param {Boolean} checked 是否被选中
+     * @param {Boolean} indeterminate 是否内部被选择部分
+     */
+    handleCheckChange(data, checked, indeterminate) {
+      // console.log(data, checked, indeterminate);
+      this.$emit("check-node", this.$refs.tree.getCheckedNodes()); //返回所有节点
+    },
 
     // 以下未使用 ✳
     //拖拽 更新节点
@@ -239,7 +276,7 @@ export default {
   align-items: center;
   justify-content: space-between;
   font-size: 14px;
-  padding-right: 8px;
+  // padding-right: 8px;
   .handle {
     // background-color: #ccc;
     padding: 0px 5px;
@@ -248,7 +285,5 @@ export default {
     line-height: 15px;
     margin-left: 30px;
   }
-  .content {
-  }
 }
 </style>

+ 259 - 0
src/views/dynamicTable/dynamicTable.vue

@@ -0,0 +1,259 @@
+<template>
+  <div>
+    <el-container>
+      <el-aside width="300px">
+        <tree @check-node="(res)=>{checkNode = res}" />
+      </el-aside>
+      <el-container>
+        <el-main>
+          <el-tabs v-model="active" type="border-card">
+            <div v-if="!active" class="tips">空空哒哟~</div>
+            <!-- 部门tabs -->
+            <template v-for="node in checkNode">
+              <el-tab-pane
+                v-if="node.id!=-1"
+                :key="node.id"
+                :label="node.name"
+                :name="node.id.toString()"
+                v-loading="loading.childrenActive"
+              >
+                <!-- 岗位tabs -->
+                <el-tabs v-model="childrenActive" editable @edit="handleTabsEdit">
+                  <div v-if="!childrenActive" class="tips">空空哒哟~</div>
+                  <el-tab-pane
+                    v-for="children in childrenNode"
+                    :key="children.id"
+                    :label="children.name"
+                    :name="children.id.toString()"
+                  >
+                    <!-- 用户table -->
+                    <el-table
+                      :data="userTableData"
+                      style="width: 100%"
+                      v-loading="loading.userTableData"
+                      border
+                    >
+                      <el-table-column prop="id" label="ID" width="180"></el-table-column>
+                      <el-table-column prop="username" label="姓名" width="180"></el-table-column>
+                      <el-table-column prop="email" label="邮箱"></el-table-column>
+                    </el-table>
+                  </el-tab-pane>
+                </el-tabs>
+              </el-tab-pane>
+            </template>
+          </el-tabs>
+        </el-main>
+        <el-footer>Footer</el-footer>
+      </el-container>
+    </el-container>
+  </div>
+</template>
+
+<script>
+import { addJob, getJob, delJob } from "@/Api/jobs";
+import { allUser } from "@/Api/user";
+export default {
+  name: "home",
+  components: {
+    tree: () => import("./components/tree")
+  },
+  data() {
+    return {
+      checkNode: [],
+      active: "",
+      childrenNode: [],
+      childrenActive: "",
+      dept: {
+        //部门信息
+        id: "",
+        name: ""
+      },
+      loading: {
+        active: false,
+        childrenActive: false,
+        userTableData: false
+      },
+      userTableData: [{ id: 1, username: "张三", email: "111@qq.com" }]
+    };
+  },
+  watch: {
+    //勾选的node 部门node
+    checkNode(val, oldval) {
+      // console.log(val);
+      this.active =
+        val.length > 0
+          ? val[0].id == -1
+            ? val.length > 1
+              ? val[1].id.toString()
+              : ""
+            : val[0].id.toString()
+          : "";
+    },
+    //初始化岗位默认选中
+    childrenNode(val) {
+      this.childrenActive = val.length == 0 ? "" : val[0].id.toString();
+    },
+    //选中的部门改变
+    active(val) {
+      //   console.log(val);
+      if (this.checkNode.length > 0 && val != "") {
+        this.initJobTab(val);
+      }
+    },
+    //选中的岗位改变
+    childrenActive(val) {
+      this.initUserDetail();
+    }
+  },
+  methods: {
+    //操作tab
+    handleTabsEdit(targetName, action) {
+      // console.log(targetName, action);
+      if (action === "add") {
+        // 插入tab
+        this.$prompt("请输入岗位名字", "增加岗位", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消"
+        })
+          .then(({ value }) => {
+            let data = {
+              name: value,
+              dept_id: this.active
+            };
+            this.loading.childrenActive = true;
+            addJob(data).then(res => {
+              console.log(res);
+              this.$message({
+                type: "success",
+                message: `增加 ${value} 部门成功`,
+                duration: 1000
+              });
+              this.loading.childrenActive = false;
+              this.initJobTab(this.active);
+            });
+            console.log(value);
+          })
+          .catch(e => {
+            console.log("取消增加岗位", e);
+          });
+      } else if (action === "remove") {
+        this.$confirm("此操作将永久删除该岗位, 是否继续?", "提示", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        })
+          .then(value => {
+            // console.log(targetName);
+            let data = {
+              id: targetName
+            };
+            this.loading.childrenActive = true;
+            delJob(data).then(res => {
+              console.log("删除成功", res);
+              this.$message({
+                type: "success",
+                message: "删除成功!"
+              });
+              this.loading.childrenActive = false;
+              this.initJobTab(this.active);
+            });
+          })
+          .catch(() => {
+            this.$message({
+              type: "info",
+              message: "已取消删除"
+            });
+          });
+      }
+    },
+    //初始化岗位tab
+    initJobTab(dept_id) {
+      let params = {
+        dept_id
+      };
+      this.loading.childrenActive = true;
+      getJob(params)
+        .then(res => {
+          console.log("查询岗位", res);
+          this.childrenNode = res.data.result.map(v => {
+            return {
+              id: v.job,
+              name: v.job__name
+            };
+          });
+          this.$message({
+            type: "success",
+            message: res.data.message,
+            duration: 1000
+          });
+          this.loading.childrenActive = false;
+        })
+        .catch(e => {
+          this.$message({
+            type: "error",
+            message: "查询失败",
+            duration: 1000
+          });
+          this.loading.childrenActive = false;
+        });
+    },
+    //初始化用户详情,岗位空则查询全部
+    initUserDetail(job_id = null) {
+      this.loading.userTableData = true;
+      allUser()
+        .then(res => {
+          this.userTableData = res.data.result;
+          this.$message({
+              type:'success',
+              message:res.data.message,
+              duration:1000
+          })
+          this.loading.userTableData = false;
+        })
+        .catch(e => {
+          this.loading.userTableData = false;
+        });
+    }
+  },
+  directives:{
+      /**
+       * todo:权限控制 v-permission
+       */
+      permission:{
+          bind(el,binding){
+              console.log('绑定',el,binding);
+          },
+          inserted(el,binding){
+              console.log('插入父节点',el,binding);
+          }
+      }
+  }
+};
+</script>
+
+<style lang="less" scoped>
+.el-container {
+  height: 70vh;
+  .tips {
+    color: #ccc;
+    text-align: center;
+  }
+  .el-header,
+  .el-footer {
+    background-color: #b3c0d1;
+    color: #333;
+    text-align: center;
+    line-height: 60px;
+  }
+
+  .el-aside {
+    // background-color: #d3dce6;
+    // color: #333;
+  }
+
+  .el-main {
+    background-color: #e9eef3;
+    color: #333;
+  }
+}
+</style>