整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          在SSM框架使用攔截器實(shí)現(xiàn)用戶登錄權(quán)限驗(yàn)證

          在SSM框架使用攔截器實(shí)現(xiàn)用戶登錄權(quán)限驗(yàn)證

          配置攔截器(不攔截登錄、注冊(cè)請(qǐng)求),對(duì)所有請(qǐng)求進(jìn)行攔截,驗(yàn)證session中保存的登錄用戶是否存在,如果不存在,說明用戶已經(jīng)下線,清空session,強(qiáng)制返回登錄頁面。

          配置攔截器

          在SpringMVC配置文件中配置攔截器,對(duì)文件上傳、用戶登錄、注冊(cè)不作攔截。

          <!-- 配置攔截器 -->
          	<mvc:interceptors>
          		<!-- 使用bean定義一個(gè)Interceptor,直接定義在mvc:interceptors根下面的Interceptor將攔截所有的請(qǐng)求 -->
          		<!-- session失效攔截器 -->
          		<mvc:interceptor>
          			<!-- 匹配的是url路徑, 如果不配置或/**,將攔截所有的Controller請(qǐng)求 -->
          			<mvc:mapping path="/**" />
          			<!-- 不需要攔截的請(qǐng)求地址 -->
          			<mvc:exclude-mapping path="/file/**.html" /><!-- 文件上傳請(qǐng)求 -->
          			<mvc:exclude-mapping path="/login.html" /><!-- 用戶登錄的請(qǐng)求 -->
          			<bean class="lxkj.zz07.interceptor.MyLoginInterceptor"></bean>
          		</mvc:interceptor>
          	</mvc:interceptors>
          12345678910111213

          定義攔截器類

          通過實(shí)現(xiàn)HandleInterceptor接口,來定義用戶攔截器類,重寫preHandle方法,該方法將在Controller處理之前進(jìn)行調(diào)用,SpringMVC中的Interceptor攔截器是鏈?zhǔn)降?,可以同時(shí)存在 多個(gè)Interceptor,然后SpringMVC會(huì)根據(jù)聲明的前后順序一個(gè)接一個(gè)的執(zhí)行,而且所有的Interceptor中的preHandle方法都會(huì)在 Controller方法調(diào)用之前調(diào)用。
          SpringMVC的這種Interceptor鏈?zhǔn)浇Y(jié)構(gòu)也是可以進(jìn)行中斷的,這種中斷方式是令preHandle的返 回值為false,當(dāng)preHandle的返回值為false的時(shí)候整個(gè)請(qǐng)求就結(jié)束了。

          eb應(yīng)用安全管理

          Web應(yīng)用的安全管理,主要包括兩個(gè)方面的內(nèi)容,一個(gè)是用戶身份的認(rèn)證,即用戶登錄的設(shè)計(jì),二是用戶授權(quán),即一個(gè)用戶在一個(gè)應(yīng)用系統(tǒng)中能夠執(zhí)行哪些操作的權(quán)限管理。權(quán)限管理的設(shè)計(jì)一般使用角色來管理,即給一個(gè)用戶賦予哪些角色,這個(gè)用戶就具有哪些權(quán)限。

          Spring框架體系中,經(jīng)典的安全體系框架是Security。關(guān)于系統(tǒng)的安全管理及各種設(shè)計(jì),Spring Security已經(jīng)大體上都實(shí)現(xiàn)了,只需要一些配置和引用就能夠正常使用。SpringBoot使用Security更加的簡單,因?yàn)镾pringBoot本身的簡單配置使用加上Security的功能豐富全面,可用快速幫助我們構(gòu)建完善的登陸認(rèn)證服務(wù)。

          關(guān)于Security,SpringBoot本身有spring-boot-starter-security依賴組件,Spring Cloud微服務(wù)全家桶中也有spring-cloud-starter-security依賴組件,并且spring-cloud-starter-security中也包含了spring-boot-starter-security,下面的學(xué)習(xí)中,會(huì)先使spring-boot-starter-security,然后再spring-cloud-starter-security學(xué)習(xí)安全管理的功能,從SpringBoot單體的登陸注冊(cè)和權(quán)限管理,到Spring Cloud微服務(wù)中構(gòu)建認(rèn)證和授權(quán)服務(wù),都會(huì)一一接觸到。

          關(guān)于版本的問題,我從SpringBoot1.3.x版的使用到2.1.x的使用,Security的配置也經(jīng)歷了不小的變化,最準(zhǔn)確的配置建議去官網(wǎng)文檔學(xué)習(xí)。下面的學(xué)習(xí)中,將使用2.1.5版本,官方文檔地址是: https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/htmlsingle/ 。 Security的源碼非常復(fù)雜,因此我們后面再討論深層次的東西,現(xiàn)在來用實(shí)例進(jìn)行入門學(xué)習(xí)。

          入門例子

          先來看一個(gè)入門例子,springboot項(xiàng)目結(jié)構(gòu)我們都很熟悉,先來看依賴:

          依賴很簡單,除了一個(gè)web組件和thymeleaf視圖組件,就是一個(gè)security。接下來看一下啟動(dòng)類:

          可以看到啟動(dòng)類沒有任何特殊的配置。至于配置文件,我們簡單的配置一下端口,其它不做任何配置:

          這樣一個(gè)簡單的入門例子就完成了,現(xiàn)在來啟動(dòng)項(xiàng)目,啟動(dòng)日志很短,可以看到有一行特殊的日志:

          這是我們加入了security組件的依賴之后,引入了security的默認(rèn)配置,此時(shí)就有了一個(gè)簡單的登錄功能,打印出的一行是默認(rèn)密碼的信息,這個(gè)密碼是現(xiàn)在沒有任何代碼和配置的狀態(tài)下每次啟動(dòng)隨機(jī)生成的,security不僅會(huì)生成一個(gè)默認(rèn)密碼,依賴組件中還有一個(gè)默認(rèn)的登陸鏈接/login,還有一個(gè)默認(rèn)的用戶名 user,而且在springboot2.1.x版本中,這個(gè)/login有一個(gè)非常不錯(cuò)的默認(rèn)登錄頁面,下面進(jìn)行測(cè)試:

          用戶名輸入user,密碼輸入日志中打印出的隨機(jī)密碼,登錄成功后,就會(huì)跳轉(zhuǎn)到默認(rèn)地址,默認(rèn)成功的地址就是登錄地址去掉/login,

          現(xiàn)在沒有定義任何鏈接匹配這個(gè)地址,我們來定義一個(gè)簡單的頁面,在resource下面,新建一個(gè)templates文件夾,在templates下面新建一個(gè)主頁 home.html,內(nèi)容如下:

          然后定義一個(gè)controller跳轉(zhuǎn)到這個(gè)頁面:

          這樣我們登陸成功后,就能自動(dòng)跳轉(zhuǎn)到這個(gè)頁面:

          這樣,一個(gè)最簡單的登錄流程就完成了,我們幾乎沒有做任何配置,只是引入了一個(gè)依賴而已。下面我們給security配置一個(gè)默認(rèn)用戶名密碼,這樣就不用每次啟動(dòng)都用隨機(jī)密碼,直接在springboot的默認(rèn)配置文件中配置:

          這樣等登陸就可以使用 admin/admin登陸了。

          代碼地址 :https://gitee.com/blueses/spring-boot-security 01

          本概念

          權(quán)限控制,最常見的基本上有 2 種

          • 基于 ACL 的權(quán)限控制
          • 基于 RBAC 的權(quán)限控制

          這個(gè)兩種到底有什么不同呢?

          我們通過下圖來分析一下

          添加圖片注釋,不超過 140 字(可選)

          ACL 是基于 用戶 -> 權(quán)限,直接為每個(gè)用戶分配權(quán)限 RBAC 基于 用戶 -> 角色 -> 權(quán)限,以角色為媒介,來為每個(gè)用戶分配權(quán)限 這樣做的好處是,某個(gè)權(quán)限過于敏感時(shí),想要將每個(gè)用戶或者部分用戶的權(quán)限去掉,就不需要每個(gè)用戶的權(quán)限都操作一遍,只需要?jiǎng)h除對(duì)應(yīng)角色的權(quán)限即可 那在實(shí)際的開發(fā)中 RBAC 是最常用的權(quán)限控制方案,就前端而言,RBAC 主要如何實(shí)現(xiàn)的呢? 主要就兩個(gè)部分

          • 頁面權(quán)限受控
          • 按鈕權(quán)限受控

          下面我們就來實(shí)現(xiàn)這兩個(gè)部分

          • 頁面權(quán)限
          • 按鈕權(quán)限

          頁面的訪問,我們都是需要配置路由表的,根據(jù)配置路由表的路徑來訪問頁面 那么,我們控制了路由表,不就能控制頁面的訪問了嗎? 實(shí)現(xiàn)思路

          • 前端根據(jù)不同用戶信息,從后端獲取該用戶所擁有權(quán)限的路由表
          • 前端動(dòng)態(tài)創(chuàng)建路由表

          基本環(huán)境

          創(chuàng)建項(xiàng)目

           npm install -g @vue/cli
           vue --version # @vue/cli 5.0.8
           vue create vue-router-dome

          添加圖片注釋,不超過 140 字(可選)

          打開項(xiàng)目,npm run serve運(yùn)行一下

          添加圖片注釋,不超過 140 字(可選)

          代碼初始化,刪除不必要的一些文件

          添加圖片注釋,不超過 140 字(可選)

          我們創(chuàng)建幾個(gè)新文件夾

          添加圖片注釋,不超過 140 字(可選)

          寫下基本的頁面

          添加圖片注釋,不超過 140 字(可選)

          <!-- home.vue -->
          <template>
            <div>主頁</div>
          </template>


          <!-- menu.vue -->
           <template>
             <div>菜單管理</div>
           </template>


           <!-- user.vue -->
           <template>
             <div>用戶管理</div>
           </template>

          寫下路由配置

          添加圖片注釋,不超過 140 字(可選)

           // remaining.ts
           import Layout from '@/layout/index.vue'
           
           const remainingRouter: AppRouteRecordRaw[]=[
             {
               path: '/remaining',
               component: Layout,
               redirect: 'home',
               children: [
                 {
                   path: '/remaining/home',
                   component: ()=> import('@/views/home.vue'),
                   name: '首頁',
                   meta: {},
                 }
               ],
               name: '主頁管理',
               meta: undefined
             },
           ]
           
           export default remainingRouter

          remaining 主要為了存放一些公共路由,沒有權(quán)限頁可以訪問,比如登錄頁、404頁面這些

          因?yàn)槭怯?typescript 編寫的,我們需要加一下聲明文件,定義下 remainingRouter 的類型

          添加圖片注釋,不超過 140 字(可選)

           // router.d.ts
           import type { RouteRecordRaw } from 'vue-router'
           import { defineComponent } from 'vue'
           
           declare module 'vue-router' {
             interface RouteMeta extends Record<string | number | symbol, unknown> {
               hidden?: boolean
               alwaysShow?: boolean
               title?: string
               icon?: string
               noCache?: boolean
               breadcrumb?: boolean
               affix?: boolean
               activeMenu?: string
               noTagsView?: boolean
               followAuth?: string
               canTo?: boolean
             }
           }
           
           type Component<T=any>=| ReturnType<typeof defineComponent>
             | (()=> Promise<typeof import('*.vue')>)
             | (()=> Promise<T>)
           
           declare global {
             interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
               name: string
               meta: RouteMeta
               component?: Component | string
               children?: AppRouteRecordRaw[]
               props?: Recordable
               fullPath?: string
               keepAlive?: boolean
             }
           
             interface AppCustomRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
               icon: any
               name: string
               meta: RouteMeta
               component: string
               componentName?: string
               path: string
               redirect: string
               children?: AppCustomRouteRecordRaw[]
               keepAlive?: boolean
               visible?: boolean
               parentId?: number
               alwaysShow?: boolean
             }
           }

          接下來編寫,創(chuàng)建路由、導(dǎo)出路由

           import type { App } from 'vue'
           import type { RouteRecordRaw } from 'vue-router'
           import { createRouter, createWebHashHistory } from 'vue-router'
           import remainingRouter from './modules/remaining'
           
           // 創(chuàng)建路由實(shí)例
           const router=createRouter({
             history: createWebHashHistory(), // createWebHashHistory URL帶#,createWebHistory URL不帶#
             strict: true,
             routes: remainingRouter as RouteRecordRaw[],
             scrollBehavior: ()=> ({ left: 0, top: 0 })
           })
           
           // 導(dǎo)出路由實(shí)例
           export const setupRouter=(app: App<Element>)=> {
             app.use(router)
           }
           
           export default router

          main.ts中導(dǎo)入下

           import { createApp } from 'vue'
           import App from './App.vue'
           import { setupRouter } from './router/index' // 路由
           import ElementPlus from 'element-plus'
           import 'element-plus/dist/index.css'
           
           // 創(chuàng)建實(shí)例
           const setupAll=async ()=> {
             const app=createApp(App)
             setupRouter(app)
             app.mount('#app')
           }
           
           setupAll()

          接下來寫下 Layout 架構(gòu)

          我們要實(shí)現(xiàn)的效果,是一個(gè)后臺(tái)管理頁面的側(cè)邊欄,點(diǎn)擊菜單右邊就能跳轉(zhuǎn)到對(duì)應(yīng)路由所在頁面

          添加圖片注釋,不超過 140 字(可選)

          創(chuàng)建

          AppMain.vue 右邊路由跳轉(zhuǎn)頁

          Sidebar.vue 側(cè)邊欄

          index.vue 作為 layout 架構(gòu)的統(tǒng)一出口

          添加圖片注釋,不超過 140 字(可選)

           <!--
           @description: AppMain
           -->
           
           <template>
             <div>
               <router-view v-slot="{ Component, route }">
                 <transition name="fade-transform" mode="out-in"> <!-- 設(shè)置過渡動(dòng)畫 -->
                   <keep-alive>
                     <component :is="Component" :key="route.fullPath" />
                   </keep-alive>
                 </transition>
               </router-view>
             </div>
           </template>

          上面是一種動(dòng)態(tài)路由的固定寫法,需要與的路由配置進(jìn)行對(duì)應(yīng) 其中最主要的就是 <component :is="Component" :key="route.fullPath" /> 中的 key,這是為確定路由跳轉(zhuǎn)對(duì)應(yīng)頁面的標(biāo)識(shí),沒這個(gè)就跳不了 有一個(gè)小知識(shí)點(diǎn)

          • route.fullPath 拿到的地址是包括 searchhash 在內(nèi)的完整地址。該字符串是經(jīng)過百分號(hào)編碼的
          • route.path 經(jīng)過百分號(hào)編碼的 URL 中的 pathname
           //路徑:http://127.0.0.1:3000/user?id=1
           console.log(route.path) // 輸出 /user
           console.log(route.fullPath) // 輸出 /user?id=1

          為了實(shí)現(xiàn)右邊側(cè)邊欄,需要引入element plus來快速搭建

               pnpm install element-plus

          main.ts改造一下,完整引入element-plus

           import { createApp } from 'vue'
           import App from './App.vue'
           import ElementPlus from 'element-plus' // element-plus 組件庫
           import 'element-plus/dist/index.css' // element-plus 組件庫樣式文件
           
           // 創(chuàng)建實(shí)例
           const setupAll=async ()=> {
             const app=createApp(App)
             app.use(ElementPlus)
             app.mount('#app')
           }
           
           setupAll() 

          我們來編寫下 側(cè)邊欄

           <!--
           @description: Sidebar
           -->
           
           <template>
             <div>
               <el-menu active-text-color="#ffd04b" background-color="#304156" default-active="2" text-color="#fff" router>
                 <el-sub-menu :index="item.path" v-for="item in routers">
                   <template #title>{{ item.name }}</template>
                   <el-menu-item :index="child.path" v-for="child in item.children">{{ child.name }}</el-menu-item>
                 </el-sub-menu>
               </el-menu>
             </div>
           </template>
           
           <script setup lang='ts'>
           import { filterRoutes } from '@/utils/router';
           import { computed } from 'vue';
           import { useRouter } from 'vue-router';
           const router=useRouter()
           // 通過計(jì)算屬性,路由發(fā)生變化時(shí)更新路由信息
           const routers=computed(()=> {
             return filterRoutes(router.getRoutes()) // router.getRoutes() 用于獲取路由信息
           })
           </script>

          統(tǒng)一導(dǎo)出 layout 架構(gòu),加一點(diǎn)小樣式

          
           <!--
           @description: layout index
           -->
           
           <template>
             <div class="app-wrapper">
               <Sidebar class="sidebar-container" />
               <App-Main class="main-container" />
             </div>
           </template>
           
           <script setup lang='ts'>
           import { ref, reactive } from 'vue'
           import Sidebar from './components/Sidebar.vue'
           import AppMain from './components/AppMain.vue'
           </script>
           
           <style scoped>
           .app-wrapper {
               display: flex;
           }
           .sidebar-container {
               width: 200px;
               height: 100vh;
               background-color: #304156;
               color: #fff;
           }
           .main-container {
               flex: 1;
               height: 100vh;
               background-color: #f0f2f5;
           }
           </style>

          pnpm run serve運(yùn)行一下

          添加圖片注釋,不超過 140 字(可選)

          頁面權(quán)限管理

          通常我們實(shí)現(xiàn)頁面權(quán)限管理,比較常見的方案是,有權(quán)限的路由信息由后端傳給前端,前端再根據(jù)路由信息進(jìn)行渲染

          我們先安裝下 pinia 模擬下后端傳過來的數(shù)據(jù)

          pnpm install pinia

          添加圖片注釋,不超過 140 字(可選)

           import { defineStore } from "pinia";
           
           interface AuthStore {
             // 菜單
             menus: any[];
           }
           
           export const useAuthStore=defineStore("authState", {
             state: (): AuthStore=> ({
               menus: [
                 {
                   path: "/routing",
                   component: null,
                   redirect: "user",
                   children: [
                     {
                       path: "/routing/user",
                       component: "/user.vue",
                       name: "用戶管理",
                       meta: {},
                     },
                     {
                       path: "/routing/menu",
                       component: "/menu.vue",
                       name: "菜單管理",
                       meta: {},
                     }
                   ],
                   name: "系統(tǒng)管理",
                   meta: undefined,
                 },
               ]
             }),
             getters: {},
             actions: {},
           });

          好了,我們把模擬的路由數(shù)據(jù),加到本地路由中

          添加圖片注釋,不超過 140 字(可選)

          // permission.ts
          import router from './router'
          import type { RouteRecordRaw } from 'vue-router'
          import { formatRoutes } from './utils/router'
          import { useAuthStore } from '@/store';
          import { App } from 'vue';
          
          
          // 路由加載前
          router.beforeEach(async (to, from, next)=> {
            const { menus }=useAuthStore()
            routerList.forEach((route)=> {
              router.addRoute(menus as unknown as RouteRecordRaw) // 動(dòng)態(tài)添加可訪問路由表
            })
            next()
          })
          
          // 路由跳轉(zhuǎn)之后調(diào)用
          router.afterEach((to)=> { })

          添加圖片注釋,不超過 140 字(可選)

          添加圖片注釋,不超過 140 字(可選)

          報(bào)錯(cuò)了,為什么呢?

          對(duì)比路由表的數(shù)據(jù),原來,組件模塊的數(shù)據(jù)與公共路由的數(shù)據(jù)不一致

          添加圖片注釋,不超過 140 字(可選)

          我們需要把模擬后端傳過來的數(shù)據(jù)處理一下

          添加圖片注釋,不超過 140 字(可選)

           // router.ts
           import Layout from '@/layout/index.vue';
           import type { RouteRecordRaw } from 'vue-router'
           
           /* 處理從后端傳過來的路由數(shù)據(jù) */
           export const formatRoutes=(routes: any[])=> {
             const formatedRoutes: RouteRecordRaw[]=[]
             routes.forEach(route=> {
                 formatedRoutes.push(
                   {
                     ...route,
                     component: Layout, // 主要是將這個(gè) null -> 組件
                     children: route.children.map((child: any)=> {
                       return {
                         ...child,
                         component: ()=> import(`@/views${child.component}`), // 根據(jù) 本地路徑配置頁面路徑
                       }
                     }),
                   }
                 )
             })
             return formatedRoutes;
           }

          再修改下permission.ts

          
           import router from './router'
           import type { RouteRecordRaw } from 'vue-router'
           import { formatRoutes } from './utils/router'
           import { useAuthStore } from '@/store';
           import { App } from 'vue';
           
           
           // 路由加載前
           router.beforeEach(async (to, from, next)=> {
             const { menus }=useAuthStore()
             const routerList=menus
             routerList.forEach((route)=> {
               router.addRoute(route as unknown as RouteRecordRaw) // 動(dòng)態(tài)添加可訪問路由表
             })
             next()
           })
           
           // 路由跳轉(zhuǎn)之后調(diào)用
           router.afterEach((to)=> { }) 

          main.ts引入一下

           import './permission'

          可以正常訪問了

          添加圖片注釋,不超過 140 字(可選)

          按鈕權(quán)限

          除了頁面權(quán)限,外我們還有按鈕權(quán)限

          可以通過自定義指令來完成,permission.ts 中定義一下

          添加圖片注釋,不超過 140 字(可選)

           /* 按鈕權(quán)限 */
           export function hasPermi(app: App<Element>) {
             app.directive('hasPermi', (el, binding)=> {
               const { permissions }=useAuthStore()
               const { value }=binding
               const all_permission='*:*:*'
           
               if (value && value instanceof Array && value.length > 0) {
                 const permissionFlag=value
           
                 const hasPermissions=permissions.some((permission: string)=> {
                   return all_permission===permission || permissionFlag.includes(permission)
                 })
           
                 if (!hasPermissions) {
                   el.parentNode && el.parentNode.removeChild(el)
                 }
               } else {
                 throw new Error('權(quán)限不存在')
               }
             })
           }
           
           export const setupAuth=(app: App<Element>)=> {
             hasPermi(app)
           }
          
          

          需要掛載到main.ts

          
          
           import { createApp } from 'vue'
           import App from './App.vue'
           import { setupRouter } from './router/index'
           import ElementPlus from 'element-plus'
           import { createPinia } from 'pinia'
           import { setupAuth } from './permission'
           import 'element-plus/dist/index.css'
           import './permission'
           
           // 創(chuàng)建實(shí)例
           const setupAll=async ()=> {
             const app=createApp(App)
             setupRouter(app)
             setupAuth(app)
             app.use(ElementPlus)
             app.use(createPinia())
             app.mount('#app')
           }
           
           setupAll() 

          還是在store那里加一下模擬數(shù)據(jù)

           export const useAuthStore=defineStore("authState", {
             state: (): AuthStore=> ({
               menus: [
                 {
                   path: "/routing",
                   component: null,
                   redirect: "user",
                   children: [
                     {
                       path: "/routing/user",
                       component: "/user.vue",
                       name: "用戶管理",
                       meta: {},
                     },
                     {
                       path: "/routing/menu",
                       component: "/menu.vue",
                       name: "菜單管理",
                       meta: {},
                     }
                   ],
                   name: "系統(tǒng)管理",
                   meta: undefined,
                 },
               ],
               permissions: [
                 // '*:*:*', // 所有權(quán)限
                 'system:user:create',
                 'system:user:update',
                 'system:user:delete',
               ]
             }),
           });

          user.vue加入幾個(gè)按鈕,使用自定義指令

           <!-- user.vue -->
           <template>
             <div>
               <el-button type="primary" v-hasPermi="['system:user:create']">創(chuàng)建</el-button>
               <el-button type="primary" v-hasPermi="['system:user:update']">更新</el-button>
               <el-button type="primary" v-hasPermi="['system:user:delete']">刪除</el-button>
               <el-button type="primary" v-hasPermi="['system:user:admin']">沒權(quán)限</el-button>
             </div>
           </template>

          system:user:admin這個(gè)權(quán)限沒有配置,無法顯示

          添加圖片注釋,不超過 140 字(可選)

          加一下權(quán)限

          添加圖片注釋,不超過 140 字(可選)

          添加圖片注釋,不超過 140 字(可選)

          擴(kuò)展

          用戶權(quán)限我們使用 v-hasPermi自定義指令,其原理是通過刪除當(dāng)前元素,來實(shí)現(xiàn)隱藏

          如果使用 Element Plus 的標(biāo)簽頁呢

          我們?cè)?src/views/home.vue 寫一下基本樣式

          <!--
          @description: 主頁
          -->
          <template>
            <div>
              <el-tabs>
                <el-tab-pane label="標(biāo)簽一" name="first">標(biāo)簽一</el-tab-pane>
                <el-tab-pane label="標(biāo)簽二" name="second">標(biāo)簽二</el-tab-pane>
              </el-tabs>
            </div>
          </template>
          
          <script setup lang='ts'>
          
          </script>

          添加圖片注釋,不超過 140 字(可選)

          我們加下按鈕權(quán)限控制

          <template>
            <div>
              <el-tabs v-model="activeName">
                <el-tab-pane label="標(biāo)簽一" v-hasPermi="['system:tabs:first']" name="first">標(biāo)簽一</el-tab-pane>
                <el-tab-pane label="標(biāo)簽二" name="second">標(biāo)簽二</el-tab-pane>
              </el-tabs>
            </div>
          </template>

          添加圖片注釋,不超過 140 字(可選)

          因?yàn)檫@個(gè)權(quán)限我們沒有配置,標(biāo)簽頁內(nèi)容隱藏了,這沒問題

          但是,標(biāo)簽沒隱藏啊,通常要是標(biāo)簽一沒權(quán)限,應(yīng)該是標(biāo)簽項(xiàng)、和標(biāo)簽內(nèi)容都隱藏才對(duì)

          為什么會(huì)這樣呢?

          我們?cè)?hasPermi 自定義指令中,打印下獲取到的元素

          添加圖片注釋,不超過 140 字(可選)

          添加圖片注釋,不超過 140 字(可選)

          id 為pane-firstpane-second元素對(duì)應(yīng)位置在哪里,我們找一下 需要先把指令去掉,因?yàn)樵囟急晃覀儎h除的話,我們看不到具體DOM結(jié)構(gòu)

          添加圖片注釋,不超過 140 字(可選)

          添加圖片注釋,不超過 140 字(可選)

          添加圖片注釋,不超過 140 字(可選)

          對(duì)比一下,明顯可以看出 hasPermi 自定義指令獲取到只是標(biāo)簽內(nèi)容的元素 那怎么辦? 解決辦法一:根據(jù)當(dāng)前元素,一層層找到標(biāo)簽項(xiàng),然后刪除,這樣是可以。但是這樣太麻煩了,也只能用于標(biāo)簽頁,那要是其他組件有這樣的問題咋辦 解決辦法二:我們寫一個(gè)函數(shù)判斷權(quán)限是否存在,再通過 v-if 進(jìn)行隱藏


          主站蜘蛛池模板: 亚洲国产一区在线观看| 成人国产精品一区二区网站| 亚洲一区二区三区在线视频| 国产一区二区三区不卡AV| 欧美人妻一区黄a片| 亚洲V无码一区二区三区四区观看| 精品国产一区二区三区香蕉事 | 精品一区二区三区视频在线观看| 99精品国产高清一区二区| 东京热无码av一区二区| 日本一区中文字幕日本一二三区视频 | 精品视频一区二区三区四区五区 | asmr国产一区在线| 精品国产一区二区二三区在线观看| 麻豆国产一区二区在线观看| 无码AV天堂一区二区三区| 精品国产亚洲一区二区三区| 国产成人精品a视频一区| 极品少妇伦理一区二区| 国产MD视频一区二区三区| 国产福利在线观看一区二区| 国产成人一区二区三区精品久久| 国产一区在线视频观看| 精品无码成人片一区二区| 亚洲成av人片一区二区三区| 精品视频一区二区三区| 亚洲一区二区三区自拍公司| 久久精品岛国av一区二区无码| 亚洲线精品一区二区三区| 伊人色综合网一区二区三区 | 亚洲国产精品一区二区三区在线观看 | 国产色欲AV一区二区三区| 国产中文字幕一区| 大屁股熟女一区二区三区| 无码av免费一区二区三区试看| 无码人妻啪啪一区二区| 亚洲一区二区视频在线观看| 无码人妻精品一区二区三区在线| 亚洲欧美日韩一区二区三区在线| 国产嫖妓一区二区三区无码| 国产一区二区三区免费|