diff --git a/packageA/pages/chat/components/ai-paging.vue b/packageA/pages/chat/components/ai-paging.vue
index cadd2b2..37766db 100644
--- a/packageA/pages/chat/components/ai-paging.vue
+++ b/packageA/pages/chat/components/ai-paging.vue
@@ -101,8 +101,19 @@
v-for="(file, vInex) in msg.files"
:key="vInex"
@click="jumpUrl(file)"
+ :class="{ 'msg-image-file': isImage(file.type, file.name) }"
>
-
+
+
{{ file.name || '附件' }}
@@ -283,27 +294,48 @@
-
-
-
-
- {{ file.name }}
-
-
-
- {{ file.size }}
-
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+ 加载失败
+ 点击重试
+
+
+
+ {{ filesList.filter(f => isImage(f.type, f.name)).indexOf(file) + 1 }}/{{ filesList.filter(f => isImage(f.type, f.name)).length }}
+
+
+
+ {{ file.name }}
+
+
+
+ {{ file.size }}
+
+
+ 图片
+
+ {{ file.size }}
-
@@ -652,8 +684,93 @@ function getGuess() {
});
}
-function isImage(type) {
- return new RegExp('image').test(type);
+// 从文件路径提取文件名
+function getFileNameFromPath(filePath) {
+ if (!filePath) return '';
+ // 处理反斜杠和正斜杠
+ const path = filePath.replace(/\\/g, '/');
+ const lastSlashIndex = path.lastIndexOf('/');
+ if (lastSlashIndex !== -1) {
+ return path.substring(lastSlashIndex + 1);
+ }
+ return path;
+}
+
+// 从文件名推断文件类型
+function getFileTypeFromName(fileName) {
+ if (!fileName) return '';
+ const lowerName = fileName.toLowerCase();
+ if (lowerName.endsWith('.jpg') || lowerName.endsWith('.jpeg')) return 'image/jpeg';
+ if (lowerName.endsWith('.png')) return 'image/png';
+ if (lowerName.endsWith('.gif')) return 'image/gif';
+ if (lowerName.endsWith('.bmp')) return 'image/bmp';
+ if (lowerName.endsWith('.webp')) return 'image/webp';
+ if (lowerName.endsWith('.svg')) return 'image/svg+xml';
+ if (lowerName.endsWith('.pdf')) return 'application/pdf';
+ if (lowerName.endsWith('.doc')) return 'application/msword';
+ if (lowerName.endsWith('.docx')) return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
+ if (lowerName.endsWith('.ppt')) return 'application/vnd.ms-powerpoint';
+ if (lowerName.endsWith('.pptx')) return 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
+ if (lowerName.endsWith('.xls')) return 'application/vnd.ms-excel';
+ if (lowerName.endsWith('.xlsx')) return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
+ if (lowerName.endsWith('.txt')) return 'text/plain';
+ if (lowerName.endsWith('.md')) return 'text/markdown';
+ if (lowerName.endsWith('.html') || lowerName.endsWith('.htm')) return 'text/html';
+ if (lowerName.endsWith('.csv')) return 'text/csv';
+ return '';
+}
+
+function isImage(type, name) {
+ if (!type && !name) return false;
+
+ const debug = false;
+ if (debug) console.log('isImage检查:', { type, name });
+
+ if (type) {
+ const lowerType = type.toLowerCase();
+
+ if (lowerType.includes('image/')) {
+ if (debug) console.log('通过MIME类型识别为图片:', lowerType);
+ return true;
+ }
+
+ if (lowerType.includes('image')) {
+ if (debug) console.log('通过"image"关键词识别为图片:', lowerType);
+ return true;
+ }
+
+ const imageExtensionsInType = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'heic', 'heif', 'tiff', 'tif'];
+ for (const ext of imageExtensionsInType) {
+ if (lowerType.includes(ext)) {
+ if (debug) console.log('通过类型中的扩展名识别为图片:', ext);
+ return true;
+ }
+ }
+ }
+
+ if (name) {
+ const lowerName = name.toLowerCase();
+ const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.heic', '.heif', '.tiff', '.tif', '.jfif', '.pjpeg', '.pjp'];
+ for (const ext of imageExtensions) {
+ if (lowerName.endsWith(ext)) {
+ if (debug) console.log('通过文件扩展名识别为图片:', ext);
+ return true;
+ }
+ }
+
+ const imageExtensionsWithoutDot = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'heic', 'heif', 'tiff', 'tif', 'jfif', 'pjpeg', 'pjp'];
+ for (const ext of imageExtensionsWithoutDot) {
+ if (lowerName.endsWith('.' + ext)) {
+ if (debug) console.log('通过带点的扩展名识别为图片:', ext);
+ return true;
+ }
+ }
+
+ if (debug) console.log('未识别为图片:', lowerName);
+ }
+
+ if (debug) console.log('不是图片文件');
+ return false;
}
function isFile(type) {
@@ -665,71 +782,504 @@ function isFile(type) {
}
function jumpUrl(file) {
- if (file.url) {
- window.open(file.url);
- } else {
+ if (!file.url && !file.tempPath) {
$api.msg('文件地址丢失');
+ return;
+ }
+
+ if (isImage(file.type, file.name)) {
+ const imageUrl = file.tempPath || file.url;
+ uni.previewImage({ urls: [imageUrl], current: 0 });
+ } else {
+ window.open(file.url);
}
}
-function VerifyNumberFiles(num) {
- if (filesList.value.length >= config.allowedFileNumber) {
- $api.msg(`最大上传文件数量 ${config.allowedFileNumber} 个`);
+function VerifyNumberFiles(additionalCount = 1) {
+ const currentCount = filesList.value.length;
+ const maxCount = config.allowedFileNumber || 9;
+
+ if (currentCount + additionalCount > maxCount) {
+ $api.msg(`最多只能上传${maxCount}个文件,当前已有${currentCount}个`);
return true;
} else {
return false;
}
}
+function onImageLoad(file) {
+ const index = filesList.value.findIndex(f => f.url === file.url);
+ if (index !== -1) {
+ filesList.value[index].loading = false;
+ filesList.value[index].error = false;
+ }
+}
+
+function onImageError(file) {
+ const index = filesList.value.findIndex(f => f.url === file.url);
+ if (index !== -1) {
+ filesList.value[index].loading = false;
+ filesList.value[index].error = true;
+ }
+}
+
+function retryImageLoad(file) {
+ const index = filesList.value.findIndex(f => f.url === file.url);
+ if (index !== -1) {
+ filesList.value[index].loading = true;
+ filesList.value[index].error = false;
+ // 触发重新加载
+ setTimeout(() => {
+ if (filesList.value[index]) {
+ filesList.value[index].loading = false;
+ }
+ }, 100);
+ }
+}
+
function uploadCamera(type = 'camera') {
- if (VerifyNumberFiles()) return;
- uni.chooseImage({
- count: 1, //默认9
- sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
- sourceType: [type], //从相册选择
- success: function (res) {
- const tempFilePaths = res.tempFilePaths;
- const file = res.tempFiles[0];
- // 继续上传
- $api.uploadFile(tempFilePaths[0], true).then((resData) => {
- resData = JSON.parse(resData);
- if (isImage(file.type)) {
- filesList.value.push({
- url: resData.msg,
- type: file.type,
- name: file.name,
+ // 在微信小程序中检查权限
+ // #ifdef MP-WEIXIN
+ const scope = type === 'camera' ? 'scope.camera' : 'scope.writePhotosAlbum';
+
+ uni.getSetting({
+ success: (res) => {
+ if (!res.authSetting[scope]) {
+ // 未授权,发起授权请求
+ uni.authorize({
+ scope: scope,
+ success: () => {
+ // 授权成功,执行选择图片
+ chooseImageAfterAuth();
+ },
+ fail: (err) => {
+ console.error('授权失败:', err);
+ if (err.errMsg && err.errMsg.includes('auth deny')) {
+ uni.showModal({
+ title: '权限提示',
+ content: `需要${type === 'camera' ? '相机' : '相册'}权限才能上传图片,请在设置中开启权限`,
+ showCancel: false,
+ confirmText: '确定'
+ });
+ } else {
+ $api.msg('授权失败,请重试');
+ }
+ }
+ });
+ } else {
+ // 已授权,直接执行选择图片
+ chooseImageAfterAuth();
+ }
+ },
+ fail: (err) => {
+ console.error('获取设置失败:', err);
+ chooseImageAfterAuth(); // 失败时也尝试选择图片
+ }
+ });
+ // #endif
+
+ // #ifndef MP-WEIXIN
+ // 非微信小程序环境直接选择图片
+ chooseImageAfterAuth();
+ // #endif
+
+ function chooseImageAfterAuth() {
+ uni.chooseImage({
+ count: 9, //支持多选,最多9张
+ sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
+ // sourceType: [type], // 移除 sourceType 参数,解决微信 "unknown scene" 错误
+ success: function (res) {
+ const tempFilePaths = res.tempFilePaths;
+ const files = res.tempFiles;
+
+ // 检查文件数量限制
+ if (VerifyNumberFiles(files.length)) return;
+
+ // 显示上传提示
+ $api.msg(`开始上传${files.length}张图片...`);
+
+ // 上传所有图片
+ const uploadPromises = tempFilePaths.map((filePath, index) => {
+ return $api.uploadFile(filePath, true).then((resData) => {
+ resData = JSON.parse(resData);
+ if (resData.code === 200 && resData.filePath) {
+ // 从文件对象获取文件信息
+ const fileInfo = files[index] || {};
+ const fileName = getFileNameFromPath(fileInfo.name || filePath);
+ const fileType = fileInfo.type || getFileTypeFromName(fileName);
+ const fileSize = fileInfo.size || 0;
+
+ // 使用统一的文件路径处理函数
+ const processedFile = processFilePath(
+ resData.filePath, // 原始服务器路径
+ fileName,
+ fileType,
+ fileSize,
+ tempFilePaths[index] // 临时路径用于预览
+ );
+
+ // 添加图片加载状态字段
+ processedFile.loading = false;
+ processedFile.error = false;
+
+ return processedFile;
+ } else {
+ throw new Error(resData.msg || '上传失败');
+ }
+ });
+ });
+
+ Promise.all(uploadPromises).then((uploadedFiles) => {
+ // 将所有上传成功的文件添加到filesList,并设置图片加载状态
+ uploadedFiles.forEach(file => {
+ if (isImage(file.type, file.name)) {
+ file.loading = true; // 图片开始加载
+ }
+ filesList.value.push(file);
});
textInput.value = state.uploadFileTips;
+ showfile.value = false; // 关闭上传面板
+ $api.msg(`成功上传${uploadedFiles.length}张图片`);
+ }).catch((error) => {
+ $api.msg(error.message || '图片上传失败');
+ });
+ },
+ fail: function (err) {
+ console.error('选择图片失败:', err);
+ // 专门处理微信 "unknown scene" 错误
+ if (err.errMsg && err.errMsg.includes('unknown scene')) {
+ $api.msg('选择图片失败,请尝试重新选择或检查权限设置');
+ } else if (err.errMsg && err.errMsg.includes('auth deny')) {
+ $api.msg('没有相机或相册权限,请在设置中开启');
+ } else {
+ $api.msg('选择图片失败: ' + (err.errMsg || '未知错误'));
}
- });
- },
- });
+ }
+ });
+ }
}
function getUploadFile(type = 'camera') {
- if (VerifyNumberFiles()) return;
- uni.chooseFile({
- count: 1,
- success: (res) => {
- const tempFilePaths = res.tempFilePaths;
- const file = res.tempFiles[0];
- const allowedTypes = config.allowedFileTypes || [];
- const size = $api.formatFileSize(file.size);
- if (!allowedTypes.includes(file.type)) {
- return $api.msg('仅支持 txt md html word pdf ppt csv excel 格式类型');
+ // 检测是否是微信小程序环境
+ const isWeixinMiniProgram = typeof wx !== 'undefined' && wx.chooseMessageFile;
+
+ console.log('当前环境检测:', {
+ isWeixinMiniProgram,
+ wxExists: typeof wx !== 'undefined',
+ hasChooseMessageFile: typeof wx !== 'undefined' && wx.chooseMessageFile
+ });
+
+ if (isWeixinMiniProgram) {
+ console.log('微信小程序环境,使用uni.chooseMessageFile');
+ // 微信小程序环境:使用uni.chooseMessageFile
+ uni.chooseMessageFile({
+ count: 9,
+ type: 'file',
+ success: (res) => {
+ console.log('uni.chooseMessageFile返回的res:', res);
+ // uni.chooseMessageFile返回的数据结构不同
+ // tempFiles数组中每个对象有path、name、size等属性
+ const tempFiles = res.tempFiles || [];
+ const tempFilePaths = tempFiles.map(file => file.path);
+ const files = tempFiles;
+
+ console.log('tempFilePaths:', tempFilePaths);
+ console.log('tempFiles:', tempFiles);
+ if (files && files.length > 0) {
+ console.log('第一个文件详情:', files[0]);
+ console.log('第一个文件的所有属性:', Object.keys(files[0]));
+ // 详细记录每个文件的所有属性
+ files.forEach((file, index) => {
+ console.log(`文件 ${index} 完整对象:`, JSON.stringify(file));
+ console.log(`文件 ${index} 关键属性:`, {
+ name: file.name,
+ type: file.type,
+ path: file.path,
+ size: file.size,
+ hasType: 'type' in file,
+ typeIsString: typeof file.type === 'string',
+ typeLength: file.type ? file.type.length : 0
+ });
+ });
+ }
+
+ // 调用公共的文件处理函数
+ handleSelectedFiles(tempFilePaths, files);
+ },
+ fail: (err) => {
+ console.error('uni.chooseMessageFile失败:', err);
+ // 微信小程序特定的错误处理
+ if (err.errMsg && err.errMsg.includes('cancel')) {
+ // 用户取消选择,不提示错误
+ console.log('用户取消选择文件');
+ } else {
+ $api.msg('选择文件失败,请重试');
+ }
}
- // 继续上传
- $api.uploadFile(tempFilePaths[0], true).then((resData) => {
- resData = JSON.parse(resData);
- filesList.value.push({
- url: resData.msg,
- type: file.type,
- name: file.name,
- size: size,
- });
- textInput.value = state.uploadFileTips;
+ });
+ } else {
+ console.log('非微信小程序环境,使用uni.chooseFile');
+ // 其他平台:使用uni.chooseFile
+ uni.chooseFile({
+ count: 9,
+ success: (res) => {
+ console.log('uni.chooseFile返回的res:', res);
+ const tempFilePaths = res.tempFilePaths;
+ const files = res.tempFiles;
+
+ console.log('tempFilePaths:', tempFilePaths);
+ console.log('tempFiles:', files);
+ if (files && files.length > 0) {
+ console.log('第一个文件详情:', files[0]);
+ console.log('第一个文件的所有属性:', Object.keys(files[0]));
+ // 详细记录每个文件的所有属性
+ files.forEach((file, index) => {
+ console.log(`文件 ${index} 完整对象:`, JSON.stringify(file));
+ console.log(`文件 ${index} 关键属性:`, {
+ name: file.name,
+ type: file.type,
+ path: file.path,
+ size: file.size,
+ hasType: 'type' in file,
+ typeIsString: typeof file.type === 'string',
+ typeLength: file.type ? file.type.length : 0
+ });
+ });
+ }
+
+ // 调用公共的文件处理函数
+ handleSelectedFiles(tempFilePaths, files);
+ },
+ fail: (err) => {
+ console.error('uni.chooseFile失败:', err);
+ if (err.errMsg && !err.errMsg.includes('cancel')) {
+ $api.msg('选择文件失败,请重试');
+ }
+ }
+ });
+ }
+}
+
+// 处理服务器返回的文件路径,生成正确的显示URL和服务器路径
+function processFilePath(originalFilePath, fileName, fileType, fileSize, tempPath) {
+ let displayUrl = originalFilePath; // 用于缩略图显示的URL(外网域名)
+ const serverPath = originalFilePath; // 原始服务器路径(内网地址,用于对话接口)
+
+ console.log('processFilePath原始fileUrl:', originalFilePath);
+
+ // 如果filePath包含IP地址,替换为配置的域名(用于显示)
+ if (displayUrl && displayUrl.includes('://')) {
+ // 检查是否是本地临时路径(如http://tmp/),如果是则保持原样
+ if (displayUrl.startsWith('http://tmp/')) {
+ // 本地临时路径,保持原样
+ console.log('本地临时路径,保持原样:', displayUrl);
+ } else {
+ // 方法1:直接替换域名部分(保持路径结构)- 使用兼容的方法
+ // 例如:http://10.98.80.146/file/xxx.jpg -> https://www.xjksly.cn/file/xxx.jpg
+ const originalUrl = displayUrl;
+
+ // 提取协议、域名和路径部分
+ let protocol = '';
+ let domain = '';
+ let path = '';
+
+ // 分离协议和剩余部分
+ const protocolIndex = originalUrl.indexOf('://');
+ if (protocolIndex !== -1) {
+ protocol = originalUrl.substring(0, protocolIndex + 3);
+ const rest = originalUrl.substring(protocolIndex + 3);
+ const slashIndex = rest.indexOf('/');
+ if (slashIndex !== -1) {
+ domain = rest.substring(0, slashIndex);
+ path = rest.substring(slashIndex);
+ } else {
+ domain = rest;
+ path = '/';
+ }
+ }
+
+ console.log('解析结果:', { protocol, domain, path });
+
+ // 使用config.baseUrl或config.imgBaseUrl作为新域名
+ let baseUrl = config.baseUrl || config.imgBaseUrl;
+ // 如果baseUrl以/api/ks结尾,去掉这个部分
+ if (baseUrl.endsWith('/api/ks')) {
+ baseUrl = baseUrl.substring(0, baseUrl.length - 7);
+ }
+
+ // 构建新URL:使用新域名 + 原路径
+ // 确保baseUrl不以/结尾,path以/开头
+ let cleanBaseUrl = baseUrl;
+ if (cleanBaseUrl.endsWith('/')) {
+ cleanBaseUrl = cleanBaseUrl.substring(0, cleanBaseUrl.length - 1);
+ }
+ if (!path.startsWith('/')) {
+ path = '/' + path;
+ }
+
+ displayUrl = cleanBaseUrl + path;
+ console.log('替换域名后的displayUrl(用于显示):', displayUrl);
+ }
+ } else if (displayUrl && !displayUrl.startsWith('http')) {
+ // 如果filePath不是完整的URL,直接添加图片基础URL前缀
+ displayUrl = config.imgBaseUrl + displayUrl;
+ console.log('非完整URL,添加imgBaseUrl前缀:', displayUrl);
+ }
+
+ return {
+ url: displayUrl, // 使用处理后的完整URL(用于缩略图显示)
+ serverPath: serverPath, // 原始服务器路径(内网地址,用于对话接口)
+ type: fileType,
+ name: fileName,
+ size: fileSize > 0 ? $api.formatFileSize(fileSize) : '',
+ tempPath: tempPath // 保存临时路径用于预览
+ };
+}
+
+// 处理选择的文件(公共逻辑)
+function handleSelectedFiles(tempFilePaths, files) {
+ // 检查文件数量限制
+ if (VerifyNumberFiles(files.length)) return;
+
+ const allowedTypes = config.allowedFileTypes || [];
+
+ // 调试:在文件类型检查之前记录详细信息
+ console.log('=== handleSelectedFiles 文件类型检查开始 ===');
+ console.log('allowedTypes配置:', allowedTypes);
+ console.log('files数组长度:', files.length);
+ files.forEach((file, index) => {
+ console.log(`文件 ${index} 检查前详情:`, {
+ name: file.name,
+ type: file.type,
+ hasType: 'type' in file,
+ typeValue: file.type,
+ typeIsString: typeof file.type === 'string',
+ typeLength: file.type ? file.type.length : 0,
+ size: file.size,
+ path: file.path
+ });
+ // 尝试从文件名推断类型
+ const inferredType = getFileTypeFromName(file.name);
+ console.log(`文件 ${index} 从文件名推断的类型:`, inferredType);
+ // 检查是否在allowedTypes中
+ const isAllowedByType = allowedTypes.includes(file.type);
+ const isAllowedByInferred = allowedTypes.includes(inferredType);
+ const isImageFile = isImage(file.type, file.name);
+ console.log(`文件 ${index} 检查结果:`, {
+ isAllowedByType,
+ isAllowedByInferred,
+ isImageFile,
+ finalAllowed: isAllowedByType || isAllowedByInferred || isImageFile
+ });
+ });
+ console.log('=== handleSelectedFiles 文件类型检查结束 ===');
+
+ // 检查所有文件类型 - 增强版:优先使用file.type,回退到文件名推断
+ const invalidFiles = files.filter(file => {
+ // 如果文件类型存在且在允许的类型列表中,则通过
+ if (file.type && allowedTypes.includes(file.type)) {
+ return false;
+ }
+
+ // 尝试从文件名推断类型
+ const inferredType = getFileTypeFromName(file.name);
+ if (inferredType && allowedTypes.includes(inferredType)) {
+ return false;
+ }
+
+ // 如果是图片,也通过
+ if (isImage(file.type, file.name)) {
+ return false;
+ }
+
+ // 否则,文件无效
+ return true;
+ });
+
+ if (invalidFiles.length > 0) {
+ // 调试:查看不支持的文件类型
+ console.log('不支持的文件类型:', invalidFiles.map(f => ({name: f.name, type: f.type})));
+ // 更详细地显示为什么这些文件不被支持
+ invalidFiles.forEach((file, index) => {
+ const inferredType = getFileTypeFromName(file.name);
+ console.log(`无效文件 ${index} 分析:`, {
+ name: file.name,
+ type: file.type,
+ inferredType,
+ isImage: isImage(file.type, file.name),
+ isInAllowedTypes: allowedTypes.includes(file.type),
+ isInferredInAllowedTypes: allowedTypes.includes(inferredType)
});
- },
+ });
+ return $api.msg('仅支持 txt md html word doc docx pdf ppt pptx csv excel xlsx 和图片格式');
+ }
+
+ // 显示上传提示
+ const imageCount = files.filter(file => isImage(file.type, file.name)).length;
+ const otherFileCount = files.length - imageCount;
+ let tip = '开始上传';
+ if (imageCount > 0) tip += `${imageCount}张图片`;
+ if (otherFileCount > 0) {
+ if (imageCount > 0) tip += '和';
+ tip += `${otherFileCount}个文件`;
+ }
+ $api.msg(tip + '...');
+
+ // 上传所有文件
+ const uploadPromises = tempFilePaths.map((filePath, index) => {
+ return $api.uploadFile(filePath, true).then((resData) => {
+ resData = JSON.parse(resData);
+ console.log('服务器返回的resData:', resData);
+ if (resData.code === 200 && resData.filePath) {
+ // 从文件对象或文件路径获取文件信息
+ const fileInfo = files[index] || {};
+ const fileName = fileInfo.name || getFileNameFromPath(tempFilePaths[index]);
+ const fileType = fileInfo.type || getFileTypeFromName(fileName);
+ const fileSize = fileInfo.size || 0;
+
+ // 使用统一的文件路径处理函数
+ const processedFile = processFilePath(
+ resData.filePath, // 原始服务器路径
+ fileName,
+ fileType,
+ fileSize,
+ tempFilePaths[index] // 临时路径用于预览
+ );
+
+ // 调试:记录文件信息
+ console.log('上传文件信息:', {
+ fileInfo: fileInfo,
+ type: fileType,
+ name: fileName,
+ size: fileSize,
+ isImage: isImage(fileType, fileName),
+ originalFilePath: resData.filePath,
+ displayUrl: processedFile.url,
+ serverPath: processedFile.serverPath,
+ imgBaseUrl: config.imgBaseUrl
+ });
+
+ return processedFile;
+ } else {
+ throw new Error(resData.msg || '上传失败');
+ }
+ });
+ });
+
+ Promise.all(uploadPromises).then((uploadedFiles) => {
+ // 将所有上传成功的文件添加到filesList
+ uploadedFiles.forEach(file => {
+ if (isImage(file.type, file.name)) {
+ file.loading = true; // 图片开始加载
+ file.error = false;
+ }
+ filesList.value.push(file);
+ });
+ textInput.value = state.uploadFileTips;
+ showfile.value = false; // 关闭上传面板
+ $api.msg(`成功上传${uploadedFiles.length}个文件`);
+ }).catch((error) => {
+ $api.msg(error.message || '文件上传失败');
});
}
@@ -1095,6 +1645,25 @@ defineExpose({ scrollToBottom, closeGuess, closeFile, changeQueries, handleTouch
font-size: 24rpx
.msg-files:active
background: #e9e9e9
+
+// 消息中的图片文件样式
+.msg-image-file
+ height: 120rpx
+ width: 120rpx
+ padding: 4rpx
+ flex-direction: column
+ justify-content: center
+ .msg-image-thumbnail
+ width: 100%
+ height: 80rpx
+ border-radius: 8rpx
+ margin-bottom: 8rpx
+ .msg-file-text
+ font-size: 20rpx
+ text-align: center
+ white-space: nowrap
+ overflow: hidden
+ text-overflow: ellipsis
.guess
padding: 5rpx 0 10rpx 0
.guess-list
@@ -1422,12 +1991,13 @@ image-margin-top = 40rpx
.area-uploadfiles
position: absolute
- top: -180rpx
+ bottom: 100%
width: calc(100% - 30rpx)
background: #FFFFFF
left: 0
padding: 10rpx 0 10rpx 30rpx
box-shadow: 0rpx -4rpx 10rpx 0rpx rgba(11,44,112,0.06);
+ z-index: 1003
.uploadfiles-scroll
height: 100%
.uploadfiles-list
@@ -1453,11 +2023,17 @@ image-margin-top = 40rpx
height: 100%
font-size: 24rpx
position: relative
- min-width: 460rpx;
- height: 160rpx;
- border-radius: 12rpx 12rpx 12rpx 12rpx;
+ min-width: 200rpx;
+ max-width: 200rpx;
+ height: 200rpx;
+ border-radius: 12rpx;
border: 2rpx solid #E2E2E2;
overflow: hidden
+ display: flex
+ flex-direction: column
+ &.file-border
+ border: 2rpx solid #4A90E2;
+ background: #F5F9FF;
.file-del
position: absolute
right: 25rpx
@@ -1489,9 +2065,61 @@ image-margin-top = 40rpx
white-space: nowrap
color: #7B7B7B;
max-width: 100%
+ .file-thumbnail
+ flex: 1
+ display: flex
+ align-items: center
+ justify-content: center
+ overflow: hidden
+ border-radius: 10rpx 10rpx 0 0
+ background: #F8F9FA
.file-iconImg
height: 100%
width: 100%
+ object-fit: cover
+ .file-info
+ padding: 12rpx 16rpx
+ background: white
+ border-top: 1rpx solid #F0F0F0
+ .file-type-tag
+ font-size: 20rpx
+ color: #4A90E2
+ background: #F0F7FF
+ padding: 4rpx 8rpx
+ border-radius: 4rpx
+ .image-loading, .image-error
+ position: absolute
+ top: 0
+ left: 0
+ right: 0
+ bottom: 0
+ display: flex
+ flex-direction: column
+ align-items: center
+ justify-content: center
+ background: rgba(255, 255, 255, 0.9)
+ .loading-text, .error-text
+ font-size: 24rpx
+ margin-top: 10rpx
+ .loading-text
+ color: #999
+ .error-text
+ color: #ff4444
+ .retry-text
+ font-size: 22rpx
+ color: #4A90E2
+ margin-top: 5rpx
+ text-decoration: underline
+ .image-count-badge
+ position: absolute
+ top: 10rpx
+ right: 10rpx
+ background: rgba(0, 0, 0, 0.6)
+ color: white
+ font-size: 20rpx
+ padding: 4rpx 8rpx
+ border-radius: 12rpx
+ z-index: 2
.filerow
display: flex
align-items: center
diff --git a/stores/userChatGroupStore.js b/stores/userChatGroupStore.js
index 62aabb9..edeab52 100644
--- a/stores/userChatGroupStore.js
+++ b/stores/userChatGroupStore.js
@@ -107,108 +107,108 @@ const useChatGroupDBStore = defineStore("messageGroup", () => {
return await baseDB.db.add(massageName.value, payload);
}
- async function getStearm(text, fileUrls = [], progress, options = {}) {
-
- return new Promise((resolve, reject) => {
- try {
- toggleTyping(true);
- const customDataID = 'message_' + UUID.generate()
-
- // 对话历史管理:只保留最近的N条消息,防止token超限
- // 计算消息数量,只保留最近的10条消息(可根据实际情况调整)
- const MAX_HISTORY_MESSAGES = 10;
- const historyMessages = messages.value.slice(-MAX_HISTORY_MESSAGES);
-
- const params = {
- data: text,
- sessionId: chatSessionID.value,
- dataId: customDataID
- };
- if (fileUrls && fileUrls.length) {
- params['fileUrl'] = fileUrls.map((item) => item.url);
- }
- // ------>
- const MsgData = {
- text: text,
- self: true,
- displayText: text,
- files: fileUrls
- };
- addMessage(MsgData); // 添加message数据
- // <------
- const newMsg = {
- text: '', // 存储原始结构化内容
- self: false,
- displayText: '', // 用于流式渲染展示
- dataId: customDataID
- };
- const index = messages.value.length;
- messages.value.push(newMsg);
-
- const rawParts = Array.isArray(text) ? text : [text]; // 统一处理
-
- // 用于追加每个部分的流式数据
- let partIndex = 0;
-
- function handleUnload() {
- newMsg.parentGroupId = chatSessionID.value;
- baseDB.db.add(massageName.value, newMsg).then((id) => {
- messages.value[index] = {
- ...newMsg,
- id
- };
- });
- }
- // #ifdef H5
- if (typeof window !== 'undefined') {
- window.addEventListener("unload", handleUnload);
- }
- // #endif
-
- function onDataReceived(data) {
- // 支持追加多个部分
- newMsg.text += data;
- newMsg.displayText += data;
- messages.value[index] = {
- ...newMsg
- };
- progress && progress();
-
- // 调用外部传入的onDataReceived回调
- if (options.onDataReceived) {
- options.onDataReceived(data, newMsg, index);
- }
- }
-
- function onError(error) {
- msg('服务响应异常');
- reject(error);
- }
-
- function onComplete() {
- messages.value[index] = {
- ...newMsg
- };
- toggleTyping(false);
- // #ifdef H5
- if (typeof window !== 'undefined') {
- window.removeEventListener("unload", handleUnload);
- }
- // #endif
- handleUnload();
- // 调用外部传入的onComplete回调
- if (options.onComplete) {
- options.onComplete();
- }
- resolve();
- }
-
- $api.streamRequest('/chat', params, onDataReceived, onError, onComplete);
- } catch (err) {
- console.log(err);
- reject(err);
- }
- });
+ async function getStearm(text, fileUrls = [], progress, options = {}) {
+
+ return new Promise((resolve, reject) => {
+ try {
+ toggleTyping(true);
+ const customDataID = 'message_' + UUID.generate()
+
+ // 对话历史管理:只保留最近的N条消息,防止token超限
+ // 计算消息数量,只保留最近的10条消息(可根据实际情况调整)
+ const MAX_HISTORY_MESSAGES = 10;
+ const historyMessages = messages.value.slice(-MAX_HISTORY_MESSAGES);
+
+ const params = {
+ data: text,
+ sessionId: chatSessionID.value,
+ dataId: customDataID
+ };
+ if (fileUrls && fileUrls.length) {
+ params['fileUrl'] = fileUrls.map((item) => item.serverPath || item.url);
+ }
+ // ------>
+ const MsgData = {
+ text: text,
+ self: true,
+ displayText: text,
+ files: fileUrls
+ };
+ addMessage(MsgData); // 添加message数据
+ // <------
+ const newMsg = {
+ text: '', // 存储原始结构化内容
+ self: false,
+ displayText: '', // 用于流式渲染展示
+ dataId: customDataID
+ };
+ const index = messages.value.length;
+ messages.value.push(newMsg);
+
+ const rawParts = Array.isArray(text) ? text : [text]; // 统一处理
+
+ // 用于追加每个部分的流式数据
+ let partIndex = 0;
+
+ function handleUnload() {
+ newMsg.parentGroupId = chatSessionID.value;
+ baseDB.db.add(massageName.value, newMsg).then((id) => {
+ messages.value[index] = {
+ ...newMsg,
+ id
+ };
+ });
+ }
+ // #ifdef H5
+ if (typeof window !== 'undefined') {
+ window.addEventListener("unload", handleUnload);
+ }
+ // #endif
+
+ function onDataReceived(data) {
+ // 支持追加多个部分
+ newMsg.text += data;
+ newMsg.displayText += data;
+ messages.value[index] = {
+ ...newMsg
+ };
+ progress && progress();
+
+ // 调用外部传入的onDataReceived回调
+ if (options.onDataReceived) {
+ options.onDataReceived(data, newMsg, index);
+ }
+ }
+
+ function onError(error) {
+ msg('服务响应异常');
+ reject(error);
+ }
+
+ function onComplete() {
+ messages.value[index] = {
+ ...newMsg
+ };
+ toggleTyping(false);
+ // #ifdef H5
+ if (typeof window !== 'undefined') {
+ window.removeEventListener("unload", handleUnload);
+ }
+ // #endif
+ handleUnload();
+ // 调用外部传入的onComplete回调
+ if (options.onComplete) {
+ options.onComplete();
+ }
+ resolve();
+ }
+
+ $api.streamRequest('/chat', params, onDataReceived, onError, onComplete);
+ } catch (err) {
+ console.log(err);
+ reject(err);
+ }
+ });
}
// 状态控制
diff --git a/utils/request.js b/utils/request.js
index 2bf8535..648f0fc 100644
--- a/utils/request.js
+++ b/utils/request.js
@@ -155,7 +155,7 @@ export function uploadFile(tempFilePaths, loading = false) {
header["Authorization"] = encodeURIComponent(Authorization);
return new Promise((resolve, reject) => {
uni.uploadFile({
- url: config.baseUrl + '/app/file/upload',
+ url: config.baseUrl + '/app/file/uploadFile',
filePath: tempFilePaths,
name: 'file',
header,