فهرست منبع

feat: permission

Casper Dai 3 سال پیش
والد
کامیت
d083c863dd

+ 1 - 4
.eslintrc.js

@@ -15,7 +15,6 @@ module.exports = {
     __DEV__: true,
     __PREVIEW__: true,
     __PLACEHOLDER__: true,
-    __DELETABLE_FOR_ADMIN__: true,
     __SENSOR__: true,
     __SENSOR_ELK__: true
   },
@@ -198,9 +197,7 @@ module.exports = {
     'yoda': [2, 'never'],
     'prefer-const': 2,
     'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
-    'object-curly-spacing': [2, 'always', {
-      objectsInObjects: false
-    }],
+    'object-curly-spacing': [2, 'always'],
     'array-bracket-spacing': [2, 'never']
   }
 }

+ 4 - 4
src/api/user.js

@@ -10,15 +10,15 @@ let realm = null
 export function setBase (kc) {
   realm = encodeURIComponent(kc.realm)
   if (kc.authServerUrl.charAt(kc.authServerUrl.length - 1) === '/') {
-    baseUrl = `${kc.authServerUrl}realms/${realm}`
+    baseUrl = kc.authServerUrl.slice(0, -1)
   } else {
-    baseUrl = `${kc.authServerUrl}/realms/${realm}`
+    baseUrl = kc.authServerUrl
   }
 }
 
 export function userinfo (silence) {
   const config = {
-    url: `${baseUrl}/account`,
+    url: `${baseUrl}/realms/${realm}/account`,
     method: 'GET',
     unusual: true
   }
@@ -31,7 +31,7 @@ export function userinfo (silence) {
 
 export function updateUser (id, data, message) {
   return update({
-    url: `/auth/admin/realms/${realm}/users/${id}`,
+    url: `${baseUrl}/admin/realms/${realm}/users/${id}`,
     method: 'PUT',
     unusual: true,
     data

+ 27 - 7
src/components/Permission/index.js

@@ -1,3 +1,21 @@
+import { mapGetters } from 'vuex'
+
+function hasPermission (roles, include, exclude) {
+  if (include) {
+    if (typeof include === 'string') {
+      return roles.includes(include)
+    }
+    return roles.some(role => include.includes(role))
+  }
+  if (exclude) {
+    if (typeof exclude === 'string') {
+      return !roles.includes(exclude)
+    }
+    return !roles.some(role => exclude.includes(role))
+  }
+  return false
+}
+
 export default {
   name: 'Permission',
   abstract: true,
@@ -6,18 +24,20 @@ export default {
       type: [Boolean, String],
       default: false
     },
-    deletable: {
-      type: [Boolean, String],
-      default: false
+    include: {
+      type: [String, Array],
+      default: null
+    },
+    exclude: {
+      type: [String, Array],
+      default: null
     }
   },
   computed: {
-    isAdmin () {
-      return this.$store.getters.isAdmin
-    }
+    ...mapGetters(['roles', 'isAdmin'])
   },
   render () {
-    if (this.skip || this.isAdmin && (!this.deletable || __DELETABLE_FOR_ADMIN__)) {
+    if (this.skip || this.isAdmin || hasPermission(this.roles, this.include, this.exclude)) {
       return this.$slots.default
     }
     return null

+ 2 - 1
src/constant.js

@@ -7,7 +7,8 @@ export const AssetType = {
 export const Role = {
   ADMIN: 'ROLE_ADMIN',
   SUPERVISOR: 'ROLE_OPERATION_SUPERVISOR',
-  STAFF: 'ROLE_OPERATION_STAFF'
+  STAFF: 'ROLE_OPERATION_STAFF',
+  VISITOR: 'ROLE_VISITOR'
 }
 
 export const State = {

+ 13 - 22
src/layout/components/Navbar/Breadcrumb.vue

@@ -15,7 +15,7 @@
           {{ item.meta.title }}
         </span>
         <span
-          v-else-if="item.meta.placeholder"
+          v-else-if="index === 0"
           class="placeholder"
         >
           {{ item.meta.title }}
@@ -36,35 +36,26 @@ import { compile } from 'path-to-regexp'
 
 export default {
   name: 'Breadcrumb',
-  data () {
-    return {
-      levelList: null
+  computed: {
+    levelList () {
+      return this.$route.matched.filter(item => item.meta?.title)
     }
   },
-  watch: {
-    $route () {
-      this.getBreadcrumb()
-    }
-  },
-  created () {
-    this.getBreadcrumb()
-  },
   methods: {
-    getBreadcrumb () {
-      this.levelList = this.$route.matched.filter(item => item.meta?.title && item.meta?.breadcrumb !== false)
-    },
-    pathCompile (path) {
-      // To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
-      const { params } = this.$route
-      return compile(path)(params)
-    },
     handleLink (item) {
-      const { redirect, path } = item
+      const { redirect, name, path } = item
       if (redirect) {
         this.$router.push(redirect)
         return
       }
-      this.$router.push(this.pathCompile(path))
+      if (name) {
+        this.$router.push({
+          name,
+          params: { ...this.$route.params }
+        })
+        return
+      }
+      this.$router.push(compile(path)(this.$route.params))
     }
   }
 }

+ 17 - 7
src/layout/components/Navbar/index.vue

@@ -7,10 +7,13 @@
     >
       <i class="c-navbar__count">1</i>
     </i>
-    <upload-dashboard class="l-flex__none c-navbar__item" />
+    <permission exclude="ROLE_VISITOR">
+      <upload-dashboard class="l-flex__none c-navbar__item" />
+    </permission>
     <el-dropdown
       class="l-flex__none c-navbar__item c-navbar__user u-pointer"
       trigger="click"
+      @command="handleCommand"
     >
       <div class="l-flex--row">
         <img
@@ -22,12 +25,10 @@
         <i class="el-icon-arrow-down" />
       </div>
       <el-dropdown-menu slot="dropdown">
-        <router-link to="/profile">
-          <el-dropdown-item>个人设置</el-dropdown-item>
-        </router-link>
+        <el-dropdown-item command="profile">个人设置</el-dropdown-item>
         <el-dropdown-item
+          command="logout"
           divided
-          @click.native="logout"
         >
           退出登录
         </el-dropdown-item>
@@ -54,8 +55,17 @@ export default {
     ])
   },
   methods: {
-    async logout () {
-      await this.$store.dispatch('user/logout')
+    handleCommand (command) {
+      switch (command) {
+        case 'profile':
+          this.$router.push({ name: 'profile' })
+          break
+        case 'logout':
+          this.$store.dispatch('user/logout')
+          break
+        default:
+          break
+      }
     }
   }
 }

+ 8 - 69
src/layout/components/Sidebar/SidebarItem.vue

@@ -1,17 +1,11 @@
 <template>
-  <div v-if="!item.hidden">
-    <template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
-      <app-link
-        v-if="onlyOneChild.meta"
-        :to="resolvePath(onlyOneChild.path)"
-      >
-        <el-menu-item
-          :index="resolvePath(onlyOneChild.path)"
-          :class="{'submenu-title-noDropdown': !isNest}"
-        >
+  <div>
+    <template v-if="!item.children">
+      <app-link :to="item.path">
+        <el-menu-item :index="item.path">
           <item
-            :icon="onlyOneChild.meta.icon || item.meta && item.meta.icon"
-            :title="onlyOneChild.meta.title"
+            :icon="item.meta.icon"
+            :title="item.meta.title"
           />
         </el-menu-item>
       </app-link>
@@ -19,12 +13,10 @@
     <el-submenu
       v-else
       ref="subMenu"
-      :index="resolvePath(item.path)"
-      popper-append-to-body
+      :index="item.path"
     >
       <template #title>
         <item
-          v-if="item.meta"
           :icon="item.meta.icon"
           :title="item.meta.title"
         />
@@ -32,18 +24,14 @@
       <sidebar-item
         v-for="child in item.children"
         :key="child.path"
-        :is-nest="true"
-        :item="child"
-        :base-path="resolvePath(child.path)"
         class="nest-menu"
+        :item="child"
       />
     </el-submenu>
   </div>
 </template>
 
 <script>
-import path from 'path'
-import { isExternal } from '@/utils/validate'
 import Item from './Item'
 import AppLink from './Link'
 import FixiOSBug from './FixiOSBug'
@@ -59,55 +47,6 @@ export default {
     item: {
       type: Object,
       required: true
-    },
-    isNest: {
-      type: Boolean,
-      default: false
-    },
-    basePath: {
-      type: String,
-      default: ''
-    }
-  },
-  data () {
-    // To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
-    // TODO: refactor with render function
-    this.onlyOneChild = null
-    return {}
-  },
-  methods: {
-    hasOneShowingChild (children = [], parent) {
-      const showingChildren = children.filter(item => {
-        if (item.hidden) {
-          return false
-        } else {
-          // Temp set(will be used if only has one showing child)
-          this.onlyOneChild = item
-          return true
-        }
-      })
-
-      // When there is only one child router, the child router is displayed by default
-      if (showingChildren.length === 1) {
-        return true
-      }
-
-      // Show parent if there are no child router to display
-      if (showingChildren.length === 0) {
-        this.onlyOneChild = { ...parent, path: '', noShowingChildren: true }
-        return true
-      }
-
-      return false
-    },
-    resolvePath (routePath) {
-      if (isExternal(routePath)) {
-        return routePath
-      }
-      if (isExternal(this.basePath)) {
-        return this.basePath
-      }
-      return path.resolve(this.basePath, routePath)
     }
   }
 }

+ 43 - 6
src/layout/components/Sidebar/index.vue

@@ -19,10 +19,9 @@
         :collapse-transition="false"
       >
         <sidebar-item
-          v-for="route in permission_routes"
+          v-for="route in routers"
           :key="route.path"
           :item="route"
-          :base-path="route.path"
         />
       </el-menu>
     </el-scrollbar>
@@ -30,16 +29,54 @@
 </template>
 
 <script>
+import path from 'path'
 import { mapGetters } from 'vuex'
 import SidebarItem from './SidebarItem'
 
+function resolve (...args) {
+  return path.resolve(...args)
+}
+
+function filterRoutes (routes, basePath) {
+  const res = []
+
+  routes.forEach(route => {
+    if (route.hidden) {
+      return
+    }
+    const { name, meta = {} } = route
+    const path = basePath ? resolve(basePath, route.path) : route.path
+    if (basePath) {
+      res.push({ path, name, meta })
+      return
+    }
+    let children = route.children
+    if (children) {
+      children = filterRoutes(children, path)
+      if (!children.length) {
+        return
+      }
+      if (children.length === 1) {
+        const { path: cpath, name: cname, meta: cmeta = {} } = children[0]
+        res.push({ path: resolve(path, cpath), name: cname, meta: { ...meta, ...cmeta } })
+        return
+      }
+    }
+    res.push({ path, name, meta, children })
+  })
+
+  return res
+}
+
 export default {
   name: 'Sidebar',
   components: { SidebarItem },
   computed: {
-    ...mapGetters([
-      'permission_routes'
-    ]),
+    ...mapGetters(['permissionRoutes']),
+    routers () {
+      // 暂时只考虑两层
+      return filterRoutes(this.permissionRoutes)
+    },
     activeMenu () {
       const route = this.$route
       const { meta, path } = route
@@ -78,7 +115,7 @@ export default {
 
   &__menu {
     flex: 1 1 auto;
-    min-height: 0;
+    min-height: 0.5;
 
     ::v-deep {
       .c-sidebar__wrapper {

+ 5 - 3
src/main.js

@@ -18,7 +18,7 @@ import Permission from './components/Permission'
 import StatusWrapper from './components/StatusWrapper'
 import Pagination from './components/Pagination'
 import CTable from './components/CTable'
-import AutoText from '@/components/AutoText'
+import AutoText from './components/AutoText'
 import EditInput from './components/EditInput'
 import SearchInput from './components/SearchInput'
 
@@ -29,7 +29,7 @@ import {
 
 import { setBase } from '@/api/user'
 
-async function startApp () {
+function startApp () {
   document.body.setAttribute('version', __VERSION__)
 
   Vue.use(Element)
@@ -59,7 +59,9 @@ async function startApp () {
   console.log(keycloak)
   setBase(keycloak)
 
-  await store.dispatch('user/login', keycloak.token)
+  store.dispatch('user/login', keycloak)
+  store.dispatch('permission/generateRoutes', store.getters.roles)
+  router.addRoutes(store.getters.permissionRoutes)
 
   new Vue({
     router,

+ 3 - 21
src/permission.js

@@ -15,8 +15,7 @@ router.beforeEach(async (to, from, next) => {
 
   if (store.getters.token) {
     // determine whether the user has obtained his permission roles through getInfo
-    const hasRoles = store.getters.roles?.length > 0
-    if (hasRoles) {
+    if (store.getters.roles.length) {
       if (to.path === '/error') {
         // redirect to the home page
         next({ path: '/' })
@@ -25,25 +24,8 @@ router.beforeEach(async (to, from, next) => {
         next()
       }
     } else {
-      try {
-        // get user info
-        const { roles } = await store.dispatch('user/getInfo')
-
-        // generate accessible routes map based on roles
-        const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
-
-        // dynamically add accessible routes
-        router.addRoutes(accessRoutes)
-
-        // hack method to ensure that addRoutes is complete
-        // set the replace: true, so the navigation will not leave a history record
-        next({ ...to, replace: true })
-      } catch (error) {
-        // remove token
-        await store.dispatch('user/resetToken')
-        next(`/error?code=400`)
-        NProgress.done()
-      }
+      next('/error?code=400')
+      NProgress.done()
     }
   } else {
     /* has no token*/

+ 45 - 39
src/router/index.js

@@ -7,22 +7,17 @@ import Solo from '@/layout/Solo'
 Vue.use(Router)
 
 /**
- * Note: sub-menu only appear when route children.length >= 1
- * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
- *
- * dev: true                     if set true, item will not show in production
- * hidden: true                  if set true, item will not show in the sidebar(default is false)
- * alwaysShow: true              if set true, will always show the root menu
- *                               if not set alwaysShow, when item has more than one children route,
- *                               it will becomes nested mode, otherwise not show the root menu
- * name:'router-name'            the name is used by <keep-alive> (must set!!!)
+ * sub-menu only appear when route children.length >= 1
+ * dev: true                       if set true, item will not show in production
+ * hidden: true                    if set true, item will not show in the sidebar(default is false)
+ * name:'router-name'              the name is used by <keep-alive> (must set!!!)
  * meta : {
-    roles: ['admin','editor']    control the page roles (you can set multiple roles)
-    title: 'title'               the name show in sidebar and breadcrumb (recommend set)
-    icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
-    breadcrumb: false            if set false, the item will hidden in breadcrumb(default is true)
-    activeMenu: '/example/list'  if set path, the sidebar will highlight the path you set
-  }
+ *   include: ['admin'],           control the page roles (you can set multiple roles)
+ *   exclude: ['editor'],          control the page roles (you can set multiple roles)
+ *   title: 'title',               the name show in sidebar and breadcrumb (recommend set)
+ *   icon: 'svg-name'/'el-icon-x', the icon show in the sidebar
+ *   activeMenu: '/example/list'   if set path, the sidebar will highlight the path you set
+ * }
  */
 
 /**
@@ -49,8 +44,8 @@ export const asyncRoutes = [
     component: Layout,
     children: [
       {
-        path: 'dashboard',
         name: 'dashboard',
+        path: 'dashboard',
         component: () => import('@/views/dashboard/index'),
         meta: { title: '首页', icon: 'el-icon-s-home' }
       },
@@ -63,27 +58,16 @@ export const asyncRoutes = [
       }
     ]
   },
-  {
-    name: 'design',
-    path: '/design/:id',
-    component: () => import('@/views/bigscreen/designer/index'),
-    hidden: true
-  },
-  {
-    name: 'view',
-    path: '/view/:id',
-    component: () => import('@/views/bigscreen/viewer/index'),
-    hidden: true
-  },
   {
     path: '/cm',
     component: Layout,
-    meta: { title: '智能信发', icon: 'el-icon-s-promotion', placeholder: true },
+    meta: { title: '智能信发', icon: 'el-icon-s-promotion' },
     children: [
       {
         name: 'media',
         path: 'media',
         component: () => import('@/views/media/index'),
+        exclude: [Role.VISITOR],
         meta: { title: '媒资管理' }
       },
       {
@@ -108,6 +92,7 @@ export const asyncRoutes = [
             name: 'schedule-design',
             path: ':id',
             component: () => import('@/views/schedule/designer/index'),
+            exclude: [Role.VISITOR],
             meta: { title: '排期编辑', activeMenu: '/cm/schedule', cache: 'ScheduleList' },
             hidden: true
           }
@@ -117,13 +102,15 @@ export const asyncRoutes = [
         name: 'schedule-deploy',
         path: 'deploy',
         component: () => import('@/views/schedule/deploy/index'),
+        exclude: [Role.VISITOR],
         meta: { title: '排期发布' }
       },
       {
         name: 'review',
         path: 'review',
         component: () => import('@/views/review/index'),
-        meta: { title: '审核管理', roles: [Role.SUPERVISOR] }
+        include: [Role.SUPERVISOR],
+        meta: { title: '审核管理' }
       },
       {
         name: 'schedule-deploy-history',
@@ -137,7 +124,7 @@ export const asyncRoutes = [
     dev: !__PREVIEW__,
     path: '/dm',
     component: Layout,
-    meta: { title: '大屏设备', icon: 'el-icon-monitor', placeholder: true },
+    meta: { title: '大屏设备', icon: 'el-icon-monitor' },
     children: [
       {
         name: 'schedule-timeline',
@@ -157,19 +144,21 @@ export const asyncRoutes = [
   {
     path: '/m',
     component: Layout,
-    meta: { title: '大屏管理', icon: 'el-icon-monitor', placeholder: true },
+    meta: { title: '大屏管理', icon: 'el-icon-monitor' },
     children: [
       {
         name: 'category',
         path: 'category',
         component: () => import('@/views/device/category/index'),
-        meta: { title: '产品分类', roles: [Role.ADMIN] }
+        include: [Role.ADMIN],
+        meta: { title: '产品分类' }
       },
       {
         name: 'product',
         path: 'product',
         component: () => import('@/views/device/product/index'),
-        meta: { title: '产品管理', roles: [Role.ADMIN] }
+        include: [Role.ADMIN],
+        meta: { title: '产品管理' }
       },
       {
         path: 'device',
@@ -196,6 +185,7 @@ export const asyncRoutes = [
         name: 'group',
         path: 'group',
         component: () => import('@/views/device/group/index'),
+        exclude: [Role.VISITOR],
         meta: { title: '分组管理' }
       }
     ]
@@ -203,33 +193,36 @@ export const asyncRoutes = [
   {
     path: '/l',
     component: Layout,
-    meta: { roles: [Role.ADMIN] },
+    exclude: [Role.STAFF],
+    meta: { icon: 'el-icon-edit-outline' },
     children: [
       {
         path: '',
         name: 'logger',
         component: () => import('@/views/logger/index'),
-        meta: { title: '操作日志', icon: 'el-icon-edit-outline' }
+        meta: { title: '操作日志' }
       }
     ]
   },
   {
     path: '/d',
     component: Layout,
-    meta: { roles: [Role.ADMIN] },
+    include: [Role.ADMIN],
+    meta: { icon: 'bug' },
     children: [
       {
         path: '',
         name: 'debug',
         component: () => import('@/views/debug/index'),
-        meta: { title: '调试', icon: 'bug' }
+        meta: { title: '调试' }
       }
     ]
   },
   {
     path: '/u',
     component: Layout,
-    meta: { title: '升级管理', icon: 'el-icon-upload', placeholder: true, roles: [Role.ADMIN] },
+    include: [Role.ADMIN],
+    meta: { title: '升级管理', icon: 'el-icon-upload' },
     children: [
       {
         path: 'apk',
@@ -245,6 +238,19 @@ export const asyncRoutes = [
       }
     ]
   },
+  {
+    name: 'design',
+    path: '/design/:id',
+    component: () => import('@/views/bigscreen/designer/index'),
+    exclude: [Role.VISITOR],
+    hidden: true
+  },
+  {
+    name: 'view',
+    path: '/view/:id',
+    component: () => import('@/views/bigscreen/viewer/index'),
+    hidden: true
+  },
   // 404 page must be placed at the end !!!
   { path: '*', redirect: '/', hidden: true }
 ]

+ 4 - 1
src/store/getters.js

@@ -13,11 +13,14 @@ const getters = {
   roles (state) {
     return state.user.roles
   },
-  permission_routes (state) {
+  permissionRoutes (state) {
     return state.permission.routes
   },
   isAdmin (state, getters) {
     return getters.roles.includes(Role.ADMIN)
+  },
+  hasEditPermission (state, getters) {
+    return getters.roles.length > 1 || getters.roles[0] !== Role.VISITOR
   }
 }
 

+ 21 - 20
src/store/modules/permission.js

@@ -1,23 +1,27 @@
 import { constantRoutes, asyncRoutes } from '@/router'
 import { Role } from '@/constant'
 
-function hasPermission (roles, route) {
-  if (route.meta?.roles) {
-    return roles.some(role => route.meta.roles.includes(role))
+function hasPermission (roles, { include, exclude }) {
+  if (include) {
+    return roles.some(role => include.includes(role))
   }
-  return true
+  return !exclude || !roles.some(role => exclude.includes(role))
 }
 
 export function filterAsyncRoutes (routes, roles, force) {
   const res = []
 
   routes.forEach(route => {
-    const tmp = { ...route }
-    if (!tmp.dev && (force || hasPermission(roles, tmp))) {
-      if (tmp.children) {
-        tmp.children = filterAsyncRoutes(tmp.children, roles, force)
+    if (!route.dev && (force || hasPermission(roles, route))) {
+      if (route.children) {
+        const children = filterAsyncRoutes(route.children, roles, force)
+        if (!children.length) {
+          return
+        }
+        res.push({ ...route, children })
+      } else {
+        res.push({ ...route })
       }
-      res.push(tmp)
     }
   })
 
@@ -30,22 +34,19 @@ const state = {
 
 const mutations = {
   SET_ROUTES (state, routes) {
-    state.routes = constantRoutes.concat(routes)
+    state.routes = routes
   }
 }
 
 const actions = {
   generateRoutes ({ commit }, roles) {
-    return new Promise(resolve => {
-      let accessedRoutes
-      if (roles.includes(Role.ADMIN)) {
-        accessedRoutes = __DEV__ ? asyncRoutes : filterAsyncRoutes(asyncRoutes, roles, true)
-      } else {
-        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
-      }
-      commit('SET_ROUTES', accessedRoutes)
-      resolve(accessedRoutes)
-    })
+    let accessedRoutes
+    if (roles.includes(Role.ADMIN)) {
+      accessedRoutes = __DEV__ ? asyncRoutes : filterAsyncRoutes(asyncRoutes, roles, true)
+    } else {
+      accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
+    }
+    commit('SET_ROUTES', constantRoutes.concat(accessedRoutes))
   }
 }
 

+ 10 - 35
src/store/modules/user.js

@@ -25,40 +25,16 @@ const mutations = {
 }
 
 const actions = {
-  login ({ commit }, accessToken) {
-    return new Promise(resolve => {
-      commit('SET_TOKEN', accessToken)
-      resolve()
-    })
-  },
-
-  getInfo ({ commit }) {
-    return new Promise((resolve, reject) => {
-      if (!Vue.prototype.$keycloak) {
-        reject('Keycloak not init!')
-        return
-      }
-
-      if (!Vue.prototype.$keycloak.authenticated) {
-        reject('Verification failed, please Login again.')
-        return
-      }
-
+  login ({ commit }, keycloak) {
+    if (keycloak.authenticated) {
       const roleArray = Object.values(Role)
-      const roles = Vue.prototype.$keycloak.realmAccess?.roles?.filter(role => roleArray.includes(role))
-      // roles must be a non-empty array
-      if (!roles?.length) {
-        reject('Roles must be a non-null array!')
-        return
-      }
-
-      const { preferred_username, avatar, family_name, given_name } = Vue.prototype.$keycloak.tokenParsed
-
-      commit('SET_ROLES', roles)
+      const roles = keycloak.realmAccess?.roles?.filter(role => roleArray.includes(role))
+      const { preferred_username, family_name, given_name, avatar } = keycloak.tokenParsed
+      commit('SET_TOKEN', keycloak.token)
+      commit('SET_ROLES', roles?.length ? roles : [Role.VISITOR])
       commit('SET_NAME', family_name && given_name ? `${family_name}${/[a-zA-z]/.test(family_name) ? ' ' : ''}${given_name}` : preferred_username)
       commit('SET_AVATAR', avatar)
-      resolve({ roles, name, avatar })
-    })
+    }
   },
 
   logout ({ commit }) {
@@ -66,11 +42,11 @@ const actions = {
       Vue.prototype.$keycloak.logout().then(() => {
         commit('SET_TOKEN', '')
         commit('SET_ROLES', [])
+        commit('SET_NAME', '')
+        commit('SET_AVATAR', '')
         resetRouter()
         resolve()
-      }).catch(error => {
-        reject(error)
-      })
+      }, reject)
     })
   },
 
@@ -78,7 +54,6 @@ const actions = {
   resetToken ({ commit }) {
     return new Promise(resolve => {
       commit('SET_TOKEN', '')
-      commit('SET_ROLES', [])
       resolve()
     })
   }

+ 6 - 5
src/views/bigscreen/index.vue

@@ -8,6 +8,7 @@
     <div class="l-flex__none l-flex--row has-bottom-padding">
       <div class="l-flex__auto l-flex--row c-sibling-item">
         <button
+          v-if="hasEditPermission"
           class="o-button"
           @click="toAdd"
         >
@@ -16,6 +17,7 @@
         </button>
       </div>
       <el-select
+        v-if="hasEditPermission"
         v-model="params.status"
         class="l-flex__none c-sibling-item o-select"
         placeholder="请选择状态"
@@ -81,10 +83,7 @@
                 />
               </el-tooltip>
             </template>
-            <permission
-              :skip="item.status === 0"
-              deletable
-            >
+            <permission :skip="item.status === 0">
               <el-tooltip
                 content="删除"
                 :hide-after="2000"
@@ -168,6 +167,7 @@
 </template>
 
 <script>
+import { mapGetters } from 'vuex'
 import { getAssetUrl } from '@/api/asset'
 import { getRatios } from '@/api/device'
 import {
@@ -198,13 +198,14 @@ export default {
       ratios: [],
       ...createListOptions({
         pageSize: 8,
-        status: void 0,
+        status: this.$store.getters.hasEditPermission ? void 0 : State.RESOLVED,
         name: ''
       }),
       disabled: false
     }
   },
   computed: {
+    ...mapGetters(['hasEditPermission']),
     isAbnormal () {
       return this.error || !this.loading && this.totalCount === 0
     }

+ 1 - 2
src/views/dashboard/index.vue

@@ -232,7 +232,6 @@ export default {
         monitor.inactive = notEnabledTotal || 0
       }).finally(() => {
         monitor.loading = false
-      }).then(() => {
         if (!this.monitor.loading) {
           this._getDevices(this.monitor.total)
         }
@@ -248,7 +247,7 @@ export default {
       return 1
     },
     _getDevices (total) {
-      if (!total) {
+      if (!total || total === '-') {
         this.deviceOptions = { list: [], loaded: true }
         return
       }

+ 25 - 20
src/views/device/index.vue

@@ -161,29 +161,31 @@
             >
               查看
             </div>
-            <permission v-if="scope.row.isMaster">
+            <template v-if="hasEditPermission">
+              <permission v-if="scope.row.isMaster">
+                <div
+                  class="c-table__btn u-pointer"
+                  @click.stop="toAddSubDevice(scope.row)"
+                >
+                  添加备份设备
+                </div>
+              </permission>
               <div
+                v-if="__PLACEHOLDER__ && scope.row.isMaster && scope.row.activate > 1"
                 class="c-table__btn u-pointer"
-                @click.stop="toAddSubDevice(scope.row)"
+                @click.stop="setDefaultProgram(scope.row)"
               >
-                添加备份设备
+                默认节目
               </div>
-            </permission>
-            <div
-              v-if="scope.row.isMaster && scope.row.activate > 1"
-              class="c-table__btn u-pointer"
-              @click.stop="setDefaultProgram(scope.row)"
-            >
-              默认节目
-            </div>
-            <permission>
-              <div
-                class="c-table__btn u-pointer"
-                @click.stop="toDelDevice(scope.row)"
-              >
-                删除
-              </div>
-            </permission>
+              <permission>
+                <div
+                  class="c-table__btn u-pointer"
+                  @click.stop="toDelDevice(scope.row)"
+                >
+                  删除
+                </div>
+              </permission>
+            </template>
           </template>
         </template>
       </el-table-column>
@@ -329,7 +331,7 @@ export default {
     }
   },
   computed: {
-    ...mapGetters(['isAdmin']),
+    ...mapGetters(['isAdmin', 'hasEditPermission']),
     operationColumnWidth () {
       return this.isAdmin ? 360 : 200
     },
@@ -553,6 +555,9 @@ export default {
       }
     },
     toggleActivate (item) {
+      if (!this.hasEditPermission) {
+        return
+      }
       (item.activate ? deactivateDevice : activateDevice)(item).then(() => {
         if (item.isMaster) {
           this.getList()

+ 1 - 4
src/views/media/index.vue

@@ -123,10 +123,7 @@
             >
               提交
             </div>
-            <permission
-              :skip="canDel"
-              deletable
-            >
+            <permission :skip="canDel">
               <div
                 class="c-table__btn u-pointer"
                 @click="toDel(scope.row)"

+ 4 - 1
src/views/profile/index.vue

@@ -66,7 +66,10 @@
             @keydown.enter="$event.target.blur()"
           />
         </div>
-        <div class="c-form__section">
+        <div
+          v-if="__PLACEHOLDER__"
+          class="c-form__section"
+        >
           <i class="o-wechat" />
           <div
             class="has-padding u-pointer"

+ 6 - 6
src/views/schedule/index.vue

@@ -26,7 +26,7 @@
       <template #header>
         <div class="l-flex__auto c-sibling-item">
           <button
-            v-if="isNormal"
+            v-if="hasEditPermission && isNormal"
             class="o-button"
             @click="toAdd"
           >
@@ -48,6 +48,7 @@
           />
         </el-select>
         <el-select
+          v-if="hasEditPermission"
           v-model="currObj.params.status"
           class="l-flex__none c-sibling-item o-select"
           placeholder="请选择状态"
@@ -148,10 +149,7 @@
               提交
             </div>
           </template>
-          <permission
-            :skip="scope.row.status === 0"
-            deletable
-          >
+          <permission :skip="scope.row.status === 0">
             <div
               class="c-table__btn u-pointer"
               @click="toDel(scope.row)"
@@ -249,6 +247,7 @@
 </template>
 
 <script>
+import { mapGetters } from 'vuex'
 import { getRatios } from '@/api/device'
 import {
   getSchedules,
@@ -286,7 +285,7 @@ export default {
       active: 'normal',
       normal: createListOptions({
         type: void 0,
-        status: void 0,
+        status: this.$store.getters.hasEditPermission ? void 0 : State.RESOLVED,
         name: ''
       }),
       defaults: createListOptions(),
@@ -299,6 +298,7 @@ export default {
     }
   },
   computed: {
+    ...mapGetters(['hasEditPermission']),
     currObj () {
       return this[this.active]
     },

+ 0 - 2
vue.config.js

@@ -14,8 +14,6 @@ const features = {
   __PREVIEW__: process.env.ENV !== 'production',
   // 未开发的功能组件
   __PLACEHOLDER__: !isProd || false,
-  // 可删除已提交审核的数据
-  __DELETABLE_FOR_ADMIN__: true,
   // 传感器
   __SENSOR__: !isProd || true,
   __SENSOR_ELK__: false

+ 2 - 2
yarn.lock

@@ -7576,8 +7576,8 @@ path-to-regexp@0.1.7:
 
 path-to-regexp@^6.2.0:
   version "6.2.0"
-  resolved "https://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-6.2.0.tgz#f7b3803336104c346889adece614669230645f38"
-  integrity sha1-97OAMzYQTDRoia3s5hRmkjBkXzg=
+  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.0.tgz#f7b3803336104c346889adece614669230645f38"
+  integrity sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg==
 
 path-type@^3.0.0:
   version "3.0.0"