zhangjie 3 anni fa
parent
commit
996ca3beb7

+ 164 - 0
src/components/UploadButton.vue

@@ -0,0 +1,164 @@
+<template>
+  <el-upload
+    :action="uploadUrl"
+    :headers="headers"
+    :max-size="maxSize"
+    :format="format"
+    :accept="accept"
+    :data="uploadDataDict"
+    :before-upload="handleBeforeUpload"
+    :on-error="handleError"
+    :on-success="handleSuccess"
+    :http-request="upload"
+    :disabled="disabled"
+    :show-file-list="false"
+    style="display:inline-block;margin: 0 10px;"
+    ref="UploadComp"
+  >
+    <el-button
+      :type="btnType"
+      :icon="btnIcon"
+      :loading="loading"
+      :disabled="disabled"
+      >{{ btnContent }}</el-button
+    >
+  </el-upload>
+</template>
+
+<script>
+import { fileMD5 } from "../plugins/md5";
+import { $post } from "@/plugins/axios";
+
+export default {
+  name: "upload-button",
+  props: {
+    btnIcon: {
+      type: String
+    },
+    btnType: {
+      type: String,
+      default: "default"
+    },
+    btnContent: {
+      type: String
+    },
+    accept: {
+      type: String
+    },
+    format: {
+      type: Array,
+      default() {
+        return ["jpg", "jpeg", "png"];
+      }
+    },
+    uploadUrl: {
+      type: String,
+      required: true
+    },
+    uploadData: {
+      type: Object,
+      default() {
+        return {};
+      }
+    },
+    maxSize: {
+      type: Number,
+      default: 20 * 1024 * 1024
+    },
+    addFilenameParam: {
+      type: String,
+      default: "filename"
+    },
+    disabled: { type: Boolean, default: false }
+  },
+  data() {
+    return {
+      headers: {
+        md5: ""
+      },
+      res: {},
+      loading: false,
+      uploadDataDict: {}
+    };
+  },
+  methods: {
+    checkFileFormat(fileType) {
+      const _file_format = fileType
+        .split(".")
+        .pop()
+        .toLocaleLowerCase();
+      return this.format.length
+        ? this.format.some(item => item.toLocaleLowerCase() === _file_format)
+        : true;
+    },
+    async handleBeforeUpload(file) {
+      this.uploadDataDict = {
+        ...this.uploadData
+      };
+      this.uploadDataDict[this.addFilenameParam] = file.name;
+
+      if (file.size > this.maxSize) {
+        this.handleExceededSize();
+        return Promise.reject();
+      }
+
+      if (!this.checkFileFormat(file.name)) {
+        this.handleFormatError();
+        return Promise.reject();
+      }
+
+      const md5 = await fileMD5(file);
+      this.headers["md5"] = md5;
+      this.loading = true;
+
+      return true;
+    },
+    upload(options) {
+      let formData = new FormData();
+      Object.entries(options.data).forEach(([k, v]) => {
+        formData.append(k, v);
+      });
+      formData.append("file", options.file);
+      this.$emit("uploading");
+
+      return $post(options.action, formData, { headers: options.headers });
+    },
+    handleError(error) {
+      this.loading = false;
+      this.res = {
+        success: false,
+        message: error.message
+      };
+      this.$emit("upload-error", error);
+    },
+    handleSuccess(responseData) {
+      this.loading = false;
+      this.res = {
+        success: true,
+        message: "导入成功!"
+      };
+      this.$emit("upload-success", {
+        data: responseData,
+        filename: this.uploadDataDict[this.addFilenameParam]
+      });
+    },
+    handleFormatError() {
+      const content = "只支持文件格式为" + this.format.join("/");
+      this.res = {
+        success: false,
+        message: content
+      };
+      this.$emit("valid-error", this.res);
+    },
+    handleExceededSize() {
+      const content =
+        "文件大小不能超过" + Math.floor(this.maxSize / (1024 * 1024)) + "M";
+      this.res = {
+        success: false,
+        message: content
+      };
+      this.$emit("valid-error", this.res);
+    }
+  }
+};
+</script>

+ 28 - 0
src/constants/adminNavs.js

@@ -0,0 +1,28 @@
+const navs = [
+  {
+    id: "1",
+    parentId: "-1",
+    name: "超管中心",
+    url: "admin"
+  },
+  {
+    id: "2",
+    parentId: "1",
+    name: "用户管理",
+    url: "AdminUserManage"
+  },
+  {
+    id: "3",
+    parentId: "1",
+    name: "系统角色管理",
+    url: "SystemRoleManage"
+  },
+  {
+    id: "4",
+    parentId: "1",
+    name: "菜单管理",
+    url: "SchoolMenuManage"
+  }
+];
+
+export default navs;

+ 6 - 0
src/constants/enumerate.js

@@ -18,6 +18,12 @@ export const CURRENT_TYPE = {
   1: "应届"
 };
 
+// 权限类型
+export const PRIVILEGE_TYPE = {
+  MENU: "菜单",
+  URL: "操作"
+};
+
 // 基础 -------------->
 // 角色
 export const ROLE_TYPE = {

+ 59 - 0
src/modules/admin/api.js

@@ -0,0 +1,59 @@
+import { $postParam, $post } from "@/plugins/axios";
+
+// user-manage
+export const userListPage = datas => {
+  return $postParam("/api/admin/sys/user/list_customer", datas);
+};
+export const updateUser = datas => {
+  return $post("/api/admin/sys/user/save_customer", datas);
+};
+export const ableUser = ({ id, enable }) => {
+  return $post("/api/admin/sys/user/enable", { id, enable });
+};
+export const resetPwd = id => {
+  return $post("/api/admin/sys/user/reset_password", { id });
+};
+export const userRoleListPage = () => {
+  return $postParam("/api/admin/sys/role/list_to_user", {});
+};
+
+// menu-manage
+export const menuList = datas => {
+  return $postParam("/api/admin/sys/privilege/list", datas);
+};
+export const updateMenu = datas => {
+  return $post("/api/admin/sys/privilege/save", datas);
+};
+export const deleteMenu = id => {
+  return $post("/api/admin/sys/privilege/remove", { id });
+};
+
+// school-menu-manage
+export const schoolMenuTree = () => {
+  return $post("/api/admin/menu/custom/list", {});
+};
+export const schoolSelectedMenuTree = schoolId => {
+  return $postParam("/api/admin/menu/custom/get_school_custom_privileges", {
+    schoolId
+  });
+};
+export const updateSchoolMenu = ({ schoolId, privilegeIds }) => {
+  return $post("/api/admin/menu/custom/save", { schoolId, privilegeIds });
+};
+// auth-set
+// school-menu-manage
+export const authSelect = () => {
+  return $post("/api/admin/auth/select", {});
+};
+export const offlineActivation = datas => {
+  return $post("/api/admin/auth/offline/activation", datas);
+};
+export const exportDeviceInfo = () => {
+  return $post(
+    "/api/admin/auth/export/device/info",
+    {},
+    {
+      responseType: "blob"
+    }
+  );
+};

+ 159 - 0
src/modules/admin/components/ModifyPrivilege.vue

@@ -0,0 +1,159 @@
+<template>
+  <el-dialog
+    class="modify-privilege"
+    :visible.sync="modalIsShow"
+    :title="title"
+    top="10vh"
+    width="600px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @opened="visibleChange"
+  >
+    <div class="part-box part-box-pad part-box-border">
+      <el-form
+        ref="modalFormComp"
+        :model="modalForm"
+        :rules="rules"
+        label-width="100px"
+      >
+        <el-form-item prop="name" label="菜单名称:">
+          <el-input
+            v-model.trim="modalForm.name"
+            placeholder="请输入菜单名称"
+            clearable
+          ></el-input>
+        </el-form-item>
+        <el-form-item prop="url" label="URL地址:">
+          <el-input
+            v-model.trim="modalForm.url"
+            placeholder="请输入URL地址"
+            clearable
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="上级菜单:">
+          <el-input v-model.trim="modalForm.parentName" disabled></el-input>
+        </el-form-item>
+        <el-form-item label="菜单类型:">
+          <el-select
+            v-model="modalForm.type"
+            style="width: 100px;"
+            placeholder="请选择"
+          >
+            <el-option
+              v-for="(val, key) in PRIVILEGE_TYPE"
+              :key="key"
+              :value="key"
+              :label="val"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="备注:">
+          <el-input v-model.trim="modalForm.remark"></el-input>
+        </el-form-item>
+      </el-form>
+    </div>
+    <div slot="footer">
+      <el-button type="primary" :disabled="isSubmit" @click="submit"
+        >确认</el-button
+      >
+      <el-button @click="cancel">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { PRIVILEGE_TYPE } from "@/constants/enumerate";
+import { updateMenu } from "../api";
+
+const initModalForm = {
+  id: null,
+  name: "",
+  url: "",
+  parentId: null,
+  parentName: "",
+  sequence: 1,
+  type: "MENU",
+  remark: ""
+};
+
+export default {
+  name: "modify-privilege",
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "权限";
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      modalForm: {},
+      PRIVILEGE_TYPE,
+      rules: {
+        name: [
+          {
+            required: true,
+            max: 30,
+            message: "菜单名称不能超过30",
+            trigger: "change"
+          }
+        ],
+        url: [
+          {
+            required: true,
+            pattern: /^[0-9a-zA-Z/_-]{3,200}$/,
+            message:
+              "URL地址只能由字母、数字、斜线、短横线和下划线组成,长度在3-200之间",
+            trigger: "change"
+          }
+        ]
+      }
+    };
+  },
+  methods: {
+    initData(val) {
+      this.modalForm = this.$objAssign(initModalForm, val);
+    },
+    visibleChange() {
+      this.initData(this.instance);
+      this.$nextTick(() => {
+        this.$refs.modalFormComp.clearValidate();
+      });
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const datas = { ...this.modalForm };
+      const data = await updateMenu(datas).catch(() => {});
+      this.isSubmit = false;
+      if (!data) return;
+
+      if (!this.isEdit) datas.id = data;
+
+      this.$emit("confirm", datas);
+      this.cancel();
+    }
+  }
+};
+</script>

+ 147 - 0
src/modules/admin/components/ModifySystemRole.vue

@@ -0,0 +1,147 @@
+<template>
+  <el-dialog
+    class="modify-system-role"
+    :visible.sync="modalIsShow"
+    :title="title"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    fullscreen
+    @opened="visibleChange"
+  >
+    <el-form
+      ref="modalFormComp"
+      :model="modalForm"
+      :rules="rules"
+      label-position="top"
+    >
+      <el-form-item prop="name" label="角色名称:">
+        <el-input
+          v-model.trim="modalForm.name"
+          style="width:282px;"
+          placeholder="请输入角色名称"
+          clearable
+        ></el-input>
+      </el-form-item>
+      <el-form-item label="角色权限:" required></el-form-item>
+    </el-form>
+    <privilege-set
+      v-if="menus && menus.length"
+      ref="PrivilegeSet"
+      :menus="menus"
+    ></privilege-set>
+    <div slot="footer">
+      <el-button type="primary" :disabled="isSubmit" @click="submit"
+        >确认</el-button
+      >
+      <el-button @click="cancel">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { updateRole, privilegeList, roleBoundPrivileges } from "../../base/api";
+import PrivilegeSet from "../../base/components/PrivilegeSet";
+
+const initModalForm = {
+  id: null,
+  name: "",
+  privilegeIds: []
+};
+
+export default {
+  name: "modify-system-role",
+  components: { PrivilegeSet },
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      }
+    }
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "角色";
+    }
+  },
+  data() {
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      menus: [],
+      modalForm: {},
+      rules: {
+        name: [
+          {
+            required: true,
+            pattern: /^[0-9a-zA-Z\u4E00-\u9FA5]{1,20}$/,
+            message: "角色名称只能输入汉字、字母和数字,长度不能超过20",
+            trigger: "change"
+          }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getMenus();
+  },
+  methods: {
+    async getMenus() {
+      const data = await privilegeList();
+      if (data) {
+        this.menus = data.map(item => {
+          item.parentId = null;
+          return item;
+        });
+      }
+    },
+    async visibleChange() {
+      let privilegeIds = [];
+      if (this.instance.id) {
+        this.modalForm = this.$objAssign(initModalForm, this.instance);
+        privilegeIds = await roleBoundPrivileges(this.instance.id);
+        privilegeIds = privilegeIds || [];
+        this.modalForm.privilegeIds = privilegeIds;
+      } else {
+        this.modalForm = { ...initModalForm };
+      }
+      this.$nextTick(() => {
+        this.$refs.modalFormComp.clearValidate();
+        this.$refs.PrivilegeSet &&
+          this.$refs.PrivilegeSet.buildTableData(privilegeIds);
+      });
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      const privilegeIds = this.$refs.PrivilegeSet.getSelectedPrivilegeIds();
+      if (!privilegeIds.length) {
+        this.$emit("请设置角色权限!");
+        return;
+      }
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const datas = { ...this.modalForm };
+      datas.privilegeIds = privilegeIds;
+      const data = await updateRole(datas).catch(() => {});
+      this.isSubmit = false;
+      if (!data) return;
+
+      this.$emit("modified", this.modalForm);
+      this.cancel();
+    }
+  }
+};
+</script>

+ 212 - 0
src/modules/admin/components/ModifyUser.vue

@@ -0,0 +1,212 @@
+<template>
+  <el-dialog
+    class="modify-user"
+    :visible.sync="modalIsShow"
+    :title="title"
+    top="10vh"
+    width="448px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    append-to-body
+    @open="visibleChange"
+  >
+    <el-form
+      ref="modalFormComp"
+      :model="modalForm"
+      :rules="rules"
+      label-position="top"
+    >
+      <el-form-item prop="loginName" label="用户名:">
+        <el-input
+          v-model.trim="modalForm.loginName"
+          placeholder="请输入用户名"
+          :disabled="isEdit"
+        ></el-input>
+      </el-form-item>
+      <el-form-item prop="realName" label="姓名:">
+        <el-input
+          v-model.trim="modalForm.realName"
+          placeholder="请输入姓名"
+          clearable
+        ></el-input>
+      </el-form-item>
+      <el-form-item prop="mobileNumber" label="手机号:">
+        <el-input
+          v-model.trim="modalForm.mobileNumber"
+          placeholder="请输入手机号"
+          clearable
+        ></el-input>
+      </el-form-item>
+      <el-form-item prop="code" label="工号:">
+        <el-input
+          v-model.trim="modalForm.code"
+          placeholder="请输入工号"
+          clearable
+        ></el-input>
+      </el-form-item>
+      <el-form-item prop="roleIds" label="角色:">
+        <el-select
+          style="width:100%;"
+          v-model="modalForm.roleIds"
+          placeholder="请选择角色"
+          multiple
+        >
+          <el-option
+            v-for="item in roles"
+            :key="item.id"
+            :value="item.id"
+            :label="item.name"
+          >
+          </el-option>
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <div slot="footer">
+      <el-button type="primary" :disabled="isSubmit" @click="submit"
+        >确认</el-button
+      >
+      <el-button @click="cancel">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { updateUser } from "../api";
+import { phone } from "@/plugins/formRules";
+
+const initModalForm = {
+  id: "",
+  loginName: "",
+  realName: "",
+  code: "",
+  mobileNumber: "",
+  roleIds: []
+};
+
+export default {
+  name: "modify-user",
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      }
+    },
+    roles: {
+      type: Array,
+      default() {
+        return [];
+      }
+    }
+  },
+  computed: {
+    isEdit() {
+      return !!this.instance.id;
+    },
+    title() {
+      return (this.isEdit ? "编辑" : "新增") + "用户";
+    }
+  },
+  data() {
+    const roleIdsValidator = (rule, value, callback) => {
+      if (!value || !value.length) {
+        callback(new Error("请选择角色"));
+      } else {
+        callback();
+      }
+    };
+    return {
+      modalIsShow: false,
+      isSubmit: false,
+      modalForm: {},
+      rules: {
+        mobileNumber: phone,
+        loginName: [
+          {
+            required: true,
+            message: "请输入用户名",
+            trigger: "change"
+          },
+          {
+            max: 50,
+            message: "用户名不能超过50",
+            trigger: "change"
+          }
+        ],
+        realName: [
+          {
+            required: true,
+            message: "请输入姓名",
+            trigger: "change"
+          },
+          {
+            max: 50,
+            message: "姓名不能超过50",
+            trigger: "change"
+          }
+        ],
+        code: [
+          {
+            required: true,
+            message: "请输入工号",
+            trigger: "change"
+          },
+          {
+            max: 50,
+            message: "工号不能超过50",
+            trigger: "change"
+          }
+        ],
+        roleIds: [
+          {
+            required: true,
+            validator: roleIdsValidator,
+            trigger: "change"
+          }
+        ]
+      },
+      user: {},
+      courses: [],
+      roleTypes: []
+    };
+  },
+  methods: {
+    initData(val) {
+      if (val.id) {
+        this.modalForm = this.$objAssign(initModalForm, val);
+        this.modalForm.roleIds = val.roles.map(item => item.id);
+      } else {
+        this.modalForm = { ...initModalForm };
+        this.$nextTick(() => {
+          this.$refs.modalFormComp.clearValidate();
+        });
+      }
+    },
+    visibleChange() {
+      this.initData(this.instance);
+    },
+    cancel() {
+      this.modalIsShow = false;
+    },
+    open() {
+      this.modalIsShow = true;
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+
+      const datas = { ...this.modalForm };
+      const data = await updateUser(datas).catch(() => {});
+      this.isSubmit = false;
+      if (!data) return;
+
+      this.$message.success("修改成功!");
+      this.$emit("modified");
+      this.cancel();
+    }
+  }
+};
+</script>

+ 39 - 0
src/modules/admin/router.js

@@ -0,0 +1,39 @@
+import AdminUserManage from "./views/AdminUserManage.vue";
+import PrivilegeManage from "./views/PrivilegeManage.vue";
+import SystemRoleManage from "./views/SystemRoleManage.vue";
+import SchoolMenuManage from "./views/SchoolMenuManage.vue";
+import AuthSet from "./views/AuthSet.vue";
+import Admin from "./views/Admin.vue";
+
+export default {
+  path: "/admin",
+  name: "Admin",
+  component: Admin,
+  children: [
+    {
+      path: "admin-user-manage",
+      name: "AdminUserManage",
+      component: AdminUserManage
+    },
+    {
+      path: "privilege-manage",
+      name: "PrivilegeManage",
+      component: PrivilegeManage
+    },
+    {
+      path: "system-role-manage",
+      name: "SystemRoleManage",
+      component: SystemRoleManage
+    },
+    {
+      path: "school-menu-manage",
+      name: "SchoolMenuManage",
+      component: SchoolMenuManage
+    },
+    {
+      path: "auth-set",
+      name: "AuthSet",
+      component: AuthSet
+    }
+  ]
+};

+ 222 - 0
src/modules/admin/views/Admin.vue

@@ -0,0 +1,222 @@
+<template>
+  <div class="admin">
+    <div class="home-header">
+      <div class="head-user menu-list">
+        <ul>
+          <li @click="toSelectSchool">
+            <i class="el-icon-s-home"></i>
+            <span>切换学校</span>
+          </li>
+          <li class="menu-item menu-item-account" @click="toResetPwd">
+            <i class="icon icon-account"></i>
+            <span :title="username">{{ username }}</span>
+          </li>
+          <li class="menu-item" @click="toLogout">
+            <i class="icon icon-logout" title="退出登录"></i>
+          </li>
+        </ul>
+      </div>
+    </div>
+
+    <div class="home-navs">
+      <div class="head-logo">
+        <div class="head-logo-content">
+          <h1>知学知考</h1>
+        </div>
+      </div>
+      <el-menu
+        class="el-menu-home"
+        active-text-color="#705eff"
+        text-color="#383b4a"
+        router
+        :default-active="curRouteName"
+        :default-openeds="curSubMenuNames"
+      >
+        <el-submenu
+          v-for="submenu in menus"
+          :key="submenu.url"
+          :index="submenu.url"
+        >
+          <template slot="title">
+            <span>{{ submenu.name }}</span>
+          </template>
+
+          <el-menu-item
+            v-for="nav in submenu.children"
+            :key="nav.url"
+            :index="nav.url"
+            :route="{ name: nav.url }"
+          >
+            <span>{{ nav.name }}</span>
+          </el-menu-item>
+        </el-submenu>
+      </el-menu>
+    </div>
+
+    <div class="home-body">
+      <div class="home-main">
+        <div class="home-breadcrumb" v-if="breadcrumbs.length">
+          <span class="breadcrumb-tips">
+            <i class="icon icon-location"></i>
+            <span>当前所在位置:</span>
+          </span>
+          <el-breadcrumb separator="/">
+            <el-breadcrumb-item
+              v-for="(bread, index) in breadcrumbs"
+              :key="index"
+              >{{ bread.name }}</el-breadcrumb-item
+            >
+          </el-breadcrumb>
+        </div>
+
+        <!-- home-view: page detail -->
+        <div class="home-view">
+          <router-view />
+        </div>
+      </div>
+    </div>
+
+    <!-- 修改密码 -->
+    <reset-pwd
+      ref="ResetPwd"
+      :user-info="userInfo"
+      @modified="resetPwdModified"
+    ></reset-pwd>
+  </div>
+</template>
+
+<script>
+import localNavs from "@/constants/adminNavs";
+import { SYS_ADMIN_NAME } from "@/constants/enumerate";
+import { logout } from "@/modules/login/api";
+import ResetPwd from "../../base/components/ResetPwd";
+
+export default {
+  name: "home",
+  components: { ResetPwd },
+  data() {
+    const user = this.$ls.get("user", { id: "", realName: "", roleList: [] });
+
+    return {
+      menus: [],
+      privileges: [],
+      curRouteName: "",
+      curSubMenuNames: [],
+      breadcrumbs: [],
+      username: user.realName,
+      userInfo: {
+        pwdCount: 0,
+        mobileNumber: 1,
+        userId: user.id
+      }
+    };
+  },
+  watch: {
+    $route(val) {
+      if (val.name === "Admin") return;
+      this.actCurNav();
+    }
+  },
+  created() {
+    const loginName = this.$ls.get("user", { loginName: "" }).loginName;
+    if (loginName !== SYS_ADMIN_NAME) {
+      this.$message.error("非法操作!");
+      this.$router.replace({ name: "Login" });
+      return;
+    }
+
+    if (this.$route.name === "Admin") {
+      this.$router.replace({
+        name: "AdminUserManage"
+      });
+    }
+    this.initData();
+  },
+  methods: {
+    initData() {
+      this.privileges = localNavs;
+      this.menus = this.getMenu();
+      this.curSubMenuNames = this.menus.map(item => item.url);
+
+      if (this.$route.name === "Admin") {
+        const firstRouteName = this.getFirstRouter();
+        this.$router.replace({
+          name: firstRouteName
+        });
+        return;
+      }
+
+      this.actCurNav();
+    },
+    getFirstRouter() {
+      let childNavs = this.privileges;
+      let firstRouteName = "";
+      while (childNavs.length) {
+        firstRouteName = childNavs[0].url;
+        childNavs = this.privileges.filter(
+          item => item.parentId === childNavs[0].id
+        );
+      }
+
+      return firstRouteName;
+    },
+    getMenu() {
+      let menus = this.privileges.filter(item => item.parentId === "-1");
+      const toTree = menus => {
+        menus.forEach(menu => {
+          const children = this.privileges.filter(
+            item => item.parentId === menu.id
+          );
+          if (children.length) {
+            menu.children = children;
+            toTree(menu.children);
+          }
+        });
+      };
+      toTree(menus);
+
+      return menus;
+    },
+    actCurNav() {
+      this.curRouteName = this.$route.name;
+      let breadcrumbs = [];
+      let curBreadcrumb = this.privileges.find(
+        item => item.url === this.curRouteName
+      );
+      breadcrumbs.push({ ...curBreadcrumb });
+
+      while (curBreadcrumb && curBreadcrumb.parentId !== "-1") {
+        curBreadcrumb = this.privileges.find(
+          item => item.id === curBreadcrumb.parentId
+        );
+        if (curBreadcrumb) breadcrumbs.unshift({ ...curBreadcrumb });
+      }
+
+      this.breadcrumbs = breadcrumbs;
+    },
+    toLogout() {
+      this.$confirm("确定要退出登录吗?", "提示", {
+        type: "warning"
+      })
+        .then(() => {
+          this.logoutAction();
+        })
+        .catch(() => {});
+    },
+    async logoutAction() {
+      await logout();
+      this.$ls.clear();
+      this.$router.push({ name: "Login" });
+    },
+    toSelectSchool() {
+      this.$router.push({ name: "SelectSchool" });
+    },
+    toResetPwd() {
+      this.$refs.ResetPwd.open();
+    },
+    resetPwdModified() {
+      this.logoutAction();
+    }
+  }
+};
+</script>

+ 204 - 0
src/modules/admin/views/AdminUserManage.vue

@@ -0,0 +1,204 @@
+<template>
+  <div class="user-manage">
+    <div class="part-box part-box-filter part-box-flex">
+      <el-form ref="FilterForm" label-position="left" label-width="85px" inline>
+        <el-form-item label="姓名:" label-width="75px">
+          <el-input
+            style="width: 142px;"
+            v-model.trim="filter.realName"
+            placeholder="姓名"
+            clearable
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="启用/禁用:" label-width="90px">
+          <el-select
+            v-model="filter.enable"
+            style="width: 120px;"
+            placeholder="启用/禁用"
+            clearable
+          >
+            <el-option
+              v-for="(val, key) in ABLE_TYPE"
+              :key="key"
+              :value="key * 1"
+              :label="val"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label-width="0px">
+          <el-button type="primary" @click="toPage(1)">查询</el-button>
+        </el-form-item>
+      </el-form>
+      <div class="part-box-action">
+        <el-button
+          type="primary"
+          icon="el-icon-circle-plus-outline"
+          @click="toAdd"
+          >新增用户</el-button
+        >
+      </div>
+    </div>
+
+    <div class="part-box part-box-pad">
+      <el-table ref="TableList" :data="users">
+        <el-table-column
+          type="index"
+          label="序号"
+          width="70"
+          :index="indexMethod"
+        ></el-table-column>
+        <el-table-column prop="loginName" label="用户名"></el-table-column>
+        <el-table-column prop="realName" label="姓名"></el-table-column>
+        <el-table-column prop="roles" label="角色">
+          <template slot-scope="scope">
+            {{ scope.row.roles | rolesFilter }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="mobileNumber" label="手机号"></el-table-column>
+        <el-table-column prop="enable" label="状态" width="100">
+          <template slot-scope="scope">
+            {{ scope.row.enable | enableFilter }}
+          </template>
+        </el-table-column>
+        <el-table-column class-name="action-column" label="操作" width="200px">
+          <template slot-scope="scope">
+            <el-button
+              class="btn-primary"
+              type="text"
+              @click="toEdit(scope.row)"
+              >编辑</el-button
+            >
+            <el-button
+              :class="scope.row.enable ? 'btn-danger' : 'btn-primary'"
+              type="text"
+              @click="toEnable(scope.row)"
+              >{{ scope.row.enable ? "禁用" : "启用" }}</el-button
+            >
+            <el-button
+              class="btn-danger"
+              type="text"
+              @click="toResetPwd(scope.row)"
+              >重置密码</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="part-page">
+        <el-pagination
+          background
+          layout="total,prev, pager, next"
+          :current-page="current"
+          :total="total"
+          :page-size="size"
+          @current-change="toPage"
+        >
+        </el-pagination>
+      </div>
+    </div>
+
+    <!-- ModifyUser -->
+    <modify-user
+      ref="ModifyUser"
+      :instance="curUser"
+      :roles="roles"
+      @modified="getList"
+    ></modify-user>
+  </div>
+</template>
+
+<script>
+import ModifyUser from "../components/ModifyUser";
+import { ABLE_TYPE } from "@/constants/enumerate";
+import { userListPage, ableUser, resetPwd, userRoleListPage } from "../api";
+
+export default {
+  name: "user-manage",
+  components: { ModifyUser },
+  data() {
+    return {
+      filter: {
+        realName: "",
+        enable: ""
+      },
+      current: 1,
+      size: this.GLOBAL.pageSize,
+      total: 0,
+      visible: false,
+      ABLE_TYPE,
+      roles: [],
+      users: [],
+      curUser: {}
+    };
+  },
+  created() {
+    this.getRoleList();
+    this.getList();
+  },
+  methods: {
+    async getRoleList() {
+      const data = await userRoleListPage();
+      this.roles = data || [];
+      this.roles = this.roles
+        .filter(item => item.type !== "ADMIN")
+        .map(item => {
+          return {
+            id: item.id,
+            name: item.name
+          };
+        });
+    },
+    async getList() {
+      const datas = {
+        ...this.filter,
+        pageNumber: this.current,
+        pageSize: this.size
+      };
+      if (datas.enable !== null && datas.enable !== "")
+        datas.enable = !!datas.enable;
+      const data = await userListPage(datas);
+      this.users = data.records;
+      this.total = data.total;
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    toEnable(row) {
+      // 自己不可以启用/禁用自己
+      const userId = this.$ls.get("user", { id: "" }).id;
+      if (row.id === userId) {
+        this.$message.error("不可以启用/禁用自己!");
+        return;
+      }
+
+      const action = row.enable ? "禁用" : "启用";
+      this.$confirm(`确定要${action}用户【${row.realName}】吗?`, "提示", {
+        type: "warning"
+      })
+        .then(async () => {
+          const enable = !row.enable;
+          await ableUser({
+            id: row.id,
+            enable
+          });
+          row.enable = enable;
+          this.$message.success("操作成功!");
+        })
+        .catch(() => {});
+    },
+    toEdit(row) {
+      this.curUser = row;
+      this.$refs.ModifyUser.open();
+    },
+    toAdd() {
+      this.curUser = {};
+      this.$refs.ModifyUser.open();
+    },
+    async toResetPwd(row) {
+      await resetPwd(row.id);
+      this.$message.success("密码重置成功!");
+    }
+  }
+};
+</script>

+ 87 - 0
src/modules/admin/views/AuthSet.vue

@@ -0,0 +1,87 @@
+<template>
+  <div class="auth-set">
+    <div class="part-box part-box-pad">
+      <el-form ref="modalFormComp" :model="modalForm" label-width="120px">
+        <el-form-item prop="loginName" label="当前信息:">
+          {{ modalForm.expireTime === null ? "未授权" : "已授权" }}
+        </el-form-item>
+        <el-form-item label="过期时间:">
+          <span v-if="modalForm.expireTime === -1">不过期</span>
+          <span v-else-if="modalForm.expireTime">
+            {{ modalForm.expireTime | timestampFilter }}</span
+          >
+          <span v-else>--</span>
+        </el-form-item>
+        <el-form-item prop="mobileNumber" label="导出:">
+          <el-button type="primary" :loading="downloading" @click="toExport">
+            硬件信息
+          </el-button>
+        </el-form-item>
+        <el-form-item prop="code" label="导入授权文件:">
+          <upload-button
+            btn-content="选择文件"
+            btn-type="primary"
+            :upload-url="uploadUrl"
+            :format="['lic']"
+            accept=".lic"
+            style="margin:0;"
+            @valid-error="validError"
+            @upload-success="uploadSuccess"
+          >
+          </upload-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+</template>
+
+<script>
+import { downloadByApi } from "@/plugins/download";
+import { exportDeviceInfo, authSelect } from "../api";
+import UploadButton from "@/components/UploadButton";
+
+export default {
+  name: "auth-set",
+  components: { UploadButton },
+  data() {
+    return {
+      modalForm: {
+        expireTime: "",
+        authFile: null
+      },
+      downloading: false,
+      // import
+      uploadUrl: "/api/admin/auth/offline/activation"
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    async search() {
+      const res = await authSelect();
+      this.modalForm.expireTime = res;
+    },
+    async toExport() {
+      if (this.downloading) return;
+      this.downloading = true;
+
+      const res = await downloadByApi(() => {
+        return exportDeviceInfo();
+      }).catch(e => {
+        this.$message.error(e || "下载失败,请重新尝试!");
+      });
+      this.downloading = false;
+
+      if (!res) return;
+      this.$message.success("下载成功!");
+    },
+    validError(errorData) {
+      this.$message.error(errorData.message);
+    },
+    uploadSuccess() {
+      this.search();
+    }
+  }
+};
+</script>

+ 153 - 0
src/modules/admin/views/PrivilegeManage.vue

@@ -0,0 +1,153 @@
+<template>
+  <div class="privilege-manage">
+    <div class="part-box">
+      <el-button type="primary" size="mini" icon="el-icon-edit" @click="toAdd"
+        >新增</el-button
+      >
+    </div>
+    <div class="part-box part-box-pad part-box-border">
+      <el-tree
+        :data="menus"
+        node-key="id"
+        default-expand-all
+        :expand-on-click-node="false"
+        :props="defaultProps"
+      >
+        <span class="custom-tree-node" slot-scope="{ node, data }">
+          <span
+            ><i class="el-icon-link" v-if="data.type === 'URL'"></i>
+            {{ node.label }}</span
+          >
+          <span>
+            <el-button
+              v-if="data.type === 'MENU'"
+              class="btn-primary"
+              type="text"
+              icon="icon icon-plus-act"
+              @click="() => append(data)"
+              title="新增"
+            ></el-button>
+            <el-button
+              class="btn-primary"
+              type="text"
+              icon="icon icon-edit"
+              @click="() => edit(node, data)"
+              title="修改"
+            ></el-button>
+            <el-button
+              class="btn-primary"
+              type="text"
+              icon="icon icon-delete"
+              @click="() => remove(node, data)"
+              title="删除"
+            ></el-button>
+          </span>
+        </span>
+      </el-tree>
+    </div>
+
+    <!-- ModifyPrivilege -->
+    <modify-privilege
+      ref="ModifyPrivilege"
+      :instance="curMenu"
+      @confirm="menuModified"
+    ></modify-privilege>
+  </div>
+</template>
+
+<script>
+import { menuList, deleteMenu } from "../api";
+import ModifyPrivilege from "../components/ModifyPrivilege";
+
+export default {
+  name: "privilege-manage",
+  components: { ModifyPrivilege },
+  data() {
+    return {
+      menus: [],
+      curMenu: {},
+      parentNode: null,
+      parentData: null,
+      defaultProps: {
+        label: "name"
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      this.menus = await menuList();
+    },
+    toAdd() {
+      this.curMenu = {
+        sequence: this.menus.length + 1
+      };
+      this.$refs.ModifyPrivilege.open();
+    },
+    edit(node, data) {
+      this.curMenu = {
+        ...data,
+        parentName: data.parentId && node.parent.data.name
+      };
+      this.parentData = data;
+      this.$refs.ModifyPrivilege.open();
+    },
+    remove(node, data) {
+      const name = node.parent.label
+        ? `${node.parent.label}->${data.name}`
+        : data.name;
+      this.$confirm(`确定要删除【${name}】吗?`, "提示", {
+        type: "warning"
+      })
+        .then(async () => {
+          await deleteMenu(data.id);
+
+          const parent = node.parent;
+          const children = parent.data.children || parent.data;
+          const index = children.findIndex(d => d.id === data.id);
+          children.splice(index, 1);
+
+          this.$message.success("删除成功!");
+        })
+        .catch(() => {});
+    },
+    append(data) {
+      const sequences = data.children
+        ? data.children.map(item => item.sequence)
+        : [];
+      const maxSequence = sequences.length
+        ? Math.max.apply(null, sequences)
+        : 0;
+
+      this.curMenu = {
+        parentId: data.id,
+        parentName: data.name,
+        sequence: maxSequence + 1
+      };
+      this.parentData = data;
+      this.$refs.ModifyPrivilege.open();
+    },
+    menuModified(curNode) {
+      if (!this.curMenu.parentName) {
+        this.getList();
+        return;
+      }
+
+      if (this.curMenu.id) {
+        this.parentData.name = curNode.name;
+        this.parentData.url = curNode.url;
+        this.parentData.type = curNode.type;
+        this.parentData.sequence = curNode.sequence;
+        this.parentData.remark = curNode.remark;
+      } else {
+        if (!this.parentData.children) {
+          this.$set(this.parentData, "children", []);
+        }
+        this.parentData.children.push(curNode);
+      }
+    }
+  }
+};
+</script>

+ 107 - 0
src/modules/admin/views/SchoolMenuManage.vue

@@ -0,0 +1,107 @@
+<template>
+  <div class="school-menu-manage">
+    <div class="part-box part-box-filter part-box-flex">
+      <el-form ref="FilterForm" label-position="left" inline>
+        <el-form-item label="学校:">
+          <school-select
+            v-model="schoolId"
+            placeholder="请选择学校"
+            :clearable="false"
+            style="width:100%;"
+            @change="search"
+          ></school-select>
+        </el-form-item>
+      </el-form>
+      <div class="part-box-action">
+        <el-button
+          type="primary"
+          :disabled="fetching"
+          :loading="loading"
+          @click="save"
+          >保存</el-button
+        >
+      </div>
+    </div>
+
+    <div class="part-box part-box-pad">
+      <privilege-set
+        v-if="menus && menus.length"
+        ref="PrivilegeSet"
+        :menus="menus"
+      ></privilege-set>
+    </div>
+  </div>
+</template>
+
+<script>
+import {
+  schoolMenuTree,
+  schoolSelectedMenuTree,
+  updateSchoolMenu
+} from "../api";
+import PrivilegeSet from "../../base/components/PrivilegeSet";
+
+export default {
+  name: "school-menu-manage",
+  components: { PrivilegeSet },
+  data() {
+    return {
+      schoolId: "",
+      menus: [],
+      loading: false,
+      fetching: false,
+      defaultProps: {
+        label: "name"
+      }
+    };
+  },
+  mounted() {
+    this.getPrivileges();
+  },
+  methods: {
+    async getPrivileges() {
+      const needHideModules = ["common", "customer"];
+      const data = await schoolMenuTree();
+      const menus = data || [];
+
+      this.menus = menus
+        .filter(item => !needHideModules.includes(item.url))
+        .map(item => {
+          item.parentId = null;
+          return item;
+        });
+
+      this.$nextTick(() => {
+        this.$refs.PrivilegeSet.buildTableData([]);
+      });
+    },
+    async search() {
+      if (!this.schoolId) return;
+      this.fetching = true;
+      const data = await schoolSelectedMenuTree(this.schoolId).catch(() => {});
+      this.fetching = false;
+
+      const privilegeIds = data || [];
+      this.$refs.PrivilegeSet.buildTableData(privilegeIds);
+    },
+    async save() {
+      if (!this.schoolId) {
+        this.$message.error("学校必须选择!");
+        return;
+      }
+      if (this.loading) return;
+
+      this.loading = true;
+      const privilegeIds = this.$refs.PrivilegeSet.getSelectedPrivilegeIds();
+      const res = await updateSchoolMenu({
+        schoolId: this.schoolId,
+        privilegeIds: privilegeIds
+      });
+      this.loading = false;
+      if (!res) return;
+
+      this.$message.success("修改成功!");
+    }
+  }
+};
+</script>

+ 83 - 0
src/modules/admin/views/SystemRoleManage.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="role-manage">
+    <div class="part-box part-box-pad part-box-flex">
+      <div></div>
+      <el-button
+        type="primary"
+        icon="el-icon-circle-plus-outline"
+        @click="toAdd"
+        >添加角色</el-button
+      >
+    </div>
+    <div class="part-box part-box-pad">
+      <el-table ref="TableList" :data="roles">
+        <el-table-column
+          type="index"
+          label="序号"
+          width="70"
+          :index="indexMethod"
+        ></el-table-column>
+        <el-table-column prop="name" label="角色名称"></el-table-column>
+        <el-table-column class-name="action-column" label="操作" width="120px">
+          <template slot-scope="scope">
+            <el-button
+              class="btn-primary"
+              type="text"
+              @click="toEdit(scope.row)"
+              >编辑</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+    <!-- ModifySystemRole -->
+    <modify-system-role
+      ref="ModifySystemRole"
+      :instance="curRole"
+      @modified="getList"
+    ></modify-system-role>
+  </div>
+</template>
+
+<script>
+import { ABLE_TYPE } from "@/constants/enumerate";
+import { userRoleListPage } from "../api";
+import ModifySystemRole from "../components/ModifySystemRole";
+
+export default {
+  name: "role-manage",
+  components: {
+    ModifySystemRole
+  },
+  data() {
+    return {
+      roles: [],
+      curRole: {},
+      ABLE_TYPE,
+      ROLE_TYPE: {}
+    };
+  },
+  created() {
+    this.toPage(1);
+  },
+  methods: {
+    async getList() {
+      const data = await userRoleListPage();
+      this.roles = data || [];
+      this.roles = this.roles.filter(item => item.type !== "ADMIN");
+    },
+    toPage(page) {
+      this.current = page;
+      this.getList();
+    },
+    toEdit(row) {
+      this.curRole = row;
+      this.$refs.ModifySystemRole.open();
+    },
+    toAdd() {
+      this.curRole = {};
+      this.$refs.ModifySystemRole.open();
+    }
+  }
+};
+</script>

+ 3 - 0
src/modules/base/api.js

@@ -46,6 +46,9 @@ export const userBoundRoles = userId => {
 export const menuAuthList = datas => {
   return $postParam("/api/admin/sys/privilege/list_auth", datas);
 };
+export const privilegeList = datas => {
+  return $postParam("/api/admin/sys/privilege/list", datas);
+};
 export const roleBoundPrivileges = roleId => {
   return $postParam("/api/admin/sys/privilege/get_role_privileges", { roleId });
 };

+ 222 - 0
src/modules/base/components/PrivilegeSet.vue

@@ -0,0 +1,222 @@
+<template>
+  <div class="privilege-set">
+    <table class="table">
+      <tr>
+        <th v-for="(item, index) in tableHead" :key="index">{{ item }}</th>
+      </tr>
+      <tr v-for="row in tableData" :key="row.id">
+        <td v-for="(col, cindex) in row.columns" :key="cindex">
+          <div v-if="col && col.type === 'page'">{{ col.name }}</div>
+          <div v-else-if="col && col.type === 'page-checkbox'">
+            <el-checkbox
+              v-model="row.enable"
+              @change="enable => pageSelectChange(row, enable)"
+            ></el-checkbox>
+          </div>
+          <div v-else-if="col && col.type">
+            <div
+              class="cell-check-list"
+              v-for="item in col.datas"
+              :key="item.field"
+            >
+              <el-checkbox
+                v-model="item.enable"
+                @change="enable => typeSelectChange(row, enable)"
+                >{{ item.name }}</el-checkbox
+              >
+            </div>
+          </div>
+          <div v-else></div>
+        </td>
+      </tr>
+    </table>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "privilege-set",
+  props: {
+    menus: {
+      type: Array,
+      default() {
+        return [];
+      }
+    }
+  },
+  data() {
+    return {
+      maxDeep: 0,
+      tableData: [],
+      tableHead: []
+    };
+  },
+  created() {
+    this.initData();
+  },
+  methods: {
+    initData() {
+      this.maxDeep = this.getNavsDeep();
+      this.tableHead = this.buildTableHead();
+      // this.buildTableData();
+    },
+    getNavsDeep() {
+      let maxDeep = 0;
+      const getDeep = (navs, deep) => {
+        ++deep;
+        navs.forEach(nav => {
+          if (maxDeep < deep) maxDeep = deep;
+
+          if (nav.children && nav.children.length) getDeep(nav.children, deep);
+        });
+      };
+      getDeep(this.menus, maxDeep);
+
+      return maxDeep;
+    },
+    buildTableHead() {
+      let headers = [];
+      let codes = ["一", "二", "三", "四", "五", "六", "七", "八"];
+      for (let index = 0; index < this.maxDeep; index++) {
+        headers.push(`${codes[index]}级页面`);
+      }
+      headers = [
+        ...headers,
+        "页面",
+        "查询条件",
+        "功能按钮",
+        "列表展示",
+        "操作列"
+      ];
+      return headers;
+    },
+    buildTableData(privilegeIds = []) {
+      let tableData = [];
+      let tableColumnCount = this.maxDeep + 5;
+      const pageSetTypes = ["conditions", "buttons", "lists", "links"];
+      const buildData = (navs, deep) => {
+        ++deep;
+        navs.forEach(nav => {
+          let columns = new Array(tableColumnCount);
+          columns[deep - 1] = { type: "page", name: nav.name };
+          columns[this.maxDeep] = {
+            type: "page-checkbox"
+          };
+
+          const isPage = pageSetTypes.some(
+            type => nav[type] && nav[type].length
+          );
+          if (isPage) {
+            pageSetTypes.forEach((type, index) => {
+              const datas = !nav[type]
+                ? []
+                : nav[type].map(elem => {
+                    let data = { ...elem };
+                    data.enable = privilegeIds.includes(elem.id);
+                    return data;
+                  });
+              columns[this.maxDeep + index + 1] = { type, datas };
+            });
+          }
+
+          tableData.push({
+            id: nav.id,
+            name: nav.name,
+            enable: privilegeIds.includes(nav.id),
+            type: nav.type,
+            parentId: nav.parentId,
+            isPage,
+            columns
+          });
+
+          if (nav.children && nav.children.length)
+            buildData(nav.children, deep);
+        });
+      };
+      buildData(this.menus, 0);
+
+      this.tableData = tableData;
+    },
+    getSelectedPrivilegeIds() {
+      let privilegeIds = [];
+      this.tableData
+        .filter(row => row.enable)
+        .forEach(row => {
+          privilegeIds.push(row.id);
+          row.columns.forEach(column => {
+            if (column.type === "page" || column.type === "page-checkbox")
+              return;
+
+            column.datas.forEach(item => {
+              if (item.enable) privilegeIds.push(item.id);
+            });
+          });
+        });
+      return privilegeIds;
+    },
+    // set change
+    pageSelectChange(row, enable) {
+      this.changRowColumnEnable(row, enable);
+      this.changeParentNodeSelected(row.parentId, enable);
+      this.changeChildrenNodeSelected(row.id, enable);
+    },
+    typeSelectChange(row, enable) {
+      if (!row.enable && enable) {
+        row.enable = enable;
+        this.changeParentNodeSelected(row.parentId, enable);
+        this.changeChildrenNodeSelected(row.id, enable);
+      }
+    },
+    changRowColumnEnable(row, enable) {
+      if (!row.isPage) return;
+      row.columns.forEach(column => {
+        if (column.type === "page" || column.type === "page-checkbox") return;
+
+        column.datas.forEach(item => {
+          item.enable = enable;
+        });
+      });
+    },
+    changeParentNodeSelected(parentId, enable) {
+      if (!parentId) return;
+      let curParentId = parentId;
+      if (enable) {
+        while (curParentId) {
+          let curParentNode = this.tableData.find(
+            row => row.id === curParentId
+          );
+          curParentNode.enable = enable;
+          curParentId = curParentNode.parentId;
+          this.changRowColumnEnable(curParentNode, enable);
+        }
+      } else {
+        while (curParentId) {
+          let curParentNode = this.tableData.find(
+            row => row.id === curParentId
+          );
+          let childrenHasOneSelected = this.tableData
+            .filter(row => row.parentId === curParentId)
+            .some(row => row.enable);
+          curParentNode.enable = childrenHasOneSelected;
+          curParentId = curParentNode.parentId;
+          this.changRowColumnEnable(curParentNode, enable);
+        }
+      }
+    },
+    changeChildrenNodeSelected(id, enable) {
+      if (!id) return;
+      let curIds = [id];
+      while (curIds.length) {
+        const validNodes = this.tableData.filter(row =>
+          curIds.includes(row.parentId)
+        );
+        validNodes.forEach(row => {
+          row.enable = enable;
+          this.changRowColumnEnable(row, enable);
+        });
+        curIds = validNodes.map(row => row.id);
+      }
+    }
+  }
+};
+</script>

+ 2 - 1
src/modules/login/views/SelectSchool.vue

@@ -1,10 +1,11 @@
 <template>
   <div class="select-school login login-box">
+    <div class="login-theme"></div>
     <div class="login-body">
       <div class="login-title">
         <h1>选择学校</h1>
       </div>
-      <div class="login-form">
+      <div class="school-form">
         <el-form ref="modalFormComp" :model="modalForm" :rules="rules">
           <el-form-item prop="schoolId">
             <school-select

+ 12 - 0
src/plugins/filters.js

@@ -18,3 +18,15 @@ Vue.filter("timestampFilter", function(val) {
     ? formatDate("YYYY-MM-DD HH:mm:ss", new Date(val * 1))
     : DEFAULT_FIELD;
 });
+Vue.filter("orgsFilter", function(val) {
+  return val.map(item => item.name).join(",");
+});
+Vue.filter("rolesFilter", function(val) {
+  return val.map(item => item.name).join(",");
+});
+Vue.filter("coursesFilter", function(val) {
+  return val.map(item => `${item.name}(${item.code})`).join(",");
+});
+Vue.filter("enableFilter", function(val) {
+  return val ? "启用" : "禁用";
+});

+ 2 - 1
src/router.js

@@ -4,7 +4,7 @@ import Router from "vue-router";
 import Home from "@/views/Home.vue";
 import NotFound from "@/views/404.vue";
 import login from "@/modules/login/router";
-// import admin from "@/modules/admin/router";
+import admin from "@/modules/admin/router";
 
 import college from "./modules/college/router";
 import base from "./modules/base/router";
@@ -44,6 +44,7 @@ let router = new Router({
       children: [...college, ...base]
     },
     { ...login },
+    { ...admin },
     ...student,
     {
       path: "*",