基于vite2+Vue3編寫一個在線幫助文檔工具
提起幫助文檔,想必大家都會想到 VuePress等,我也體驗了一下,但是感覺和我的思路不太一樣,我希望的是那種可以直接在線編輯文檔,然后無需編譯就可以直接發(fā)布的方式,另外可以在線寫(修改)代碼并且運行的效果。
VuePress 是“靜態(tài)網(wǎng)站生成器”,需要我們自行編寫文檔,然后交給VuePress變成網(wǎng)站,VuePress 并沒有提供編寫環(huán)境,我知道有很多編寫 Markdown 的方式,但是我還是喜歡編寫、瀏覽合為**“一體”**的方式。
似乎沒有,那么 —— 自己動手豐衣足食吧,開干!
技術棧
- vite: ^2.7.0
- vue: ^3.2.23
- axios: ^0.25.0 獲取json格式的配置和文檔
- element-plus: ^2.0.2 UI庫
- nf-ui-elp": ^0.1.0 二次封裝的UI庫
- @element-plus/icons-vue: ^0.2.4 圖標
- @kangc/v-md-editor:"^2.3.13 md 編輯器
- vite-plugin-prismjs: ^0.0.8 代碼高亮
- nf-state": ^0.2.4 狀態(tài)管理
- nf-web-storage": ^0.2.3 訪問 indexedDB
建立庫項目(@naturefw/press-edit)實現(xiàn)文檔的編寫、瀏覽功能
首先使用 vite2 建立一個 Vue3 的項目:
- 安裝 elementPlus 實現(xiàn)頁面效果;
- 安裝 v-md-editor 實現(xiàn) Markdown 的編輯和顯示;
- 安裝 @naturefw/storage 操作 indexedDB ,實現(xiàn)幫助文檔的存儲;
- 安裝 @naturefw/nf-state 實現(xiàn)狀態(tài)管理;
- 安裝axios 用于加載 json文件,實現(xiàn)導入功能。
- 用node寫一個后端API,實現(xiàn)寫入json文件的功能。
注意:庫項目需要安裝以上插件,幫助文檔項目只需要安裝 @naturefw/press-edit 即可。
基本功能就是這樣,心急的可以先看在線演示和源碼。
編輯頁面
瀏覽頁面
兩個狀態(tài):編輯和瀏覽
一開始做了兩個項目,分別實現(xiàn)編輯文檔和顯示文檔的功能,但是后來發(fā)現(xiàn),內部代碼大部分是相同的,維護的時候有點麻煩,所以改為在編輯文檔的項目里加入“瀏覽”的狀態(tài),然后設置切換的功能,這樣便于內部代碼的維護,以后成熟了可能會分為兩個單獨的項目。
編輯狀態(tài)的功能
- 菜單維護
- 文檔維護
- 文檔展示
- 導入導出
- 在線編寫/執(zhí)行代碼
我喜歡在線編輯的方式,這樣更省心,于是我用 el-menu 實現(xiàn)導航和左側的菜單,然后加上了維護功能。 使用 v-md-editor 實現(xiàn) Markdown 的編輯和顯示。 然后用node寫了一個后端API,實現(xiàn)保存 json文件的功能,這樣就完美了。
瀏覽狀態(tài)的功能
- 導航
- 菜單
- 文檔展示
- 執(zhí)行代碼
就是在編輯狀態(tài)的功能的基礎上,去掉一些功能?;蛘咂鋵嵖梢苑催^來思考。
實現(xiàn)導航
首先參考 VuePress 設置一個json文件,用于加載和保存網(wǎng)站信息、導航信息。
/public/docs/.nfpress/project.json
{ "projectId": "1000", "title": "nf-press-edit !", "description": "這是一個在線編輯、展示文檔的小工具", "navi": [ { "naviId": "1010", "text": "指南", "link": "menu" }, { "naviId": "1020", "text": "組件", "link": "menu" }, { "naviId": "1380", "text": "Gitee", "link": "https://gitee.com/nfpress/nf-press-edit" }, { "naviId": "1390", "text": "在線演示", "link": "https://nfpress.gitee.io/nf-press-edit/" }, { "naviId": "1395", "text": "我要提意見", "link": "https://gitee.com/nfpress/nf-press-edit/issues" } ] }
- projectId:項目ID,可以用于區(qū)分不同的幫助文檔項目。
- navi: 存放導航項。
- naviId: 關聯(lián)到菜單。
- text: 導航上顯示的文字。
- link: 連接方式或鏈接地址。menu:表示要打開對應的菜單;URL:在新頁面里打開連接。
然后做一個組件,用 el-menu 綁定數(shù)據(jù)渲染出來即可實現(xiàn)導航效果。
/lib/navi/navi.vue
<el-menu :default-active="activeIndex2" class="el-menu-demo" mode="horizontal" v-bind="$attrs" :background-color="backgroundColor" @select="handleSelect" > <el-menu-item v-for="(item, index) in naviList" :key="index" :index="item.naviId" > {{item.text}} </el-menu-item> </el-menu>
可以是多級的導航,暫時沒有實現(xiàn)在線維護功能。
import { ref } from 'vue' import { ElMenu, ElMenuItem } from 'element-plus' import { state } from '@naturefw/nf-state' const props = defineProps({ 'background-color': { // 默認背景色 type: String, default: '#ece5d9' }, itemProps: Object }) // 獲取狀態(tài)和導航內容 const { current, naviList } = state // 激活第一個導航項 const activeIndex2 = ref(naviList[0].naviId) const handleSelect = (key, keyPath) => { const navi = naviList.find((item) => item.naviId === key) if (navi.link === 'menu') { // 打開菜單 current.naviId = key } else { // 打開連接 window.open(navi.link, '_blank') } }
@naturefw/nf-state
自己寫的一個輕量級狀態(tài)管理,可以當做大號 reactive 來使用,通過狀態(tài)管理加載 project.json 然后綁定渲染。
naviList
導航列表,由狀態(tài)管理加載。
current
當前激活的各種信息,比如“current.naviId”表示激活的導航項。
實現(xiàn)菜單
和導航類似,只是需要增加兩個功能:n級分組和維護。
首先參考 VuePress 設置一個json文件,保存菜單信息。
/public/docs/.nfpress/menu.json
[ { "naviId": "1010", "menus": [ { "menuId": "110100", "text": "介紹", "description": "描述", "icon": "FolderOpened", "children": [] }, { "menuId": "111100", "text": "快速上手", "description": "描述", "icon": "FolderOpened", "children": [ { "menuId": 111120, "text": "編輯文檔項目", "description": "", "icon": "UserFilled", "children": [] }, { "menuId": 111130, "text": "展示文檔項目", "description": "", "icon": "UserFilled" } ] } ], "ver": 1.6 }, { "naviId": "1020", "menus": [ { "menuId": "21000", "text": "導航(docNavi)", "description": "描述", "icon": "Star", "children": [] } ], "ver": 1.5 } ]
- naviId: 關聯(lián)導航項ID,可以是數(shù)字,也可以是其他字符。需要和導航項ID對應。
- menus: 導航項對應的菜單項集合。
- menuId: 菜單項ID,關聯(lián)一個文檔,可以是數(shù)字或者英文。
- text: 菜單項名稱。
- description: 描述,考慮以后用于查詢。
- icon: 菜單使用的圖標名稱。
- children: 子菜單項目,沒有的話可以去掉。
- ver: 版本號,便于更新文檔。
然后用 el-menu 綁定數(shù)據(jù)渲染,因為要實現(xiàn)n級分組,所以做一個遞歸組件實現(xiàn)n級菜單的效果。
實現(xiàn)n級分組菜單
做一個遞歸組件實現(xiàn)n級分組的功能:
/lib/menu/menu-sub-edit.vue
<template v-for="(item, index) in subMenu"> <!--樹枝--> <template v-if="item.children && item.children.length > 0"> <el-sub-menu :key="item.menuId + '_' + index" :index="item.menuId" style="vertical-align: middle;" > <template #title> <div style="display:inline;width: 100%;"> <component :is="$icon[item.icon]" style="width: 1.5em; height: 1.5em; margin-right: 8px;vertical-align: middle;" > </component> <span>{{item.text}}</span> </div> </template> <!--遞歸子菜單--> <my-sub-menu2 :subMenu="item.children" :dialogAddInfo="dialogAddInfo" :dialogModInfo="dialogModInfo" /> </el-sub-menu> </template> <!--樹葉--> <el-menu-item v-else :index="item.menuId" :key="item.menuId + 'son_' + index" > <template #title> <div style="display:inline;width: 100%;"> <span style="float: left;"> <component :is="$icon[item.icon]" style="width: 1.5em; height: 1.5em; margin-right: 8px;vertical-align: middle;" > </component> <span >{{item.text}}</span> </span> </div> </template> </el-menu-item> </template>
import { ElMenuItem, ElSubMenu } from 'element-plus' // 展示子菜單 - 遞歸 import mySubMenu2 from './menu-sub.vue' const props = defineProps({ subMenu: Array, // 要顯示的菜單,可以n級 dialogAddInfo: Object, // 添加菜單 dialogModInfo: Object // 修改菜單 })
- subMenu 要顯示的子菜單項
- dialogAddInfo 添加菜單的信息
- dialogModInfo 修改菜單的信息
實現(xiàn)菜單的維護功能
這個就比較簡單了,做個表單實現(xiàn)菜單的增刪改即可,篇幅有限跳過。
實現(xiàn) Markdown 的編輯
使用 v-md-editor 實現(xiàn) Markdown 的編輯和展示,首先該插件非常好用,其次支持VuePress的主題。
建立 /lib/md/md-edit.vue 實現(xiàn)編輯 Markdown 的功能:
<v-md-editor :toolbar="toolbar" left-toolbar="undo redo clear | tip emoji code | h bold italic strikethrough quote | ul ol table hr | link image | save | customToolbar" :include-level="[1, 2, 3, 4]" v-model="current.docInfo.md" :height="editHeight + 'px'" @save="mySave" > </v-md-editor>
import { watch,ref } from 'vue' import { ElMessage, ElRadioGroup, ElRadioButton } from 'element-plus' import mdController from '../service/md.js' // 狀態(tài) import { state } from '@naturefw/nf-state' // 獲取當前激活的信息 const current = state.current // 文檔的加載和保存 const { loadDocById, saveDoc } = mdController() // 可見的高度 const editHeight = document.documentElement.clientHeight - 200 // 單擊 保存 按鈕,實現(xiàn)保存功能 const mySave = (text, html) => { saveDoc(current) } // 定時保存 let timeout = null let isSaved = true const timeSave = () => { if (isSaved) { // 保存過了,重新計時 isSaved = false } else { return // 有計時,退出 } timeout = setTimeout(() => { // 保存文檔 saveDoc(current).then(() => { ElMessage({ message: '自動保存文檔成功!', type: 'success', }) }) isSaved = true }, 10000) } // 定時保存文檔 watch(() => current.docInfo.md, () => { timeSave() }) // 根據(jù)激活的菜單項,加載對應的文檔 watch( () => current.menuId, async (id) => { const ver = current.ver loadDocById(id, ver).then((res) => { // 找到了文檔 Object.assign(current.docInfo, res) }).catch((res) => { // 沒有文檔 Object.assign(current.docInfo, res) }) })
- mdController 實現(xiàn)文檔的增刪改查的controller
- timeSave 定時保存文檔,避免忘記點保存按鈕
是不是挺簡單的。
實現(xiàn)在線編寫代碼并且運行的功能
因為是基于Vue3建立的項目,而且也是為了寫vue3相關的幫助文檔,那么就有一個很實用的要求:在線寫代碼并且可以運行。
個人感覺這個功能還是很實用的,我知道有第三方網(wǎng)站提供了這種功能,但是網(wǎng)速有點慢,另外有一種大炮打蚊子的感覺,我只需要實現(xiàn)簡單的代碼演示。
于是我基于 vue 的 defineAsyncComponent 寫了一個簡單版的在線編寫代碼且運行的功能:
/lib/runCode/run.vue
<div style="padding: 5px; border: 1px solid #ccc!important;"> <async-comp></async-comp> </div>
import { defineAsyncComponent, ref, reactive,... // 其他常用的vue內置指令 } from 'vue' // 使用 eval編譯js代碼 const mysetup = ` (function setup () { {[code]} }) ` // 通過屬性傳入需要運行的代碼和模板 const props = defineProps({ code: { type: Object, default: () => { return { js: '', template: '', style: '' } } } }) const code = props.code // 使用 defineAsyncComponent 讓代碼運行起來 const AsyncComp = defineAsyncComponent( () => new Promise((resolve, reject) => { resolve({ template: code.template, // 設置模板 style: [code.style], // 大概是樣式設置,但是好像沒啥效果 setup: (props, ctx) => { const tmpJs = code.js // 獲取js代碼 let fun = null // 轉換后的函數(shù) try { if (tmpJs) fun = eval(mysetup.replace('{[code]}', tmpJs)) // 用 eval 把 字符串 變成 函數(shù) } catch (error) { console.error('轉換出現(xiàn)異常:', error) } const re = typeof fun === 'function' ? fun : () => {} return { ...re(props, ctx) // 運行函數(shù),解構返回對象 } } }) }) )
- defineAsyncComponent
實用 defineAsyncComponent 加載組件,需要設置三個部分:模板、setup和style。
- template: 字符串形式,可以直接傳入
- setup: js代碼,可以用eval的方式進行動態(tài)編譯。
- style: 可以設置樣式。
這樣即可讓在線編寫的代碼運行起來,當然功能有限,只能用于一些簡單的代碼演示。
導出
以上這些功能都是基于 indexedDB 進行的,想要發(fā)布的話,需要先導出為json文件。
因為瀏覽器里不能直接寫文件,所以需要使用折中的方式:
- 復制粘貼
- 下載
- 導出
復制粘貼
這個簡單,用文本域顯示json即可。
下載
使用 chrome 瀏覽器提供的下載功能下載文件。
const uri = 'data:text/json;charset=utf-8,\ufeff' + encodeURIComponent(show.navi) //通過創(chuàng)建a標簽實現(xiàn) var link = document.createElement("a") link.href = uri //對下載的文件命名 link.download = fileName document.body.appendChild(link) link.click() document.body.removeChild(link)
以上介紹的是內部原理,如果只是想簡單使用的話,可以跳過,直接看下面的介紹。
用后端寫文件
以上兩種都不太方便,于是用node做了個簡單的后端API,用于實現(xiàn)寫入json文件的功能。
代碼放在了 api文件夾里,可以使用 yarn api
運行。當然需要在 package.json 里做一下設置。
"scripts": { "dev": "vite", "build": "vite build --mode project", "lib": "vite build --mode lib", "serve": "vite preview", "api": "node api/server.js" },
實現(xiàn)一個幫助文檔的項目
上面介紹的是庫項目的基本原理,我們要做幫助文檔的時候,并不需要那么復雜。
使用 vite2 建立一個vue3的項目,然后安裝 @naturefw/press-edit,使用提供的組件即可方便的實現(xiàn)。
main.js
首先需要在 main.js 里面做一些設置。
import { createApp } from 'vue' import App from './App.vue' // 設置 axios 的 baseUrl const baseUrl = (document.location.host.includes('.gitee.io')) ? '/doc-ui-core/' : '/' // 輕量級狀態(tài) // 設置 indexedDB 數(shù)據(jù)庫,存放文檔的各種信息。 import { setupIndexedDB, setupStore } from '@naturefw/press-edit' // 初始化 indexedDB 數(shù)據(jù)庫 setupIndexedDB(baseUrl) // UI庫 import ElementPlus from 'element-plus' // import 'element-plus/lib/theme-chalk/index.css' // import 'dayjs/locale/zh-cn' import zhCn from 'element-plus/es/locale/lang/zh-cn' // 二次封裝 import { nfElementPlus } from '@naturefw/ui-elp' // 設置icon import installIcon from './icon/index.js' // 設置 Markdown 的配置函數(shù) import setMarkDown from './main-md.js' // 主題 import vuepressTheme from '@kangc/v-md-editor/lib/theme/vuepress.js' const { VueMarkdownEditor, // Markdown 的編輯器 VMdPreview // Markdown 的瀏覽器 } = setMarkDown(vuepressTheme) const app = createApp(App) app.config.globalProperties.$ELEMENT = { locale: zhCn, size: 'small' } app.use(setupStore) // 狀態(tài)管理 .use(nfElementPlus) // 二次封裝的組件 .use(installIcon) // 注冊全局圖標 .use(ElementPlus, { locale: zhCn, size: 'small' }) // UI庫 .use(VueMarkdownEditor) // markDown編輯器 .use(VMdPreview) // markDown 顯示 .mount('#app')
- baseUrl: 根據(jù)發(fā)布平臺的情況進行設置,比如這里需要設置為:“/doc-ui-core/”
- setupIndexedDB: 初始化 indexedDB 數(shù)據(jù)庫
- setupStore: 設置狀態(tài)
- element-plus:element-plus 可以不掛載,但是css需要 import 進來,這里采用CDN的方式引入。
- nfElementPlus: 二次封裝的組件,便于實現(xiàn)增刪改查。
- setMarkDown: 加載 v-md-editor ,以及需要的插件。
- vuepressTheme: 設置主題。
設置 Markdown
因為 v-md-editor 相關設置比較多,所以設置了一個單獨文件進行管理:
/src/main-md.js
// Markdown 編輯器 import VueMarkdownEditor from '@kangc/v-md-editor' import '@kangc/v-md-editor/lib/style/base-editor.css' // 在這里引入,不被識別? // import vuepressTheme from '@kangc/v-md-editor/lib/theme/vuepress.js' import '@kangc/v-md-editor/lib/theme/style/vuepress.css' // 代碼高亮 import Prism from 'prismjs' // emoji import createEmojiPlugin from '@kangc/v-md-editor/lib/plugins/emoji/index' import '@kangc/v-md-editor/lib/plugins/emoji/emoji.css' // 流程圖 // import createMermaidPlugin from '@kangc/v-md-editor/lib/plugins/mermaid/cdn' // import '@kangc/v-md-editor/lib/plugins/mermaid/mermaid.css' // todoList import createTodoListPlugin from '@kangc/v-md-editor/lib/plugins/todo-list/index' import '@kangc/v-md-editor/lib/plugins/todo-list/todo-list.css' // 代碼行號 import createLineNumbertPlugin from '@kangc/v-md-editor/lib/plugins/line-number/index'; // 高亮代碼行 import createHighlightLinesPlugin from '@kangc/v-md-editor/lib/plugins/highlight-lines/index' import '@kangc/v-md-editor/lib/plugins/highlight-lines/highlight-lines.css' // 復制代碼 import createCopyCodePlugin from '@kangc/v-md-editor/lib/plugins/copy-code/index' import '@kangc/v-md-editor/lib/plugins/copy-code/copy-code.css' // markdown 顯示器 import VMdPreview from '@kangc/v-md-editor/lib/preview' // import '@kangc/v-md-editor/lib/style/preview.css' /** * 設置 Markdown 編輯器 和瀏覽器 * @param {*} vuepressTheme * @returns */ export default function setMarkDown (vuepressTheme) { // 設置 vuePress 主題 VueMarkdownEditor.use(vuepressTheme, { Prism, extend(md) { // md為 markdown-it 實例,可以在此處進行修改配置,并使用 plugin 進行語法擴展 // md.set(option).use(plugin); }, } ) // 預覽 VMdPreview.use(vuepressTheme, { Prism, extend(md) { // md為 markdown-it 實例,可以在此處進行修改配置,并使用 plugin 進行語法擴展 // md.set(option).use(plugin); }, } ) // emoji VueMarkdownEditor.use(createEmojiPlugin()) // 流程圖 // VueMarkdownEditor.use(createMermaidPlugin()) // todoList VueMarkdownEditor.use(createTodoListPlugin()) // 代碼行號 VueMarkdownEditor.use(createLineNumbertPlugin()) // 高亮代碼行 VueMarkdownEditor.use(createHighlightLinesPlugin()) // 復制代碼 VueMarkdownEditor.use(createCopyCodePlugin()) // 預覽的插件 VMdPreview.use(createEmojiPlugin()) VMdPreview.use(createTodoListPlugin()) VMdPreview.use(createLineNumbertPlugin()) VMdPreview.use(createHighlightLinesPlugin()) VMdPreview.use(createCopyCodePlugin()) return { VueMarkdownEditor, VMdPreview } }
不多介紹了,可以根據(jù)需要選擇插件。
布局
在App.vue文件里面進行整體布局
<el-container> <el-header> <!--導航--> <div style="float: left;"> <!--寫網(wǎng)站logo、標題等--> <h1>nf-press</h1> </div> <div style="float: right;min-width: 100px;height: 60px;padding-top: 13px;"> <!--寫網(wǎng)站logo、標題等--> <el-switch v-model="$state.current.isView" v-bind="itemProps"></el-switch> </div> <div style="float: right;min-width: 600px;height: 60px;"> <!--網(wǎng)站導航--> <doc-navi ></doc-navi> </div> </el-header> <el-container> <!--左側邊欄--> <el-aside width="330px"> <!--菜單--> <doc-menu ></doc-menu> </el-aside> <el-main> <!--文檔區(qū)域--> <component :is="docControl[$state.current.isView]" /> </el-main> </el-container> </el-container>
import { reactive, defineAsyncComponent } from 'vue' import { ElHeader, ElContainer ,ElAside, ElMain } from 'element-plus' import { docMenu, docNavi, config } from '@naturefw/press-edit' // 菜單 導航 import docView from './views/doc.vue' // 顯示文檔 // 加載菜單子控件 const docControl = { true: docView, false: defineAsyncComponent(() => import('./views/main.vue')) // 修改文檔 } const itemProps = reactive({ 'inline-prompt': true, 'active-text': '看', 'inactive-text': '寫', 'active-color': '#378FEB', 'inactive-color': '#EA9712' })
- $state:全局狀態(tài),$state.current.isView 設置是否是瀏覽狀態(tài)。
- doc-navi:導航組件
- doc-menu:菜單組件
- docControl:根據(jù)狀態(tài)選擇加載顯示組件或者編輯組件的字典。
這種方式雖然有點麻煩,但是比較靈活,可以根據(jù)需要進行各種靈活設置,比如添加版權信息、備案信息、廣告等內容。
導航、菜單、編輯和瀏覽
直接使用組件實現(xiàn),比較簡單不搬運了,直接看源碼即可。
打包發(fā)布與版本管理
需要打包的情況分為兩種:第一次打包、修改代碼(非在線編輯的代碼)后打包。
如果只是文檔內容有變化的話,只需要直接上傳json文件即可,不需要再次打包。
內置了一個簡單的版本管理功能,可以通過 ver.json文件里的版本號實現(xiàn)更新功能。
以上就是基于vite2+Vue3編寫一個在線幫助文檔工具的詳細內容,更多關于vite2 Vue3幫助文檔的資料請關注腳本之家其它相關文章!
相關文章
vue封裝自定義指令之動態(tài)顯示title操作(溢出顯示,不溢出不顯示)
這篇文章主要介紹了vue封裝自定義指令之動態(tài)顯示title操作(溢出顯示,不溢出不顯示),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11Vue3+TypeScript實現(xiàn)遞歸菜單組件的完整實例
Vue.js中的遞歸組件是一個可以調用自己的組件,遞歸組件一般用于博客上顯示評論,形菜單或者嵌套菜單,文章主要給大家介紹了關于Vue3+TypeScript實現(xiàn)遞歸菜單組件的相關資料,需要的朋友可以參考下2021-08-08vscode 配置vue+vetur+eslint+prettier自動格式化功能
這篇文章主要介紹了vscode 配置vue+vetur+eslint+prettier自動格式化功能,本文通過實例代碼圖文的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03vue項目如何讀取本地json文件數(shù)據(jù)實例
很多時候我們需要從本地讀取JSON文件里面的內容,這篇文章主要給大家介紹了關于vue項目如何讀取本地json文件數(shù)據(jù)的相關資料,需要的朋友可以參考下2023-06-06