From-wh 2 лет назад
Родитель
Сommit
913be84f27

+ 13 - 26
template/admin/src/pages/setting/systemMenus/index.vue

@@ -9,25 +9,25 @@
         @submit.native.prevent
       >
         <Row type="flex" :gutter="24">
-          <Col v-bind="grid">
+          <!-- <Col v-bind="grid">
             <FormItem label="规则状态:">
               <Select v-model="roleData.is_show" placeholder="请选择" clearable @on-change="getData">
                 <Option value="1">显示</Option>
                 <Option value="0">不显示</Option>
               </Select>
             </FormItem>
-          </Col>
+          </Col> -->
           <Col v-bind="grid">
             <FormItem label="按钮名称:" prop="status2" label-for="status2">
               <Input v-model="roleData.keyword" search enter-button placeholder="请输入按钮名称" @on-search="getData" />
             </FormItem>
           </Col>
         </Row>
-        <Row type="flex">
+        <!-- <Row type="flex">
           <Col v-bind="grid">
             <Button type="primary" @click="menusAdd('添加规则')" icon="md-add">添加规则 </Button>
           </Col>
-        </Row>
+        </Row> -->
       </Form>
       <vxe-table
         :border="false"
@@ -46,38 +46,24 @@
         <vxe-table-column field="menu_path" title="路由" min-width="240" tooltip="true">
           <template v-slot="{ row }">
             <span v-if="row.auth_type == 1">页面:{{ row.menu_path }}</span>
-            <span v-if="row.auth_type == 2">接口:[{{ row.methods }}]{{ row.api_url }}</span>
+            <span v-if="row.auth_type == 2">按钮:[{{ row.methods }}]{{ row.api_url }}</span>
           </template>
         </vxe-table-column>
-        <vxe-table-column field="flag" title="规则状态" min-width="120">
+        <vxe-table-column field="flag" title="是否显示" min-width="120">
           <template v-slot="{ row }">
             <i-switch
-              v-model="row.is_show"
-              :value="row.is_show"
+              v-model="row.is_show_path"
+              :value="row.is_show_path"
               :true-value="1"
               :false-value="0"
               @on-change="onchangeIsShow(row)"
               size="large"
             >
-              <span slot="open">开启</span>
-              <span slot="close">关闭</span>
+              <span slot="open">显示</span>
+              <span slot="close">隐藏</span>
             </i-switch>
           </template>
         </vxe-table-column>
-        <vxe-table-column field="date" title="操作" align="right" width="250" fixed="right">
-          <template v-slot="{ row }">
-            <span>
-              <a @click="addRoute(row)" v-if="row.auth_type === 1">添加权限</a>
-              <Divider type="vertical" v-if="row.auth_type === 1" />
-              <a @click="addE(row, '添加子菜单')" v-if="row.auth_type === 1">添加子菜单</a>
-              <!-- <a @click="addE(row, '添加规则')" v-else>添加规则</a> -->
-            </span>
-            <Divider type="vertical" v-if="row.auth_type === 1" />
-            <a @click="edit(row, '编辑')">编辑</a>
-            <Divider type="vertical" />
-            <a @click="del(row, '删除规则')">删除</a>
-          </template>
-        </vxe-table-column>
       </vxe-table>
     </Card>
     <menus-from
@@ -183,7 +169,7 @@ export default {
         xs: 24,
       },
       roleData: {
-        is_show: '',
+        is_show: 1,
         keyword: '',
       },
       defaultProps: {
@@ -314,7 +300,8 @@ export default {
     onchangeIsShow(row) {
       let data = {
         id: row.id,
-        is_show: row.is_show,
+        is_show_path: row.is_show_path,
+        is_show: -1,
       };
       isShowApi(data)
         .then(async (res) => {

+ 545 - 0
template/admin/src/pages/system/systemMenus/components/menusFrom.vue

@@ -0,0 +1,545 @@
+<template>
+  <div>
+    <Modal
+      v-model="modals"
+      width="700"
+      scrollable
+      closable
+      :title="titleFrom"
+      :mask-closable="false"
+      :z-index="1"
+      @on-cancel="handleReset"
+      @on-visible-change="visible"
+    >
+      <Form ref="formValidate" :model="formValidate" :label-width="110" @submit.native.prevent>
+        <!-- <Row type="flex" :gutter="24">
+          <Col span="24">
+            <FormItem label="类型:">
+              <RadioGroup v-model="formValidate.auth_type" @on-change="changeRadio">
+                <Radio :label="item.value" v-for="(item, i) in optionsRadio" :key="i">
+                  <Icon type="social-apple"></Icon>
+                  <span>{{ item.label }}</span>
+                </Radio>
+              </RadioGroup>
+            </FormItem>
+          </Col>
+        </Row> -->
+        <Row type="flex" :gutter="24">
+          <Col v-bind="grid">
+            <FormItem :label="!authType ? '接口名称:' : '按钮名称:'" prop="menu_name">
+              <div class="add">
+                <Input v-model="formValidate.menu_name" :placeholder="!authType ? '请输入接口名称' : '请输入按钮名称'">
+                </Input>
+                <!-- <Button class="ml10 df" v-show="!authType" @click="getRuleList()" icon="ios-apps"></Button> -->
+              </div>
+            </FormItem>
+          </Col>
+          <Col v-bind="grid">
+            <FormItem label="父级分类:">
+              <Cascader :data="menuList" change-on-select v-model="formValidate.path" filterable></Cascader>
+            </FormItem>
+          </Col>
+          <!-- <Col v-bind="grid" v-if="!authType">
+            <FormItem label="请求方式:" prop="methods">
+              <Select v-model="formValidate.methods">
+                <Option value="">请求</Option>
+                <Option value="GET">GET</Option>
+                <Option value="POST">POST</Option>
+                <Option value="PUT">PUT</Option>
+                <Option value="DELETE">DELETE</Option>
+              </Select>
+            </FormItem>
+          </Col> -->
+          <!-- <Col v-bind="grid" v-if="!authType">
+            <FormItem label="接口地址:">
+              <Input v-model="formValidate.api_url" placeholder="请输入接口地址" prop="api_url"></Input>
+            </FormItem>
+          </Col> -->
+          <Col v-bind="grid" v-show="authType">
+            <FormItem label="路由地址:" prop="menu_path">
+              <Input v-model="formValidate.menu_path" placeholder="请输入路由地址" @on-change="changeUnique">
+                <template #prepend>
+                  <span>{{ $routeProStr }}</span>
+                </template>
+              </Input>
+            </FormItem>
+          </Col>
+          <Col v-bind="grid">
+            <FormItem label="权限标识:" prop="unique_auth">
+              <Input v-model="formValidate.unique_auth" placeholder="请输入权限标识"></Input>
+            </FormItem>
+          </Col>
+          <Col v-bind="grid" v-if="authType">
+            <FormItem label="图标:">
+              <Input
+                v-model="formValidate.icon"
+                placeholder="请选择图标,点击右面图标"
+                icon="ios-appstore"
+                @on-click="iconClick"
+              ></Input>
+            </FormItem>
+          </Col>
+
+          <Col v-bind="grid" v-if="authType">
+            <FormItem label="排序:">
+              <Input type="number" v-model="formValidate.sort" placeholder="请输入排序" number></Input>
+            </FormItem>
+          </Col>
+          <!-- <Col v-bind="grid" v-show="authType">
+            <FormItem label="隐藏菜单:">
+              <RadioGroup v-model="formValidate.is_show_path">
+                <Radio :label="item.value" v-for="(item, i) in isShowPathRadio" :key="i">
+                  <Icon type="social-apple"></Icon>
+                  <span>{{ item.label }}</span>
+                </Radio>
+              </RadioGroup>
+            </FormItem>
+          </Col> -->
+          <Col v-bind="grid">
+            <FormItem label="状态:">
+              <RadioGroup v-model="formValidate.is_show">
+                <Radio :label="item.value" v-for="(item, i) in isShowRadio" :key="i">
+                  <Icon type="social-apple"></Icon>
+                  <span>{{ item.label }}</span>
+                </Radio>
+              </RadioGroup>
+            </FormItem>
+          </Col>
+        </Row>
+      </Form>
+      <template #footer>
+        <Button @click="modals = false">取消</Button>
+        <Button type="primary" @click="handleSubmit('formValidate')" :disabled="valids">提交</Button>
+      </template>
+    </Modal>
+    <Modal v-model="modal12" scrollable width="600" title="图标选择" footer-hide>
+      <Input
+        v-model="iconVal"
+        placeholder="输入关键词搜索,注意全是英文"
+        clearable
+        style="width: 300px"
+        @on-change="upIcon(iconVal)"
+        ref="search"
+      />
+      <div class="trees-coadd">
+        <div class="scollhide">
+          <div class="iconlist">
+            <ul class="list-inline">
+              <li class="icons-item" v-for="(item, i) in list" :key="i" :title="item.type">
+                <Icon :type="item.type" @click="iconChange(item.type)" class="ivu-icon" />
+              </li>
+            </ul>
+          </div>
+        </div>
+      </div>
+    </Modal>
+    <Modal v-model="ruleModal" scrollable width="1100" title="权限列表" footer-hide @on-visible-change="modalchange">
+      <div class="search-rule">
+        <Input
+          class="mr10"
+          v-model="searchRule"
+          placeholder="输入关键词搜索"
+          clearable
+          style="width: 300px"
+          ref="search"
+        />
+        <Button class="mr10" type="primary" @click="searchRules">搜索</Button>
+        <Button @click="init">重置</Button>
+      </div>
+      <div class="rule">
+        <div
+          class="rule-list"
+          v-show="!arrs.length || arrs.includes(index)"
+          :class="{ 'select-rule': arrs.includes(index) }"
+          v-for="(item, index) in ruleList"
+          :key="index"
+          @click="selectRule(item)"
+        >
+          <div>接口名称:{{ item.real_name }}</div>
+          <div>请求方式:{{ item.method }}</div>
+          <div>接口地址:{{ item.rule }}</div>
+        </div>
+      </div>
+    </Modal>
+  </div>
+</template>
+
+<script>
+import { addMenusApi, addMenus, getRuleList } from '@/api/systemMenus';
+import icon from '@/utils/icon';
+
+export default {
+  name: 'menusFrom',
+  props: {
+    formValidate: {
+      type: Object,
+      default: null,
+    },
+    titleFrom: {
+      type: String,
+      default: '',
+    },
+  },
+  data() {
+    return {
+      arrs: [],
+      searchRule: '',
+      iconVal: '',
+      grid: {
+        xl: 12,
+        lg: 12,
+        md: 12,
+        sm: 24,
+        xs: 24,
+      },
+      modals: false,
+      modal12: false,
+      FromData: [],
+      valids: false,
+      list2: [],
+      list: icon,
+      authType: true,
+      search: icon,
+      ruleModal: false,
+      ruleList: [],
+    };
+  },
+  watch: {
+    'formValidate.header': function (n) {
+      this.formValidate.is_header = n ? 1 : 0;
+    },
+    'formValidate.auth_type': function (n) {
+      if (n === undefined) {
+        n = 1;
+      }
+      this.authType = n === 1;
+    },
+    'formValidate.data': function (n) {},
+  },
+  computed: {
+    /* eslint-disable */
+    optionsList() {
+      let a = [];
+      this.FromData.map((item) => {
+        if ('pid' === item.field) {
+          a = item.options;
+        }
+      });
+      return a;
+    },
+    headerOptionsList() {
+      let a = [];
+      this.FromData.map((item) => {
+        if ('header' === item.field) {
+          a = item.options;
+        }
+      });
+      return a;
+    },
+    optionsListmodule() {
+      let a = [];
+      this.FromData.map((item) => {
+        if ('module' === item.field) {
+          a = item.options;
+        }
+      });
+      return a;
+    },
+    optionsRadio() {
+      let a = [];
+      this.FromData.map((item) => {
+        if ('auth_type' === item.field) {
+          a = item.options;
+        }
+      });
+      return a;
+    },
+    isheaderRadio() {
+      let a = [];
+      this.FromData.map((item) => {
+        if ('is_header' === item.field) {
+          a = item.options;
+        }
+      });
+      return a;
+    },
+    isShowRadio() {
+      let a = [];
+      this.FromData.map((item) => {
+        if ('is_show' === item.field) {
+          a = item.options;
+        }
+      });
+      return a;
+    },
+    isShowPathRadio() {
+      let a = [];
+      this.FromData.map((item) => {
+        if ('is_show_path' === item.field) {
+          a = item.options;
+        }
+      });
+      return a;
+    },
+    menuList() {
+      let a = [];
+      this.FromData.map((item) => {
+        if ('menu_list' === item.field) {
+          a = item.props.data;
+        }
+      });
+      return a;
+    },
+  },
+  methods: {
+    // 获取权限列表
+    getRuleList() {
+      getRuleList().then((res) => {
+        this.ruleList = res.data;
+        this.ruleModal = true;
+      });
+    },
+    modalchange(type) {
+      if (!type) {
+        this.arrs = [];
+        this.ruleModal = '';
+        this.ruleModal = false;
+      }
+    },
+    changeUnique(val) {
+      let value = this.$routeProStr + val.target.value;
+      if (value.slice(0, 1) === '/') value = value.replace('/', '');
+      this.formValidate.unique_auth = value.replaceAll('/', '-');
+    },
+    visible(type) {
+      if (!type) {
+        this.authType = true;
+      }
+    },
+    selectRule(data) {
+      this.$emit('selectRule', data);
+      this.$nextTick((e) => {
+        this.ruleModal = false;
+      });
+    },
+    changeRadio(n) {
+      this.authType = n === 1 ? true : false;
+    },
+    // 搜索
+    upIcon(n) {
+      let arrs = [];
+      for (var i = 0; i < this.search.length; i++) {
+        if (this.search[i].type.indexOf(n) !== -1) {
+          arrs.push(this.search[i]);
+          this.list = arrs;
+        }
+      }
+    },
+    // 搜索规则
+    searchRules() {
+      if (this.searchRule.trim()) {
+        this.arrs = [];
+        for (var i = 0; i < this.ruleList.length; i++) {
+          if (this.ruleList[i].real_name.indexOf(this.searchRule) !== -1) {
+            this.arrs.push(i);
+          }
+        }
+      } else {
+        this.arrs = [];
+      }
+    },
+    init() {
+      this.searchRule = '';
+      this.arrs = [];
+    },
+    handleCreate1(val) {
+      this.headerOptionsList.push({
+        value: val,
+        label: val,
+      });
+    },
+    // 获取新增表单
+    getAddFrom() {
+      addMenus()
+        .then(async (res) => {
+          this.FromData = res.data.rules;
+        })
+        .catch((res) => {
+          this.$Message.error(res.msg);
+        });
+    },
+    iconClick() {
+      this.modal12 = true;
+    },
+    iconChange(n) {
+      this.formValidate.icon = n;
+      this.modal12 = false;
+    },
+    // 提交
+    handleSubmit(name) {
+      //判断是否选择父级分类
+      if (this.formValidate.path) {
+        let length = this.formValidate.path.length;
+        this.formValidate.pid = this.formValidate.path[length - 1] || 0;
+      }
+      let data = {
+        url: this.formValidate.id ? `/setting/menus/${this.formValidate.id}` : '/setting/menus',
+        method: this.formValidate.id ? 'put' : 'post',
+        datas: this.formValidate,
+      };
+      if (this.authType) {
+        if (!this.formValidate.menu_name) {
+          return this.$Message.warning('请填写按钮名称');
+        }
+        if (!this.formValidate.menu_path) {
+          return this.$Message.warning('请填写路由地址');
+        }
+      } else {
+        if (!this.formValidate.menu_name) {
+          return this.$Message.warning('请填写接口名称');
+        }
+        if (!this.formValidate.methods) {
+          return this.$Message.warning('请选择请求方式');
+        }
+        if (!this.formValidate.api_url) {
+          return this.$Message.warning('请选择接口地址');
+        }
+      }
+      this.valids = true;
+      addMenusApi(data)
+        .then(async (res) => {
+          this.$Message.success(res.msg);
+          this.modals = false;
+          this.$emit('changeMenu');
+          this.getAddFrom();
+          // this.$store.dispatch('menus/getMenusNavList');
+        })
+        .catch((res) => {
+          this.valids = false;
+          this.$Message.error(res.msg);
+        });
+    },
+    handleReset() {
+      this.modals = false;
+      this.authType = true;
+      this.$refs['formValidate'].resetFields();
+      this.$emit('clearFrom');
+    },
+  },
+  created() {
+    this.list = this.search;
+    this.getAddFrom();
+  },
+};
+</script>
+
+<style scoped>
+.trees-coadd {
+  width: 100%;
+  height: 500px;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.scollhide {
+  width: 100%;
+  height: 100%;
+  overflow: auto;
+  margin-left: 18px;
+  padding: 10px 0 10px 0;
+  box-sizing: border-box;
+}
+
+.content {
+  font-size: 12px;
+}
+
+.time {
+  font-size: 12px;
+  color: #2d8cf0;
+}
+
+.icons-item {
+  float: left;
+  margin: 6px 6px 6px 0;
+  width: 53px;
+  text-align: center;
+  list-style: none;
+  cursor: pointer;
+  height: 50px;
+  color: #5c6b77;
+  transition: all 0.2s ease;
+  position: relative;
+  padding-top: 10px;
+}
+
+.icons-item .ivu-icon {
+  font-size: 16px;
+}
+
+.search-rule {
+  display: flex;
+  align-items: center;
+  padding: 10px;
+  background-color: #f2f2f2;
+}
+
+.rule {
+  display: flex;
+  flex-wrap: wrap;
+  max-height: 700px;
+  overflow: scroll;
+}
+
+/*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
+.rule::-webkit-scrollbar {
+  width: 10px;
+  height: 10px;
+  background-color: #f5f5f5;
+}
+
+/*定义滚动条轨道 内阴影+圆角*/
+.rule::-webkit-scrollbar-track {
+  border-radius: 4px;
+  background-color: #f5f5f5;
+}
+
+/*定义滑块 内阴影+圆角*/
+.rule::-webkit-scrollbar-thumb {
+  border-radius: 4px;
+  background-color: #555;
+}
+
+.rule-list {
+  background-color: #f8f5f5;
+  width: 32%;
+  margin: 5px;
+  border-radius: 3px;
+  padding: 10px;
+  color: #333;
+  cursor: pointer;
+  transition: all 0.1s;
+}
+
+.rule-list:hover {
+  background-color: #c5d1dd;
+}
+
+.rule-list div {
+  white-space: nowrap;
+}
+
+.select-rule {
+  background-color: #c5d1dd;
+}
+
+.add {
+  display: flex;
+  align-items: center;
+}
+
+.df {
+  display: flex;
+  justify-content: center;
+}
+</style>

+ 561 - 0
template/admin/src/pages/system/systemMenus/index.vue

@@ -0,0 +1,561 @@
+<template>
+  <div>
+    <Card :bordered="false" dis-hover class="ivu-mt">
+      <Form
+        ref="roleData"
+        :model="roleData"
+        :label-width="labelWidth"
+        :label-position="labelPosition"
+        @submit.native.prevent
+      >
+        <Row type="flex" :gutter="24">
+          <Col v-bind="grid">
+            <FormItem label="规则状态:">
+              <Select v-model="roleData.is_show" placeholder="请选择" clearable @on-change="getData">
+                <Option value="1">显示</Option>
+                <Option value="0">不显示</Option>
+              </Select>
+            </FormItem>
+          </Col>
+          <Col v-bind="grid">
+            <FormItem label="按钮名称:" prop="status2" label-for="status2">
+              <Input v-model="roleData.keyword" search enter-button placeholder="请输入按钮名称" @on-search="getData" />
+            </FormItem>
+          </Col>
+        </Row>
+        <Row type="flex">
+          <Col v-bind="grid">
+            <Button type="primary" @click="menusAdd('添加规则')" icon="md-add">添加规则 </Button>
+          </Col>
+        </Row>
+      </Form>
+      <vxe-table
+        :border="false"
+        class="vxeTable mt25"
+        highlight-hover-row
+        highlight-current-row
+        :loading="loading"
+        ref="xTable"
+        header-row-class-name="false"
+        :tree-config="tabconfig"
+        :data="tableData"
+        row-id="id"
+      >
+        <vxe-table-column field="menu_name" tree-node title="按钮名称" min-width="100"></vxe-table-column>
+        <vxe-table-column field="unique_auth" title="前端权限" min-width="200"></vxe-table-column>
+        <vxe-table-column field="menu_path" title="路由" min-width="240" tooltip="true">
+          <template v-slot="{ row }">
+            <span v-if="row.auth_type == 1">页面:{{ row.menu_path }}</span>
+            <span v-if="row.auth_type == 2">接口:[{{ row.methods }}]{{ row.api_url }}</span>
+          </template>
+        </vxe-table-column>
+        <vxe-table-column field="flag" title="规则状态" min-width="120">
+          <template v-slot="{ row }">
+            <i-switch
+              v-model="row.is_show"
+              :value="row.is_show"
+              :true-value="1"
+              :false-value="0"
+              @on-change="onchangeIsShow(row)"
+              size="large"
+            >
+              <span slot="open">开启</span>
+              <span slot="close">关闭</span>
+            </i-switch>
+          </template>
+        </vxe-table-column>
+        <vxe-table-column field="date" title="操作" align="right" width="250" fixed="right">
+          <template v-slot="{ row }">
+            <span>
+              <a @click="addRoute(row)" v-if="row.auth_type === 1">添加权限</a>
+              <Divider type="vertical" v-if="row.auth_type === 1" />
+              <a @click="addE(row, '添加子菜单')" v-if="row.auth_type === 1">添加子菜单</a>
+              <!-- <a @click="addE(row, '添加规则')" v-else>添加规则</a> -->
+            </span>
+            <Divider type="vertical" v-if="row.auth_type === 1" />
+            <a @click="edit(row, '编辑')">编辑</a>
+            <Divider type="vertical" />
+            <a @click="del(row, '删除规则')">删除</a>
+          </template>
+        </vxe-table-column>
+      </vxe-table>
+    </Card>
+    <menus-from
+      :formValidate="formValidate"
+      :titleFrom="titleFrom"
+      @getList="getList"
+      @changeMenu="getMenusUnique"
+      ref="menusFrom"
+      @clearFrom="clearFrom"
+    ></menus-from>
+    <Modal
+      v-model="ruleModal"
+      scrollable
+      width="1100"
+      title="权限列表"
+      @on-ok="addRouters"
+      @on-cancel="ruleModal = false"
+      @on-visible-change="modalchange"
+    >
+      <div class="search-rule">
+        <Alert
+          >基础接口,可多选,并且添加后不会再展示出现;删除权限后才会出现;公共接口,可多选,并且添加后会继续展示;</Alert
+        >
+        <Input
+          class="mr10"
+          v-model="searchRule"
+          placeholder="输入关键词搜索"
+          clearable
+          style="width: 300px"
+          ref="search"
+          @on-enter="searchRules"
+          @on-clear="searchRules"
+        />
+        <Button class="mr10" type="primary" @click="searchRules">搜索</Button>
+        <Button @click="init">重置</Button>
+      </div>
+      <div class="route-list">
+        <div class="tree">
+          <el-tree
+            ref="treeBox"
+            :data="ruleCateList"
+            :highlight-current="true"
+            :props="defaultProps"
+            node-key="id"
+            :default-expanded-keys="expandedKeys"
+            :current-node-key="nodeKey"
+            @node-click="handleNodeClick"
+          ></el-tree>
+        </div>
+        <div class="rule">
+          <div
+            class="rule-list"
+            v-show="!arrs.length || arrs.includes(item.id)"
+            :class="{ 'select-rule': seletRouteIds.includes(item.id) }"
+            v-for="(item, index) in children"
+            :key="index"
+            @click="selectRule(item)"
+          >
+            <div>接口名称:{{ item.name }}</div>
+            <div>请求方式:{{ item.method }}</div>
+            <div>接口地址:{{ item.path }}</div>
+          </div>
+        </div>
+      </div>
+      <!-- <Tabs v-model="routeType" @on-click="changTab">
+        <TabPane :label="item.name" :name="'' + index" v-for="(item, index) in foundationList" :key="item"></TabPane>
+      </Tabs> -->
+    </Modal>
+  </div>
+</template>
+
+<script>
+import { mapState } from 'vuex';
+import {
+  getTable,
+  menusDetailsApi,
+  isShowApi,
+  editMenus,
+  getRuleList,
+  menusBatch,
+  getMenusUnique,
+  menusRuleCate,
+} from '@/api/systemMenus';
+import formCreate from '@form-create/iview';
+import menusFrom from './components/menusFrom';
+import { formatFlatteningRoutes, findFirstNonNullChildren, findFirstNonNullChildrenKeys } from '@/libs/system';
+
+export default {
+  name: 'systemMenus',
+  data() {
+    return {
+      children: [],
+      expandedKeys: [],
+      tabconfig: { children: 'children', reserve: true, accordion: true },
+      spinShow: false,
+      ruleModal: false,
+      searchRule: '',
+      grid: {
+        xl: 7,
+        lg: 7,
+        md: 12,
+        sm: 24,
+        xs: 24,
+      },
+      roleData: {
+        is_show: '',
+        keyword: '',
+      },
+      defaultProps: {
+        children: 'children',
+        label: 'name',
+      },
+      ruleCateList: [], //权限树
+      loading: false,
+      tableData: [],
+      FromData: null,
+      icons: '',
+      formValidate: {},
+      titleFrom: '',
+      modalTitleSs: '',
+      routeType: '0',
+      arrs: [],
+      foundationList: [], // 基础接口列表
+      openList: [], // 公开接口列表
+      seletRoute: [], // 选中路由
+      seletRouteIds: [], // 选中id
+      menusId: 0, // 选中分类id
+      nodeKey: 0, // 选中节点
+    };
+  },
+  components: { menusFrom, formCreate: formCreate.$form() },
+  computed: {
+    ...mapState('admin/layout', ['isMobile']),
+    labelWidth() {
+      return this.isMobile ? undefined : 75;
+    },
+    labelPosition() {
+      return this.isMobile ? 'top' : 'right';
+    },
+  },
+  mounted() {
+    this.getData();
+  },
+  methods: {
+    init() {
+      this.searchRule = '';
+      this.searchRules();
+    },
+    addRouters() {
+      let data = {
+        menus: this.seletRoute,
+      };
+      menusBatch(data)
+        .then((res) => {
+          this.getData();
+        })
+        .catch((res) => {
+          this.$Message.error(res.msg);
+        });
+    },
+    selectRule(data) {
+      if (this.seletRouteIds.includes(data.id)) {
+        let i = this.seletRouteIds.findIndex((e) => e == data.id);
+        this.seletRouteIds.splice(i, 1);
+        this.seletRoute.splice(i, 1);
+      } else {
+        this.seletRouteIds.push(data.id);
+        this.seletRoute.push({
+          menu_name: data.name,
+          unique_auth: '',
+          api_url: data.path,
+          path: this.menusId,
+          method: data.method,
+        });
+      }
+    },
+    changTab(name) {
+      this.routeType = name;
+      let index = parseInt(name);
+      this.children = this.foundationList[index] ? this.foundationList[index].children : [];
+      this.searchRules();
+    },
+    // 搜索规则
+    searchRules() {
+      if (this.searchRule.trim()) {
+        this.arrs = [];
+        let arr = this.foundationList;
+        for (var i = 0; i < arr.length; i++) {
+          if (arr[i].name.indexOf(this.searchRule) !== -1) {
+            this.arrs.push(arr[i].id);
+          }
+        }
+      } else {
+        this.arrs = [];
+      }
+    },
+    addRoute(row) {
+      this.menusId = row.id;
+      this.routeType = '0';
+      // this.getRuleList();
+      menusRuleCate().then((res) => {
+        this.ruleCateList = res.data;
+        this.ruleModal = true;
+        if (res.data.length) {
+          this.$nextTick((e) => {
+            this.expandedKeys = findFirstNonNullChildrenKeys(res.data[0], []);
+            this.nodeKey = findFirstNonNullChildren(res.data).id;
+            this.$refs.treeBox.setCurrentKey(this.nodeKey);
+            this.getRuleList(this.nodeKey);
+          });
+        }
+      });
+    },
+    handleNodeClick(data) {
+      this.getRuleList(data.id);
+    },
+    modalchange() {
+      this.seletRouteIds = [];
+      this.seletRoute = [];
+    },
+    // 获取权限列表
+    getRuleList(cate_id) {
+      getRuleList(cate_id).then((res) => {
+        this.foundationList = res.data;
+        this.children = res.data;
+        this.searchRules();
+
+        // this.openList = [];
+        // this.seletRouteIds = [];
+        // this.seletRoute = [];
+      });
+    },
+    // 修改规则状态
+    onchangeIsShow(row) {
+      let data = {
+        id: row.id,
+        is_show: row.is_show,
+      };
+      isShowApi(data)
+        .then(async (res) => {
+          this.$Message.success(res.msg);
+          this.$store.dispatch('menus/getMenusNavList');
+        })
+        .catch((res) => {
+          this.$Message.error(res.msg);
+        });
+    },
+    // 请求列表
+    getList() {
+      this.formValidate = Object.assign({}, this.$options.data().formValidate);
+      this.getData();
+    },
+
+    // 清除表单数据
+    clearFrom() {
+      this.formValidate = Object.assign({}, this.$options.data().formValidate);
+    },
+    // 添加子菜单
+    addE(row, title) {
+      this.formValidate = {};
+      let pid = row.id.toString();
+      if (pid) {
+        menusDetailsApi(row.id)
+          .then(async (res) => {
+            this.formValidate.path = res.data.path;
+            this.formValidate.path.push(row.id);
+            this.formValidate.pid = pid;
+            this.$refs.menusFrom.modals = true;
+            this.$refs.menusFrom.valids = false;
+            this.titleFrom = title;
+            this.formValidate.auth_type = 1;
+            this.formValidate.is_show = 0;
+            this.formValidate.is_show_path = 0;
+          })
+          .catch((res) => {
+            this.$Message.error(res.msg);
+          });
+      } else {
+        this.formValidate.pid = pid;
+        this.$refs.menusFrom.modals = true;
+        this.$refs.menusFrom.valids = false;
+        this.titleFrom = title;
+        this.formValidate.auth_type = 1;
+        this.formValidate.is_show = 0;
+        this.formValidate.is_show_path = 0;
+      }
+      // this.formValidate.pid = row.id.toString();
+      // this.$refs.menusFrom.modals = true;
+      // this.$refs.menusFrom.valids = false;
+      // this.titleFrom = title;
+      // this.formValidate.auth_type = 1;
+      // this.formValidate.is_show = '0';
+    },
+    // 删除
+    del(row, tit) {
+      let delfromData = {
+        title: tit,
+        url: `/setting/menus/${row.id}`,
+        method: 'DELETE',
+        ids: '',
+      };
+
+      this.$modalSure(delfromData)
+        .then((res) => {
+          this.$Message.success(res.msg);
+          this.getData();
+          this.getMenusUnique();
+          // this.$store.dispatch('menus/getMenusNavList');
+        })
+        .catch((res) => {
+          this.$Message.error(res.msg);
+        });
+    },
+    // 规则详情
+    menusDetails(id) {
+      menusDetailsApi(id)
+        .then(async (res) => {
+          this.formValidate = res.data;
+          this.$refs.menusFrom.modals = true;
+        })
+        .catch((res) => {
+          this.$Message.error(res.msg);
+        });
+    },
+    // 编辑
+    edit(row, title, index) {
+      this.formValidate = {};
+      this.menusDetails(row.id);
+      this.titleFrom = title;
+      this.$refs.menusFrom.valids = false;
+      this.$refs.menusFrom.getAddFrom(row.id);
+    },
+    // 添加
+    menusAdd(title) {
+      this.formValidate = {};
+      this.$refs.menusFrom.modals = true;
+      this.$refs.menusFrom.valids = false;
+      // this.formValidate = Object.assign(this.$data, this.$options.formValidate());
+      this.titleFrom = title;
+      this.formValidate.auth_type = 1;
+      this.formValidate.is_show = 0;
+      this.formValidate.is_show_path = 0;
+    },
+    // 新增页面表单
+    // getAddFrom () {
+    //     this.spinShow = true;
+    //     addMenus(this.roleData).then(async res => {
+    //         this.FromData = res.data;
+    //         this.$refs.menusFrom.modals = true;
+    //         this.spinShow = false;
+    //     }).catch(res => {
+    //         this.spinShow = false;
+    //         this.$Message.error(res.msg);
+    //     })
+    // },
+    // 列表
+    getData() {
+      this.loading = true;
+      this.roleData.is_show = this.roleData.is_show || '';
+      getTable(this.roleData)
+        .then(async (res) => {
+          this.tableData = res.data;
+          this.loading = false;
+        })
+        .catch((res) => {
+          this.loading = false;
+          this.$Message.error(res.msg);
+        });
+    },
+    getMenusUnique() {
+      getMenusUnique().then((res) => {
+        let data = res.data;
+        this.$store.commit('userInfo/uniqueAuth', data.uniqueAuth);
+        this.$store.commit('menus/getmenusNav', data.menus);
+        this.$store.dispatch('routesList/setRoutesList', data.menus);
+        let arr = formatFlatteningRoutes(this.$router.options.routes);
+        this.formatTwoStageRoutes(arr);
+        let routes = formatFlatteningRoutes(data.menus);
+        this.$store.commit('menus/setOneLvRoute', routes);
+        this.bus.$emit('routesListChange');
+      });
+    },
+    formatTwoStageRoutes(arr) {
+      if (arr.length <= 0) return false;
+      const newArr = [];
+      const cacheList = [];
+      arr.forEach((v) => {
+        if (v && v.meta && v.meta.keepAlive) {
+          newArr.push({ ...v });
+          cacheList.push(v.name);
+          this.$store.dispatch('keepAliveNames/setCacheKeepAlive', cacheList);
+        }
+      });
+      return newArr;
+    },
+    // 关闭按钮
+    cancel() {
+      this.$emit('onCancel');
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.vxeTable {
+  > .vxe-table--header-wrapper {
+    background: #fff !important;
+  }
+  .icon {
+    font-size: 20px;
+  }
+}
+
+.rule {
+  display: flex;
+  flex-wrap: wrap;
+  overflow-y: scroll;
+  height: max-content;
+  max-height: 600px;
+  flex: 1;
+}
+.tree::-webkit-scrollbar {
+  width: 2px;
+  background-color: #f5f5f5;
+}
+/*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
+.rule::-webkit-scrollbar {
+  width: 10px;
+  height: 10px;
+  background-color: #f5f5f5;
+}
+
+/*定义滚动条轨道 内阴影+圆角*/
+.rule::-webkit-scrollbar-track {
+  border-radius: 4px;
+  background-color: #f5f5f5;
+}
+
+/*定义滑块 内阴影+圆角*/
+.rule::-webkit-scrollbar-thumb {
+  border-radius: 4px;
+  background-color: #ccc;
+}
+
+.rule-list {
+  background-color: #f2f2f2;
+  width: 48.5%;
+  height: max-content;
+  margin: 5px;
+  border-radius: 3px;
+  padding: 10px;
+  color: #333;
+  cursor: pointer;
+  transition: all 0.1s;
+}
+
+.rule-list:hover {
+  background-color: #badbfb;
+}
+
+.rule-list div {
+  white-space: nowrap;
+}
+
+.select-rule {
+  background-color: #badbfb;
+}
+.route-list {
+  display: flex;
+  margin-top: 10px;
+
+  .tree {
+    width: 200px;
+    overflow-y: scroll;
+    max-height: 600px;
+    /deep/ .el-tree-node__children .el-tree-node .el-tree-node__content {
+      padding-left: 14px !important;
+    }
+  }
+}
+</style>

+ 9 - 0
template/admin/src/router/modules/system.js

@@ -190,5 +190,14 @@ export default {
       },
       component: () => import('@/pages/system/crontab/index'),
     },
+    {
+      path: 'system_menus/index',
+      name: `${pre}systemMenus`,
+      meta: {
+        auth: ['system-system-menus'],
+        title: '权限规则',
+      },
+      component: () => import('@/pages/system/systemMenus/index'),
+    },
   ],
 };