為nuxt項目寫一個面包屑cli工具實現(xiàn)自動生成頁面與面包屑配置

公司項目的面包屑導(dǎo)航是使用 element 的面包屑組件,配合一份 json 配置文件來實現(xiàn)的,每次寫新頁面都需要去寫 json 配置,非常麻煩,所以寫一個面包屑cli,自動生成頁面、自動配置面包屑數(shù)據(jù),提高效率:rocket:

目前成都創(chuàng)新互聯(lián)已為上千余家的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)頁空間、網(wǎng)站托管運營、企業(yè)網(wǎng)站設(shè)計、白銀網(wǎng)站維護等服務(wù),公司將堅持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。

明確目標(biāo)

  • 提供 init 命令,在一個新項目中能夠通過初始化生成面包屑相關(guān)文件
  • 能夠通過命令生成頁面,并且自動配置面包屑 json 數(shù)據(jù)
  • 按照項目原有需求,能夠配置面包屑是否可點擊跳轉(zhuǎn)
  • 按照項目原有需求,能夠配置某路徑下是否展示面包屑
  • 支持僅配置而不生成文件,能夠為已存在的頁面生成配置
  • 能夠動態(tài)配置當(dāng)前面包屑導(dǎo)航的數(shù)據(jù)
  • …… (后續(xù)在使用中發(fā)現(xiàn)問題并優(yōu)化)

實現(xiàn)分成兩部分

  • 面包屑實現(xiàn)
  • cli 命令實現(xiàn)

面包屑實現(xiàn)

  • 在路由前置守衛(wèi) beforEach 中根據(jù)當(dāng)前路徑在配置文件中匹配到相應(yīng)的數(shù)據(jù)
  • 把這些配置存到 vuex
  • 在面包屑組件中根據(jù) vuex 中的數(shù)據(jù) v-for 循環(huán)渲染出面包屑

JSON 配置文件

json 配置文件是通過命令生成的,一個配置對象包含 name path clickable isShow 屬性

[
 {
  "name": "應(yīng)用", // 面包屑名稱(在命令交互中輸入)
  "path": "/app", // 面包屑對應(yīng)路徑(根據(jù)文件自動生成)
  "clickable": true, // 是否可點擊跳轉(zhuǎn)
  "isShow": true // 是否顯示
 },
 {
  "name": "應(yīng)用詳情",
  "path": "/app/detail",
  "clickable": true, // 是否可點擊跳轉(zhuǎn)
  "isShow": true // 是否顯示
 }
]

匹配配置文件中的數(shù)據(jù)

比如按照上面的配置文件,進入 /app/detail 時,將會匹配到如下數(shù)據(jù)

[
  {
    "name": "應(yīng)用",
    "path": "/app",
    "clickable": true,
    "isShow": true
  },
  {
    "name": "應(yīng)用",
    "path": "/app/detail",
    "clickable": true,
    "isShow": true
  }
]

動態(tài)面包屑實現(xiàn)

有時候需要動態(tài)修改面包屑數(shù)據(jù)(比如動態(tài)路由),由于數(shù)據(jù)是存在 vuex 中的,所以修改起來非常方便,只需在 vuex 相關(guān)文件中提供 mutation 即可,這些 mutation 在數(shù)據(jù)中尋找相應(yīng)的項,并改掉

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)
 }
}

根據(jù)路徑匹配相應(yīng)配置數(shù)據(jù)具體代碼

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

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

function matchBreadcrumbData (matchPath) {
 return path3Arr(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 = path3Arr(to.path)
  const toPathArrLength = toPathArr.length
  let matchPath = ''

  // 從 matched 中找出當(dāng)前路徑的路由配置
  for (let match of to.matched) {
   const matchPathArr = path3Arr(match.path)
   if (matchPathArr.length === toPathArrLength) {
    matchPath = match.path
    break
   }
  }

  const breadcrumbData = matchBreadcrumbData(matchPath)

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

面包屑組件

面包屑組件中渲染匹配到的數(shù)據(jù)

<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命令實現(xiàn)

cli命令開發(fā)用到的相關(guān)庫如下:這些就不細說了,基本上看下 README 就知道怎么用了

  • commander :命令行工具
  • boxen :在終端畫一個框
  • inquirer :命令行交互工具
  • handlebar:模版引擎

目錄結(jié)構(gòu)

lib // 存命令行文件
  |-- bcg.js
template // 存模版
  |-- breadcrumb // 面包屑配置文件與組件,將生成在項目 @/components 中
    |-- breadcrumb.config.json
    |-- index.vue
  |-- braadcrumb.js // vuex 相關(guān)文件,將生成在項目 @/store 中
  |-- new-page.vue // 新文件模版,將生成在命令行輸入的新路徑中
  |-- route.js // 路由前置守衛(wèi)配置文件,將生成在 @/plugins 中
test // 單元測試相關(guān)文件

node 支持命令行,只需在 package.json 的 bin 字段中關(guān)聯(lián)命令行執(zhí)行文件

// 執(zhí)行 bcg 命令時,就會執(zhí)行 lib/bcg.js 的代碼
{
 "bin": {
  "bcg": "lib/bcg.js"
 }
}

實現(xiàn)命令

實現(xiàn)一個 init 命令,生成相關(guān)面包屑文件(面包屑組件、 json配置文件、 前置守衛(wèi)plugin、 面包屑store)

bcg init

實現(xiàn)一個 new 命令生成文件,默認基礎(chǔ)路徑是 src/pages ,帶一個 -b 選項,可用來修改基礎(chǔ)路徑

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 生成相關(guān)文件'))
   return
  }

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

  // 如果已經(jīng)有配置就過濾掉
  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: '是否可點擊跳轉(zhuǎn)? (默認 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('生成頁面并配置面包屑,默認基礎(chǔ)路徑為 src/pages,可通過 -b 修改')
 .option('-b, --basePath <base-path>', '修改基礎(chǔ)路徑 (不要以 / 開頭)')
 .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()
}

發(fā)布 npm

開發(fā)完成后,發(fā)布到 npm,具體方法就不細說了,發(fā)布后全局安裝就能愉快的使用咯!

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。

標(biāo)題名稱:為nuxt項目寫一個面包屑cli工具實現(xiàn)自動生成頁面與面包屑配置
分享鏈接:http://www.muchs.cn/article2/iidjic.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站設(shè)計、網(wǎng)站導(dǎo)航微信小程序、面包屑導(dǎo)航、Google靜態(tài)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

小程序開發(fā)