版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/csu_passer/article/details/89183314
我并不太喜欢直接使用UI库,因为往往不好直接修改源码来自定义。同时为了更好的理解Vue
,和以前编写的各种组件一样打算自己手动实现自己项目需求中的组件–
UI
UI
大体和Iview
保持一致
组件源码复制即用,没有animated.css
的项目就没有动画效果…
使用
<Page @pageClick="handleChangePage" :total="total" :current="current" :page-size="pageSize" @pageSizeChange="handlePageSizeChange"></Page>
需要自己计算切换每页显示条目后的当前页√
handleChangePage (index) {
this.current = index
},
handlePageSizeChange (size) {
this.current = Math.ceil(Math.ceil(this.total / size) * this.current / Math.ceil(this.total / this.pageSize))
this.pageSize = size
}
组件源码
<template>
<div class="page-container">
<span v-if="showTotal">共 {{total}} 条</span>
<Cell :title="'上一页'" :disabled="current===1" @click.native="current > 1 && $emit('pageClick', current - 1)"><</Cell><Cell v-for="(item, index) in groupList" :key="index" :active="item === current" :disabled="item==='...'" @click.native="item !== current && item !== '...' && $emit('pageClick', item)">{{item}}</Cell><Cell :title="'下一页'" :disabled="current===page" @click.native="current < page && $emit('pageClick', current + 1)">></Cell>
<Elevator v-if="showElevator"/>
<Options :options="pageSizeOpts" @pageSizeChange="$emit('pageSizeChange', $event)"/>
</div>
</template>
<script>
import Cell from './Cell'
import Elevator from './Elevator'
import Options from './Options'
export default {
name: 'Page',
components: {Options, Elevator, Cell},
props: {
current: {
type: Number,
default: 1
},
total: {
type: Number,
default: 0
},
pageSize: {
type: Number,
default: 10
},
pageSizeOpts: {
type: Array,
default () {
return [10, 20, 30, 40]
}
},
showElevator: {
type: Boolean,
default: false
},
showTotal: {
type: Boolean,
default: false
}
},
methods: {
/**
* 生成一个从 start 到 end 的连续数组 start 和 end都需是正整数 且 start < end
* @param start
* @param end
*/
generateArray (start, end) {
return Array.from(new Array(end + 1).keys()).slice(start)
}
},
computed: {
page () {
return Math.ceil(this.total / this.pageSize)
},
groupList () {
let ret = []
const pageTotal = this.page
const before = 3 // current所在数的前三后四
const after = 4
const current = this.current
if (pageTotal < before + after) {
ret = this.generateArray(1, pageTotal)
} else {
if (current < before) {
ret.push(...this.generateArray(1, before))
ret.push('...')
ret.push(pageTotal - 1)
ret.push(pageTotal)
} else if (current > this.page - after) {
ret.push(1)
ret.push(2)
ret.push('...')
ret.push(...this.generateArray(current - 1, current))
ret.push(...this.generateArray(current + 1, pageTotal))
} else {
if (current - before > 1) {
ret.push(1)
if (current - before > 2) {
ret.push('...')
}
}
ret.push(...this.generateArray(current - before > 0 ? current - before : 1, current + after))
if (current + after < pageTotal) {
if (current + after < pageTotal - 1) {
ret.push('...')
}
ret.push(pageTotal)
}
}
}
return ret
}
}
}
</script>
<style lang="less" scoped>
.page-container{
font-size: 12px;
color: #515a6e;
&>span{
margin-right: 10px;
}
}
</style>
<template>
<div class="page-elevator">跳至 <input title="" @blur="handleBlur" @keyup.enter="handleBlur" type="text" autocomplete="off" spellcheck="false">页</div>
</template>
<script>
export default {
name: 'Elevator',
methods: {
handleBlur (e) {
let value = e.target.value
if (value === '') {
return
}
let page = value.replace(/\D/g, '')
if (page !== '') {
this.$emit('changeTo', page)
e.target.value = ''
} else {
this.$message({
type: 'warn',
text: '请输入合法的页数'
})
}
}
}
}
</script>
<style lang="less" scoped>
.page-elevator{
display: inline-block;
vertical-align: middle;
height: 32px;
line-height: 32px;
font-size: 12px;
color: #515a6e;
input{
display: inline-block;
height: 32px;
line-height: 1.5;
padding: 4px 7px;
font-size: 12px;
border: 1px solid #dcdee2;
color: #515a6e;
background: #fff none;
position: relative;
cursor: text;
transition: border .2s ease-in-out,background .2s ease-in-out,box-shadow .2s ease-in-out;
border-radius: 4px;
margin: 0 8px;
width: 50px;
&:focus{
border-color: #57a3f3;
outline: 0;
box-shadow: 0 0 0 2px rgba(45,140,240,.2);
}
}
}
</style>
<template>
<div class="page-opt">
<div class="selected" @click="showOpt = !showOpt">
<span>{{options[current] + unit}}</span>
<img class="icon" src="../../assets/icon/arrow_down.png"/>
</div>
<transition enter-active-class="animated bounceIn" leave-active-class="animated fadeOut">
<ul class="opt" v-if="showOpt">
<li :class="['opt-item', {'opt-item-active': current === index}]" v-for="(item, index) in options" @click="handleClick(index)" :key="index">{{item + unit}}</li>
</ul>
</transition>
</div>
</template>
<script>
export default {
name: 'Options',
props: {
options: {
type: Array,
default () {
return []
}
}
},
methods: {
handleOtherClicked (e) {
if (!this.$el.contains(e.target)) {
this.showOpt = false
}
},
handleClick (index) {
if (index !== this.current) {
this.current = index
this.showOpt = false
this.$emit('pageSizeChange', this.options[index])
}
}
},
data () {
return {
current: 0,
unit: '条/页',
showOpt: false
}
},
mounted () {
document.addEventListener('click', this.handleOtherClicked)
},
destroyed () {
document.removeEventListener('click', this.handleOtherClicked)
}
}
</script>
<style lang="less" scoped>
.page-opt{
display: inline-block;
height: 32px;
position: relative;
.selected{
padding: 0 5px;
height: 100%;
border: 1px solid #dcdee2;
cursor: pointer;
border-radius: 4px;
user-select: none;
display: flex;
justify-content: space-around;
align-items: center;
img{
width: 12px;
height: 100%;
margin-left: 5px;
object-fit: contain;
}
&:hover{
border-color: #2d8cf0;
color: #2d8cf0;
}
}
.opt{
min-width: 100%;
list-style: none;
max-height: 200px;
overflow: auto;
margin: 5px 0;
padding: 5px 0;
background-color: #fff;
box-sizing: border-box;
border-radius: 4px;
box-shadow: 0 1px 6px rgba(0,0,0,.2);
position: absolute;
z-index: 900;
.opt-item{
margin: 0;
line-height: normal;
padding: 7px 16px;
clear: both;
color: #515a6e;
font-size: 12px;
white-space: nowrap;
list-style: none;
cursor: pointer;
transition: background .2s ease-in-out;
user-select: none;
&:hover{
background: #f3f3f3;
}
&-active{
color: #2d8cf0;
background: #f3f3f3;
}
}
}
}
</style>
<template>
<div :class="['table-cell', {'table-cell-active': active}, {'table-cell-disabled': disabled}]" :title="localTitle">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'Cell',
props: {
active: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
title: {
type: String,
default: ''
}
},
mounted () {
this.localTitle = this.title !== '' ? this.title : this.$slots.default[0].text
},
data () {
return {
localTitle: ''
}
}
}
</script>
<style lang="less" scoped>
.table-cell{
user-select: none;
min-width: 32px;
height: 32px;
list-style: none;
text-align: center;
cursor: pointer;
color: #666;
font-family: Arial,serif;
border: 1px solid #dcdee2;
border-radius: 4px;
transition: all .2s ease-in-out;
margin: 0 2px;
display: inline-flex;
justify-content: center;
align-items: center;
&:hover{
border-color: #2d8cf0;
color: #2d8cf0;
}
&-active{
border-color: #2d8cf0;
color: #2d8cf0;
}
&-disabled{
cursor: not-allowed;
color: #666 !important;
}
&-disabled:hover{
border-color: #dcdee2;
}
}
</style>