Prechádzať zdrojové kódy

修改图片展示及预览

yangg 6 mesiacov pred
rodič
commit
da83b66078
1 zmenil súbory, kde vykonal 410 pridanie a 48 odobranie
  1. 410 48
      pages/ai-assistant/index.vue

+ 410 - 48
pages/ai-assistant/index.vue

@@ -9,6 +9,35 @@
 						<text class="sender-name">{{ message.user === 'user' ? userInfo.nickName : 'AI助理' }}</text>
 					</view>
 					<view class="message-content"> <!-- 改为 message-content 类 -->
+						<!-- 添加图片展示部分 -->
+						<template v-if="message.images && message.images.length > 0">
+							<view class="message-images">
+								<image v-for="(img, index) in message.images" :key="index" :src="img" class="chat-image"
+									mode="aspectFill" @tap="previewImage(message.images, index)" />
+							</view>
+						</template>
+						<!-- 视频展示部分 -->
+						<template v-if="message.videos && message.videos.length > 0">
+							<view class="message-videos">
+								<view v-for="(video, index) in message.videos" :key="index"
+									class="video-thumbnail-container">
+									<!-- 视频封面图 -->
+									<image :src="video + '?x-oss-process=video/snapshot,t_0,f_jpg'" class="chat-image"
+										mode="aspectFill" @tap="previewVideoThumbnail(message.videos, index)" />
+								</view>
+							</view>
+						</template>
+						<!-- 文档展示部分 -->
+						<template v-if="message.docs && message.docs.length > 0">
+							<view class="message-docs">
+								<view v-for="(doc, index) in message.docs" :key="index" class="doc-preview">
+									<view class="doc-preview-container">
+										<image src="/static/doc-icon.svg" mode="aspectFit" class="doc-icon" />
+										<text class="doc-name">{{ doc.name }}</text>
+									</view>
+								</view>
+							</view>
+						</template>
 						<template v-if="message.message.includes('相关资料来源')">
 							<view>{{ getMainContent(message.message) }}</view>
 							<view class="source-section" v-if="minioUrls && minioUrls.length > 0">
@@ -56,9 +85,10 @@
 						</view>
 					</view>
 					<!-- 视频预览 -->
-					<view class="image-preview" v-for="(video, index) in selectedVideos" :key="'vid-'+index">
+					<view class="image-preview" v-for="(video, index) in uploadedVideos" :key="'vid-'+index">
 						<view class="video-preview-container">
-							<image src="/static/video-placeholder.svg" mode="aspectFill" class="preview-img" />
+							<image :src="video.url + '?x-oss-process=video/snapshot,t_0,f_jpg'" mode="aspectFill"
+								class="preview-img" />
 							<!-- <image src="/static/play-icon.svg" class="play-icon" /> -->
 						</view>
 						<view class="delete-btn" @tap="removeVideo(index)">
@@ -88,7 +118,7 @@
 					class="input-textarea" />
 				<!-- 添加设置按钮 -->
 				<view class="set_btn">
-					<image src="/static/images/setting.png" @tap="showVoiceSettings" class="settings-icon" />
+					<!-- <image src="/static/images/setting.png" @tap="showVoiceSettings" class="settings-icon" /> -->
 					<view
 						v-if="!userInput.trim() && !selectedImages.length && !selectedVideos.length && !selectedDocs.length"
 						class="upload-options">
@@ -213,6 +243,18 @@
 				languageOptions: [], // 可选择的语言
 				currentVoices: [], // 当前语言下的声音选项
 				styleOptions: [], // 当前选择的声音可用的风格
+				videoThumbnails: {}, // 存储视频封面
+				/* 文档类型 */
+				docTypeMap: {
+					'pdf': 'PDF文档',
+					'doc': 'Word文档',
+					'docx': 'Word文档',
+					'txt': '文本文件',
+					'xls': 'Excel表格',
+					'xlsx': 'Excel表格',
+					'ppt': 'PPT演示',
+					'pptx': 'PPT演示'
+				},
 			};
 		},
 		created() {
@@ -330,7 +372,7 @@
 					this.platform.name = 'App';
 					// #endif
 
-					// console.log('前平台:', this.platform);
+					// console.log('��前平台:', this.platform);
 
 					// 获取更详细的平台信息
 					const platformInfo = {
@@ -422,12 +464,11 @@
 				let userDataString;
 
 				if (uni.getStorageSync('userData') == '') {
-				
+
 					// 跳转到个人页面
 					uni.switchTab({
 						url: '/pages/my/index',
-						success: function() {
-						},
+						success: function() {},
 						fail: function(err) {
 							console.error('Failed to switch to personal page:', err);
 						}
@@ -526,7 +567,7 @@
 
 			// 新增方法来处理消息
 			handleWebSocketMessage(result) {
-			
+
 
 				// 检查消息是否完结
 				if (result.done) {
@@ -558,7 +599,8 @@
 				} */
 
 				const message = this.userInput.trim();
-				if (!message && !this.selectedImages.length && !this.selectedVideos.length && !this.selectedDocs.length) {
+				if (!message && !this.selectedImages.length && !this.selectedVideos.length && !this.selectedDocs
+					.length) {
 					return;
 				}
 
@@ -597,6 +639,7 @@
 				};
 				this.messages.push(botMessage);
 
+
 				try {
 					const response = await uni.request({
 						url: this.AiUrl,
@@ -604,11 +647,12 @@
 						header: {
 							'Content-Type': 'application/json'
 						},
+						timeout: 180000, // 设置30秒超时,默认是60秒
 						data: {
 							message: message,
 							image_urls: userMessage.images,
 							video_urls: userMessage.videos, // 添加视频URL到请求
-							documents_file: userMessage.docs, // 添加文档URL到请求
+							documents: userMessage.docs, // 添加文档URL到请求
 							chat_config_id: '17',
 							user_id: this.userInfo.phoneNumber,
 							session_id: this.session_id || "",
@@ -656,7 +700,7 @@
 					if (this.currentAudioContext) {
 						// 先停止播放
 						this.currentAudioContext.stop();
-						
+
 						// 更新消息的播放状态
 						if (this.currentPlayingMessageId) {
 							const message = this.messages.find(m => m.id === this.currentPlayingMessageId);
@@ -689,7 +733,7 @@
 
 					// 创建新的音频上下文
 					const innerAudioContext = uni.createInnerAudioContext();
-					
+
 					// 设置音频源
 					innerAudioContext.src = audioBase64;
 
@@ -833,7 +877,7 @@
 							doTypewriter();
 						}, 30);
 					} else {
-						// 打字效果
+						// 打字效果��
 						// 保存原有的音频数据
 						const audioData = existingMessage.audioData;
 
@@ -1148,7 +1192,7 @@
 					console.error('图片上传失败:', error);
 					uni.hideLoading();
 					uni.showToast({
-						title: '传失败',
+						title: '��传失败',
 						icon: 'none'
 					});
 				}
@@ -1161,7 +1205,7 @@
 				this.uploadedFiles.splice(index, 1);
 			},
 
-			// 添加选择视频方法
+			// 修改选择视频方法
 			chooseVideo() {
 				if (this.selectedVideos.length >= this.maxVideos) {
 					uni.showToast({
@@ -1174,8 +1218,19 @@
 				uni.chooseVideo({
 					count: 1,
 					sourceType: ['album', 'camera'],
+					maxDuration: 60, // 限制视频时长最大60秒
 					success: async (res) => {
-						await this.uploadSingleVideo(res.tempFilePath);
+						// 检查视频大小(res.size 单位为字节)
+						const sizeInMB = res.size / (1024 * 1024);
+						if (sizeInMB > 10) {
+							uni.showToast({
+								title: '视频大小不能超过10MB',
+								icon: 'none'
+							});
+							return;
+						}
+						
+						await this.uploadSingleVideo(res.tempFilePath, res.size);
 					},
 					fail: (err) => {
 						console.error('选择视频失败:', err);
@@ -1187,9 +1242,14 @@
 				});
 			},
 
-			// 添加上传单个视频方法
-			async uploadSingleVideo(tempFilePath) {
+			// 修改上传单个视频方法
+			async uploadSingleVideo(tempFilePath, fileSize) {
 				try {
+					// 再次检查文件大小
+					if (fileSize && fileSize / (1024 * 1024) > 10) {
+						throw new Error('视频大小不能超过10MB');
+					}
+
 					uni.showLoading({
 						title: '正在上传视频...',
 						mask: true
@@ -1201,16 +1261,28 @@
 						name: 'file',
 						header: {
 							'Content-Type': 'multipart/form-data'
+						},
+						formData: {
+							// 可以添加额外的表单数据
+							fileType: 'video',
+							maxSize: 10 * 1024 * 1024 // 10MB in bytes
 						}
 					});
 
 					if (uploadResult.statusCode === 200) {
 						const response = JSON.parse(uploadResult.data);
+						
+						// 检查服务器响应
+						if (response.code === 413 || response.message?.includes('大小超过限制')) {
+							throw new Error('视频大小超过限制');
+						}
+
 						const fileUrl = response.data.fileUrl;
 
 						// 添加到已上传视频数组
 						this.uploadedVideos.push({
-							url: fileUrl
+							url: fileUrl,
+							size: fileSize
 						});
 
 						// 更新预览视频数组
@@ -1228,12 +1300,22 @@
 					console.error('视频上传失败:', error);
 					uni.hideLoading();
 					uni.showToast({
-						title: '上传失败',
-						icon: 'none'
+						title: error.message || '上传失败',
+						icon: 'none',
+						duration: 2000
 					});
 				}
 			},
 
+			// 添加获取文件大小的辅助方法
+			formatFileSize(bytes) {
+				if (bytes === 0) return '0 B';
+				const k = 1024;
+				const sizes = ['B', 'KB', 'MB', 'GB'];
+				const i = Math.floor(Math.log(bytes) / Math.log(k));
+				return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
+			},
+
 			// 修改移除视频方法
 			removeVideo(index) {
 				this.selectedVideos.splice(index, 1);
@@ -1249,9 +1331,9 @@
 				if (this.selectedVideos.length < this.maxVideos) {
 					options.push('选择视频');
 				}
-				if (this.selectedDocs.length < this.maxDocs) {
+				/* if (this.selectedDocs.length < this.maxDocs) {
 					options.push('选择文档');
-				}
+				} */
 
 				if (options.length === 0) {
 					uni.showToast({
@@ -1269,9 +1351,9 @@
 							this.chooseImage();
 						} else if (selected === '选择视频') {
 							this.chooseVideo();
-						} else if (selected === '选择文档') {
+						} /* else if (selected === '选择文档') {
 							this.chooseDoc();
-						}
+						} */
 					}
 				});
 			},
@@ -1352,28 +1434,32 @@
 			chooseDoc() {
 				if (this.selectedDocs.length >= this.maxDocs) {
 					uni.showToast({
-						title: `最多只能上传${this.max +'Docs'}个文档`,
+						title: `最多只能上传${this.maxDocs}个文档`,
 						icon: 'none'
 					});
 					return;
 				}
-
-				// #ifdef H5
-				// H5环境下使用input标签选择文件
-				const input = document.createElement('input');
-				input.type = 'file';
-				input.accept = '.doc,.docx,.pdf,.txt';
-				input.onchange = (event) => {
-					const file = event.target.files[0];
-					if (file) {
-						this.uploadSingleDoc(file);
+				this.chooseFromChat();
+				/* // 根据平台显示不同的选项
+				const options = ['从聊天记录选择', '从文件管理器选择'];
+				
+				uni.showActionSheet({
+					itemList: options,
+					success: (res) => {
+						if (res.tapIndex === 0) {
+							// 从聊天记录选择
+							
+						} else {
+							// 从文件管理器选择
+							this.chooseFromFileSystem();
+						}
 					}
-				};
-				input.click();
-				// #endif
+				}); */
+			},
 
-				// #ifdef APP-PLUS || MP-WEIXIN
-				// 小程序和APP环境下使用选择文件API
+			// 添加从聊天记录选择文档的方法
+			chooseFromChat() {
+				// #ifdef MP-WEIXIN
 				uni.chooseMessageFile({
 					count: 1,
 					type: 'file',
@@ -1384,9 +1470,69 @@
 					}
 				});
 				// #endif
+
+				// #ifdef APP-PLUS
+				plus.gallery.pick((path) => {
+					this.uploadSingleDoc({
+						path: path,
+						name: path.substring(path.lastIndexOf('/') + 1)
+					});
+				}, (error) => {
+					uni.showToast({
+						title: '选择文件失败',
+						icon: 'none'
+					});
+				}, {
+					filter: 'document'
+				});
+				// #endif
+			},
+
+			// 修改 chooseFromFileSystem 方法
+			async chooseFromFileSystem() {
+				// #ifdef MP-WEIXIN
+				// 微信小程序使用 wx.chooseMessageFile API
+				uni.chooseMessageFile({
+					count: 1, // 可选择文件的数量
+					type: 'file', // 选择文件类型
+					extension: ['doc', 'docx', 'pdf', 'txt'], // 允许的文件类型
+					success: (res) => {
+						if (res.tempFiles && res.tempFiles.length > 0) {
+							const file = res.tempFiles[0];
+							this.uploadSingleDoc({
+								path: file.path,
+								name: file.name
+							});
+						}
+					},
+					fail: (err) => {
+						console.error('选择文件失败:', err);
+						uni.showToast({
+							title: '选择文件失败',
+							icon: 'none'
+						});
+					}
+				});
+				// #endif
+
+				// #ifdef APP-PLUS
+				// 保持原有的 APP 端代码
+				try {
+					if (uni.getSystemInfoSync().platform === 'android') {
+						// ... 原有的 Android 代码 ...
+					}
+				} catch (error) {
+					console.error('File picker error:', error);
+					uni.showToast({
+						title: '选择文件失败',
+						icon: 'none',
+						duration: 2000
+					});
+				}
+				// #endif
 			},
 
-			// 添加上传单个文档方法
+			// 修改 uploadSingleDoc 方法以处理不同格式的文件路径
 			async uploadSingleDoc(file) {
 				try {
 					uni.showLoading({
@@ -1394,9 +1540,39 @@
 						mask: true
 					});
 
+					let uploadFile;
+					// #ifdef APP-PLUS
+					if (typeof file === 'object' && file.path) {
+						// 处理 Android/iOS 返回的文件对象
+						uploadFile = {
+							path: file.path,
+							name: file.name
+						};
+					}
+					// #endif
+
+					// #ifdef MP-WEIXIN
+					if (file.path) {
+						uploadFile = {
+							path: file.path,
+							name: file.name
+						};
+					}
+					// #endif
+
+					// #ifdef H5
+					if (file instanceof File) {
+						uploadFile = file;
+					}
+					// #endif
+
+					if (!uploadFile) {
+						throw new Error('Invalid file format');
+					}
+
 					const uploadResult = await uni.uploadFile({
 						url: `${this.apiUrl}/upload/file`,
-						filePath: file.path || file,
+						filePath: uploadFile.path || uploadFile,
 						name: 'file',
 						header: {
 							'Content-Type': 'multipart/form-data'
@@ -1406,16 +1582,17 @@
 					if (uploadResult.statusCode === 200) {
 						const response = JSON.parse(uploadResult.data);
 						const fileUrl = response.data.fileUrl;
+
 						// 添加到已上传文档数组
 						this.uploadedDocs.push({
 							url: fileUrl,
-							name: file.name || '文档'
+							name: uploadFile.name || '文档'
 						});
 
 						// 更新预览文档数组
 						this.selectedDocs.push({
-							name: file.name || '文档',
-							path: file.path || file
+							name: uploadFile.name || '文档',
+							path: uploadFile.path || uploadFile
 						});
 
 						uni.hideLoading();
@@ -1579,7 +1756,86 @@
 					document.body.style.overflow = 'auto';
 					// #endif
 				}
-			}
+			},
+			// 添加图片预览方法
+			previewImage(images, current) {
+				uni.previewImage({
+					urls: images,
+					current: images[current]
+				});
+			},
+			// 添加权限检查方法
+			async checkAndRequestPermissions() {
+				// #ifdef APP-PLUS
+				if (uni.getSystemInfoSync().platform === 'android') {
+					const permission = 'android.permission.READ_EXTERNAL_STORAGE';
+					const result = await new Promise((resolve) => {
+						plus.android.requestPermissions(
+							[permission],
+							function(resultObj) {
+								resolve(resultObj.granted.indexOf(permission) >= 0);
+							},
+							function(error) {
+								console.error('Error requesting permissions:', error);
+								resolve(false);
+							}
+						);
+					});
+
+					if (!result) {
+						uni.showToast({
+							title: '需要存储权限才能选择文件',
+							icon: 'none'
+						});
+						return false;
+					}
+					return true;
+				}
+				// #endif
+				return true;
+			},
+			// 修改文件选择方法
+			async chooseFromFileSystem() {
+				// 先检查权限
+				const hasPermission = await this.checkAndRequestPermissions();
+				if (!hasPermission) {
+					return;
+				}
+
+				// 原有的文件选择代码...
+			},
+			// 获取文档类型的中文释义
+			getDocumentType(filename) {
+				const extension = filename.split('.').pop().toLowerCase();
+				return this.docTypeMap[extension] || '未知文件';
+			},
+
+			// 格式化文件名显示
+			formatFileName(filename) {
+				// 如果文件名过长,截断显示
+				const maxLength = 10;
+				const name = filename.split('.')[0]; // 获取文件名(不含扩展名)
+				if (name.length > maxLength) {
+					return name.substring(0, maxLength) + '...';
+				}
+				return name;
+			},
+			// 预览视频缩略图
+			previewVideoThumbnail(videos, current) {
+				// 生成所有视频的缩略图URL数组
+				const thumbnailUrls = videos.map(video => video + '?x-oss-process=video/snapshot,t_0,f_jpg');
+
+				uni.previewImage({
+					urls: thumbnailUrls,
+					current: thumbnailUrls[current],
+					longPressActions: {
+						itemList: ['发送给朋友', '保存图片', '收藏'],
+						success: function(data) {
+							console.log('选中了第' + (data.tapIndex + 1) + '个按钮');
+						}
+					}
+				});
+			},
 		},
 		// updated() {
 		//   const messagesContainer = this.$el.querySelector(".messages");
@@ -1712,6 +1968,7 @@
 		margin-left: 30px;
 		margin-top: 0;
 		font-size: 12px;
+		word-break: break-all;
 	}
 
 
@@ -1737,6 +1994,7 @@
 		display: flex;
 		align-items: center;
 		gap: 10px;
+		margin-bottom: 10px;
 	}
 
 	.input-textarea {
@@ -2076,7 +2334,7 @@
 		opacity: 0.7;
 	}
 
-	/* 设置窗样式 */
+	/* 设置��窗样式 */
 	.settings-popup {
 		background: #fff;
 		border-radius: 12px;
@@ -2205,4 +2463,108 @@
 		justify-content: center;
 		align-items: center;
 	}
+
+	/* 添加图片聊天相关样式 */
+	.message-images {
+		margin-bottom: 8px;
+	}
+
+	.chat-image {
+		width: 120px;
+		height: 120px;
+		border-radius: 8px;
+		margin-bottom: 5px;
+	}
+
+	/* 视频样式 */
+	.message-videos {
+		margin-bottom: 8px;
+	}
+
+	.video-thumbnail-container {
+		position: relative;
+		width: 120px;
+		height: 120px;
+		margin-bottom: 5px;
+	}
+
+	.hidden-video {
+		position: absolute;
+		width: 1px;
+		height: 1px;
+		opacity: 0;
+		pointer-events: none;
+	}
+
+	.play-icon-overlay {
+		position: absolute;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		background-color: rgba(0, 0, 0, 0.2);
+		border-radius: 8px;
+	}
+
+	.play-icon {
+		width: 40px;
+		height: 40px;
+		opacity: 0.8;
+	}
+
+	/* 调整视频缩略图样式 */
+	.chat-image {
+		width: 120px;
+		height: 120px;
+		border-radius: 8px;
+		object-fit: cover;
+	}
+
+	/* 文档展示 */
+	.message-docs {
+		display: flex;
+		flex-wrap: wrap;
+		gap: 5px;
+		margin-top: 5px;
+	}
+
+	.doc-preview {
+		width: 75px;
+		height: 75px;
+		position: relative;
+		flex-shrink: 0;
+		border-radius: 8px;
+		overflow: hidden;
+		background-color: #f5f5f5;
+	}
+
+	.doc-preview-container {
+		width: 100%;
+		height: 100%;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+		padding: 5px;
+	}
+
+	.doc-icon {
+		width: 30px;
+		height: 30px;
+		margin-bottom: 5px;
+	}
+
+	.doc-name {
+		font-size: 10px;
+		color: #666;
+		text-align: center;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+		width: 100%;
+		padding: 0 5px;
+	}
 </style>