소스 검색

权限页面

yangg 1 년 전
부모
커밋
8cc134940b

+ 81 - 0
src/api/menus.js

@@ -0,0 +1,81 @@
+/**
+ * 菜单管理相关API
+ */
+
+import request from '@/utils/request'
+
+/**
+ * 获取菜单
+ * @param parent_id
+ */
+export function getMenusChildren(parent_id = 0) {
+  return request({
+    url: `admin/systems/menus/children/${parent_id}`,
+    method: 'get',
+    params: { parent_id }
+  })
+}
+
+/**
+ * 添加菜单
+ * @param params
+ */
+export function addMenu(params) {
+  return request({
+    url: 'admin/systems/menus',
+    method: 'post',
+    data: params
+  })
+}
+
+/**
+ * 修改菜单
+ * @param id
+ * @param params
+ */
+export function editMenu(id, params) {
+  return request({
+    url: `admin/systems/menus/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+
+/**
+ * 删除菜单
+ * @param id
+ */
+export function deleteMenu(id) {
+  return request({
+    url: `admin/systems/menus/${id}`,
+    method: 'delete'
+  })
+}
+
+// /admin/erp/user/menu
+export function editHomeMenu(params) {
+  return request({
+    url: '/admin/erp/user/menu',
+    method: 'post',
+    data: params
+  })
+}
+
+// /admin/erp/user/menu/{id}
+export function deleteHomeMenu(id) {
+  return request({
+    url: `/admin/erp/user/menu/${id}`,
+    method: 'delete'
+  })
+}
+
+// 获取分页列表 /admin/erp/user/menu
+
+export function getMenuList(params) {
+  return request({
+    url: '/admin/erp/user/menu',
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}

+ 32 - 2
src/components/layout/Sidebar.vue

@@ -105,6 +105,36 @@
                 </li>
               </ul>
             </li> -->
+            <li class="menu nav-item">
+              <button
+                type="button"
+                class="nav-link group w-full"
+                :class="{ active: activeDropdown === 'package' }"
+                @click="activeDropdown === 'package' ? (activeDropdown = null) : (activeDropdown = 'package')"
+              >
+                <div class="flex items-center">
+                  <icon-menu-users class="group-hover:!text-primary shrink-0" />
+
+                  <span class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">{{ $t('package') }}</span>
+                </div>
+                <div :class="{ 'rtl:rotate-90 -rotate-90': activeDropdown == 'package' }">
+                  <icon-caret-down />
+                </div>
+              </button>
+              <vue-collapsible :isOpen="activeDropdown === 'package'">
+                <ul class="sub-menu text-gray-500">
+                  <li>
+                    <router-link to="/package/packageList" @click="toggleMobileMenu">{{ $t('packageList') }}</router-link>
+                  </li>
+                   <li>
+                    <router-link to="/package/packagePermissions" @click="toggleMobileMenu">{{ $t('packagePermissions') }}</router-link>
+                  </li>
+                </ul>
+              </vue-collapsible>
+              <vue-collapsible :isOpen="activeDropdown === 'package'">
+                <ul class="sub-menu text-gray-500"></ul>
+              </vue-collapsible>
+            </li>
             <li class="menu nav-item">
               <button
                 type="button"
@@ -123,9 +153,9 @@
               </button>
               <vue-collapsible :isOpen="activeDropdown === 'users'">
                 <ul class="sub-menu text-gray-500">
-                  <li>
+                  <!-- <li>
                     <router-link to="/setting/package" @click="toggleMobileMenu">{{ $t('package') }}</router-link>
-                  </li>
+                  </li> -->
                   <li>
                     <router-link to="/setting/company" @click="toggleMobileMenu">{{ $t('company') }}</router-link>
                   </li>

+ 0 - 0
src/composables/Foundation.js → src/composables/ui-utils/Foundation.js


+ 4 - 1
src/ui-utils/RegExp.js → src/composables/ui-utils/RegExp.js

@@ -47,6 +47,8 @@ let URL = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[
 let TEL = /0\d{2,3}-\d{7,8}/
 //字母和数字
 let code=/^[A-Za-z0-9]+$/
+/* 禁止输入空格 */
+let reg=/^\S*$/
 
 module.exports = {
   mobile: mobile,
@@ -60,5 +62,6 @@ module.exports = {
   userName: userName,
   URL: URL,
   TEL: TEL,
-  code:code
+  code:code,
+  reg:reg
 }

+ 7 - 0
src/composables/ui-utils/index.js

@@ -0,0 +1,7 @@
+const Foundation = require('./Foundation');
+const RegExp = require('./RegExp');
+
+module.exports = {
+  Foundation,
+  RegExp
+};

+ 5 - 2
src/locales/zh.json

@@ -5,11 +5,14 @@
     "goodsTransfer":"库存列表",
     "goodsExchange":"入库管理",
     "goodsOutBound":"出库管理",
-    
+       
     "setting":"系统管理",
-    "package":"套餐管理",
     "company":"公司管理",
     "roleManage":"权限管理",
+    
+    "package":"套餐管理",
+    "packageList":"套餐列表",
+    "packagePermissions":"套餐权限",
 
     "sales": "销售量",
     "analytics": "分析",

+ 0 - 1
src/permission.js

@@ -4,7 +4,6 @@ import { useStore } from 'vuex';
 import 'nprogress/nprogress.css' // Progress 进度条样式 */
 import { ElMessage } from 'element-plus'
 import Storage from '@/utils/storage'
-//import  {Foundation}  from './ui-utils/index'
 
 const whiteList = ['/login', '/franchise/login']
 

+ 14 - 36
src/router/index.ts

@@ -31,7 +31,19 @@ const asyncRouterMap: RouteRecordRaw[] = [
             { path: 'goodsExchange', component: () => import('@/views/stock/warehousing.vue'), name: 'goodsExchange', meta: { title: 'goodsExchange' } },
             { path: 'goodsOutBound', component: () => import('@/views/stock/outbound.vue'), name: 'goodsOutBound', meta: { title: 'goodsOutBound' } },
         ]
-    }, // 系统管理
+    }, 
+    // 系统管理
+    {
+        path: '/package',
+        redirect: '/package',
+        name: 'package',
+        children: [
+            { path: 'packageList', component: () => import('@/views/package/packageList.vue'), name: 'packageList', meta: { title: 'packageList',layout: 'app' } },
+            { path: 'packagePermissions', component: () => import('@/views/package/packagePermissions.vue'), name: 'packagePermissions', meta: { title: 'packagePermissions',layout: 'app' } },
+            { path: 'packageAdd', component: () => import('@/views/package/packageAdd.vue'), name: 'packageAdd', meta: { title: 'packageAdd',layout: 'app' } },
+        ]
+    },
+    // 系统管理
     {
         path: '/setting',
         redirect: '/setting',
@@ -41,43 +53,9 @@ const asyncRouterMap: RouteRecordRaw[] = [
             icon: 'setting-manage'
         },
         children: [
-            { path: 'package', component: () => import('@/views/setting/package.vue'), name: 'package', meta: { title: 'package',layout: 'app' } },
             { path: 'company', component: () => import('@/views/setting/company.vue'), name: 'company', meta: { title: 'company',layout: 'app' } },
             { path: 'roleManage', component: () => import('@/views/setting/roleManage.vue'), name: 'roleManage', meta: { title: 'roleManage',layout: 'app' } },
-            /* {
-                path: '/setting/system-params',
-                component: () => import('@/views/setting/shop-settings/index'),
-                redirect: '/setting/system-params/system',
-                name: 'systemParams',
-                meta: { title: 'systemParams' },
-                children: [
-                    { path: 'system', component: () => import('@/views/setting/shop-settings/systemSettings'), name: 'systemSettings', meta: { title: 'systemSettings' } },
-                    { path: 'storage-solution', component: () => import('@/views/setting/shop-settings/storageSolution'), name: 'storageSolution', meta: { title: 'storageSolution' } },
-                    { path: 'dict-data', component: () => import('@/views/setting/tool-manage/dictData'), name: 'dictData', meta: { title: 'dictData' } },
-                    { path: 'system-logs', component: () => import('@/views/setting/systemLogs'), name: 'systemLogs', meta: { title: 'systemLogs' } },
-                    // serialList
-
-                ]
-            },
-            {
-                path: '/setting/oauth2',
-                component: () => import('@/views/setting/shop-settings/index'),
-                redirect: '/setting/oauth2/client',
-                name: 'oauth2',
-                hidden: true,
-                meta: { title: 'oauth2' },
-                children: [
-                    { path: 'client', component: () => import('@/views/setting/oauth2/client'), name: 'oauth2Client', meta: { title: 'oauth2Client' } },
-                    { path: 'token', component: () => import('@/views/setting/oauth2/token'), name: 'oauth2Token', meta: { title: 'oauth2Token' } }
-                ]
-            }, */
-           /*  { path: 'administrator-manage', component: () => import('@/views/setting/auth-settings/administratorManage'), name: 'administratorManage', meta: { title: 'administratorManage' } },
-            { path: 'role-manage', component: () => import('@/views/setting/auth-settings/roleManage'), name: 'roleManage', meta: { title: 'roleManage' } },
-            { path: 'role-permission/:id(\\d+)', component: () => import('@/views/setting/auth-settings/rolePermission'), name: 'roleManageEdit', hidden: true, meta: { title: 'rolePermission', noCache: true } },
-            { path: 'role-permission', component: () => import('@/views/setting/auth-settings/rolePermission'), name: 'roleManageAdd', hidden: true, meta: { title: 'rolePermission', noCache: true } },
-            { path: 'menu-manage', component: () => import('@/views/setting/tool-manage/menuManage'), name: 'menuManage', meta: { title: 'menuManage' } },
-            { path: 'branch', component: () => import('@/views/setting/tool-manage/branch'), name: 'branch', meta: { title: 'branch' } },
-            { path: 'station', component: () => import('@/views/setting/tool-manage/station'), name: 'station', meta: { title: 'station' } }, */
+            { path: 'roleManageAdd', component: () => import('@/views/setting/roleManageAdd.vue'), name: 'roleManageAdd', meta: { title: 'roleManageAdd',layout: 'app' } },
         ]
     },
 ]

+ 0 - 243
src/ui-utils/Foundation.js

@@ -1,243 +0,0 @@
-/**
- * 一些常用的基础方法
- * unixToDate    将unix时间戳转换为指定格式
- * dateToUnix    将时间转unix时间戳
- * deepClone     对一个对象进行深拷贝
- * formatPrice   货币格式化
- * secrecyMobile 手机号隐私保护
- * randomString  随机生成指定长度的字符串
- */
-
-/**
- * 将数据转成tree
- * @param list   数据
- * @param parent_id 父id
- * @returns {*|string}
- */
-let buildTree = function buildTree(list, parent_id) {
-  const tree = []
-  for (let i = 0; i < list.length; i++) {
-    if (list[i].parent_id === parent_id) {
-      const node = {
-        id: list[i].id,
-        parent_id: list[i].parent_id,
-        name: list[i].name,
-        create_time: list[i].create_time,
-        remark: list[i].remark,
-        sn: list[i].sn,
-        sort: list[i].sort,
-        type: list[i].type,
-        region_ids: list[i].region_ids,
-        children: this.buildTree(list, list[i].id)
-      }
-      tree.push(node)
-    }
-  }
-  return tree
-}
-
-/**
- * 将unix时间戳转换为指定格式
- * @param unix   时间戳【秒】
- * @param format 转换格式
- * @returns {*|string}
- */
-let unixToDate = function unixToDate(unix, format) {
-  RegExp = window.RegExp
-  if (!unix) return unix
-  let _format = format || 'yyyy-MM-dd hh:mm:ss'
-  let d = new Date(unix * 1000)
-  let o = {
-    'M+': d.getMonth() + 1,
-    'd+': d.getDate(),
-    'h+': d.getHours(),
-    'm+': d.getMinutes(),
-    's+': d.getSeconds(),
-    'q+': Math.floor((d.getMonth() + 3) / 3),
-    S: d.getMilliseconds()
-  }
-  if (/(y+)/.test(_format)) _format = _format.replace(RegExp.$1, (d.getFullYear() + '').substr(4 - RegExp.$1.length))
-  for (let k in o) if (new RegExp('(' + k + ')').test(_format)) _format = _format.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
-  return _format
-}
-
-/**
- * 将时间转unix时间戳
- * @param date
- * @returns {number} 【秒】
- */
-let dateToUnix = function dateToUnix(date) {
-  let newStr = date.replace(/:/g, '-')
-  newStr = newStr.replace(/ /g, '-')
-  let arr = newStr.split('-')
-  let datum = new Date(Date.UTC(
-    arr[0],
-    arr[1] - 1,
-    arr[2],
-    arr[3] - 8 || -8,
-    arr[4] || 0,
-    arr[5] || 0
-  ))
-  return parseInt(datum.getTime() / 1000)
-}
-
-/**
- * 对一个对象进行深拷贝
- * @param object
- * @returns {*}
- */
-let deepClone = function deepClone(object) {
-  let str
-  let newobj = object.constructor === Array ? [] : {}
-  if (typeof object !== 'object') {
-    return object
-  } else if (window.JSON) {
-    str = JSON.stringify(object)
-    newobj = JSON.parse(str)
-  } else {
-    for (let i in object) {
-      if (object.hasOwnProperty(i)) {
-        newobj[i] = typeof object[i] === 'object' ? deepClone(object[i]) : object[i]
-      }
-    }
-  }
-  return newobj
-}
-
-/**
- * 货币格式化
- * @param price
- * @returns {string}
- */
-let formatPrice = function formatPrice(price) {
-  if (typeof price !== 'number') return price
-  return String(Number(price).toFixed(2)).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
-}
-
-/**
- * 手机号隐私保护
- * 隐藏中间四位数字
- * @param mobile
- * @returns {*}
- */
-let secrecyMobile = function secrecyMobile(mobile) {
-  mobile = String(mobile)
-  if (!/\d{11}/.test(mobile)) {
-    return mobile
-  }
-  return mobile.replace(/(\d{3})(\d{4})(\d{4})/, '$1****$3')
-}
-
-/**
- * 随机生成指定长度的字符串
- * @param length
- * @returns {string}
- */
-let randomString = function randomString(length) {
-  if (!length) length = 32
-  let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
-  let maxPos = chars.length
-  let _string = ''
-  for (let i = 0; i < length; i++) {
-    _string += chars.charAt(Math.floor(Math.random() * maxPos))
-  }
-  return _string
-}
-
-/**
- * 计算传秒数的倒计时【天、时、分、秒】
- * @param seconds
- * @returns {{day : *, hours : *, minutes : *, seconds : *}}
- */
-let countTimeDown = function countTimeDown(seconds) {
-  let leftTime = function(time) {
-    if (time < 10) time = '0' + time
-    return time + ''
-  }
-  return {
-    day: leftTime(parseInt(seconds / 60 / 60 / 24, 10)),
-    hours: leftTime(parseInt(seconds / 60 / 60 % 24, 10)),
-    minutes: leftTime(parseInt(seconds / 60 % 60, 10)),
-    seconds: leftTime(parseInt(seconds % 60, 10))
-  }
-}
-
-/**
- * 计算当前时间到第二天0点的倒计时[秒]
- * @returns {number}
- */
-let theNextDayTime = function theNextDayTime() {
-  let nowDate = new Date()
-  let time = new Date(nowDate.getFullYear(), nowDate.getMonth(), nowDate.getDate() + 1, 0, 0, 0).getTime() - nowDate.getTime()
-  return parseInt(time / 1000)
-}
-
-/**
- * 获取顶部路由路径
- * @param router
- * @param next
- * @returns {string}
- */
-let toFirstRoute = function getTopRoutePath(router, next) {
-  const excludes = [
-    "",
-    "*",
-    '/404',
-    '/401',
-    '/500',
-    '/login',
-    '/cashier/login',
-    '/franchise/login',
-    '/payType'
-  ]
-  const paths = router.getRoutes().map(item => item.path)
-  const path = paths.filter(item => !excludes.includes(item) && item.indexOf('/:') === -1)[0]
-  if (typeof next === 'function') {
-    next(path)
-  } else {
-    router.replace(path)
-  }
-}
-
-/**
- * 获取顶部路由路径
- * @param routers
- * @returns {string}
- */
-let getTopRoutePath = function getTopRoutePath(routers) {
-  let paths = []
-  getPath(getFirstRoute(routers))
-
-  function getPath(route) {
-    paths.push(route.path)
-    if (Array.isArray(route.children) && getFirstRoute(route.children)) {
-      getPath(getFirstRoute(route.children))
-    }
-  }
-
-  let path = paths.filter(item => !!item).join('/')
-  if (path.indexOf('/') !== 0) {
-    path = '/' + path
-  }
-  path = path.replaceAll('//', '/')
-
-  function getFirstRoute(routers) {
-    return routers.find(item => item.path.indexOf('/:') === -1)
-  }
-
-  return path
-}
-
-module.exports = {
-  buildTree: buildTree,
-  unixToDate: unixToDate,
-  dateToUnix: dateToUnix,
-  deepClone: deepClone,
-  formatPrice: formatPrice,
-  secrecyMobile: secrecyMobile,
-  randomString: randomString,
-  countTimeDown: countTimeDown,
-  theNextDayTime: theNextDayTime,
-  toFirstRoute: toFirstRoute,
-  getTopRoutePath: getTopRoutePath
-}

+ 0 - 5
src/ui-utils/index.js

@@ -1,5 +0,0 @@
-import Foundation from './Foundation'
-import RegExp from './RegExp'
-
-export const Foundation = Foundation
-export const RegExp = RegExp

+ 2 - 2
src/views/auth/boxed-signin.vue

@@ -52,7 +52,7 @@
                             </button>
                         </form> -->
             <!-- <el-button @click="goHome">测试</el-button> -->
-            <el-form :model="loginForm" :rules="loginRules" ref="loginFormRef" class="login-input">
+            <el-form :model="loginForm" :rules="loginRules" ref="loginFormRef" class="login-input" label-width="120px">
               <el-form-item label="公司名称:" prop="company_name">
                 <el-input v-model="loginForm.company_name" :placeholder="$t('company_name')" autofocus clearable>
                   <template v-slot:prefix>
@@ -63,7 +63,7 @@
                   <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
                 </el-select> -->
               </el-form-item>
-              <el-form-item label="账  号:" prop="username">
+              <el-form-item label="账号:" prop="username">
                 <el-input v-model="loginForm.username" :placeholder="$t('username')" autofocus clearable>
                   <template v-slot:prefix>
                     <svg-icon class="el-input__icon" icon-class="user" />

+ 524 - 0
src/views/package/packageAdd.vue

@@ -0,0 +1,524 @@
+<template>
+  <div class="permission-container">
+    <el-form :model="permissionForm" :rules="permissionRules" ref="permissionForm" label-width="200px" class="demo-ruleForm">
+      <el-form-item label="角色名称" prop="role_name">
+        <el-input v-model="permissionForm.role_name"></el-input>
+      </el-form-item>
+      <el-form-item label="数据范围" prop="dept_ids">
+        <el-cascader
+          @visible-change="visibleChange"
+          ref="cascader"
+          :clearable="true"
+          v-model="permissionForm.dept_ids"
+          :options="deptList"
+          @change="selectHandle"
+          :props="props"
+        ></el-cascader>
+      </el-form-item>
+      <el-form-item label="角色描述" prop="role_describe">
+        <el-input v-model="permissionForm.role_describe"></el-input>
+      </el-form-item>
+      <el-form-item label="角色权限" prop="permission">
+        <el-checkbox :indeterminate="allIndeterminate" v-model="allCheck" @change="handleCheckAll">全部选择</el-checkbox>
+        <div v-for="(item, index) in permissions" :key="item.identifier" class="level_1">
+          <el-row :gutter="20">
+            <el-col :span="4">
+              <el-checkbox
+                v-model="item.checked"
+                :label="item.title"
+                :indeterminate="checkIndeterminate(item.children)"
+                @change="handleCheckboxChanged(item)"
+              ></el-checkbox>
+            </el-col>
+            <el-col :span="20">
+              <div v-for="(_item, _index) in item.children" :key="_item.identifier" class="checkbox-dropdown">
+                <el-popover placement="bottom" trigger="hover" :open-delay="300" :disabled="!hasChildren(_item)" :append-to-body="false">
+                  <template #reference>
+                    <el-checkbox
+                      v-model="_item.checked"
+                      :indeterminate="checkIndeterminate(_item.children)"
+                      @click.stop
+                      @change="handleCheckboxChanged(_item, item)"
+                    >
+                      {{ _item.title }}
+                      <template v-if="hasChildren(_item)">
+                        <i class="el-icon-arrow-down"></i>
+                      </template>
+                    </el-checkbox>
+                  </template>
+                  <div class="role-dropdown__list" v-if="hasChildren(_item)">
+                    <div class="role-dropdown" v-for="(__item, __index) in _item.children" :key="__item.identifier">
+                      <el-popover placement="right" trigger="hover" :open-delay="200" :disabled="!hasChildren(__item)" :append-to-body="false">
+                        <template #reference>
+                          <div class="role-dropdown__label">
+                            <el-checkbox
+                              v-model="__item.checked"
+                              :indeterminate="checkIndeterminate(__item.children)"
+                              @click.stop
+                              @change="handleCheckboxChanged(__item, _item)"
+                            >
+                              {{ __item.title }}
+                              <template v-if="hasChildren(__item)">
+                                <i class="el-icon-arrow-right"></i>
+                              </template>
+                            </el-checkbox>
+                          </div>
+                        </template>
+                        <div class="role-dropdown__list" v-if="hasChildren(__item)">
+                          <div class="role-dropdown" v-for="(___item, __index) in __item.children" :key="___item.identifier">
+                            <el-popover placement="right" trigger="hover" :open-delay="200" :disabled="!hasChildren(___item)" :append-to-body="false">
+                              <template #reference>
+                                <div class="role-dropdown__label">
+                                  <el-checkbox
+                                    v-model="___item.checked"
+                                    :indeterminate="checkIndeterminate(___item.children)"
+                                    @click.stop
+                                    @change="handleCheckboxChanged(___item, __item)"
+                                    >{{ ___item.title }}</el-checkbox
+                                  >
+                                </div>
+                              </template>
+                            </el-popover>
+                          </div>
+                        </div>
+                      </el-popover>
+                    </div>
+                  </div>
+                </el-popover>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+      </el-form-item>
+      <el-form-item label="">
+        <el-button type="primary" size="small" style="margin-top: 15px" @click="saveRolePermission">保存设置</el-button>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+  
+  <script lang="ts" setup>
+import { ref, reactive, onMounted, watch, nextTick } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { ElMessage } from 'element-plus';
+import * as API_Auth from '@/api/auth';
+import * as API_Menus from '@/api/menus';
+import * as API_Setting from '@/api/setting';
+/*   import { Foundation } from '../../composables/ui-utils'; */
+
+const route = useRoute();
+const router = useRouter();
+
+const props = {
+  label: 'name',
+  value: 'id',
+  emitPath: false,
+  multiple: true,
+  checkStrictly: true,
+};
+
+const checked = ref(true);
+const deptList = ref([]);
+const oneDimensionalList = ref([]); // 源数据平铺成一级节点
+
+/** 权限 表单 */
+const permissionForm = reactive({
+  data_scope: 'ALL',
+  role_name: '',
+  role_describe: '',
+});
+
+/** 权限 表单规则 */
+const permissionRules = reactive({
+  role_name: [
+    { required: true, message: '请输入角色名称!', trigger: 'blur' },
+    { min: 1, max: 10, message: '长度在 1 到 10 个字符', trigger: 'blur' },
+  ],
+});
+
+const permissions = ref([]);
+const allCheck = ref(false);
+const allIndeterminate = ref(false);
+const isAllSelected = ref(false);
+
+const role_id = ref<any>(route.query.id);
+console.log('role_id:', route.query);
+const searchSelectAllNodeIndex = () => {
+  let selectAllOptionIndex = -1;
+  permissionForm.dept_ids.forEach((res, index) => {
+    if (res.length === 1) {
+      selectAllOptionIndex = index;
+    }
+  });
+  return selectAllOptionIndex;
+};
+
+const visibleChange = (visible) => {
+  if (visible) {
+    deptList.value.unshift({
+      id: '全选',
+      name: '全选',
+      value: '全选',
+    });
+  } else {
+    deptList.value.shift();
+  }
+};
+
+// 选择级联选择器
+const selectHandle = async (e = []) => {
+  const selectLength = e.filter((v) => v !== '全选').length;
+  const has_all_option = e.some((v) => v === '全选');
+  let dept_ids = [...permissionForm.dept_ids];
+
+  if (has_all_option && isAllSelected.value) {
+    // 移除了选项中除全部外的某个节点
+    dept_ids = dept_ids.filter((v) => v !== '全选');
+    dept_ids.splice(searchSelectAllNodeIndex(), 1);
+    permissionForm.dept_ids = dept_ids;
+    isAllSelected.value = false;
+  } else if (has_all_option && !isAllSelected.value) {
+    // 选中全部节点
+    isAllSelected.value = true;
+    const deptListData = deptList.value;
+    const dept_ids = [];
+    const loopSelectData = (list) => {
+      list.forEach((e) => {
+        dept_ids.push(e.id);
+        if (e.children && e.children.length > 0) {
+          loopSelectData(e.children);
+        }
+      });
+    };
+    loopSelectData(deptListData);
+    permissionForm.dept_ids = dept_ids;
+  } else if (!has_all_option && isAllSelected.value) {
+    // 取消选中全部节点
+    isAllSelected.value = false;
+    permissionForm.dept_ids = [];
+  } else if (selectLength === oneDimensionalList.value.length) {
+    isAllSelected.value = true;
+    dept_ids.unshift('全选');
+    permissionForm.dept_ids = dept_ids;
+  }
+  nextTick(() => {
+    // 强制更新
+  });
+};
+/* 处理树形数据 */
+let buildTree = function buildTree(list, parent_id) {
+  const tree = [];
+  for (let i = 0; i < list.length; i++) {
+    if (list[i].parent_id === parent_id) {
+      const node = {
+        id: list[i].id,
+        parent_id: list[i].parent_id,
+        name: list[i].name,
+        create_time: list[i].create_time,
+        remark: list[i].remark,
+        sn: list[i].sn,
+        sort: list[i].sort,
+        type: list[i].type,
+        region_ids: list[i].region_ids,
+        children: this.buildTree(list, list[i].id),
+      };
+      tree.push(node);
+    }
+  }
+  return tree;
+};
+/** 获取部门 */
+const GET_DeptList = () => {
+  API_Setting.getDeptList().then((response) => {
+    deptList.value = buildTree(response, '0');
+    const oneDimensionalListData = [];
+    const loopData = (list) => {
+      list.forEach((e) => {
+        oneDimensionalListData.push(e);
+        if (e.children && e.children.length > 0) {
+          loopData(e.children);
+        }
+      });
+    };
+    loopData(response);
+    oneDimensionalList.value = oneDimensionalListData;
+    if (permissionForm.dept_ids.length === oneDimensionalListData.length) {
+      // 设置数据范围全选
+      permissionForm.dept_ids.push('全选');
+      isAllSelected.value = true;
+    }
+  });
+};
+
+/** 判断是否还有子集 */
+const hasChildren = (item) => {
+  return Array.isArray(item.children) && item.children.length !== 0;
+};
+
+/** 全选 */
+const handleCheckAll = (checked) => {
+  allIndeterminate.value = false;
+  permissions.value = setPermissionsCheck(permissions.value, checked);
+};
+
+/** 选择 */
+const handleCheckboxChanged = (item, parent) => {
+  if (item.children && item.children.length) {
+    item.children = setPermissionsCheck(item.children, item.checked);
+  }
+  countAllPermissions();
+  countParentChecked();
+};
+
+/** 设置权限状态 */
+const setPermissionsCheck = (permissions, checked) => {
+  const perm = [...permissions];
+  perm.map((item) => {
+    item.checked = checked;
+    if (item.children && item.children.length) {
+      item.children = setPermissionsCheck(item.children, checked);
+    }
+  });
+  return perm;
+};
+
+/** 检测是否有不确定性 */
+const checkIndeterminate = (permissions) => {
+  if (!Array.isArray(permissions)) return false;
+  const _len = permissions.length;
+  const __len = permissions.filter((item) => item.checked).length;
+  return __len !== 0 && _len !== __len;
+};
+
+/** 获取所有权限展开后的长度、被选中的长度 */
+const countAllPermissions = (permissions) => {
+  permissions = permissions || permissions.value;
+  const _list = [];
+  permissions.forEach((item) => {
+    _list.push(item);
+    if (item.children) _list.push(...countAllPermissions(item.children));
+  });
+  const length = _list.length;
+  const length_checked = _list.filter((_item) => _item.checked).length;
+  allCheck.value = length === _list.filter((_item) => _item.checked).length;
+  allIndeterminate.value = length_checked !== 0 && length !== length_checked;
+  return _list;
+};
+
+/** 计算所有父辈的选中状态 */
+const countParentChecked = (permissions) => {
+  permissions = permissions || permissions.value;
+  permissions.forEach((item) => {
+    if (item.children && item.children.length) {
+      countParentChecked(item.children);
+      const length = item.children.length;
+      const checked_length = item.children.filter((_item) => _item.checked).length;
+      item.checked = !!checked_length;
+    }
+  });
+};
+
+/** 保存角色权限 */
+const saveRolePermission = () => {
+  permissionForm.validate((valid) => {
+    if (valid) {
+      let params = {
+        ...permissionForm,
+        menus: permissions.value,
+      };
+      if (!params.dept_ids) {
+        ElMessage.error('请选择数据范围!');
+        return;
+      }
+      params.dept_ids = params.dept_ids.filter((v) => v !== '全选');
+      if (params.dept_ids) {
+        if (isAllSelected.value) {
+          params.data_scope = 'ALL';
+          params.dept_ids = params.dept_ids.join(',');
+        } else {
+          params.data_scope = 'DEPT_CUSTOM';
+          params.dept_ids = params.dept_ids.join(',');
+        }
+      }
+      const saveSuccess = () => {
+        ElMessage.success('保存成功!');
+        router.push({ name: 'RoleList' });
+      };
+      role_id.value === 0 ? API_Auth.addRole(params).then(() => saveSuccess()) : API_Auth.editRole(role_id.value, params).then(() => saveSuccess());
+    } else {
+      ElMessage.error('表单填写有误,请检查!');
+      return false;
+    }
+  });
+};
+
+/** 获取权限菜单树 */
+const GET_RolePermission = () => {
+  API_Menus.getMenusChildren().then((res) => {
+    if (role_id.value !== '0') {
+      console.log(role_id.value !== 0);
+      API_Auth.getRolePermission(role_id.value).then((response) => {
+        role_id.value = response.role_id;
+        permissionForm.data_scope = response.data_scope;
+        permissionForm.role_name = response.role_name;
+        permissionForm.role_describe = response.role_describe;
+        permissionForm.dept_ids = response.dept_ids.split(',');
+        const checkedIds = expandRouters(response.menus);
+        permissions.value = filterRoleRouter(res, checkedIds);
+        countAllPermissions();
+        GET_DeptList();
+      });
+    } else {
+      permissions.value = res;
+      countAllPermissions();
+      GET_DeptList();
+    }
+  });
+};
+
+/** 展开路由的identifier */
+const expandRouters = (menus) => {
+  const routers = [];
+  menus.forEach((item) => {
+    item.checked && routers.push(item.identifier);
+    if (item.children && item.children.length) {
+      routers.push(...expandRouters(item.children));
+    }
+  });
+  return routers;
+};
+
+/** 递归筛选被选中的路由 */
+const filterRoleRouter = (routers, ids) => {
+  const _routers = [];
+  routers.forEach((item) => {
+    if (ids.includes(item.identifier) || item.hidden) {
+      item.checked = true;
+    } else {
+      item.checked = false;
+    }
+    if (item.children) {
+      item.children = filterRoleRouter(item.children, ids);
+    }
+    _routers.push(item);
+  });
+  return _routers;
+};
+
+onMounted(() => {
+  GET_RolePermission();
+});
+
+watch(
+  () => route.query.id,
+  () => {
+    role_id.value = route.query.id;
+    GET_RolePermission();
+  }
+);
+</script>
+  
+<style scoped>
+.permission-container {
+  padding: 10px;
+  background-color: #fff;
+  padding-bottom: 150px;
+}
+
+.level_1 {
+  padding: 15px 0;
+  border-bottom: 1px dashed #e7e7e7;
+}
+
+.level_1:last-child {
+  border-bottom: none;
+}
+
+::v-deep .el-form-item__label {
+  padding-top: 15px;
+}
+
+::v-deep .el-form-item__content {
+  display: block;
+  border-left: 1px solid #e7e7e7;
+  padding-left: 20px;
+  padding-top: 15px;
+}
+
+::v-deep .el-form-item__content .el-form-item__error {
+  padding-left: 20px;
+}
+
+::v-deep .el-form-item:last-child .el-form-item__content {
+  padding-top: 0;
+}
+
+::v-deep .el-form-item:not(:first-child) {
+  border-top: 1px solid #e7e7e7;
+  position: relative;
+}
+
+::v-deep .el-form-item:not(:first-child)::after {
+  content: ' ';
+  width: 1px;
+  height: 22px;
+  background-color: #e7e7e7;
+  position: absolute;
+  top: -22px;
+  left: 200px;
+}
+
+::v-deep .el-button-group {
+  display: inline-block;
+}
+
+::v-deep .el-button-group .el-button {
+  display: inline-block;
+  padding: 0;
+  border: none;
+}
+
+::v-deep .el-button-group .el-button:focus,
+::v-deep .el-button-group .el-button:hover {
+  color: #606266;
+  border-color: #fff;
+  background-color: #fff;
+}
+
+.checkbox-dropdown {
+  display: inline-block;
+  min-width: 130px;
+  cursor: pointer;
+}
+
+.checkbox-dropdown .checked {
+  color: #409eff;
+}
+
+.role-dropdown {
+  display: flex;
+  align-items: center;
+  width: 25%;
+  height: 30px;
+  margin-bottom: 10px;
+}
+
+.role-dropdown:last-child {
+  margin-bottom: 0;
+}
+
+.role-dropdown__label {
+  display: flex;
+  align-items: center;
+  width: 100%;
+}
+
+::v-deep .role-dropdown__label .el-checkbox__label {
+  color: inherit;
+}
+
+.role-dropdown__list {
+  max-height: 320px;
+  overflow-y: auto;
+}
+</style>

+ 0 - 0
src/views/setting/package.vue → src/views/package/packageList.vue


+ 112 - 0
src/views/package/packagePermissions.vue

@@ -0,0 +1,112 @@
+<template>
+    <en-table-layout :tableData="tableData.data" :loading="loading">
+      <template #toolbar>
+        <div class="inner-toolbar w-11/12 flex justify-between">
+          <div class="toolbar-btns">
+            <el-button size="mini" type="primary" icon="el-icon-circle-plus-outline" v-if="checkPermission(['roleManage:add'])" @click="handleAddRole"
+              >添加</el-button
+            >
+          </div>
+          <div class="toolbar-search">
+            <en-table-search @search="searchEvent" placeholder="请输入关键字" />
+          </div>
+        </div>
+      </template>
+  
+      <template #table-columns>
+        <el-table-column prop="role_name" label="角色名称" />
+        <el-table-column prop="role_describe" label="角色描述" />
+        <el-table-column label="操作">
+          <template #default="scope">
+            <el-button size="mini" type="primary" @click="handleEditRole(scope.row)" v-if="checkPermission(['roleManage:edit'])">编辑</el-button>
+            <el-button size="mini" type="danger" @click="handleDeleteRole(scope.row)" v-if="checkPermission(['roleManage:del'])">删除</el-button>
+          </template>
+        </el-table-column>
+      </template>
+  
+      <template #pagination>
+        <el-pagination
+          v-if="tableData"
+          @size-change="handlePageSizeChange"
+          @current-change="handlePageCurrentChange"
+          :current-page="tableData.page_no"
+          :page-sizes="[10, 20, 50, 100]"
+          :page-size="tableData.page_size"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="tableData.data_total"
+        >
+        </el-pagination>
+      </template>
+    </en-table-layout>
+  </template>
+    
+    <script lang="ts" setup>
+  import { ref, onMounted } from 'vue';
+  import { useRouter } from 'vue-router';
+  import { ElMessage, ElMessageBox } from 'element-plus';
+  import * as API_Auth from '@/api/auth';
+  
+  const router = useRouter();
+  const loading = ref(false);
+  const params = ref({
+    page_no: 1,
+    page_size: 10,
+    keyword: '',
+  });
+  const tableData = ref<any>({});
+  
+  const checkPermission = (permissions: string[]): boolean => {
+    return true;
+  };
+  
+  const handlePageSizeChange = (size: number) => {
+    params.value.page_size = size;
+    GET_RoleList();
+  };
+  
+  const handlePageCurrentChange = (page: number) => {
+    params.value.page_no = page;
+    GET_RoleList();
+  };
+  
+  const handleAddRole = () => {
+    router.push({ name: 'packageAdd', query: { id: 0 } });
+  };
+  
+  const handleEditRole = (row: any) => {
+    //router.push({ name: 'roleManageEdit', params: { id: row.role_id, callback: GET_RoleList } });
+  };
+  
+  const handleDeleteRole = (row: any) => {
+    ElMessageBox.confirm('确定要删除这个角色吗?', '提示', { type: 'warning' })
+      .then(() => {
+        API_Auth.deleteRole(row.role_id).then(() => {
+          ElMessage.success('删除成功!');
+          GET_RoleList();
+        });
+      })
+      .catch(() => {});
+  };
+  
+  const searchEvent = (data: string) => {
+    params.value.keyword = data;
+    params.value.page_no = 1;
+    GET_RoleList();
+  };
+  
+  const GET_RoleList = () => {
+    loading.value = true;
+    API_Auth.getRoleList(params.value)
+      .then((response) => {
+        loading.value = false;
+        tableData.value = response;
+      })
+      .catch(() => {
+        loading.value = false;
+      });
+  };
+  
+  onMounted(() => {
+    GET_RoleList();
+  });
+  </script>

+ 0 - 1
src/views/setting/branch.vue

@@ -103,7 +103,6 @@
   import { ref, onMounted } from 'vue';
   import { ElMessage, ElMessageBox } from 'element-plus';
   import * as API_Setting from '@/api/setting';
- /*  import {   } from '~/ui-utils'; */
   
   const defaultRegions = ref(null);
   const deptList = ref([]);

+ 1 - 1
src/views/setting/company.vue

@@ -244,7 +244,7 @@ const handleRenew = (index: number, row: any) => {
     renewVisible.value = true;
   });
 };
-const submitPackageForm = () => {
+const submitPackageForm = () => { 
   packageFormRef.value.validate((valid: boolean) => {
     if (valid) {
       API_Company.difference(packageId.value).then((response) => {

+ 1 - 1
src/views/setting/roleManage.vue

@@ -70,7 +70,7 @@ const handlePageCurrentChange = (page: number) => {
 };
 
 const handleAddRole = () => {
-  //router.push({ name: 'roleManageAdd', params: { id: 0, callback: GET_RoleList } });
+  router.push({ name: 'roleManageAdd', query: { id: 0 } });
 };
 
 const handleEditRole = (row: any) => {

+ 524 - 0
src/views/setting/roleManageAdd.vue

@@ -0,0 +1,524 @@
+<template>
+    <div class="permission-container">
+      <el-form :model="permissionForm" :rules="permissionRules" ref="permissionForm" label-width="200px" class="demo-ruleForm">
+        <el-form-item label="角色名称" prop="role_name">
+          <el-input v-model="permissionForm.role_name"></el-input>
+        </el-form-item>
+        <el-form-item label="数据范围" prop="dept_ids">
+          <el-cascader
+            @visible-change="visibleChange"
+            ref="cascader"
+            :clearable="true"
+            v-model="permissionForm.dept_ids"
+            :options="deptList"
+            @change="selectHandle"
+            :props="props"
+          ></el-cascader>
+        </el-form-item>
+        <el-form-item label="角色描述" prop="role_describe">
+          <el-input v-model="permissionForm.role_describe"></el-input>
+        </el-form-item>
+        <el-form-item label="角色权限" prop="permission">
+          <el-checkbox :indeterminate="allIndeterminate" v-model="allCheck" @change="handleCheckAll">全部选择</el-checkbox>
+          <div v-for="(item, index) in permissions" :key="item.identifier" class="level_1">
+            <el-row :gutter="20">
+              <el-col :span="4">
+                <el-checkbox
+                  v-model="item.checked"
+                  :label="item.title"
+                  :indeterminate="checkIndeterminate(item.children)"
+                  @change="handleCheckboxChanged(item)"
+                ></el-checkbox>
+              </el-col>
+              <el-col :span="20">
+                <div v-for="(_item, _index) in item.children" :key="_item.identifier" class="checkbox-dropdown">
+                  <el-popover placement="bottom" trigger="hover" :open-delay="300" :disabled="!hasChildren(_item)" :append-to-body="false">
+                    <template #reference>
+                      <el-checkbox
+                        v-model="_item.checked"
+                        :indeterminate="checkIndeterminate(_item.children)"
+                        @click.stop
+                        @change="handleCheckboxChanged(_item, item)"
+                      >
+                        {{ _item.title }}
+                        <template v-if="hasChildren(_item)">
+                          <i class="el-icon-arrow-down"></i>
+                        </template>
+                      </el-checkbox>
+                    </template>
+                    <div class="role-dropdown__list" v-if="hasChildren(_item)">
+                      <div class="role-dropdown" v-for="(__item, __index) in _item.children" :key="__item.identifier">
+                        <el-popover placement="right" trigger="hover" :open-delay="200" :disabled="!hasChildren(__item)" :append-to-body="false">
+                          <template #reference>
+                            <div class="role-dropdown__label">
+                              <el-checkbox
+                                v-model="__item.checked"
+                                :indeterminate="checkIndeterminate(__item.children)"
+                                @click.stop
+                                @change="handleCheckboxChanged(__item, _item)"
+                              >
+                                {{ __item.title }}
+                                <template v-if="hasChildren(__item)">
+                                  <i class="el-icon-arrow-right"></i>
+                                </template>
+                              </el-checkbox>
+                            </div>
+                          </template>
+                          <div class="role-dropdown__list" v-if="hasChildren(__item)">
+                            <div class="role-dropdown" v-for="(___item, __index) in __item.children" :key="___item.identifier">
+                              <el-popover placement="right" trigger="hover" :open-delay="200" :disabled="!hasChildren(___item)" :append-to-body="false">
+                                <template #reference>
+                                  <div class="role-dropdown__label">
+                                    <el-checkbox
+                                      v-model="___item.checked"
+                                      :indeterminate="checkIndeterminate(___item.children)"
+                                      @click.stop
+                                      @change="handleCheckboxChanged(___item, __item)"
+                                      >{{ ___item.title }}</el-checkbox
+                                    >
+                                  </div>
+                                </template>
+                              </el-popover>
+                            </div>
+                          </div>
+                        </el-popover>
+                      </div>
+                    </div>
+                  </el-popover>
+                </div>
+              </el-col>
+            </el-row>
+          </div>
+        </el-form-item>
+        <el-form-item label="">
+          <el-button type="primary" size="small" style="margin-top: 15px" @click="saveRolePermission">保存设置</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </template>
+    
+    <script lang="ts" setup>
+  import { ref, reactive, onMounted, watch, nextTick } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import * as API_Auth from '@/api/auth';
+  import * as API_Menus from '@/api/menus';
+  import * as API_Setting from '@/api/setting';
+  /*   import { Foundation } from '../../composables/ui-utils'; */
+  
+  const route = useRoute();
+  const router = useRouter();
+  
+  const props = {
+    label: 'name',
+    value: 'id',
+    emitPath: false,
+    multiple: true,
+    checkStrictly: true,
+  };
+  
+  const checked = ref(true);
+  const deptList = ref([]);
+  const oneDimensionalList = ref([]); // 源数据平铺成一级节点
+  
+  /** 权限 表单 */
+  const permissionForm = reactive({
+    data_scope: 'ALL',
+    role_name: '',
+    role_describe: '',
+  });
+  
+  /** 权限 表单规则 */
+  const permissionRules = reactive({
+    role_name: [
+      { required: true, message: '请输入角色名称!', trigger: 'blur' },
+      { min: 1, max: 10, message: '长度在 1 到 10 个字符', trigger: 'blur' },
+    ],
+  });
+  
+  const permissions = ref([]);
+  const allCheck = ref(false);
+  const allIndeterminate = ref(false);
+  const isAllSelected = ref(false);
+  
+  const role_id = ref<any>(route.query.id);
+  console.log('role_id:', route.query);
+  const searchSelectAllNodeIndex = () => {
+    let selectAllOptionIndex = -1;
+    permissionForm.dept_ids.forEach((res, index) => {
+      if (res.length === 1) {
+        selectAllOptionIndex = index;
+      }
+    });
+    return selectAllOptionIndex;
+  };
+  
+  const visibleChange = (visible) => {
+    if (visible) {
+      deptList.value.unshift({
+        id: '全选',
+        name: '全选',
+        value: '全选',
+      });
+    } else {
+      deptList.value.shift();
+    }
+  };
+  
+  // 选择级联选择器
+  const selectHandle = async (e = []) => {
+    const selectLength = e.filter((v) => v !== '全选').length;
+    const has_all_option = e.some((v) => v === '全选');
+    let dept_ids = [...permissionForm.dept_ids];
+  
+    if (has_all_option && isAllSelected.value) {
+      // 移除了选项中除全部外的某个节点
+      dept_ids = dept_ids.filter((v) => v !== '全选');
+      dept_ids.splice(searchSelectAllNodeIndex(), 1);
+      permissionForm.dept_ids = dept_ids;
+      isAllSelected.value = false;
+    } else if (has_all_option && !isAllSelected.value) {
+      // 选中全部节点
+      isAllSelected.value = true;
+      const deptListData = deptList.value;
+      const dept_ids = [];
+      const loopSelectData = (list) => {
+        list.forEach((e) => {
+          dept_ids.push(e.id);
+          if (e.children && e.children.length > 0) {
+            loopSelectData(e.children);
+          }
+        });
+      };
+      loopSelectData(deptListData);
+      permissionForm.dept_ids = dept_ids;
+    } else if (!has_all_option && isAllSelected.value) {
+      // 取消选中全部节点
+      isAllSelected.value = false;
+      permissionForm.dept_ids = [];
+    } else if (selectLength === oneDimensionalList.value.length) {
+      isAllSelected.value = true;
+      dept_ids.unshift('全选');
+      permissionForm.dept_ids = dept_ids;
+    }
+    nextTick(() => {
+      // 强制更新
+    });
+  };
+  /* 处理树形数据 */
+  let buildTree = function buildTree(list, parent_id) {
+    const tree = [];
+    for (let i = 0; i < list.length; i++) {
+      if (list[i].parent_id === parent_id) {
+        const node = {
+          id: list[i].id,
+          parent_id: list[i].parent_id,
+          name: list[i].name,
+          create_time: list[i].create_time,
+          remark: list[i].remark,
+          sn: list[i].sn,
+          sort: list[i].sort,
+          type: list[i].type,
+          region_ids: list[i].region_ids,
+          children: buildTree(list, list[i].id),
+        };
+        tree.push(node);
+      }
+    }
+    return tree;
+  };
+  /** 获取部门 */
+  const GET_DeptList = () => {
+    API_Setting.getDeptList().then((response) => {
+      deptList.value = buildTree(response, '0');
+      const oneDimensionalListData = [];
+      const loopData = (list) => {
+        list.forEach((e) => {
+          oneDimensionalListData.push(e);
+          if (e.children && e.children.length > 0) {
+            loopData(e.children);
+          }
+        });
+      };
+      loopData(response);
+      oneDimensionalList.value = oneDimensionalListData;
+      if (permissionForm.dept_ids.length === oneDimensionalListData.length) {
+        // 设置数据范围全选
+        permissionForm.dept_ids.push('全选');
+        isAllSelected.value = true;
+      }
+    });
+  };
+  
+  /** 判断是否还有子集 */
+  const hasChildren = (item) => {
+    return Array.isArray(item.children) && item.children.length !== 0;
+  };
+  
+  /** 全选 */
+  const handleCheckAll = (checked) => {
+    allIndeterminate.value = false;
+    permissions.value = setPermissionsCheck(permissions.value, checked);
+  };
+  
+  /** 选择 */
+  const handleCheckboxChanged = (item, parent) => {
+    if (item.children && item.children.length) {
+      item.children = setPermissionsCheck(item.children, item.checked);
+    }
+    countAllPermissions();
+    countParentChecked();
+  };
+  
+  /** 设置权限状态 */
+  const setPermissionsCheck = (permissions, checked) => {
+    const perm = [...permissions];
+    perm.map((item) => {
+      item.checked = checked;
+      if (item.children && item.children.length) {
+        item.children = setPermissionsCheck(item.children, checked);
+      }
+    });
+    return perm;
+  };
+  
+  /** 检测是否有不确定性 */
+  const checkIndeterminate = (permissions) => {
+    if (!Array.isArray(permissions)) return false;
+    const _len = permissions.length;
+    const __len = permissions.filter((item) => item.checked).length;
+    return __len !== 0 && _len !== __len;
+  };
+  
+  /** 获取所有权限展开后的长度、被选中的长度 */
+  const countAllPermissions = (permissions) => {
+    permissions = permissions || permissions.value;
+    const _list = [];
+    permissions.forEach((item) => {
+      _list.push(item);
+      if (item.children) _list.push(...countAllPermissions(item.children));
+    });
+    const length = _list.length;
+    const length_checked = _list.filter((_item) => _item.checked).length;
+    allCheck.value = length === _list.filter((_item) => _item.checked).length;
+    allIndeterminate.value = length_checked !== 0 && length !== length_checked;
+    return _list;
+  };
+  
+  /** 计算所有父辈的选中状态 */
+  const countParentChecked = (permissions) => {
+    permissions = permissions || permissions.value;
+    permissions.forEach((item) => {
+      if (item.children && item.children.length) {
+        countParentChecked(item.children);
+        const length = item.children.length;
+        const checked_length = item.children.filter((_item) => _item.checked).length;
+        item.checked = !!checked_length;
+      }
+    });
+  };
+  
+  /** 保存角色权限 */
+  const saveRolePermission = () => {
+    permissionForm.validate((valid) => {
+      if (valid) {
+        let params = {
+          ...permissionForm,
+          menus: permissions.value,
+        };
+        if (!params.dept_ids) {
+          ElMessage.error('请选择数据范围!');
+          return;
+        }
+        params.dept_ids = params.dept_ids.filter((v) => v !== '全选');
+        if (params.dept_ids) {
+          if (isAllSelected.value) {
+            params.data_scope = 'ALL';
+            params.dept_ids = params.dept_ids.join(',');
+          } else {
+            params.data_scope = 'DEPT_CUSTOM';
+            params.dept_ids = params.dept_ids.join(',');
+          }
+        }
+        const saveSuccess = () => {
+          ElMessage.success('保存成功!');
+          router.push({ name: 'RoleList' });
+        };
+        role_id.value === 0 ? API_Auth.addRole(params).then(() => saveSuccess()) : API_Auth.editRole(role_id.value, params).then(() => saveSuccess());
+      } else {
+        ElMessage.error('表单填写有误,请检查!');
+        return false;
+      }
+    });
+  };
+  
+  /** 获取权限菜单树 */
+  const GET_RolePermission = () => {
+    API_Menus.getMenusChildren().then((res) => {
+      if (role_id.value !== '0') {
+        console.log(role_id.value !== 0);
+        API_Auth.getRolePermission(role_id.value).then((response) => {
+          role_id.value = response.role_id;
+          permissionForm.data_scope = response.data_scope;
+          permissionForm.role_name = response.role_name;
+          permissionForm.role_describe = response.role_describe;
+          permissionForm.dept_ids = response.dept_ids.split(',');
+          const checkedIds = expandRouters(response.menus);
+          permissions.value = filterRoleRouter(res, checkedIds);
+          countAllPermissions();
+          GET_DeptList();
+        });
+      } else {
+        permissions.value = res;
+        countAllPermissions();
+        GET_DeptList();
+      }
+    });
+  };
+  
+  /** 展开路由的identifier */
+  const expandRouters = (menus) => {
+    const routers = [];
+    menus.forEach((item) => {
+      item.checked && routers.push(item.identifier);
+      if (item.children && item.children.length) {
+        routers.push(...expandRouters(item.children));
+      }
+    });
+    return routers;
+  };
+  
+  /** 递归筛选被选中的路由 */
+  const filterRoleRouter = (routers, ids) => {
+    const _routers = [];
+    routers.forEach((item) => {
+      if (ids.includes(item.identifier) || item.hidden) {
+        item.checked = true;
+      } else {
+        item.checked = false;
+      }
+      if (item.children) {
+        item.children = filterRoleRouter(item.children, ids);
+      }
+      _routers.push(item);
+    });
+    return _routers;
+  };
+  
+  onMounted(() => {
+    GET_RolePermission();
+  });
+  
+  watch(
+    () => route.query.id,
+    () => {
+      role_id.value = route.query.id;
+      GET_RolePermission();
+    }
+  );
+  </script>
+    
+  <style scoped>
+  .permission-container {
+    padding: 10px;
+    background-color: #fff;
+    padding-bottom: 150px;
+  }
+  
+  .level_1 {
+    padding: 15px 0;
+    border-bottom: 1px dashed #e7e7e7;
+  }
+  
+  .level_1:last-child {
+    border-bottom: none;
+  }
+  
+  ::v-deep .el-form-item__label {
+    padding-top: 15px;
+  }
+  
+  ::v-deep .el-form-item__content {
+    display: block;
+    border-left: 1px solid #e7e7e7;
+    padding-left: 20px;
+    padding-top: 15px;
+  }
+  
+  ::v-deep .el-form-item__content .el-form-item__error {
+    padding-left: 20px;
+  }
+  
+  ::v-deep .el-form-item:last-child .el-form-item__content {
+    padding-top: 0;
+  }
+  
+  ::v-deep .el-form-item:not(:first-child) {
+    border-top: 1px solid #e7e7e7;
+    position: relative;
+  }
+  
+  ::v-deep .el-form-item:not(:first-child)::after {
+    content: ' ';
+    width: 1px;
+    height: 22px;
+    background-color: #e7e7e7;
+    position: absolute;
+    top: -22px;
+    left: 200px;
+  }
+  
+  ::v-deep .el-button-group {
+    display: inline-block;
+  }
+  
+  ::v-deep .el-button-group .el-button {
+    display: inline-block;
+    padding: 0;
+    border: none;
+  }
+  
+  ::v-deep .el-button-group .el-button:focus,
+  ::v-deep .el-button-group .el-button:hover {
+    color: #606266;
+    border-color: #fff;
+    background-color: #fff;
+  }
+  
+  .checkbox-dropdown {
+    display: inline-block;
+    min-width: 130px;
+    cursor: pointer;
+  }
+  
+  .checkbox-dropdown .checked {
+    color: #409eff;
+  }
+  
+  .role-dropdown {
+    display: flex;
+    align-items: center;
+    width: 25%;
+    height: 30px;
+    margin-bottom: 10px;
+  }
+  
+  .role-dropdown:last-child {
+    margin-bottom: 0;
+  }
+  
+  .role-dropdown__label {
+    display: flex;
+    align-items: center;
+    width: 100%;
+  }
+  
+  ::v-deep .role-dropdown__label .el-checkbox__label {
+    color: inherit;
+  }
+  
+  .role-dropdown__list {
+    max-height: 320px;
+    overflow-y: auto;
+  }
+  </style>