<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Vue 路由權(quán)限控制

          共 18679字,需瀏覽 38分鐘

           ·

          2020-11-17 02:51

          作者:sky124380729

          來(lái)源:SegmentFault 思否社區(qū)




          Vue路由權(quán)限控制


          當(dāng)我們?cè)谧龊笈_(tái)管理系統(tǒng)的時(shí)候,都會(huì)涉及到系統(tǒng)左側(cè)的菜單樹(shù)如何動(dòng)態(tài)顯示的問(wèn)題。目前基本上都是RBAC的解決方案,即Role-Based Access Control,權(quán)限與角色相關(guān)聯(lián),用戶通過(guò)成為適當(dāng)角色的成員而得到這些角色的權(quán)限。這就極大地簡(jiǎn)化了權(quán)限的管理。

          vue有很多優(yōu)秀的后臺(tái)管理系統(tǒng)模板,這些開(kāi)源項(xiàng)目都提供了RBAC權(quán)限控制的思路,但是在實(shí)際項(xiàng)目中,寫(xiě)死角色的方式可能并不適合。


          看了網(wǎng)上蠻多的解決方案,個(gè)人感覺(jué)都有弊端,好多都是前端先把完整的路由表注冊(cè)到項(xiàng)目中,然后通過(guò)后臺(tái)返回樹(shù)過(guò)濾顯示的方案,這樣的做法其實(shí)只是隱藏了左側(cè)菜單,但是路由還是已經(jīng)注冊(cè)進(jìn)去了,用戶猜到訪問(wèn)路徑還是可以輕易進(jìn)入頁(yè)面,沒(méi)有真正的做到動(dòng)態(tài)路由加載




          以下是我設(shè)計(jì)的解決方案,?-。-


          先看一下已經(jīng)完成的系統(tǒng)結(jié)構(gòu),便于理解,用戶綁定角色(一對(duì)多),角色綁定菜單(一對(duì)多)


          用戶菜單



          選擇角色



          角色菜單



          選擇菜單,由于本項(xiàng)目是多系統(tǒng),所以會(huì)有 ADMIN 和 HMI 兩個(gè)子系統(tǒng),后面再來(lái)解釋



          資源管理(我這里沒(méi)有叫做菜單管理,因?yàn)闀?huì)涉及到各個(gè)子系統(tǒng)我稱作模塊,模塊下面有菜單,菜單下面有按鈕)




          好了,看完幾張圖大家估計(jì)也明白了這就是典型的RBAC。內(nèi)部具體怎么運(yùn)作的呢


          要實(shí)現(xiàn)動(dòng)態(tài)添加路由,即只有有權(quán)限的路由才會(huì)注冊(cè)到Vue實(shí)例中,核心是vue-router的addRoutes和導(dǎo)航鉤子beforeEach兩個(gè)方法





          大體思路為,在beforeEach方法中(即每次路由跳轉(zhuǎn)之前做判斷),如果已經(jīng)加載了路由表,則把路由表注冊(cè)到實(shí)例中,如果沒(méi)有,則從后端拉取路由表注冊(cè)到實(shí)例中。那為什么不在登錄的時(shí)候加載一次就可以了呢?這是因?yàn)槿绻皇堑卿浀臅r(shí)候加載一次,網(wǎng)頁(yè)刷新的時(shí)候注冊(cè)的路由表會(huì)丟失,所以我們統(tǒng)一在beforeEach這個(gè)鉤子中去實(shí)現(xiàn)


          //?permission.js,此文件在main.js中直接導(dǎo)入即可,這邊的思路是仿照的element-admin

          import?router?from?'./router'
          import?store?from?'./store'
          import?NProgress?from?'nprogress'?//?progress?bar
          import?'nprogress/nprogress.css'?//?progress?bar?style
          import?Cookies?from?'js-cookie'
          import?screenfull?from?'screenfull'

          router.beforeEach(async?(to,?from,?next)?=>?{
          ????const?token?=?Cookies.get('token')
          ????NProgress.start()
          ????if?(token)?{
          ????????//?如果已經(jīng)處于登錄狀態(tài),跳到登錄頁(yè)重定向到首頁(yè)
          ????????if?(to.path?===?'/login')?{
          ????????????next({?path:?'/'?})
          ????????????NProgress.done()
          ????????}?else?{
          ????????????if?(!store.state.authorized)?{
          ????????????????try?{
          ????????????????????router.addRoutes(await?store.dispatch('setAccessRoutes'))
          ????????????????????store.dispatch('setAllDict')
          ????????????????????next({?...to,?replace:?true?})
          ????????????????}?catch?(e)?{
          ????????????????????Cookies.remove('token')
          ????????????????????Cookies.remove('userInfo')
          ????????????????????next({?path:?'/login'?})
          ????????????????????NProgress.done()
          ????????????????}
          ????????????}?else?{
          ????????????????next()
          ????????????????//?全屏參數(shù)判斷該頁(yè)面是否全屏
          ????????????????if?(!screenfull.isEnabled)?return
          ????????????????if?(to.meta?&&?to.meta.fullScreen)?{
          ????????????????????screenfull.request().catch(()?=>?null)
          ????????????????}?else?{
          ????????????????????if?(screenfull.isFullscreen)?{
          ????????????????????????screenfull.exit()
          ????????????????????}
          ????????????????}
          ????????????}
          ????????}
          ????}?else?{
          ????????next(to.path?!==?'/login'???{?path:?'/login'?}?:?true)
          ????}
          })

          router.afterEach(()?=>?{
          ????NProgress.done()
          })


          由于路由是動(dòng)態(tài)注冊(cè)的,所以項(xiàng)目的初始路由就會(huì)很簡(jiǎn)潔,只要提供基礎(chǔ)的路由,其他路由都是從服務(wù)器返回之后動(dòng)態(tài)注冊(cè)進(jìn)來(lái)的


          //?router.js

          import?Vue?from?'vue'
          import?Router?from?'vue-router'
          import?Login?from?'modules/Login'
          import?NoPermission?from?'modules/NoPermission'
          Vue.use(Router)

          //?Fixed?NavigationDuplicated?Problem
          const?originalPush?=?Router.prototype.push
          Router.prototype.push?=?function?push(location,?onComplete,?onAbort)?{
          ????if?(onComplete?||?onAbort)?return?originalPush.call(this,?location,?onComplete,?onAbort)
          ????return?originalPush.call(this,?location).catch(err?=>?err)
          }

          const?createRouter?=?()?=>
          ????new?Router({
          ????????mode:?'history',
          ????????scrollBehavior:?()?=>?({?y:?0?}),
          ????????routes:?[
          ????????????{
          ????????????????path:?'/',
          ????????????????redirect:?'/platform'
          ????????????},
          ????????????{
          ????????????????path:?'/noPermission',
          ????????????????component:?NoPermission
          ????????????},
          ????????????{
          ????????????????path:?'/login',
          ????????????????component:?Login
          ????????????}
          ????????]
          ????})

          const?router?=?createRouter()

          export?function?resetRouter()?{
          ????const?newRouter?=?createRouter()
          ????router.matcher?=?newRouter.matcher?//?reset?router
          }

          export?default?router

          webpack之前不支持動(dòng)態(tài)編譯,所以很多項(xiàng)目都在路由表維護(hù)了一份映射表如下:

          const?routerMap?=?{
          ????user:?()?=>?import('/views/user'),
          ????role:?()?=>?import('/views/role'),
          ????...
          }


          我覺(jué)得這樣很不nice,最新版的vue-cli集成的webpack已經(jīng)可以支持動(dòng)態(tài)導(dǎo)入啦,因此可以把所有的路由信息全部放到數(shù)據(jù)庫(kù)里面配置,前端不在需要維護(hù)一份router的映射關(guān)系表啦,如果你是老版的CLI,可以使用dynamic-import

          我們?cè)賮?lái)看看下面這個(gè)神奇的文件,也可以大致瀏覽一下然后看下面的解釋


          //?menu.json

          //?id:隨便是什么規(guī)則,只要唯一就行,這里前端寫(xiě)死ID而不是每次導(dǎo)入數(shù)據(jù)庫(kù)時(shí)候再生成是因?yàn)槿绻看稳霂?kù)的時(shí)候重新生成會(huì)丟失之前的關(guān)聯(lián)關(guān)系
          //?title:菜單的標(biāo)題
          //?name:唯一標(biāo)識(shí)
          //?type:'MD'代表模塊(子系統(tǒng)),'MN'代表菜單,'BT'代表按鈕,如果需要控制到按鈕權(quán)限則需要配置到BT級(jí)別
          //?icon:菜單的圖標(biāo)
          //?uri:菜單的路由地址
          //?componentPath:該菜單在對(duì)應(yīng)前端項(xiàng)目的路徑,在后續(xù)的store.js會(huì)看到用法,就是上述說(shuō)的不需要在寫(xiě)一份routerMap
          //?hidden:作為菜單的時(shí)候是否在左側(cè)顯示,有些菜單比如某個(gè)列表的詳情頁(yè),需要注冊(cè)到實(shí)例中,但是并不需要在左側(cè)菜單欄顯示
          //?noCache:由于項(xiàng)目頁(yè)面增加了緩存控制,因此該字段用于判斷當(dāng)前頁(yè)面是否需要緩存
          //?fullScreen:有些菜單,進(jìn)入的時(shí)候就是全屏展示的,例如某些大屏展示頁(yè)面,通過(guò)該字段配置
          //?children:和上述字段一樣

          [
          ????{
          ????????"id":?"00b82eb6e50a45a495df301b0a3cde8b",
          ????????"title":?"SV?ADMIN",
          ????????"name":?"ADMIN",
          ????????"type":?"MD",
          ????????"children":?[
          ????????????{
          ????????????????{
          ????????????????"id":?"06f1082640a0440b97009d536590cf4f",
          ????????????????"title":?"系統(tǒng)管理",
          ????????????????"name":?"system",
          ????????????????"icon":?"el-icon-setting",
          ????????????????"uri":?"/system",
          ????????????????"componentPath":?"modules/Layout",
          ????????????????"type":?"MN",
          ????????????????"children":?[
          ????????????????????{
          ????????????????????????"id":?"b9bd920263bb47dbbfbf4c6e47cc087b",
          ????????????????????????"title":?"用戶管理",
          ????????????????????????"name":?"principal",
          ????????????????????????"uri":?"principal",
          ????????????????????????"componentPath":?"views/system/principal",
          ????????????????????????"type":?"MN",
          ????????????????????????"children":?[
          ????????????????????????????{?"id":?"b37f971139ca49ab8c6506d4b30eddb3",?"title":?"新增",?"name":?"create",?"type":?"BT"?},
          ????????????????????????????{?"id":?"d3bcee30ec03432db9db2da999bb210f",?"title":?"編輯",?"name":?"edit",?"type":?"BT"?},
          ????????????????????????????{?"id":?"7c2ce28dcedf439fabc4ae9ad94f6899",?"title":?"刪除",?"name":?"delete",?"type":?"BT"?},
          ????????????????????????????{?"id":?"bdf4d9e8bf004e40a82b80f0e88c866c",?"title":?"修改密碼",?"name":?"resetPwd",?"type":?"BT"?},
          ????????????????????????????{?"id":?"ba09f8a270e3420bb8877f8def455f6f",?"title":?"選擇角色",?"name":?"setRole",?"type":?"BT"?}
          ????????????????????????]
          ????????????????????},
          ????????????????????{
          ????????????????????????"id":?"c47c8ad710774576871739504c6cd2a8",
          ????????????????????????"title":?"角色管理",
          ????????????????????????"name":?"role",
          ????????????????????????"uri":?"role",
          ????????????????????????"componentPath":?"views/system/role",
          ????????????????????????"type":?"MN",
          ????????????????????????"children":?[
          ????????????????????????????{?"id":?"81c0dca0ed2c455d9e6b6d0c86d24b10",?"title":?"新增",?"name":?"create",?"type":?"BT"?},
          ????????????????????????????{?"id":?"19a2bf03e6834d3693d69a70e919d55e",?"title":?"編輯",?"name":?"edit",?"type":?"BT"?},
          ????????????????????????????{?"id":?"6136cc46c45a47f4b2f20e899308b097",?"title":?"刪除",?"name":?"delete",?"type":?"BT"?},
          ????????????????????????????{?"id":?"ad5cf52a78b54a1da7c65be74817744b",?"title":?"設(shè)置菜單",?"name":?"setMenu",?"type":?"BT"?}
          ????????????????????????]
          ????????????????????},
          ????????????????????{
          ????????????????????????"id":?"8b5781640b9b4a5cb28ac616da32636c",
          ????????????????????????"title":?"資源管理",
          ????????????????????????"name":?"resource",
          ????????????????????????"uri":?"resource",
          ????????????????????????"componentPath":?"views/system/resource",
          ????????????????????????"type":?"MN",
          ????????????????????????"children":?[
          ????????????????????????????{?"id":?"d4182147883f48069173b7d173e821dc",?"title":?"新增",?"name":?"create",?"type":?"BT"?},
          ????????????????????????????{?"id":?"935fcb52fffa45acb2891043ddb37ace",?"title":?"編輯",?"name":?"edit",?"type":?"BT"?},
          ????????????????????????????{?"id":?"3f99d47b4bfd402eb3c787ee10633f77",?"title":?"刪除",?"name":?"delete",?"type":?"BT"?}
          ????????????????????????]
          ????????????????????}
          ????????????????]
          ????????????},
          ????????????}
          ????????]
          ????},
          ????{
          ????????"id":?"fc8194b529fa4e87b454f970a2e71899",
          ????????"title":?"SV?HMI",
          ????????"name":?"HMI",
          ????????"type":?"MD",
          ????????"children":?[
          ????????????{?"id":?"eb5370681213412d8541d171e9929c84",?"title":?"啟動(dòng)檢測(cè)","name":?"001"?},
          ????????????{?"id":?"06eb36e7224043ddbb591eb4d688f438",?"title":?"設(shè)備信息","name":?"002"?},
          ????????????{?"id":?"76696598fd46432aa19d413bc15b5110",?"title":?"AI模型庫(kù)","name":?"003"?},
          ????????????{?"id":?"2896f3861d9e4506af8120d6fcb59ee1",?"title":?"保養(yǎng)維修","name":?"004"?},
          ????????????{?"id":?"91825c6d7d7a457ebd70bfdc9a3a2d81",?"title":?"繼續(xù)","name":?"005"?},
          ????????????{?"id":?"24694d28b2c943c88487f6e44e7db626",?"title":?"暫停","name":?"006"?},
          ????????????{?"id":?"225387753cf24781bb7c853ee538d087",?"title":?"結(jié)束","name":?"007"?}
          ????????]
          ????}
          ]


          以上是前端的路由配置信息,之前提到過(guò),路由是后端返回的,為什么前端還有一份菜單文件呢?


          因?yàn)槁酚衫锩娴膬?nèi)容全部都是前端需要使用的,比如菜單顯示的圖表,菜單對(duì)應(yīng)的前端路徑等等...既然和前端關(guān)系比較大,所以前端維護(hù)該文件更適合,而不是讓后端去配置XML或者liquibase。你每次菜單有修改的時(shí)候要通知你的后臺(tái)要更新一下數(shù)據(jù)庫(kù),然后切換多個(gè)環(huán)境的時(shí)候每個(gè)后臺(tái)都要通知一聲...后臺(tái)還不一定樂(lè)意X你...然后你想改個(gè)小圖標(biāo)都要小心翼翼被你的后臺(tái)大佬懟...


          當(dāng)然如果是多環(huán)境部署的話還是讓后臺(tái)使用liquibase比較合適,但是開(kāi)發(fā)模式下前端完全可以自己玩。等到需要多環(huán)境(不同數(shù)據(jù)庫(kù))同時(shí)部署的時(shí)候,你把sql語(yǔ)句給后端就OK了,文章下面會(huì)提到怎么導(dǎo)出sql...


          Question:


          既然前端有該文件,是不是意味著路由的源碼又暴露出去了,那和別人的猜路徑就可以訪問(wèn)有什么區(qū)別?不是說(shuō)好了從數(shù)據(jù)庫(kù)拉取菜單信息,你跟我直接用這個(gè)json,那數(shù)據(jù)庫(kù)咋整?別急...


          Answer:


          1. 這只是前端用來(lái)mock的配置文件,build的時(shí)候不會(huì)打包該內(nèi)容。
          2. 在用戶角色菜單這些關(guān)聯(lián)關(guān)系還沒(méi)有建立之前,菜單只能通過(guò)mock來(lái)建立,這個(gè)時(shí)候直接讀取前端的配置文件...額,真香,具體是怎么做到的可以看下面store.js的做法
          3. 菜單始終由前端來(lái)維護(hù),當(dāng)需要上線的時(shí)候,前端可以通過(guò)node將menu.json生成SQL語(yǔ)句導(dǎo)入到數(shù)據(jù)庫(kù)中。后面會(huì)介紹

          接下來(lái)就是如何注冊(cè)這些路由表了

          //?store.js

          import?Vue?from?'vue'
          import?Vuex?from?'vuex'
          import?Cookie?from?'js-cookie'
          import?NotFound?from?'modules/NotFound'
          import?{?resetRouter?}?from?'../router'
          import?{?getUserResourceTree,?getDictAllModel?}?from?'apis'
          import?{?deepClone?}?from?'utils/tools'

          //?此處的IS_TESTING就是用來(lái)判斷當(dāng)前是拉取數(shù)據(jù)庫(kù)的真實(shí)菜單還是直接用前端的menu.json,在資源的關(guān)聯(lián)關(guān)系還沒(méi)有建立之前非常有用
          import?{?IS_TESTING?}?from?'@/config'
          import?{?Message?}?from?'element-ui'

          Vue.use(Vuex)

          //?生產(chǎn)可訪問(wèn)的路由表
          const?createRouter?=?(routes,?cname?=?'')?=>?{
          ????return?routes.reduce((prev,?{?type,?uri:?path,?componentPath,?name,?title,?icon,?redirectUri:?redirect,?hidden,?fullScreen,?noCache,?children?=?[]?})?=>?{
          ????????//?是菜單項(xiàng)就注冊(cè)到路由進(jìn)去
          ????????if?(type?===?'MN')?{
          ????????????prev.push({
          ????????????????path,
          ????????????????//?此處就是webpack動(dòng)態(tài)導(dǎo)入啦,是不是so?easy,媽媽再用不用擔(dān)心我再寫(xiě)一份routerMap放到源碼里了
          ????????????????component:?()?=>?import(`@/${componentPath}`),
          ????????????????name:?(cname?+?'-'?+?name).slice(1),
          ????????????????props:?true,
          ????????????????redirect,
          ????????????????meta:?{?title,?icon,?hidden:?hidden?===?'Y',?type,?fullScreen:?fullScreen?===?'Y',?noCache:?noCache?===?'Y'?},
          ????????????????children:?children.length???createRouter(children,?cname?+?'-'?+?name)?:?[]
          ????????????})
          ????????}
          ????????return?prev
          ????},?[])
          }

          //?生產(chǎn)權(quán)限按鈕表
          const?createPermissionBtns?=?router?=>?{
          ????let?btns?=?[]
          ????const?c?=?(router,?name?=?'')?=>?{
          ????????router.forEach(v?=>?{
          ????????????v.type?===?'BT'?&&?btns.push((name?+?'-'?+?v.name).slice(1))
          ????????????return?v.children?&&?v.children.length???c(v.children,?name?+?'-'?+?v.name)?:?null
          ????????})
          ????????return?btns
          ????}
          ????return?c(router)
          }

          export?default?new?Vuex.Store({
          ????state:?{
          ????????collapse:?false,?//?菜單欄是否收縮
          ????????authorized:?false,?//?是否拉取了授權(quán)菜單
          ????????dict:?{},
          ????????accsessRoutes:?[],?//?已注冊(cè)的路由
          ????????permissionBtns:?[],?//?有權(quán)限的按鈕
          ????????navTags:?[],?//?標(biāo)簽導(dǎo)航列表
          ????????cachedViews:?[]?//?緩存的頁(yè)面
          ????},
          ????getters:?{
          ????????collapse:?state?=>?state.collapse,
          ????????cachedViews:?state?=>?state.cachedViews,
          ????????accsessRoutes:?state?=>?state.accsessRoutes,
          ????????//?菜單欄(過(guò)濾掉hidden)
          ????????menuList:?state?=>?{
          ????????????const?filterMenus?=?menus?=>?{
          ????????????????return?menus.filter(item?=>?{
          ????????????????????if?(item.children?&&?item.children.length)?{
          ????????????????????????item.children?=?filterMenus(item.children)
          ????????????????????}
          ????????????????????return?item.meta?&&?!item.meta.hidden
          ????????????????})
          ????????????}
          ????????????return?filterMenus(deepClone(state.accsessRoutes))
          ????????},
          ????????navTags:?state?=>?state.navTags
          ????},
          ????mutations:?{
          ????????SET_ACCSESS_ROUTES(state,?accsessRoutes)?{
          ????????????state.authorized?=?true
          ????????????state.accsessRoutes?=?accsessRoutes
          ????????},
          ????????SET_ALL_DICT(state,?dict)?{
          ????????????state.dict?=?dict
          ????????},
          ????????SET_PERMISSION_BTNS(state,?btns)?{
          ????????????state.permissionBtns?=?btns
          ????????},
          ????????SET_COLLAPSE(state,?flag)?{
          ????????????state.collapse?=?flag
          ????????},
          ????????SET_CACHED_VIEWS(state,?cachedViews)?{
          ????????????state.cachedViews?=?cachedViews
          ????????},
          ????????//?退出登錄
          ????????LOGOUT:?state?=>?{
          ????????????state.cachedViews?=?[]
          ????????????state.authorized?=?false
          ????????????resetRouter()
          ????????????Cookie.remove('token')
          ????????????Cookie.remove('userInfo')
          ????????}
          ????},
          ????actions:?{
          ????????setAccessRoutes:?({?commit?})?=>?{
          ????????????return?new?Promise(async?(resolve,?reject)?=>?{
          ????????????????//?404頁(yè)面選擇在動(dòng)態(tài)添加路由之后再注冊(cè)進(jìn)來(lái),是因?yàn)槿绻_(kāi)始就注冊(cè)到項(xiàng)目中,在addRoutes之后會(huì)有限匹配該404,造成BUG
          ????????????????const?routerExt?=?[
          ????????????????????{?path:?'*',?redirect:?'/404'?},
          ????????????????????{?path:?'/404',?component:?NotFound?}
          ????????????????]
          ????????????????//?getUserResourceTree這個(gè)接口邏輯是查詢當(dāng)前登錄人角色所包含的資源,過(guò)濾出模塊名(這里是ADMIN)下面的子節(jié)點(diǎn)(包含菜單和按鈕)
          ????????????????const?res?=?await?(IS_TESTING???import('@/mock/menu.json')?:?getUserResourceTree('ADMIN'))
          ????????????????if?(!res)?return?reject()
          ????????????????let?router
          ????????????????if?(IS_TESTING)?{
          ????????????????????//?這里取第0個(gè)是因?yàn)槲疫@個(gè)系統(tǒng)是屬于大系統(tǒng)的第一個(gè)子系統(tǒng),在菜單menu.json可以看到
          ????????????????????router?=?res[0].children
          ????????????????}?else?{
          ????????????????????if?(!res.data.length)?{
          ????????????????????????reject()
          ????????????????????????return?Message.error('用戶未配置菜單或菜單配置不正確,請(qǐng)檢查后重試~')
          ????????????????????}?else?{
          ????????????????????????router?=?res.data
          ????????????????????}
          ????????????????}
          ????????????????const?accessRoutes?=?createRouter(router).concat(routerExt)
          ????????????????commit('SET_ACCSESS_ROUTES',?accessRoutes)
          ????????????????commit('SET_PERMISSION_BTNS',?createPermissionBtns(router))
          ????????????????resolve(accessRoutes)
          ????????????})
          ????????},
          ????????setAllDict:?async?({?commit?})?=>?{
          ????????????if?(IS_TESTING)?return
          ????????????const?res?=?await?getDictAllModel()
          ????????????if?(!res)?return
          ????????????commit('SET_ALL_DICT',?res.data)
          ????????},
          ????????logout:?({?commit?})?=>?{
          ????????????return?new?Promise(resolve?=>?{
          ????????????????commit('LOGOUT')
          ????????????????resolve()
          ????????????})
          ????????}
          ????}
          })


          好了,最后一步就是在上線的時(shí)候如何把menu.json變成數(shù)據(jù)庫(kù)的SQL,之后就可以把IS_TESTING改為false,真正拉取數(shù)據(jù)庫(kù)的菜單啦

          //?createMenu.js
          const?fs?=?require('fs')
          const?path?=?require('path')
          const?chalk?=?require('chalk')
          const?execSync?=?require('child_process').execSync?//同步子進(jìn)程
          const?resolve?=?dir?=>?path.join(__dirname,?dir)
          const?format?=?(data?=?new?Date(),?fmt?=?'yyyy-MM-dd')?=>?{
          ????let?o?=?{
          ????????'M+':?data.getMonth()?+?1,?//?月份
          ????????'d+':?data.getDate(),?//?日
          ????????'h+':?data.getHours(),?//?小時(shí)
          ????????'m+':?data.getMinutes(),?//?分
          ????????'s+':?data.getSeconds(),?//?秒
          ????????'q+':?Math.floor((data.getMonth()?+?3)?/?3),?//?季度
          ????????S:?data.getMilliseconds()?//?毫秒
          ????}
          ????if?(/(y+)/.test(fmt))?{
          ????????fmt?=?fmt.replace(RegExp.$1,?(data.getFullYear()?+?'').substr(4?-?RegExp.$1.length))
          ????}
          ????for?(var?k?in?o)?{
          ????????if?(new?RegExp('('?+?k?+?')').test(fmt))?{
          ????????????fmt?=?fmt.replace(RegExp.$1,?RegExp.$1.length?===?1???o[k]?:?('00'?+?o[k]).substr((''?+?o[k]).length))
          ????????}
          ????}
          ????return?fmt
          }
          //?導(dǎo)出的文件目錄位置
          const?SQL_PATH?=?resolve('./menu.sql')
          //?獲取全局配置的git的用戶名,用來(lái)追蹤SQL是誰(shuí)導(dǎo)出的(出問(wèn)題鍋是誰(shuí)的QAQ)
          const?myname?=?execSync('git?show?-s?--format=%cn')
          ????.toString()
          ????.trim()
          //?導(dǎo)出SQL的函數(shù)
          function?createSQL(data,?name?=?'',?pid?=?'0',?arr?=?[])?{
          ????data.forEach(function(v,?d)?{
          ????????if?(v.children?&&?v.children.length)?{
          ????????????createSQL(v.children,?name?+?'-'?+?v.name,?v.id,?arr)
          ????????}
          ????????arr.push({
          ????????????id:?v.id,
          ????????????created_at:?format(new?Date(),?'yyyy-MM-dd?hh:mm:ss'),
          ????????????modified_at:?format(new?Date(),?'yyyy-MM-dd?hh:mm:ss'),
          ????????????created_by:?myname,
          ????????????modified_by:?myname,
          ????????????version:?1,
          ????????????is_delete:?'N',
          ????????????code:?(name?+?'-'?+?v.name).slice(1),
          ????????????name:?v.name,
          ????????????title:?v.title,
          ????????????icon:?v.icon,
          ????????????uri:?v.uri,
          ????????????sort:?d?+?1,
          ????????????parent_id:?pid,
          ????????????type:?v.type,
          ????????????component_path:?v.componentPath,
          ????????????redirect_uri:?v.redirectUri,
          ????????????full_screen:?v.fullScreen?===?'Y'???'Y'?:?'N',
          ????????????hidden:?v.hidden?===?'Y'???'Y'?:?'N',
          ????????????no_cache:?v.noCache?===?'Y'???'Y'?:?'N'
          ????????})
          ????})
          ????return?arr
          }

          fs.readFile(resolve('src/mock/menu.json'),?'utf-8',?(err,?data)?=>?{
          ????const?menuList?=?createSQL(JSON.parse(data))
          ????const?sql?=?menuList
          ????????.map(sql?=>?{
          ????????????let?value?=?''
          ????????????for?(const?v?of?Object.values(sql))?{
          ????????????????value?+=?','
          ????????????????value?+=?v???`'${v}'`?:?null
          ????????????}
          ????????????return?'INSERT?INTO?`t_sys_resource`?VALUES?('?+?value.slice(1)?+?')'?+?'\n'
          ????????})
          ????????.join(';')
          ????const?mySQL?=
          ????????'DROP?TABLE?IF?EXISTS?`t_sys_resource`;'?+
          ????????'\n'?+
          ????????'CREATE?TABLE?`t_sys_resource`?('?+
          ????????'\n'?+
          ????????'`id`?varchar(64)?NOT?NULL,'?+
          ????????'\n'?+
          ????????"`created_at`?timestamp?NULL?DEFAULT?NULL?COMMENT?'創(chuàng)建時(shí)間',"?+
          ????????'\n'?+
          ????????"`modified_at`?timestamp?NULL?DEFAULT?NULL?COMMENT?'更新時(shí)間',"?+
          ????????'\n'?+
          ????????"`created_by`?varchar(64)?DEFAULT?NULL?COMMENT?'創(chuàng)建人',"?+
          ????????'\n'?+
          ????????"`modified_by`?varchar(64)?DEFAULT?NULL?COMMENT?'更新人',"?+
          ????????'\n'?+
          ????????"`version`?int(11)?DEFAULT?NULL?COMMENT?'版本(樂(lè)觀鎖)',"?+
          ????????'\n'?+
          ????????"`is_delete`?char(1)?DEFAULT?NULL?COMMENT?'邏輯刪除',"?+
          ????????'\n'?+
          ????????"`code`?varchar(150)?NOT?NULL?COMMENT?'編碼',"?+
          ????????'\n'?+
          ????????"`name`?varchar(50)?DEFAULT?NULL?COMMENT?'名稱',"?+
          ????????'\n'?+
          ????????"`title`?varchar(50)?DEFAULT?NULL?COMMENT?'標(biāo)題',"?+
          ????????'\n'?+
          ????????"`icon`?varchar(50)?DEFAULT?NULL?COMMENT?'圖標(biāo)',"?+
          ????????'\n'?+
          ????????"`uri`?varchar(250)?DEFAULT?NULL?COMMENT?'路徑',"?+
          ????????'\n'?+
          ????????"`sort`?int(11)?DEFAULT?NULL?COMMENT?'排序',"?+
          ????????'\n'?+
          ????????"`parent_id`?varchar(64)?DEFAULT?NULL?COMMENT?'父id',"?+
          ????????'\n'?+
          ????????"`type`?char(2)?DEFAULT?NULL?COMMENT?'類型',"?+
          ????????'\n'?+
          ????????"`component_path`?varchar(250)?DEFAULT?NULL?COMMENT?'組件路徑',"?+
          ????????'\n'?+
          ????????"`redirect_uri`?varchar(250)?DEFAULT?NULL?COMMENT?'重定向路徑',"?+
          ????????'\n'?+
          ????????"`full_screen`?char(1)?DEFAULT?NULL?COMMENT?'全屏',"?+
          ????????'\n'?+
          ????????"`hidden`?char(1)?DEFAULT?NULL?COMMENT?'隱藏',"?+
          ????????'\n'?+
          ????????"`no_cache`?char(1)?DEFAULT?NULL?COMMENT?'緩存',"?+
          ????????'\n'?+
          ????????'PRIMARY?KEY?(`id`),'?+
          ????????'\n'?+
          ????????'UNIQUE?KEY?`code`?(`code`)?USING?BTREE'?+
          ????????'\n'?+
          ????????")?ENGINE=InnoDB?DEFAULT?CHARSET=utf8?COMMENT='資源';"?+
          ????????'\n'?+
          ????????sql
          ????fs.writeFile(SQL_PATH,?mySQL,?err?=>?{
          ????????if?(err)?return?console.log(err)
          ??????? console.log(chalk.cyanBright(`恭喜你,創(chuàng)建sql語(yǔ)句成功,位置:${SQL_PATH}`))
          ????})
          })

          //?package.json
          "scripts":?{
          ????"build":?"vue-cli-service?build",
          ????"lint":?"vue-cli-service?lint",
          ????"dev":?"vue-cli-service?serve",
          ????"menu":?"node?createMenu"
          ??},

          需要生成SQL的時(shí)候執(zhí)行一下?npm run menu?就好啦

          為了方便,以上SQL是會(huì)先刪除資源表再重新創(chuàng)建,導(dǎo)入數(shù)據(jù)庫(kù)之前記得備份一下。

          整個(gè)流程是不是so easy?so easy?so easy?

          后臺(tái)表建好了之后前端自己玩,自給自足的感覺(jué)香不香?



          點(diǎn)擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開(kāi)更多互動(dòng)和交流。

          -?END -

          瀏覽 67
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  av噜噜国产在线观看 | 亚洲久久成人 | 青青草免费在线公开视频 | 成年人性生活免费视频 | 国产一级片电影免费专区 |