feat: add new features, update theme and build config
- Add 40+ new UI components including chat modules, discovery cards, photo galleries, FAQ and booking tools - Standardize brand color across all styles by replacing $theme-color-500 SCSS variables with #0ccd58 - Add sass 1.58.3 dependency and update vite config for modern scss compiler support - Refactor existing components (AddCarCrad, login page) and remove unused /quick/list router route - Add utility functions for URL parameter handling - Add static assets including custom znicons font, component images and icons - Fix scss syntax issues and deprecation warnings
This commit is contained in:
198
src/pages/home/components/AnswerComponent/index.vue
Normal file
198
src/pages/home/components/AnswerComponent/index.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div class="w-full bg-white border-box border-ff overflow-hidden rounded-20 flex flex-col">
|
||||
<!-- 占位撑开 -->
|
||||
<div class="w-vw"></div>
|
||||
<div class="flex flex-col px-16 pt-16 pb-12 border-box">
|
||||
<div v-if="tag" class="long-answer-tag">{{ tag }}</div>
|
||||
<div v-if="title" class="flex flex-row flex-items-start flex-justify-start mb-4">
|
||||
<uni-icons class="icon-active" type="fire-filled" size="18" color="opacity" />
|
||||
<span class="font-size-16 font-500 span-color-900 ml-6"> {{ title }}</span>
|
||||
</div>
|
||||
<!-- 文字内容,最多显示3行 -->
|
||||
<div v-if="processedContent" class="answer-content font-size-12 font-color-600">
|
||||
<ChatMarkdown :text="processedContent" />
|
||||
</div>
|
||||
<!-- 超过3行时显示...提示 -->
|
||||
<div v-if="!finish" class="flex flex-row flex-items-center mt-8">
|
||||
<span class="font-size-12 font-400 font-color-600">正在生成</span>
|
||||
<ChatLoading />
|
||||
</div>
|
||||
<div v-if="isOverflow" class="flex flex-row flex-items-center flex-justify-between mt-4"
|
||||
@click="lookDetailAction">
|
||||
<span class="font-size-12 font-400 theme-color-500 mr-4">查看完整{{ tag }}</span>
|
||||
<uni-icons class="icon-active" type="right" size="14" color="opacity"></uni-icons>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, computed, watch, onBeforeUnmount } from "vue";
|
||||
|
||||
import ChatMarkdown from "./ChatMarkdown/index.vue";
|
||||
import ChatLoading from "./ChatLoading/index.vue";
|
||||
import StreamManager from '@/utils/StreamManager.js';
|
||||
import {
|
||||
getLongTextPredivText,
|
||||
getLongTextValue,
|
||||
hasLongTextExtraSections,
|
||||
} from "@/utils/longTextCard";
|
||||
|
||||
// 直接根据文字长度判断,超过约100个字符认为会溢出(约3行)
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
longTextData: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
finish: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const tag = computed(() => getLongTextValue(props.longTextData, "tag"));
|
||||
const title = computed(() => getLongTextValue(props.longTextData, "title"));
|
||||
const predivContent = computed(() => {
|
||||
return getLongTextPredivText(props.longTextData, ["content_summary"]);
|
||||
});
|
||||
|
||||
// 处理文本内容:按行截断以保证预览最多显示三行(更贴近视觉行数)
|
||||
// 点击“查看详情”会跳转到完整页面(不受预览截断影响)。
|
||||
const PREdiv_LINES = 3;
|
||||
const PREdiv_CHAR_LIMIT = 100; // 作为备用,当没有换行但过长时也会截断
|
||||
const processedContent = computed(() => {
|
||||
const content = predivContent.value ? String(predivContent.value) : "";
|
||||
if (!content) return "";
|
||||
|
||||
// 按行分割(保留空行)
|
||||
const lines = content.split(/\r?\n/);
|
||||
|
||||
// 如果行数超过限制,截取前 PREdiv_LINES 行并添加省略号
|
||||
if (lines.length > PREdiv_LINES) {
|
||||
return lines.slice(0, PREdiv_LINES).join("\n") + "...";
|
||||
}
|
||||
|
||||
// 若虽然行数不超过,但总长度仍然很长,做字符级截断作为兜底
|
||||
if (content.length > PREdiv_CHAR_LIMIT) {
|
||||
return content.slice(0, PREdiv_CHAR_LIMIT) + "...";
|
||||
}
|
||||
|
||||
return content;
|
||||
});
|
||||
|
||||
const isOverflow = computed(() => {
|
||||
const contentStr = predivContent.value ? String(predivContent.value) : "";
|
||||
const lines = contentStr.split(/\r?\n/);
|
||||
return (
|
||||
hasLongTextExtraSections(props.longTextData) ||
|
||||
lines.length > PREdiv_LINES ||
|
||||
contentStr.length > PREdiv_CHAR_LIMIT
|
||||
);
|
||||
});
|
||||
|
||||
let stopForwardWatcher = null;
|
||||
let stopFinishWatcher = null;
|
||||
let stopLongTextWatcher = null;
|
||||
|
||||
const cleanupStreamWatchers = () => {
|
||||
if (stopForwardWatcher) {
|
||||
stopForwardWatcher();
|
||||
stopForwardWatcher = null;
|
||||
}
|
||||
if (stopFinishWatcher) {
|
||||
stopFinishWatcher();
|
||||
stopFinishWatcher = null;
|
||||
}
|
||||
if (stopLongTextWatcher) {
|
||||
stopLongTextWatcher();
|
||||
stopLongTextWatcher = null;
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeUnmount(cleanupStreamWatchers);
|
||||
|
||||
const lookDetailAction = () => {
|
||||
const message = predivContent.value ? String(predivContent.value) : "";
|
||||
// 使用 StreamManager 以 streamId 转发当前及后续流式更新,详情页通过 streamId 订阅
|
||||
const streamId = `stream_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
||||
const updateStream = () => {
|
||||
StreamManager.updateStream(
|
||||
streamId,
|
||||
predivContent.value ? String(predivContent.value) : "",
|
||||
!!props.finish,
|
||||
props.longTextData || null,
|
||||
);
|
||||
};
|
||||
|
||||
StreamManager.openStream(streamId, message, !!props.finish, props.longTextData || null);
|
||||
|
||||
cleanupStreamWatchers();
|
||||
|
||||
if (!props.finish) {
|
||||
// 将当前组件后续 props.content/props.finish 的更新转发到 StreamManager
|
||||
stopForwardWatcher = watch(
|
||||
() => props.content,
|
||||
updateStream
|
||||
);
|
||||
stopLongTextWatcher = watch(
|
||||
() => props.longTextData,
|
||||
updateStream,
|
||||
{ deep: true }
|
||||
);
|
||||
stopFinishWatcher = watch(
|
||||
() => props.finish,
|
||||
(f) => {
|
||||
StreamManager.updateStream(
|
||||
streamId,
|
||||
predivContent.value ? String(predivContent.value) : "",
|
||||
!!f,
|
||||
props.longTextData || null,
|
||||
);
|
||||
if (f) {
|
||||
cleanupStreamWatchers();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 传递 finished 参数,完成状态下不自动滚到底部
|
||||
uni.navigateTo({ url: `/pages/ChatMain/ChatLongAnswer/index?streamId=${encodeURIComponent(streamId)}&finished=${props.finish ? '1' : '0'}` });
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.icon-active {
|
||||
margin-top: 1px;
|
||||
color: #0CCD58;
|
||||
}
|
||||
|
||||
.answer-content {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 16px;
|
||||
max-height: 80px;
|
||||
}
|
||||
|
||||
.long-answer-tag {
|
||||
display: inline-flex;
|
||||
width: fit-content;
|
||||
margin-bottom: 8px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(#0CCD58, 0.2);
|
||||
background: rgba(#0CCD58, 0.08);
|
||||
color: #0CCD58;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user