手把手做一个基于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
伸手摘星,即使徒劳无功也不致满手污泥。

浙公网安备 33010602011771号