vue组件库及文档开发

·  阅读 4224

步骤

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 注册用户,然后登陆,和下面图片一样就登陆成功了

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>
复制代码

打包完成后,部署到码云,这里就不写了,最后附上地址

ui库

分类:
前端
标签:
分类:
前端
标签: