vue3自定义封装组件:消息提示、轮播图、加载更多、骨架屏组件

加载更多组件 

定义组件:src/components/library/xtx-infinite-loading.vue

<template>
  <div class="xtx-infinite-loading" ref="container">
    <div class="loading" v-if="loading">
      <span class="img"></span>
      <span class="text">正在加载...</span>
    </div>
    <div class="none" v-if="finished">
      <span class="img"></span>
      <span class="text">亲,没有更多了</span>
    </div>
  </div>
</template>

<script>
import { ref } from 'vue'
import { useIntersectionObserver } from '@vueuse/core'
export default {
  name: 'XtxInfiniteLoading',
  props: {
    loading: {
      type: Boolean,
      default: false
    },
    finished: {
      type: Boolean,
      default: false
    }
  },
  setup (props, { emit }) {
    const container = ref(null)
    useIntersectionObserver(
      container,
      ([{ isIntersecting }], dom) => {
        if (isIntersecting) {
          if (props.loading === false && props.finished === false) {
            emit('infinite')
          }
        }
      },
      {
        threshold: 0
      }
    )
    return { container }
  }
}
</script>

<style scoped lang='less'>
.xtx-infinite-loading {
  .loading {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 200px;
    .img {
      width: 50px;
      height: 50px;
      background: url(../../assets/images/load.gif) no-repeat center / contain;
    }
    .text {
      color: #999;
      font-size: 16px;
    }
  }
  .none {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 200px;
    .img {
      width: 200px;
      height: 134px;
      background: url(../../assets/images/none.png) no-repeat center / contain;
    }
    .text {
      color: #999;
      font-size: 16px;
    }
  }
}
</style>

注册组件:src/components/library/index.js

import XtxInfiniteLoading from './xtx-InfiniteLoading.vue'
export default {
    install(app) {
        app.component(XtxInfiniteLoading.name, XtxInfiniteLoading)
    }
}

 引用组件:src/main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

// 导入自定插件
import ui from './components/library'

// 创建一个vue应用实例
createApp(App).use(store).use(router).use(ui).mount('#app')

使用组件: .vue文件

 <XtxInfiniteLoading :loading="loading" :finished="finished" 
   @infinite="getData" />

<script>
import { ref} from 'vue'
import { useRoute } from 'vue-router'
import { findSubCategoryGoods } from '@/api/category'
export default {
  name: 'SubCategory',
  setup () {
    const loading = ref(false)
    const finished = ref(false)
    const route = useRoute()
    const goodsList = ref([])
    // 查询参数
    let reqParams = {
      page: 1,
      pageSize: 20
    }

    // 获取数据函数
    const getData = () => {
      loading.value = true
      reqParams.categoryId = route.params.id
      findSubCategoryGoods(reqParams).then(({ result }) => {
        if (result.items.length) {
          goodsList.value.push(...result.items)
          reqParams.page++
        } else {
          // 加载完毕
          finished.value = true
        }
        // 请求结束
        loading.value = false
      })
    }
    return { loading, finished, getData }
  }
}
</script>

轮播图组件 

首先是轮播图的样式:src/components/library/xtx-carousel.vue 

<template>
</template>

<script>
</script>

<style scoped lang="less">
.xtx-carousel {
    width: 100%;
    height: 100%;
    min-width: 300px;
    min-height: 150px;
    position: relative;

    .carousel {
        &-body {
            width: 100%;
            height: 100%;
        }

        &-item {
            width: 100%;
            height: 100%;
            position: absolute;
            left: 0;
            top: 0;
            opacity: 0;
            transition: opacity 0.5s linear;

            &.fade {
                opacity: 1;
                z-index: 1;
            }

            img {
                width: 100%;
                height: 100%;
            }

            // 轮播商品
            .slider {
                display: flex;
                justify-content: space-around;
                padding: 0 40px;

                >a {
                    width: 240px;
                    text-align: center;

                    img {
                        padding: 20px;
                        width: 230px !important;
                        height: 230px !important;
                    }

                    .name {
                        font-size: 16px;
                        color: #666;
                        padding: 0 40px;
                    }

                    .price {
                        font-size: 16px;
                        color: @priceColor;
                        margin-top: 15px;
                    }
                }
            }
        }

        &-indicator {
            position: absolute;
            left: 0;
            bottom: 20px;
            z-index: 2;
            width: 100%;
            text-align: center;

            span {
                display: inline-block;
                width: 12px;
                height: 12px;
                background: rgba(0, 0, 0, 0.2);
                border-radius: 50%;
                cursor: pointer;

                ~span {
                    margin-left: 12px;
                }

                &.active {
                    background: #fff;
                }
            }
        }

        &-btn {
            width: 44px;
            height: 44px;
            background: rgba(0, 0, 0, .2);
            color: #fff;
            border-radius: 50%;
            position: absolute;
            top: 228px;
            z-index: 2;
            text-align: center;
            line-height: 44px;
            opacity: 0;
            transition: all 0.5s;

            &.prev {
                left: 20px;
            }

            &.next {
                right: 20px;
            }
        }
    }

    &:hover {
        .carousel-btn {
            opacity: 1;
        }
    }
}
</style>

然后是轮播图的结构与逻辑封装:src/components/library/xtx-carousel.vue

<template>
  <div class='xtx-carousel' @mouseenter="stop()" @mouseleave="start()">
    <ul class="carousel-body">
    <!--  fade是控制显示那的那张图片 需要一个默认索引数据,
    渲染第一张图和激活第一个点 -->
      <li class="carousel-item" v-for="(item,i) in sliders" :key="i" 
       :class="{fade:index===i}">
        <RouterLink to="/">
          <img :src="item.imgUrl" alt="">
        </RouterLink>
      </li>
    </ul>
    <!-- 左右点击按钮 -->
    <a @click="toogle(-1)" href="javascript:;" class="carousel-btn prev">
       <i class="iconfont icon-angle-left"></i>
    </a>
    <a @click="toogle(1)" href="javascript:;" class="carousel-btn next">
       <i class="iconfont icon-angle-right"></i>
    </a>
    <!-- 跟随的小点 -->
    <div class="carousel-indicator">
      <span v-for="(item,i) in sliders" :key="i" 
       :class="{active:index===i}"></span>
    </div>
  </div>
</template>
<script>
import { onUnmounted, ref, watch } from 'vue'
export default {
    name: 'XtxCarousel',
    props: {
        silders: {
            type: Array,
            default: () => []
        },
        //自动轮播的间隙
        duration: {
            type: Number,
            default: 3000
        },
        //是否自动轮播
        antoPlay: {
            type: Boolean,
            default: true
        }
    },
    setup(props) {
        // 设置默认索引
        const index = ref(0)
        // 自动播放
        let timer = null
        const autoPlayFn = () => {
            clearInterval(timer)
            timer = setTimeout(() => {
                index.value++

                if (index.value >= props.silders.length) {
                    index.value = 0
                }
            }, props.duration)
        }

        watch(() => index.value, () => {
            // 有数据 并且开启了自动播放,才调用自动播放
            if (props.sliders.length && props.antoPlay) {
                autoPlayFn()
            }
        }, { immediate: true })

        // 鼠标进入轮播 计时停止
        const stop = () => {
            if (timer) clearInterval(timer)
        }

        // 鼠标离开轮播 计时开始
        const start = () => {
            if (props.silders.length && props.antoPlay) {
                autoPlayFn()
            }
        }

        // 左右按钮切换
        const toogle = (step) => {
            const newIndex = index.value + step
            if (newIndex >= props.silders.length) {
                index.value = 0
            } else if (newIndex < 0) {
                index.value = props.silders.length - 1
            } else {
                index.value = newIndex
            }

        }

        // 销毁计时器
        onUnmounted(() => {
            clearInterval(timer)
        })
        return {
            index,
            stop,
            start,
            timer,
            toogle
        }
    }
}
</script>

插件注册:src/components/library/index.js

+import XtxCarousel from './xtx-carousel.vue'


export default {
  install (app) {
    +app.component(XtxCarousel.name, XtxCarousel)
  }
}

插件使用:src/main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'normalize.css'

+import ui from './components/library'

+createApp(App).use(store).use(router).use(ui).mount('#app')

在.vue文件中使用

<XtxCarousel :sliders="sliders" />

消息提示组件

封装组件: src/components/library/xtx-message.vue

<template>
  <div class="xtx-message" :style="style[type]">
    <!-- 上面绑定的是样式 -->
    <!-- 不同提示图标会变 -->
    <i class="iconfont" :class="[style[type].icon]"></i>
    <span class="text">{
   
   {text}}</span>
  </div>
</template>
<script>
export default {
  name: 'XtxMessage',
  props: {
    text: {
      type: String,
      default: ''
    },
    type: {
      type: String,
      // warn 警告  error 错误  success 成功
      default: 'warn'
    }
  },
  setup () {
    // 定义一个对象,包含三种情况的样式,对象key就是类型字符串
    const style = {
      warn: {
        icon: 'icon-warning',
        color: '#E6A23C',
        backgroundColor: 'rgb(253, 246, 236)',
        borderColor: 'rgb(250, 236, 216)'
      },
      error: {
        icon: 'icon-shanchu',
        color: '#F56C6C',
        backgroundColor: 'rgb(254, 240, 240)',
        borderColor: 'rgb(253, 226, 226)'
      },
      success: {
        icon: 'icon-queren2',
        color: '#67C23A',
        backgroundColor: 'rgb(240, 249, 235)',
        borderColor: 'rgb(225, 243, 216)'
      }
    }
    return { style }
  }
}
</script>
<style scoped lang="less">
.xtx-message {
  width: 300px;
  height: 50px;
  position: fixed;
  z-index: 9999;
  left: 50%;
  margin-left: -150px;
  top: 25px;
  line-height: 50px;
  padding: 0 25px;
  border: 1px solid #e4e4e4;
  background: #f5f5f5;
  color: #999;
  border-radius: 4px;
  i {
    margin-right: 4px;
    vertical-align: middle;
  }
  .text {
    vertical-align: middle;
  }
}
</style>

插件定义:src/componets/library/index.js

import XtxMessage from './xtx-message.vue'

export default {
  install (app) {
    // 在app上进行扩展,app提供 component directive函数
    // component是用来进行全局组件注册的
    // directive主要是用来自定义指令的
    // 如果要挂载原型 app.config.globalProperties 方式
    app.component(XtxMessage.name, XtxMessage)
  }
}

使用插件:src/main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'normalize.css'
+import ui from './components/library'

+// 插件的使用,在main.js使用app.use(插件)
+createApp(App).use(store).use(router).use(ui).mount('#app')

在.vue文件中使用:

 <XtxMessage text="账号密码错误" type="error"/>

骨架屏组件

 封装组件:src/components/library/xtx-skeleton.vue

<template>
  <div class="xtx-skeleton" :style="{width,height}" :class="{shan:animated}">
    <!-- 1 盒子-->
    <div class="block" :style="{backgroundColor:bg}"></div>
    <!-- 2 闪效果 xtx-skeleton 伪元素 --->
  </div>
</template>
<script>
export default {
  name: 'XtxSkeleton',
  // 使用的时候需要动态设置 高度,宽度,背景颜色,是否闪下
  props: {
    bg: {
      type: String,
      default: '#efefef'
    },
    width: {
      type: String,
      default: '100px'
    },
    height: {
      type: String,
      default: '100px'
    },
    animated: {
      type: Boolean,
      default: false
    }
  }
}
</script>
<style scoped lang="less">
.xtx-skeleton {
  display: inline-block;
  position: relative;
  overflow: hidden;
  vertical-align: middle;
  .block {
    width: 100%;
    height: 100%;
    border-radius: 2px;
  }
}
.shan {
  &::after {
    content: "";
    position: absolute;
    animation: shan 1.5s ease 0s infinite;
    top: 0;
    width: 50%;
    height: 100%;
    background: linear-gradient(
      to left,
      rgba(255, 255, 255, 0) 0,
      rgba(255, 255, 255, 0.3) 50%,
      rgba(255, 255, 255, 0) 100%
    );
    transform: skewX(-45deg);
  }
}
@keyframes shan {
  0% {
    left: -100%;
  }
  100% {
    left: 120%;
  }
}
</style>

插件定义: src/componets/library/index.js

import XtxSkeleton from './xtx-skeleton.vue'

export default {
  install (app) {
    // 在app上进行扩展,app提供 component directive 函数
    // 如果要挂载原型 app.config.globalProperties 方式
    // component是用来进行全局组件注册的
    // directive主要是用来自定义指令的
    app.component(XtxSkeleton.name, XtxSkeleton)
  }
}

使用插件: src/main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'normalize.css'
+import ui from './components/library'

+// 插件的使用,在main.js使用app.use(插件)
+createApp(App).use(store).use(router).use(ui).mount('#app')

在.vue文件中使用:

<XtxSkeleton bg="#e4e4e4" width="306px" height="306px" animated />
<XtxSkeleton width="50px" height="18px" bg="rgba(255,255,255,0.2)" />

猜你喜欢

转载自blog.csdn.net/weixin_52479803/article/details/129882329
今日推荐