浮窗触底加载

This commit is contained in:
BTMuli
2025-06-15 15:34:38 +08:00
parent 848227f6b0
commit 0e01f2bdaa
4 changed files with 83 additions and 26 deletions

View File

@@ -10,9 +10,8 @@
</div>
<div class="vo-of-actions">
<v-btn class="vo-of-btn" @click="loadMore(true)" :loading="loading">刷新</v-btn>
<v-btn class="vo-of-btn" @click="loadMore()" :loading="loading">加载更多</v-btn>
</div>
<div class="vp-of-list">
<div class="vp-of-list" ref="listRef">
<TPostcard
class="vp-of-list-item"
v-for="(item, index) in posts"
@@ -27,18 +26,31 @@
import TOverlay from "@comp/app/t-overlay.vue";
import TPostcard from "@comp/app/t-postcard.vue";
import showSnackbar from "@comp/func/snackbar.js";
import { useBoxReachBottom } from "@hooks/reachBottom.js";
import painterReq from "@req/painterReq.js";
import useUserStore from "@store/user.js";
import { storeToRefs } from "pinia";
import { ref, shallowRef, watch } from "vue";
import { ref, shallowRef, useTemplateRef, watch } from "vue";
const { cookie } = storeToRefs(useUserStore());
const listEl = useTemplateRef<HTMLElement>("listRef");
const { isReachBottom } = useBoxReachBottom(listEl);
const visible = defineModel<boolean>();
const offset = ref<number>();
const isLast = ref<boolean>(false);
const loading = ref<boolean>(false);
const posts = shallowRef<Array<TGApp.BBS.Post.FullData>>([]);
watch(
() => isReachBottom.value,
async () => {
if (!isReachBottom.value) return;
await loadMore();
},
);
watch(
() => visible.value,
async () => {
@@ -71,6 +83,9 @@ async function loadMore(refresh: boolean = false): Promise<void> {
else posts.value = posts.value.concat(resp.list);
loading.value = false;
showSnackbar.success(`成功加载${resp.list.length}条数据`);
if (refresh && listEl.value) {
listEl.value.scrollTo({ top: 0, behavior: "smooth" });
}
}
</script>
<style lang="scss" scoped>

View File

@@ -4,12 +4,9 @@
<div class="tops-top">查找{{ search }}</div>
<div class="tops-act">
<span>分区{{ label }}</span>
<v-btn :loading="load" size="small" class="tops-btn" @click="searchPosts()" rounded>
加载更多({{ results.length }})
</v-btn>
</div>
<div class="tops-divider" />
<div class="tops-list">
<div class="tops-list" ref="listRef">
<TPostCard
class="tops-item"
:model-value="item"
@@ -24,15 +21,19 @@
import TOverlay from "@comp/app/t-overlay.vue";
import TPostCard from "@comp/app/t-postcard.vue";
import showSnackbar from "@comp/func/snackbar.js";
import { useBoxReachBottom } from "@hooks/reachBottom.js";
import postReq from "@req/postReq.js";
import useBBSStore from "@store/bbs.js";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
import { computed, onMounted, ref, shallowRef, useTemplateRef, watch } from "vue";
type ToPostSearchProps = { gid: string; keyword?: string };
const { gameList } = storeToRefs(useBBSStore());
const listEl = useTemplateRef<HTMLElement>("listRef");
const { isReachBottom } = useBoxReachBottom(listEl);
const props = defineProps<ToPostSearchProps>();
const visible = defineModel<boolean>();
@@ -54,6 +55,13 @@ onMounted(async () => {
if (visible.value) await searchPosts();
});
watch(
() => isReachBottom.value,
async () => {
if (!isReachBottom.value) return;
await searchPosts();
},
);
watch(
() => visible.value,
async () => {
@@ -118,6 +126,7 @@ async function searchPosts(): Promise<void> {
isLast.value = res.is_last;
load.value = false;
if (!visible.value) visible.value = true;
showSnackbar.success(`成功加载${res.posts.length}条数据`);
}
</script>
<style lang="css" scoped>
@@ -181,10 +190,4 @@ async function searchPosts(): Promise<void> {
height: fit-content;
flex-shrink: 0;
}
.tops-btn {
width: fit-content;
background: var(--tgc-btn-1);
color: var(--btn-text);
}
</style>

View File

@@ -21,16 +21,13 @@
</div>
</div>
<div class="vp-ou-mid">
<v-btn :loading="load" size="small" class="vp-ou-btn" @click="loadPosts()" rounded>
加载更多({{ results.length }})
</v-btn>
<div class="vp-ouu-extra" v-if="userInfo">
<span>ID:{{ userInfo.uid }}</span>
<span>IP:{{ userInfo.ip_region }}</span>
</div>
</div>
<div class="vp-ou-divider" />
<div class="vp-ou-list">
<div class="vp-ou-list" ref="listRef">
<TPostCard
class="vp-ou-item"
:model-value="item"
@@ -46,12 +43,16 @@ import TMiImg from "@comp/app/t-mi-img.vue";
import TOverlay from "@comp/app/t-overlay.vue";
import TPostCard from "@comp/app/t-postcard.vue";
import showSnackbar from "@comp/func/snackbar.js";
import { useBoxReachBottom } from "@hooks/reachBottom.js";
import bbsReq from "@req/bbsReq.js";
import postReq from "@req/postReq.js";
import { computed, ref, shallowRef, watch } from "vue";
import { computed, ref, shallowRef, useTemplateRef, watch } from "vue";
type ToPostUserProps = { gid: number; uid: string; postId?: string };
const listEl = useTemplateRef<HTMLElement>("listRef");
const { isReachBottom } = useBoxReachBottom(listEl);
const props = defineProps<ToPostUserProps>();
const visible = defineModel<boolean>();
const offset = ref<string>();
@@ -69,6 +70,13 @@ const levelColor = computed<string>(() => {
return "var(--tgc-od-white)";
});
watch(
() => isReachBottom.value,
async () => {
if (!isReachBottom.value) return;
await loadPosts();
},
);
watch(
() => visible.value,
async () => {
@@ -117,6 +125,7 @@ async function loadPosts(): Promise<void> {
isLast.value = resp.is_last;
results.value = results.value.concat(resp.list);
load.value = false;
showSnackbar.success(`成功加载${resp.list.length}条数据`);
}
</script>
<style lang="scss" scoped>
@@ -260,12 +269,6 @@ async function loadPosts(): Promise<void> {
justify-content: space-between;
}
.vp-ou-btn {
width: fit-content;
background: var(--tgc-btn-1);
color: var(--btn-text);
}
.vp-ouu-extra {
position: relative;
display: flex;

View File

@@ -4,7 +4,7 @@
* @since Beta v0.7.7
*/
import { onMounted, onUnmounted, ref, Ref } from "vue";
import { onMounted, onUnmounted, ref, Ref, TemplateRef, watch } from "vue";
type ReachBottomReturn = {
isReachBottom: Ref<boolean>;
@@ -39,3 +39,39 @@ export function usePageReachBottom(): ReachBottomReturn {
});
return { isReachBottom };
}
/**
* @function useBoxReachBottom
* @description 元素触底检测
* @since Beta v0.7.7
* @param {TemplateRef<HTMLElement>} boxRef - 需要检测的元素引用
* @return ReachBottomReturn
*/
export function useBoxReachBottom(boxRef: TemplateRef<HTMLElement>): ReachBottomReturn {
const isReachBottom = ref<boolean>(false);
const handleScroll = () => {
if (!boxRef.value) return;
const scrollTop = boxRef.value.scrollTop;
const clientHeight = boxRef.value.clientHeight;
const scrollHeight = boxRef.value.scrollHeight;
// 检测是否触底
isReachBottom.value = scrollTop + clientHeight >= scrollHeight - 1; // 减1是为了避免浮点数误差
};
watch(
() => boxRef.value,
() => {
if (!boxRef.value) return;
boxRef.value.addEventListener("scroll", handleScroll);
handleScroll();
},
{ immediate: true },
);
onUnmounted(() => {
boxRef.value?.removeEventListener("scroll", handleScroll);
});
return { isReachBottom };
}