1.手写分页组件
效果图:
适合vue3,稍作修改可用于vue2版本
<template> <div class="xtx-pagination"> <a href="javascript:;" v-if="myCurrentPage===1" class="disabled">上一页</a> <a href="javascript:;" v-else @click="go(-1)">上一页</a> <!-- 如果起点按钮大于2就显示 --> <span v-if="pageInfo.start>2">...</span> <a href="javascript:;" @click="changePage(item)" :class="{active: myCurrentPage === item}" v-for="(item,idx) in pageInfo.pager" :key="idx"> {{item}} </a> <!-- 如果当前终点按钮小于总页数就显示 当终点按钮是20总按钮也是20 就不显示 --> <span v-if="pageInfo.end < pageInfo.pageCount">...</span> <!-- 如果当前按钮等于最后一页就禁止点击 disabled --> <a href="javascript:;" v-if="myCurrentPage==pageInfo.end" class="disabled">下一页</a> <a href="javascript:;" v-else @click="go(1)">下一页</a> </div> </template> <script> import { computed, ref, watch } from 'vue' export default { name: 'XtxPagination', props: { total: { type: Number, default: 100 }, pageSize: { type: Number, default: 10 }, currentPage: { type: Number, default: 1 }, btnCount: { type: Number, default: 5 } }, setup (props, { emit }) { const myTotal = ref(100) // 总条数 const myPageSize = ref(5) // 每页共几条 const myCurrentPage = ref(3) // 用户实时点击,修改 const myBtnCount = ref(5) // 分页按钮的个数5个 // 让当前的页码处于正中间 // const pager = ref([1, 2, 3, 4, 5]) // 根据上边信息,实时计算 pager,起始页码,结束页码 const pageInfo = computed(() => { // 总页数 = 总条数/每页几条 const pageCount = Math.ceil(myTotal.value / myPageSize.value) // 起点 = 当前页数-总页数/2 举例 3 - Math.floor(5/2) = 1 let start = myCurrentPage.value - Math.floor((myBtnCount.value / 2)) // 终点 = 起点页数 + 总页数 - 1 距离 1 + 5 -1 || 3 + 5 -1 let end = start + myBtnCount.value - 1 // 意外1 当起点小于1 if (start < 1) { start = 1 // 终点= 当前页数>总页数?总页数 否则 当前页数 end = myBtnCount.value > pageCount ? pageCount : myBtnCount.value } // 意外2 当终点大于最大页码 if (end > pageCount) { end = pageCount // 起点= 终点+(-所有页数+1)>1?1:= 终点+(-所有页数+1) start = (end - myBtnCount.value + 1) < 1 ? 1 : (end - myBtnCount.value + 1) } const pager = [] for (let i = start; i <= end; i++) { pager.push(i) } return { start, end, pageCount, pager } }) // 上一页下一页 const go = (step) => { myCurrentPage.value += step } const changePage = (page) => { // 如果等于现在页页码 保持不动 if (page === myCurrentPage.value) return myCurrentPage.value = page emit('currentCahnge', page) } // 监听传入的值改变 watch(props, () => { myTotal.value = props.total myPageSize.value = props.pageSize myCurrentPage.value = props.currentPage myBtnCount.value = props.btnCount }, { immediate: true }) return { myTotal, myPageSize, myCurrentPage, myBtnCount, pageInfo, go, changePage } } } </script> <style scoped lang="less"> .xtx-pagination { display: flex; justify-content: center; padding: 30px; > a { display: inline-block; padding: 5px 10px; border: 1px solid #e4e4e4; border-radius: 4px; margin-right: 10px; &:hover { color: @xtxColor; } &.active { background: @xtxColor; color: #fff; border-color: @xtxColor; } &.disabled { cursor: not-allowed; opacity: 0.4; &:hover { color: #333 } } } > span { margin-right: 10px; } } </style>
2.手写轮播图组件
功能:
- 图片切换显示
- 上一张,下一张按钮点击
- 底部指示条高亮
- 自动播放,鼠标移入开始,鼠标移出继续
主要逻辑:
一: 自动播放:
- 暴露自动轮播属性,设置了就自动轮播
- 暴露自动播放间隔时间
- 如果有自动播放,鼠标进入离开,暂停,开启
- 销毁组件时清理定时器
- 离开暂停: 如果有自动播放,鼠标进入离开,暂停,开启
销毁组件,清理定时器
二: 上下切换指示器
思路: 封装一个函数,处理上一张,下一张按钮的回调。
<template> <div class='xtx-carousel'> <ul class="carousel-body"> <li class="carousel-item fade"> <RouterLink to="/"> <img src="http://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-04-15/1ba86bcc-ae71-42a3-bc3e-37b662f7f07e.jpg" alt=""> </RouterLink> </li> </ul> <a href="javascript:;" class="carousel-btn prev"><i class="iconfont icon-angle-left"></i></a> <a href="javascript:;" class="carousel-btn next"><i class="iconfont icon-angle-right"></i></a> <div class="carousel-indicator"> <span v-for="i in 5" :key="i"></span> </div> </div> </template> <script> export default { name: 'XtxCarousel' } </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%; } } &-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>
3.手写提示框组件
效果:
思路:
组件:
<template> <div class="xtx-confirm" :class="{fade}"> <div class="wrapper" :class="{fade}"> <div class="header"> <h3>{{title}}</h3> <a @click="cancelCallback()" href="JavaScript:;" class="iconfont icon-close-new"></a> </div> <div class="body"> <i class="iconfont icon-warning"></i> <span>{{text}}</span> </div> <div class="footer"> <XtxButton @click="cancelCallback()" size="mini" type="gray">取消</XtxButton> <XtxButton @click="submitCallback()" size="mini" type="primary">确认</XtxButton> </div> </div> </div> </template> <script> // 当前组件不是在APP下进行渲染,无法使用APP下的环境(全局组件,全局指令,原型属性函数) import XtxButton from '@/components/XtxButton.vue' import { onMounted, ref } from 'vue' export default { name: 'XtxConfirm', components: { XtxButton }, props: { title: { type: String, default: '温馨提示' }, text: { type: String, default: '' }, submitCallback: { type: Function }, cancelCallback: { type: Function } }, setup () { const fade = ref(false) onMounted(() => { // 当元素渲染完毕立即过渡的动画不会触发 fade.value = true }) return { fade } } } </script> <style scoped lang="less"> .xtx-confirm { position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-index: 8888; background: rgba(0,0,0,0); &.fade { transition: all 0.4s; background: rgba(0,0,0,.5); } .wrapper { width: 400px; background: #fff; border-radius: 4px; position: absolute; top: 50%; left: 50%; transform: translate(-50%,-60%); opacity: 0; &.fade { transition: all 0.4s; transform: translate(-50%,-50%); opacity: 1; } .header,.footer { height: 50px; line-height: 50px; padding: 0 20px; } .body { padding: 20px 40px; font-size: 16px; .icon-warning { color: @priceColor; margin-right: 3px; font-size: 16px; } } .footer { text-align: right; .xtx-button { margin-left: 20px; } } .header { position: relative; h3 { font-weight: normal; font-size: 18px; } a { position: absolute; right: 15px; top: 15px; font-size: 20px; width: 20px; height: 20px; line-height: 20px; text-align: center; color: #999; &:hover { color: #666; } } } } } </style>
转换成函数调用
Confirm.js
// 实现使用函数调用xtx-message组件的逻辑 // 引入 创建虚拟节点 和渲染方法 import { createVNode, render } from 'vue' // 引入信息提示组件 import XtxConfirm from './xtxConfirm' // 准备dom容器 const div = document.createElement('div') // 添加类名 div.setAttribute('class', 'xtx-message-container') // 添加到body上 document.body.appendChild(div) // 该函数渲染XtxConfirm组件,标题和文本 // 函数的返回值是promise对象 export default ({ type, text }) => { return new Promise((resolve, reject) => { // 1. 渲染组件 // 2. 点击确认按钮,触发resolve同时销毁组件 // 3. 点击取消按钮,触发reject同时销毁组件 const submitCallback = () => { render(null, div) resolve() } const cancelCallback = () => { render(null, div) // 希望给出promise错误原因 reject(new Error()) } // 实现:根据xtx-message.vue渲染消息提示 // 1. 导入组件 // 2. 根据组件创建虚拟节点 第一个参数为要创建的虚拟节点 第二个参数为props的参数 const vnode = createVNode(XtxConfirm, { type, text, submitCallback, cancelCallback }) // 3. 准备一个DOM容器 // 4. 把虚拟节点渲染DOM容器中 render(vnode, div) }) }
使用:
<button @click="delete">删除</button> <script> import Confirm from '@/components/confirm' export default { setup () { const delete= () => { Confirm({ text: '您确定从购物车删除该商品吗?' }).then(() => { console.log('点击确认') }).catch(e => { console.log('点击取消') }) } return { delete} } } </script>