爲nuxt項目寫一個麪包屑cli工具實現自動生成頁面與麪包屑配置

這篇文章主要介紹了爲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,具體方法就不細說了,發佈後全局安裝就能愉快的使用咯!

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持神馬文庫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章