【组件库开发】——基于Vue框架(持续更新中)

72 阅读3分钟

一、组件库

本项目的组件: Button,Alert,CheckBox,Link,Message,Tag,Menu,Upload,Card Dialog,Form,Input,Radio,Select,Switch,Upload,Carousel

已更新组件:Button按钮,Message消息提示,Carousel轮播图

二、组件

Button组件

需求数据类型默认值
类型typeprimary,info,danger,warning,success,primary
plainBooleanfalse
禁用disabledBooleanfalse
圆角roundBooleanfalse
circleBooleanfalse
iconStringdefault
组件样式
.zj-button{
    display: inline-block;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    background-color: #fff;
    border: 1px solid #dcdfe6;
    color: #606266;
    -webkit-appearance: none;
    appearance: none;
    text-align: center;
    box-sizing: border-box;
    outline: none;
    margin: 0;
    transition: 0.1s;
    font-weight: 500;
    -moz-user-select: none;
    user-select: none;
    -webkit-user-select: none;
    -ms-user-select: none;
    padding: 12px 20px;
    font-size: 14px;
    border-radius: 4px
}
.zj-button-default{
    color: #606266;
    background-color: #fff;
    border-color: #dcdfe6;
}
.zj-button-default:hover{
    background: #ecf5ff;
    border-color: #c6e2ff;
    color: #409eff;
}
.zj-button-default.is-plain{
    color: #606266;
    background-color: #fff;
    border-color: #dcdfe6;
}
.zj-button-default.is-plain:hover{
    color: #409eff;
    background-color: #ffffff;
    border-color: #409eff;
}
.zj-button-primary{
    color: #fff;
    background-color: #409eff;
    border-color: #409eff;
}
.zj-button-primary:hover{
    background: #66b1ff;
    border-color: #66b1ff;
    color: #fff;
}
.zj-button-primary.is-plain{
    color: #409eff;
    background-color: #eff7ff;
    border-color: #409eff;
}
.zj-button-primary.is-plain:hover{
    background: #409eff;
    border-color: #409eff;
    color: #fff;
}
.zj-button-success{
    color: #fff;
    background-color: #67c23a;
    border-color: #67c23a;
}
.zj-button-success:hover{
    background: #85ce61;
    border-color: #85ce61;
    color: #fff;
}
.zj-button-success.is-plain{
    color: #67c23a;
    background-color: #f0f9eb;
    border-color: #67c23a;
}
.zj-button-success.is-plain:hover{
    background: #67c23a;
    border-color: #67c23a;
    color: #fff;
}

.zj-button-warning{
    color: #fff;
    background-color: #e6a23c;
    border-color: #e6a23c;
}
.zj-button-warning:hover{
    background: #ebb563;
    border-color: #ebb563;
    color: #fff;
}
.zj-button-warning.is-plain{
    color: #e6a23c;
    background-color: #fdf6ec;
    border-color: #e6a23c;
}
.zj-button-warning.is-plain:hover{
    background: #e6a23c;
    border-color: #e6a23c;
    color: #fff;
}
.zj-button-danger{
    color: #fff;
    background-color: #f56c6c;
    border-color: #f56c6c;
}
.zj-button-danger:hover{
    background: #f78989;
    border-color: #f78989;
    color: #fff;
}
.zj-button-danger.is-plain{
    color: #f56c6c;
    background-color: #fef0f0;
    border-color: #f56c6c;
}
.zj-button-danger.is-plain:hover{
    background: #f56c6c;
    border-color: #f56c6c;
    color: #fff;
}
.zj-button-info{
    color: #fff;
    background-color: #909399;
    border-color: #909399;
}
.zj-button-info{
    color: #fff;
    background-color: #909399;
    border-color: #909399;
}
.zj-button-info:hover{
    background: #a6a9ad;
    border-color: #a6a9ad;
    color: #fff;
}
.zj-button-info.is-plain{
    color: #909399;
    background-color: #f4f4f5;
    border-color: #909399;
}
.zj-button-info.is-plain:hover{
    background: #909399;
    border-color: #909399;
    color: #fff;
}
.is-round{
    border-radius: 20px;
}
.is-circle{
    border-radius: 50%;
    padding: 12px;
}
.zj-button+[class*=zj-icon-]+span{
    margin-left: 5px;
}
.is-disabled{
    cursor: not-allowed;
    opacity: 0.6;
}
.is-disabled:hover{
    cursor: not-allowed;
    opacity: 0.6;
}

组件结构

<template>
  <button class="zj-button"
   :class="[`zj-button-${type}`,
       {'is-plain':plain},
       {'is-round':round},
       {'is-circle':circle},
       {'is-disabled':disabled}]"
       :disabled="disabled"
    >
    <i v-if="icon" :class="` zj-icon-${icon}`"></i>
    <span v-if="$slots.default">
        <slot></slot>
    </span>
  </button>
</template>

组件数据和方法

<script>
export default {
  name: 'ZjButton',
  props: {
    type: {
      type: String,
      default: 'primary'
    },
    plain: {
      type: Boolean,
      default: false
    },
    round: {
      type: Boolean,
      default: false
    },
    circle: {
      type: Boolean,
      default: false
    },
    icon: {
      type: String,
      default: 'default'
    },
    disabled: {
      type: Boolean,
      default: false
    }
  }
}
</script>

全局引入

main.js中

import { createApp } from 'vue'
import App from './App.vue'
import ZjButton from '../src/components/Button.vue'
import ZjDialog from '../src/components/Dialog.vue'
import ZjInput from '../src/components/Input.vue'
import ZjSwitch from '../src/components/Switch.vue'
import ZjRadio from '../src/components/Radio.vue'
import ZjRadioGroup from '../src/components/RadioGroup.vue'
import ZjCheckBox from '../src/components/CheckBox.vue'
import ZjCheckBoxGroup from '../src/components/CheckBoxGroup.vue'
import ZjFormItem from '../src/components/FormItem.vue'
import ZjForm from '../src/components/Form.vue'
// import '../src/assets/fonts/font.scss'
import '../src/assets/font2/iconfont.css'
const app = createApp(App)
const components = [ZjButton, ZjDialog, ZjInput, ZjSwitch, ZjRadio, ZjRadioGroup, ZjCheckBox, ZjCheckBoxGroup, ZjFormItem, ZjForm]
components.forEach(component => {
  app.component(component.name, component)
})
app.mount('#app')

Message消息提示

Message不同于Input,Form等组件,使用的时候是通过函数传入选项来调用的

import Message from '../Message/Message.vue'
import { render, createVNode } from 'vue'
const instances = []
const MessagePlugin = {
  install (app) {
    function generateInstance (options) {
      let offset = options.offset || 20
      instances.forEach(item => {
        offset += item.el.offsetHeight + 20
      })
      const params = {
        ...options,
        offset
      }
      const div = document.createElement('div') // 创建一个div
      const vnode = createVNode(Message, params) // 创建一个message组件的虚拟DOM
      vnode.props.onDestroy = () => {
        render(null, div)
        instances.pop()
      }
      render(vnode, div) // 渲染虚拟DOM
      document.body.appendChild(div.firstElementChild) // 加入到body中
      instances.push(vnode)
    }

    app.config.globalProperties.$message = function (options) {
      generateInstance(options)
    }
  }
}

export default MessagePlugin

轮播图组件

需求数据类型默认值
heightString200px
autoplayBooleantrue
durationNumber1000
initialNumber0
hasDotBooleantrue
hasDirectorBooleantrue

用法:

  <ZjCarousel
  :has-director="true"
  :has-dot="true"
  :initial="0"
   indicator-position="outside"
   :autoplay="true" :duration="2000">
    <ZjCarouselItem v-for="(item,index) in list" :key="index">
      <!-- <h3 text="2xl">{{ item }}</h3> -->
      <img :src="require(`./assets/${index+1}.png`)" style="width: 300px;" :alt="item">
    </ZjCarouselItem>
  </ZjCarousel>

Carousel:

Carousel结构

    <div class="zj-carousel"
    :style="{
        height: height,
        width: width,
        overflow: 'hidden',
    }"
    @mouseenter="mouseEnter"
    @mouseleave="mouseLeave"
    >
        <div class="zj-carousel_inner" >
            <slot></slot>
        </div>
            <ZjDirective @dirClick="dirClick"></ZjDirective>

        <div class="zj-carousel__dots" v-if="hasDot">
            <ZjDots
            :itemLen="itemLength"
             :currentIndex="currentIndex"
             @dotClick="dotClick"
             ></ZjDots>
        </div>
    </div>

Carousel样式

<style lang="scss" scoped>
.zj-carousel{
    width: 100%;
    height: auto;
    .zj-carousel_inner{
        width: 100%;
        height: 100%;
        position: relative;
        text-align: center;
        vertical-align:middle;
    }
}
.zj-carousel_container{
    position: relative;
    height: 30px;
    z-index: 999;
}
.zj-carousel__dots{
    display: flex;
    position: absolute;
    left: 50%;
    align-items: center;
    text-align: center;
    transform: translateX(-50%);
}
</style>

Carousel数据和方法

<script>
import { reactive, toRefs, onMounted, onBeforeUnmount, getCurrentInstance, watch } from 'vue'
export default {
  name: 'ZjCarousel',
  props: {
    height: {
      type: String,
      default: '200px'
    },
    width: {
      type: String,
      default: '100%'
    },
    autoplay: {
      type: Boolean,
      default: true
    },
    duration: {
      type: Number,
      default: 1000
    },
    initial: {
      type: Number,
      default: 0
    },
    hasDot: {
      type: Boolean,
      default: true
    },
    hasDirector: {
      type: Boolean,
      default: true
    }
  },
  setup (props) {
    const instance = getCurrentInstance()
    const state = reactive({
      currentIndex: props.initial,
      itemLength: 0
    })
    let timer = null
    const mouseEnter = () => {
      _clearInterval()
    }
    const mouseLeave = () => {
      autoPlay()
    }
    function _clearInterval () {
      clearInterval(timer)
      timer = null
    }
    const setIndex = (dir) => {
      switch (dir) {
        case 'prev':
          state.currentIndex -= 1
          if (state.currentIndex === -1) {
            console.log(state.currentIndex)

            state.currentIndex = state.itemLength - 1
          }
          break
        case 'next':
          state.currentIndex += 1
          if (state.currentIndex === state.itemLength) {
            state.currentIndex = 0
          }
          break
        default:
          break
      }
    }
    const dotClick = (index) => {
      console.log(index)
      state.currentIndex = index - 1
    }
    const dirClick = (dir) => {
      if (dir === 'prev') {
        setIndex('prev')
      } else {
        setIndex('next')
      }
    }
    const autoPlay = () => {
      if (props.autoplay === true) {
        timer = setInterval(() => {
          setIndex('next')
        }, props.duration)
      }
    }
    watch(() => state.itemLength, (val) => {
      state.itemLength = val
    })
    onMounted(() => {
      state.itemLength = instance.slots.default()[0].children.length
      autoPlay()
    })
    onBeforeUnmount(() => {
      _clearInterval()
    })
    return {
      dotClick,
      setIndex,
      mouseEnter,
      mouseLeave,
      dirClick,
      ...toRefs(state)
    }
  }
}
</script>

CarouselItem:

CarouselItem结构:

<template>
  <transition name="fade">
    <div class="zj-carousel-item"
    :class="{active: active}"
    v-show="selfIndex === currentIndex"
    >

            <slot></slot>
    </div>
  </transition>
</template>

CarouselItem数据和方法:

<script>
import { getCurrentInstance, reactive, toRefs, watch } from 'vue'
export default {
  name: 'ZjCarouselItem',
  data () {
    return {
      current: 0
    }
  },
  props: {
    active: {
      type: Boolean,
      default: false
    }
  },
  setup () {
    const instance = getCurrentInstance()
    const state = reactive({
      selfIndex: instance.vnode.key,
      currentIndex: instance.parent.ctx.currentIndex,
      isNext: true
    })
    console.log(state)
    watch(() => instance.parent.ctx.currentIndex, (val) => {
      state.currentIndex = val
    })
    return {
      ...toRefs(state)
    }
  }
}
</script>

CarouselItem样式:

<style lang="scss" scoped>
.zj-carousel-item{
    width: 100%;
    height: auto;
    position: absolute;
    top:0;
    left: 0;
    img{
        width: 100%;
        height: 100%;
        margin: 0 auto;
    }
}
.fade-enter-active, .fade-leave-active {
  transition: all .5s linear;
}
.fade-enter-active{
  transform: translateX(100%);
}
.fade-leave-active{
  transform: translateX(-100%);
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}
.fade-leave-to{
  transform: translateX(-100%);
}
.fade-enter-to{
  transform: translateX(0);
}
</style>

Directive:(左右箭头控制轮播图前进后退) Directive结构:

<template>
    <div v-if="hasDirector" class="zj-carousel__indicators">
   <div class="left" @click="dirClick('prev')">
    <i class="el-icon-arrow-left">{{'<'}}</i>
   </div>
    <div class="right" @click="dirClick('next')">
        <i class="el-icon-arrow-right">{{'>'}}</i>
    </div>
    </div>
</template>

Directive数据和方法:

<script>
export default {
  name: 'ZjDirective',
  props: {
    hasDirector: {
      type: Boolean,
      default: true
    },
    dir: String
  },
  setup (props, ctx) {
    const dirClick = (dir) => {
      ctx.emit('dirClick', dir)
    }
    return {
      dirClick
    }
  }
}
</script>

Directive样式:

<style lang="scss">
.zj-carousel__indicators{
    display: flex;
    position: absolute;
    top: 10%;
    left: 0;
    justify-content: space-between;
    align-items: center;
    width: inherit;
    height: 40px;
}
.left{
     width: 40px;
     height: 40px;
     display: flex;
     justify-content: center;
     align-items: center;
     cursor: pointer;
     &:hover{
         background-color: rgb(218, 218, 218);
         color:aliceblue;
         width: 40px;
         height: 40px;
         border-radius: 50%;
     }
}
.right{
     width: 40px;
     height: 40px;
     display: flex;
     justify-content: center;
     align-items: center;
     cursor: pointer;
     &:hover{
         background-color: rgb(218, 218, 218);
         color:aliceblue;
         width: 40px;
         height: 40px;
         border-radius: 50%;
     }
 }

</style>

Dots:

需要itemLen作为底部导航小圆点个数限制,需要currentIndex活跃的item显示不同样式,点击事件dotClick(item),传递所点击的图片索引,切换父组件(轮播图)显示对应图片

 <div class="dots">
        <div class="dot"
         v-for="item in itemLen"
         :key="item"
         :class="{activeDot: currentIndex === item-1}"
         @click="dotClick(item)"
         ></div>
    </div>

Dots样式

<style lang="scss">
.dots {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 80px;
  height: 20px;
  z-index: 3000;
}
.dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  z-index: 3000;
  background: #8d8d8d;
}
.activeDot{
    background: #ffcaca;
}
</style>
<script>
export default {
  name: 'ZjDots',
  props: {
    itemLen: {
      type: Number,
      default: 4
    },
    currentIndex: {
      type: Number,
      default: 0
    }
  },
  setup (props, ctx) {
    const dotClick = (index) => {
      ctx.emit('dotClick', index)
    }
    return {
      dotClick
    }
  }
}
</script>

Card组件

用法示例:

<ZjCard style="max-width: 480px" shadow="Never">
    <template #header>
      <div class="card-header">
        <span>Card name</span>
      </div>
    </template>
    <p v-for="o in 4" :key="o" class="text item">{{ 'List item ' + o }}</p>
    <template #footer>Footer content</template>
</ZjCard>
需求类型默认值
shadowStringalways/Never/hover

样式

<style lang="scss">
.zj-card{
    width: 100%;
    height: 100%;
    border: 1px solid #ccc;
    border-radius: 5px;
    padding: 10px 0;
    margin: 5px;
    box-sizing: border-box;
    transition: all 0.3s;
    .zj-card-header{
        padding: 15px;
        border-bottom: 1px solid #ccc;
    }
    .zj-card-body{
        padding: 10px 15px;
    }
    .zj-card-footer{
        padding: 15px;
        border-top: 1px solid #ccc;
    }
}
.zj-card-always{
    box-shadow: 0 0 10px #ccc;
}
.zj-card-hover{
    &:hover{
        box-shadow: 0 0 10px #ccc;
    }
}
.zj-card-never{
    box-shadow: none;
}
</style>

Slots插槽

Card结构

<template>
    <div class="zj-card" :class="`zj-card-${shadow}`">
        <div class="zj-card-header" v-if="$slots.header">
            <slot name="header"></slot>
        </div>
        <div class="zj-card-body">
            <slot></slot>
        </div>
        <div class="zj-card-footer" v-if="$slots.footer">
            <slot name="footer"></slot>
        </div>
    </div>
</template>

Card

<script>
export default {
  name: 'ZjCard',
  props: {
    shadow: {
      type: String,
      default: 'always'
    }
  }
}
</script>