基于coze+微信小程序的ai对话

发布于:2025-02-28 ⋅ 阅读:(17) ⋅ 点赞:(0)

界面介绍:

代码:(替换你的coze的配置)

<template>
	<view class="container">
		<!-- 高斯模糊背景 -->
		<view class="animated-bg">
			<view class="gradient-blob"></view>
			<view class="gradient-blob two"></view>
			<view class="gradient-blob three"></view>
			<view class="gradient-blob four"></view>
			<view class="overlay"></view>
		</view>
		
		<!-- 返回按钮 -->
		        <view class="back-button" @tap="goBack">
		            <text class="back-icon">←</text>
		            <text class="back-text">返回</text>
		        </view>
		
		<!-- 头部区域 -->
		<view class="header">
			<text class="logo-text">AI Assistant</text>
			<text class="subtitle">智能学习英语,探索无限可能</text>
		</view>

		<!-- 搜索区域 -->
		<view class="search-section" :class="{ 'search-focused': isFocused }">
			<view class="search-box">
				<text class="search-icon">🔍</text>
				<input 
					class="search-input" 
					type="text" 
					v-model="searchQuery"
					placeholder="探索任何英语问题..."
					@focus="handleFocus"
					@blur="handleBlur"
					@confirm="handleSearch"
				/>
				<view v-if="searchQuery" @tap="clearSearch" class="clear-btn">
					<text class="clear-icon">✕</text>
				</view>
			</view>
		</view>

		<!-- 内容区域 -->
		<scroll-view 
			class="content-section" 
			scroll-y 
			:class="{ 'has-result': searchResult }"
		>
			<!-- 默认状态 -->
			<view class="welcome-section" v-if="!searchResult && !loading">
				<view class="feature-cards">
					<view class="feature-card">
						<text class="card-icon">💡</text>
						<text class="card-title">智能问答</text>
					</view>
					<view class="feature-card">
						<text class="card-icon">🎯</text>
						<text class="card-title">精准解析</text>
					</view>
					<view class="feature-card">
						<text class="card-icon">🚀</text>
						<text class="card-title">即时响应</text>
					</view>
				</view>

				<view class="quick-starts">
					<text class="section-title">快速开始</text>
					<view class="suggestion-chips">
						<text class="chip" @tap="useExample('讲解英语的发展历程')">英语简史</text>
						<text class="chip" @tap="useExample('如何有效的学习英语?')">英语学习方法</text>
						<text class="chip" @tap="useExample('如何提高学习效率?')">提高英语学习效率</text>
					</view>
				</view>
			</view>

			<!-- 加载状态 -->
			<view class="loading-state" v-if="loading">
				<view class="pulse-loader">
					<view class="pulse"></view>
					<view class="pulse"></view>
					<view class="pulse"></view>
				</view>
				<text class="loading-text">AI 思考中...</text>
			</view>

			<!-- 结果展示 -->
			<view class="result-wrapper" v-if="searchResult">
				<view class="result-card">
					<view class="result-header">
						<text class="ai-badge">AI</text>
						<text class="timestamp">{{new Date().toLocaleTimeString()}}</text>
					</view>
					<text class="result-text">{{searchResult}}</text>
				</view>

				<!-- 跟进问题 -->
				<view class="follow-up-section" v-if="followUpQuestions.length">
					<text class="section-title">延伸思考</text>
					<view class="follow-up-list">
						<view 
							class="follow-up-item" 
							v-for="(question, index) in followUpQuestions" 
							:key="index"
							@tap="useExample(question)"
						>
							<text class="follow-up-icon">❯</text>
							<text class="follow-up-text">{{question}}</text>
						</view>
					</view>
				</view>
			</view>
		</scroll-view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				searchQuery: '',
				searchResult: '',
				followUpQuestions: [],
				loading: false,
				isFocused: false,
				// Coze API 配置
				cozeConfig: {
					apiUrl: 'https://api.coze.cn/open_api/v2/chat',
					token: '', // 替换为您的访问令牌
					botId: '', // 替换为您的机器人ID
					userId: '' // 用户ID,可以根据需要修改
				}
			}
		},
		methods: {
			// 返回上一页
			        goBack() {
			            uni.navigateBack({
			                delta: 1 // 返回的页面层数,1 表示返回上一页
			            });
			        },
			handleFocus() {
				this.isFocused = true
			},
			handleBlur() {
				this.isFocused = false
			},
			clearSearch() {
				this.searchQuery = ''
				this.searchResult = ''
			},
			useExample(text) {
				this.searchQuery = text
				this.handleSearch()
			},
			async handleSearch() {
			    if (!this.searchQuery.trim()) {
			        uni.showToast({
			            title: '请输入搜索内容',
			            icon: 'none'
			        });
			        return;
			    }
			
			    this.loading = true;
			    this.followUpQuestions = [];
			
			    try {
			        const response = await uni.request({
			            url: this.cozeConfig.apiUrl,
			            method: 'POST',
			            header: {
			                'Authorization': `Bearer ${this.cozeConfig.token}`,
			                'Content-Type': 'application/json',
			                'Accept': '*/*',
			                'Host': 'api.coze.cn',
			                'Connection': 'keep-alive'
			            },
			            data: {
			                conversation_id: Date.now().toString(),
			                bot_id: this.cozeConfig.botId,
			                user: this.cozeConfig.userId,
			                query: this.searchQuery,
			                stream: false
			            }
			        });
			
			        // 解析响应
			        const responseData = response[1].data; // 第二个元素才是实际数据
			        if (responseData.code === 0) {
			            const messages = responseData.messages || [];
			
			            // 只获取类型为 answer 的第一条消息作为核心回答
			            const answer = messages.find(msg => msg.type === 'answer');
			            if (answer) {
			                // 移除可能的 verbose 信息和系统提示
			                let cleanAnswer = answer.content
			                    .replace(/\{.*?\}/g, '') // 移除 JSON 格式的内容
			                    .replace(/\[.*?\]/g, '') // 移除方括号内的内容
			                    .trim();
			
			                this.searchResult = cleanAnswer;
			            }
			
			            // 获取跟进问题
			            const followUps = messages.filter(msg => msg.type === 'follow_up');
			            if (followUps.length > 0) {
			                this.followUpQuestions = followUps.map(q => q.content);
			            }
			        } else {
			            throw new Error('API 请求失败');
			        }
			    } catch (error) {
			        console.error('API Error:', error);
			        uni.showToast({
			            title: '搜索失败,请重试',
			            icon: 'none'
			        });
			    } finally {
			        this.loading = false;
			    }
			}
		}
	}
</script>

<style>
	.container {
		min-height: 100vh;
		position: relative;
		overflow: hidden;
		background: #f8f9fa;
	}

	.animated-bg {
		position: fixed;
		width: 100%;
		height: 100%;
		top: 0;
		left: 0;
		overflow: hidden;
		z-index: 0;
		background: linear-gradient(125deg, #00fff3, #ff00e6);
		animation: gradientBG 15s ease infinite;
	}

	.gradient-blob {
		position: absolute;
		width: 1000rpx;
		height: 1000rpx;
		background: linear-gradient(
			45deg,
			rgba(255, 49, 97, 0.5),
			rgba(255, 97, 162, 0.5)
		);
		border-radius: 50%;
		filter: blur(80px);
		animation: blob-movement 20s infinite linear,
				   color-shift 10s infinite alternate;
		opacity: 0.7;
		top: -300rpx;
		left: -300rpx;
	}

	.gradient-blob.two {
		background: linear-gradient(
			45deg,
			rgba(67, 206, 162, 0.5),
			rgba(24, 90, 157, 0.5)
		);
		animation: blob-movement-2 25s infinite linear,
				   color-shift-2 12s infinite alternate;
		animation-delay: -3s;
		top: 60%;
		left: 60%;
	}

	.gradient-blob.three {
		background: linear-gradient(
			45deg,
			rgba(255, 197, 84, 0.5),
			rgba(255, 159, 0, 0.5)
		);
		animation: blob-movement-3 30s infinite linear,
				   color-shift-3 8s infinite alternate;
		animation-delay: -5s;
		top: 40%;
		left: 30%;
	}

	.gradient-blob.four {
		background: linear-gradient(
			45deg,
			rgba(147, 88, 255, 0.5),
			rgba(19, 123, 255, 0.5)
		);
		animation: blob-movement-4 28s infinite linear,
				   color-shift-4 15s infinite alternate;
		animation-delay: -7s;
		top: 20%;
		left: 70%;
	}

	.overlay {
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		background: rgba(255, 255, 255, 0.2);
		backdrop-filter: blur(20px);
	}

	@keyframes gradientBG {
		0% {
			background-position: 0% 50%;
		}
		50% {
			background-position: 100% 50%;
		}
		100% {
			background-position: 0% 50%;
		}
	}

	@keyframes blob-movement {
		0% {
			transform: translate(0, 0) scale(1) rotate(0deg);
		}
		33% {
			transform: translate(300rpx, 200rpx) scale(1.2) rotate(120deg);
		}
		66% {
			transform: translate(-200rpx, 400rpx) scale(0.8) rotate(240deg);
		}
		100% {
			transform: translate(0, 0) scale(1) rotate(360deg);
		}
	}

	@keyframes blob-movement-2 {
		0% {
			transform: translate(0, 0) scale(1.1) rotate(0deg);
		}
		33% {
			transform: translate(-250rpx, -300rpx) scale(0.9) rotate(120deg);
		}
		66% {
			transform: translate(300rpx, -200rpx) scale(1.2) rotate(240deg);
		}
		100% {
			transform: translate(0, 0) scale(1.1) rotate(360deg);
		}
	}

	@keyframes blob-movement-3 {
		0% {
			transform: translate(0, 0) scale(0.9) rotate(0deg);
		}
		33% {
			transform: translate(400rpx, -100rpx) scale(1.1) rotate(120deg);
		}
		66% {
			transform: translate(-300rpx, 200rpx) scale(0.8) rotate(240deg);
		}
		100% {
			transform: translate(0, 0) scale(0.9) rotate(360deg);
		}
	}

	@keyframes blob-movement-4 {
		0% {
			transform: translate(0, 0) scale(1) rotate(0deg);
		}
		33% {
			transform: translate(-200rpx, 300rpx) scale(1.1) rotate(120deg);
		}
		66% {
			transform: translate(250rpx, -250rpx) scale(0.9) rotate(240deg);
		}
		100% {
			transform: translate(0, 0) scale(1) rotate(360deg);
		}
	}

	@keyframes color-shift {
		0% {
			background: linear-gradient(45deg, rgba(255, 49, 97, 0.5), rgba(255, 97, 162, 0.5));
		}
		100% {
			background: linear-gradient(45deg, rgba(255, 182, 193, 0.5), rgba(255, 20, 147, 0.5));
		}
	}

	@keyframes color-shift-2 {
		0% {
			background: linear-gradient(45deg, rgba(67, 206, 162, 0.5), rgba(24, 90, 157, 0.5));
		}
		100% {
			background: linear-gradient(45deg, rgba(0, 255, 255, 0.5), rgba(0, 128, 128, 0.5));
		}
	}

	@keyframes color-shift-3 {
		0% {
			background: linear-gradient(45deg, rgba(255, 197, 84, 0.5), rgba(255, 159, 0, 0.5));
		}
		100% {
			background: linear-gradient(45deg, rgba(255, 215, 0, 0.5), rgba(255, 140, 0, 0.5));
		}
	}

	@keyframes color-shift-4 {
		0% {
			background: linear-gradient(45deg, rgba(147, 88, 255, 0.5), rgba(19, 123, 255, 0.5));
		}
		100% {
			background: linear-gradient(45deg, rgba(138, 43, 226, 0.5), rgba(65, 105, 225, 0.5));
		}
	}

	/* 头部样式 */
	.header {
		padding: 60rpx 40rpx;
		text-align: center;
		z-index: 1;
		position: relative;
	}

	.logo-text {
		font-size: 48rpx;
		color: #1a1a1a;
		font-weight: 600;
		letter-spacing: 2rpx;
		text-shadow: 0 2px 4px rgba(255, 255, 255, 0.2);
	}

	.subtitle {
		font-size: 28rpx;
		color: rgba(0, 0, 0, 0.7);
		margin-top: 16rpx;
		display: block;
	}

	/* 搜索框样式 */
	.search-section {
		padding: 20rpx 40rpx;
		position: relative;
		z-index: 2;
	}

	.search-box {
		background: rgba(255, 255, 255, 0.2);
		backdrop-filter: blur(20px);
		border-radius: 20rpx;
		padding: 24rpx 32rpx;
		display: flex;
		align-items: center;
		border: 1px solid rgba(255, 255, 255, 0.3);
		transition: all 0.3s ease;
	}

	.search-box:hover {
		background: rgba(255,255,255,0.15);
	}

	.search-input {
		flex: 1;
		margin: 0 20rpx;
		font-size: 32rpx;
		color: #1a1a1a;
		height: 48rpx;
		line-height: 48rpx;
	}

	.search-icon, .clear-icon {
		font-size: 32rpx;
		color: rgba(255,255,255,0.6);
	}

	/* 功能卡片 */
	.feature-cards {
		display: flex;
		justify-content: space-around;
		padding: 40rpx;
		gap: 20rpx;
	}

	.feature-card {
		background: rgba(255, 255, 255, 0.2);
		backdrop-filter: blur(10px);
		border-radius: 16rpx;
		padding: 30rpx;
		text-align: center;
		flex: 1;
		border: 1px solid rgba(255, 255, 255, 0.3);
		transition: all 0.3s ease;
	}

	.feature-card:active {
		transform: scale(0.98);
		background: rgba(255,255,255,0.15);
	}

	.card-icon {
		font-size: 48rpx;
		margin-bottom: 16rpx;
		display: block;
	}

	.card-title {
		color: #fff;
		font-size: 28rpx;
	}

	/* 建议词条 */
	.quick-starts {
		padding: 40rpx;
	}

	.section-title {
		color: rgba(0, 0, 0, 0.7);
		font-size: 28rpx;
		margin-bottom: 20rpx;
		display: block;
	}

	.suggestion-chips {
		display: flex;
		flex-wrap: wrap;
		gap: 16rpx;
	}

	.chip {
		background: rgba(255,255,255,0.1);
		padding: 16rpx 32rpx;
		border-radius: 100rpx;
		color: #fff;
		font-size: 28rpx;
		transition: all 0.3s ease;
	}

	.chip:active {
		background: rgba(255,255,255,0.2);
		transform: scale(0.98);
	}

	/* 加载动画 */
	.loading-state {
		padding: 60rpx;
		text-align: center;
	}

	.pulse-loader {
		display: flex;
		justify-content: center;
		gap: 12rpx;
	}

	.pulse {
		width: 16rpx;
		height: 16rpx;
		background: #fff;
		border-radius: 50%;
		animation: pulse 1.5s infinite ease-in-out;
	}

	.pulse:nth-child(2) { animation-delay: 0.2s; }
	.pulse:nth-child(3) { animation-delay: 0.4s; }

	@keyframes pulse {
		0%, 100% { transform: scale(0.5); opacity: 0.2; }
		50% { transform: scale(1); opacity: 1; }
	}

	.loading-text {
		color: rgba(255,255,255,0.7);
		font-size: 28rpx;
		margin-top: 20rpx;
		display: block;
	}

	/* 结果卡片 */
	.result-wrapper {
		padding: 40rpx;
	}

	.result-card {
		background: rgba(255, 255, 255, 0.2);
		backdrop-filter: blur(15px);
		border-radius: 20rpx;
		padding: 30rpx;
		border: 1px solid rgba(255, 255, 255, 0.3);
		animation: slideUp 0.3s ease;
	}

	.result-header {
		display: flex;
		justify-content: space-between;
		align-items: center;
		margin-bottom: 20rpx;
	}

	.ai-badge {
		background: linear-gradient(135deg, #7C3AED, #3B82F6);
		color: #fff;
		padding: 6rpx 16rpx;
		border-radius: 8rpx;
		font-size: 24rpx;
	}

	.timestamp {
		color: rgba(255,255,255,0.5);
		font-size: 24rpx;
	}

	.result-text {
		color: #1a1a1a;
		font-size: 30rpx;
		line-height: 1.6;
	}

	/* 跟进问题 */
	.follow-up-section {
		margin-top: 40rpx;
	}

	.follow-up-list {
		display: flex;
		flex-direction: column;
		gap: 16rpx;
	}

	.follow-up-item {
		background: rgba(255, 255, 255, 0.15);
		padding: 24rpx;
		border-radius: 16rpx;
		display: flex;
		align-items: center;
		transition: all 0.3s ease;
		backdrop-filter: blur(10px);
	}

	.follow-up-item:active {
		background: rgba(255,255,255,0.15);
		transform: translateX(10rpx);
	}

	.follow-up-icon {
		color: rgba(255,255,255,0.5);
		margin-right: 16rpx;
		font-size: 24rpx;
	}

	.follow-up-text {
		color: #1a1a1a;
		font-size: 28rpx;
	}

	@keyframes slideUp {
		from { 
			opacity: 0;
			transform: translateY(20rpx);
		}
		to {
			opacity: 1;
			transform: translateY(0);
		}
	}

	.search-input::placeholder {
		color: rgba(0, 0, 0, 0.4);
	}
	.back-button {
	    position: fixed;
	    top: 40rpx;
	    left: 20rpx;
	    display: flex;
	    align-items: center;
	    padding: 10rpx 20rpx;
	    background: rgba(255, 255, 255, 0.8);
	    border-radius: 50rpx;
	    backdrop-filter: blur(10px);
	    z-index: 999;
	    box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
	}
	
	.back-icon {
	    font-size: 36rpx;
	    margin-right: 10rpx;
	    color: #333;
	}
	
	.back-text {
	    font-size: 28rpx;
	    color: #333;
	}
</style>