🚸 优化回复浮窗处理 (#169)

* Initial plan

* Fix secondary reply scroll position issue by adding scroll-strategy="close" to submenu

Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com>

* Add auto-load on scroll for reply and sub-reply lists

Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com>

* Fix sub-reply scroll issues with custom event-based solution

Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com>

* Fix sub-reply initialization to use embedded sub_replies data

Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com>

* 🎨 codeStyle

* Fix duplicate sub-reply data by filtering existing reply IDs

Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com>

* Use persistent Set for existingIds to improve duplicate filtering efficiency

Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: BTMuli <72692909+BTMuli@users.noreply.github.com>
Co-authored-by: BTMuli <bt-muli@outlook.com>
This commit is contained in:
Copilot
2025-11-25 18:26:59 +08:00
committed by GitHub
parent 1124927c0e
commit 4d937b365b
2 changed files with 72 additions and 4 deletions

View File

@@ -51,7 +51,7 @@
</div>
</div>
</div>
<v-list class="tpr-reply-list">
<v-list class="tpr-reply-list" @scroll="handleListScroll">
<VpReplyItem
v-for="(item, index) in reply"
:key="index"
@@ -75,6 +75,7 @@
import showSnackbar from "@comp/func/snackbar.js";
import postReq from "@req/postReq.js";
import useAppStore from "@store/app.js";
import { emit } from "@tauri-apps/api/event";
import { storeToRefs } from "pinia";
import { computed, ref, shallowRef, watch } from "vue";
@@ -117,6 +118,22 @@ watch(
},
);
async function handleListScroll(e: Event): Promise<void> {
const target = <HTMLElement>e.target;
if (!target) return;
// Emit event to close sub-reply menus when parent scrolls
await emit("closeReplySub");
// Check if scrolled to bottom for auto-load
const scrollTop = target.scrollTop;
const clientHeight = target.clientHeight;
const scrollHeight = target.scrollHeight;
if (scrollTop + clientHeight >= scrollHeight - 1) {
if (!loading.value && !isLast.value) {
await loadReply();
}
}
}
async function showReply(): Promise<void> {
if (reply.value.length > 0) return;
if (isLast.value) return;

View File

@@ -52,7 +52,12 @@
:close-on-content-click="false"
v-model="showSub"
>
<v-list class="tpr-reply-sub" width="300px" max-height="400px">
<v-list
class="tpr-reply-sub"
width="300px"
max-height="400px"
@scroll="handleSubScroll"
>
<VpReplyItem
v-for="(reply, index) in subReplies"
:key="index"
@@ -121,6 +126,7 @@ type TprReplyProps =
const props = defineProps<TprReplyProps>();
const replyId = `reply_${props.modelValue.reply.post_id}_${props.modelValue.reply.floor_id}_${props.modelValue.reply.reply_id}`;
let subListener: UnlistenFn | null = null;
let closeSubListener: UnlistenFn | null = null;
console.log("TprReply", toRaw(props.modelValue));
@@ -129,6 +135,7 @@ const lastId = ref<string>();
const isLast = ref<boolean>(false);
const loading = ref<boolean>(false);
const subReplies = shallowRef<Array<TGApp.BBS.Reply.ReplyFull>>([]);
const existingIds = new Set<string>();
const levelColor = computed<string>(() => {
const level = props.modelValue.user.level_exp.level;
if (level < 5) return "var(--tgc-od-green)";
@@ -138,12 +145,21 @@ const levelColor = computed<string>(() => {
return "var(--tgc-od-white)";
});
onMounted(async () => (props.mode === "main" ? (subListener = await listenSub()) : null));
onMounted(async () => {
if (props.mode === "main") {
subListener = await listenSub();
closeSubListener = await listenCloseSub();
}
});
onUnmounted(() => {
if (subListener !== null) {
subListener();
subListener = null;
}
if (closeSubListener !== null) {
closeSubListener();
closeSubListener = null;
}
});
watch(
@@ -159,6 +175,26 @@ async function listenSub(): Promise<UnlistenFn> {
});
}
async function listenCloseSub(): Promise<UnlistenFn> {
return await event.listen<void>("closeReplySub", async () => {
if (showSub.value) showSub.value = false;
});
}
async function handleSubScroll(e: globalThis.Event): Promise<void> {
const target = <HTMLElement>e.target;
if (!target) return;
// Check if scrolled to bottom for auto-load
const scrollTop = target.scrollTop;
const clientHeight = target.clientHeight;
const scrollHeight = target.scrollHeight;
if (scrollTop + clientHeight >= scrollHeight - 1) {
if (!loading.value && !isLast.value) {
await loadSub();
}
}
}
async function share(): Promise<void> {
const replyDom = document.querySelector<HTMLElement>(`#${replyId}`);
if (replyDom === null) return;
@@ -166,6 +202,17 @@ async function share(): Promise<void> {
}
async function showReply(): Promise<void> {
if (subReplies.value.length === 0 && props.modelValue.sub_replies?.length > 0) {
subReplies.value = [...props.modelValue.sub_replies];
// Populate existingIds with embedded sub-replies
props.modelValue.sub_replies.forEach((r) => existingIds.add(r.reply.reply_id));
const lastReply = props.modelValue.sub_replies[props.modelValue.sub_replies.length - 1];
if (lastReply?.reply?.reply_id) lastId.value = lastReply.reply.reply_id;
if (props.modelValue.sub_replies.length >= props.modelValue.sub_reply_count) {
isLast.value = true;
}
return;
}
if (subReplies.value.length > 0) return;
if (isLast.value) return;
await loadSub();
@@ -186,7 +233,11 @@ async function loadSub(): Promise<void> {
}
isLast.value = resp.is_last;
lastId.value = resp.last_id;
subReplies.value = subReplies.value.concat(resp.list);
// Filter out duplicates using persistent existingIds Set
const newReplies = resp.list.filter((r) => !existingIds.has(r.reply.reply_id));
// Add new reply IDs to the Set
newReplies.forEach((r) => existingIds.add(r.reply.reply_id));
subReplies.value = subReplies.value.concat(newReplies);
loading.value = false;
if (isLast.value) showSnackbar.warn("没有更多了");
}