为nuxt项目写一个面包屑cli工具实现自动生成页面与面包屑配置
公司项目的面包屑导航是使用element的面包屑组件,配合一份json配置文件来实现的,每次写新页面都需要去写json配置,非常麻烦,所以写一个面包屑cli,自动生成页面、自动配置面包屑数据,提高效率:rocket:
明确目标
- 提供init命令,在一个新项目中能够通过初始化生成面包屑相关文件
- 能够通过命令生成页面,并且自动配置面包屑json数据
- 按照项目原有需求,能够配置面包屑是否可点击跳转
- 按照项目原有需求,能够配置某路径下是否展示面包屑
- 支持仅配置而不生成文件,能够为已存在的页面生成配置
- 能够动态配置当前面包屑导航的数据
- ……(后续在使用中发现问题并优化)
实现分成两部分
- 面包屑实现
- cli命令实现
面包屑实现
- 在路由前置守卫beforEach中根据当前路径在配置文件中匹配到相应的数据
- 把这些配置存到vuex
- 在面包屑组件中根据vuex中的数据v-for循环渲染出面包屑
JSON配置文件
json配置文件是通过命令生成的,一个配置对象包含namepathclickableisShow属性
[ { "name":"应用",//面包屑名称(在命令交互中输入) "path":"/app",//面包屑对应路径(根据文件自动生成) "clickable":true,//是否可点击跳转 "isShow":true//是否显示 }, { "name":"应用详情", "path":"/app/detail", "clickable":true,//是否可点击跳转 "isShow":true//是否显示 } ]
匹配配置文件中的数据
比如按照上面的配置文件,进入/app/detail时,将会匹配到如下数据
[ { "name":"应用", "path":"/app", "clickable":true, "isShow":true }, { "name":"应用", "path":"/app/detail", "clickable":true, "isShow":true } ]
动态面包屑实现
有时候需要动态修改面包屑数据(比如动态路由),由于数据是存在vuex中的,所以修改起来非常方便,只需在vuex相关文件中提供mutation即可,这些mutation在数据中寻找相应的项,并改掉
exportconststate=()=>({ breadcrumbData:[] }) exportconstmutations={ setBreadcrumb(state,breadcrumbData){ state.breadcrumbData=breadcrumbData }, setBreadcrumbByName(state,{oldName,newName}){ letcurBreadcrumb=state.breadcrumbData.find(breadcrumb=>breadcrumb.name===oldName) curBreadcrumb&&(curBreadcrumb.name=newName) }, setBreadcrumbByPath(state,{path,name}){ letcurBreadcrumb=state.breadcrumbData.find( breadcrumb=>breadcrumb.path===path ) curBreadcrumb&&(curBreadcrumb.name=name) } }
根据路径匹配相应配置数据具体代码
importbreadcrumbsfrom'@/components/breadcrumb/breadcrumb.config.json' functionpath2Arr(path){ returnpath.split('/').filter(p=>p) } functionmatchBreadcrumbData(matchPath){ returnpath2Arr(matchPath) .map(path=>{ path=path.replace(/^:([^:?]+)(\?)?$/,(match,$1)=>{ return`_${$1}` }) return'/'+path }) .map((path,index,paths)=>{ //第0个不需拼接 if(index){ letresult='' for(leti=0;i<=index;i++){ result+=paths[i] } returnresult } returnpath }) .map(path=>{ constitem=breadcrumbs.find(bread=>bread.path===path) if(item){ returnitem } return{ name:path.split('/').pop(), path, clickable:false, isShow:true } }) } exportdefault({app,store})=>{ app.router.beforeEach((to,from,next)=>{ consttoPathArr=path2Arr(to.path) consttoPathArrLength=toPathArr.length letmatchPath='' //从matched中找出当前路径的路由配置 for(letmatchofto.matched){ constmatchPathArr=path2Arr(match.path) if(matchPathArr.length===toPathArrLength){ matchPath=match.path break } } constbreadcrumbData=matchBreadcrumbData(matchPath) store.commit('breadcrumb/setBreadcrumb',breadcrumbData) next() }) }
面包屑组件
面包屑组件中渲染匹配到的数据
{{item.name}}
cli命令实现
cli命令开发用到的相关库如下:这些就不细说了,基本上看下README就知道怎么用了
- commander:命令行工具
- boxen:在终端画一个框
- inquirer:命令行交互工具
- handlebar:模版引擎
目录结构
lib//存命令行文件 |--bcg.js template//存模版 |--breadcrumb//面包屑配置文件与组件,将生成在项目@/components中 |--breadcrumb.config.json |--index.vue |--braadcrumb.js//vuex相关文件,将生成在项目@/store中 |--new-page.vue//新文件模版,将生成在命令行输入的新路径中 |--route.js//路由前置守卫配置文件,将生成在@/plugins中 test//单元测试相关文件
node支持命令行,只需在package.json的bin字段中关联命令行执行文件
//执行bcg命令时,就会执行lib/bcg.js的代码 { "bin":{ "bcg":"lib/bcg.js" } }
实现命令
实现一个init命令,生成相关面包屑文件(面包屑组件、json配置文件、前置守卫plugin、面包屑store)
bcginit
实现一个new命令生成文件,默认基础路径是 src/pages,带一个 -b选项,可用来修改基础路径
bcgnew-b
具体代码如下
#!/usr/bin/envnode constpath=require('path') constfs=require('fs-extra') constboxen=require('boxen') constinquirer=require('inquirer') constcommander=require('commander') constHandlebars=require('handlebars') const{ createPathArr, log, errorLog, successLog, infoLog, copyFile }=require('./utils') constVUE_SUFFIX='.vue' constsource={ VUE_PAGE_PATH:path.resolve(__dirname,'../template/new-page.vue'), BREADCRUMB_COMPONENT_PATH:path.resolve(__dirname,'../template/breadcrumb'), PLUGIN_PATH:path.resolve(__dirname,'../template/route.js'), STORE_PATH:path.resolve(__dirname,'../template/breadcrumb.js') } consttarget={ BREADCRUMB_COMPONENT_PATH:'src/components/breadcrumb', BREADCRUMB_JSON_PATH:'src/components/breadcrumb/breadcrumb.config.json', PLUGIN_PATH:'src/plugins/route.js', STORE_PATH:'src/store/breadcrumb.js' } functioninitBreadCrumbs(){ try{ copyFile(source.BREADCRUMB_COMPONENT_PATH,target.BREADCRUMB_COMPONENT_PATH) copyFile(source.PLUGIN_PATH,target.PLUGIN_PATH) copyFile(source.STORE_PATH,target.STORE_PATH) }catch(err){ throwerr } } functiongenerateVueFile(newPagePath){ try{ if(fs.existsSync(newPagePath)){ log(errorLog(`${newPagePath}已存在`)) return } constfileName=path.basename(newPagePath).replace(VUE_SUFFIX,'') constvuePage=fs.readFileSync(source.VUE_PAGE_PATH,'utf8') consttemplate=Handlebars.compile(vuePage) constresult=template({filename:fileName}) fs.outputFileSync(newPagePath,result) log(successLog('\nvue页面生成成功咯\n')) }catch(err){ throwerr } } functionupdateConfiguration(filePath,{ clickable, isShow }={}){ try{ if(!fs.existsSync(target.BREADCRUMB_JSON_PATH)){ log(errorLog('面包屑配置文件不存在,配置失败咯,可通过bcginit生成相关文件')) return } letpathArr=createPathArr(filePath) constconfigurationArr=fs.readJsonSync(target.BREADCRUMB_JSON_PATH) //如果已经有配置就过滤掉 pathArr=pathArr.filter(pathItem=>!configurationArr.some(configurationItem=>configurationItem.path===pathItem)) constquestions=pathArr.map(pathItem=>{ return{ type:'input', name:pathItem, message:`请输入${pathItem}的面包屑显示名称`, default:pathItem } }) inquirer.prompt(questions).then(answers=>{ constpathArrLastIdx=pathArr.length-1 pathArr.forEach((pathItem,index)=>{ configurationArr.push({ clickable:index===pathArrLastIdx?clickable:false, isShow:index===pathArrLastIdx?isShow:true, name:answers[pathItem], path:pathItem }) }) fs.writeJsonSync(target.BREADCRUMB_JSON_PATH,configurationArr,{ spaces:2 }) log(successLog('\n生成面包屑配置成功咯')) }) }catch(err){ log(errorLog('生成面包屑配置失败咯')) throwerr } } functiongenerating(newPagePath,filePath){ inquirer.prompt([ { type:'confirm', name:'clickable', message:'是否可点击跳转?(默认yes)', default:true }, { type:'confirm', name:'isShow', message:'是否展示面包屑?(默认yes)', default:true }, { type:'confirm', name:'onlyConfig', message:'是否仅生成配置而不生成文件?(默认no)', default:false } ]).then(({clickable,isShow,onlyConfig})=>{ if(onlyConfig){ updateConfiguration(filePath,{clickable,isShow}) return } generateVueFile(newPagePath) updateConfiguration(filePath,{clickable,isShow}) }) } constprogram=newcommander.Command() program .command('init') .description('初始化面包屑') .action(initBreadCrumbs) program .version('0.1.0') .command('new') .description('生成页面并配置面包屑,默认基础路径为src/pages,可通过-b修改') .option('-b,--basePath ','修改基础路径(不要以/开头)') .action((filePath,opts)=>{ filePath=filePath.endsWith(VUE_SUFFIX)?filePath:`${filePath}${VUE_SUFFIX}` constbasePath=opts.basePath||'src/pages' constnewPagePath=path.join(basePath,filePath) log( infoLog( boxen(`即将配置${newPagePath}`,{ padding:1, margin:1, borderStyle:'round' }) ) ) generating(newPagePath,filePath) }) program.parse(process.argv) if(!process.argv.slice(2)[0]){ program.help() }
发布npm
开发完成后,发布到npm,具体方法就不细说了,发布后全局安装就能愉快的使用咯!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。