feat: 长文本组件的对接调试

This commit is contained in:
2026-05-20 15:26:52 +08:00
parent dd7b41d1ad
commit d087ee6b35
6 changed files with 316 additions and 63 deletions

View File

@@ -2,22 +2,23 @@
<view class="w-full bg-white border-box border-ff overflow-hidden rounded-20 flex flex-col">
<!-- 占位撑开 -->
<view class="w-vw"></view>
<view class="flex flex-col px-12 pb-12 pt-4 border-box border-left-4">
<view class="flex flex-col px-16 pt-16 pb-12 border-box">
<view v-if="tag" class="long-answer-tag">{{ tag }}</view>
<view v-if="title" class="flex flex-row flex-items-start flex-justify-start mb-8">
<uni-icons class="icon-active" type="fire-filled" size="18" color="opacity" />
<text class="font-size-16 font-500 text-color-900 ml-6"> {{ title }}</text>
</view>
<!-- 文字内容最多显示3行 -->
<view class="answer-content font-size-12 font-color-600">
<ChatMarkdown :text="processedText" />
<view v-if="processedContent" class="answer-content font-size-12 font-color-600">
<ChatMarkdown :text="processedContent" />
</view>
<!-- 超过3行时显示...提示 -->
<view v-if="!finish" class="flex flex-row flex-items-center mt-8">
<text class="font-size-12 font-400 font-color-600">正在生成</text>
<ChatLoading />
</view>
<view v-if="isOverflow" class="flex flex-row flex-items-center mt-8" @click="lookDetailAction">
<text class="font-size-12 font-400 theme-color-500 mr-4">查看详情</text>
<view v-if="isOverflow" class="flex flex-row flex-items-center flex-justify-between mt-8" @click="lookDetailAction">
<text class="font-size-12 font-400 theme-color-500 mr-4">查看完整{{ tag }}</text>
<uni-icons class="icon-active" type="right" size="14" color="opacity"></uni-icons>
</view>
@@ -32,16 +33,21 @@ import { defineProps, computed, watch, onBeforeUnmount } from "vue";
import ChatMarkdown from "../../ChatMain/ChatMarkdown/index.vue";
import ChatLoading from "../../ChatMain/ChatLoading/index.vue";
import StreamManager from '@/utils/StreamManager.js';
import {
getLongTextPreviewText,
getLongTextValue,
hasLongTextExtraSections,
} from "@/utils/longTextCard";
// 直接根据文字长度判断超过约100个字符认为会溢出约3行
const props = defineProps({
title: {
content: {
type: String,
default: "",
},
text: {
type: String,
default: "",
longTextData: {
type: Object,
default: null,
},
finish: {
type: Boolean,
@@ -49,16 +55,22 @@ const props = defineProps({
},
});
const tag = computed(() => getLongTextValue(props.longTextData, "tag"));
const title = computed(() => getLongTextValue(props.longTextData, "title"));
const previewContent = computed(() => {
return getLongTextPreviewText(props.longTextData) || (props.content ? String(props.content) : "");
});
// 处理文本内容:按行截断以保证预览最多显示三行(更贴近视觉行数)
// 点击“查看详情”会跳转到完整页面(不受预览截断影响)。
const PREVIEW_LINES = 3;
const PREVIEW_CHAR_LIMIT = 100; // 作为备用,当没有换行但过长时也会截断
const processedText = computed(() => {
const txt = props.text ? String(props.text) : "";
if (!txt) return "";
const processedContent = computed(() => {
const content = previewContent.value ? String(previewContent.value) : "";
if (!content) return "";
// 按行分割(保留空行)
const lines = txt.split(/\r?\n/);
const lines = content.split(/\r?\n/);
// 如果行数超过限制,截取前 PREVIEW_LINES 行并添加省略号
if (lines.length > PREVIEW_LINES) {
@@ -66,21 +78,26 @@ const processedText = computed(() => {
}
// 若虽然行数不超过,但总长度仍然很长,做字符级截断作为兜底
if (txt.length > PREVIEW_CHAR_LIMIT) {
return txt.slice(0, PREVIEW_CHAR_LIMIT) + "...";
if (content.length > PREVIEW_CHAR_LIMIT) {
return content.slice(0, PREVIEW_CHAR_LIMIT) + "...";
}
return txt;
return content;
});
const isOverflow = computed(() => {
const textStr = props.text ? String(props.text) : "";
const lines = textStr.split(/\r?\n/);
return lines.length > PREVIEW_LINES || textStr.length > PREVIEW_CHAR_LIMIT;
const contentStr = previewContent.value ? String(previewContent.value) : "";
const lines = contentStr.split(/\r?\n/);
return (
hasLongTextExtraSections(props.longTextData) ||
lines.length > PREVIEW_LINES ||
contentStr.length > PREVIEW_CHAR_LIMIT
);
});
let stopForwardWatcher = null;
let stopFinishWatcher = null;
let stopLongTextWatcher = null;
const cleanupStreamWatchers = () => {
if (stopForwardWatcher) {
@@ -91,30 +108,51 @@ const cleanupStreamWatchers = () => {
stopFinishWatcher();
stopFinishWatcher = null;
}
if (stopLongTextWatcher) {
stopLongTextWatcher();
stopLongTextWatcher = null;
}
};
onBeforeUnmount(cleanupStreamWatchers);
const lookDetailAction = () => {
const message = props.text ? String(props.text) : "";
const message = previewContent.value ? String(previewContent.value) : "";
// 使用 StreamManager 以 streamId 转发当前及后续流式更新,详情页通过 streamId 订阅
const streamId = `stream_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;
StreamManager.openStream(streamId, message, !!props.finish);
const updateStream = () => {
StreamManager.updateStream(
streamId,
previewContent.value ? String(previewContent.value) : "",
!!props.finish,
props.longTextData || null,
);
};
StreamManager.openStream(streamId, message, !!props.finish, props.longTextData || null);
cleanupStreamWatchers();
if (!props.finish) {
// 将当前组件后续 props.text/props.finish 的更新转发到 StreamManager
// 将当前组件后续 props.content/props.finish 的更新转发到 StreamManager
stopForwardWatcher = watch(
() => props.text,
(v) => {
StreamManager.updateStream(streamId, v ? String(v) : "", !!props.finish);
}
() => props.content,
updateStream
);
stopLongTextWatcher = watch(
() => props.longTextData,
updateStream,
{ deep: true }
);
stopFinishWatcher = watch(
() => props.finish,
(f) => {
StreamManager.updateStream(streamId, props.text ? String(props.text) : "", !!f);
StreamManager.updateStream(
streamId,
previewContent.value ? String(previewContent.value) : "",
!!f,
props.longTextData || null,
);
if (f) {
cleanupStreamWatchers();
}
@@ -142,7 +180,16 @@ const lookDetailAction = () => {
line-height: 16px;
max-height: 80px;
}
.border-left-4 {
border-left: 4px solid $theme-color-500;
.long-answer-tag {
display: inline-flex;
width: fit-content;
margin-bottom: 8px;
padding: 3px 8px;
border-radius: 12px;
border: 1px solid rgba($theme-color-500, 0.2);
background: rgba($theme-color-500, 0.08);
color: $theme-color-500;
font-size: 12px;
line-height: 18px;
}
</style>