前端 实现文字打字效果(仿AI)

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);
		}
	}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值