feat: lazy load reply
This commit is contained in:
parent
cd511f5ede
commit
dbdcfa84fd
|
@ -44,6 +44,11 @@ interface CommentDraft {
|
||||||
content: string
|
content: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取回复的请求
|
||||||
|
interface ReplyRequest {
|
||||||
|
sortField: string
|
||||||
|
sortOrder: 'asc' | 'desc'
|
||||||
|
}
|
||||||
// 话题页面的 store
|
// 话题页面的 store
|
||||||
interface Topic {
|
interface Topic {
|
||||||
// 是否正在被编辑
|
// 是否正在被编辑
|
||||||
|
@ -56,7 +61,7 @@ interface Topic {
|
||||||
// 回复的缓存
|
// 回复的缓存
|
||||||
replyDraft: ReplyDraft
|
replyDraft: ReplyDraft
|
||||||
// 获取回复的请求接口格式
|
// 获取回复的请求接口格式
|
||||||
replyRequest: TopicReplyRequestData
|
replyRequest: ReplyRequest
|
||||||
|
|
||||||
// 评论的缓存
|
// 评论的缓存
|
||||||
commentDraft: CommentDraft
|
commentDraft: CommentDraft
|
||||||
|
@ -81,11 +86,8 @@ export const useKUNGalgameTopicStore = defineStore({
|
||||||
publishStatus: false,
|
publishStatus: false,
|
||||||
},
|
},
|
||||||
replyRequest: {
|
replyRequest: {
|
||||||
tid: 0,
|
|
||||||
page: 1,
|
|
||||||
limit: 5,
|
|
||||||
sortField: '',
|
sortField: '',
|
||||||
sortOrder: '',
|
sortOrder: 'asc',
|
||||||
},
|
},
|
||||||
commentDraft: {
|
commentDraft: {
|
||||||
c_uid: 0,
|
c_uid: 0,
|
||||||
|
@ -135,13 +137,17 @@ export const useKUNGalgameTopicStore = defineStore({
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// 获取回复
|
// 获取回复
|
||||||
getReplies(tid: number): Promise<TopicReplyResponseData> {
|
getReplies(
|
||||||
|
tid: number,
|
||||||
|
page?: number,
|
||||||
|
limit?: number
|
||||||
|
): Promise<TopicReplyResponseData> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// 这里的默认值用于初始化
|
// 这里的默认值用于初始化
|
||||||
const requestData: TopicReplyRequestData = {
|
const requestData: TopicReplyRequestData = {
|
||||||
tid: tid,
|
tid: tid,
|
||||||
page: this.replyRequest.page || 1,
|
page: page || 1,
|
||||||
limit: this.replyRequest.limit || 5,
|
limit: limit || 5,
|
||||||
sortField: this.replyRequest.sortField || 'floor',
|
sortField: this.replyRequest.sortField || 'floor',
|
||||||
sortOrder: this.replyRequest.sortOrder || 'desc',
|
sortOrder: this.replyRequest.sortOrder || 'desc',
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,11 @@ import {
|
||||||
onBeforeMount,
|
onBeforeMount,
|
||||||
computed,
|
computed,
|
||||||
watch,
|
watch,
|
||||||
|
onBeforeUnmount,
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { onBeforeRouteLeave } from 'vue-router'
|
import { onBeforeRouteLeave, useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
import { TopicDetail, TopicReply } from '@/api'
|
||||||
|
|
||||||
// Aside
|
// Aside
|
||||||
import Aside from './aside/Aside.vue'
|
import Aside from './aside/Aside.vue'
|
||||||
|
@ -25,13 +28,9 @@ const ReplyPanel = defineAsyncComponent(
|
||||||
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
||||||
// 导入话题页面 store
|
// 导入话题页面 store
|
||||||
import { useKUNGalgameTopicStore } from '@/store/modules/topic'
|
import { useKUNGalgameTopicStore } from '@/store/modules/topic'
|
||||||
|
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
import { TopicDetail, TopicReply } from '@/api'
|
// 当前的路由
|
||||||
|
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
// 使用设置面板的 store
|
// 使用设置面板的 store
|
||||||
|
@ -51,18 +50,20 @@ const tid = computed(() => {
|
||||||
const topicData = ref<TopicDetail>()
|
const topicData = ref<TopicDetail>()
|
||||||
// 单个话题的回复数据
|
// 单个话题的回复数据
|
||||||
const repliesData = ref<TopicReply[]>([])
|
const repliesData = ref<TopicReply[]>([])
|
||||||
|
// 页面的容器,用于计算是否到达底部
|
||||||
|
const content = ref<HTMLElement>()
|
||||||
|
// 当前的页数
|
||||||
|
const currentPage = ref(1)
|
||||||
|
// 是否加载,因为已经加载完了
|
||||||
|
const isLoading = ref(true)
|
||||||
|
|
||||||
const fetchTopicData = async () => {
|
/** 这里拿到的已经是后端返回回来的 data 数据了 */
|
||||||
|
onMounted(async () => {
|
||||||
// 获取单个话题的数据
|
// 获取单个话题的数据
|
||||||
const topicResponseData = (
|
const topicResponseData = (
|
||||||
await useKUNGalgameTopicStore().getTopicByTid(tid.value)
|
await useKUNGalgameTopicStore().getTopicByTid(tid.value)
|
||||||
).data
|
).data
|
||||||
topicData.value = topicResponseData
|
topicData.value = topicResponseData
|
||||||
}
|
|
||||||
|
|
||||||
/** 这里拿到的已经是后端返回回来的 data 数据了 */
|
|
||||||
onMounted(async () => {
|
|
||||||
await fetchTopicData()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 调用 getReplies 获取回复数据(watch 大法好!)
|
// 调用 getReplies 获取回复数据(watch 大法好!)
|
||||||
|
@ -73,13 +74,66 @@ watch(
|
||||||
replyDraft.value.publishStatus,
|
replyDraft.value.publishStatus,
|
||||||
],
|
],
|
||||||
async () => {
|
async () => {
|
||||||
repliesData.value = (
|
const replyResponseData = (
|
||||||
await useKUNGalgameTopicStore().getReplies(tid.value)
|
await useKUNGalgameTopicStore().getReplies(tid.value)
|
||||||
).data
|
).data
|
||||||
|
repliesData.value = replyResponseData
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 滚动事件处理函数
|
||||||
|
const scrollHandler = async () => {
|
||||||
|
// 滚动到底部的处理逻辑
|
||||||
|
if (isScrollAtBottom() && isLoading.value) {
|
||||||
|
// 自动增加页数
|
||||||
|
currentPage.value++
|
||||||
|
// 获取下一页的回复数据
|
||||||
|
|
||||||
|
const lazyLoadReplies = await useKUNGalgameTopicStore().getReplies(
|
||||||
|
tid.value,
|
||||||
|
currentPage.value
|
||||||
|
)
|
||||||
|
|
||||||
|
// 判断是否已经将数据加载完,加载完则不需要加载了
|
||||||
|
if (!lazyLoadReplies.data.length) {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将新加载的回复数据追加到已有的回复数据中
|
||||||
|
repliesData.value = [...repliesData.value, ...lazyLoadReplies.data]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否滚动到底部
|
||||||
|
const isScrollAtBottom = () => {
|
||||||
|
if (content.value) {
|
||||||
|
const scrollHeight = content.value.scrollHeight
|
||||||
|
const scrollTop = content.value.scrollTop
|
||||||
|
const clientHeight = content.value.clientHeight
|
||||||
|
|
||||||
|
return scrollHeight - scrollTop === clientHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在组件挂载后,添加滚动事件监听器
|
||||||
|
onMounted(() => {
|
||||||
|
// 获取滚动元素的引用
|
||||||
|
const element = content.value
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
element.addEventListener('scroll', scrollHandler)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 在组件卸载前,移除滚动事件监听器
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
const element = content.value
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
element.removeEventListener('scroll', scrollHandler)
|
||||||
|
}
|
||||||
|
})
|
||||||
/* 话题界面的页面宽度 */
|
/* 话题界面的页面宽度 */
|
||||||
const topicPageWidth = computed(() => {
|
const topicPageWidth = computed(() => {
|
||||||
return showKUNGalgamePageWidth.value.Topic + '%'
|
return showKUNGalgamePageWidth.value.Topic + '%'
|
||||||
|
@ -112,7 +166,7 @@ onBeforeMount(() => {
|
||||||
<Aside v-if="topicData?.tags" :tags="topicData.tags" />
|
<Aside v-if="topicData?.tags" :tags="topicData.tags" />
|
||||||
|
|
||||||
<!-- 内容区 -->
|
<!-- 内容区 -->
|
||||||
<div class="content">
|
<div class="content" ref="content">
|
||||||
<Master v-if="topicData" :topicData="topicData" />
|
<Master v-if="topicData" :topicData="topicData" />
|
||||||
<Reply v-if="repliesData" :repliesData="repliesData" />
|
<Reply v-if="repliesData" :repliesData="repliesData" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -133,7 +187,8 @@ onBeforeMount(() => {
|
||||||
.content-container {
|
.content-container {
|
||||||
width: v-bind(topicPageWidth);
|
width: v-bind(topicPageWidth);
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
height: 100%;
|
height: calc(100vh - 65px);
|
||||||
|
min-height: 500px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
/* 设置背景的毛玻璃效果 */
|
/* 设置背景的毛玻璃效果 */
|
||||||
|
@ -144,6 +199,7 @@ onBeforeMount(() => {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 右侧内容区 */
|
/* 右侧内容区 */
|
||||||
|
@ -153,6 +209,8 @@ onBeforeMount(() => {
|
||||||
/* 右侧内容区为弹性盒(用户可以一直向下滑) */
|
/* 右侧内容区为弹性盒(用户可以一直向下滑) */
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-x: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1000px) {
|
@media (max-width: 1000px) {
|
||||||
|
|
|
@ -61,8 +61,6 @@ watch(
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
/* 侧边栏部分 */
|
/* 侧边栏部分 */
|
||||||
.aside {
|
.aside {
|
||||||
top: 70px;
|
|
||||||
position: sticky;
|
|
||||||
/* 侧边栏距离文章区域的距离 */
|
/* 侧边栏距离文章区域的距离 */
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
width: v-bind('asideWidth');
|
width: v-bind('asideWidth');
|
||||||
|
@ -77,6 +75,8 @@ watch(
|
||||||
span {
|
span {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
overflow-y: scroll;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
/* 侧边栏交互 */
|
/* 侧边栏交互 */
|
||||||
.nav-aside {
|
.nav-aside {
|
||||||
|
|
|
@ -67,7 +67,6 @@ const props = defineProps<{
|
||||||
margin-top: 27px;
|
margin-top: 27px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
border-bottom: 1px solid var(--kungalgame-blue-1);
|
border-bottom: 1px solid var(--kungalgame-blue-1);
|
||||||
border-top: 1px solid var(--kungalgame-blue-1);
|
|
||||||
}
|
}
|
||||||
.info {
|
.info {
|
||||||
margin-left: 50px;
|
margin-left: 50px;
|
||||||
|
|
|
@ -97,6 +97,7 @@ const {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
/* 楼主话题距离其它人话题的距离 */
|
/* 楼主话题距离其它人话题的距离 */
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
flex-shrink: 0;
|
||||||
/* TODO: */
|
/* TODO: */
|
||||||
/* 楼主话题背景图 */
|
/* 楼主话题背景图 */
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,12 @@ defineProps<{
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 其它人的回复 -->
|
<!-- 其它人的回复 -->
|
||||||
<TransitionGroup class="trans-list" name="list" tag="div">
|
|
||||||
<!-- 这里使用 Math.random 的原因是 key 必须唯一 -->
|
<!-- 这里使用 Math.random 的原因是 key 必须唯一 -->
|
||||||
|
<Transition
|
||||||
|
enter-active-class="animate__animated animate__fadeInUp animate__faster"
|
||||||
|
appear
|
||||||
|
>
|
||||||
|
<div>
|
||||||
<div
|
<div
|
||||||
class="other-topic-container"
|
class="other-topic-container"
|
||||||
v-for="(reply, index) in repliesData"
|
v-for="(reply, index) in repliesData"
|
||||||
|
@ -88,7 +92,8 @@ defineProps<{
|
||||||
<Comments />
|
<Comments />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TransitionGroup>
|
</div>
|
||||||
|
</Transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -112,12 +117,11 @@ defineProps<{
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-style: oblique;
|
font-style: oblique;
|
||||||
color: var(--kungalgame-red-3);
|
color: var(--kungalgame-red-3);
|
||||||
padding: 5px;
|
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
/* 这里的阴影只能这么绘制 */
|
/* 这里的阴影只能这么绘制 */
|
||||||
filter: drop-shadow(1px 2px 4px var(--kungalgame-trans-blue-4));
|
filter: drop-shadow(1px 2px 2px var(--kungalgame-trans-blue-4));
|
||||||
span {
|
span {
|
||||||
transform: rotate(10deg) translateY(10px) translateX(30px);
|
transform: rotate(10deg) translateY(40px);
|
||||||
padding: 0 30px;
|
padding: 0 30px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: var(--kungalgame-trans-white-2);
|
background-color: var(--kungalgame-trans-white-2);
|
||||||
|
@ -193,25 +197,6 @@ defineProps<{
|
||||||
color: var(--kungalgame-font-color-3);
|
color: var(--kungalgame-font-color-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-move, /* 对移动中的元素应用的过渡 */
|
|
||||||
.list-enter-active,
|
|
||||||
.list-leave-active {
|
|
||||||
transition: all 0.5s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-enter-from,
|
|
||||||
.list-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(30px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 确保将离开的元素从布局流中删除
|
|
||||||
以便能够正确地计算移动的动画。 */
|
|
||||||
.list-leave-active {
|
|
||||||
transform: scale(0.5);
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1000px) {
|
@media (max-width: 1000px) {
|
||||||
.top {
|
.top {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
Loading…
Reference in a new issue