From 099b3ee4062d10c8af5021a23e8667a1386e9b12 Mon Sep 17 00:00:00 2001 From: chabai <14799297+dhasjklhdfjkasfbhfasfj@user.noreply.gitee.com> Date: Fri, 8 Aug 2025 16:40:05 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=95=86=E5=8A=A1?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=8F=AF=E4=BB=A5=E9=80=89=E6=8B=A9=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PREVIEW_FEATURE_GUIDE.md | 88 ++ src/types/auto-imports.d.ts | 2 +- src/views/bussiness-data/bussiness.vue | 838 ++++++------------ .../data-processing/data-storage/index.vue | 23 +- 4 files changed, 352 insertions(+), 599 deletions(-) create mode 100644 PREVIEW_FEATURE_GUIDE.md diff --git a/PREVIEW_FEATURE_GUIDE.md b/PREVIEW_FEATURE_GUIDE.md new file mode 100644 index 0000000..6f57aad --- /dev/null +++ b/PREVIEW_FEATURE_GUIDE.md @@ -0,0 +1,88 @@ +# 商务数据库文件预览功能说明 + +## 功能概述 + +商务数据库信息模块的文件预览功能已经实现,支持多种文件类型的在线预览,为用户提供便捷的文件查看体验。 + +## 支持的文件类型 + +### 1. 图片文件 +- **支持格式**: JPG, JPEG, PNG, GIF, BMP, WebP +- **预览方式**: 在模态窗口中直接显示图片 +- **特性**: + - 自适应大小,最大高度为70vh + - 圆角和阴影效果提升视觉体验 + - 显示文件名信息 + +### 2. PDF文件 +- **支持格式**: PDF +- **预览方式**: 在新窗口中打开PDF文件 +- **特性**: 利用浏览器原生PDF查看器 + +### 3. 文本文件 +- **支持格式**: TXT, MD, JSON, XML, CSV +- **预览方式**: 在模态窗口中显示文本内容 +- **特性**: + - 等宽字体显示,保持格式 + - 400px高度的滚动区域 + - 保留原文本格式和换行 + +### 4. Office文档 +- **支持格式**: DOC, DOCX, XLS, XLSX, PPT, PPTX +- **预览方式**: 提示用户下载查看 +- **说明**: 由于Office文档需要特殊渲染,建议下载后使用相应软件打开 + +### 5. 其他文件类型 +- **处理方式**: 询问用户是否下载查看 +- **说明**: 对于不支持在线预览的文件类型,系统会友好提示用户 + +## 功能特性 + +### 用户体验优化 +1. **加载提示**: 预览过程中显示"正在加载预览..."提示 +2. **错误处理**: 针对不同错误状态提供详细的错误信息 +3. **资源管理**: 自动清理预览使用的内存资源 + +### 错误处理机制 +- **404错误**: "文件不存在或已被删除" +- **403错误**: "没有权限访问该文件" +- **500错误**: "服务器内部错误,请稍后重试" +- **网络错误**: "预览文件失败,请检查网络连接" + +### 安全性 +- 使用Blob URL进行文件预览,确保安全性 +- 10秒后自动释放URL资源,防止内存泄漏 + +## 使用方法 + +1. 在文件列表中找到要预览的文件 +2. 点击文件行中的"眼睛"图标按钮 +3. 系统会根据文件类型自动选择合适的预览方式 +4. 对于不支持的文件类型,系统会提供下载选项 + +## 技术实现 + +### API接口 +- **接口地址**: `/businessData/file/preview` +- **请求方式**: GET +- **参数**: `fileId` (必需) +- **响应类型**: Blob + +### 核心函数 +- `handlePreview(file)`: 主预览函数 +- `showImagePreview(url, fileName)`: 图片预览组件 +- `showTextPreview(blob, fileName)`: 文本预览组件 + +## 注意事项 + +1. **文件大小限制**: 大文件可能需要较长加载时间 +2. **浏览器兼容性**: 确保浏览器支持Blob和URL.createObjectURL +3. **网络要求**: 稳定的网络连接确保预览效果 +4. **权限控制**: 需要有相应的文件访问权限 + +## 后续优化建议 + +1. 添加文件预览缓存机制 +2. 支持更多文件格式的在线预览 +3. 添加图片预览的缩放和旋转功能 +4. 支持文档的分页预览功能 diff --git a/src/types/auto-imports.d.ts b/src/types/auto-imports.d.ts index eab6be6..369aad4 100644 --- a/src/types/auto-imports.d.ts +++ b/src/types/auto-imports.d.ts @@ -70,6 +70,6 @@ declare global { // for type re-export declare global { // @ts-ignore - export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' + export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' import('vue') } diff --git a/src/views/bussiness-data/bussiness.vue b/src/views/bussiness-data/bussiness.vue index b8cdb6b..905c6aa 100644 --- a/src/views/bussiness-data/bussiness.vue +++ b/src/views/bussiness-data/bussiness.vue @@ -413,6 +413,7 @@ :show-file-list="false" @change="handleFileChange" :accept="allowedFileTypes" + multiple > @@ -1079,19 +1080,9 @@ const handleFileChange = (info) => { console.log('文件列表:', fileList); console.log('文件列表长度:', fileList.length); - // 每次选择文件时,强制清空所有状态 - fileListTemp.value = []; - console.log('已清空之前的文件列表'); - - // 强制重置上传组件状态 - if (uploadRef.value) { - try { - uploadRef.value.reset(); - console.log('已强制重置上传组件'); - } catch (error) { - console.log('重置上传组件时出错:', error); - } - } + // 获取当前已存在的文件UID列表,用于去重 + const existingUids = fileListTemp.value.map(f => f.uid); + console.log('已存在的文件UID:', existingUids); // 处理新选择的文件 - 只处理当前选择的文件 fileList.forEach((file, index) => { @@ -1101,6 +1092,12 @@ const handleFileChange = (info) => { console.log('文件UID:', file.uid); console.log('文件对象结构:', Object.keys(file)); + // 检查文件是否已存在(去重) + if (existingUids.includes(file.uid)) { + console.log('⚠️ 文件已存在,跳过:', file.name); + return; + } + // 确保文件对象有正确的属性 const fileObj = { uid: file.uid, @@ -1119,16 +1116,14 @@ const handleFileChange = (info) => { console.log('文件验证结果:', isValid); if (isValid) { - // 确保只添加当前选择的文件 - fileListTemp.value = [fileObj]; // 只保留当前文件 - console.log('✅ 成功设置文件到列表:', fileObj.name); + // 添加文件到列表(支持多文件) + fileListTemp.value.push(fileObj); + console.log('✅ 成功添加文件到列表:', fileObj.name); } else { console.log('❌ 文件验证失败:', fileObj.name, '错误:', fileObj.error); } }); - // 文件列表已在上方清空并重新填充,无需额外清理 - console.log('=== 当前文件列表状态 ==='); console.log('fileListTemp长度:', fileListTemp.value.length); console.log('fileListTemp内容:', fileListTemp.value); @@ -1415,267 +1410,26 @@ const handlePreview = async (file) => { // 显示图片预览 const showImagePreview = (url, fileName) => { - const imageScale = ref(1); - const imageRotation = ref(0); - - const container = h('div', { - style: { - width: '100%', - height: '100%', - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - padding: '24px', - boxSizing: 'border-box', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - borderRadius: '16px', - position: 'relative', - overflow: 'hidden' - } - }, [ - // 背景装饰 - h('div', { + Modal.info({ + title: '图片预览', + content: h('div', { style: { - position: 'absolute', - top: '-50%', - left: '-50%', - width: '200%', - height: '200%', - background: 'radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%)', - animation: 'float 6s ease-in-out infinite' - } - }), - - // 顶部工具栏 - h('div', { - style: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - gap: '16px', - marginBottom: '24px', - padding: '16px 24px', - backgroundColor: 'rgba(255, 255, 255, 0.95)', - borderRadius: '16px', - boxShadow: '0 8px 32px rgba(0,0,0,0.15)', - backdropFilter: 'blur(10px)', - border: '1px solid rgba(255,255,255,0.2)', - position: 'relative', - zIndex: 10 - } - }, [ - h('button', { - onClick: () => imageScale.value = Math.max(0.3, imageScale.value - 0.2), - style: { - padding: '10px 16px', - border: 'none', - borderRadius: '12px', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - color: 'white', - cursor: 'pointer', - fontSize: '14px', - fontWeight: '500', - display: 'flex', - alignItems: 'center', - gap: '8px', - transition: 'all 0.3s ease', - boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)' - }, - onMouseenter: (e) => { - e.target.style.transform = 'translateY(-2px)'; - e.target.style.boxShadow = '0 6px 16px rgba(102, 126, 234, 0.4)'; - }, - onMouseleave: (e) => { - e.target.style.transform = 'translateY(0)'; - e.target.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.3)'; - } - }, [ - h('span', { style: { fontSize: '18px' } }, '🔍'), - '缩小' - ]), - h('button', { - onClick: () => imageScale.value = Math.min(4, imageScale.value + 0.2), - style: { - padding: '10px 16px', - border: 'none', - borderRadius: '12px', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - color: 'white', - cursor: 'pointer', - fontSize: '14px', - fontWeight: '500', - display: 'flex', - alignItems: 'center', - gap: '8px', - transition: 'all 0.3s ease', - boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)' - }, - onMouseenter: (e) => { - e.target.style.transform = 'translateY(-2px)'; - e.target.style.boxShadow = '0 6px 16px rgba(102, 126, 234, 0.4)'; - }, - onMouseleave: (e) => { - e.target.style.transform = 'translateY(0)'; - e.target.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.3)'; - } - }, [ - h('span', { style: { fontSize: '18px' } }, '🔍'), - '放大' - ]), - h('button', { - onClick: () => imageRotation.value += 90, - style: { - padding: '10px 16px', - border: 'none', - borderRadius: '12px', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - color: 'white', - cursor: 'pointer', - fontSize: '14px', - fontWeight: '500', - display: 'flex', - alignItems: 'center', - gap: '8px', - transition: 'all 0.3s ease', - boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)' - }, - onMouseenter: (e) => { - e.target.style.transform = 'translateY(-2px)'; - e.target.style.boxShadow = '0 6px 16px rgba(102, 126, 234, 0.4)'; - }, - onMouseleave: (e) => { - e.target.style.transform = 'translateY(0)'; - e.target.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.3)'; - } - }, [ - h('span', { style: { fontSize: '18px' } }, '🔄'), - '旋转' - ]), - h('button', { - onClick: () => { - imageScale.value = 1; - imageRotation.value = 0; - }, - style: { - padding: '10px 16px', - border: 'none', - borderRadius: '12px', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - color: 'white', - cursor: 'pointer', - fontSize: '14px', - fontWeight: '500', - display: 'flex', - alignItems: 'center', - gap: '8px', - transition: 'all 0.3s ease', - boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)' - }, - onMouseenter: (e) => { - e.target.style.transform = 'translateY(-2px)'; - e.target.style.boxShadow = '0 6px 16px rgba(102, 126, 234, 0.4)'; - }, - onMouseleave: (e) => { - e.target.style.transform = 'translateY(0)'; - e.target.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.3)'; - } - }, [ - h('span', { style: { fontSize: '18px' } }, '🏠'), - '重置' - ]) - ]), - - // 图片容器 - h('div', { - style: { - flex: 1, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - overflow: 'auto', - backgroundColor: 'rgba(255, 255, 255, 0.95)', - borderRadius: '20px', - padding: '32px', - boxShadow: '0 16px 48px rgba(0,0,0,0.2)', - minHeight: '500px', - backdropFilter: 'blur(10px)', - border: '1px solid rgba(255,255,255,0.3)', - position: 'relative', - zIndex: 5 + textAlign: 'center', + padding: '20px' } }, [ h('img', { src: url, style: { maxWidth: '100%', - maxHeight: '100%', - objectFit: 'contain', - borderRadius: '16px', - transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)', - transform: `scale(${imageScale.value}) rotate(${imageRotation.value}deg)`, - boxShadow: '0 8px 24px rgba(0,0,0,0.15)', - border: '2px solid rgba(255,255,255,0.8)' + maxHeight: '70vh', + objectFit: 'contain' } }) ]), - - // 文件名 - h('div', { - style: { - marginTop: '20px', - fontSize: '16px', - color: 'white', - textAlign: 'center', - wordBreak: 'break-all', - padding: '12px 24px', - backgroundColor: 'rgba(255, 255, 255, 0.15)', - borderRadius: '12px', - boxShadow: '0 4px 16px rgba(0,0,0,0.1)', - backdropFilter: 'blur(10px)', - border: '1px solid rgba(255,255,255,0.2)', - fontWeight: '500', - position: 'relative', - zIndex: 5 - } - }, fileName) - ]); - - Modal.info({ - title: h('div', { - style: { - display: 'flex', - alignItems: 'center', - gap: '12px', - fontSize: '20px', - fontWeight: '600', - color: '#2c3e50', - textAlign: 'center', - width: '100%', - justifyContent: 'center' - } - }, [ - h('span', { style: { fontSize: '24px' } }, '🖼️'), - '图片预览' - ]), - content: container, - width: '95%', - style: { - maxWidth: '1600px', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - borderRadius: '20px', - overflow: 'hidden', - boxShadow: '0 32px 96px rgba(0,0,0,0.3)', - border: '3px solid rgba(255,255,255,0.2)' - }, - mask: true, - maskClosable: true, + width: '80%', footer: null, - closable: true, - okText: null, - cancelText: null + closable: true }); }; @@ -1683,208 +1437,37 @@ const showImagePreview = (url, fileName) => { const showTextPreview = async (blob, fileName) => { try { const text = await blob.text(); - const fontSize = ref(16); - const container = h('div', { - style: { - width: '100%', - height: '100%', - display: 'flex', - flexDirection: 'column', - padding: '24px', - boxSizing: 'border-box', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - borderRadius: '16px', - position: 'relative', - overflow: 'hidden' - } - }, [ - // 背景装饰 - h('div', { - style: { - position: 'absolute', - top: '-50%', - left: '-50%', - width: '200%', - height: '200%', - background: 'radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%)', - animation: 'float 6s ease-in-out infinite' - } - }), - - // 顶部工具栏 - h('div', { - style: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - gap: '16px', - marginBottom: '24px', - padding: '16px 24px', - backgroundColor: 'rgba(255, 255, 255, 0.95)', - borderRadius: '16px', - boxShadow: '0 8px 32px rgba(0,0,0,0.15)', - backdropFilter: 'blur(10px)', - border: '1px solid rgba(255,255,255,0.2)', - position: 'relative', - zIndex: 10 - } - }, [ - h('button', { - onClick: () => fontSize.value = Math.max(12, fontSize.value - 2), - style: { - padding: '10px 16px', - border: 'none', - borderRadius: '12px', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - color: 'white', - cursor: 'pointer', - fontSize: '14px', - fontWeight: '500', - display: 'flex', - alignItems: 'center', - gap: '8px', - transition: 'all 0.3s ease', - boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)' - }, - onMouseenter: (e) => { - e.target.style.transform = 'translateY(-2px)'; - e.target.style.boxShadow = '0 6px 16px rgba(102, 126, 234, 0.4)'; - }, - onMouseleave: (e) => { - e.target.style.transform = 'translateY(0)'; - e.target.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.3)'; - } - }, [ - h('span', { style: { fontSize: '18px' } }, '🔍'), - '缩小字体' - ]), - h('button', { - onClick: () => fontSize.value = Math.min(28, fontSize.value + 2), - style: { - padding: '10px 16px', - border: 'none', - borderRadius: '12px', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - color: 'white', - cursor: 'pointer', - fontSize: '14px', - fontWeight: '500', - display: 'flex', - alignItems: 'center', - gap: '8px', - transition: 'all 0.3s ease', - boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)' - }, - onMouseenter: (e) => { - e.target.style.transform = 'translateY(-2px)'; - e.target.style.boxShadow = '0 6px 16px rgba(102, 126, 234, 0.4)'; - }, - onMouseleave: (e) => { - e.target.style.transform = 'translateY(0)'; - e.target.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.3)'; - } - }, [ - h('span', { style: { fontSize: '18px' } }, '🔍'), - '放大字体' - ]), - h('button', { - onClick: () => { - const textArea = document.createElement('textarea'); - textArea.value = text; - document.body.appendChild(textArea); - textArea.select(); - document.execCommand('copy'); - document.body.removeChild(textArea); - Message.success('文本已复制到剪贴板'); - }, - style: { - padding: '10px 16px', - border: 'none', - borderRadius: '12px', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - color: 'white', - cursor: 'pointer', - fontSize: '14px', - fontWeight: '500', - display: 'flex', - alignItems: 'center', - gap: '8px', - transition: 'all 0.3s ease', - boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)' - }, - onMouseenter: (e) => { - e.target.style.transform = 'translateY(-2px)'; - e.target.style.boxShadow = '0 6px 16px rgba(102, 126, 234, 0.4)'; - }, - onMouseleave: (e) => { - e.target.style.transform = 'translateY(0)'; - e.target.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.3)'; - } - }, [ - h('span', { style: { fontSize: '18px' } }, '📋'), - '复制文本' - ]) - ]), - - // 文本内容 - h('div', { - style: { - flex: 1, - backgroundColor: 'rgba(255, 255, 255, 0.95)', - borderRadius: '20px', - padding: '32px', - boxShadow: '0 16px 48px rgba(0,0,0,0.2)', - overflow: 'auto', - fontFamily: "'JetBrains Mono', 'Fira Code', 'Courier New', 'Monaco', 'Menlo', monospace", - fontSize: `${fontSize.value}px`, - lineHeight: '1.8', - whiteSpace: 'pre-wrap', - wordBreak: 'break-word', - color: '#2c3e50', - border: '1px solid rgba(255,255,255,0.3)', - backdropFilter: 'blur(10px)', - position: 'relative', - zIndex: 5 - } - }, text) - ]); - Modal.info({ - title: h('div', { + title: '文本预览', + content: h('div', { style: { - display: 'flex', - alignItems: 'center', - gap: '12px', - fontSize: '20px', - fontWeight: '600', - color: '#2c3e50', textAlign: 'center', - width: '100%', - justifyContent: 'center' + padding: '20px' } }, [ - h('span', { style: { fontSize: '24px' } }, '📄'), - `文本预览 - ${fileName}` + h('div', { + style: { + maxWidth: '100%', + maxHeight: '70vh', + overflow: 'auto', + backgroundColor: '#f8f9fa', + border: '1px solid #e9ecef', + borderRadius: '8px', + padding: '20px', + fontFamily: "'Consolas', 'Monaco', 'Courier New', monospace", + fontSize: '14px', + lineHeight: '1.6', + whiteSpace: 'pre-wrap', + wordBreak: 'break-word', + color: '#333', + textAlign: 'left' + } + }, text) ]), - content: container, - width: '90%', - style: { - maxWidth: '1400px', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - borderRadius: '20px', - overflow: 'hidden', - boxShadow: '0 32px 96px rgba(0,0,0,0.3)', - border: '3px solid rgba(255,255,255,0.2)' - }, - mask: true, - maskClosable: true, + width: '80%', footer: null, - closable: true, - okText: null, - cancelText: null + closable: true }); } catch (error) { console.error('文本预览失败:', error); @@ -1895,6 +1478,7 @@ const showTextPreview = async (blob, fileName) => { // 显示视频预览 const showVideoPreview = (url, fileName) => { const container = h('div', { + class: 'preview-container', style: { width: '100%', height: '100%', @@ -1902,40 +1486,24 @@ const showVideoPreview = (url, fileName) => { flexDirection: 'column', alignItems: 'center', justifyContent: 'center', - padding: '24px', + padding: '20px', boxSizing: 'border-box', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - borderRadius: '16px', - position: 'relative', - overflow: 'hidden' + background: '#f8fafc !important' } }, [ - // 背景装饰 - h('div', { - style: { - position: 'absolute', - top: '-50%', - left: '-50%', - width: '200%', - height: '200%', - background: 'radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%)', - animation: 'float 6s ease-in-out infinite' - } - }), - // 视频容器 h('div', { style: { width: '100%', maxWidth: '1000px', - backgroundColor: 'rgba(255, 255, 255, 0.95)', - borderRadius: '20px', - padding: '32px', - boxShadow: '0 16px 48px rgba(0,0,0,0.2)', - backdropFilter: 'blur(10px)', - border: '1px solid rgba(255,255,255,0.3)', - position: 'relative', - zIndex: 5 + backgroundColor: '#ffffff', + borderRadius: '8px', + padding: '20px', + boxShadow: '0 2px 8px rgba(0,0,0,0.1)', + border: '1px solid #e2e8f0', + display: 'flex', + flexDirection: 'column', + alignItems: 'center' } }, [ h('video', { @@ -1944,34 +1512,41 @@ const showVideoPreview = (url, fileName) => { style: { width: '100%', maxHeight: '70vh', - borderRadius: '16px', + borderRadius: '8px', background: '#000', - boxShadow: '0 12px 32px rgba(0,0,0,0.3)', - border: '3px solid rgba(255,255,255,0.8)', - transition: 'all 0.3s ease' + boxShadow: '0 4px 12px rgba(0,0,0,0.15)', + border: '1px solid #e2e8f0' } }) ]), - // 文件名 + // 文件信息栏 h('div', { style: { - marginTop: '20px', - fontSize: '16px', - color: 'white', - wordBreak: 'break-all', - textAlign: 'center', - padding: '12px 24px', - backgroundColor: 'rgba(255, 255, 255, 0.15)', - borderRadius: '12px', - boxShadow: '0 4px 16px rgba(0,0,0,0.1)', - backdropFilter: 'blur(10px)', - border: '1px solid rgba(255,255,255,0.2)', - fontWeight: '500', - position: 'relative', - zIndex: 5 + marginTop: '16px', + padding: '12px 16px', + backgroundColor: '#ffffff', + borderRadius: '8px', + boxShadow: '0 2px 8px rgba(0,0,0,0.1)', + border: '1px solid #e2e8f0', + width: '100%', + maxWidth: '1000px', + display: 'flex', + alignItems: 'center', + gap: '16px', + fontSize: '14px', + color: '#2c3e50' } - }, fileName) + }, [ + h('span', { style: { display: 'flex', alignItems: 'center', gap: '6px' } }, [ + h('span', { style: { fontSize: '16px' } }, '🎬'), + fileName + ]), + h('span', { style: { color: '#666', fontSize: '13px' } }, '|'), + h('span', { style: { color: '#666', fontSize: '13px' } }, '视频文件'), + h('span', { style: { color: '#666', fontSize: '13px' } }, '|'), + h('span', { style: { color: '#666', fontSize: '13px' } }, new Date().toLocaleString('zh-CN')) + ]) ]); Modal.info({ @@ -1979,32 +1554,41 @@ const showVideoPreview = (url, fileName) => { style: { display: 'flex', alignItems: 'center', - gap: '12px', - fontSize: '20px', + gap: '8px', + fontSize: '16px', fontWeight: '600', - color: '#2c3e50', - textAlign: 'center', - width: '100%', - justifyContent: 'center' + color: '#2c3e50' } }, [ - h('span', { style: { fontSize: '24px' } }, '🎬'), + h('span', { style: { fontSize: '18px' } }, '🎬'), '视频预览' ]), content: container, width: '90%', style: { - maxWidth: '1400px', + maxWidth: '1200px', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', - borderRadius: '20px', + borderRadius: '8px', overflow: 'hidden', - boxShadow: '0 32px 96px rgba(0,0,0,0.3)', - border: '3px solid rgba(255,255,255,0.2)' + boxShadow: '0 8px 24px rgba(0,0,0,0.15)', + border: '1px solid #e2e8f0', + zIndex: 10000 }, mask: true, maskClosable: true, + maskStyle: { + backgroundColor: 'rgba(0, 0, 0, 0.6)', + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0, + width: '100vw', + height: '100vh', + zIndex: 9999 + }, footer: null, closable: true, okText: null, @@ -2015,6 +1599,7 @@ const showVideoPreview = (url, fileName) => { // 显示音频预览 const showAudioPreview = (url, fileName) => { const container = h('div', { + class: 'preview-container', style: { width: '100%', height: '100%', @@ -2022,49 +1607,30 @@ const showAudioPreview = (url, fileName) => { flexDirection: 'column', alignItems: 'center', justifyContent: 'center', - padding: '24px', + padding: '20px', boxSizing: 'border-box', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', - borderRadius: '16px', - position: 'relative', - overflow: 'hidden' + background: '#f8fafc !important' } }, [ - // 背景装饰 - h('div', { - style: { - position: 'absolute', - top: '-50%', - left: '-50%', - width: '200%', - height: '200%', - background: 'radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%)', - animation: 'float 6s ease-in-out infinite' - } - }), - // 音频容器 h('div', { style: { width: '100%', maxWidth: '500px', - backgroundColor: 'rgba(255, 255, 255, 0.95)', - borderRadius: '20px', - padding: '40px', - boxShadow: '0 16px 48px rgba(0,0,0,0.2)', - backdropFilter: 'blur(10px)', - border: '1px solid rgba(255,255,255,0.3)', - position: 'relative', - zIndex: 5, + backgroundColor: '#ffffff', + borderRadius: '8px', + padding: '32px', + boxShadow: '0 2px 8px rgba(0,0,0,0.1)', + border: '1px solid #e2e8f0', textAlign: 'center' } }, [ // 音频图标 h('div', { style: { - fontSize: '64px', - marginBottom: '24px', - animation: 'pulse 2s ease-in-out infinite' + fontSize: '48px', + marginBottom: '20px', + color: '#165DFF' } }, '🎵'), @@ -2074,26 +1640,54 @@ const showAudioPreview = (url, fileName) => { controls: true, style: { width: '100%', - height: '50px', - borderRadius: '12px', - marginBottom: '20px' + height: '40px', + borderRadius: '6px', + marginBottom: '16px' } }), // 文件名 h('div', { style: { - fontSize: '16px', + fontSize: '14px', color: '#2c3e50', wordBreak: 'break-all', textAlign: 'center', - padding: '12px 16px', - backgroundColor: 'rgba(102, 126, 234, 0.1)', - borderRadius: '12px', + padding: '8px 12px', + backgroundColor: '#f8fafc', + borderRadius: '6px', fontWeight: '500', - border: '1px solid rgba(102, 126, 234, 0.2)' + border: '1px solid #e2e8f0' } }, fileName) + ]), + + // 文件信息栏 + h('div', { + style: { + marginTop: '16px', + padding: '12px 16px', + backgroundColor: '#ffffff', + borderRadius: '8px', + boxShadow: '0 2px 8px rgba(0,0,0,0.1)', + border: '1px solid #e2e8f0', + width: '100%', + maxWidth: '500px', + display: 'flex', + alignItems: 'center', + gap: '16px', + fontSize: '14px', + color: '#2c3e50' + } + }, [ + h('span', { style: { display: 'flex', alignItems: 'center', gap: '6px' } }, [ + h('span', { style: { fontSize: '16px' } }, '🎵'), + fileName + ]), + h('span', { style: { color: '#666', fontSize: '13px' } }, '|'), + h('span', { style: { color: '#666', fontSize: '13px' } }, '音频文件'), + h('span', { style: { color: '#666', fontSize: '13px' } }, '|'), + h('span', { style: { color: '#666', fontSize: '13px' } }, new Date().toLocaleString('zh-CN')) ]) ]); @@ -2102,16 +1696,13 @@ const showAudioPreview = (url, fileName) => { style: { display: 'flex', alignItems: 'center', - gap: '12px', - fontSize: '20px', + gap: '8px', + fontSize: '16px', fontWeight: '600', - color: '#2c3e50', - textAlign: 'center', - width: '100%', - justifyContent: 'center' + color: '#2c3e50' } }, [ - h('span', { style: { fontSize: '24px' } }, '🎵'), + h('span', { style: { fontSize: '18px' } }, '🎵'), '音频预览' ]), content: container, @@ -2121,13 +1712,25 @@ const showAudioPreview = (url, fileName) => { top: '50%', left: '50%', transform: 'translate(-50%, -50%)', - borderRadius: '20px', + borderRadius: '8px', overflow: 'hidden', - boxShadow: '0 32px 96px rgba(0,0,0,0.3)', - border: '3px solid rgba(255,255,255,0.2)' + boxShadow: '0 8px 24px rgba(0,0,0,0.15)', + border: '1px solid #e2e8f0', + zIndex: 10000 }, mask: true, maskClosable: true, + maskStyle: { + backgroundColor: 'rgba(0, 0, 0, 0.6)', + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0, + width: '100vw', + height: '100vh', + zIndex: 9999 + }, footer: null, closable: true, okText: null, @@ -2341,6 +1944,18 @@ const handleCreateFolder = () => { // 打开上传文件对话框 const handleUploadFile = () => { + // 清空文件列表,避免显示之前上传的文件 + fileListTemp.value = []; + + // 重置上传组件状态 + if (uploadRef.value) { + try { + uploadRef.value.reset(); + } catch (error) { + console.log('重置上传组件时出错:', error); + } + } + uploadForm.folderId = currentFolderId.value || ''; uploadDialogVisible.value = true; }; @@ -2361,6 +1976,8 @@ watch(currentFolderId, (newId) => { } }); + + // 初始化加载 onMounted(() => { initData(); @@ -3072,57 +2689,106 @@ onMounted(() => { /* 预览弹窗样式增强 */ :deep(.arco-modal) { - animation: slideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1); + animation: slideIn 0.2s ease-out; + z-index: 10000 !important; } +/* 确保遮罩覆盖整个屏幕 */ :deep(.arco-modal-mask) { - animation: fadeIn 0.3s ease; - backdrop-filter: blur(8px); + animation: fadeIn 0.2s ease; + background-color: rgba(0, 0, 0, 0.6) !important; + position: fixed !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + width: 100vw !important; + height: 100vh !important; + z-index: 9999 !important; + margin: 0 !important; + padding: 0 !important; + border: none !important; + outline: none !important; + box-sizing: border-box !important; } :deep(.arco-modal-content) { - border-radius: 20px !important; + border-radius: 8px !important; overflow: hidden; - box-shadow: 0 32px 96px rgba(0,0,0,0.3) !important; - border: 3px solid rgba(255,255,255,0.2) !important; + box-shadow: 0 8px 24px rgba(0,0,0,0.15) !important; + border: 1px solid #e2e8f0 !important; + background: #ffffff !important; +} + +:deep(.arco-modal-body) { + background: #f8fafc !important; + padding: 0 !important; +} + +/* 强制覆盖所有可能的背景样式 */ +:deep(.arco-modal-body > div) { + background: #f8fafc !important; +} + +:deep(.arco-modal-body > div > div) { + background: #f8fafc !important; +} + +/* 确保预览弹窗内容区域使用正确的背景 */ +:deep(.arco-modal-body .preview-container) { + background: #f8fafc !important; +} + +/* 强制覆盖所有预览弹窗的背景 */ +:deep(.arco-modal-body) { + background: #f8fafc !important; +} + +:deep(.arco-modal-body > *) { + background: #f8fafc !important; +} + +/* 确保没有渐变背景 */ +:deep(.arco-modal-body *[style*="linear-gradient"]) { + background: #f8fafc !important; } :deep(.arco-modal-header) { - background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); - border-bottom: 1px solid rgba(255,255,255,0.2); - padding: 20px 24px; + background: #ffffff; + border-bottom: 1px solid #e2e8f0; + padding: 16px 20px; } :deep(.arco-modal-title) { font-weight: 600; color: #2c3e50; + font-size: 16px; } :deep(.arco-modal-close) { - background: rgba(255,255,255,0.9); + background: #f8fafc; border-radius: 50%; - width: 32px; - height: 32px; + width: 28px; + height: 28px; display: flex; align-items: center; justify-content: center; - transition: all 0.3s ease; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); + transition: all 0.2s ease; + border: 1px solid #e2e8f0; &:hover { - background: rgba(255,255,255,1); - transform: scale(1.1); - box-shadow: 0 4px 12px rgba(0,0,0,0.15); + background: #e2e8f0; + color: #165DFF; } } /* 按钮悬停效果增强 */ :deep(.arco-btn) { - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transition: all 0.2s ease; &:hover { - transform: translateY(-2px); - box-shadow: 0 6px 16px rgba(0,0,0,0.15); + border-color: #165DFF; + color: #165DFF; } &:active { diff --git a/src/views/operation-platform/data-processing/data-storage/index.vue b/src/views/operation-platform/data-processing/data-storage/index.vue index 3b95a64..8b3d1e7 100644 --- a/src/views/operation-platform/data-processing/data-storage/index.vue +++ b/src/views/operation-platform/data-processing/data-storage/index.vue @@ -106,8 +106,8 @@ - - + + @@ -115,7 +115,6 @@ import { ref, reactive, computed,onMounted } from 'vue' import { Message } from '@arco-design/web-vue' import type { TableColumnData } from '@arco-design/web-vue' -import PreviewModal from './components/PreviewModal.vue' import { getProjectList, @@ -129,7 +128,8 @@ import { uploadImageToPartV2 } from '@/apis/industrial-image' -const previewModal = ref() +// 预览弹窗引用(待重新设计) +// const previewModal = ref() // 活动选项卡 const activeTab = ref('image') @@ -358,18 +358,17 @@ const getImageUrl = (imagePath: string): string => { return `${baseUrl}${imagePath}` } -// 预览文件 +// 预览文件(待重新设计预览弹窗) const previewFile = (file: any) => { - /* previewFileData.value = file - previewModalVisible.value = true*/ - - const fileObj ={ + const fileObj = { id: file.id, name: file.name, - url: getImageUrl(file.imagePath), - audios: file.audioList + url: getImageUrl(file.imagePath) } - previewModal.value.openPreview(fileObj) + // TODO: 重新设计预览弹窗后取消注释 + // previewModal.value.openPreview(fileObj) + console.log('预览文件:', fileObj) + // 暂时禁用预览功能,等待重新设计 } // 删除文件 From 0401a280374d24a6a1f65dcc75ad42900a76491a Mon Sep 17 00:00:00 2001 From: chabai <14799297+dhasjklhdfjkasfbhfasfj@user.noreply.gitee.com> Date: Fri, 8 Aug 2025 17:02:32 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=95=86=E5=8A=A1?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/components.d.ts | 59 +++++++++++++++++++ src/views/bussiness-data/bussiness.vue | 81 +++++++++++++++++++++++++- 2 files changed, 138 insertions(+), 2 deletions(-) diff --git a/src/types/components.d.ts b/src/types/components.d.ts index 7fa6b1b..19a4246 100644 --- a/src/types/components.d.ts +++ b/src/types/components.d.ts @@ -7,7 +7,66 @@ export {} declare module 'vue' { export interface GlobalComponents { + Avatar: typeof import('./../components/Avatar/index.vue')['default'] + Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default'] + CellCopy: typeof import('./../components/CellCopy/index.vue')['default'] + Chart: typeof import('./../components/Chart/index.vue')['default'] + ColumnSetting: typeof import('./../components/GiTable/src/components/ColumnSetting.vue')['default'] + CronForm: typeof import('./../components/GenCron/CronForm/index.vue')['default'] + CronModal: typeof import('./../components/GenCron/CronModal/index.vue')['default'] + DateRangePicker: typeof import('./../components/DateRangePicker/index.vue')['default'] + DayForm: typeof import('./../components/GenCron/CronForm/component/day-form.vue')['default'] + FilePreview: typeof import('./../components/FilePreview/index.vue')['default'] + GiCellAvatar: typeof import('./../components/GiCell/GiCellAvatar.vue')['default'] + GiCellGender: typeof import('./../components/GiCell/GiCellGender.vue')['default'] + GiCellStatus: typeof import('./../components/GiCell/GiCellStatus.vue')['default'] + GiCellTag: typeof import('./../components/GiCell/GiCellTag.vue')['default'] + GiCellTags: typeof import('./../components/GiCell/GiCellTags.vue')['default'] + GiCodeView: typeof import('./../components/GiCodeView/index.vue')['default'] + GiDot: typeof import('./../components/GiDot/index.tsx')['default'] + GiEditTable: typeof import('./../components/GiEditTable/GiEditTable.vue')['default'] + GiFooter: typeof import('./../components/GiFooter/index.vue')['default'] + GiForm: typeof import('./../components/GiForm/src/GiForm.vue')['default'] + GiIconBox: typeof import('./../components/GiIconBox/index.vue')['default'] + GiIconSelector: typeof import('./../components/GiIconSelector/index.vue')['default'] + GiIframe: typeof import('./../components/GiIframe/index.vue')['default'] + GiOption: typeof import('./../components/GiOption/index.vue')['default'] + GiOptionItem: typeof import('./../components/GiOptionItem/index.vue')['default'] + GiPageLayout: typeof import('./../components/GiPageLayout/index.vue')['default'] + GiSpace: typeof import('./../components/GiSpace/index.vue')['default'] + GiSplitButton: typeof import('./../components/GiSplitButton/index.vue')['default'] + GiSplitPane: typeof import('./../components/GiSplitPane/index.vue')['default'] + GiSplitPaneFlexibleBox: typeof import('./../components/GiSplitPane/components/GiSplitPaneFlexibleBox.vue')['default'] + GiSvgIcon: typeof import('./../components/GiSvgIcon/index.vue')['default'] + GiTable: typeof import('./../components/GiTable/src/GiTable.vue')['default'] + GiTag: typeof import('./../components/GiTag/index.tsx')['default'] + GiThemeBtn: typeof import('./../components/GiThemeBtn/index.vue')['default'] + HourForm: typeof import('./../components/GenCron/CronForm/component/hour-form.vue')['default'] + Icon403: typeof import('./../components/icons/Icon403.vue')['default'] + Icon404: typeof import('./../components/icons/Icon404.vue')['default'] + Icon500: typeof import('./../components/icons/Icon500.vue')['default'] + IconBorders: typeof import('./../components/icons/IconBorders.vue')['default'] + IconTableSize: typeof import('./../components/icons/IconTableSize.vue')['default'] + IconTreeAdd: typeof import('./../components/icons/IconTreeAdd.vue')['default'] + IconTreeReduce: typeof import('./../components/icons/IconTreeReduce.vue')['default'] + ImageImport: typeof import('./../components/ImageImport/index.vue')['default'] + ImageImportWizard: typeof import('./../components/ImageImportWizard/index.vue')['default'] + IndustrialImageList: typeof import('./../components/IndustrialImageList/index.vue')['default'] + JsonPretty: typeof import('./../components/JsonPretty/index.vue')['default'] + MinuteForm: typeof import('./../components/GenCron/CronForm/component/minute-form.vue')['default'] + MonthForm: typeof import('./../components/GenCron/CronForm/component/month-form.vue')['default'] + ParentView: typeof import('./../components/ParentView/index.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + SecondForm: typeof import('./../components/GenCron/CronForm/component/second-form.vue')['default'] + SplitPanel: typeof import('./../components/SplitPanel/index.vue')['default'] + TextCopy: typeof import('./../components/TextCopy/index.vue')['default'] + TurbineGrid: typeof import('./../components/TurbineGrid/index.vue')['default'] + UserSelect: typeof import('./../components/UserSelect/index.vue')['default'] + Verify: typeof import('./../components/Verify/index.vue')['default'] + VerifyPoints: typeof import('./../components/Verify/Verify/VerifyPoints.vue')['default'] + VerifySlide: typeof import('./../components/Verify/Verify/VerifySlide.vue')['default'] + WeekForm: typeof import('./../components/GenCron/CronForm/component/week-form.vue')['default'] + YearForm: typeof import('./../components/GenCron/CronForm/component/year-form.vue')['default'] } } diff --git a/src/views/bussiness-data/bussiness.vue b/src/views/bussiness-data/bussiness.vue index 905c6aa..08a65a2 100644 --- a/src/views/bussiness-data/bussiness.vue +++ b/src/views/bussiness-data/bussiness.vue @@ -409,6 +409,7 @@ { console.log('文件列表:', fileList); console.log('文件列表长度:', fileList.length); + // 检查是否是组件内部状态触发的(可能是之前的状态) + if (fileList.length === 0) { + console.log('⚠️ 文件列表为空,可能是组件内部状态,跳过处理'); + return; + } + + // 检查是否是对话框刚打开时的触发(可能是之前的状态) + if (!uploadDialogVisible.value) { + console.log('⚠️ 对话框未显示,可能是之前的状态触发,跳过处理'); + return; + } + // 获取当前已存在的文件UID列表,用于去重 const existingUids = fileListTemp.value.map(f => f.uid); console.log('已存在的文件UID:', existingUids); - // 处理新选择的文件 - 只处理当前选择的文件 + // 获取当前文件夹中已存在的文件名列表,用于检查重复 + const currentFolderFiles = fileList.value || []; + const existingFileNames = currentFolderFiles.map(f => f.fileName || f.name); + console.log('当前文件夹中的文件:', existingFileNames); + + // 强制重置上传组件状态 + if (uploadRef.value) { + try { + uploadRef.value.reset(); + console.log('已强制重置上传组件'); + } catch (error) { + console.log('重置上传组件时出错:', error); + } + } + + // 处理新选择的文件 - 支持多文件选择,同时避免重复 fileList.forEach((file, index) => { console.log(`处理第${index + 1}个文件:`, file); console.log('文件名称:', file.name); @@ -1111,12 +1139,21 @@ const handleFileChange = (info) => { console.log('开始验证新文件...'); + // 检查文件是否已存在于当前文件夹中 + if (existingFileNames.includes(fileObj.name)) { + fileObj.error = '文件已存在于当前文件夹中'; + console.log('⚠️ 文件已存在于文件夹中:', fileObj.name); + // 显示友好的提示信息 + Message.warning(`文件 "${fileObj.name}" 已存在于当前文件夹中,已跳过`); + return; + } + // 验证文件 const isValid = validateFile(fileObj); console.log('文件验证结果:', isValid); if (isValid) { - // 添加文件到列表(支持多文件) + // 支持多文件:添加到列表 fileListTemp.value.push(fileObj); console.log('✅ 成功添加文件到列表:', fileObj.name); } else { @@ -1951,6 +1988,13 @@ const handleUploadFile = () => { if (uploadRef.value) { try { uploadRef.value.reset(); + // 强制清空组件的内部文件列表 + if (uploadRef.value.fileList) { + uploadRef.value.fileList = []; + } + if (uploadRef.value.fileListTemp) { + uploadRef.value.fileListTemp = []; + } } catch (error) { console.log('重置上传组件时出错:', error); } @@ -1976,6 +2020,39 @@ watch(currentFolderId, (newId) => { } }); +// 监听上传对话框显示状态,确保文件列表清空 +watch(uploadDialogVisible, (visible) => { + if (visible) { + console.log('=== 上传对话框已显示,确保文件列表清空 ==='); + // 立即清空文件列表 + fileListTemp.value = []; + console.log('✅ 已清空文件列表'); + + // 强制重置上传组件 + if (uploadRef.value) { + try { + uploadRef.value.reset(); + // 强制清空组件的内部文件列表 + if (uploadRef.value.fileList) { + uploadRef.value.fileList = []; + } + if (uploadRef.value.fileListTemp) { + uploadRef.value.fileListTemp = []; + } + console.log('✅ 已重置上传组件'); + } catch (error) { + console.log('❌ 重置上传组件时出错:', error); + } + } + + // 延迟再次清空,确保处理完所有可能的触发 + setTimeout(() => { + fileListTemp.value = []; + console.log('✅ 延迟清空文件列表'); + }, 100); + } +}); + // 初始化加载