步骤
1、编写一个简单组件
2、完成组件单元测试
3、利用vuepress编写文档
4、发布到NPM
5、发布文档
编写一个简单的组件
- 使用vue-cli3创建一个工程,注意要选择单元测试
- 单元测试解决方案需要选择测试框架karma
? Pick a unit testing solution:
> Mocha + Chai # ui测试需要使用karma
Jest
复制代码
- 目录结构
编写组件第一个步骤
目的:把我们的组件做成一个插件,这样我们用的时候不需要一个个导入,只需要我们在入口里导进来,然后注册一下,这样页面就可以直接使用了
- 首先在src下创建packages文件夹用来放组件,然后在组件文件夹里创建button.vue和icon.vue
<template>
<button>button</button>
</template>
复制代码
<template>
<div>icon</div>
</template>
复制代码
- 在组件有了之后,我们需要对组件进行整合,在packages文件夹下创建index.js入口文件对组件进行整合。
用组件库大概是这样一个思路
1.先导入组件库 import polyUI from 'polyUI'
2.再通过全局方法Vue.use(polyUI)加载插件
3.然后就可以通过标签去使用polyUI中的组件了<poly-button></poly-button>
复制代码
- 根据上面的思路,这个index.js入口文件它就应该提供一个install方法,在install方法里面呢,就要不停的将这些组件都注册成全局组件
// 所有组件的入口,我们可以在这里进行扩展一些组件,并进行整合
import Button from './button.vue'
import Icon from './icon.vue'
// 这里提供一个方法,待会用的时候就use这个方法,因为use是vue的方法,所以这个方法要把Vue传进来
const install = (Vue)=>{
// 在install方法里注册全局组件
// Vue.component('poly-button',Button) //这种方式有个问题,组件名字poly-button写死了
Vue.component(Button.name,Button) // 我们可以把名字在组件文件里用name定义好,这样就取的是组件文件的名字
Vue.component(Icon.name,Icon)
}
//这样我们有很多组件就可以在install方法里先注册一下,到时候别人要用的时候再导出一个对象,整个文件作为一个入口,后续再复杂的封装,
//并且有可能组件会通过script标签的方式引入<script src='polyUI'>,这样它就不会去调用install方法,这种情况下,如果它是通过标签引入的话,我们就要让它自动的去调用install方法,这里需要判断window下是否有Vue实例
if(typeof window.Vue !== 'undefined'){ // 这个判断条件这样写是因为Vue只有用script标签的方式导入才会挂载到window上,import的方式导入是挂载不到window上的,而是在当前的模块内
// 当前全局window下有Vue实例的话,直接调用install把Vue传进去
install(Vue) // 全局直接通过script引用的方式会默认调用install方法
}
export default {
install
}
复制代码
- 组件中添加name,这样后续要改名的话,在组件文件里改就好了
<template>
<button>button</button>
</template>
<script>
export default {
name: 'poly-button' // 定义组件名
}
</script>
复制代码
<template>
<div>icon</div>
</template>
<script>
export default {
name: 'poly-icon' // 定义组件名
}
</script>
复制代码
- 然后在main.js中就可以在实例化之前导入我们自己的组件库了 import polyUI from './packages/index'
import Vue from 'vue'
import App from './App.vue'
import polyUI from './pa
ckages/index' // 引入自己的ui库
Vue.use(polyUI) // 挂载到全局,在代码里就可以直接用了
new Vue({
render:h=>h(App),
}).$mount('#app')
复制代码
- App.vue中使用
<template>
<div id='app'>
<poly-button></poly-button>
<poly-icon></poly-icon>
</div>
</template>
复制代码
编写组件第二个步骤
- 完善按钮组件
在src下新建styles文件夹,在里面新建_var.scss,用来作为组件的样式
$border-radius: 4px; //设置圆角
$primary: #409EFF; // 各种颜色
$success: #67C23A;
$warning: #E6A23C;
$danger: #F56C6C;
$info: #909399;
$primary-hover: #66b1ff; // 鼠标移入的颜色
$success-hover: #85ce61;
$warning-hover: #ebb563;
$danger-hover: #f78989;
$info-hover: #a6a9ad;
$primary-active: #3a8ee6; // 激活的颜色
$success-active: #5daf34;
$warning-active: #cf9236;
$danger-active: #dd6161;
$info-active: #82848a;
* { // 清除默认样式
padding: 0;
margin: 0;
box-sizing: border-box;
}
复制代码
使用插槽处理按钮组件的内容,并调试下按钮默认样式
// 一般使用按钮都会在标签中间放入内容
<poly-button>默认按钮</poly-button>
复制代码
button.vue
// 在组件里面就要用插槽接收
<template>
<button class='poly-button'>
// 因为有可能插槽会有一些样式,这里用span多包一层,并且判断有插槽再创建span
<span v-if='this.$slots.default'>
<slot></slot>
</span>
</button>
</template>
<script>
export default {
name: 'poly-button' // 定义组件名
}
</script>
<style lang='scss'>
@import '../styles/_var.scss'; // 导入公共样式
$height: 42px; // 设置一些公共变量
$font-size: 16px;
$color: #606266;
$border-color: #dcdfe6;
$background: #ecf5ff;
$active-color: #3a8ee6;
.poly-button {
border-radius: $border-radius;
border: 1px solid $border-color;
color: $color;
background: #fff;
height: $height;
cursor: pointer;
font-size: $font-size;
line-height: 1;
padding: 12px 20px;
display: inline-flex;
justify-content: center;
vertical-align: middle;
&:hover {
border-color: $border-color;
background-color: $background;
}
&:focus,&:active {
color: $active-color;
border-color: $active-color;
background-color: $background;
outline: none;
}
</style>
复制代码
实现按钮的type属性,箱主要primary、警告warning、危险danger、成功success、信息info
app.vue
<template>
<div id='app'>
// 默认按钮
<poly-button>默认按钮</poly-button>
// 带类型的按钮
<poly-button type='primary'>主要按钮</poly-button>
<poly-button type='warning'>警告按钮</poly-button>
<poly-button type='danger'>危险按钮</poly-button>
<poly-button type='success'>成功按钮</poly-button>
<poly-button type='info'>信息按钮</poly-button>
</div>
</template>
复制代码
在button.vue里就需要用props接收这个属性,配置完接受type属性后,还要根据type修改按钮的样式
```vue
// 在组件里面就要用插槽接收
<template>
// 根据type的类型,用:class动态的绑定按钮的样式,因为是多个,所以得用数组
// <button class='poly-button' :class='[{},{}]'> //又因为我们写代码的时候,尽量对css和js进行分离
// 所以上面一行:class数组里的对象,最好是算出来再放进去,而不是直接在标签上面放一堆,放一堆太不直观
<button class='poly-button' :class='btnClass'> // 这里可以绑定一个btnClass,然后通过计算属性算出一个btnClass
// 因为有可能插槽会有一些样式,这里用span多包一层,并且判断有插槽再创建span
<span v-if='this.$solts.default'>
<solt></solt>
</span>
</button>
</template>
<script>
export default {
name: 'poly-button', // 定义组件名
props:{
type: {
String, // 限制type的类型为字符串
default:'' // 默认为空字符串
// 做一个内容的校验
validator(type){ // 接受的参数就是传入的类型值
// 判断类型值不为空且不在我们定义的那五个类型里面
if(type&&!['primary','warning','danger','success','info'].includes(type) ){
// 如果类型不在这几个内,则打印error一下
console.error('type的类型必须为primary,warning,danger,success,info')
}
// 如果在五个之内,则什么都不用处理
return true // 这里一定要返回true,false会报错
}
}
},
computed:{
btnClass(){
// 因为多个样式用数组,所以先定义一个数组
let classes = []
// 如果用户传了type,并且type有值,则将type往数组里放对应的css选择器名称就好啦
if(this.type){
classes.push(`poly-button-${this.type}`)
}
// 如果用户再传入其他属性,像圆角啊,就可以在这里不停的加if做其他处理
// ...
// 最后将这个数组返回
return classes
}
}
}
</script>
<style lang='scss'>
@import '../styles/_var.scss'; // 导入公共样式
$height: 42px; // 设置一些公共变量
$font-size: 16px;
$color: #606266;
$border-color: #dcdfe6;
$background: #ecf5ff;
$active-color: #3a8ee6;
.poly-button {
border-radius: $border-radius;
border: 1px solid $border-color;
color: $color;
background: #fff;
height: $height;
cursor: pointer;
font-size: $font-size;
line-height: 1;
padding: 12px 20px;
display: inline-flex;
justify-content: center;
vertical-align: middle;
&:hover {
bord
er-color: $border-color;
background-color: $background;
}
&:focus,&:active {
color: $active-color;
border-color: $active-color;
background-color: $background;
outline: none;
}
// 因为五中类型的按钮样式一个个写太过复杂,这里采用scss的循环遍历@each来写
// 格式:@each 类型,颜色 in (key:值) {
// #{}取值表达式
// }
$color-list: ( // 定义颜色maps,类似于数组,但是里面可以放键值,数组只能单个值
primary: $primary,
success: $success,
info: $info,
warning: $warning,
danger: $danger
);
// 循环颜色maps,两个参数第一个为键第二个为值
@each $c123, $c2 in $color-list {
#{"." + $c123} {
background: #{$c2};
border: 1px solid #{$c2};
color: #fff;
}
}
@each $type,$color in (primary:$primary-hover, success:$success-hover, info:$info-hover, warning:$warning-hover, danger:$danger-hover) {
&-#{$type}:hover {
background: #{$color};
border: 1px solid #{$color};
color: #fff;
}
}
@each $type,$color in (primary:$primary-active, success:$success-active, info:$info-active, warning:$warning-active, danger:$danger-active) {
&-#{$type}:active, &-#{$type}:focus {
background: #{$color};
border: 1px solid #{$color};
color: #fff;
}
}
}
</style>
复制代码
编写icon组件
1.去阿里巴巴矢量图标库下载图标,以symbol(svg)的方式下载,这样下载的是js文件可以控制宽高,下载后放入src下的styles文件夹下,命名为icon.js,阿里图标库 2.在icon.vue中以import的方式引入icon.js,在组件里以svg方式使用字体鼠标,在App.vue使用icon组件使用组件就行了
<template>
<svg class="poly-icon" aria-hidden="true">
<use xlink:href="#iconshezhi" /> //
</svg>
</template>
<script>
export default {
name: 'poly-icon' // 定义组件名
}
</script>
复制代码
3.这个href的名字是#加上图标库里复制的名字
4.对icon组件做参数封装,给它加一个icon属性,并且调整下样式,样式可以直接赋值图标库给的
<template>
<svg class="poly-icon" aria-hidden="true">
<use :xlink:href="`#${icon}`" />
</svg>
</template>
<script>
import './../styles/icon'
export default {
name: 'poly-icon',
props:{
icon:{
type:String,
require:true // 设置必传
}
}
}
</script>
<style>
.poly-icon {
width: 30px;
height: 30px;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
复制代码
5.在App.vue使用的时候,带上icon属性就完成了
<template>
<div id="app">
<poly-button>默认按钮</poly-button>
<poly-button type="primary">主要按钮</poly-button>
<poly-button type="warning">警告按钮</poly-button>
<poly-button type="danger">危险按钮</poly-button>
<poly-button type="success">成功按钮</poly-button>
<poly-button type="info">信息按钮</poly-button>
<br />
<br />
<poly-icon icon='iconshezhi'></poly-icon>
<poly-icon icon='iconwode'></poly-icon>
<poly-icon icon='iconback'></poly-icon>
</div>
</template>
<script>
export default {
name: "App",
};
</script>
复制代码
在button组件里使用icon,实现带icon的按钮组件
- button组件接收icon属性,在标签中使用poly-icon组件,并且调整样式
<template>
<button class="poly-button" :class="className">
<!-- 使用icon标签,并且在有icon传入时才显示 -->
<poly-icon :icon="icon" v-if="icon" class="icon"></poly-icon>
<span v-if="$slots.default">
<slot></slot>
</span>
</button>
</template>
<script>
export default {
name: 'poly-button',
props: {
type: {
type: String,
default: '',
validator: function (type) {
let typeArr = ['primary', 'warning', 'danger', 'success', 'info']
if (type && typeArr.indexOf(type) == -1) {
console.error('type的类型必须为primary,warning,danger,success,info')
return true
}
return true
}
},
icon: { // 接收icon参数
type: String
}
},
mounted () {
},
computed: {
className () {
let classes = []
if (this.type) {
classes.push(this.type)
}
return classes
}
}
}
</script>
<style lang='scss'>
@import "../styles/_var.scss"; // 导入公共样式
$height: 42px; // 设置一些公共变量
$font-size: 16px;
$color: #606266;
$border-color: #dcdfe6;
$background: #ecf5ff;
$active-color: #3a8ee6;
.poly-button {
border-radius: $border-radius;
border: 1px solid $border-color;
color: $color;
background: #fff;
height: $height;
cursor: pointer;
font-size: $font-size;
line-height: 1;
padding: 12px 20px;
display: inline-flex;
justify-content: center;
vertical-align: middle;
&:hover {
border-color: $border-color;
background-color: $background;
}
&:focus,
&:active {
color: $active-color;
border-color: $active-color;
background-color: $background;
outline: none;
}
// 设置按钮icon大小
.icon {
width: 16px;
height: 16px;
}
// 当icon组件后面有span时,给span加一个左边的外间距
.icon + span{
margin-left: 5px;
}
}
$color-list: (
primary: $primary,
success: $success,
info: $info,
warning: $warning,
danger: $danger
);
@each $c123, $c2 in $color-list {
#{"." + $c123} {
background: #{$c2};
border: 1px solid #{$c2};
color: #fff;
fill: #fff; //把颜色填充到当前元素内
}
}
@each $type,
$color
in (
primary: $primary-hover,
success: $success-hover,
info: $info-hover,
warning: $warning-hover,
danger: $danger-hover
)
{
#{"." + $type}:hover {
background: #{$color};
border: 1px solid #{$color};
color: #fff;
}
}
@each $type,
$color
in (
primary: $primary-active,
success: $success-active,
info: $info-active,
warning: $warning-active,
danger: $danger-active
)
{
#{"." + $type}:active,
#{"." + $type}:focus {
background: #{$color};
border: 1px solid #{$color};
color: #fff;
}
}
</style>
复制代码
- 在App.vue中使用时带上icon属性
<poly-button type="primary" icon='iconshezhi'> 带icon的按钮</poly-button>
复制代码
- 通过控制类名+弹性布局设置子元素顺序的方式设置按钮图标左右位置
<template>
<button class="poly-button" :class="className">
<!-- 使用icon标签,并且在有icon传入时才显示 -->
<poly-icon :icon="icon" v-if="icon" class="icon"></poly-icon>
<span v-if="$slots.default">
<slot></slot>
</span>
</button>
</template>
<script>
export default {
name: 'poly-button',
props: {
type: {
type: String,
default: '',
validator: function (type) {
let typeArr = ['primary', 'warning', 'danger', 'success', 'info']
if (type && typeArr.indexOf(type) == -1) {
console.error('type的类型必须为primary,warning,danger,success,info')
return true
}
return true
}
},
icon: { // 接收icon参数
type: String
},
iconPosition: {
type: String,
default: 'left',
validator (data) {
let Arr = ['left', 'right']
if (data && Arr.indexOf(data) == -1) {
console.error('iconPosition类型必须为left,right')
return true
}
return true
}
}
},
mounted () {
},
computed: {
className () {
let classes = []
if (this.type) {
classes.push(this.type)
}
// 图标位置通过样式的方式处理
if (this.iconPosition) {
classes.push(`icon-${this.iconPosition}`)
}
return classes
}
}
}
</script>
<style lang='scss'>
@import "../styles/_var.scss"; // 导入公共样式
$height: 42px; // 设置一些公共变量
$font-size: 16px;
$color: #606266;
$border-color: #dcdfe6;
$background: #ecf5ff;
$active-color: #3a8ee6;
.poly-button {
border-radius: $border-radius;
border: 1px solid $border-color;
color: $color;
background: #fff;
height: $height;
cursor: pointer;
font-size: $font-size;
line-height: 1;
padding: 12px 20px;
display: inline-flex;
justify-content: center;
vertical-align: middle;
&:hover {
border-color: $border-color;
background-color: $background;
}
&:focus,
&:active {
color: $active-color;
border-color: $active-color;
background-color: $background;
outline: none;
}
// 设置按钮icon大小
.icon {
width: 16px;
height: 16px;
}
}
// 设置子元素顺序的方式控制图标的位置
.icon-left {
.icon {
order: 1; // 顺序排第一
margin-right: 5px;
}
span {
order: 2; // 顺序排第二
}
}
.icon-right {
.icon {
margin-left: 5px;
order: 2;
}
span {
order: 1;
}
}
$color-list: (
primary: $primary,
success: $success,
info: $info,
warning: $warning,
danger: $danger
);
@each $c123, $c2 in $color-list {
#{"." + $c123} {
background: #{$c2};
border: 1px solid #{$c2};
color: #fff;
fill: #fff; //把颜色填充到当前元素内
}
}
@each $type,
$color
in (
primary: $primary-hover,
success: $success-hover,
info: $info-hover,
warning: $warning-hover,
danger: $danger-hover
)
{
#{"." + $type}:hover {
background: #{$color};
border: 1px solid #{$color};
color: #fff;
}
}
@each $type,
$color
in (
primary: $primary-active,
success: $success-active,
info: $info-active,
warning: $warning-active,
danger: $danger-active
)
{
#{"." + $type}:active,
#{"." + $type}:focus {
background: #{$color};
border: 1px solid #{$color};
color: #fff;
}
}
</style>
复制代码
- 给按钮组件增加loading效果,和绑定点击事件 button.vue
<template>
<!-- 使用$emit触发click事件,,同时要把事件源$event传出去,效果是在外面一点击就把当前事件触发给click事件,同时在外面绑定的方法里,可以拿到事件源。-->
<button class="poly-button" :class="className" :disabled='loading' @click="$emit('click',$event)">
<!-- 使用icon标签,并且在有icon传入时才显示 -->
<poly-icon :icon="icon" v-if="icon&&!loading" class="icon"></poly-icon>
<!-- 加载状态 -->
<poly-icon icon="iconjiazai" v-if="loading" class="icon"></poly-icon>
<!-- 按钮文本 -->
<span v-if="$slots.default">
<slot></slot>
</span>
</button>
</template>
<script>
export default {
name: 'poly-button',
props: {
type: {
type: String,
default: '',
validator: function (type) {
let typeArr = ['primary', 'warning', 'danger', 'success', 'info']
if (type && typeArr.indexOf(type) == -1) {
console.error('type的类型必须为primary,warning,danger,success,info')
return true
}
return true
}
},
icon: { // 接收icon参数
type: String
},
iconPosition: {
type: String,
default: 'left',
validator (data) {
let Arr = ['left', 'right']
if (data && Arr.indexOf(data) == -1) {
console.error('iconPosition类型必须为left,right')
return true
}
return true
}
},
loading: {
type: Boolean, // 布尔类型在传值的时候可以直接写不用赋值
default: false
}
},
mounted () {
},
computed: {
className () {
let classes = []
if (this.type) {
classes.push(this.type)
}
// 图标位置通过样式的方式处理
if (this.iconPosition) {
classes.push(`icon-${this.iconPosition}`)
}
return classes
}
}
}
</script>
<style lang='scss'>
@import "../styles/_var.scss"; // 导入公共样式
$height: 42px; // 设置一些公共变量
$font-size: 16px;
$color: #606266;
$border-color: #dcdfe6;
$background: #ecf5ff;
$active-color: #3a8ee6;
.poly-button {
border-radius: $border-radius;
border: 1px solid $border-color;
color: $color;
background: #fff;
height: $height;
cursor: pointer;
font-size: $font-size;
line-height: 1;
padding: 12px 20px;
display: inline-flex;
justify-content: center;
vertical-align: middle;
&:hover {
border-color: $border-color;
background-color: $background;
}
&:focus,
&:active {
color: $active-color;
border-color: $active-color;
background-color: $background;
outline: none;
}
// 设置按钮icon大小
.icon {
width: 16px;
height: 16px;
}
&[disabled]{ // 属性选择器
cursor: not-allowed; // 禁止点击
}
}
// 设置子元素顺序的方式控制图标的位置
.icon-left {
.icon {
order: 1; // 顺序排第一
margin-right: 5px;
}
span {
order: 2; // 顺序排第二
}
}
.icon-right {
.icon {
margin-left: 5px;
order: 2;
}
span {
order: 1;
}
}
$color-list: (
primary: $primary,
success: $success,
info: $info,
warning: $warning,
danger: $danger
);
@each $c123, $c2 in $color-list {
#{"." + $c123} {
background: #{$c2};
border: 1px solid #{$c2};
color: #fff;
fill: #fff; //把颜色填充到当前元素内
}
}
@each $type,
$color
in (
primary: $primary-hover,
success: $success-hover,
info: $info-hover,
warning: $warning-hover,
danger: $danger-hover
)
{
#{"." + $type}:hover {
background: #{$color};
border: 1px solid #{$color};
color: #fff;
}
}
@each $type,
$color
in (
primary: $primary-active,
success: $success-active,
info: $info-active,
warning: $warning-active,
danger: $danger-active
)
{
#{"." + $type}:active,
#{"." + $type}:focus {
background: #{$color};
border: 1px solid #{$color};
color: #fff;
}
}
</style>
复制代码
App.vue
<template>
<div id="app">
<poly-button>默认按钮</poly-button>
<poly-button type="primary">主要按钮</poly-button>
<poly-button type="warning">警告按钮</poly-button>
<br />
<br />
<poly-button type="danger">危险按钮</poly-button>
<poly-button type="success">成功按钮</poly-button>
<poly-button type="info">信息按钮</poly-button>
<br />
<br />
<poly-button type="primary" icon='iconshezhi'>带icon的按钮</poly-button>
<poly-button type="primary" icon='iconshezhi' icon-position='right'>icon在右边的按钮</poly-button>
<br />
<br />
<poly-button type="primary" loading>加载中的按钮,不可点击</poly-button>
<br />
<br />
<poly-button type="success" @click='btn'>绑定了点击事件的按钮</poly-button>
<br />
<br />
<poly-icon icon='iconshezhi'></poly-icon>
<poly-icon icon='iconwode'></poly-icon>
<poly-icon icon='iconback'></poly-icon>
</div>
</template>
<script>
export default {
name: "App",
methods:{
btn(e){
console.log('*****按钮点击效果',e);
}
}
};
</script>
<style lang="scss"></style>
复制代码
按钮组组件的实现
1.在packages文件夹下创建button-group.vue,按钮组主要只是改下按钮的样式,唯一值得注意的是,需要校验按钮组里面只能放置按钮组件,得在mounted钩子里面校验一下
<template>
<div class="button-group">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'poly-button-group',
// 在钩子里面,校验内部的元素是否是我们的button组件,如果不是就报错
mounted () {
// 其实就是把当前原生的dom元素拿到
let children = this.$el.children
// 遍历元素
for (let i = 0; i < children.length; i###### ) {
console.assert(children[i].tagName === 'BUTTON', '子元素必须为button') // 使用断言工具函数
}
}
}
</script>
<style lang='scss'>
@import "./../styles/_var.scss"; // 导入公共样式
// 通过样式让整个按钮组只有四个角是圆角,并且两个按钮之间只有1px边框
.button-group {
display: inline-flex; // 设置不要独占一行
vertical-align: middle; // 上下居中
//先覆盖掉原来按钮的圆角
.poly-button {
position: relative; // 设置个定位,让各种hover状态可以控制层级
border-radius: 0;
// 让第一个按钮左边和最后一个按钮右边有圆角
&:first-child {
border-radius: $border-radius 0 0 $border-radius;
}
&:last-child {
border-radius: 0 $border-radius $border-radius 0;
}
// 让按钮与按钮中间的边框只有一像素,可以让后面的盒子左移1px
&:not(:first-child) {
margin-left: -1px;
}
&:hover{
z-index: 1;
}
&:focus{
z-index: 1;
}
}
}
</style>
复制代码
2.在packages文件夹下的index.js内导入
import Vue from 'vue'
// 导入组件
import Button from './button.vue'
import Icon from './icon.vue'
import ButtonGroup from './button-group.vue'
// 定义一个加载方法
const install = (Vue) => {
Vue.component(Button.name,Button)
Vue.component(Icon.name,Icon)
Vue.component(ButtonGroup.name,ButtonGroup)
}
if (typeof Vue !== 'undefined') {
install(Vue)
}
export default {
install
}
复制代码
3.在App.vue中使用
<poly-button-group>
<poly-button icon='iconback' type="primary" >上一页</poly-button>
<poly-button icon='iconmore' type="primary" icon-position='right'>下一页</poly-button>
</poly-button-group>
复制代码
到这里基本上完成了第一个阶段,编写一个简单的组件,效果是这样的
完成组件单元测试(使用karma框架)
我们需要测试ui
渲染后的结果。需要在浏览器中测试,所有需要使用Karma
Karma
配置
1.安装karma(包括karma、karma浏览器启动,karma覆盖率,karma配合webpack的等)
cnpm install --save-dev @vue/test-utils karma karma-chrome-launcher karma-mocha karma-sourcemap-loader karma-spec-reporter karma-webpack mocha karma-chai
复制代码
2.在项目根目录配置karma文件
karma.conf.js
var webpackConfig = require('@vue/cli-service/webpack.config')
module.exports = function(config) {
config.set({
frameworks: ['mocha'],
files: ['tests/**/*.spec.js'], // 去监控tests文件夹下以.spec.js结尾的所有文件
preprocessors: {
'**/*.spec.js': ['webpack', 'sourcemap']
},
autoWatch: true, // 自动观察文件变化
webpack: webpackConfig,
reporters: ['spec'],
browsers: ['ChromeHeadless'] //启动一个看不到界面的浏览器
})
}
复制代码
3.在package.json配置单元测试命令
{
"scripts": {
"test": "karma start"
}
}
复制代码
4.在src下tests文件夹下的unit文件夹下创建button.spec.js测试用例文件
import {
shallowMount
} from '@vue/test-utils'; //vue提供的快速测试的方法
import {
expect
} from 'chai' // 引入chai
import Button from '@/packages/button.vue'
import Icon from '@/packages/icon' // 引入自己的组件
// 描述测试button组件
describe('button.vue', () => {
// it后面是测试用例
it('1.测试slot是否能正常显示', () => {
const wrapper = shallowMount(Button, {
slots: {
default: 'poly-ui'
}
})
expect(wrapper.text()).to.equal('poly-ui')
})
it('2.测试传入icon属性', () => {
const wrapper = shallowMount(Button, {
stubs: {
'poly-icon': Icon
},
propsData: {
icon: 'edit' // 传入的是edit 测试一下 edit是否ok
}
})
expect(wrapper.find('use').attributes('href')).to.equal('#icon-edit')
})
it('3.测试传入loading,是否能,控制loading属性', () => {
const wrapper = shallowMount(Button, {
stubs: {
'poly-icon': Icon
},
propsData: {
loading: true // 传入的是edit 测试一下 edit是否ok
}
})
expect(wrapper.find('use').attributes('href')).to.eq('#icon-loading');
expect(wrapper.find('button').attributes('disabled')).to.eq('disabled');
})
it('4.测试点击按钮', () => {
const wrapper = shallowMount(Button, {
stubs: ['poly-icon']
})
wrapper.find('button').trigger('click')
expect(wrapper.emitted('click').length).to.eq(1);
});
// 5.测试前后图标
it('5.测试前后图标', () => {
const wrapper = shallowMount(Button, {
stubs: {
'poly-icon': Icon
},
slots:{
default:'hello'
},
attachToDocument: true,
propsData: {
iconPosition: 'left',
icon: 'edit'
}
});
let ele = wrapper.vm.$el.querySelector('span');
expect(getComputedStyle(ele, null).order).to.eq('2');
wrapper.setProps({
iconPosition: 'right'
});
return wrapper.vm.$nextTick().then(() => {
expect(getComputedStyle(ele, null).order).to.eq('1');
});
});
})
复制代码
5.运行npm run test就可以查看测试效果了,具体的karma框架使用,看文档,感觉平时对工作用处不大,了解下就够了
使用vuepress编写文档
利用vue脚手架打包开发的组件
1.在package.json中增加打包组件库指令
"script":{
// 官网解释:你可以通过下面的命令将一个单独的入口构建为一个库
// 利用vue自带的工具 构建 构建目标(库) 库的名字 poly-ui 当前的入口文件
"lib":"vue-cli-service build --target lib --name poly-ui ./src/packages/index.js"
}
复制代码
2.运行npm run lib指令,根目录下会多出一个dist文件夹,里面就是我们自己开发的ui库
- 用户通常引用的就是.umd.min.js和.css 3.在package.json设置使用时的默认引用路径main,设置完成后就能发布到npm上正常使用了
"script":{
// 官网解释:你可以通过下面的命令将一个单独的入口构建为一个库
// 利用vue自带的工具 构建 构建目标(库) 库的名字 poly-ui 当前的入口文件
"lib":"vue-cli-service build --target lib --name poly-ui ./src/packages/index.js"
}
"main":"./dist/poly-ui.umd.min.js"
复制代码
使用vuepress编写文档,让开发者知道这个组件怎么使用
vuepress介绍
- Vue 驱动的静态网站生成器
- 以 Markdown 为中心的项目结构,以最少的配置帮助你专注于写作。
- 享受 Vue + webpack 的开发体验,可以在 Markdown 中使用 Vue 组件,又可以使用 Vue 来开发自定义主题。
- VuePress 会为每个页面预渲染生成静态的 HTML,同时,每个页面被加载的时候,将作为 SPA 运行。
1.创建文档工程
- 另起一个目录创建poly-ui-doc文件夹
进入poly-ui-doc文件夹
npm init -y // 创建package.json
cnpm install vuepress -D // 安装vuepress框架
复制代码
在package.json中配置运行指令
"scripts":{
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs"
}
复制代码
安装要用到的其他插件
// element-ui 代码高亮 sass
cnpm install element-ui highlight.js node-sass sass-loader --save
复制代码
在根目录下创建docs文件夹,里面创建README.md文件,规定死的,两个的名称都不能改
---
home: true // 是否首页
actionText: 欢迎 → // 首页文本
actionLink: /components/button
features:
- title: poly-ui官方文档
details: poly-ui官方文档
---
复制代码
创建components文件夹,每个组件都有一个md文件,例如button.md
## 按钮组件
复制代码
运行npm run docs:dev看下效果
在docs文件夹下创建.vuepress文件夹,在里面创建config.js,用来配置导航等文档基本结构信息,修改后重启项目才生效
module.exports = {
title: 'poly-ui', // 设置网站标题
description: 'ui 库', //描述
dest: './build', // 设置输出目录
port: 1234, //端口
themeConfig: { //主题配置
nav: [{
text: '主页',
link: '/'
}, // 导航条
],
// 为以下路由添加侧边栏
sidebar: {
'/components/': [{
collapsable: true,
children: [
'button'
//...
// 每加一个菜单需要在这里也新增一下
]
}
]
}
}
}
复制代码
在.vuepress文件夹下创建styles文件夹,在里面创建palette.styl文件写自己的样式去格式化掉框架自带的一些默认样式
$codeBgColor = #fafafa // 代码背景颜色
$accentColor = #3eaf7c
$textColor = #2c3e50
$borderColor = #eaecef
$arrowBgColor = #ccc
$badgeTipColor = #42b983
$badgeWarningColor = darken(#ffe564, 35%)
$badgeErrorColor = #DA5961
.content pre{ margin: 0!important;}
.theme-default-content:not(.custom){
max-width: 1000px !important;
}
复制代码
在页面里要用上自己的组件,要在.vuepress文件夹下创建一个enhanceApp.js文件作为入口(启动当前的vuepress,这个文件就是当前项目的一个入口),框架规定的,名字不能改
import Vue from 'vue';
import Element from 'element-ui'; // 引入elementUi,因为要用到elementui里的结构
import 'element-ui/lib/theme-chalk/index.css'
import hljs from 'highlight.js' //引入高亮js
import 'highlight.js/styles/googlecode.css' //引入高亮js样式文件
//正常情况引入poly-ui要根据目录../../一层一层去引入的,
//这里为了方便在poly-ui文件夹根目录下npm link一下,把poly-ui添加到本地全局,
//注意要以package.json里的name名字为准
//然后在poly-ui-doc文件夹下npm link poly-ui一下,把本地全局的poly-ui引入到文档项目下
//成功之后在poly-ui-doc下的nodemodule下就能看到这个poly-ui文件夹了(实际上是个链接,每次poly-ui文件夹改动了,这里的也会改动)
//这样就可以以下面一行的方式引用了
import polyUi from 'poly-ui' // 要编写对应的文档的包
import 'poly-ui/dist/poly-ui.css'
// 写了一个高亮指令,可以去解析pre code里面的标签,添加高亮
Vue.directive('highlight',function (el) {
let blocks = el.querySelectorAll('pre code');
blocks.forEach((block)=>{
hljs.highlightBlock(block)
})
})
export default ({
Vue,
options,
router,
siteData
}) => {
Vue.use(Element);
Vue.use(polyUi) // 设置为全局组件
}
复制代码
在.vuepress文件夹下创建components文件夹,放置为引入的poly-ui写一些测试代码组件,只要是这个目录下的都是全局组件,例如:在里面创建button文件夹,里面放button1.vue,那么buton1.vue就是button的测试
<template>
</template>
<script>
export default {}
</script>
复制代码
然后button1.vue就可以在docs目录下的components内的所有.md文件内使用了
## 按钮组件
//button是.vuepress内components下文件夹button的名字,-后面的button1是button文件夹下button1.vue的名字
//找到的就是button1.vue,它会把这个文件渲染到文档上
<button-button1></button-button1>
复制代码
如果遇到core-js报错,就cnpm install core-js@2重新装一下,
成功之后就可以看到这个效果了
在.vuepress内components下文件夹下创建demo-block
可收缩代码块,开发像elementui那种查看例子时,可以展开代码的功能
<template>
<!-- 主要用到了三个插槽 -->
<div
class="demo-block"
:class="[blockClass, { 'hover': hovering }]"
@mouseenter="hovering = true"
@mouseleave="hovering = false"
>
<div style="padding:24px">
<!-- 第一个是源代码插槽 -->
<slot name="source"></slot>
</div>
<div class="meta" ref="meta">
<div class="description" v-if="$slots.default">
<!-- 第二个是描述插槽 -->
<slot></slot>
</div>
<div class="highlight" v-highlight>
<!-- 第三个是高亮插槽 -->
<slot name="highlight"></slot>
</div>
</div>
<div class="demo-block-control" ref="control" @click="isExpanded = !isExpanded">
<transition name="arrow-slide">
<i :class="[iconClass, { 'hovering': hovering }]"></i>
</transition>
<transition name="text-slide">
<span v-show="hovering">{{ controlText }}</span>
</transition>
</div>
</div>
</template>
<style lang="scss">
.demo-block {
border: solid 1px #ebebeb;
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);
}
code {
font-family: Menlo, Monaco, Consolas, Courier, monospace;
}
.demo-button {
float: right;
}
.source {
padding: 24px;
}
.meta {
background-color: #fafafa;
border-top: solid 1px #eaeefb;
overflow: hidden;
height: 0;
transition: height 0.2s;
}
.description {
padding: 20px;
box-sizing: border-box;
border: solid 1px #ebebeb;
border-radius: 3px;
font-size: 14px;
line-height: 22px;
color: #666;
word-break: break-word;
margin: 10px;
background-color: #fff;
p {
margin: 0;
line-height: 26px;
}
code {
color: #5e6d82;
background-color: #e6effb;
margin: 0 4px;
display: inline-block;
padding: 1px 5px;
font-size: 12px;
border-radius: 3px;
height: 18px;
line-height: 18px;
}
}
.highlight {
pre {
margin: 0;
}
code.hljs {
margin: 0;
border: none;
max-height: none;
border-radius: 0;
line-height: 1.8;
color: black;
&::before {
content: none;
}
}
}
.demo-block-control {
border-top: solid 1px #eaeefb;
height: 44px;
box-sizing: border-box;
background-color: #fff;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
text-align: center;
margin-top: -1px;
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: #409eff;
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>
<script type="text/babel">
export default {
name: 'demo-block',
data () {
return {
hovering: false,
isExpanded: false,
fixedControl: false,
scrollParent: null,
langConfig: {
"hide-text": "隐藏代码",
"show-text": "显示代码",
"button-text": "在线运行",
"tooltip-text": "前往 jsfiddle.net 运行此示例"
}
}
},
props: {
jsfiddle: Object,
default () {
return {}
}
},
methods: {
scrollHandler () {
const { top, bottom, left } = this.$refs.meta.getBoundingClientRect()
this.fixedControl = bottom > document.documentElement.clientHeight &&
top + 44 <= document.documentElement.clientHeight
},
removeScrollHandler () {
this.scrollParent && this.scrollParent.removeEventListener('scroll', this.scrollHandler)
}
},
computed: {
lang () {
return this.$route.path.split('/')[1]
},
blockClass () {
return `demo-${this.lang} demo-${this.$router.currentRoute.path.split('/').pop()}`
},
iconClass () {
return this.isExpanded ? 'el-icon-caret-top' : 'el-icon-caret-bottom'
},
controlText () {
return this.isExpanded ? this.langConfig['hide-text'] : this.langConfig['show-text']
},
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'
if (!val) {
this.fixedControl = false
this.$refs.control.style.left = '0'
this.removeScrollHandler()
return
}
setTimeout(() => {
this.scrollParent = document.querySelector('.page-component__scroll > .el-scrollbar__wrap')
this.scrollParent && this.scrollParent.addEventListener('scroll', this.scrollHandler)
this.scrollHandler()
}, 200)
}
},
mounted () {
this.$nextTick(() => {
let highlight = this.$el.getElementsByClassName('highlight')[0]
if (this.$el.getElementsByClassName('description').length === 0) {
highlight.style.width = '100%'
highlight.borderRight = 'none'
}
})
},
beforeDestroy () {
this.removeScrollHandler()
}
};
</script>
复制代码
将可收缩代码块在组件.md文件中使用
# Button组件
常用的操作按钮。
## 基础用法
基础的按钮用法。
<demo-block>
::: slot source
<button-button1></button-button1>
:::
使用type属性来定义 Button 的样式。
::: slot highlight
```html
<div>
<poly-button>默认按钮</poly-button>
<poly-button type="primary">主要按钮</poly-button>
<poly-button type="success">成功按钮</poly-button>
<poly-button type="info">信息按钮</poly-button>
<poly-button type="warning">警告按钮</poly-button>
<poly-button type="danger">危险按钮</poly-button>
</div>
// ```
:::
</demo-block>
复制代码
到这里就基本上完成了一个文档的功能
发布到npm
进入到poly-ui文件夹
切换到npm官方源
npm config get registry // 查看源,我这里是https://registry.npmjs.org/不用切
nrm use npm // 如果不是就要切换下
复制代码
进入poly-ui文件夹,要发到npm上要在根目录下创建一个.npmignore文件,设置不上传到npm的文件
src // 源码不用发
public // public不用发
tests // 测试用例不用发,其他的无所谓,dist一定要发
复制代码
设置package.json
{
"name": "poly-ui",
"version": "0.1.0", // 版本号
"private": false, // 是否设置私有,设置为true是发布不了到npm的
// ...
"main": "./dist/poly-ui.umd.min.js", // 一定要配置入口
}
复制代码
编写README.md文件
# polyUI
## 安装
npm install poly-ui element-ui -s
### 使用
// 导入elementui
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
import polyUi from 'poly-ui'
import 'poly-ui/dist/poly-ui.css'
Vue.use(polyUi)
复制代码
发布到npm
1.在npm 注册用户,然后npm login 登陆,再一步步填写信息,和下面图片一样就登陆成功了
2.在poly-ui文件夹下,发布模块 npm publish
如果出现下图,你要在校验下邮箱
如果出现下面的图片,就表示发布到npm成功了
在npm账户下就可以看到这个包了
3.生成npm包的版本图标,并复制生成的连接粘贴到README.md文件上
添加npm图标 badge.fury.io/for/js
4.设置npm与github关联,可以相互跳转(根据实际需要处理)
发布文档
打包指令
进入到poly-ui-doc文件夹,运行 npm run docs:build,执行完成之后会生成build文件夹
我这儿报错了
vuepress官网部署地址 官网找到一句这个
找不到构建失败的原因,从头开始调试文档
新建一个vuepress工程,按官网来打包都报一堆错
还是使用脚手架吧
vuepress脚手架官网
根据文档来,基本上一切ok,把代码挪过来的时候,打包还是报这个错
请教了大佬,回复是:因为用到window变量了,最好引入时单独逐一引入,就是当是开发的时候可以这样做,但是最终上线的话,你还是得带./去引,因为我在这个install里面有个window属性,它不兼容啊,或者有一个方法,但是比较极端,那最好还是一个个去引吧。并且字体图标组件 里面也用到window属性,要把icon.js改成在标签里引入
// poly-ui下的main.js
import Vue from "vue"
import App from "./App.vue"
Vue.config.productionTip = false
// 导入elementui
import * as ElementUI from './@element-ui.js'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
// 导入自己的组件库
// import polyUI from './packages/index'
// Vue.use(polyUI)
import Button from './button.vue'
import Icon from './icon.vue'
import ButtonGroup from './button-group.vue'
import PaginationPlus from './Pagination-Plus.vue'
import FormPlus from './Form-Plus.vue'
import tablePlus from './table-Plus/main.vue'
Vue.component(Button.name, Button)
Vue.component(Icon.name, Icon)
Vue.component(ButtonGroup.name, ButtonGroup)
Vue.component(PaginationPlus.name, PaginationPlus)
Vue.component(FormPlus.name, FormPlus)
Vue.component(tablePlus.name, tablePlus)
new Vue({
render: h => h(App)
}).$mount("#app")
复制代码
//icon.vue
<template>
<svg class="poly-icon" aria-hidden="true">
<use :xlink:href="`#${icon}`" />
</svg>
</template>
<script>
// import './../styles/icon'
export default {
name: 'poly-icon',
props:{
icon:{
type:String,
require:true // 设置必传
}
}
}
</script>
<style>
.poly-icon {
width: 30px;
height: 30px;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
复制代码
// poly-ui下的public下的index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<script src="./../src/styles/icon.js"></script>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
复制代码
- 这样修改后,打包和部署是ok了,但是icon不显示了,应该是引入方式有问题
- 搞不定不导入本地的了,还是改成链接吧,改成链接就可以了
<script src="http://at.alicdn.com/t/font_2047860_llcfe29jc6g.js"></script>
复制代码