提交代码
This commit is contained in:
1 parent
82ecb8621d
commit
fbb8dd6bd5
10 files changed
+4027
-211
No files matched your search
@@ -98,7 +98,7 @@ const getImageBlobUrl = async (imageUrl: string): Promise<string> => {
|
||||
url: imageUrl,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
timeout: 30000 // 30秒超时
|
||||
timeout: 300000 // 300秒超时
|
||||
});
|
||||
|
||||
// 由于axios响应拦截器对blob类型响应直接返回blob数据
|
||||
@@ -127,31 +127,31 @@ const getImageBlobUrl = async (imageUrl: string): Promise<string> => {
|
||||
}
|
||||
|
||||
// 检查blob内容类型
|
||||
const contentType = blob.type;
|
||||
// const contentType = blob.type;
|
||||
|
||||
// 如果Content-Type明确表明这不是图片数据且不是通用二进制格式,进行检查
|
||||
if (
|
||||
contentType &&
|
||||
!contentType.toLowerCase().startsWith('image/') &&
|
||||
!contentType.toLowerCase().includes('octet-stream') &&
|
||||
(contentType.toLowerCase().includes('text/') || contentType.toLowerCase().includes('html'))
|
||||
) {
|
||||
const text = await blob.text();
|
||||
const preview = text.substring(0, 300);
|
||||
throw new Error(`响应Content-Type不是图片格式: ${contentType}。响应内容预览: ${preview}`);
|
||||
}
|
||||
// if (
|
||||
// contentType &&
|
||||
// !contentType.toLowerCase().startsWith('image/') &&
|
||||
// !contentType.toLowerCase().includes('octet-stream') &&
|
||||
// (contentType.toLowerCase().includes('text/') || contentType.toLowerCase().includes('html'))
|
||||
// ) {
|
||||
// const text = await blob.text();
|
||||
// const preview = text.substring(0, 300);
|
||||
// throw new Error(`响应Content-Type不是图片格式: ${contentType}。响应内容预览: ${preview}`);
|
||||
// }
|
||||
|
||||
// 验证是否为有效图片
|
||||
if (!validateImageBlob(blob)) {
|
||||
throw new Error(`无效的图片格式: ${blob.type}, 大小: ${blob.size}`);
|
||||
}
|
||||
// if (!validateImageBlob(blob)) {
|
||||
// throw new Error(`无效的图片格式: ${blob.type}, 大小: ${blob.size}`);
|
||||
// }
|
||||
|
||||
// 创建blob URL
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// 缓存blob URL
|
||||
urlCache.value.set(imageUrl, blobUrl);
|
||||
blobUrls.value.push(blobUrl);
|
||||
// urlCache.value.set(imageUrl, blobUrl);
|
||||
// blobUrls.value.push(blobUrl);
|
||||
|
||||
return blobUrl;
|
||||
} catch (error) {
|
||||
@@ -164,40 +164,40 @@ const getUrlByOssId = async (ossId: string): Promise<string> => {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
const response = await listByIds(ossId);
|
||||
// RuoYi框架的响应格式:{code, msg, data},实际数据在data字段中
|
||||
// const response = await listByIds(ossId);
|
||||
// // RuoYi框架的响应格式:{code, msg, data},实际数据在data字段中
|
||||
|
||||
// 正确提取数据,考虑可能的响应格式
|
||||
const ossList: OssVO[] = (response as any)?.data || response;
|
||||
// // 正确提取数据,考虑可能的响应格式
|
||||
// const ossList: OssVO[] = (response as any)?.data || response;
|
||||
|
||||
if (!ossList || ossList.length === 0) {
|
||||
throw new Error(`OSS ID ${ossId} 在数据库中不存在或已被删除。可能原因:1) OSS ID不正确 2) 文件已被删除 3) 权限不足`);
|
||||
}
|
||||
// if (!ossList || ossList.length === 0) {
|
||||
// throw new Error(`OSS ID ${ossId} 在数据库中不存在或已被删除。可能原因:1) OSS ID不正确 2) 文件已被删除 3) 权限不足`);
|
||||
// }
|
||||
|
||||
const ossFile = ossList[0];
|
||||
// const ossFile = ossList[0];
|
||||
|
||||
// 额外验证ossFile对象的完整性
|
||||
if (!ossFile || typeof ossFile !== 'object') {
|
||||
throw new Error(`OSS数据格式异常:期望对象,实际获得 ${typeof ossFile}`);
|
||||
}
|
||||
// // 额外验证ossFile对象的完整性
|
||||
// if (!ossFile || typeof ossFile !== 'object') {
|
||||
// throw new Error(`OSS数据格式异常:期望对象,实际获得 ${typeof ossFile}`);
|
||||
// }
|
||||
|
||||
if (!ossFile.url) {
|
||||
throw new Error(`OSS文件缺少url属性。完整数据:${JSON.stringify(ossFile)}`);
|
||||
}
|
||||
const imageUrl = ossFile.url;
|
||||
// if (!ossFile.url) {
|
||||
// throw new Error(`OSS文件缺少url属性。完整数据:${JSON.stringify(ossFile)}`);
|
||||
// }
|
||||
// const imageUrl = ossFile.url;
|
||||
|
||||
// 检查URL是否有效
|
||||
if (!imageUrl || typeof imageUrl !== 'string') {
|
||||
throw new Error(`OSS接口返回的URL无效。URL: ${imageUrl}, 类型: ${typeof imageUrl}`);
|
||||
}
|
||||
// // 检查URL是否有效
|
||||
// if (!imageUrl || typeof imageUrl !== 'string') {
|
||||
// throw new Error(`OSS接口返回的URL无效。URL: ${imageUrl}, 类型: ${typeof imageUrl}`);
|
||||
// }
|
||||
|
||||
// 如果URL不是以/开头的下载路径,可能是直接的OSS URL
|
||||
if (!imageUrl.startsWith('/resource/oss/download/')) {
|
||||
// 对于直接的OSS URL,我们仍然通过认证请求获取
|
||||
}
|
||||
// // 如果URL不是以/开头的下载路径,可能是直接的OSS URL
|
||||
// if (!imageUrl.startsWith('/resource/oss/download/')) {
|
||||
// // 对于直接的OSS URL,我们仍然通过认证请求获取
|
||||
// }
|
||||
|
||||
// 获取认证的blob URL
|
||||
return await getImageBlobUrl(imageUrl);
|
||||
return await getImageBlobUrl('/resource/oss/download/' + ossId);
|
||||
} catch (error) {
|
||||
return '';
|
||||
} finally {
|
||||
|
||||
@@ -105,7 +105,7 @@ const loadingStates = ref<Map<string, boolean>>(new Map());
|
||||
const loadErrorStates = ref<Map<string, boolean>>(new Map());
|
||||
|
||||
// 使用环境变量
|
||||
const baseUrl = '/dev-api';
|
||||
const baseUrl = import.meta.env.VITE_APP_BASE_API;
|
||||
const uploadImgUrl = ref(baseUrl + '/resource/oss/upload'); // 上传的图片服务器地址
|
||||
const headers = ref(globalHeaders());
|
||||
|
||||
@@ -189,24 +189,24 @@ const getPreviewImageBlobUrl = async (imageUrl: string): Promise<string> => {
|
||||
}
|
||||
|
||||
// 检查blob内容类型
|
||||
const contentType = blob.type;
|
||||
// const contentType = blob.type;
|
||||
|
||||
// 如果Content-Type明确表明这不是图片数据且不是通用二进制格式,进行检查
|
||||
if (
|
||||
contentType &&
|
||||
!contentType.toLowerCase().startsWith('image/') &&
|
||||
!contentType.toLowerCase().includes('octet-stream') &&
|
||||
(contentType.toLowerCase().includes('text/') || contentType.toLowerCase().includes('html'))
|
||||
) {
|
||||
const text = await blob.text();
|
||||
const preview = text.substring(0, 300);
|
||||
throw new Error(`响应Content-Type不是图片格式: ${contentType}。响应内容预览: ${preview}`);
|
||||
}
|
||||
// if (
|
||||
// contentType &&
|
||||
// !contentType.toLowerCase().startsWith('image/') &&
|
||||
// !contentType.toLowerCase().includes('octet-stream') &&
|
||||
// (contentType.toLowerCase().includes('text/') || contentType.toLowerCase().includes('html'))
|
||||
// ) {
|
||||
// const text = await blob.text();
|
||||
// const preview = text.substring(0, 300);
|
||||
// throw new Error(`响应Content-Type不是图片格式: ${contentType}。响应内容预览: ${preview}`);
|
||||
// }
|
||||
|
||||
// 验证是否为有效图片
|
||||
if (!validateImageBlob(blob)) {
|
||||
throw new Error(`无效的图片格式: ${blob.type}, 大小: ${blob.size}`);
|
||||
}
|
||||
// if (!validateImageBlob(blob)) {
|
||||
// throw new Error(`无效的图片格式: ${blob.type}, 大小: ${blob.size}`);
|
||||
// }
|
||||
|
||||
// 创建blob URL
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
@@ -225,29 +225,29 @@ const getPreviewImageBlobUrl = async (imageUrl: string): Promise<string> => {
|
||||
// 通过ossid获取预览URL
|
||||
const getPreviewUrlByOssId = async (ossId: string): Promise<string> => {
|
||||
try {
|
||||
const response = await listByIds(ossId);
|
||||
// RuoYi框架的响应格式:{code, msg, data},实际数据在data字段中
|
||||
const ossList: OssVO[] = (response as any)?.data || response;
|
||||
// const response = await listByIds(ossId);
|
||||
// // RuoYi框架的响应格式:{code, msg, data},实际数据在data字段中
|
||||
// const ossList: OssVO[] = (response as any)?.data || response;
|
||||
|
||||
if (!ossList || ossList.length === 0) {
|
||||
throw new Error(`OSS ID ${ossId} 在数据库中不存在或已被删除`);
|
||||
}
|
||||
// if (!ossList || ossList.length === 0) {
|
||||
// throw new Error(`OSS ID ${ossId} 在数据库中不存在或已被删除`);
|
||||
// }
|
||||
|
||||
const ossFile = ossList[0];
|
||||
// const ossFile = ossList[0];
|
||||
|
||||
if (!ossFile || !ossFile.url) {
|
||||
throw new Error(`OSS文件缺少url属性`);
|
||||
}
|
||||
// if (!ossFile || !ossFile.url) {
|
||||
// throw new Error(`OSS文件缺少url属性`);
|
||||
// }
|
||||
|
||||
const imageUrl = ossFile.url;
|
||||
// const imageUrl = ossFile.url;
|
||||
|
||||
// 检查URL是否有效
|
||||
if (!imageUrl || typeof imageUrl !== 'string') {
|
||||
throw new Error(`OSS接口返回的URL无效`);
|
||||
}
|
||||
// // 检查URL是否有效
|
||||
// if (!imageUrl || typeof imageUrl !== 'string') {
|
||||
// throw new Error(`OSS接口返回的URL无效`);
|
||||
// }
|
||||
|
||||
// 获取认证的blob URL
|
||||
return await getPreviewImageBlobUrl(imageUrl);
|
||||
return await getPreviewImageBlobUrl('/resource/oss/download/' + ossId);
|
||||
} catch (error) {
|
||||
console.error('通过ossId获取预览图片失败:', error);
|
||||
throw error;
|
||||
@@ -265,11 +265,17 @@ const loadFileImageAsync = async (item: any, index: number): Promise<void> => {
|
||||
try {
|
||||
let blobUrl = '';
|
||||
|
||||
if (typeof item === 'string') {
|
||||
if (item.ossId) {
|
||||
// 优先通过OSS ID获取预览URL
|
||||
console.log(`loadFileImageAsync: 通过OSS ID获取预览URL for ${item.ossId}`);
|
||||
blobUrl = await getPreviewUrlByOssId(item.ossId);
|
||||
} else if (typeof item === 'string') {
|
||||
// 直接URL情况
|
||||
console.log(`loadFileImageAsync: 直接URL情况 for ${item}`);
|
||||
blobUrl = await getPreviewImageBlobUrl(item);
|
||||
} else if (item.url) {
|
||||
// OSS对象情况
|
||||
// OSS对象情况(降级处理)
|
||||
console.log(`loadFileImageAsync: OSS对象情况 for ${item.url}`);
|
||||
blobUrl = await getPreviewImageBlobUrl(item.url);
|
||||
}
|
||||
|
||||
@@ -280,6 +286,8 @@ const loadFileImageAsync = async (item: any, index: number): Promise<void> => {
|
||||
if (fileIndex !== -1) {
|
||||
fileList.value[fileIndex].url = blobUrl;
|
||||
fileList.value[fileIndex].isLoaded = true;
|
||||
fileList.value[fileIndex].isPlaceholder = false;
|
||||
console.log(`loadFileImageAsync: 成功更新文件 ${item.ossId || item.url}`);
|
||||
}
|
||||
} else {
|
||||
throw new Error('获取图片blob URL失败');
|
||||
@@ -287,6 +295,24 @@ const loadFileImageAsync = async (item: any, index: number): Promise<void> => {
|
||||
} catch (error) {
|
||||
console.error('异步加载图片失败:', error);
|
||||
loadErrorStates.value.set(fileKey, true);
|
||||
|
||||
// 降级处理:尝试通过OSS ID重新获取
|
||||
if (item.ossId) {
|
||||
try {
|
||||
console.log(`loadFileImageAsync: 降级处理,重新通过OSS ID获取 ${item.ossId}`);
|
||||
const fallbackBlobUrl = await getPreviewUrlByOssId(item.ossId);
|
||||
|
||||
const fileIndex = fileList.value.findIndex((f) => f.ossId === item.ossId);
|
||||
if (fileIndex !== -1 && fallbackBlobUrl) {
|
||||
fileList.value[fileIndex].url = fallbackBlobUrl;
|
||||
fileList.value[fileIndex].isLoaded = true;
|
||||
fileList.value[fileIndex].isPlaceholder = false;
|
||||
console.log(`loadFileImageAsync: 降级处理成功 for ${item.ossId}`);
|
||||
}
|
||||
} catch (fallbackError) {
|
||||
console.error('loadFileImageAsync: 降级处理也失败:', fallbackError);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loadingStates.value.set(fileKey, false);
|
||||
}
|
||||
@@ -448,8 +474,16 @@ const handleExceed = () => {
|
||||
// 上传成功回调
|
||||
const handleUploadSuccess = (res: any, file: any) => {
|
||||
console.log('handleUploadSuccess:res', res);
|
||||
console.log('handleUploadSuccess:file', file);
|
||||
|
||||
if (res.code === 200) {
|
||||
uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
|
||||
const uploadItem = {
|
||||
name: res.data.fileName,
|
||||
url: res.data.url,
|
||||
ossId: res.data.ossId
|
||||
};
|
||||
console.log('添加上传项到uploadList:', uploadItem);
|
||||
uploadList.value.push(uploadItem);
|
||||
uploadedSuccessfully();
|
||||
} else {
|
||||
number.value--;
|
||||
@@ -489,9 +523,126 @@ const handleDelete = (file: any): boolean => {
|
||||
};
|
||||
|
||||
// 上传结束处理
|
||||
const uploadedSuccessfully = () => {
|
||||
const uploadedSuccessfully = async () => {
|
||||
console.log('uploadedSuccessfully called, number:', number.value, 'uploadList length:', uploadList.value.length);
|
||||
|
||||
if (number.value > 0 && uploadList.value.length === number.value) {
|
||||
fileList.value = fileList.value.filter((f) => f.url !== undefined).concat(uploadList.value);
|
||||
console.log('开始处理新上传的文件:', uploadList.value);
|
||||
|
||||
// 对新上传的文件进行异步加载处理
|
||||
const newUploadedFiles = uploadList.value.map((item) => ({
|
||||
...item,
|
||||
isLoaded: false,
|
||||
isPlaceholder: false
|
||||
}));
|
||||
|
||||
console.log('处理后的新上传文件:', newUploadedFiles);
|
||||
|
||||
// 先显示新上传的文件(使用占位符)
|
||||
const placeholderItems = newUploadedFiles.map((item, index) => ({
|
||||
name: item.name,
|
||||
url: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIiBmaWxsPSIjZjVmN2ZhIi8+CjxjaXJjbGUgY3g9IjUwIiBjeT0iNTAiIHI9IjE1IiBzdHJva2U9IiM5MDkzOTkiIHN0cm9rZS13aWR0aD0iMiIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtZGFzaGFycmF5PSI2LCAzIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj4KCTxhbmltYXRlVHJhbnNmb3JtIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgdHlwZT0icm90YXRlIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBkdXI9IjFzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIvPgo8L2NpcmNsZT4KPC9zdmc+',
|
||||
originalUrl: item.url,
|
||||
ossId: item.ossId,
|
||||
isLoaded: false,
|
||||
isPlaceholder: true
|
||||
}));
|
||||
|
||||
console.log('占位符项目:', placeholderItems);
|
||||
|
||||
// 合并现有文件和新上传的文件
|
||||
const existingFiles = fileList.value.filter((f) => f.url !== undefined);
|
||||
console.log('现有文件:', existingFiles);
|
||||
|
||||
fileList.value = existingFiles.concat(placeholderItems);
|
||||
console.log('合并后的文件列表:', fileList.value);
|
||||
|
||||
// 异步加载新上传的图片
|
||||
const loadPromises = newUploadedFiles.map((item, index) => {
|
||||
const fileKey = item.ossId || item.url || `file_${index}`;
|
||||
|
||||
console.log(`开始加载文件 ${index}:`, item);
|
||||
|
||||
// 设置加载状态
|
||||
loadingStates.value.set(fileKey, true);
|
||||
loadErrorStates.value.set(fileKey, false);
|
||||
|
||||
return (async () => {
|
||||
try {
|
||||
let blobUrl = '';
|
||||
|
||||
if (item.ossId) {
|
||||
console.log(`通过OSS ID获取预览URL for ${item.ossId}`);
|
||||
// 通过OSS ID获取预览URL
|
||||
blobUrl = await getPreviewUrlByOssId(item.ossId);
|
||||
console.log(`获取到的blob URL for ${item.ossId}:`, blobUrl);
|
||||
} else if (item.url) {
|
||||
console.log(`通过URL获取blob URL for ${item.ossId}:`, item.url);
|
||||
// 直接URL情况(降级处理)
|
||||
blobUrl = await getPreviewImageBlobUrl(item.url);
|
||||
console.log(`获取到的blob URL for ${item.ossId}:`, blobUrl);
|
||||
}
|
||||
|
||||
if (blobUrl) {
|
||||
// 更新文件列表中的对应项
|
||||
const fileIndex = fileList.value.findIndex((f) => f.ossId === item.ossId);
|
||||
console.log(`更新文件列表索引 ${fileIndex} for ${item.ossId}`);
|
||||
|
||||
if (fileIndex !== -1) {
|
||||
fileList.value[fileIndex].url = blobUrl;
|
||||
fileList.value[fileIndex].isLoaded = true;
|
||||
fileList.value[fileIndex].isPlaceholder = false;
|
||||
console.log(`成功更新文件 ${item.ossId}`);
|
||||
}
|
||||
} else {
|
||||
throw new Error('获取图片blob URL失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('异步加载新上传图片失败:', error);
|
||||
loadErrorStates.value.set(fileKey, true);
|
||||
|
||||
// 降级处理:通过OSS ID重新获取
|
||||
try {
|
||||
console.log(`降级处理:重新通过OSS ID获取 ${item.ossId}`);
|
||||
const fallbackBlobUrl = await getPreviewUrlByOssId(item.ossId);
|
||||
|
||||
const fileIndex = fileList.value.findIndex((f) => f.ossId === item.ossId);
|
||||
if (fileIndex !== -1 && fallbackBlobUrl) {
|
||||
fileList.value[fileIndex].url = fallbackBlobUrl;
|
||||
fileList.value[fileIndex].isLoaded = true;
|
||||
fileList.value[fileIndex].isPlaceholder = false;
|
||||
console.log(`降级处理成功,通过OSS ID重新获取 for ${item.ossId}`);
|
||||
} else {
|
||||
// 最后的降级:使用原始URL
|
||||
const fileIndex = fileList.value.findIndex((f) => f.ossId === item.ossId);
|
||||
if (fileIndex !== -1) {
|
||||
fileList.value[fileIndex].url = item.url;
|
||||
fileList.value[fileIndex].isLoaded = true;
|
||||
fileList.value[fileIndex].isPlaceholder = false;
|
||||
console.log(`最终降级处理,使用原始URL for ${item.ossId}`);
|
||||
}
|
||||
}
|
||||
} catch (fallbackError) {
|
||||
console.error('降级处理也失败:', fallbackError);
|
||||
// 最后的降级:使用原始URL
|
||||
const fileIndex = fileList.value.findIndex((f) => f.ossId === item.ossId);
|
||||
if (fileIndex !== -1) {
|
||||
fileList.value[fileIndex].url = item.url;
|
||||
fileList.value[fileIndex].isLoaded = true;
|
||||
fileList.value[fileIndex].isPlaceholder = false;
|
||||
console.log(`最终降级处理,使用原始URL for ${item.ossId}`);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loadingStates.value.set(fileKey, false);
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
// 等待所有图片加载完成
|
||||
await Promise.all(loadPromises);
|
||||
console.log('所有图片加载完成,最终文件列表:', fileList.value);
|
||||
|
||||
uploadList.value = [];
|
||||
number.value = 0;
|
||||
// 设置内部更新标志
|
||||
@@ -592,8 +743,39 @@ const listToString = (list: any[], separator?: string) => {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
// 占位符图片样式优化
|
||||
&.placeholder-item {
|
||||
.el-upload-list__item-thumbnail {
|
||||
background-color: #f5f7fa;
|
||||
border: 1px dashed #d9d9d9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .el-upload-list__item-thumbnail img {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 加载完成后的样式
|
||||
&.loaded-item {
|
||||
.el-upload-list__item-thumbnail {
|
||||
img {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载动画
|
||||
@keyframes loading-rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
@@ -602,4 +784,19 @@ const listToString = (list: any[], separator?: string) => {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
// 占位符加载动画
|
||||
@keyframes placeholder-pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder-loading {
|
||||
animation: placeholder-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
</style>
|
||||
@@ -302,7 +302,7 @@
|
||||
<el-form-item label="报告厅图片">
|
||||
<div class="view-hall-images">
|
||||
<div class="image-gallery" v-if="viewHallImages.length > 0">
|
||||
<div v-for="(image, index) in viewHallImages" :key="index" class="image-item" @click="previewImage(image, index)">
|
||||
<div v-for="(image, index) in viewHallImages" :key="index" class="image-item">
|
||||
<!-- <img :src="image" class="gallery-image" @error="handleImageError" />
|
||||
<div class="image-overlay">
|
||||
<el-icon class="preview-icon"><ZoomIn /></el-icon>
|
||||
|
||||
@@ -2434,7 +2434,7 @@ const handleDownloadAttachment = async (file: any) => {
|
||||
url: '/resource/oss/download/' + downloadUrl,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
timeout: 30000 // 30秒超时
|
||||
timeout: 300000 // 300秒超时
|
||||
});
|
||||
// 由于axios响应拦截器对blob类型响应直接返回blob数据
|
||||
// 这里response直接就是blob对象,而不是包含data属性的响应对象
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -236,7 +236,11 @@
|
||||
:before-upload="beforePhotoUpload"
|
||||
:http-request="handlePhotoUpload"
|
||||
>
|
||||
<img v-if="form.photoUrl" :src="form.photoUrl" class="photo" />
|
||||
<div v-if="photoLoading" class="photo-loading">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<img v-else-if="asyncPhotoUrl" :src="asyncPhotoUrl" class="photo" />
|
||||
<div v-else class="photo-placeholder">暂无照片</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
@@ -912,7 +916,11 @@
|
||||
<el-col :span="8">
|
||||
<el-form-item label="学生照片">
|
||||
<div class="photo-display">
|
||||
<img v-if="detailForm.photoUrl" :src="detailForm.photoUrl" class="photo" />
|
||||
<div v-if="detailPhotoLoading" class="photo-loading">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<img v-else-if="asyncDetailPhotoUrl" :src="asyncDetailPhotoUrl" class="photo" />
|
||||
<div v-else class="photo-placeholder">暂无照片</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
@@ -1474,7 +1482,7 @@ import { to } from 'await-to-js';
|
||||
import { globalHeaders } from '@/utils/request';
|
||||
// import type { UploadInstance } from 'element-plus';
|
||||
// import type { FormInstance, TreeInstance } from 'element-plus';
|
||||
import { Plus, ArrowDown, InfoFilled } from '@element-plus/icons-vue';
|
||||
import { Plus, ArrowDown, InfoFilled, Loading } from '@element-plus/icons-vue';
|
||||
import service from '@/utils/request';
|
||||
interface ComponentInternalInstance {
|
||||
proxy?: any;
|
||||
@@ -1760,6 +1768,12 @@ const form = reactive<StudentMsgForm>({
|
||||
...initFormData
|
||||
});
|
||||
|
||||
// 异步加载的图片URL
|
||||
const asyncPhotoUrl = ref<string>('');
|
||||
const asyncDetailPhotoUrl = ref<string>('');
|
||||
const photoLoading = ref<boolean>(false);
|
||||
const detailPhotoLoading = ref<boolean>(false);
|
||||
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: '学生姓名不能为空', trigger: 'blur' }],
|
||||
studentNo: [{ required: true, message: '学号不能为空', trigger: 'blur' }],
|
||||
@@ -2513,6 +2527,88 @@ const closeDialog = () => {
|
||||
reset();
|
||||
};
|
||||
|
||||
// 通过认证请求获取图片数据并转换为blob URL
|
||||
const getImageBlobUrl = async (imageUrl: string): Promise<string> => {
|
||||
try {
|
||||
// 通过axios请求图片数据,这样会自动带上token和clientId
|
||||
const response = await service({
|
||||
url: '/resource/oss/download/' + imageUrl,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
timeout: 300000 // 300秒超时
|
||||
});
|
||||
|
||||
// 由于axios响应拦截器对blob类型响应直接返回blob数据
|
||||
// 这里response直接就是blob对象,而不是包含data属性的响应对象
|
||||
const blob = response;
|
||||
|
||||
// 验证响应数据
|
||||
if (!blob) {
|
||||
throw new Error('响应数据为空');
|
||||
}
|
||||
|
||||
if (!(blob instanceof Blob)) {
|
||||
// 如果不是Blob,尝试获取实际内容用于调试
|
||||
let actualContent = 'Unknown';
|
||||
try {
|
||||
if (typeof blob === 'string') {
|
||||
actualContent = (blob as string).substring(0, 200);
|
||||
} else if (blob && typeof blob.toString === 'function') {
|
||||
actualContent = blob.toString().substring(0, 200);
|
||||
}
|
||||
} catch (e) {
|
||||
actualContent = 'Cannot convert to string';
|
||||
}
|
||||
|
||||
throw new Error(`响应数据不是有效的Blob对象。实际类型: ${typeof blob}, 内容: ${actualContent}`);
|
||||
}
|
||||
|
||||
// 创建blob URL
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
return blobUrl;
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// 监听photoPath变化,异步加载图片
|
||||
watchEffect(async () => {
|
||||
if (form.photoPath) {
|
||||
photoLoading.value = true;
|
||||
try {
|
||||
const blobUrl = await getImageBlobUrl(form.photoPath);
|
||||
asyncPhotoUrl.value = blobUrl;
|
||||
} catch (error) {
|
||||
console.error('加载图片失败:', error);
|
||||
asyncPhotoUrl.value = '';
|
||||
} finally {
|
||||
photoLoading.value = false;
|
||||
}
|
||||
} else {
|
||||
asyncPhotoUrl.value = '';
|
||||
photoLoading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 监听detailForm.photoPath变化,异步加载详情图片
|
||||
watchEffect(async () => {
|
||||
if (detailForm.photoPath) {
|
||||
detailPhotoLoading.value = true;
|
||||
try {
|
||||
const blobUrl = await getImageBlobUrl(detailForm.photoPath);
|
||||
asyncDetailPhotoUrl.value = blobUrl;
|
||||
} catch (error) {
|
||||
console.error('加载详情图片失败:', error);
|
||||
asyncDetailPhotoUrl.value = '';
|
||||
} finally {
|
||||
detailPhotoLoading.value = false;
|
||||
}
|
||||
} else {
|
||||
asyncDetailPhotoUrl.value = '';
|
||||
detailPhotoLoading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getDeptTree(); // 初始化部门数据(会在完成后自动调用getList)
|
||||
getRegionTree(); // 初始化行政区划数据
|
||||
@@ -2665,6 +2761,32 @@ onMounted(() => {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
|
||||
/* 照片加载状态样式 */
|
||||
.photo-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border: 2px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
background-color: #fafafa;
|
||||
color: #8c8d91;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.photo-loading .el-icon {
|
||||
font-size: 24px;
|
||||
margin-bottom: 8px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.photo-loading span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 照片上传样式 */
|
||||
.photo-uploader .photo {
|
||||
width: 130px;
|
||||
|
||||
Reference in new issue
Block a user