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

