Jika situs Anda berisi konten dalam jumlah besar, maka untuk menampilkannya, pengguna harus membagikannya dengan satu atau lain cara.
Semua metode yang saya tahu memiliki kekurangan dan saya mencoba membuat sistem yang dapat menyelesaikan beberapa di antaranya tanpa terlalu sulit untuk diterapkan.
Metode yang ada
1. Penomoran halaman (pembagian menjadi halaman terpisah)
Penomoran halaman atau pemisahan menjadi halaman terpisah adalah cara yang cukup lama untuk membagi konten, yang juga digunakan di Habré. Keunggulan utamanya adalah keserbagunaan dan kemudahan penerapannya baik dari sisi server maupun dari sisi klien.
Kode untuk meminta data dari database paling sering dibatasi pada beberapa baris.
Di sini dan contoh lebih lanjut dalam bahasa aql arangodb, saya menyembunyikan kode server karena belum ada yang menarik di sana.
// 20 .
LET count = 20
LET offset = count * ${page}
FOR post IN posts
SORT post.date DESC //
LIMIT offset, count
RETURN post
Di sisi klien, kami meminta dan menampilkan hasil yang dihasilkan, saya menggunakan vuejs dengan nuxtjs sebagai contoh, tetapi hal yang sama dapat dilakukan pada tumpukan lain, saya akan menandatangani semua poin khusus vue.
# https://example.com/posts?page=3
main.vue
<template> <!-- template body -->
<div>
<template v-for="post in posts"> <!-- -->
<div :key="post.id">
{{ item.title }}
</div>
</template>
</div>
</template>
<script>
export default {
data() {
return {
posts: [], //
}
},
computed: { // this,
currentPage(){
// +
return +this.$route.query.page || 0
},
},
async fetch() { //
const page = this.currentPage
// ,
this.posts = await this.$axios.$get('posts', {params: {page}})
}
}
</script>
Sekarang kita memiliki semua posting di halaman yang ditampilkan, tapi tunggu, bagaimana pengguna akan beralih antar halaman? Mari tambahkan beberapa tombol untuk membalik halaman.
<template> <!-- template body -->
<div>
<div>
<template v-for="post in posts"> <!-- -->
<div :key="post.id">
{{ item.title }}
</div>
</template>
</div>
<div> <!-- -->
<button @click="prev">
</button>
<button @click="next">
</button>
</div>
</div>
</template>
<script>
export default {
//...
methods: {//
prev(){
const page = this.currentPage()
if(page > 0)
// https://example.com/posts?page={page - 1}
this.$router.push({query: {page: page - 1}})
},
next(){
const page = this.currentPage()
if(page < 100) // 100
// https://example.com/posts?page={page + 1}
this.$router.push({query: {page: page + 1}})
},
},
}
</script>
Kontra dari metode ini
.
, . 2, , 3, 4 , . GET .
, , .
2.
, .
, .
№3 , 2 , , id , 40 ? 3 , , . 2 ( 20 ). !
:
, , , . , mvp.
, , . 2 . -, . -, , , . , , , , .
, . , . !
, , .
, .
0, 1, (page) , . , offset ().
LET count = 20
LET offset = ${offset}
FOR post IN posts
SORT post.date ASC //
LIMIT offset, count
RETURN post
, GET "/?offset=0" .
, , ( nodejs):
async getPosts({offset}) {
const isOffset = offset !== undefined
if (isOffset && isNaN(+offset)) throw new BadRequestException()
const count = 20
// ,
if (offset % count !== 0) throw new BadRequestException()
const sort = isOffset ? `
SORT post.date DESC
LIMIT ${+offset}, ${count}
` : `
SORT post.date ASC
LIMIT 0, ${count * 2} // *
`
const q = {
query: `
FOR post IN posts
${sort}
RETURN post
`,
bindVars: {}
}
//
const cursor = await this.db.query(q, {fullCount: true, count: isOffset})
const fullCount = cursor.extra.stats.fullCount
/*
* count{20} 2 [21-39]
.
20 1- c count{20}
*/
let data;
if (isOffset) {
//
const allow = offset <= fullCount - cursor.count - count
if (!allow) throw new NotFoundException()
// , .
data = (await cursor.all()).reverse()
} else {
const all = await cursor.all()
if (fullCount % count === 0) {
// 20 , , ,
data = all.slice(0, count)
} else {
/* , 0-20 ,
20 ,
0-20 ,
40
*/
const pagesCountUp = Math.ceil(fullCount / count)
const resultCount = fullCount - pagesCountUp * count + count * 2
data = all.slice(0, resultCount)
}
}
if (!data.length) throw new NotFoundException()
return { fullCount, count: data.length, data }
}
:
id .
, id offset.
(
:
, , , null , , .. , , "null-" , null- .
( ), . ( id).
№2.
<template>
<div>
<div ref='posts'>
<template v-for="post in posts">
<div :key="post.id" style="height: 200px"> <!-- , -->
{{ item.title }}
</div>
</template>
</div>
<div> <!-- . -->
<button @click="prev" v-if="currentPage > 1">
</button>
</div>
</div>
</template>
<script>
const count = 20
export default {
data() {
return {
posts: [],
fullCount: 0,
pagesCount: 0,
dataLoading: true,
offset: undefined,
}
},
async fetch() {
const offset = this.$route.query?.offset
this.offset = offset
this.posts = await this.loadData(offset)
setTimeout(() => this.dataLoading = false)
},
computed: {
currentPage() {
return this.offset === undefined ? 1 : this.pageFromOffset(this.offset)
}
},
methods: {
//
pageFromOffset(offset) {
return offset === undefined ? 1 : this.pagesCount - offset / count
},
offsetFromPage(page) {
return page === 1 ? undefined : this.pagesCount * count - count * page
},
prev() {
const offset = this.offsetFromPage(this.currentPage - 1)
this.$router.push({query: {offset}})
},
async loadData(offset) {
try {
const data = await this.$axios.$get('posts', {params: {offset}})
this.fullCount = data.fullCount
this.pagesCount = Math.ceil(data.fullCount / count)
//
if (this.fullCount % count !== 0)
this.pagesCount -= 1
return data.data
} catch (e) {
//... 404
return []
}
},
onScroll() {
// 1000
const load = this.$refs.posts.getBoundingClientRect().bottom - window.innerHeight < 1000
const nextPage = this.pageFromOffset(this.offset) + 1
const nextOffset = this.offsetFromPage(nextPage)
if (!this.dataLoading && load && nextPage <= this.pagesCount) {
this.dataLoading = true
this.offset = nextOffset
this.loadData(nextOffset).then(async (data) => {
const top = window.scrollY
//
this.posts.push(...data)
await this.$router.replace({query: {offset: nextOffset}})
this.$nextTick(() => {
// viewport
window.scrollTo({top});
this.dataLoading = false
})
})
}
}
},
mounted() {
window.addEventListener('scroll', this.onScroll)
},
beforeDestroy() {
window.removeEventListener('scroll', this.onScroll)
},
}
</script>
. , , .
:
1 , , ( ):
< 1 ... 26 [27] 28 ... 255 >
< [1] 2 3 4 5 ... 255 >
< 1 ... 251 252 253 254 [255] >
Dasar dari metode untuk menghasilkan pagination diambil dari diskusi ini: https://gist.github.com/kottenator/9d936eb3e4e3c3e02598#gistcomment-3238804 dan disilangkan dengan solusi saya.
Tampilkan kelanjutan bonus
Pertama, Anda perlu menambahkan metode pembantu ini di dalam tag <script>
const getRange = (start, end) => Array(end - start + 1).fill().map((v, i) => i + start)
const pagination = (currentPage, pagesCount, count = 4) => {
const isFirst = currentPage === 1
const isLast = currentPage === pagesCount
let delta
if (pagesCount <= 7 + count) {
// delta === 7: [1 2 3 4 5 6 7]
delta = 7 + count
} else {
// delta === 2: [1 ... 4 5 6 ... 10]
// delta === 4: [1 2 3 4 5 ... 10]
delta = currentPage > count + 1 && currentPage < pagesCount - (count - 1) ? 2 : 4
delta += count
delta -= (!isFirst + !isLast)
}
const range = {
start: Math.round(currentPage - delta / 2),
end: Math.round(currentPage + delta / 2)
}
if (range.start - 1 === 1 || range.end + 1 === pagesCount) {
range.start += 1
range.end += 1
}
let pages = currentPage > delta
? getRange(Math.min(range.start, pagesCount - delta), Math.min(range.end, pagesCount))
: getRange(1, Math.min(pagesCount, delta + 1))
const withDots = (value, pair) => (pages.length + 1 !== pagesCount ? pair : [value])
if (pages[0] !== 1) {
pages = withDots(1, [1, '...']).concat(pages)
}
if (pages[pages.length - 1] < pagesCount) {
pages = pages.concat(withDots(pagesCount, ['...', pagesCount]))
}
if (!isFirst) pages.unshift('<')
if (!isLast) pages.push('>')
return pages
}
Menambahkan metode yang hilang
<template>
<div ref='posts'>
<div>
<div v-for="post in posts" :key="item.id">{{ post.title }}</div>
</div>
<div style="position: fixed; bottom: 0;"> <!-- -->
<template v-for="(i, key) in pagination">
<button v-if="i === '...'" :key="key + i" @click="selectPage()">{{ i }}</button>
<button :key="i" v-else :disabled="currentPage === i" @click="loadPage(pagePaginationOffset(i))">{{ i }}</button>
</template>
</div>
</div>
</template>
<script>
export default {
data() {
return {
posts: [],
fullCount: 0,
pagesCount: 0,
interval: null,
dataLoading: true,
offset: undefined,
}
},
async fetch() {/* */},
computed: {
currentPage() {/* */},
//
pagination() {
return this.pagesCount ? pagination(this.currentPage, this.pagesCount) : []
},
},
methods: {
pageFromOffset(offset) {/* */},
offsetFromPage(page) {/* */},
async loadData(offset) {/* */},
onScroll() {/* */},
//
loadPage(offset) {
window.scrollTo({top: 0})
this.dataLoading = true
this.loadData(offset).then((data) => {
this.offset = offset
this.posts = data
this.$nextTick(() => {
this.dataLoading = false
})
})
},
//
pagePaginationOffset(item) {
if (item === '...') return undefined
let page = isNaN(item) ? this.currentPage + (item === '>') - (item === '<') : item
return page <= 1 ? undefined : this.offsetFromPage(page)
},
//
selectPage() {
const page = +prompt(" ");
this.loadPage(this.offsetFromPage(page))
},
},
mounted() {
window.addEventListener('scroll', this.onScroll)
},
beforeDestroy() {
window.removeEventListener('scroll', this.onScroll)
},
}
</script>
Sekarang, jika perlu, Anda dapat membuka halaman yang diinginkan.