DOM结构
<scroll-view class="scroll-view" scroll-y="true" :scroll-top="scrollTop" :style="{height: contentHeight + 'px'}"
scroll-with-animation show-scrollbar="false" id="report-scroll-view">
<view class="report-container">
<!-- 内容 -->
<view class="report-content" v-if="reportData && reportData.aiAnalysis">
<!-- 使用解析后的多个文本分段显示 -->
<block v-for="(item, index) in parsedContent" :key="index">
<!-- 标题 -->
<view v-if="item.type === 'title'" class="title-section">
<text class="title-text">{{item.content}}</text>
</view>
<!-- 普通段落 -->
<view v-if="item.type === 'paragraph'" class="paragraph">
<text>{{item.content}}</text>
</view>
<!-- 缩进列表项 -->
<view v-if="item.type === 'list-item'" class="list-item">
<text>{{item.content}}</text>
</view>
</block>
</view>
<!-- 没有内容 -->
<view class="report-content no-content" v-else-if="!isLoading">
<text class="report-text">暂无此内容</text>
</view>
<!-- 加载中状态 -->
<view class="loading-container" v-if="isLoading">
<view class="loading-spinner"></view>
</view>
<!-- 底部AI解读提示 - 仅在解读完成后显示 -->
<view class="ai-disclaimer" v-if="!isLoading && reportData">
<text class="disclaimer-text">此内容由人工智能DeepSeek进行解读</text>
</view>
</view>
</scroll-view>
data值
scrollTop: 0,
contentHeight: 0, //内容区高度,动态计算
reportData: null,
parsedContent: [], // 解析后的内容数组
isLoading: false, //是否正在加载数据
displayText: '', // 当前显示的文本
typingSpeed: 10, // 打字效果速度(毫秒)
isTyping: false, // 是否正在展示打字效果
fullText: '', // 完整的报告文本
parsedContent: [], // 解析后的内容数组
statusBarHeight: 0, //状态栏高度,用于动态调整布局
监听displayText变化,解析文本结构
watch: {
// 监听displayText变化,解析文本结构
displayText(newVal) {
this.parseContent(newVal);
}
},
onLoad方法
onLoad(option) {
// 获取系统信息设置顶部状态栏高度
try {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 20;
// 计算内容区域高度(屏幕高度减去状态栏、标题栏和额外的顶部间距)
this.contentHeight = systemInfo.windowHeight - this.statusBarHeight - 90 - 15;
} catch (e) {
this.statusBarHeight = 20;
this.contentHeight = 500;
}
// 获取数据
this.getReportInterpretation();
},
methods
// 解析内容为标题、段落和列表项
parseContent(text) {
if (!text) {
this.parsedContent = [];
return;
}
const lines = text.split('\n');
const result = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (!line) continue;
// 处理标题行(数字+点开头,后面可能有冒号结尾)
if (/^\d+\..*?[::]?$/.test(line)) {
result.push({
type: 'title',
content: line
});
}
// 处理列表项(短横线开头)
else if (line.startsWith('- ') || line.startsWith('• ')) {
result.push({
type: 'list-item',
content: line
});
}
// 处理普通段落
else {
result.push({
type: 'paragraph',
content: line
});
}
}
this.parsedContent = result;
},
打字效果
// 打字效果实现
startTypeEffect(text) {
if (!text) return;
this.fullText = text;
this.displayText = '';
this.isTyping = true;
let currentIndex = 0;
const typeNextChar = () => {
if (currentIndex < this.fullText.length && this.isTyping) {
this.displayText = this.fullText.substring(0, currentIndex + 1);
currentIndex++;
// 滚动到底部
this.$nextTick(() => {
this.scrollToBottom();
});
// 使用setTimeout实现打字效果
setTimeout(typeNextChar, this.typingSpeed);
} else {
this.isTyping = false;
}
};
typeNextChar();
},
滚动方法
// 滚动到底部
scrollToBottom() {
const query = uni.createSelectorQuery().in(this);
query.select('#report-scroll-view').boundingClientRect();
query.select('.report-container').boundingClientRect();
query.exec(res => {
if (res && res[0] && res[1]) {
const scrollViewHeight = res[0].height;
const containerHeight = res[1].height;
if (containerHeight > scrollViewHeight) {
this.scrollTop = containerHeight - scrollViewHeight;
}
}
});
},
发送请求获取数据
// 获取报告解读
getReportInterpretation() {
// 设置加载状态
this.isLoading = true;
// 发送请求到接口
getListByPageDeepseek(params)
.then(res => {
// 处理返回数据
if (res && res.data) {
// 保存报告数据
this.reportData = res.data;
// 启动打字效果
if (res.data.aiAnalysis) {
this.startTypeEffect(res.data.aiAnalysis);
} else {
const defaultText = '暂无此报告的解读内容';
this.startTypeEffect(defaultText);
}
} else {
// 没有报告数据
console.warn('接口返回数据中无data字段');
const defaultAnalysis =
'由于您提供的报告内容显示为"null"(空值),我无法获取具体信息进行分析。;
this.reportData = {
aiAnalysis: defaultAnalysis
};
this.startTypeEffect(defaultAnalysis);
}
// 关闭加载状态
this.isLoading = false;
})
.catch(error => {
console.error('获取报告解读失败:', error);
let errorMsg = '系统内部异常,请稍后再试';
if (typeof error === 'string') {
errorMsg = error;
} else if (error && error.message) {
errorMsg = error.message;
}
this.reportData = {
aiAnalysis: errorMsg
};
this.startTypeEffect(errorMsg);
// 关闭加载状态
this.isLoading = false;
});
}
css,有些是多余的,自己删吧
.container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
position: relative;
.header {
padding-bottom: 10rpx;
background-color: #fff;
border-bottom: 1px solid #eee;
position: sticky;
top: 0;
left: 0;
right: 0;
z-index: 100;
.header-content {
position: relative;
display: flex;
align-items: center;
height: 80rpx;
padding: 0 30rpx;
.back-button {
position: absolute;
left: 30rpx;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
.bacl-icon {
font-size: 28rpx;
}
}
.title-container {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
.title {
font-size: 36rpx;
font-weight: bold;
text-align: center;
}
}
}
}
.scroll-view {
flex: 1;
position: relative;
overflow: hidden;
.report-container {
display: flex;
flex-direction: column;
.report-content {
background-color: #fff;
padding: 30rpx;
font-size: 28rpx;
line-height: 1.6;
color: #333;
.title-section {
margin-top: 20rpx;
margin-bottom: 10rpx;
.title-text {
font-size: 32rpx;
font-weight: bold;
color: #333;
display: block;
}
}
.paragraph {
text-indent: 2em;
margin-bottom: 15rpx;
}
.list-item {
padding-left: 2em;
margin-bottom: 10rpx;
}
&.no-content {
color: #999;
text-align: center;
padding: 60rpx 30rpx;
}
}
.loading-container {
display: flex;
justify-content: center;
align-items: center;
padding: 100rpx 0;
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 6rpx solid rgba(0, 0, 0, 0.1);
border-left-color: #4e8ef7;
border-radius: 50%;
animation: spin 1s linear infinite;
}
}
.ai-disclaimer {
background-color: #FFF9E6;
padding: 20rpx 30rpx;
.disclaimer-text {
font-size: 24rpx;
color: #8F7846;
line-height: 1.4;
}
}
}
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}