文件上传功开发
This commit is contained in:
@@ -101,8 +101,19 @@
|
|||||||
v-for="(file, vInex) in msg.files"
|
v-for="(file, vInex) in msg.files"
|
||||||
:key="vInex"
|
:key="vInex"
|
||||||
@click="jumpUrl(file)"
|
@click="jumpUrl(file)"
|
||||||
|
:class="{ 'msg-image-file': isImage(file.type, file.name) }"
|
||||||
>
|
>
|
||||||
<image class="msg-file-icon" src="/static/icon/Vector2.png"></image>
|
<image
|
||||||
|
v-if="isImage(file.type, file.name)"
|
||||||
|
class="msg-image-thumbnail"
|
||||||
|
:src="file.url"
|
||||||
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
<image
|
||||||
|
v-else
|
||||||
|
class="msg-file-icon"
|
||||||
|
src="/static/icon/Vector2.png"
|
||||||
|
></image>
|
||||||
<text class="msg-file-text">{{ file.name || '附件' }}</text>
|
<text class="msg-file-text">{{ file.name || '附件' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -283,27 +294,48 @@
|
|||||||
<view class="uploadfiles-list">
|
<view class="uploadfiles-list">
|
||||||
<view
|
<view
|
||||||
class="file-uploadsend"
|
class="file-uploadsend"
|
||||||
:class="{ 'file-border': isImage(file.type) }"
|
:class="{ 'file-border': isImage(file.type, file.name) }"
|
||||||
v-for="(file, index) in filesList"
|
v-for="(file, index) in filesList"
|
||||||
:key="index"
|
:key="index"
|
||||||
>
|
>
|
||||||
|
<view class="file-thumbnail" @click="jumpUrl(file)">
|
||||||
<image
|
<image
|
||||||
class="file-iconImg"
|
class="file-iconImg"
|
||||||
@click="jumpUrl(file)"
|
v-if="isImage(file.type, file.name) && !file.error"
|
||||||
v-if="isImage(file.type)"
|
:src="file.tempPath || file.url"
|
||||||
:src="file.url"
|
mode="aspectFill"
|
||||||
|
@load="onImageLoad(file)"
|
||||||
|
@error="onImageError(file)"
|
||||||
></image>
|
></image>
|
||||||
<view class="file-doc" @click="jumpUrl(file)" v-else>
|
<view class="file-doc" v-else-if="!isImage(file.type, file.name)">
|
||||||
<FileIcon class="doc-icon" :type="file.type"></FileIcon>
|
<FileIcon class="doc-icon" :type="file.type"></FileIcon>
|
||||||
<view class="doc-con">
|
</view>
|
||||||
|
<view class="image-loading" v-if="isImage(file.type, file.name) && file.loading">
|
||||||
|
<uni-icons type="spinner-cycle" size="30" color="#999"></uni-icons>
|
||||||
|
<text class="loading-text">加载中...</text>
|
||||||
|
</view>
|
||||||
|
<view class="image-error" v-if="isImage(file.type, file.name) && file.error" @click="retryImageLoad(file)">
|
||||||
|
<uni-icons type="info" size="30" color="#ff4444"></uni-icons>
|
||||||
|
<text class="error-text">加载失败</text>
|
||||||
|
<text class="retry-text">点击重试</text>
|
||||||
|
</view>
|
||||||
|
<!-- 图片数量标签 -->
|
||||||
|
<view class="image-count-badge" v-if="isImage(file.type, file.name) && filesList.filter(f => isImage(f.type, f.name)).length > 1">
|
||||||
|
{{ filesList.filter(f => isImage(f.type, f.name)).indexOf(file) + 1 }}/{{ filesList.filter(f => isImage(f.type, f.name)).length }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="file-info">
|
||||||
<view class="filename-text">{{ file.name }}</view>
|
<view class="filename-text">{{ file.name }}</view>
|
||||||
<view class="filerow">
|
<view class="filerow" v-if="!isImage(file.type, file.name)">
|
||||||
<FileText :type="file.type"></FileText>
|
<FileText :type="file.type"></FileText>
|
||||||
<view class="row-x"></view>
|
<view class="row-x"></view>
|
||||||
<view class="filename-size">{{ file.size }}</view>
|
<view class="filename-size">{{ file.size }}</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="filerow" v-else>
|
||||||
|
<text class="file-type-tag">图片</text>
|
||||||
|
<view class="row-x"></view>
|
||||||
|
<view class="filename-size">{{ file.size }}</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- <image class="file-icon" @click="jumpUrl(file)" :src=""></image> -->
|
|
||||||
</view>
|
</view>
|
||||||
<view class="file-del" catchtouchmove="true" @click="delfile(file)">
|
<view class="file-del" catchtouchmove="true" @click="delfile(file)">
|
||||||
<uni-icons type="closeempty" color="#FFFFFF" size="10"></uni-icons>
|
<uni-icons type="closeempty" color="#FFFFFF" size="10"></uni-icons>
|
||||||
@@ -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) {
|
function isFile(type) {
|
||||||
@@ -665,71 +782,504 @@ function isFile(type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function jumpUrl(file) {
|
function jumpUrl(file) {
|
||||||
if (file.url) {
|
if (!file.url && !file.tempPath) {
|
||||||
window.open(file.url);
|
|
||||||
} else {
|
|
||||||
$api.msg('文件地址丢失');
|
$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) {
|
function VerifyNumberFiles(additionalCount = 1) {
|
||||||
if (filesList.value.length >= config.allowedFileNumber) {
|
const currentCount = filesList.value.length;
|
||||||
$api.msg(`最大上传文件数量 ${config.allowedFileNumber} 个`);
|
const maxCount = config.allowedFileNumber || 9;
|
||||||
|
|
||||||
|
if (currentCount + additionalCount > maxCount) {
|
||||||
|
$api.msg(`最多只能上传${maxCount}个文件,当前已有${currentCount}个`);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
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') {
|
function uploadCamera(type = 'camera') {
|
||||||
if (VerifyNumberFiles()) return;
|
// 在微信小程序中检查权限
|
||||||
uni.chooseImage({
|
// #ifdef MP-WEIXIN
|
||||||
count: 1, //默认9
|
const scope = type === 'camera' ? 'scope.camera' : 'scope.writePhotosAlbum';
|
||||||
sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
|
|
||||||
sourceType: [type], //从相册选择
|
uni.getSetting({
|
||||||
success: function (res) {
|
success: (res) => {
|
||||||
const tempFilePaths = res.tempFilePaths;
|
if (!res.authSetting[scope]) {
|
||||||
const file = res.tempFiles[0];
|
// 未授权,发起授权请求
|
||||||
// 继续上传
|
uni.authorize({
|
||||||
$api.uploadFile(tempFilePaths[0], true).then((resData) => {
|
scope: scope,
|
||||||
resData = JSON.parse(resData);
|
success: () => {
|
||||||
if (isImage(file.type)) {
|
// 授权成功,执行选择图片
|
||||||
filesList.value.push({
|
chooseImageAfterAuth();
|
||||||
url: resData.msg,
|
},
|
||||||
type: file.type,
|
fail: (err) => {
|
||||||
name: file.name,
|
console.error('授权失败:', err);
|
||||||
|
if (err.errMsg && err.errMsg.includes('auth deny')) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '权限提示',
|
||||||
|
content: `需要${type === 'camera' ? '相机' : '相册'}权限才能上传图片,请在设置中开启权限`,
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '确定'
|
||||||
});
|
});
|
||||||
textInput.value = state.uploadFileTips;
|
} 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') {
|
function getUploadFile(type = 'camera') {
|
||||||
if (VerifyNumberFiles()) return;
|
// 检测是否是微信小程序环境
|
||||||
uni.chooseFile({
|
const isWeixinMiniProgram = typeof wx !== 'undefined' && wx.chooseMessageFile;
|
||||||
count: 1,
|
|
||||||
|
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) => {
|
success: (res) => {
|
||||||
const tempFilePaths = res.tempFilePaths;
|
console.log('uni.chooseMessageFile返回的res:', res);
|
||||||
const file = res.tempFiles[0];
|
// uni.chooseMessageFile返回的数据结构不同
|
||||||
const allowedTypes = config.allowedFileTypes || [];
|
// tempFiles数组中每个对象有path、name、size等属性
|
||||||
const size = $api.formatFileSize(file.size);
|
const tempFiles = res.tempFiles || [];
|
||||||
if (!allowedTypes.includes(file.type)) {
|
const tempFilePaths = tempFiles.map(file => file.path);
|
||||||
return $api.msg('仅支持 txt md html word pdf ppt csv excel 格式类型');
|
const files = tempFiles;
|
||||||
}
|
|
||||||
// 继续上传
|
console.log('tempFilePaths:', tempFilePaths);
|
||||||
$api.uploadFile(tempFilePaths[0], true).then((resData) => {
|
console.log('tempFiles:', tempFiles);
|
||||||
resData = JSON.parse(resData);
|
if (files && files.length > 0) {
|
||||||
filesList.value.push({
|
console.log('第一个文件详情:', files[0]);
|
||||||
url: resData.msg,
|
console.log('第一个文件的所有属性:', Object.keys(files[0]));
|
||||||
type: file.type,
|
// 详细记录每个文件的所有属性
|
||||||
|
files.forEach((file, index) => {
|
||||||
|
console.log(`文件 ${index} 完整对象:`, JSON.stringify(file));
|
||||||
|
console.log(`文件 ${index} 关键属性:`, {
|
||||||
name: file.name,
|
name: file.name,
|
||||||
size: size,
|
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('选择文件失败,请重试');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} 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;
|
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
|
font-size: 24rpx
|
||||||
.msg-files:active
|
.msg-files:active
|
||||||
background: #e9e9e9
|
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
|
.guess
|
||||||
padding: 5rpx 0 10rpx 0
|
padding: 5rpx 0 10rpx 0
|
||||||
.guess-list
|
.guess-list
|
||||||
@@ -1422,12 +1991,13 @@ image-margin-top = 40rpx
|
|||||||
|
|
||||||
.area-uploadfiles
|
.area-uploadfiles
|
||||||
position: absolute
|
position: absolute
|
||||||
top: -180rpx
|
bottom: 100%
|
||||||
width: calc(100% - 30rpx)
|
width: calc(100% - 30rpx)
|
||||||
background: #FFFFFF
|
background: #FFFFFF
|
||||||
left: 0
|
left: 0
|
||||||
padding: 10rpx 0 10rpx 30rpx
|
padding: 10rpx 0 10rpx 30rpx
|
||||||
box-shadow: 0rpx -4rpx 10rpx 0rpx rgba(11,44,112,0.06);
|
box-shadow: 0rpx -4rpx 10rpx 0rpx rgba(11,44,112,0.06);
|
||||||
|
z-index: 1003
|
||||||
.uploadfiles-scroll
|
.uploadfiles-scroll
|
||||||
height: 100%
|
height: 100%
|
||||||
.uploadfiles-list
|
.uploadfiles-list
|
||||||
@@ -1453,11 +2023,17 @@ image-margin-top = 40rpx
|
|||||||
height: 100%
|
height: 100%
|
||||||
font-size: 24rpx
|
font-size: 24rpx
|
||||||
position: relative
|
position: relative
|
||||||
min-width: 460rpx;
|
min-width: 200rpx;
|
||||||
height: 160rpx;
|
max-width: 200rpx;
|
||||||
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
height: 200rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
border: 2rpx solid #E2E2E2;
|
border: 2rpx solid #E2E2E2;
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
&.file-border
|
||||||
|
border: 2rpx solid #4A90E2;
|
||||||
|
background: #F5F9FF;
|
||||||
.file-del
|
.file-del
|
||||||
position: absolute
|
position: absolute
|
||||||
right: 25rpx
|
right: 25rpx
|
||||||
@@ -1489,9 +2065,61 @@ image-margin-top = 40rpx
|
|||||||
white-space: nowrap
|
white-space: nowrap
|
||||||
color: #7B7B7B;
|
color: #7B7B7B;
|
||||||
max-width: 100%
|
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
|
.file-iconImg
|
||||||
height: 100%
|
height: 100%
|
||||||
width: 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
|
.filerow
|
||||||
display: flex
|
display: flex
|
||||||
align-items: center
|
align-items: center
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ const useChatGroupDBStore = defineStore("messageGroup", () => {
|
|||||||
dataId: customDataID
|
dataId: customDataID
|
||||||
};
|
};
|
||||||
if (fileUrls && fileUrls.length) {
|
if (fileUrls && fileUrls.length) {
|
||||||
params['fileUrl'] = fileUrls.map((item) => item.url);
|
params['fileUrl'] = fileUrls.map((item) => item.serverPath || item.url);
|
||||||
}
|
}
|
||||||
// ------>
|
// ------>
|
||||||
const MsgData = {
|
const MsgData = {
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ export function uploadFile(tempFilePaths, loading = false) {
|
|||||||
header["Authorization"] = encodeURIComponent(Authorization);
|
header["Authorization"] = encodeURIComponent(Authorization);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
uni.uploadFile({
|
uni.uploadFile({
|
||||||
url: config.baseUrl + '/app/file/upload',
|
url: config.baseUrl + '/app/file/uploadFile',
|
||||||
filePath: tempFilePaths,
|
filePath: tempFilePaths,
|
||||||
name: 'file',
|
name: 'file',
|
||||||
header,
|
header,
|
||||||
|
|||||||
Reference in New Issue
Block a user