index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. <template>
  2. <div>
  3. <Card :bordered="false" dis-hover class="ivu-mt">
  4. <Form
  5. ref="roleData"
  6. :model="roleData"
  7. :label-width="labelWidth"
  8. :label-position="labelPosition"
  9. @submit.native.prevent
  10. >
  11. <Row type="flex" :gutter="24">
  12. <Col v-bind="grid">
  13. <FormItem label="规则状态:">
  14. <Select v-model="roleData.is_show" placeholder="请选择" clearable @on-change="getData">
  15. <Option value="1">显示</Option>
  16. <Option value="0">不显示</Option>
  17. </Select>
  18. </FormItem>
  19. </Col>
  20. <Col v-bind="grid">
  21. <FormItem label="按钮名称:" prop="status2" label-for="status2">
  22. <Input v-model="roleData.keyword" search enter-button placeholder="请输入按钮名称" @on-search="getData" />
  23. </FormItem>
  24. </Col>
  25. </Row>
  26. <Row type="flex">
  27. <Col v-bind="grid">
  28. <Button type="primary" @click="menusAdd('添加规则')" icon="md-add">添加规则 </Button>
  29. </Col>
  30. </Row>
  31. </Form>
  32. <vxe-table
  33. :border="false"
  34. class="vxeTable mt25"
  35. highlight-hover-row
  36. highlight-current-row
  37. :loading="loading"
  38. ref="xTable"
  39. header-row-class-name="false"
  40. :tree-config="tabconfig"
  41. :data="tableData"
  42. row-id="id"
  43. >
  44. <vxe-table-column field="menu_name" tree-node title="按钮名称" min-width="100"></vxe-table-column>
  45. <vxe-table-column field="unique_auth" title="前端权限" min-width="200"></vxe-table-column>
  46. <vxe-table-column field="menu_path" title="路由" min-width="240" tooltip="true">
  47. <template v-slot="{ row }">
  48. <span v-if="row.auth_type == 1">页面:{{ row.menu_path }}</span>
  49. <span v-if="row.auth_type == 2">接口:[{{ row.methods }}]{{ row.api_url }}</span>
  50. </template>
  51. </vxe-table-column>
  52. <vxe-table-column field="flag" title="规则状态" min-width="120">
  53. <template v-slot="{ row }">
  54. <i-switch
  55. v-model="row.is_show"
  56. :value="row.is_show"
  57. :true-value="1"
  58. :false-value="0"
  59. @on-change="onchangeIsShow(row)"
  60. size="large"
  61. >
  62. <span slot="open">开启</span>
  63. <span slot="close">关闭</span>
  64. </i-switch>
  65. </template>
  66. </vxe-table-column>
  67. <vxe-table-column field="mark" title="备注" min-width="120"></vxe-table-column>
  68. <vxe-table-column field="date" title="操作" align="right" width="250" fixed="right">
  69. <template v-slot="{ row }">
  70. <span>
  71. <a @click="addRoute(row)" v-if="row.auth_type === 1">添加权限</a>
  72. <Divider type="vertical" v-if="row.auth_type === 1" />
  73. <a @click="addE(row, '添加子菜单')" v-if="row.auth_type === 1">添加子菜单</a>
  74. <!-- <a @click="addE(row, '添加规则')" v-else>添加规则</a> -->
  75. </span>
  76. <Divider type="vertical" v-if="row.auth_type === 1" />
  77. <a @click="edit(row, '编辑')">编辑</a>
  78. <Divider type="vertical" />
  79. <a @click="del(row, '删除规则')">删除</a>
  80. </template>
  81. </vxe-table-column>
  82. </vxe-table>
  83. </Card>
  84. <menus-from
  85. :formValidate="formValidate"
  86. :titleFrom="titleFrom"
  87. @getList="getList"
  88. @changeMenu="getMenusUnique"
  89. ref="menusFrom"
  90. @clearFrom="clearFrom"
  91. ></menus-from>
  92. <Modal
  93. v-model="ruleModal"
  94. scrollable
  95. width="1100"
  96. title="权限列表"
  97. @on-ok="addRouters"
  98. @on-cancel="ruleModal = false"
  99. @on-visible-change="modalchange"
  100. >
  101. <div class="search-rule">
  102. <Alert
  103. >基础接口,可多选,并且添加后不会再展示出现;删除权限后才会出现;公共接口,可多选,并且添加后会继续展示;</Alert
  104. >
  105. <Input
  106. class="mr10"
  107. v-model="searchRule"
  108. placeholder="输入关键词搜索"
  109. clearable
  110. style="width: 300px"
  111. ref="search"
  112. @on-enter="searchRules"
  113. @on-clear="searchRules"
  114. />
  115. <Button class="mr10" type="primary" @click="searchRules">搜索</Button>
  116. <Button @click="init">重置</Button>
  117. </div>
  118. <div class="route-list">
  119. <div class="tree">
  120. <el-tree
  121. ref="treeBox"
  122. :data="ruleCateList"
  123. :highlight-current="true"
  124. :props="defaultProps"
  125. node-key="id"
  126. :default-expanded-keys="expandedKeys"
  127. :current-node-key="nodeKey"
  128. @node-click="handleNodeClick"
  129. ></el-tree>
  130. </div>
  131. <div class="rule">
  132. <div
  133. class="rule-list"
  134. v-show="!arrs.length || arrs.includes(item.id)"
  135. :class="{ 'select-rule': seletRouteIds.includes(item.id) }"
  136. v-for="(item, index) in children"
  137. :key="index"
  138. @click="selectRule(item)"
  139. >
  140. <div>接口名称:{{ item.name }}</div>
  141. <div>请求方式:{{ item.method }}</div>
  142. <div>接口地址:{{ item.path }}</div>
  143. </div>
  144. </div>
  145. </div>
  146. <!-- <Tabs v-model="routeType" @on-click="changTab">
  147. <TabPane :label="item.name" :name="'' + index" v-for="(item, index) in foundationList" :key="item"></TabPane>
  148. </Tabs> -->
  149. </Modal>
  150. </div>
  151. </template>
  152. <script>
  153. import { mapState } from 'vuex';
  154. import {
  155. getTable,
  156. menusDetailsApi,
  157. isShowApi,
  158. editMenus,
  159. getRuleList,
  160. menusBatch,
  161. getMenusUnique,
  162. menusRuleCate,
  163. } from '@/api/systemMenus';
  164. import formCreate from '@form-create/iview';
  165. import menusFrom from './components/menusFrom';
  166. import { formatFlatteningRoutes, findFirstNonNullChildren, findFirstNonNullChildrenKeys } from '@/libs/system';
  167. export default {
  168. name: 'systemMenus',
  169. data() {
  170. return {
  171. children: [],
  172. expandedKeys: [],
  173. tabconfig: { children: 'children', reserve: true, accordion: true },
  174. spinShow: false,
  175. ruleModal: false,
  176. searchRule: '',
  177. grid: {
  178. xl: 7,
  179. lg: 7,
  180. md: 12,
  181. sm: 24,
  182. xs: 24,
  183. },
  184. roleData: {
  185. is_show: '',
  186. keyword: '',
  187. },
  188. defaultProps: {
  189. children: 'children',
  190. label: 'name',
  191. },
  192. ruleCateList: [], //权限树
  193. loading: false,
  194. tableData: [],
  195. FromData: null,
  196. icons: '',
  197. formValidate: {},
  198. titleFrom: '',
  199. modalTitleSs: '',
  200. routeType: '0',
  201. arrs: [],
  202. foundationList: [], // 基础接口列表
  203. openList: [], // 公开接口列表
  204. seletRoute: [], // 选中路由
  205. seletRouteIds: [], // 选中id
  206. menusId: 0, // 选中分类id
  207. nodeKey: 0, // 选中节点
  208. };
  209. },
  210. components: { menusFrom, formCreate: formCreate.$form() },
  211. computed: {
  212. ...mapState('admin/layout', ['isMobile']),
  213. labelWidth() {
  214. return this.isMobile ? undefined : 75;
  215. },
  216. labelPosition() {
  217. return this.isMobile ? 'top' : 'right';
  218. },
  219. },
  220. mounted() {
  221. this.getData();
  222. },
  223. methods: {
  224. init() {
  225. this.searchRule = '';
  226. this.searchRules();
  227. },
  228. addRouters() {
  229. let data = {
  230. menus: this.seletRoute,
  231. };
  232. menusBatch(data)
  233. .then((res) => {
  234. this.getData();
  235. })
  236. .catch((res) => {
  237. this.$Message.error(res.msg);
  238. });
  239. },
  240. selectRule(data) {
  241. if (this.seletRouteIds.includes(data.id)) {
  242. let i = this.seletRouteIds.findIndex((e) => e == data.id);
  243. this.seletRouteIds.splice(i, 1);
  244. this.seletRoute.splice(i, 1);
  245. } else {
  246. this.seletRouteIds.push(data.id);
  247. this.seletRoute.push({
  248. menu_name: data.name,
  249. unique_auth: '',
  250. api_url: data.path,
  251. path: this.menusId,
  252. method: data.method,
  253. });
  254. }
  255. },
  256. changTab(name) {
  257. this.routeType = name;
  258. let index = parseInt(name);
  259. this.children = this.foundationList[index] ? this.foundationList[index].children : [];
  260. this.searchRules();
  261. },
  262. // 搜索规则
  263. searchRules() {
  264. if (this.searchRule.trim()) {
  265. this.arrs = [];
  266. let arr = this.foundationList;
  267. for (var i = 0; i < arr.length; i++) {
  268. if (arr[i].name.indexOf(this.searchRule) !== -1) {
  269. this.arrs.push(arr[i].id);
  270. }
  271. }
  272. } else {
  273. this.arrs = [];
  274. }
  275. },
  276. addRoute(row) {
  277. this.menusId = row.id;
  278. this.routeType = '0';
  279. // this.getRuleList();
  280. menusRuleCate().then((res) => {
  281. this.ruleCateList = res.data;
  282. this.ruleModal = true;
  283. if (res.data.length) {
  284. this.$nextTick((e) => {
  285. this.expandedKeys = findFirstNonNullChildrenKeys(res.data[0], []);
  286. this.nodeKey = findFirstNonNullChildren(res.data).id;
  287. this.$refs.treeBox.setCurrentKey(this.nodeKey);
  288. this.getRuleList(this.nodeKey);
  289. });
  290. }
  291. });
  292. },
  293. handleNodeClick(data) {
  294. this.getRuleList(data.id);
  295. },
  296. modalchange() {
  297. this.seletRouteIds = [];
  298. this.seletRoute = [];
  299. },
  300. // 获取权限列表
  301. getRuleList(cate_id) {
  302. getRuleList(cate_id).then((res) => {
  303. this.foundationList = res.data;
  304. this.children = res.data;
  305. this.searchRules();
  306. // this.openList = [];
  307. // this.seletRouteIds = [];
  308. // this.seletRoute = [];
  309. });
  310. },
  311. // 修改规则状态
  312. onchangeIsShow(row) {
  313. let data = {
  314. id: row.id,
  315. is_show: row.is_show,
  316. };
  317. isShowApi(data)
  318. .then(async (res) => {
  319. this.$Message.success(res.msg);
  320. this.$store.dispatch('menus/getMenusNavList');
  321. })
  322. .catch((res) => {
  323. this.$Message.error(res.msg);
  324. });
  325. },
  326. // 请求列表
  327. getList() {
  328. this.formValidate = Object.assign({}, this.$options.data().formValidate);
  329. this.getData();
  330. },
  331. // 清除表单数据
  332. clearFrom() {
  333. this.formValidate = Object.assign({}, this.$options.data().formValidate);
  334. },
  335. // 添加子菜单
  336. addE(row, title) {
  337. this.formValidate = {};
  338. let pid = row.id.toString();
  339. if (pid) {
  340. menusDetailsApi(row.id)
  341. .then(async (res) => {
  342. this.formValidate.path = res.data.path;
  343. this.formValidate.path.push(row.id);
  344. this.formValidate.pid = pid;
  345. this.$refs.menusFrom.modals = true;
  346. this.$refs.menusFrom.valids = false;
  347. this.titleFrom = title;
  348. this.formValidate.auth_type = 1;
  349. this.formValidate.is_show = 0;
  350. this.formValidate.is_show_path = 0;
  351. })
  352. .catch((res) => {
  353. this.$Message.error(res.msg);
  354. });
  355. } else {
  356. this.formValidate.pid = pid;
  357. this.$refs.menusFrom.modals = true;
  358. this.$refs.menusFrom.valids = false;
  359. this.titleFrom = title;
  360. this.formValidate.auth_type = 1;
  361. this.formValidate.is_show = 0;
  362. this.formValidate.is_show_path = 0;
  363. }
  364. // this.formValidate.pid = row.id.toString();
  365. // this.$refs.menusFrom.modals = true;
  366. // this.$refs.menusFrom.valids = false;
  367. // this.titleFrom = title;
  368. // this.formValidate.auth_type = 1;
  369. // this.formValidate.is_show = '0';
  370. },
  371. // 删除
  372. del(row, tit) {
  373. let delfromData = {
  374. title: tit,
  375. url: `/setting/menus/${row.id}`,
  376. method: 'DELETE',
  377. ids: '',
  378. };
  379. this.$modalSure(delfromData)
  380. .then((res) => {
  381. this.$Message.success(res.msg);
  382. this.getData();
  383. this.getMenusUnique();
  384. // this.$store.dispatch('menus/getMenusNavList');
  385. })
  386. .catch((res) => {
  387. this.$Message.error(res.msg);
  388. });
  389. },
  390. // 规则详情
  391. menusDetails(id) {
  392. menusDetailsApi(id)
  393. .then(async (res) => {
  394. this.formValidate = res.data;
  395. this.$refs.menusFrom.modals = true;
  396. })
  397. .catch((res) => {
  398. this.$Message.error(res.msg);
  399. });
  400. },
  401. // 编辑
  402. edit(row, title, index) {
  403. this.formValidate = {};
  404. this.menusDetails(row.id);
  405. this.titleFrom = title;
  406. this.$refs.menusFrom.valids = false;
  407. this.$refs.menusFrom.getAddFrom(row.id);
  408. },
  409. // 添加
  410. menusAdd(title) {
  411. this.formValidate = {};
  412. this.$refs.menusFrom.modals = true;
  413. this.$refs.menusFrom.valids = false;
  414. // this.formValidate = Object.assign(this.$data, this.$options.formValidate());
  415. this.titleFrom = title;
  416. this.formValidate.auth_type = 1;
  417. this.formValidate.is_show = 0;
  418. this.formValidate.is_show_path = 0;
  419. },
  420. // 新增页面表单
  421. // getAddFrom () {
  422. // this.spinShow = true;
  423. // addMenus(this.roleData).then(async res => {
  424. // this.FromData = res.data;
  425. // this.$refs.menusFrom.modals = true;
  426. // this.spinShow = false;
  427. // }).catch(res => {
  428. // this.spinShow = false;
  429. // this.$Message.error(res.msg);
  430. // })
  431. // },
  432. // 列表
  433. getData() {
  434. this.loading = true;
  435. this.roleData.is_show = this.roleData.is_show || '';
  436. getTable(this.roleData)
  437. .then(async (res) => {
  438. this.tableData = res.data;
  439. this.loading = false;
  440. })
  441. .catch((res) => {
  442. this.loading = false;
  443. this.$Message.error(res.msg);
  444. });
  445. },
  446. getMenusUnique() {
  447. getMenusUnique().then((res) => {
  448. let data = res.data;
  449. this.$store.commit('userInfo/uniqueAuth', data.uniqueAuth);
  450. this.$store.commit('menus/getmenusNav', data.menus);
  451. this.$store.dispatch('routesList/setRoutesList', data.menus);
  452. let arr = formatFlatteningRoutes(this.$router.options.routes);
  453. this.formatTwoStageRoutes(arr);
  454. let routes = formatFlatteningRoutes(data.menus);
  455. this.$store.commit('menus/setOneLvRoute', routes);
  456. this.bus.$emit('routesListChange');
  457. });
  458. },
  459. formatTwoStageRoutes(arr) {
  460. if (arr.length <= 0) return false;
  461. const newArr = [];
  462. const cacheList = [];
  463. arr.forEach((v) => {
  464. if (v && v.meta && v.meta.keepAlive) {
  465. newArr.push({ ...v });
  466. cacheList.push(v.name);
  467. this.$store.dispatch('keepAliveNames/setCacheKeepAlive', cacheList);
  468. }
  469. });
  470. return newArr;
  471. },
  472. // 关闭按钮
  473. cancel() {
  474. this.$emit('onCancel');
  475. },
  476. },
  477. };
  478. </script>
  479. <style scoped lang="scss">
  480. .vxeTable {
  481. > .vxe-table--header-wrapper {
  482. background: #fff !important;
  483. }
  484. .icon {
  485. font-size: 20px;
  486. }
  487. }
  488. .rule {
  489. display: flex;
  490. flex-wrap: wrap;
  491. overflow-y: scroll;
  492. height: max-content;
  493. max-height: 600px;
  494. flex: 1;
  495. }
  496. .tree::-webkit-scrollbar {
  497. width: 2px;
  498. background-color: #f5f5f5;
  499. }
  500. /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
  501. .rule::-webkit-scrollbar {
  502. width: 10px;
  503. height: 10px;
  504. background-color: #f5f5f5;
  505. }
  506. /*定义滚动条轨道 内阴影+圆角*/
  507. .rule::-webkit-scrollbar-track {
  508. border-radius: 4px;
  509. background-color: #f5f5f5;
  510. }
  511. /*定义滑块 内阴影+圆角*/
  512. .rule::-webkit-scrollbar-thumb {
  513. border-radius: 4px;
  514. background-color: #ccc;
  515. }
  516. .rule-list {
  517. background-color: #f2f2f2;
  518. width: 48.5%;
  519. height: max-content;
  520. margin: 5px;
  521. border-radius: 3px;
  522. padding: 10px;
  523. color: #333;
  524. cursor: pointer;
  525. transition: all 0.1s;
  526. }
  527. .rule-list:hover {
  528. background-color: #badbfb;
  529. }
  530. .rule-list div {
  531. white-space: nowrap;
  532. }
  533. .select-rule {
  534. background-color: #badbfb;
  535. }
  536. .route-list {
  537. display: flex;
  538. margin-top: 10px;
  539. .tree {
  540. width: 200px;
  541. overflow-y: scroll;
  542. max-height: 600px;
  543. /deep/ .el-tree-node__children .el-tree-node .el-tree-node__content {
  544. padding-left: 14px !important;
  545. }
  546. }
  547. }
  548. </style>