为nuxt项目写一个面包屑cli工具实现自动生成页面与面包屑配置

公司项目的面包屑导航是使用 element 的面包屑组件,配合一份 json 配置文件来实现的,每次写新页面都需要去写 json 配置,非常麻烦,所以写一个面包屑cli,自动生成页面、自动配置面包屑数据,提高效率:rocket:

明确目标

  • 提供 init 命令,在一个新项目中能够通过初始化生成面包屑相关文件
  • 能够通过命令生成页面,并且自动配置面包屑 json 数据
  • 按照项目原有需求,能够配置面包屑是否可点击跳转
  • 按照项目原有需求,能够配置某路径下是否展示面包屑
  • 支持仅配置而不生成文件,能够为已存在的页面生成配置
  • 能够动态配置当前面包屑导航的数据
  • …… (后续在使用中发现问题并优化)

实现分成两部分

  • 面包屑实现
  • cli 命令实现

面包屑实现

  • 在路由前置守卫 beforEach 中根据当前路径在配置文件中匹配到相应的数据
  • 把这些配置存到 vuex
  • 在面包屑组件中根据 vuex 中的数据 v-for 循环渲染出面包屑

JSON 配置文件

json 配置文件是通过命令生成的,一个配置对象包含 name path clickable isShow 属性

[
 {
  "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 在数据中寻找相应的项,并改掉

export const state = () => ({
  breadcrumbData: []
})

export const mutations = {
 setBreadcrumb (state, breadcrumbData) {
  state.breadcrumbData = breadcrumbData
 },

 setBreadcrumbByName (state, {oldName, newName}) {
  let curBreadcrumb = state.breadcrumbData.find(breadcrumb => breadcrumb.name === oldName)

  curBreadcrumb && (curBreadcrumb.name = newName)
 },

 setBreadcrumbByPath (state, {path, name}) {
  let curBreadcrumb = state.breadcrumbData.find(
   breadcrumb => breadcrumb.path === path
  )
  curBreadcrumb && (curBreadcrumb.name = name)
 }
}

根据路径匹配相应配置数据具体代码

import breadcrumbs from '@/components/breadcrumb/breadcrumb.config.json'

function path2Arr(path) {
 return path.split('/').filter(p => p)
}

function matchBreadcrumbData (matchPath) {
 return path2Arr(matchPath)
  .map(path => {
   path = path.replace(/^:([^:?]+)(\?)?$/, (match, $1) => {
    return `_${$1}`
   })
   return '/' + path
  })
  .map((path, index, paths) => {

   // 第 0 个不需拼接
   if (index) {
    let result = ''
    for (let i = 0; i <= index; i++) {
     result += paths[i]
    }
    return result
   }
   return path
  })
  .map(path => {
   const item = breadcrumbs.find(bread => bread.path === path)
   if (item) {
    return item
   }
   return {
    name: path.split('/').pop(),
    path,
    clickable: false,
    isShow: true
   }
  })
}

export default ({ app, store }) => {
 app.router.beforeEach((to, from, next) => {
  const toPathArr = path2Arr(to.path)
  const toPathArrLength = toPathArr.length
  let matchPath = ''

  // 从 matched 中找出当前路径的路由配置
  for (let match of to.matched) {
   const matchPathArr = path2Arr(match.path)
   if (matchPathArr.length === toPathArrLength) {
    matchPath = match.path
    break
   }
  }

  const breadcrumbData = matchBreadcrumbData(matchPath)

  store.commit('breadcrumb/setBreadcrumb', breadcrumbData)
  next()
 })
}

面包屑组件

面包屑组件中渲染匹配到的数据

<template>
 <div class="bcg-breadcrumb" v-if="isBreadcrumbShow">
  <el-breadcrumb separator="/">
   <el-breadcrumb-item
    v-for="(item, index) in breadcrumbData"
    :to="item.clickable ? item.path : ''"
    :key="index">
    {{ item.name }}
   </el-breadcrumb-item>
  </el-breadcrumb>
 </div>
</template>

<script>
import breadcrumbs from "./breadcrumb.config"
export default {
 name: 'Breadcrumb',
 computed: {
  isBreadcrumbShow () {
   return this.curBreadcrumb && this.curBreadcrumb.isShow
  },
  breadcrumbData () {
   return this.$store.state.breadcrumb.breadcrumbData
  },
  curBreadcrumb () {
   return this.breadcrumbData[this.breadcrumbData.length - 1]
  }
 }
}
</script>

cli命令实现

cli命令开发用到的相关库如下:这些就不细说了,基本上看下 README 就知道怎么用了

目录结构

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)

bcg init

实现一个 new 命令生成文件,默认基础路径是 src/pages ,带一个 -b 选项,可用来修改基础路径

bcg new <file-path> -b <base-path>

具体代码如下

#!/usr/bin/env node
const path = require('path')
const fs = require('fs-extra')

const boxen = require('boxen')
const inquirer = require('inquirer')
const commander = require('commander')
const Handlebars = require('handlebars')

const {
 createPathArr,
 log,
 errorLog,
 successLog,
 infoLog,
 copyFile
} = require('./utils')

const VUE_SUFFIX = '.vue'

const source = {
 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')
}

const target = {
 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'
}

function initBreadCrumbs() {
 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) {
  throw err
 }
}

function generateVueFile(newPagePath) {
 try {
  if (fs.existsSync(newPagePath)) {
   log(errorLog(`${newPagePath} 已存在`))
   return
  }

  const fileName = path.basename(newPagePath).replace(VUE_SUFFIX, '')
  const vuePage = fs.readFileSync(source.VUE_PAGE_PATH, 'utf8')
  const template = Handlebars.compile(vuePage)
  const result = template({ filename: fileName })

  fs.outputFileSync(newPagePath, result)

  log(successLog('\nvue页面生成成功咯\n'))
 } catch (err) {
  throw err
 }
}

function updateConfiguration(filePath, {
 clickable,
 isShow
} = {}) {
 try {
  if (!fs.existsSync(target.BREADCRUMB_JSON_PATH)) {
   log(errorLog('面包屑配置文件不存在, 配置失败咯, 可通过 bcg init 生成相关文件'))
   return
  }

  let pathArr = createPathArr(filePath)
  const configurationArr = fs.readJsonSync(target.BREADCRUMB_JSON_PATH)

  // 如果已经有配置就过滤掉
  pathArr = pathArr.filter(pathItem => !configurationArr.some(configurationItem => configurationItem.path === pathItem))

  const questions = pathArr.map(pathItem => {
   return {
    type: 'input',
    name: pathItem,
    message: `请输入 ${pathItem} 的面包屑显示名称`,
    default: pathItem
   }
  })

  inquirer.prompt(questions).then(answers => {
   const pathArrLastIdx = 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('生成面包屑配置失败咯'))
  throw err
 }
}

function generating(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 })
 })
}

const program = new commander.Command()

program
 .command('init')
 .description('初始化面包屑')
 .action(initBreadCrumbs)

program
 .version('0.1.0')
 .command('new <file-path>')
 .description('生成页面并配置面包屑,默认基础路径为 src/pages,可通过 -b 修改')
 .option('-b, --basePath <base-path>', '修改基础路径 (不要以 / 开头)')
 .action((filePath, opts) => {
  filePath = filePath.endsWith(VUE_SUFFIX) ? filePath : `${filePath}${VUE_SUFFIX}`
  const basePath = opts.basePath || 'src/pages'
  const newPagePath = 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,具体方法就不细说了,发布后全局安装就能愉快的使用咯!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解基于 Nuxt 的 Vue.js 服务端渲染实践

    Vue.js 是目前最火热的前端框架之一,而 Nuxt.js 是针对 Vue.js 推出的服务端渲染框架,通过高度定制化的配置以及简洁的 API,开发者可以快速进行服务端渲染项目的开发,本文将对 Nuxt.js 框架做一个简要介绍. 服务端渲染 服务端渲染(Server Side Render)并不是一个新的概念,在单页应用(SPA)还没有流行起来的时候,页面就是通过服务端渲染好,并传递给浏览器的.当用户需要访问新的页面时,需要再次请求服务器,返回新的页面. 为了优化体验,开发者们开始选择采用

  • 详解Nuxt.js部署及踩过的坑

    Nuxt.js 提供了两种发布部署应用的方式:服务端渲染应用部署 和 静态应用部署. 静态应用部署就不说了,主要说说服务端渲染应用部署. 官方推荐部署方式 关于服务端渲染应用部署,官方文档是这么写的: 部署 Nuxt.js 服务端渲染的应用不能直接使用 nuxt 命令,而应该先进行编译构建,然后再启动 Nuxt 服务,可通过以下两个命令来完成: nuxt build nuxt start 推荐的 package.json 配置如下: { "name": "my-app&quo

  • nuxt.js 缓存实践

    nuxt 是基于 vue 的 ssr 解决方案,可以是使用vue语法完成前后端的同构. 然而在与传统纯字符串拼接的 ssr 方案相比,性能就没那么好了, nuxt 需要在服务端生成虚拟 dom ,然后再序列化出HTML字符串,我们常说 nodejs 的高性能指的是异步IO操作频繁的场景而非CPU操作密集的场景,毕竟 nodejs 是运行在单线程下的,在涉及到高并发的场景下,性能就会有所下降,可以考虑采用合理的缓存策略 nuxt 的缓存可以分为 组件级别缓存 , API级别缓存 以及 页面级别缓存

  • 使用Nuxt.js改造已有项目的方法

    前言 如果我们需要对现有的vue项目进行ssr改造,使用nuxt.js是一个不错的选择.这里用作例子的"现有项目"是一个"高仿"饿了么外卖APP的spa.不过我没有把全部功能开发出来(全部做出来就不得了了),只是做出几个基本界面意思意思 下面就以这个demo为小白鼠进行ssr改造 准备 现有目录 很明显,这是使用vue-cli搭建的项目 其中prod.server.js是build之后的启动文件 dataa.json文件是模拟数据,在build/dev-server

  • Nuxt.js实战详解

    一.为什么选择Nuxt.js 多数是基于webpack构建的项目,编译出来的html文件,资源文件都被打包到js中,以下图404页面代码为例.从代码中可以看出,这样的页面是不利于 搜索引擎优化(SEO, Search Engine Optimization) ,并且 内容到达时间(time-to-content) (或称之为首屏渲染时长)也有很大的优化空间.为了解决以上问题,引入了 Nuxt.js 框架. vue官网对于Nuxt.js也是很推荐的,除此之外,Nuxt.js的开发者积极活跃,版本迭

  • Nuxt.js开启SSR渲染的教程详解

    第一节:nuxt.js相关概述 nuxt.js简单的说是Vue.js的通用框架,最常用的就是用来作SSR(服务器端渲染).Vue.js是开发SPA(单页应用)的,Nuxt.js这个框架,用Vue开发多页应用,并在服务端完成渲染,可以直接用命令把我们制作的vue项目生成为静态html. 1.那服务器端渲染到底有什么好处呢? 主要的原因时SPA(单页应用)不利于搜索引擎的SEO操作,Nuxt.js适合作新闻.博客.电影.咨询这样的需要搜索引擎提供流量的项目.如果你要作移动端的项目,就没必要使用这个框

  • Nuxt.js实现校验访问浏览器类型的中间件

    前言 Nuxt.js 十分简单易用.一个简单的项目只需将 nuxt 添加为依赖组件即可. 这个需求非常常见,分享出去的页面的有时候在手机访问,有时候别人是PC打开的; 若是不是共享同一个页面的情况,就需要拦截跳转了; 当然你要共享同一个页面也可以(放大化到PC也需要添加某些CSS,也需判断设备) 思路 本质上还是校验 UA , 只是这次是从 req 拿到,而不是从客户端获取再做处理 客户端的处理的姿势 window.navigator.userAgent 服务端的处理姿势 其实基本和上面的思路一

  • Nuxt.js踩坑总结分享

    构建问题 1. 如何在 head 里面引入js文件? 背景: 在<head>标签中,以inline的形式引入flexible.js文件.本项目主要为移动端项目,引入flexible.js 实现移动端适配问题. Nuxt.js 通过 vue-meta 实现头部标签管理,通过查看文档发现,可以按照如下方式配置: // nuxt.config.js head: { script: [ { innerHTML: 'console.log("hello")', type: 'text

  • Nuxt.js之自动路由原理的实现方法

    Nuxt.js 是一个基于 Vue.js 的通用应用框架.集成了Vue 2.Vue-Router. Vuex.Vue-Meta,用于开发完整而强大的 Web 应用. 它的特性有服务端渲染.强大的路由功能,支持异步数据.HTML头部标签管理等. 今天主要介绍的就是Nuxt的特性之一,强大的路由功能.nuxt.js会根据pages目录结构自动生成vue-router模块的路由配置. nuxt源码地址: https://github.com/nuxt/nuxt.js 建议打开源码跟着文章的步骤看,第一

  • 为nuxt项目写一个面包屑cli工具实现自动生成页面与面包屑配置

    公司项目的面包屑导航是使用 element 的面包屑组件,配合一份 json 配置文件来实现的,每次写新页面都需要去写 json 配置,非常麻烦,所以写一个面包屑cli,自动生成页面.自动配置面包屑数据,提高效率:rocket: 明确目标 提供 init 命令,在一个新项目中能够通过初始化生成面包屑相关文件 能够通过命令生成页面,并且自动配置面包屑 json 数据 按照项目原有需求,能够配置面包屑是否可点击跳转 按照项目原有需求,能够配置某路径下是否展示面包屑 支持仅配置而不生成文件,能够为已存

  • 基于Python写一个番茄钟小工具

    目录 一.功能简述 二.使用到的主要模块 三.核心模块代码分析 1.番茄钟模块 2.音乐控制函数 3.main中的按钮部分 四.整体代码 一.功能简述 番茄钟即番茄工作法,番茄工作法是简单易行的时间管理工具,使用番茄工作法即一个番茄时间共30分钟,25分钟工作,5分钟休息: 特点一:番茄时长有三档 因为这个工具本人也是考虑到每个人情况不一样,不一定25分钟就适合自己,所以将番茄钟时长设为30min/45min/60min三档,自由选择 特点二:番茄统计功能 特点三:休息期间会自动播放放松音乐,当

  • 写一个含数字,拼音,汉字的验证码生成类

    本次和大家分享的是一个集成1:小写拼音 2:大写拼音 3:数字 4:汉字的验证码生成类,从标题来看感觉很普通的样子,没错的确很普通,只是这个验证码类生成的时候可以通过参数指定验证码返回格式的规则,更主要的是希望能给大家带来一定的实用性,本章例子也会有一个mvc使用验证码校验的场景,希望大家能够喜欢. » 验证码生成流程图 » 验证码生成池代码的解析 » 把验证代码画到图片上 » mvc登录操作测试验证码正确性 下面一步一个脚印的来分享: » 验证码生成流程图 首先,咋们来看一下本次分享的验证码生

  • kotlin开发cli工具小技巧详解

    目录 脚手架 开搞 开发调试 jcommander Main 函数声明 压缩模板 放飞自我 生成最终产物 结尾 脚手架 脚手架是为了保证各施工过程顺利进行而搭设的工作平台 而在程序开发过程中,每个工程或者说公司也都需要一个脚手架工具.通过脚手架命令行的形式简化开发流程,避免发生一些人为的相对低级的问题,所以这个也就是为什么叫做脚手架的原因吧. 而由于每个公司的代码规范都不同,一般情况下会主动让开发同学进行工程方面的cv操作,就是成本高并且容易出错.这也就是为什么我们打算写一些这样的工具的原因.

  • java实现一个扫描包的工具类实例代码

    前言 在很多的实际场景中,我们需要得到某个包名下面所有的类,比如我们在使用SpringMVC的时候,知道SpringMVC可以扫描指定包下的所有类,在平时的开发中,我们也有这样的场景,所以今天写一个扫描包的工具类,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 代码如下: package com.gujin.utils; import java.io.File; import java.io.FileFilter; import java.io.IOException; imp

  • nuxt.js写项目时增加错误提示页面操作

    为项目增加错误提示页面,比如后端接口没有数据或接口报错的时候,如果不增加错误提示页面的话,那接口报错的信息就会在页面中显示,这显然不利于用户体验. 实际操作过程中,可能因为各种原因无法显示正确的返回页面,比如本身这篇文章的id不存在,或者网络请求问题,这时候就需要一个错误展示页用来提示用户: nuxt.js官方也有错误提示页面的写法:传送门-> 官方错误提示页面的写法 此项目中的错误提示页面是放在layouts目录中: layouts中的error.vue页面内容为: <template>

  • node通过npm写一个cli命令行工具

    前言 如果你想写一个npm插件,如果你想通过命令行来简化自己的操作,如果你也是个懒惰的人,那么这篇文章值得一看. po主的上一篇文章介绍了定制自己的模版,但这样po主还是不满足啊,项目中我们频繁的需要新建一些页面,逻辑样式等文件,每次都手动new一个,然后复制一些基本代码进去非常的麻烦,所以就有了这篇文章.接下来就让po主为大家一步一步演示怎么做一个npm命令行插件. 注册npm账户 发布npm插件,首先肯定要有个npm帐号了,过程就不啰嗦了,走你. npm官网 有了账号后,我们通过npm in

  • 如何用C写一个web服务器之GCC项目编译

    前言 本想着接下来大概实现一下 CGI 协议,但是实现过程中被一个问题卡住了: C进程与php进程的交互数据类型问题: 在 C 进程中我准备将服务器处理后的请求数据存储在一个结构体内,然后将此结构体中的信息传给 PHP,而 PHP 进程内也会有一个全局数组与之对应,可是众所周之,结构体是 C 进程内的内存数据,是无法直接传给 PHP 使用的. 这时候我们也需要一种"协议"来解决进程数据类型的异构性.当然这个解决方案确定起来还是很简单的,无非是对C结构体进行序列化,使用xml,json,

  • 使用Node.js写一个代码生成器的方法步骤

     背景 第一次接触代码生成器用的是动软代码生成器,数据库设计好之后,一键生成后端 curd代码.之后也用过 CodeSmith , T4.目前市面上也有很多优秀的代码生成器,而且大部分都提供可视化界面操作. 自己写一个的原因是因为要集成到自己写的一个小工具中,而且使用 Node.js 这种动态脚本语言进行编写更加灵活. 原理 代码生成器的原理就是: 数据 + 模板 => 文件 . 数据 一般为数据库的表字段结构. 模板 的语法与使用的模板引擎有关. 使用模板引擎将 数据 和 模板 进行编译,编译

  • Nuxt.js实现一个SSR的前端博客的示例代码

    为什么要用Nuxt.js 公司现有的项目只有落地页是通过前端本身server读取pug文件进行服务端渲染的,当然是为了首屏加载速度以及SEO.Nuxt.js 是一个基于Vue.js的通用应用框架,预设了利用Vue.js开发服务端渲染的应用所需要的各种配置,只需要安装官方文档的要求进行开发,就可以很好的解决SSR的问题.我们以一个简单的博客为例,来实践一下Nuxt.js. 项目介绍 当前基于Nuxt.js的简化版博客,包括注册.登录.文章列表页面.文章详情页.以及用户列表页等几个页面,用户信息使用

随机推荐