基于vue3环境搭建
# 简介
# 说明
基于vue3语法,非lerna方式实现;
# 比较
跟v2版本主要的不同点:
- vue3 js中操作的语法;
- 组件样式做了封装处理;
- 测试用例的方式引入不同;
- 文档库该用vitepress;
- 文档中的引入组件示范格式不同;
# 组件的划分
以elementUi
为基准划分为
Basic
:Icon图标
、Button
、Layout布局
、container布局容器
...Form
:Input
、Radio
、checkbox
、DatePicker
、Upload
...Data
:Table
、Tree
、Pagination
...Notice
:Alert
、Loading
、Message
...Navigation
:Tabs
、Dropdown
、NavMenu
...Others
:Popover
,Dialog
、inifiniteScroll
、Carousel
...
# 初始化项目
vue create rat-ui-vue
1
? Check the features needed for your project:
(*) Choose Vue version
(*) Babel
( ) TypeScript
( ) Progressive Web App (PWA) Support
( ) Router
( ) Vuex
(*) CSS Pre-processors
( ) Linter / Formatter
(*) Unit Testing
( ) E2E Testing
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
2.x
> 3.x (Preview)
1
2
2
> Sass/SCSS (with dart-sass)
Sass/SCSS (with node-sass)
Less
Stylus
1
2
3
4
2
3
4
? Pick a unit testing solution:
> Mocha + Chai # ui测试需要使用karma
Jest
1
2
3
2
3
# 目录结构配置
│ .browserslistrc # 兼容版本
│ .gitignore
│ babel.config.js # babel的配置文件
│ package-lock.json
│ package.json
│ README.md
| examples # 组件使用案例
├─public
│ favicon.ico
│ index.html
├─src
│ │ App.vue
│ │ main.js
│ │
│ ├─packages # 需要打包的组件
│ │ button
| | button-group
│ │ icon
│ │ index.js # 所有组件的入口
│ │
│ └─styles # 公共样式
│ common
| mixins
└─tests # 单元测试
└─unit
button.spec.js
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
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
# 编写插件入口
import Icon from './icon';
import Button from './button';
import ButtonGroup from './button-group';
const plugins = [
Icon,
Button,
];
const install = (app: any) => {
plugins.forEach(plugin => app.use(plugin));
}
//全局导出;
export default {
install
}
//单个导出
export {
Icon,
Button,
ButtonGroup,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { createApp } from 'vue'
import App from './App.vue'
import Rat from './packages/index';
createApp(App).use(Rat).mount('#app')
1
2
3
4
5
2
3
4
5
可以通过插件的方式去引入我们的组件库
# Icon组件
使用
iconfont
添加图标 (opens new window)
# 代码
<template>
<svg class="r-icon" aria-hidden="true">
<use :xlink:href="`#icon-${icon}`" />
</svg>
</template>
<script>
import "./font";
export default {
props: {
icon: String,
},
name: "RIcon",
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
import Icon from './icon.vue'
import '../../style/icon.scss'
// Button组件是可以单独使用
// import {Button} from 'r-ui';
// app.use(Button)
Icon.install = (app) => { // Vue3.0 app createApp().use().mount()
app.component(Icon.name,Icon)
}
export default Icon
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 样式抽取
$namespace:'r-';
@mixin blockquote($block) {
$blockName: $namespace + $block !global;// r-xxx
.#{$blockName} {
@content;
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
@import "./common/var.scss";
@import "./mixins/mixins.scss";
@include blockquote(icon) {
width: 24px;
height: 24px;
vertical-align: middle;
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 效果
# Button组件
# 实现功能规划
- [x] 按钮的基本用法
- [x] 按钮加载中状态
- [x] 图标按钮
- [x] 按钮组的实现
# 准备备用样式
├─common
│ |-- var.scss # 基本样式
└─mixins
│ |-- mixins.scss # 混合的方法
│ button.scss
| button-group.scss
| icon.scss
1
2
3
4
5
6
7
2
3
4
5
6
7
// 样式变量
$primary: #409EFF;
$success: #67C23A;
$warning: #E6A23C;
$danger: #F56C6C;
$info: #909399;
$primary-active: #3a8ee6;
$success-active: #5daf34;
$warning-active: #cf9236;
$danger-active: #dd6161;
$info-active: #82848a;
$font-size:12px;
$border-radius:4px;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
@import './common/_var.scss'; // 全局的样式
@import './mixins/mixins.scss'; // 方法
$color-list:(primary:$primary,
success:$success,
info:$info,
warning:$warning,
danger:$danger);
$color-active-list:(primary:$primary-active,
success:$success-active,
info:$info-active,
warning:$warning-active,
danger:$danger-active);
@include blockquote(button) {
@include status($color-list);
display: inline-flex;
font-size: $font-size;
border-radius: $border-radius;
padding: 0px 20px;
border: none;
outline: none;
min-width: 80px;
box-shadow: 2px 2px #ccc;
color: #fff;
align-items: center;
justify-content: center;
height: 40px;
line-height: 40px;
vertical-align: middle;
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.loading {
animation: rotate 1s linear infinite;
}
&:disabled {
cursor: not-allowed;
}
.icon {
fill: #fff;
vertical-align: middle;
}
&:active:not(:disabled) {
@include status($color-active-list);
}
&.r-button-left {
.icon {
order:1
}
span{
order:2
}
}
&.r-button-right {
.icon {
order:2
}
span{
order:1
}
}
}
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
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
# 按钮的实现
带图标的按钮可增强辨识度(有文字)或节省空间(无文字)。
要设置为 loading 状态,只要设置loading
属性为true
即可。
<template>
<button :class="classs" :disabled="loading">
<r-icon :icon="icon" v-if="icon && !loading" class="icon"></r-icon>
<r-icon icon="loading" v-if="loading" class="icon loading"></r-icon>
<span v-if="$slots.default">
<slot></slot>
</span>
</button>
</template>
<script>
import { computed } from "vue";
export default {
props: {
type: {
type: String,
default: "primary",
validator(type) {
if (
type &&
!["warning", "success", "danger", "info", "primary"].includes(type)
) {
console.log(
"组件的type类型必须为:" +
["warning", "success", "danger", "info", "primary"].join("、")
);
}
return true;
},
},
icon: String,
loading:{
type:Boolean,
default:false
},
position:{
type:String,
default:'left'
}
},
name: "RButton",
setup(props, context) {
// 计算出所有样式
const classs = computed(() => [
`r-button`,
`r-button-${props.type}`,
`r-button-${props.position}`
]);
return {
classs,
};
},
};
</script>
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
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
# 入口导出
/*
* @Author: samy
* @email: yessz#foxmail.com
* @time: 2021-08-20 18:39:41
* @modAuthor: samy
* @modTime: 2021-08-20 18:45:11
* @desc: Button组件
* Copyright © 2015~2021 BDP FE
*/
import Button from './button.vue'
import '../../style/button.scss'
Button.install = (app) => {
app.component(Button.name, Button)
}
export default Button
// Button组件是可以单独使用
// import {Button} from 'r-ui';
// app.use(Button)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ButtonGroup组件
# 逻辑
以按钮组的方式出现,常用于多项类似操作。主要还是拿到实例节点下的孩子判断是否是Buton验证;
<template>
<div class="r-button-group">
<slot></slot>
</div>
</template>
<script>
import { onMounted, getCurrentInstance } from "vue";
export default {
name: "RButtonGroup",
setup(props) {
onMounted(() => {
let context = getCurrentInstance();
let ele = context.ctx.$el;
let children = ele.children;
for (let i = 0; i < children.length; i++) {
console.assert(children[i].tagName === "BUTTON", "必须子节点是button");
}
});
},
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 样式
处理左右两边及中间样式;
@import "./common/var.scss"; // 公共样式
@import "./mixins/mixins.scss";
@include blockquote(button-group) {
display: inline-flex;
vertical-align: middle;
button {
border-radius: 0;
position: relative;
box-shadow: none;
&:not(first-child) {
margin-left: -1px;
}
&:first-child {
border-top-left-radius: $border-radius;
border-bottom-left-radius: $border-radius;
}
&:last-child {
border-top-right-radius: $border-radius;
border-bottom-right-radius: $border-radius;
}
}
button:hover {
z-index: 1;
}
button:focus {
z-index: 2;
}
}
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
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
# 组件测试
需要测试ui
渲染后的结果。需要在浏览器中测试,所有需要使用Karma
# Karma
配置
# 安装karma
npm install --save-dev karma karma-chrome-launcher karma-mocha karma-sourcemap-loader karma-spec-reporter karma-webpack mocha karma-chai
1
# 配置karma文件
karma.conf.js
var webpackConfig = require('@vue/cli-service/webpack.config')
module.exports = function(config) {
config.set({
frameworks: ['mocha'],
files: ['tests/**/*.spec.js'],
preprocessors: {
'**/*.spec.js': ['webpack', 'sourcemap']
},
autoWatch: true,
webpack: webpackConfig,
reporters: ['spec'],
browsers: ['ChromeHeadless']
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"scripts": {
"test": "karma start"
}
}
1
2
3
4
5
2
3
4
5
# 单元测试
以button为测试示范使用;
import { expect } from 'chai'
import Button from '@/packages/button';
import Icon from '@/packages/icon';
// @ts-ignore
import { createApp } from 'vue/dist/vue.esm-bundler.js';
describe('HelloWorld.vue', () => {
it('测试插槽显示是否正常', () => {
const container = document.createElement('div');
const app = createApp({
template: `<RButton>hello</RButton>`,
components: {
"RButton": Button,
}
}, {
icon: 'edit',
}).mount(container);
let html = app.$el.innerHTML
expect(html).to.match(/hello/)
});
it('测试icon是否能够正常显示', () => {
const container = document.createElement('div');
const app = createApp({
...Button,
}, {
icon: 'edit',
}).use(Icon).mount(container);
let useEle = app.$el.querySelector('use');
let href = useEle.getAttribute('xlink:href');
expect(href).to.eq('#icon-edit');
});
it('测试传入loading时 按钮为禁用态', () => {
const container = document.createElement('div');
const app = createApp({
template: `<RButton></RButton>`,
components: {
"RButton": Button,
}
}, {
loading: true,
}).use(Icon).mount(container);
let useEle = app.$el.querySelector('use');
let href = useEle.getAttribute('xlink:href');
let disabeld = app.$el.getAttribute('disabled')
expect(href).to.eq('#icon-loading');
expect(disabeld).not.to.eq(null);
});
// todo....
})
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
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
# 打包组件【核心】
# 配置打包命令【要点】
构建示范
scripts": {
"lib": "vue-cli-service build --target lib --name plugin_demo --dest lib src/installComponents.js", //专属打包
}
//npm run lib
1
2
3
4
2
3
4
--target: 构建目标,默认为应用模式。这里修改为 lib 启用库模式。
--dest : 输出目录,默认 dist。这里我们改成 lib
[entry]: 最后一个参数为入口文件,默认为 src/App.vue。这里我们指定编译 全局注册组件的installComponents.js
构建配置
"build": "vue-cli-service build --target lib --name R ./src/packages/index.ts --no-clean && vue-cli-service build --all --no-clean",
1
const args = process.argv.slice(2);
if (process.env.NODE_ENV == 'production' && args.includes('--all')) {
const fs = require('fs');
const path = require('path');
const getEntries = (dir) => {
let absPath = path.resolve(dir);
let files = fs.readdirSync(absPath);
let entries = {}
files.forEach(item => {
let p = path.resolve(absPath, item);
if (fs.statSync(p).isDirectory()) {
p = path.resolve(p, 'index.ts')
entries[item.split('.')[0]] = p
}
});
return entries;
}
module.exports = {
outputDir: 'dist', // 打包出口
configureWebpack: {
entry: { // 配置多入口
...getEntries('./src/packages')
},
output: {
filename: `lib/[name]/index.js`,
libraryTarget: 'umd',
libraryExport: 'default',
library: ['rat', '[name]']
},
externals:{
vue: {
root: 'Vue',
commonjs: 'vue',
commonjs2: 'vue',
amd: 'vue'
}
},
},
css: {
sourceMap: true,
extract: {
filename: 'css/[name]/style.css'
}
},
chainWebpack: config => {
config.optimization.delete('splitChunks')
config.plugins.delete('copy')
config.plugins.delete('preload')
config.plugins.delete('prefetch')
config.plugins.delete('html')
config.plugins.delete('hmr')
config.entryPoints.delete('app')
},
}
}
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
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
# 配置运行入口
package.json
"main": "./dist/rat.umd.min.js"
1
# link到全局下【要点】
npm link
1
# 搭建库文档
# 基本配置
# 安装
npm install vitepress -D
1
# 配置scripts
{
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs"
}
1
2
3
4
2
3
4
# 初始化docs
增加入口页面index.md
# 配置导航
增加config.js
module.exports = {
title: 'r-ui', // 设置网站标题
description: 'ui 库', //描述
dest: './build', // 设置输出目录
themeConfig: { //主题配置
nav: [
{ text: '主页', link: '/' },
{ text: '联系我', link: '/contact/' },
{ text: '我的博客', link: 'https://' },
],
// 为以下路由添加侧边栏
sidebar: [
{
text: 'Button 按钮', // 必要的
link: '/button/', // 可选的, 标题的跳转链接,应为绝对路径且必须存在
collapsable: false, // 可选的, 默认值是 true,
sidebarDepth: 1, // 可选的, 默认值是 1
},
{
text: 'Icon 图标', // 必要的
link: '/icon/', // 可选的, 标题的跳转链接,应为绝对路径且必须存在
collapsable: false, // 可选的, 默认值是 true,
sidebarDepth: 1, // 可选的, 默认值是 1
},
]
}
}
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
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
# 可能碰到的问题
# 提交及发布
# 发布到npm
配置.npmignore
配置文件
npm addUser
npm publish
1
2
2
# 推送到git
添加npm
图标 https://badge.fury.io/for/js
git remote add origin
git push origin master
1
2
2
上次更新: 2022/04/15, 05:41:28