feat: 长文本组件的对接调试
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user