104 lines
2.4 KiB
Vue
104 lines
2.4 KiB
Vue
<template>
|
||
<view class="flex flex-col bg-liner h-screen overflow-hidden">
|
||
<!-- ✅ 顶部固定导航 -->
|
||
<view class="flex-shrink-0">
|
||
<TopNavBar :title="title" background="transparent" />
|
||
</view>
|
||
|
||
<!-- ✅ 滚动区域 -->
|
||
<scroll-view class="flex-full overflow-hidden" scroll-y :scroll-into-view="scrollIntoViewId" scroll-with-animation>
|
||
<view class="pt-12 px-12 pb-24 border-box">
|
||
<!-- ✅ 答案内容,支持markdown -->
|
||
<ChatMarkdown :text="answerText" />
|
||
|
||
<!-- ✅ 底部锚点(必须存在) -->
|
||
<view id="bottom-anchor"></view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import TopNavBar from "@/components/TopNavBar/index.vue";
|
||
import ChatMarkdown from "../index/components/chat/ChatMarkdown/index.vue";
|
||
import { defineProps, ref, nextTick } from "vue";
|
||
import { onLoad, onUnload } from "@dcloudio/uni-app";
|
||
import StreamManager from "@/utils/StreamManager.js";
|
||
|
||
const props = defineProps({
|
||
answerText: {
|
||
type: String,
|
||
default: "",
|
||
},
|
||
});
|
||
|
||
const answerText = ref(props.answerText || "");
|
||
const title = ref("");
|
||
|
||
let unsubscribe = null;
|
||
|
||
/** ✅ scroll-into-view 控制 */
|
||
const scrollIntoViewId = ref("");
|
||
|
||
/** ✅ 防抖 */
|
||
let scrollTimer = null;
|
||
|
||
function scrollToBottom() {
|
||
if (scrollTimer) return;
|
||
|
||
scrollTimer = setTimeout(() => {
|
||
// ❗关键:强制触发滚动(小程序必须这样)
|
||
scrollIntoViewId.value = "";
|
||
|
||
nextTick(() => {
|
||
// 再次 nextTick + 延迟,兼容 markdown 渲染延迟
|
||
setTimeout(() => {
|
||
scrollIntoViewId.value = "bottom-anchor";
|
||
}, 50);
|
||
});
|
||
|
||
scrollTimer = null;
|
||
}, 100);
|
||
}
|
||
|
||
onLoad(({ message = "", streamId = "" }) => {
|
||
if (streamId) {
|
||
// ✅ 流式数据
|
||
unsubscribe = StreamManager.subscribe(
|
||
streamId,
|
||
(text = "", finished = false) => {
|
||
answerText.value = text || "";
|
||
|
||
if (answerText.value.length > 6) {
|
||
title.value = answerText.value.substring(0, 6) + "...";
|
||
}
|
||
|
||
nextTick(() => {
|
||
scrollToBottom();
|
||
});
|
||
}
|
||
);
|
||
} else {
|
||
// ✅ 非流式
|
||
answerText.value = decodeURIComponent(message || "");
|
||
|
||
if (answerText.value.length > 6) {
|
||
title.value = answerText.value.substring(0, 6) + "...";
|
||
}
|
||
|
||
nextTick(() => {
|
||
scrollToBottom();
|
||
});
|
||
}
|
||
});
|
||
|
||
onUnload(() => {
|
||
try {
|
||
unsubscribe && unsubscribe();
|
||
} catch (e) { }
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
|
||
</style> |