UI 框架-完善官网

虽然现在官方文档基本快要完工了,但是也太难看了

让我们把它变得好看一些吧


markdown

如果用原生 html 慢慢写文档,也不太好控制排版了

我决定使用 md 来写文档

新建 src/markdown 目录,在里面新建 install.md 等三篇文档,然后写完整先

但是 vue 默认不支持 md,要想个办法让它支持

我们可以自己写一个插件,告诉 vue 如何识别 md 文件

这里直接给出插件代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// plugins/md.ts
import path from 'path'
import fs from 'fs'
import marked from 'marked'
const mdToJs = str => {
const content = JSON.stringify(marked(str))
return `export default ${content}`
}

export function md() {
return {
configureServer: [
async ({ app }) => {
app.use(async (ctx, next) => {
if (ctx.path.endsWith('.md')) {
ctx.type = 'js'
const filePath = path.join(process.cwd(), ctx.path)
ctx.body = mdToJs(fs.readFileSync(filePath).toString())
} else {
await next()
}
})
},
],
transforms: [{
test: context => context.path.endsWith('.md'),
transform: ({ code }) => mdToJs(code)
}]
}
}

可以照抄,放在项目根目录下的 plugins/md.ts 就行

注意到,这个插件依赖 marked 这个 npm 库,记得先安装

1
npm install marked --save

不然就会在试图 run 的时候一直报无法识别 md 文件的静态错误

然后也是在项目根目录下,新建文件 vite.config.ts

1
2
3
4
5
import { md } from "./plugins/md";

export default {
plugins: [md()],
};

通过在 vite 初始化的时候,导入额外的插件(我们自己写的 md 插件),来识别 markdown 文件

最后修改一下三篇文档的视图,以 install.vue 为例

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<article v-html="md"></article>
</template>
<script lang="ts">
import { ref } from "vue";
export default {
setup() {
const md = ref(null);
import('../markdown/install.md').then((res) => (md.value = res.default));
return { md };
},
};
</script>

因为 vue 不能直接识别 md,所以不可以通过静态 import 来导入,只能通过动态 import

而动态 import 有以下两个特点

  1. 是异步的,必须要使用有监听的对象,才能自动重渲染

  2. 取回的内容,根据 export 的字段而定

    默认 export default 的情况下,就必须使用 .default 来取值才可以获得实际内容

最后,使用 v-html 绑定到模板即可

github markdown 样式

现在虽然正常引入 md 了,但是没有排版,还是丑

我们可以使用 github-markdown-css 这个库来获取样式表

1
npm install github-markdown-css --save

安装完成后,在 main.ts 中引入

1
import 'github-markdown-css'

最后,找到放置文档的地方,赋予 class="markdown-body" 即可适用 github markdown 样式

1
<article v-html="md" class="markdown-body"></article>

多组样例

一个 UI 库,每个组件不可能只有一个用例

所以我们要给每个组件都配多个用例,但是为了方便管理,不能写在一起

所以现在,新建 src/components/examples,然后把已有的几个组件样例都抽取为 [componentName][index].example.vue,再引入到文档页中

以 Button 为例,现在原本的 src/components/Button.vue,在原本之基础上新增了一个 src/components/example/Button/Button1.example.vue

1
2
3
4
5
6
7
8
9
10
11
12
// src/components/example/Button/Button1.example.vue
<template>
<laby-button>测试</laby-button>
</template>
<script lang="ts">
import LabyButton from "../../../lib/Button.vue";
export default {
components: {
LabyButton,
},
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/components/Button.vue
<template>
<div>Button 文档</div>
<laby-card>
<Example />
</laby-card>
</template>
<script lang="ts">
import LabyCard from "../lib/Card.vue";

import Example from "./example/Button/Button1.example.vue";
export default {
components: {
Example,
LabyCard,
},
};
</script>

使用我们制作好的 LabyCard 来充当容器

如上,把所有用例都拆分开

展示代码

好的 UI 库,不仅会给用户展示例,还会给出这个例子所使用的代码

可是怎么获得代码呢?

我们可以在 vite 初始化的时候做手脚——修改 vite.config.ts 即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// vite.config.ts
// @ts-nocheck
import { md } from "./plugins/md";
import fs from 'fs'
import { baseParse } from '@vue/compiler-core'

export default {
plugins: [md()],
vueCustomBlockTransforms: {
example: (options) => {
const { code, path } = options
const file = fs.readFileSync(path).toString()
const parsed = baseParse(file).children.find(n => n.tag === 'example') // 解析包含example标签的vue文件
const title = parsed.children[0].content
const main = file.split(parsed.loc.source).join('').trim()
return `export default function (Component) {
Component.__sourceCode = ${JSON.stringify(main)
}
Component.__sourceCodeTitle = ${JSON.stringify(title)}
}`.trim()
}
}
};

可以使用如上代码,在 vite 初始化的时候,解析每个包含 <example> 标签的 vue 文件,将其 example 标签内的内容作为标题,其余部分作为源代码,附加到每个 vue 文件的 export 上

并且,因为我们不希望能被用户访问到,所以最好是使用双下划线开头的变量名,标识源代码和源代码之标题

注意,因为我们使用的是 typescript,在类型中可能不存在某字段的时候,会引发 ts 静态报错

可以通过 // @ts-nocheck 注释,来忽略静态报错

现在,我们就可以展示每个例子中的代码了

src/component/Button.vue 为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/component/Button.vue
<template>
<div>Button 文档</div>
<laby-card>
<Example />
<code>
<pre>{{ Example.__sourceCode }}</pre>
</code>
</laby-card>
</template>
<script lang="ts">
import LabyCard from "../lib/Card.vue";

import Example from "./example/Button/Button1.example.vue";
export default {
components: {
Example,
LabyCard,
},
setup() {
return { Example };
},
};
</script>

记得在 setup 中 return 一下,否则 component 和变量是不一样的,会访问到 undefined

然后,记得在 src/component/example/Button/Button1.example.vue 中,加入 example 标签

1
<example>基础用法</example>

效果如图

效果图

然后再用上我们的 github 样式,变为这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/component/Button.vue
<template>
<div>Button 文档</div>
<laby-card>
<Example />
<code class="markdown-body">
<pre>{{ Example.__sourceCode }}</pre>
</code>
</laby-card>
</template>
<script lang="ts">
import LabyCard from "../lib/Card.vue";

import Example from "./example/Button/Button1.example.vue";
export default {
components: {
Example,
LabyCard,
},
setup() {
return { Example };
},
};
</script>

效果如图

效果图

代码高亮

但是代码黑乎乎的,还是丑

我们可以用 prismjs 库来获得代码高亮

先安装

1
npm install prismjs --save

然后在需要使用的地方,分别引入 prismjsprismjs/themes/prism.css,即可开始使用

prismjs的工作原理,是构造一个对象,并绑定到 window 上,所以在模板中使用的时候,需要先获取 window.Prism,再在 setup 中 return 出去

Prism 对象的一个用例如下

1
2
3
4
5
Prism.highlight(
[sourceCode],
Prism.languages.html,
'html'
)

该对象上提供一个名为 highlight 的方法,该方法要求传入 3 个参数,按顺序分别如下

  1. 源代码
  2. 作为什么代码进行解析
  3. 作为什么代码进行显示(渲染)

那么还是以 Button 为例,可得如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// src/component/Button.vue
<template>
<div>Button 文档</div>
<laby-card>
<Example />
<code class="markdown-body">
<pre
v-html="
Prism.highlight(Example.__sourceCode, Prism.languages.html, 'html')
"
></pre>
</code>
</laby-card>
</template>
<script lang="ts">
import "prismjs";
import "prismjs/themes/prism.css";

const Prism = (window as any).Prism;

import LabyCard from "../lib/Card.vue";

import Example from "./example/Button/Button1.example.vue";
export default {
components: {
Example,
LabyCard,
},
setup() {
return { Example, Prism };
},
};
</script>

效果如图

效果图

照猫画虎,把每个例子都补上就行啦

展开/收束代码

加个打开关闭的功能就可以了

以 Button 文档页为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<template>
<div>Button 文档</div>
<div class="container">
<laby-card class="example">
<h2>{{ Example.__sourceCodeTitle }}</h2>
<Example />
<code class="markdown-body">
<pre
v-if="visibility"
v-html="
Prism.highlight(Example.__sourceCode, Prism.languages.html, 'html')
"
></pre>
</code>

<button class="toggle" @click="toggle">
<span class="close" v-if="visibility">

<span class="desp">隐藏代码</span>
</span>
<span class="open" v-else>

<span class="desp">显示代码</span>
</span>
</button>
</laby-card>
</div>
</template>
<script lang="ts">
import "prismjs";
import "prismjs/themes/prism.css";

const Prism = (window as any).Prism;

import LabyCard from "../lib/Card.vue";

import Example from "./example/Button/Button1.example.vue";
import { ref } from "vue";
export default {
components: {
Example,
LabyCard,
},
setup() {
const visibility = ref(false);
const toggle = (index) => {
visibility.value = !visibility.value;
};
return { Example, Prism, visibility, toggle };
},
};
</script>

再补上 显示代码 这个功能的样式表就 ok 了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$theme-color: #f3678e;
.container {
&:hover {
> .example > .toggle > * > .desp {
display: inline;
}
}
> .example {
> .toggle {
display: block;
width: 100%;
height: 32px;
border: none;
transition: background-color 250ms;
outline: none;
&:focus {
outline: none;
}
background: white;
cursor: pointer;
&:hover {
background: fade-out($theme-color, 0.95);
}
> * > .desp {
display: none;
}
}
}
}

参数列表

用户光看样例,不知道参数代表什么含义,怎么可以

这里就可以用我们制作好的 Table 组件来展示样式表

引入 Table 组件,然后写好即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
<template>
<div>Button 文档</div>
<div class="container">
<laby-card class="example">
<h2>{{ Example.__sourceCodeTitle }}</h2>
<Example />
<code class="markdown-body">
<pre
v-if="visibility"
v-html="
Prism.highlight(Example.__sourceCode, Prism.languages.html, 'html')
"
></pre>
</code>

<button class="toggle" @click="toggle">
<span class="close" v-if="visibility">

<span class="desp">隐藏代码</span>
</span>
<span class="open" v-else>

<span class="desp">显示代码</span>
</span>
</button>
</laby-card>
</div>
<laby-table bordered>
<thead>
<tr>
<th v-for="(head, index) in heads" :key="index">{{ head.name }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(attribute, index) in attributes" :key="index">
<td v-for="key in keys" :key="key" v-html="attribute[key]"></td>
</tr>
</tbody>
</laby-table>
</template>
<script lang="ts">
import "prismjs";
import "prismjs/themes/prism.css";

const Prism = (window as any).Prism;

import LabyCard from "../lib/Card.vue";
import LabyTable from "../lib/Table.vue";

import Example from "./example/Button/Button1.example.vue";
import { ref } from "vue";
export default {
components: {
Example,
LabyCard,
LabyTable,
},
setup() {
const visibility = ref(false);
const toggle = (index) => {
visibility.value = !visibility.value;
};
const heads = [
{ name: "参数", identifier: "attr" },
{ name: "含义", identifier: "desp" },
{ name: "类型", identifier: "type" },
{ name: "可选值", identifier: "values" },
{ name: "默认值", identifier: "default" },
];
const keys = heads.map((item: any) => item.identifier);
const attributes = [
{
attr: "level",
desp: "默认类型",
type: "string",
values: "default / plain / primary / success / info / warning / danger",
default: "default",
},
{
attr: "disabled",
desp: "是否禁用",
type: "boolean",
values: "false / true",
default: "false",
},
{
attr: "theme",
desp: "式样",
type: "string",
values: "button / link / text",
default: "button",
},
{
attr: "loding",
desp: "是否加载中",
type: "boolean",
values: "false / true",
default: "false",
},
{
attr: "size",
desp: "尺寸",
type: "string",
values: "middle / small / large",
default: "middle",
},
{
attr: "color",
desp: "颜色",
type: "string",
values: "任意合法颜色值",
default: "#f3678e",
},
];
return { Example, Prism, visibility, toggle, heads, keys, attributes };
},
};
</script>
<style lang="scss" scoped>
$theme-color: #f3678e;
.container {
&:hover {
> .example > .toggle > * > .desp {
display: inline;
}
}
> .example {
> .toggle {
display: block;
width: 100%;
height: 32px;
border: none;
transition: background-color 250ms;
outline: none;
&:focus {
outline: none;
}
background: white;
cursor: pointer;
&:hover {
background: fade-out($theme-color, 0.95);
}
> * > .desp {
display: none;
}
}
}
}
</style>

效果如图

效果图

用户体验

因为用户使用你的库,不可能是从 lib 引用的,而是先 npm install 之后直接从 node_modules 引用,所以要像用户一样的使用,才可以尽可能发现用户使用中可能出现的 bug,提升用户体验

那么我们先要下载自己的库

1
npm install laby-ui --save

并在 main.ts 中引入样式表

1
2
// main.ts
import 'laby-ui/lib/laby.css'

然后修改每个例子中的引用

Button1.example.vue 为例

1
2
3
4
5
6
import { LabyButton } from "laby-ui";
export default {
components: {
LabyButton,
},
};

效果如图

效果图


感谢阅读

--It's the end.Thanks for your read.--