手把手做一个基于vue-cli的组件库(下篇)
基于vue-cli4的ui组件库,上篇:如何做一个初步的组件。下篇:编写说明文档及页面优化。接上篇,开工。
GitHub源码地址:https://github.com/sq-github/sq-ui
GitHub预览地址:https://sq-github.github.io/sq-ui/dist
五、添加markdown说明文本
1、删除app.vue原有的app.vue内容,及其他一些项目创建时引用的不需要的组件。修改后app.vue如下:
<template> <div id="app"> <router-view /> </div> </template>
2、因为文档是用markdown写的,需要项目能识别markdown组件。
npm i vue-markdown-loader -D
3、修改vue.config.js 需要能识别md文件。
const path = require('path') module.exports = { // 修改 pages 入口 pages: { index: { entry: 'examples/main.js', // 入口 template: 'public/index.html', // 模板 filename: 'index.html' // 输出文件 } }, parallel: false, // 扩展 webpack 配置 chainWebpack: config => { // @ 默认指向 src 目录,这里要改成 examples // 另外也可以新增一个 ~ 指向 packages config.resolve.alias .set('@', path.resolve('examples')) .set('~', path.resolve('packages')) // 把 packages 和 examples 加入编译,因为新增的文件默认是不被 webpack 处理的 config.module .rule('js') .include.add(/packages/) .end() .include.add(/examples/) .end() .use('babel') .loader('babel-loader') .tap(options => { // 修改它的选项... return options }) config.module .rule('md') .test(/\.md/) .use('vue-loader') .loader('vue-loader') .end() .use('vue-markdown-loader') .loader('vue-markdown-loader/lib/markdown-compiler') .options({ raw: true }) } }
4、创建文档的目录及文件。
在 examples 目录下创建 docs 文件夹,在docs文件夹下创建 test1.md,文件内容如下
## tip :::tip 这是一个 tip。这是一个 tip。这是一个 tip。这是一个 tip。这是一个 tip。这是一个 tip。 ::: ## warning :::warning 这是一个 warning,**这是一个 warning,**。 这是一个 warning。 这是一个 warning,这是一个 warning,这是一个 warning,这是一个 warning。 ::: ## demo :::demo 这是一个 demo。这是一个 demo。这是一个 demo。这是一个 demo。这是一个 demo。这是一个 demo。这是一个 demo。这是一个 demo。 ```html <sq-button></sq-button> ``` :::
将 test1.md 添加进路由进行测试
在router/index.js中添加
{ path: '/test1', name: 'test1', component: () => import(/* webpackChunkName: "about" */ '../docs/test1.md') }
重新运行测试:如果没有报错,页面能正确显示 test1.md 内的文本,这一步就算成功了。
5、接下来安装其他的markdown插件
mpm i markdown-it markdown-it-container -S
6、再次修改vue.config.js文件
const path = require('path') const md = require('markdown-it')() // 引入markdown-it module.exports = { // 修改 pages 入口 pages: { index: { entry: 'examples/main.js', // 入口 template: 'public/index.html', // 模板 filename: 'index.html' // 输出文件 } }, parallel: false, // 扩展 webpack 配置 chainWebpack: config => { // @ 默认指向 src 目录,这里要改成 examples // 另外也可以新增一个 ~ 指向 packages config.resolve.alias .set('@', path.resolve('examples')) .set('~', path.resolve('packages')) // 把 packages 和 examples 加入编译,因为新增的文件默认是不被 webpack 处理的 config.module .rule('js') .include.add(/packages/) .end() .include.add(/examples/) .end() .use('babel') .loader('babel-loader') .tap(options => { // 修改它的选项... return options }) config.module .rule('md') .test(/\.md/) .use('vue-loader') .loader('vue-loader') .end() .use('vue-markdown-loader') .loader('vue-markdown-loader/lib/markdown-compiler') .options({ raw: true, preventExtract: true, // 这个加载器将自动从html令牌内容中提取脚本和样式标签 // 定义处理规则 preprocess: (MarkdownIt, source) => { // 对于代码块去除v - pre, 添加高亮样式; const defaultRender = md.renderer.rules.fence MarkdownIt.renderer.rules.fence = ( tokens, idx, options, env, self ) => { const token = tokens[idx] // 判断该 fence 是否在 :::demo 内 const prevToken = tokens[idx - 1] const isInDemoContainer = prevToken && prevToken.nesting === 1 && prevToken.info.trim().match(/^demo\s*(.*)$/) if (token.info === 'html' && isInDemoContainer) { return `<template slot="highlight"><pre v-pre><code class="html">${md.utils.escapeHtml( token.content )}</code></pre></template>` } return defaultRender(tokens, idx, options, env, self) } return source }, use: [ // :::demo **** // // ::: // 匹配:::后面的内容 nesting == 1,说明:::demo 后面有内容 // m为数组,m[1]表示 **** [ require('markdown-it-container'), 'demo', { validate: function(params) { return params.trim().match(/^demo\s*(.*)$/) }, render: function(tokens, idx) { const m = tokens[idx].info.trim().match(/^demo\s*(.*)$/) if (tokens[idx].nesting === 1) { // const description = m && m.length > 1 ? m[1] : '' // 获取正则捕获组中的描述内容,即::: demo xxx中的xxx const content = tokens[idx + 1].type === 'fence' ? tokens[idx + 1].content : '' return `<demo-block> <div slot="source">${content}</div> ${description ? `<div>${md.render(description)}</div>` : ''} ` } return '</demo-block>' } } ], [require('markdown-it-container'), 'tip'], [require('markdown-it-container'), 'warning'] ] }) } }
7、重新运行 会有一个报错 说没有<demo-block>组件,接下来只需要添加这个组件即可。在 examples/components 下添加 DemoBlock.vue,内容如下:
<template> <div class="demo-block" :class="blockClass"> <!-- 源码运行 --> <div class="source"> <slot name="source"></slot> </div> <!-- 源码 --> <div class="meta" ref="meta"> <!-- 描述 --> <div class="description" v-if="$slots.default"> <slot></slot> </div> <!-- 源码 --> <div class="highlight"> <slot name="highlight"></slot> </div> </div> <!-- 源码 显示或者隐藏 --> <div class="demo-block-control" ref="control" :class="{ 'is-fixed': fixedControl }" @click="isExpanded = !isExpanded" > <transition name="text-slide"> <span>{{ controlText }}</span> </transition> </div> </div> </template> <script> export default { data() { return { isExpanded: false, fixedControl: false, scrollParent: null } }, computed: { lang() { return this.$route.path.split('/')[1] }, blockClass() { return `demo-${this.lang} demo-${this.$router.currentRoute.path .split('/') .pop()}` }, controlText() { return this.isExpanded ? '隐藏代码' : '显示代码' }, codeArea() { return this.$el.getElementsByClassName('meta')[0] }, codeAreaHeight() { if (this.$el.getElementsByClassName('description').length > 0) { return ( this.$el.getElementsByClassName('description')[0].clientHeight + this.$el.getElementsByClassName('highlight')[0].clientHeight + 20 ) } return this.$el.getElementsByClassName('highlight')[0].clientHeight } }, watch: { isExpanded(val) { this.codeArea.style.height = val ? `${this.codeAreaHeight + 1}px` : '0' console.log(this.$el.getElementsByClassName('description').length) console.log(this.$el.getElementsByClassName('highlight')) console.log(this.codeAreaHeight) if (!val) { this.fixedControl = false this.$refs.control.style.left = '0' } } } } </script> <style lang="scss"> .demo-block { width: 60%; padding: 8px 16px; margin: auto; margin-top: 10px; border-left: solid 5px#fc297f; background-color: #f8d1db; border-radius: 3px; transition: 0.2s; &.hover { box-shadow: 0 0 8px 0 rgba(232, 237, 250, 0.6), 0 2px 4px 0 rgba(232, 237, 250, 0.5); } .meta { margin-top: 10px; background-color: #fafafa; border-radius: 8px; overflow: hidden; height: 0; transition: height 0.2s; } .description { box-sizing: border-box; border-radius: 3px; font-size: 14px; line-height: 22px; color: #666; word-break: break-word; margin: 10px; background-color: #fff; p { width: 100%; } } .demo-block-control { border-top: solid 1px #eaeefb; height: 44px; box-sizing: border-box; background-color: #fff; border-radius: 8px; text-align: center; margin-top: 10px; color: #d3dce6; cursor: pointer; position: relative; &.is-fixed { position: fixed; bottom: 0; width: 868px; } i { font-size: 16px; line-height: 44px; transition: 0.3s; &.hovering { transform: translateX(-40px); } } > span { position: absolute; transform: translateX(-30px); font-size: 14px; line-height: 44px; transition: 0.3s; display: inline-block; } &:hover { color: #fc297f; background-color: #f9fafc; } & .text-slide-enter, & .text-slide-leave-active { opacity: 0; transform: translateX(10px); } .control-button { line-height: 26px; position: absolute; top: 0; right: 0; font-size: 14px; padding-left: 5px; padding-right: 25px; } } } </style>
然后在main.js中引用组件
import DemoBlock from './components/DemoBlock.vue'
Vue.component('DemoBlock', DemoBlock)
8、现在应该能看到demo组件的效果了,后面接下来需要添加tip和warning的样式
添加一个公共scss文件,在assets下新建common.scss文件
html, body { margin: 0; padding: 0; height: 100%; background-color: #17171d; font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', SimSun, sans-serif; font-weight: 400; -webkit-font-smoothing: antialiased; -webkit-tap-highlight-color: transparent; &.is-component { overflow: hidden; } } #app { height: 100%; &.is-component { overflow-y: hidden; .main-cnt { padding: 0; margin-top: 0; height: 100%; min-height: auto; } .headerWrapper { position: fixed; width: 100%; left: 0; top: 0; z-index: 1500; .container { padding: 0; } } } } a { color: #409eff; text-decoration: none; } code { padding: 0 4px; border: 1px solid #eaeefb; border-radius: 4px; } button, input, select, textarea { font-family: inherit; font-size: inherit; line-height: inherit; color: inherit; } .hljs { line-height: 20px; font-family: Menlo, Monaco, Consolas, Courier, monospace; font-size: 12px; padding: 10px 24px 18px 24px; border: solid 1px #eaeefb; border-radius: 4px; -webkit-font-smoothing: auto; } .main-cnt { margin-top: -80px; padding: 80px 0 340px; box-sizing: border-box; min-height: 100%; } #app { h2 { font-size: 28px; color: #fc297f; margin: 0; } h3 { font-size: 22px; } h2, h3, h4, h5 { width: 60%; margin: auto; margin-top: 10px; font-weight: normal; color: #fc297f; a { display: none; } &:hover a { opacity: 0.4; } } p { width: 60%; margin: auto; padding: 10px; font-size: 16px; color: #d3aec2; line-height: 30px; } .tip { width: 60%; margin: auto; margin-top: 10px; padding: 8px 16px; background-color: #ecf8ff; border-radius: 4px; border-left: #1b9ae4 5px solid; p { width: 100%; } code { background-color: rgb(255, 255, 255); color: #445368; } } .warning { width: 60%; margin: auto; margin-top: 10px; padding: 8px 16px; background-color: #fff6f7; border-radius: 4px; border-left: rgb(252, 122, 2) 5px solid; p { width: 100%; } code { background-color: rgba(255, 255, 255, 0.7); color: #445368; } } } @media (max-width: 1140px) { .container, .page-container { width: 100%; } } @media (max-width: 768px) { .container, .page-container { padding: 0 20px; } #app.is-component .headerWrapper .container { padding: 0 12px; } }
在main.js引入
import './assets/common.scss' // 公共样式
9、现在效果应该都出来了,可以给代码添加高亮,使其更漂亮。
npm i highlight.js -S
再在main.js中添加如下配置,然后代码就能语法高亮了,perfact!
import hljs from 'highlight.js' import 'highlight.js/styles/monokai-sublime.css' router.afterEach(() => { Vue.nextTick(() => { const blocks = document.querySelectorAll('pre code:not(.hljs)') Array.prototype.forEach.call(blocks, hljs.highlightBlock) }) })
10、最后有个小问题,如果有eslint检查的话,在md文件中添加vue模板文件时会报错,比如下面这种:
:::demo 这里贴出的是源码,刷新可重播。 ```html <template> <div> <div>测试</div> </div> </template> <script></script> <style></style> ``` :::
解决方法是:在跟目录添加一个.eslintignore文件,目录和内容如下:
*.sh node_modules lib coverage *.md *.scss *.woff *.ttf aui-web build
六、现在说明文件格式已经弄好了,类似下面这种效果,最后一步就是将路由和左侧的导航菜单弄好。
1、添加路由配置文件routerCon.json
[ { "name": "test", "groups": [ { "groupName": "测试组件", "list": [ { "path": "/test1", "title": "测试1" }, { "path": "/test2", "title": "测试2" } ] } ] } ]
2、修改路由的index.js文件,倒数第二行的路由重定向 redirect ,可以自己定义。
// export default router import Vue from 'vue' import Router from 'vue-router' import navConfig from './routerCon' Vue.use(Router) const docsRoutefun = navConfig => { const route = [] navConfig.forEach(item => { if (item.groups) { item.groups.forEach(group => { group.list.forEach(nav => { route.push({ path: nav.path, name: nav.name, component: r => require.ensure([], () => r(require(`@/docs${nav.path}.md`))) }) }) }) } else { route.push({ path: item.path, name: item.name, component: r => require.ensure([], () => r(require(`@/docs${item.path}.md`))) }) } }) return route } const docsRoute = docsRoutefun(navConfig) export default new Router({ mode: 'history', base: process.env.BASE_URL, routes: [{ path: '/', redirect: '/test1' }, ...docsRoute] })
3、添加左侧菜单组件menuCom.vue
<template> <div id="app"> <div class="main"> <!-- sidebar --> <div class="sidebar"> <menuCom :data="navsData"></menuCom> </div> <div class="view page-container"> <router-view></router-view> </div> </div> </div> </template> <script> import menuCom from './components/menuCom' import navsData from './router/routerCon.json' export default { components: { menuCom }, data() { return { navsData } } } </script> <style lang="scss"> html, body { margin: 0; height: 100%; background-color: #17171d; } .header { top: 0; height: 60px; background-color: #121217; } .footer { position: absolute; height: 60px; background-color: antiquewhite; width: 100%; } .footer { bottom: 0; } .main { background-color: #17171d; position: absolute; bottom: 0; top: 60px; width: 100%; overflow: hidden; } .sidebar, .view { overflow: auto; } .sidebar { float: left; height: 100%; width: 200px; padding: 10px 0 10px 0; border-right: #000000 3px solid; } .view { padding: 0 0 80px 0; float: left; height: calc(100% - 50px); width: calc(100% - 203px); overflow: auto; } </style>
最后:赶紧npm run lib 加 npm publish,引用看看效果吧,别忘了修改发布版本哟。
总算码完了,期间看了一些博文和源码,有些文章不太完整,踩了一些坑。现在自己从头总结,感觉算是尽力在这篇中将详细的步骤和源码贴出来了,主要是想分享交流,互相避坑,如有不足,希望大家交流指正。
如果需要完成开头图片那种效果,页头布局、logo以及其他的组件都放在github的源码里面了。如果觉得还有趣,不妨star一下,十分感谢。
参考项目链接:
https://github.com/xiaolannuoyi/yuan-ui
https://segmentfault.com/a/1190000018310478
https://blog.csdn.net/qq_31126175/article/details/100527322?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522158788190919725247652639%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.57644%2522%257D&request_id=158788190919725247652639&biz_id=0&utm_source=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-7
伸手摘星,即使徒劳无功也不致满手污泥。