Browse Source

用户管理和组件

YY 2 years ago
parent
commit
a31b8eb228

+ 50 - 0
src/components/e-dialog.vue

@@ -0,0 +1,50 @@
+<template>
+  <div id="">
+    <el-dialog :title="dialog.title" :visible.sync="dialog.show" :width="width" :before-close="toClose" :close-on-click-modal="false" :append-to-body="true">
+      <slot name="info"></slot>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'e-dialog',
+  props: {
+    dialog: { type: Object, default: () => {} },
+    width: { type: String, default: '40%' },
+  },
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {
+    toClose() {
+      this.$emit('toClose');
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+/deep/.el-dialog__body {
+  padding: 10px;
+  min-height: 30px;
+  max-height: 400px;
+  overflow-y: auto;
+}
+</style>

+ 158 - 0
src/layout/btn-1.vue

@@ -0,0 +1,158 @@
+<template>
+  <div id="btn-1">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <template v-if="vOpera">
+            <el-button v-opera="{ method: 'add' }" type="primary" plain icon="el-icon-plus" size="mini" @click="toAdd()">新增</el-button>
+            <el-button v-opera="{ method: 'edit' }" type="success" plain icon="el-icon-edit" size="mini" :disabled="true">修改</el-button>
+            <el-button v-opera="{ method: 'del' }" type="danger" plain icon="el-icon-delete" size="mini" @click="toDel()">删除</el-button>
+            <el-button v-opera="{ method: 'others' }" type="warning" plain icon="el-icon-download" size="mini" @click="toExport()">导出</el-button>
+          </template>
+          <template v-else>
+            <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="toAdd()">新增</el-button>
+            <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="true">修改</el-button>
+            <el-button type="danger" plain icon="el-icon-delete" size="mini" @click="toDel()">删除</el-button>
+            <el-button type="warning" plain icon="el-icon-download" size="mini" @click="toExport()">导出</el-button>
+          </template>
+        </el-col>
+      </el-col>
+    </el-row>
+    <e-dialog :dialog="dialog" @toClose="toClose">
+      <template v-slot:info>
+        <el-col :span="24" class="dailog_one" v-if="dialog.type == '1'">
+          <el-col :span="24" class="one_1">
+            <el-transfer v-model="selectList" :titles="['未选择', '已选择']" :button-texts="['取消', '确认']" :data="columnList" target-order="push">
+              <el-col :span="24" slot-scope="{ option }">
+                <el-col :span="24" class="textOver">{{ option.zh }}</el-col>
+              </el-col>
+            </el-transfer>
+          </el-col>
+          <el-col :span="24" class="one_2">
+            <el-button type="primary" size="mini" @click="onSubmit()"> 确认导出 </el-button>
+          </el-col>
+        </el-col>
+      </template>
+    </e-dialog>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions } = createNamespacedHelpers('usual');
+export default {
+  name: 'btn-1',
+  props: {
+    table: { type: String },
+    ids: { type: Array },
+    vOpera: { type: Boolean, default: true }, // 是否受指令控制
+  },
+  components: {},
+  data: function () {
+    return {
+      dialog: { title: '导出列信息管理', show: false, type: '1' },
+      columnList: [],
+      selectList: [],
+    };
+  },
+  created() {},
+  methods: {
+    ...mapActions(['selectkey', 'selectExp', 'selectDel']),
+    // 新增
+    toAdd() {
+      this.$emit('toAdd');
+    },
+    // 删除
+    async toDel() {
+      let arr = this.ids.map((i) => i._id);
+      if (!arr.length > 0) {
+        this.$message({ type: `error`, message: `暂无数据可批量删除` });
+      } else {
+        this.$confirm('是否确认删除数据项?', '警告', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning',
+        })
+          .then(async () => {
+            let res = await this.selectDel({ table: this.table, ids: arr });
+            if (this.$checkRes(res)) {
+              this.$message({ type: `success`, message: `批量删除成功` });
+              this.$emit('search');
+            }
+          })
+          .catch(() => {});
+      }
+    },
+    // 导出
+    async toExport() {
+      this.$confirm('是否确认导出所有角色数据项?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(async () => {
+          let res = await this.selectkey({ table: this.table });
+          if (this.$checkRes(res)) {
+            this.$set(this, `columnList`, res.data);
+            this.dialog = { title: '导出列信息管理', show: true, type: '1' };
+          }
+        })
+        .catch(() => {});
+    },
+    // 确认导出
+    async onSubmit() {
+      let dup = { table: this.table, config: [] };
+      if (this.selectList.length > 0) {
+        for (const val of this.selectList) {
+          let p1 = this.columnList.find((i) => i.key == val);
+          if (p1) dup.config.push(p1);
+        }
+        let res = await this.selectExp(dup);
+        if (this.$checkRes(res)) {
+          window.open(`${res.data}`, '_blank');
+          this.toClose();
+        }
+      } else {
+        this.$message({ type: `error`, message: `暂无数据可批量导出` });
+      }
+    },
+    // 关闭
+    toClose() {
+      this.dialog = { title: '导出列信息管理', show: false, type: '1' };
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.dialog {
+  /deep/.el-dialog__body {
+    padding: 10px;
+    min-height: 30px;
+    max-height: 400px;
+    overflow-y: auto;
+  }
+}
+.dailog_one {
+  .one_1 {
+    padding: 0 70px;
+    margin: 0 0 10px 0;
+  }
+  .one_2 {
+    text-align: center;
+  }
+}
+</style>

+ 6 - 0
src/layout/data/menu.js

@@ -153,6 +153,12 @@ export const devMenu = [
         name: '菜单管理',
         index: '100-2',
       },
+      {
+        icon: 'icon-rencai',
+        path: '/dev/role',
+        name: '角色管理',
+        index: '100-3',
+      },
     ],
   },
 ];

+ 2 - 0
src/plugins/components.js

@@ -4,12 +4,14 @@ import dataForm from '@/components/usual/c-form.vue';
 import dataUpload from '@/components/usual/c-upload.vue';
 import dataSearch from '@/components/usual/c-search.vue';
 import dataBtn from '@/components/usual/c-btnbar.vue';
+import eDialog from '@/components/e-dialog.vue';
 const Plugin = (vue) => {
   vue.component('data-table', dataTable);
   vue.component('data-form', dataForm);
   vue.component('data-upload', dataUpload);
   vue.component('data-search', dataSearch);
   vue.component('data-btn', dataBtn);
+  vue.component('e-dialog', eDialog);
 };
 
 Vue.use(Plugin);

+ 6 - 0
src/router/module/dev.js

@@ -17,4 +17,10 @@ export default [
     meta: { title: '开发设置-菜单管理' },
     component: () => import(/* webpackChunkName: "dev_menu" */ '@/views/dev/menu/index.vue'),
   },
+  {
+    path: '/dev/role',
+    name: 'dev_role',
+    meta: { title: '开发设置-角色管理' },
+    component: () => import(/* webpackChunkName: "dev_role" */ '@/views/dev/role/index.vue'),
+  },
 ];

+ 255 - 6
src/views/dev/menu/index.vue

@@ -1,24 +1,273 @@
 <template>
   <div id="index">
     <el-row>
-      <el-col :span="24" class="main"> test </el-col>
+      <el-col :span="24" style="text-align: right; padding: 10px">
+        <el-button type="primary" size="small" @click="toAdd">添加</el-button>
+      </el-col>
+      <el-col :span="24">
+        <el-table :data="list" row-key="_id" border>
+          <el-table-column align="center" label="菜单名称" prop="name"></el-table-column>
+          <el-table-column align="center" label="父级菜单" prop="parent_name"></el-table-column>
+          <el-table-column align="center" label="图标" width="80">
+            <template #default="{ row }"><span :class="['iconfont', row.icon]"></span></template>
+          </el-table-column>
+          <el-table-column align="center" label="顺序" sortable prop="order_num" width="80"></el-table-column>
+          <el-table-column align="center" label="路由地址" prop="path"></el-table-column>
+          <el-table-column align="center" label="组件地址" prop="component"></el-table-column>
+          <el-table-column align="center" label="菜单类型" prop="type">
+            <template #default="{ row }">{{ getType(row) }} </template>
+          </el-table-column>
+          <el-table-column align="center" label="状态" prop="status" width="50">
+            <template #default="{ row }">{{ getStatus(row) }} </template>
+          </el-table-column>
+          <el-table-column align="center" label="备注" prop="remark"> </el-table-column>
+          <el-table-column align="center" label="操作">
+            <template #default="{ row }">
+              <el-link :underline="false" type="primary" size="mini" @click="toUpdate(row)" style="margin-right: 10px">修改</el-link>
+              <el-link :underline="false" type="primary" size="mini" @click="toAddNext(row)" style="margin-right: 10px">添加下一级</el-link>
+              <el-link :underline="false" type="danger" size="mini" @click="toDelete(row)">删除</el-link>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-col>
     </el-row>
+    <el-dialog :visible.sync="dialog" title="菜单信息" :destroy-on-close="false" @close="toClose">
+      <el-tabs v-model="tab" type="card">
+        <el-tab-pane label="基本信息" name="basic">
+          <el-form label-position="left" label-width="120px">
+            <el-form-item label="菜单名称">
+              <el-input v-model="form.name" placeholder="请填写菜单名称"></el-input>
+            </el-form-item>
+            <el-form-item label="菜单类型">
+              <el-select v-model="form.type" placeholder="请选择菜单类型">
+                <el-option v-for="(i, index) in typeList" :key="`t${index}`" :label="i.label" :value="i.value"></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="父级菜单">
+              <el-select v-model="form.parent_id" placeholder="" :disabled="true">
+                <el-option v-for="(i, index) in getOneDimensionList()" :key="`m${index}`" :label="i.name" :value="i._id"></el-option>
+              </el-select>
+            </el-form-item>
+            <template v-if="form.type === '1' || form.type === '2'">
+              <el-form-item label="路由地址">
+                <el-input v-model="form.path" placeholder="请填写路由地址"></el-input>
+              </el-form-item>
+              <el-form-item label="组件地址">
+                <el-input v-model="form.component" placeholder="请填写组件地址"></el-input>
+              </el-form-item>
+            </template>
+            <el-form-item label="顺序">
+              <el-input-number v-model="form.order_num"></el-input-number>
+            </el-form-item>
+            <el-form-item label="图标">
+              <el-select v-model="form.icon" clearable filterable placeholder="请选择图标">
+                <el-option v-for="item in iconList" :key="item.dict_label" :label="item.dict_label" :value="item.dict_label">
+                  <span style="float: left" :class="['iconfont', item.dict_label]"></span>
+                  <span style="float: right; color: #8492a6; font-size: 13px">{{ item.dict_label }}</span>
+                </el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="状态">
+              <el-radio-group v-model="form.status">
+                <el-radio label="0">使用</el-radio>
+                <el-radio label="1">禁用</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="备注">
+              <el-input v-model="form.remark" placeholder="请输入备注" type="textarea" :autosize="{ minRows: 5, maxRows: 5 }"></el-input>
+            </el-form-item>
+            <!-- <el-form-item>
+              
+            </el-form-item> -->
+          </el-form>
+        </el-tab-pane>
+        <el-tab-pane label="设置" name="config" v-if="form.type && form.type !== '0'">
+          <el-row>
+            <el-col :span="24" style="text-align: right; margin: 10px 0">
+              <el-button size="mini" type="primary" @click="toAddConfig()">添加功能</el-button>
+            </el-col>
+            <el-col :span="24">
+              <el-table :data="form.config">
+                <el-table-column align="center" label="中文">
+                  <template #default="{ row }">
+                    <el-input v-model="row.zh"></el-input>
+                  </template>
+                </el-table-column>
+                <el-table-column align="center" label="code">
+                  <template #default="{ row }">
+                    <el-input v-model="row.code"></el-input>
+                  </template>
+                </el-table-column>
+                <el-table-column align="center" label="操作">
+                  <template #default="{ $index }">
+                    <el-button size="mini" type="danger" @click="deleteConfig($index)">删除</el-button>
+                  </template>
+                </el-table-column>
+              </el-table>
+            </el-col>
+          </el-row>
+        </el-tab-pane>
+      </el-tabs>
+      <el-row type="flex" justify="space-around" style="margin-top: 10px">
+        <el-col :span="6">
+          <el-button @click="toSave" size="small" type="primary">保存</el-button>
+        </el-col>
+      </el-row>
+    </el-dialog>
   </div>
 </template>
 
 <script>
+const _ = require('lodash');
 import { mapState, createNamespacedHelpers } from 'vuex';
+// const { mapActions: maM } = createNamespacedHelpers('menus');
+// const { mapActions: sysdictdata } = createNamespacedHelpers('dictData');
+
 export default {
   name: 'index',
   props: {},
   components: {},
   data: function () {
-    return {};
+    return {
+      list: [],
+      form: {},
+      dialog: false,
+      typeList: [
+        { label: '目录', value: '0' },
+        { label: '菜单', value: '1' },
+        { label: '子页面', value: '2' },
+      ],
+      // 图标列表
+      iconList: [],
+      tab: 'basic',
+    };
+  },
+  async created() {
+    this.searchOther();
+    this.search();
+    await this.initRouter();
   },
-  created() {},
-  methods: {},
-  computed: {
-    ...mapState(['user']),
+  methods: {
+    // ...sysdictdata({ sQuery: 'query' }),
+    // ...maM(['query', 'create', 'update', 'delete']),
+    async search() {
+      // let res = await this.query();
+      // if (this.$checkRes(res)) {
+      //   this.$set(this, `list`, res.data);
+      // }
+    },
+    toAdd() {
+      this.dialog = true;
+      this.form = { config: [] };
+    },
+    toUpdate(row) {
+      if (!row.config) row.config = [];
+      this.$set(this, `form`, _.cloneDeep(row));
+      this.dialog = true;
+    },
+    toAddNext(row) {
+      const obj = { parent_id: row._id };
+      this.$set(this, `form`, obj);
+      this.dialog = true;
+    },
+    async toSave() {
+      const data = _.cloneDeep(this.form);
+      let res;
+      if (data._id) res = await this.update(data);
+      else res = await this.create(data);
+      if (this.$checkRes(res, '操作成功', '操作失败')) {
+        this.toClose();
+        this.search();
+      }
+    },
+    async toDelete(row) {
+      this.$confirm('删除该菜单吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(async () => {
+          let res = await this.delete(row._id);
+          if (this.$checkRes(res, '删除成功', '删除失败')) {
+            this.search();
+          }
+        })
+        .catch(() => {});
+    },
+    toClose() {
+      this.form = {};
+      this.tab = 'basic';
+      this.dialog = false;
+    },
+    getType(row) {
+      let word = '';
+      const r = this.typeList.find((f) => f.value === row.type);
+      if (r) word = r.label;
+      return word;
+    },
+    getStatus(row) {
+      let word = '';
+      switch (row.status) {
+        case '0':
+          word = '使用';
+          break;
+        case '1':
+          word = '禁用';
+          break;
+
+        default:
+          break;
+      }
+      return word;
+    },
+    // 获取一维(平面)的菜单数据
+    getOneDimensionList() {
+      let dup = _.cloneDeep(this.list);
+      let arr = this.getAllChild(dup);
+      return arr;
+    },
+    // 获取所有的子菜单
+    getAllChild(children) {
+      let arr = [];
+      for (const i of children) {
+        const { children, ...others } = i;
+        arr.push(others);
+        if (children) {
+          const marr = this.getAllChild(children);
+          arr.push(...marr);
+        }
+      }
+      return arr;
+    },
+
+    async initRouter() {
+      // const obj = {
+      //   path: '/adminCenter/homeIndex',
+      //   name: 'adminCenter',
+      //   component: (resolve) => require([`@common/src/components/admin-frame/Home.vue`], resolve),
+      //   children: [{ path: '/system/user', meta: { title: '用户管理' }, component: this.loadView('/system/user/index') }],
+      // };
+      // console.log(obj);
+      // router.addRoute(obj);
+    },
+    loadView(view) {
+      // 路由懒加载
+      return (resolve) => require([`@/views${view}.vue`], resolve);
+    },
+    // 查询其他信息
+    async searchOther() {
+      // let res = await this.sQuery({ dict_type: 'info_icon' });
+      // if (this.$checkRes(res)) {
+      //   this.$set(this, `iconList`, res.data);
+      // }
+    },
+
+    toAddConfig() {
+      this.form.config.push({});
+    },
+    deleteConfig(index) {
+      this.form.config.splice(index, 1);
+    },
   },
   metaInfo() {
     return { title: this.$route.meta.title };

+ 178 - 0
src/views/dev/role/index.vue

@@ -0,0 +1,178 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight">
+        <el-col :span="24" class="one">
+          <search-1 :form="searchForm" @onSubmit="search" @toReset="toClose"></search-1>
+        </el-col>
+        <el-col :span="24" class="two">
+          <btn-1 table="role" @toAdd="toAdd" :ids="selected" @search="search"></btn-1>
+        </el-col>
+        <el-col :span="24" class="thr">
+          <data-table
+            :fields="fields"
+            :opera="opera"
+            :data="list"
+            @query="search"
+            :total="total"
+            @edit="toEdit"
+            @del="toDel"
+            :select="true"
+            :selected="selected"
+            @handleSelect="handleSelect"
+          >
+          </data-table>
+        </el-col>
+      </el-col>
+    </el-row>
+    <e-dialog :dialog="dialog" @toClose="toClose">
+      <template v-slot:info>
+        <form-1 v-if="dialog.type == '1'" :form="form" @onSubmit="onSubmit" @toReset="toClose" :menuList="menuList" :typeList="typeList"></form-1>
+      </template>
+    </e-dialog>
+  </div>
+</template>
+
+<script>
+import btn1 from '@/layout/btn-1.vue';
+import search1 from './parts/search-1.vue';
+import form1 from './parts/form-1.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+// const { mapActions } = createNamespacedHelpers('role');
+// const { mapActions: menu } = createNamespacedHelpers('menus');
+// const { mapActions: sysdictdata } = createNamespacedHelpers('dictData');
+export default {
+  name: 'index',
+  props: {},
+  components: { search1, form1, btn1 },
+  data: function () {
+    return {
+      // 列表
+      opera: [
+        { label: '修改', method: 'edit' },
+        { label: '删除', method: 'del', confirm: true, type: 'danger' },
+      ],
+      fields: [
+        { label: '角色名称', prop: 'name' },
+        { label: '角色代码', prop: 'code' },
+        {
+          label: '角色状态',
+          prop: 'status',
+          format: (i) => (i === '0' ? '使用' : '停用'),
+        },
+      ],
+      list: [],
+      total: 0,
+      // 多选值
+      selected: [],
+      // 弹框
+      dialog: { title: '信息管理', show: false, type: '1' },
+      // 增加数据
+      form: {},
+      // 查詢
+      searchForm: {},
+      // 菜单
+      menuList: [],
+      // 角色类型
+      typeList: [],
+    };
+  },
+  async created() {
+    await this.searchOther();
+    await this.search();
+  },
+  methods: {
+    // ...menu({ menuQuery: 'query' }),
+    // ...sysdictdata({ sQuery: 'query' }),
+    // ...mapActions(['query', 'fetch', 'create', 'update', 'delete']),
+    // 查询
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const condition = _.cloneDeep(this.searchForm);
+      // let res = await this.query({ skip, limit, ...condition, ...info });
+      // if (this.$checkRes(res)) {
+      //   this.$set(this, `list`, res.data);
+      //   this.$set(this, `total`, res.total);
+      // }
+    },
+    // 新增
+    toAdd() {
+      this.dialog = { title: '信息管理', show: true, type: '1' };
+    },
+    // 修改
+    async toEdit({ data }) {
+      let res = await this.fetch(data._id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+        this.dialog = { title: '信息管理', show: true, type: '1' };
+      }
+    },
+    // 提交
+    async onSubmit({ data }) {
+      if (data.id) {
+        let res = await this.update(data);
+        if (this.$checkRes(res)) {
+          this.$message({ type: `success`, message: `维护信息成功` });
+          this.toClose();
+        }
+      } else {
+        let res = await this.create(data);
+        if (this.$checkRes(res)) {
+          this.$message({ type: `success`, message: `创建信息成功` });
+          this.toClose();
+        }
+      }
+    },
+    // 删除
+    async toDel({ data }) {
+      let res = await this.delete(data._id);
+      if (this.$checkRes(res)) {
+        this.$message({ type: `success`, message: `刪除信息成功` });
+        this.search();
+      }
+    },
+    // 关闭
+    toClose() {
+      this.form = {};
+      this.searchForm = {};
+      this.dialog = { title: '信息管理', show: false, type: '1' };
+      this.search();
+    },
+    // 多选
+    handleSelect(data) {
+      this.$set(this, `selected`, data);
+    },
+    // 查询其他信息
+    async searchOther() {
+      // let res = await this.menuQuery({ status: '0' });
+      // if (this.$checkRes(res)) {
+      //   let data = res.data;
+      //   this.$set(this, `menuList`, res.data);
+      // }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    margin: 0 0 10px 0;
+  }
+  .two {
+    margin: 0 0 10px 0;
+  }
+}
+</style>

+ 243 - 0
src/views/dev/role/parts/form-1.vue

@@ -0,0 +1,243 @@
+<template>
+  <div id="form-1">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-form :model="form" :rules="rules" ref="form" label-width="100px">
+          <el-form-item label="名称" prop="name">
+            <el-input v-model="form.name" placeholder="请输入名称"></el-input>
+          </el-form-item>
+          <el-form-item label="简介" prop="brief">
+            <el-input v-model="form.brief" placeholder="请输入简介"></el-input>
+          </el-form-item>
+          <el-form-item label="角色编码" prop="code">
+            <el-input v-model="form.code" placeholder="请输入角色编码"></el-input>
+          </el-form-item>
+          <el-form-item label="菜单" prop="menu">
+            <div><el-checkbox v-model="allMenu" @change="treeDisabled">所有权限</el-checkbox></div>
+            <el-tree
+              :data="menuList"
+              show-checkbox
+              default-expand-all
+              node-key="path"
+              ref="tree"
+              highlight-current
+              :props="defaultProps"
+              :expand-on-click-node="false"
+              @check="nodeSelect"
+            >
+              <template #default="{ node, data }">
+                <span class="custom-tree-node">
+                  <span>{{ data.name }}</span>
+                  <span>
+                    <el-button type="text" size="mini" @click="toUpdateRole(node, data)"> 修改权限 </el-button>
+                  </span>
+                </span>
+              </template>
+            </el-tree>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" size="mini" @click="onSubmit('form')">确定</el-button>
+            <el-button type="danger" size="mini" @click="toReset('form')">取消</el-button>
+          </el-form-item>
+        </el-form>
+      </el-col>
+    </el-row>
+    <el-dialog append-to-body :visible.sync="show" title="权限分配">
+      <el-form>
+        <el-form-item label="权限控制">
+          <el-radio-group v-model="operaData.mode" @change="operaModelSelect">
+            <el-radio label="allow">全部允许</el-radio>
+            <el-radio label="refuse">全部禁止</el-radio>
+            <el-radio :label="undefined">自定义</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="权限选择">
+          <el-checkbox-group v-model="operaData.opera" @change="operaSelect">
+            <el-checkbox v-for="i in opera" :key="i.code" :label="i.code">{{ i.zh }}</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="saveOpera">保存权限</el-button>
+        </el-form-item>
+      </el-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'form-1',
+  props: { form: { type: Object }, menuList: { type: Array }, typeList: { type: Array } },
+  components: {},
+  data: function () {
+    return {
+      rules: {
+        // name: [{ required: true, message: '请输入角色名称', trigger: 'blur' }],
+      },
+      defaultProps: {
+        children: 'children',
+        label: 'name',
+        disabled: true,
+      },
+      allMenu: false,
+      show: false,
+      opera: [],
+      operaData: {
+        menu_id: '',
+        mode: '',
+        opera: [],
+      },
+    };
+  },
+  created() {},
+  methods: {
+    toUpdateRole(node, data) {
+      if (!node.checked) {
+        this.$message.warning('您没有使用此菜单,无需设置权限');
+        return;
+      }
+      const { path: id, config = [] } = data;
+      if (config.length <= 0) {
+        this.$message.warning('该页面没有可操作事务,无需设置权限');
+        return;
+      }
+      this.$set(this, 'opera', config);
+      const menuConfig = this.form.menu[id];
+      let obj;
+      if (!menuConfig) {
+        obj = { mode: '', opera: [], menu_id: id };
+      } else {
+        const { opera, mode } = menuConfig;
+        obj = { menu_id: id, opera, mode };
+      }
+      this.$set(this, 'operaData', obj);
+      this.show = true;
+    },
+    // 所有权限,直接修改form
+    treeDisabled(data) {
+      let keys = this.$refs.tree.getCheckedKeys();
+      for (const key of keys) {
+        this.$refs.tree.setChecked(key, false, true);
+      }
+      if (data) {
+        this.form.menu = { mode: 'all' };
+      }
+    },
+    // 节点选择直接同步到form中
+    nodeSelect(data, { checkedKeys }) {
+      if (checkedKeys.length <= 0) return;
+      this.allMenu = false;
+      const menu = _.get(this.form, 'menu', {});
+      const newMenu = { mode: 'select' };
+      // 原有设置的菜单id
+      let keys = Object.keys(menu);
+      // 过滤掉mode key,这东西不用管
+      keys = keys.filter((f) => f === 'mode');
+      for (const key of keys) {
+        // 找下 原key是否还在现在的已选择列表中
+        const r = checkedKeys.find((f) => f === key);
+        // 不在:说明被取消了,应该删除
+        if (!r) continue;
+        // 还在,那就直接把它拿出来放到新的里面
+        newMenu[key] = menu[key];
+      }
+      // 在过滤一次,查看新加的
+      for (const id of checkedKeys) {
+        if (newMenu[id]) continue;
+        newMenu[id] = { opera: [] };
+      }
+      this.$set(this.form, 'menu', newMenu);
+    },
+    onSubmit(formName) {
+      this.$refs[formName].validate((valid) => {
+        if (valid) {
+          this.$emit('onSubmit', { data: this.form });
+        } else {
+          console.log('error submit!!');
+          return false;
+        }
+      });
+    },
+    toReset(formName) {
+      this.$refs[formName].resetFields();
+      this.$emit('toReset');
+    },
+    getOneLevelMenus(menus) {
+      if (!menus) menus = _.cloneDeep(this.menuList);
+      const arr = [];
+      for (const menu of menus) {
+        const { children, ...others } = menu;
+        arr.push(others);
+        if (children) {
+          const cArr = this.getOneLevelMenus(children);
+          arr.push(...cArr);
+        }
+      }
+      return arr;
+    },
+    operaModelSelect(data) {
+      if (data) this.operaData.opera = [];
+    },
+    operaSelect(data) {
+      if (data.length > 0) {
+        this.operaData.mode = undefined;
+      }
+    },
+    saveOpera() {
+      let dup = _.cloneDeep(this.operaData);
+      const { menu_id: id, ...operas } = dup;
+      this.form.menu[id] = operas;
+      this.show = false;
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    form: {
+      deep: true,
+      immediate: true,
+      handler(val) {
+        const menu = _.get(val, 'menu');
+        if (!menu) return;
+        const { mode } = menu;
+        this.$nextTick(() => {
+          const keys = this.$refs.tree.getCheckedKeys();
+          for (const key of keys) {
+            this.$refs.tree.setChecked(key, false, true);
+          }
+        });
+
+        if (mode === 'all') {
+          this.allMenu = true;
+          return;
+        } else {
+          this.allMenu = false;
+          let keys = Object.keys(menu);
+          keys = keys.filter((f) => f !== 'mode');
+          this.$nextTick(() => {
+            for (const key of keys) {
+              this.$refs.tree.setChecked(key, true);
+            }
+          });
+        }
+      },
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.custom-tree-node {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 14px;
+  padding-right: 8px;
+}
+</style>

+ 62 - 0
src/views/dev/role/parts/search-1.vue

@@ -0,0 +1,62 @@
+<template>
+  <div id="search-1">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-form :model="form" ref="form" label-width="100px">
+          <el-col :span="6">
+            <el-form-item label="名称" prop="name">
+              <el-input v-model="form.name" placeholder="请输入名称" size="small"></el-input>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24" class="btn">
+            <el-button type="primary" icon="el-icon-search" size="mini" @click="onSubmit('form')">搜索</el-button>
+            <el-button icon="el-icon-refresh" size="mini" @click="toReset('form')">重置</el-button>
+          </el-col>
+        </el-form>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'search-1',
+  props: { form: { type: Object } },
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {
+    onSubmit() {
+      this.$emit('onSubmit');
+    },
+    toReset(formName) {
+      this.$refs[formName].resetFields();
+      this.$emit('toReset');
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .btn {
+    text-align: right;
+  }
+}
+</style>