Skip to content
This repository was archived by the owner on Jan 8, 2025. It is now read-only.

Commit f57c42b

Browse files
committed
feat(ui/pagination): add component pagination
1 parent 6c57bc1 commit f57c42b

File tree

22 files changed

+2147
-3
lines changed

22 files changed

+2147
-3
lines changed

packages/varlet-vue2-ui/src/cell/Cell.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="var-cell" :class="[border ? 'var-cell--border' : null]">
2+
<div class="var-cell" :class="[border ? 'var-cell--border' : null]" @click="onClickCell">
33
<div class="var-cell__icon" :class="[iconClass ? iconClass : null]" v-if="hasSlots('icon') || icon">
44
<slot name="icon">
55
<var-icon class="var--flex" :name="icon" />
@@ -34,6 +34,12 @@ export default defineComponent({
3434
},
3535
3636
props,
37+
38+
methods: {
39+
onClickCell() {
40+
this.getListeners()?.onClick?.()
41+
},
42+
},
3743
})
3844
</script>
3945

packages/varlet-vue2-ui/src/icon/Icon.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export default defineComponent({
5959
isURL,
6060
toNumber,
6161
onClickIcon() {
62-
this.getListeners()?.onClick()
62+
this.getListeners()?.onClick?.()
6363
},
6464
},
6565
})

packages/varlet-vue2-ui/src/menu/Menu.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export default defineComponent({
108108
class="var-menu__menu var-elevation--3"
109109
ref="menu"
110110
style={this.transitionStyle}
111-
v-show:show={this.show}
111+
v-show={this.show}
112112
onClick={(event) => {
113113
event.stopPropagation()
114114
}}
Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
<template>
2+
<ul class="var-pagination">
3+
<li
4+
v-ripple="{ disabled: localCurrent <= 1 || disabled }"
5+
class="var-pagination__item var-pagination__prev"
6+
:class="{
7+
'var-pagination__item-disabled': localCurrent <= 1 || disabled,
8+
'var-pagination__item-hover': simple,
9+
'var-elevation--2': !simple,
10+
}"
11+
@click="clickItem('prev')"
12+
>
13+
<slot name="prev">
14+
<var-icon name="chevron-left" />
15+
</slot>
16+
</li>
17+
<li v-if="simple" class="var-pagination__simple" :class="{ 'var-pagination__item-disabled': disabled }">
18+
<var-input
19+
v-model="simpleValue"
20+
:disabled="disabled"
21+
var-pagination-cover
22+
@blur="setPage('simple', simpleValue, $event)"
23+
@keydown.enter="setPage('simple', simpleValue, $event)"
24+
/>
25+
<span>/ {{ pageCount }}</span>
26+
</li>
27+
<li
28+
v-else
29+
v-ripple="{ disabled }"
30+
v-for="(item, index) in pageList"
31+
:key="index"
32+
:item-mode="getMode(item, index)"
33+
class="var-pagination__item var-elevation--2"
34+
:class="{
35+
'var-pagination__item-active': item === localCurrent && !disabled,
36+
'var-pagination__item-hide': isHideEllipsis(item, index),
37+
'var-pagination__item-disabled': disabled,
38+
'var-pagination__item-disabled-active': item === localCurrent && disabled,
39+
}"
40+
@click="clickItem(item, index)"
41+
>
42+
{{ item }}
43+
</li>
44+
<li
45+
v-ripple="{ disabled: localCurrent >= pageCount || disabled }"
46+
class="var-pagination__item var-pagination__next"
47+
:class="{
48+
'var-pagination__item-disabled': localCurrent >= pageCount || disabled,
49+
'var-pagination__item-hover': simple,
50+
'var-elevation--2': !simple,
51+
}"
52+
@click="clickItem('next')"
53+
>
54+
<slot name="next">
55+
<var-icon name="chevron-right" />
56+
</slot>
57+
</li>
58+
59+
<li v-if="showSizeChanger" class="var-pagination__size" :class="{ 'var-pagination__item-disabled': disabled }">
60+
<var-menu :show.sync="menuVisible" :offset-x="-4">
61+
<div class="var-pagination__size-open" style="display: flex" @click="showMenu">
62+
<span>{{ localSize }}{{ pack.paginationItem }} / {{ pack.paginationPage }}</span>
63+
<var-icon class="var-pagination__size-open-icon" var-pagination-cover name="menu-down" />
64+
</div>
65+
66+
<template #menu>
67+
<var-cell
68+
class="var-pagination__list"
69+
:class="{
70+
'var-pagination__list-active': localSize === option,
71+
}"
72+
v-ripple
73+
v-for="(option, index) in sizeOption"
74+
:key="index"
75+
@click="clickSize(option)"
76+
>
77+
{{ option }}{{ pack.paginationItem }} / {{ pack.paginationPage }}
78+
</var-cell>
79+
</template>
80+
</var-menu>
81+
</li>
82+
<li
83+
v-if="showQuickJumper && !simple"
84+
class="var-pagination__quickly"
85+
:class="{ 'var-pagination__item-disabled': disabled }"
86+
>
87+
{{ pack.paginationJump }}
88+
<var-input
89+
v-model="inputValue"
90+
:disabled="disabled"
91+
var-pagination-cover
92+
@blur="setPage('quick', inputValue, $event)"
93+
@keydown.enter="setPage('quick', inputValue, $event)"
94+
/>
95+
</li>
96+
97+
<li v-if="totalText" class="var-pagination__total">
98+
{{ totalText }}
99+
</li>
100+
</ul>
101+
</template>
102+
103+
<script>
104+
import VarMenu from '../menu'
105+
import Ripple from '../ripple'
106+
import VarIcon from '../icon'
107+
import VarCell from '../cell'
108+
import VarInput from '../input'
109+
import { defineComponent } from '../utils/create'
110+
import { props } from './props'
111+
import { isNumber, toNumber } from '../utils/shared'
112+
import { pack } from '../locale'
113+
114+
export default defineComponent({
115+
name: 'VarPagination',
116+
117+
components: {
118+
VarMenu,
119+
VarIcon,
120+
VarCell,
121+
VarInput,
122+
},
123+
124+
directives: { Ripple },
125+
126+
props,
127+
128+
data() {
129+
return {
130+
menuVisible: false,
131+
inputValue: '',
132+
simpleValue: '1',
133+
isHideEllipsisHead: false,
134+
isHideEllipsisTail: false,
135+
pageList: [],
136+
localCurrent: this.toNumber(this.current) || 1,
137+
localSize: this.toNumber(this.size) || 10,
138+
}
139+
},
140+
141+
computed: {
142+
pack() {
143+
return pack.value
144+
},
145+
146+
activePosition() {
147+
return Math.ceil(this.maxPagerCount / 2)
148+
},
149+
150+
pageCount() {
151+
return Math.ceil(this.toNumber(this.total) / this.toNumber(this.localSize))
152+
},
153+
154+
range() {
155+
const start = this.localSize * (this.localCurrent - 1) + 1
156+
const end = Math.min(this.localSize * this.localCurrent, this.toNumber(this.total))
157+
return [start, end]
158+
},
159+
160+
totalText() {
161+
if (!this.showTotal) return ''
162+
return this.showTotal(this.toNumber(this.total), this.range)
163+
},
164+
165+
listenPropsSizeAndCurrent() {
166+
const { current, size } = this
167+
return { current, size }
168+
},
169+
170+
listenDataSizeAndCurrent() {
171+
const { localCurrent, localSize } = this
172+
return { localCurrent, localSize }
173+
},
174+
},
175+
176+
watch: {
177+
listenPropsSizeAndCurrent({ current, size }) {
178+
this.localCurrent = this.toNumber(current) || 1
179+
this.localSize = this.toNumber(size || 10)
180+
},
181+
182+
listenDataSizeAndCurrent: {
183+
handler(newValue, oldValue) {
184+
const { localCurrent: newCurrent, localSize: newSize } = newValue
185+
186+
let oldCurrent
187+
let oldSize
188+
if (oldValue) {
189+
oldCurrent = oldValue.localCurrent
190+
oldSize = oldValue.localSize
191+
}
192+
193+
if (newCurrent > this.pageCount) {
194+
this.localCurrent = this.pageCount
195+
return
196+
}
197+
198+
let list = []
199+
const { maxPagerCount, total, getListeners } = this
200+
const { onChange } = getListeners()
201+
const oldCount = Math.ceil(this.toNumber(total) / this.toNumber(oldSize))
202+
const rEllipseSign = this.pageCount - (maxPagerCount - this.activePosition) - 1
203+
this.simpleValue = `${newCurrent}`
204+
205+
if (this.pageCount - 2 > maxPagerCount) {
206+
if (oldCurrent === undefined || this.pageCount !== oldCount) {
207+
for (let i = 2; i < maxPagerCount + 2; i++) list.push(i)
208+
}
209+
210+
// 左边不需要出现省略符号占位
211+
if (newCurrent <= maxPagerCount && newCurrent < rEllipseSign) {
212+
list = []
213+
214+
for (let i = 1; i < maxPagerCount + 1; i++) {
215+
list.push(i + 1)
216+
}
217+
218+
this.isHideEllipsisHead = true
219+
this.isHideEllipsisTail = false
220+
}
221+
// 两边都需要出现省略符号占位
222+
if (newCurrent > maxPagerCount && newCurrent < rEllipseSign) {
223+
list = []
224+
225+
for (let i = 1; i < maxPagerCount + 1; i++) {
226+
list.push(newCurrent + i - this.activePosition)
227+
}
228+
229+
// 针对 maxPagerCount===1 的特殊处理
230+
this.isHideEllipsisHead = newCurrent === 2 && maxPagerCount === 1
231+
this.isHideEllipsisTail = false
232+
}
233+
// 右边不需要出现省略符号占位
234+
if (newCurrent >= rEllipseSign) {
235+
list = []
236+
237+
for (let i = 1; i < maxPagerCount + 1; i++) {
238+
list.push(this.pageCount - (maxPagerCount - i) - 1)
239+
}
240+
241+
this.isHideEllipsisHead = false
242+
this.isHideEllipsisTail = true
243+
}
244+
245+
list = [1, '...', ...list, '...', this.pageCount]
246+
} else {
247+
for (let i = 1; i <= this.pageCount; i++) list.push(i)
248+
}
249+
250+
this.pageList = list
251+
252+
if (oldCurrent !== undefined) onChange?.(newCurrent, newSize)
253+
getListeners()['onUpdate:current']?.(newCurrent)
254+
getListeners()['onUpdate:size']?.(newSize)
255+
},
256+
257+
immediate: true,
258+
259+
deep: true,
260+
},
261+
},
262+
263+
methods: {
264+
isNumber,
265+
266+
toNumber,
267+
268+
isHideEllipsis(item, index) {
269+
if (this.isNumber(item)) return false
270+
return index === 1 ? this.isHideEllipsisHead : this.isHideEllipsisTail
271+
},
272+
273+
getMode(item, index) {
274+
if (this.isNumber(item)) return 'basic'
275+
276+
return index === 1 ? 'head' : 'tail'
277+
},
278+
279+
clickItem(item, index) {
280+
if (item === this.localCurrent || this.disabled) return
281+
282+
if (this.isNumber(item)) this.localCurrent = item
283+
else if (item === 'prev') this.localCurrent > 1 && (this.localCurrent -= 1)
284+
else if (item === 'next') this.localCurrent < this.pageCount && (this.localCurrent += 1)
285+
else if (item === '...') {
286+
if (index === 1) {
287+
this.localCurrent = Math.max(this.localCurrent - this.maxPagerCount, 1)
288+
} else {
289+
this.localCurrent = Math.min(this.localCurrent + this.maxPagerCount, this.pageCount)
290+
}
291+
}
292+
},
293+
294+
showMenu() {
295+
if (this.disabled) return
296+
this.menuVisible = true
297+
},
298+
299+
clickSize(option) {
300+
this.localSize = option
301+
this.menuVisible = false
302+
},
303+
304+
isValidatePage(value) {
305+
const pattern = /^[1-9][0-9]*$/
306+
return pattern.test(value)
307+
},
308+
309+
setPage(type, value, event) {
310+
event.target.blur()
311+
if (this.isValidatePage(value)) {
312+
let valueNum = this.toNumber(value)
313+
314+
if (valueNum > this.pageCount) {
315+
valueNum = this.pageCount
316+
this.simpleValue = `${valueNum}`
317+
}
318+
319+
if (valueNum !== this.localCurrent) this.localCurrent = valueNum
320+
}
321+
if (type === 'quick') this.inputValue = ''
322+
323+
if (type === 'simple' && !this.isValidatePage(value)) this.simpleValue = `${this.localCurrent}`
324+
},
325+
},
326+
})
327+
</script>
328+
329+
<style lang="less">
330+
@import '../styles/common';
331+
@import '../menu/menu';
332+
@import '../cell/cell';
333+
@import '../input/input';
334+
@import '../ripple/ripple';
335+
@import '../icon/icon';
336+
@import './pagination';
337+
</style>

0 commit comments

Comments
 (0)