diff --git a/mobile/src/pages/xg/xsjcxx/xsqj/leaveEdit/index.vue b/mobile/src/pages/xg/xsjcxx/xsqj/leaveEdit/index.vue index 6097c8ac..413fe352 100644 --- a/mobile/src/pages/xg/xsjcxx/xsqj/leaveEdit/index.vue +++ b/mobile/src/pages/xg/xsjcxx/xsqj/leaveEdit/index.vue @@ -31,39 +31,79 @@ - - - - 🔧 开发调试工具(正式发布前请隐藏) + + + + 微信SDK调试 + + + {{ wechatInitializing ? '初始化中' : '重新初始化' }} + + + {{ debugMode ? '关闭调试' : '开启调试' }} + + 清空日志 + - - - 文件大小检查 - - - 调试图片文件 - - - 处理图片文件 - - 审批记录 - 🧪测试上传事件 - - 📷真实选择 - + + + + + 微信环境: + + {{ isWechatBrowser ? '是' : '否' }} + + + + SDK状态: + + {{ wechatSDK.isInitialized ? '已初始化' : '未初始化' }} + + + + 版本信息: + {{ wechatSDK.getVersion() }} + + + 可用API: + {{ wechatSDK.getAvailableApis() }} + + + + + + + {{ log.time }} + {{ log.message }} + + {{ log.detail }} + + + + 暂无调试日志 + + + + + + + + - 定位 + + {{ isWechatBrowser ? '微信定位' : '定位' }} + + + + + + 坐标: {{ geoLatitude.toFixed(6) }}, {{ geoLongitude.toFixed(6) }} + + + + 查看位置 @@ -236,125 +293,234 @@ 附件上传 - - + + + 图片附件 + + + + + - + + + + {{ image.progress || 0 }}% + + + + + + 上传失败 + + + + + + + + + + + + 下载 + + + 重试 + + + 删除 + + + + + + {{ getShortFileName(image.name) }} + + + + + + + + + 微信图片上传进度: {{ uploadProgress }}% + + + + 文档附件 + + + + + + + + + + {{ file.progress || 0 }}% + + + + + + 上传失败 + + + + + + + + + + + {{ getFileExt(file.name) }} + + {{ formatFileSize(file.size) }} + + + + + + + 下载 + + + 重试 + + + 删除 + + + + + + {{ getShortFileName(file.name) }} + + + + + - 上传文档附件 + 添加附件(图片) - + - - - - 直接选择文件 - - - 测试上传逻辑 - - - 测试uview2事件 - - - - - - - - - - {{ getShortFileName(file.name) }} - {{ getFileExt(file.name) }} - - - - - 下载 - - - 删除 - - - - - 上传中... - - - 上传失败 - - + + + + • 支持格式:JPG、PNG等 • 单个文件不超过10MB • 最多上传15个文件 + + • 图片文件将以图片形式展示,支持预览和下载 + + • 在微信中使用可获得更好的图片选择体验 + @@ -438,6 +604,7 @@ import { REQUEST_CONFIG } from '@/common/config.js'; import ApprovalButton from '@/components/Process/approvalButton.vue'; import SubmitVerify from '@/components/Process/submitVerify.vue'; import ApprovalRecord from '@/components/Process/approvalRecord.vue'; +import weChatSDK from '@/utils/wechat.js'; export default { name: 'LeaveEdit', @@ -461,6 +628,19 @@ export default { { label: '销假流程', value: 'hzdyjs-xj' }, ], + // 微信SDK相关 + wechatSDK: weChatSDK, + isWechatBrowser: false, + wechatInitializing: false, + wechatImageLoading: false, + debugMode: false, + wechatLogs: [], + latestLogId: '', + + // 微信图片相关 + selectedWechatImages: [], + uploadProgress: 0, // 上传进度 + form: { id: undefined, studentId: '', @@ -548,46 +728,6 @@ export default { return index >= 0 ? index : 0; }, - // uview2 upload组件所需的文件列表格式 - uploadFileList() { - const imageFiles = this.attachmentList.filter((file) => this.isImageFile(file)); - console.log('uploadFileList - 图片文件列表:', imageFiles); - - return imageFiles.map((file) => { - // 确保图片文件有正确的显示属性 - const displayUrl = file.blobUrl || file.url || file.path; - const uploadFile = { - ...file, - url: displayUrl, - status: file.status || 'success', - message: file.status === 'error' ? '上传失败' : '', - // 明确告诉 u-upload 这是图片文件 - fileType: 'image', - type: 'image', - isImage: true, - thumb: displayUrl, - name: file.name || 'image.jpg', - }; - - // 对于从服务器获取的图片文件,确保有正确的缩略图显示 - if (file.ossId && displayUrl) { - // uview2 upload组件需要的额外属性 - if (!uploadFile.file) { - uploadFile.file = { - name: uploadFile.name, - type: 'image', - size: file.size || 0, - path: displayUrl, - url: displayUrl, - }; - } - } - - console.log('处理后的upload文件:', uploadFile); - return uploadFile; - }); - }, - // 图片文件(用于内部处理) imageFiles() { return this.attachmentList.filter((file) => this.isImageFile(file)); @@ -598,8 +738,12 @@ export default { return this.attachmentList.filter((file) => !this.isImageFile(file)); }, }, - onLoad(options) { + async onLoad(options) { this.routeParams = options || {}; + + // 初始化微信环境检查并自动初始化SDK + await this.checkWechatEnvironment(); + this.initPageData(); }, onUnload() { @@ -607,6 +751,462 @@ export default { this.cleanupBlobUrls(); }, methods: { + // === 微信SDK相关方法 === + + // 检查微信环境并自动初始化SDK + async checkWechatEnvironment() { + this.addWechatLog('开始检查微信环境...', 'info'); + + try { + // 检查是否在微信浏览器中 + this.isWechatBrowser = this.wechatSDK.isWeChatBrowser(); + this.addWechatLog( + `微信环境检测结果: ${this.isWechatBrowser ? '是' : '否'}`, + this.isWechatBrowser ? 'success' : 'warning', + ); + + if (this.isWechatBrowser) { + // 在微信环境中,自动初始化SDK + this.addWechatLog('检测到微信环境,开始自动初始化SDK...', 'info'); + await this.initWechatSDK(); + } else { + this.addWechatLog('非微信环境,跳过SDK初始化', 'warning'); + } + + // 在开发环境或微信环境中默认开启调试模式 + if (process.env.NODE_ENV === 'development' || this.isWechatBrowser) { + this.debugMode = false; // 默认关闭,用户可手动开启 + this.addWechatLog('调试模式已准备就绪', 'info'); + } + } catch (error) { + console.error('微信环境检查失败:', error); + this.addWechatLog(`微信环境检查失败: ${error.message}`, 'error'); + } + }, + + // 初始化微信SDK + async initWechatSDK() { + if (this.wechatInitializing) { + this.addWechatLog('SDK正在初始化中,请等待...', 'warning'); + return; + } + + this.wechatInitializing = true; + this.addWechatLog('开始初始化微信SDK...', 'info'); + + try { + // 使用微信SDK工具类进行初始化 + await this.wechatSDK.init({ + debug: process.env.NODE_ENV === 'development', + jsApiList: ['chooseImage', 'previewImage', 'getLocation', 'openLocation'], + }); + + this.addWechatLog('微信SDK初始化成功', 'success'); + this.addWechatLog(`SDK版本: ${this.wechatSDK.getVersion()}`, 'info'); + this.addWechatLog(`可用API: ${this.wechatSDK.getAvailableApis()}`, 'info'); + + // 检查关键API支持情况 + try { + const apiCheckResult = await this.wechatSDK.checkJsApi([ + 'chooseImage', + 'getLocation', + ]); + this.addWechatLog( + `API支持检查完成`, + 'success', + JSON.stringify(apiCheckResult, null, 2), + ); + } catch (apiError) { + this.addWechatLog(`API支持检查失败: ${apiError.message}`, 'warning'); + } + } catch (error) { + console.error('微信SDK初始化失败:', error); + this.addWechatLog(`微信SDK初始化失败: ${error.message}`, 'error'); + + // 显示用户友好的错误提示 + if (error.message.includes('invalid signature')) { + this.addWechatLog('可能原因: 签名验证失败,请检查域名配置', 'error'); + } else if (error.message.includes('invalid url domain')) { + this.addWechatLog('可能原因: 当前域名未在微信公众平台配置', 'error'); + } + } finally { + this.wechatInitializing = false; + } + }, + + // 重新初始化微信SDK + async reinitWechatSDK() { + this.addWechatLog('用户触发重新初始化SDK...', 'info'); + this.wechatSDK.isInitialized = false; + await this.initWechatSDK(); + }, + + // 添加调试日志 + addWechatLog(message, type = 'info', detail = '') { + const log = { + time: new Date().toLocaleTimeString(), + message: message, + type: type, // 'info', 'success', 'warning', 'error' + detail: detail, + }; + + this.wechatLogs.push(log); + + // 控制日志数量,避免内存溢出 + if (this.wechatLogs.length > 100) { + this.wechatLogs.shift(); + } + + // 更新最新日志ID,用于自动滚动 + this.latestLogId = `wechat-log-${this.wechatLogs.length - 1}`; + + // 同时输出到控制台 + const consoleMethod = + { + info: 'log', + success: 'log', + warning: 'warn', + error: 'error', + }[type] || 'log'; + + console[consoleMethod](`[微信SDK] ${message}`, detail ? detail : ''); + }, + + // 清空调试日志 + clearWechatLogs() { + this.wechatLogs = []; + this.latestLogId = ''; + this.addWechatLog('调试日志已清空', 'info'); + }, + + // 切换调试模式 + toggleDebugMode() { + this.debugMode = !this.debugMode; + this.addWechatLog(`调试模式已${this.debugMode ? '开启' : '关闭'}`, 'info'); + + if (this.debugMode) { + uni.showToast({ + title: '调试模式已开启', + icon: 'success', + }); + } + }, + + // 微信选择图片 + async chooseWechatImage() { + if (!this.isWechatBrowser) { + this.addWechatLog('非微信环境,无法使用微信选择图片功能', 'error'); + uni.showToast({ + title: '请在微信中打开', + icon: 'none', + }); + return; + } + + if (!this.wechatSDK.isInitialized) { + this.addWechatLog('微信SDK未初始化,尝试重新初始化...', 'warning'); + await this.initWechatSDK(); + + if (!this.wechatSDK.isInitialized) { + uni.showToast({ + title: '微信SDK未就绪', + icon: 'none', + }); + return; + } + } + + // 检查文件数量限制 + if (this.attachmentList.length >= 15) { + uni.showToast({ + title: '最多只能上传15个文件', + icon: 'none', + }); + return; + } + + this.wechatImageLoading = true; + this.uploadProgress = 0; + this.addWechatLog('开始选择微信图片...', 'info'); + + try { + // 计算剩余可选择的图片数量 + const remainingCount = 5 - this.attachmentList.length; + const selectCount = Math.min(remainingCount, 9); + + this.addWechatLog(`可选择图片数量: ${selectCount}`, 'info'); + + const result = await this.wechatSDK.chooseImage({ + count: selectCount, + sizeType: ['original', 'compressed'], + sourceType: ['album', 'camera'], + }); + + this.addWechatLog(`成功选择了 ${result.localIds.length} 张图片`, 'success'); + + // 处理选中的图片 + if (result.localIds && result.localIds.length > 0) { + await this.processWechatImages(result.localIds); + } + } catch (error) { + console.error('微信选择图片失败:', error); + this.addWechatLog(`选择图片失败: ${error.message}`, 'error'); + + uni.showToast({ + title: '选择图片失败', + icon: 'none', + }); + } finally { + this.wechatImageLoading = false; + this.uploadProgress = 0; + } + }, + + // 上传微信图片到OSS(参考stuMsgDialog.vue的实现) + async uploadWechatImageToOSS(localId) { + try { + this.addWechatLog('🚀 开始上传微信图片到OSS...', 'info'); + this.uploadProgress = 10; + + // 步骤1:上传到微信服务器(20%进度) + this.addWechatLog('📤 正在上传到微信服务器...', 'info'); + const wxUploadRes = await this.wechatSDK.uploadImage(localId); + + if (!wxUploadRes.serverId) { + throw new Error('微信服务器上传失败,未获取到serverId'); + } + + const mediaId = wxUploadRes.serverId; + this.uploadProgress = 30; + this.addWechatLog(`✅ 微信服务器上传成功,mediaId: ${mediaId}`, 'success'); + + // 步骤2:保存到OSS(80%进度) + this.uploadProgress = 50; + this.addWechatLog('💾 正在保存到云端存储...', 'info'); + + const ossResponse = await request({ + url: '/system/wechat/downloadToOss', + method: 'GET', + params: { + mediaId: mediaId, + }, + timeout: 30000, // 30秒超时 + }); + + if (ossResponse.code === 200) { + const ossResult = ossResponse.data; + this.uploadProgress = 90; + + this.addWechatLog( + '🎉 图片保存成功!', + 'success', + `文件名: ${ossResult.fileName}\nOSS ID: ${ossResult.ossId}`, + ); + + this.uploadProgress = 100; + return { + ossId: ossResult.ossId, + url: ossResult.url, + fileName: ossResult.fileName, + fileSize: ossResult.fileSize || 0, + }; + } else { + throw new Error(ossResponse.msg || '保存图片到云端存储失败'); + } + } catch (error) { + console.error('上传微信图片失败:', error); + this.addWechatLog(`❌ 图片上传失败: ${error.message}`, 'error'); + + // 上传失败时重置进度 + this.uploadProgress = 0; + + // 显示详细错误信息 + let errorMsg = '图片上传失败,请重试'; + if (error.message.includes('timeout')) { + errorMsg = '上传超时,请检查网络连接后重试'; + } else if ( + error.message.includes('mediaId') || + error.message.includes('serverId') + ) { + errorMsg = '微信服务器上传失败,请重试'; + } else if (error.message.includes('OSS')) { + errorMsg = '保存到云端存储失败,请重试'; + } + + uni.showToast({ + title: errorMsg, + icon: 'none', + duration: 2000, + }); + + throw error; + } + }, + + // 处理微信选中的图片(支持多文件上传) + async processWechatImages(localIds) { + this.addWechatLog(`开始处理 ${localIds.length} 张微信图片...`, 'info'); + + // 检查文件数量限制 + if (this.attachmentList.length + localIds.length > 15) { + const canUpload = 15 - this.attachmentList.length; + if (canUpload <= 0) { + uni.showToast({ + title: '最多只能上传15个文件', + icon: 'none', + }); + return; + } + + // 只处理允许上传的数量 + localIds = localIds.slice(0, canUpload); + uni.showToast({ + title: `已选择前${canUpload}张图片`, + icon: 'none', + duration: 2000, + }); + } + + const totalFiles = localIds.length; + let successCount = 0; + let failureCount = 0; + + for (let i = 0; i < totalFiles; i++) { + const localId = localIds[i]; + + // 计算总体进度 + const overallProgress = Math.round((i / totalFiles) * 100); + this.addWechatLog( + `正在处理第 ${i + 1}/${totalFiles} 张图片... (${overallProgress}%)`, + 'info', + ); + + try { + // 创建文件对象并添加到列表(显示上传中状态) + const file = { + uid: `wechat_${Date.now()}_${i}`, + name: `微信图片_${Date.now()}_${i + 1}.jpg`, + url: localId, + localId: localId, + status: 'uploading', + type: 'image/jpeg', + size: 0, // 微信API无法直接获取文件大小 + source: 'wechat', + progress: 0, + }; + + // 添加到附件列表(立即显示) + this.attachmentList.push(file); + + // 上传到OSS + const uploadResult = await this.uploadWechatImageToOSS(localId); + + // 更新文件信息 + file.status = 'success'; + file.ossId = uploadResult.ossId; + file.url = uploadResult.url; + file.name = uploadResult.fileName || file.name; + file.size = uploadResult.fileSize || 0; + file.progress = 100; + + successCount++; + this.addWechatLog(`✅ 第 ${i + 1} 张图片上传成功: ${file.name}`, 'success'); + } catch (error) { + console.error(`处理微信图片失败:`, error); + + // 更新失败的文件状态 + const failedFile = this.attachmentList.find((f) => f.localId === localId); + if (failedFile) { + failedFile.status = 'error'; + failedFile.progress = 0; + } + + failureCount++; + this.addWechatLog(`❌ 第 ${i + 1} 张图片上传失败: ${error.message}`, 'error'); + } + + // 重置单个文件的进度条 + this.uploadProgress = 0; + } + + // 显示最终结果 + const resultMessage = `上传完成: 成功${successCount}个,失败${failureCount}个`; + this.addWechatLog(resultMessage, successCount > 0 ? 'success' : 'warning'); + + if (successCount > 0) { + uni.showToast({ + title: `成功上传${successCount}个文件`, + icon: 'success', + duration: 2000, + }); + } + + if (failureCount > 0) { + uni.showModal({ + title: '部分文件上传失败', + content: `${failureCount}个文件上传失败,请检查网络后重试`, + showCancel: false, + confirmText: '知道了', + }); + } + }, + + // 打开地图查看位置 + async openLocationInMap() { + if (!this.geoLatitude || !this.geoLongitude) { + uni.showToast({ + title: '暂无位置信息', + icon: 'none', + }); + return; + } + + if (this.isWechatBrowser && this.wechatSDK.isInitialized) { + try { + this.addWechatLog('使用微信打开地图...', 'info'); + await this.wechatSDK.openLocation({ + latitude: this.geoLatitude, + longitude: this.geoLongitude, + name: '请假地点', + address: this.form.leaveLocation || '当前位置', + scale: 16, + }); + this.addWechatLog('微信地图打开成功', 'success'); + } catch (error) { + this.addWechatLog(`微信地图打开失败: ${error.message}`, 'error'); + this.fallbackOpenMap(); + } + } else { + this.fallbackOpenMap(); + } + }, + + // 备用地图打开方式 + fallbackOpenMap() { + const url = `https://apis.map.qq.com/uri/v1/marker?marker=coord:${this.geoLatitude},${ + this.geoLongitude + };title:请假地点;addr:${encodeURIComponent( + this.form.leaveLocation || '当前位置', + )}&referer=智慧校园`; + + // #ifdef H5 + window.open(url, '_blank'); + // #endif + + // #ifndef H5 + uni.showModal({ + title: '查看地图', + content: '是否打开外部地图应用查看位置?', + success: (res) => { + if (res.confirm) { + // 在非H5环境中,可以尝试使用系统的地图应用 + plus.runtime.openURL(url); + } + }, + }); + // #endif + }, + // 初始化页面数据 async initPageData() { try { @@ -804,9 +1404,20 @@ export default { console.log(`图片URL处理: ${item.url} -> ${processedUrl}`); } + // 构造完整的文件名(包含扩展名) + let fullFileName = item.originalName || `附件${index + 1}`; + if ( + item.fileSuffix && + !fullFileName + .toLowerCase() + .endsWith(`.${item.fileSuffix.toLowerCase()}`) + ) { + fullFileName = `${fullFileName}.${item.fileSuffix}`; + } + const fileObj = { uid: `existing_${index}_${Date.now()}`, - name: item.originalName || `附件${index + 1}`, + name: fullFileName, url: processedUrl, ossId: item.ossId, status: 'success', @@ -815,7 +1426,7 @@ export default { size: item.fileSize || 0, // 为uview2 upload组件添加必要的属性 file: { - name: item.originalName || `附件${index + 1}`, + name: fullFileName, type: fileType, size: item.fileSize || 0, path: processedUrl, @@ -826,6 +1437,7 @@ export default { console.log(`附件 ${index + 1} 处理结果:`, { 原始文件名: item.originalName, 文件后缀: item.fileSuffix, + 完整文件名: fullFileName, 文件类型: fileType, 是否为图片: isImage, 图片URL: item.url, @@ -843,17 +1455,9 @@ export default { // 为图片文件获取blob数据以确保正确显示 await this.processImageFiles(); - // 强制刷新upload组件显示 + // 强制刷新组件显示 this.$nextTick(() => { - console.log('强制刷新upload组件显示'); - if (this.$refs.uploadRef) { - // 触发upload组件的重新渲染 - this.$refs.uploadRef.$forceUpdate(); - } - - // 调试:检查图片文件的处理情况 - this.debugImageFiles(); - + console.log('强制刷新图片组件显示'); // 预加载图片以确保能正确显示 this.preloadImages(); }); @@ -1179,85 +1783,159 @@ export default { }); }, - // 智能定位:根据环境自动选择最适合的定位方式 + // 智能定位:根据环境自动选择最适合的定位方式(优化版) async handleGetLocation() { try { uni.showLoading({ title: '定位中...', mask: true }); + this.addWechatLog('开始智能定位...', 'info'); - console.log('=== 开始智能定位 ==='); - console.log('当前环境检测结果:', { - isWeChat: this.isWeChat(), + const envInfo = { + isWechatBrowser: this.isWechatBrowser, + isWechatSdkReady: this.wechatSDK.isInitialized, isH5Web: this.isH5Web(), isBrowserGeolocationSupported: this.isBrowserGeolocationSupported(), - }); + }; - // 优先级1:微信端定位(如果在微信环境中) - if (this.isWeChat()) { - console.log('检测到微信环境,尝试微信JSSDK定位...'); - const wxLocationSuccess = await this.tryWeChatLocation(); + this.addWechatLog('当前环境检测结果', 'info', JSON.stringify(envInfo, null, 2)); + + // 优先级1:微信SDK定位(如果在微信环境且SDK已就绪) + if (this.isWechatBrowser && this.wechatSDK.isInitialized) { + this.addWechatLog('尝试微信SDK定位...', 'info'); + const wxLocationSuccess = await this.tryWeChatLocationWithSDK(); if (wxLocationSuccess) { - console.log('微信定位成功'); + this.addWechatLog('微信SDK定位成功', 'success'); return; } - console.log('微信定位失败,尝试下一种方式...'); + this.addWechatLog('微信SDK定位失败,尝试下一种方式...', 'warning'); } - // 优先级2:H5 web端原生定位(如果在H5环境且浏览器支持) + // 优先级2:微信环境但SDK未就绪,尝试初始化后重试 + if (this.isWechatBrowser && !this.wechatSDK.isInitialized) { + this.addWechatLog('微信SDK未初始化,尝试快速初始化...', 'warning'); + try { + await this.initWechatSDK(); + if (this.wechatSDK.isInitialized) { + const wxLocationSuccess = await this.tryWeChatLocationWithSDK(); + if (wxLocationSuccess) { + this.addWechatLog('微信SDK初始化并定位成功', 'success'); + return; + } + } + } catch (error) { + this.addWechatLog(`微信SDK初始化失败: ${error.message}`, 'error'); + } + } + + // 优先级3:H5 web端原生定位 if (this.isH5Web() && this.isBrowserGeolocationSupported()) { - console.log('检测到H5环境且支持原生定位,尝试浏览器原生定位...'); + this.addWechatLog('尝试H5浏览器原生定位...', 'info'); const h5LocationSuccess = await this.handleH5WebLocation(); if (h5LocationSuccess) { - console.log('H5原生定位成功'); + this.addWechatLog('H5原生定位成功', 'success'); return; } - console.log('H5原生定位失败,尝试下一种方式...'); + this.addWechatLog('H5原生定位失败,尝试最后的备用方案...', 'warning'); } - // 优先级3:uni-app通用定位(最后的回退方案) - console.log('尝试uni-app通用定位...'); + // 优先级4:uni-app通用定位(最后的回退方案) + this.addWechatLog('尝试uni-app通用定位...', 'info'); await this.handleFallbackLocation(); } catch (error) { console.error('智能定位流程出错:', error); + this.addWechatLog(`智能定位流程异常: ${error.message}`, 'error'); + uni.hideLoading(); - uni.showToast({ - title: '定位功能暂时不可用', - icon: 'none', - duration: 2000, + uni.showModal({ + title: '定位失败', + content: '定位功能暂时不可用,您可以手动输入位置信息。是否查看详细错误信息?', + showCancel: true, + cancelText: '取消', + confirmText: '查看详情', + success: (res) => { + if (res.confirm && this.isWechatBrowser) { + this.debugMode = true; // 自动开启调试模式以查看详情 + } + }, }); } }, - // 尝试微信定位(独立方法,便于错误处理) - async tryWeChatLocation() { + // 使用微信SDK进行定位(新版本,集成调试日志) + async tryWeChatLocationWithSDK() { try { - const wxInitSuccess = await this.initWeChatJssdk(); - if (wxInitSuccess && typeof wx !== 'undefined') { - return new Promise((resolve) => { - wx.getLocation({ - type: 'gcj02', - success: (res) => { - console.log('微信定位成功:', res); - this.geoLatitude = res.latitude; - this.geoLongitude = res.longitude; - this.form.leaveLocation = `${res.latitude},${res.longitude}`; - uni.hideLoading(); - uni.showToast({ title: '微信定位成功', icon: 'success' }); - resolve(true); - }, - fail: (error) => { - console.error('微信定位失败:', error); - resolve(false); - }, - }); + this.addWechatLog('开始微信SDK定位...', 'info'); + + const result = await this.wechatSDK.getLocation({ + type: 'gcj02', // 火星坐标系,适合国内地图 + }); + + this.addWechatLog( + '微信定位API调用成功', + 'success', + JSON.stringify(result, null, 2), + ); + + if (result.latitude && result.longitude) { + this.geoLatitude = parseFloat(result.latitude); + this.geoLongitude = parseFloat(result.longitude); + + // 格式化位置信息 + const locationStr = `${this.geoLatitude.toFixed(6)},${this.geoLongitude.toFixed( + 6, + )}`; + this.form.leaveLocation = locationStr; + + this.addWechatLog( + `定位成功 - 纬度: ${this.geoLatitude}, 经度: ${this.geoLongitude}`, + 'success', + ); + this.addWechatLog(`位置精度: ${result.accuracy || '未知'}米`, 'info'); + + uni.hideLoading(); + uni.showToast({ + title: '微信定位成功', + icon: 'success', + duration: 2000, }); + + return true; + } else { + this.addWechatLog( + '微信定位返回数据异常', + 'error', + JSON.stringify(result, null, 2), + ); + return false; } - return false; } catch (error) { - console.error('微信JSSDK初始化失败:', error); + console.error('微信SDK定位失败:', error); + this.addWechatLog(`微信SDK定位失败: ${error.message}`, 'error'); + + // 根据错误类型提供更友好的提示 + if (error.message && error.message.includes('denied')) { + this.addWechatLog('用户拒绝了位置权限请求', 'warning'); + uni.showModal({ + title: '定位权限', + content: '需要获取位置信息来记录请假地点,请在微信中允许位置权限。', + showCancel: false, + confirmText: '知道了', + }); + } else if (error.message && error.message.includes('unavailable')) { + this.addWechatLog('位置服务不可用,可能是设备问题', 'warning'); + } else if (error.message && error.message.includes('timeout')) { + this.addWechatLog('定位超时,网络或信号问题', 'warning'); + } + return false; } }, + // 保留原有方法作为备用(标记为废弃) + async tryWeChatLocation() { + console.warn('tryWeChatLocation 方法已废弃,请使用 tryWeChatLocationWithSDK 方法'); + return this.tryWeChatLocationWithSDK(); + }, + // 一键定位:优先使用微信JSSDK,失败回退到uni.getLocation async handleGetWeChatLocation() { console.warn('handleGetWeChatLocation 方法已废弃,请使用 handleGetLocation 方法'); @@ -1539,6 +2217,229 @@ export default { return /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(fileName); }, + // === 新增图片处理方法 === + + // 图片预览 + handleImagePreview(image, index) { + console.log('图片预览:', image); + + if (image.status !== 'success') { + uni.showToast({ + title: '图片尚未上传完成', + icon: 'none', + }); + return; + } + + // 获取所有成功上传的图片URL + const imageUrls = this.imageFiles + .filter((file) => file.status === 'success') + .map((file) => file.blobUrl || file.url); + + if (imageUrls.length === 0) { + uni.showToast({ + title: '暂无可预览的图片', + icon: 'none', + }); + return; + } + + // 找到当前图片在成功列表中的索引 + const currentUrl = image.blobUrl || image.url; + const currentIndex = imageUrls.findIndex((url) => url === currentUrl); + + uni.previewImage({ + urls: imageUrls, + current: currentIndex >= 0 ? currentIndex : 0, + success: () => { + console.log('图片预览成功'); + }, + fail: (error) => { + console.error('图片预览失败:', error); + uni.showToast({ + title: '预览失败', + icon: 'none', + }); + }, + }); + }, + + // 图片下载 + async handleImageDownload(image) { + console.log('图片下载:', image); + + if (image.status !== 'success') { + uni.showToast({ + title: '图片尚未上传完成', + icon: 'none', + }); + return; + } + + uni.showLoading({ + title: '准备下载...', + mask: true, + }); + + try { + // 如果有ossId,通过API下载 + if (image.ossId) { + const downloadUrl = `/api/resource/oss/download/${image.ossId}`; + + // 在H5环境中,直接打开下载链接 + // #ifdef H5 + const link = document.createElement('a'); + link.href = downloadUrl; + link.download = image.name || 'image.jpg'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + uni.hideLoading(); + uni.showToast({ + title: '下载已开始', + icon: 'success', + }); + // #endif + + // #ifndef H5 + // 在小程序或App中,使用downloadFile + uni.downloadFile({ + url: downloadUrl, + success: (res) => { + if (res.statusCode === 200) { + uni.saveImageToPhotosAlbum({ + filePath: res.tempFilePath, + success: () => { + uni.showToast({ + title: '保存成功', + icon: 'success', + }); + }, + fail: () => { + uni.showToast({ + title: '保存失败', + icon: 'none', + }); + }, + }); + } + }, + fail: (error) => { + console.error('下载失败:', error); + uni.showToast({ + title: '下载失败', + icon: 'none', + }); + }, + complete: () => { + uni.hideLoading(); + }, + }); + // #endif + } else { + // 没有ossId的情况,直接使用图片URL + const imageUrl = image.blobUrl || image.url; + if (imageUrl) { + // #ifdef H5 + const link = document.createElement('a'); + link.href = imageUrl; + link.download = image.name || 'image.jpg'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + uni.hideLoading(); + uni.showToast({ + title: '下载已开始', + icon: 'success', + }); + // #endif + + // #ifndef H5 + uni.downloadFile({ + url: imageUrl, + success: (res) => { + if (res.statusCode === 200) { + uni.saveImageToPhotosAlbum({ + filePath: res.tempFilePath, + success: () => { + uni.showToast({ + title: '保存成功', + icon: 'success', + }); + }, + fail: () => { + uni.showToast({ + title: '保存失败', + icon: 'none', + }); + }, + }); + } + }, + fail: (error) => { + console.error('下载失败:', error); + uni.showToast({ + title: '下载失败', + icon: 'none', + }); + }, + complete: () => { + uni.hideLoading(); + }, + }); + // #endif + } + } + } catch (error) { + console.error('下载过程出错:', error); + uni.hideLoading(); + uni.showToast({ + title: '下载失败', + icon: 'none', + }); + } + }, + + // 图片删除 + handleImageDelete(image, index) { + console.log('删除图片:', image, index); + + uni.showModal({ + title: '确认删除', + content: '确定要删除这张图片吗?删除后无法恢复。', + confirmColor: '#ff5722', + success: (res) => { + if (res.confirm) { + // 从附件列表中删除 + const attachmentIndex = this.attachmentList.findIndex( + (file) => + file.uid === image.uid || + (file.ossId && file.ossId === image.ossId) || + (file.url && file.url === image.url), + ); + + if (attachmentIndex >= 0) { + this.attachmentList.splice(attachmentIndex, 1); + } + + // 清理blob URL,防止内存泄漏 + if (image.blobUrl && image.blobUrl.startsWith('blob:')) { + URL.revokeObjectURL(image.blobUrl); + } + + uni.showToast({ + title: '删除成功', + icon: 'success', + }); + + console.log('图片删除完成,当前附件数量:', this.attachmentList.length); + } + }, + }); + }, + // 获取文件扩展名 getFileExt(fileName) { if (!fileName) return 'FILE'; @@ -1567,19 +2468,145 @@ export default { return name.substring(0, 8) + '...' + ext; }, - // 文档上传方法 - handleDocUpload() { - console.log('=== 文档上传方法 ==='); + // 格式化文件大小 + formatFileSize(bytes) { + if (!bytes || bytes === 0) return '未知大小'; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + + if (i === 0) return bytes + ' ' + sizes[i]; + return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i]; + }, + + // 重试上传文件 + async retryUploadFile(file) { + console.log('=== 重试上传文件 ===', file); + + // 重置文件状态 + file.status = 'uploading'; + file.progress = 0; + + try { + // 根据文件来源选择不同的上传方式 + if (file.source === 'wechat' && file.localId) { + // 微信图片重新上传 + const uploadResult = await this.uploadWechatImageToOSS(file.localId); + + // 更新文件信息 + file.status = 'success'; + file.ossId = uploadResult.ossId; + file.url = uploadResult.url; + file.name = uploadResult.fileName || file.name; + file.size = uploadResult.fileSize || 0; + file.progress = 100; + } else { + // 普通文件重新上传 + await this.uploadFileToServer(file); + } + + uni.showToast({ + title: '重新上传成功', + icon: 'success', + duration: 1500, + }); + } catch (error) { + console.error('重试上传失败:', error); + file.status = 'error'; + file.progress = 0; + + uni.showToast({ + title: '重试失败,请稍后再试', + icon: 'none', + duration: 2000, + }); + } + }, + + // 统一附件上传方法(支持图片和文档) + handleDocUpload() { + console.log('=== 统一附件上传方法 ==='); + + // 检查文件数量限制 + if (this.attachmentList.length >= 15) { + uni.showToast({ + title: '最多只能上传15个文件', + icon: 'none', + }); + return; + } + + // 显示选择文件类型的操作菜单 + uni.showActionSheet({ + itemList: ['选择图片', '选择文档', '微信选择图片'], + success: (res) => { + switch (res.tapIndex) { + case 0: + this.chooseImages(); + break; + case 1: + this.chooseDocuments(); + break; + case 2: + if (this.isWechatBrowser) { + this.chooseWechatImage(); + } else { + uni.showToast({ + title: '请在微信中使用', + icon: 'none', + }); + } + break; + } + }, + fail: () => { + console.log('用户取消选择'); + }, + }); + }, + + // 选择图片文件 + chooseImages() { + uni.chooseImage({ + count: Math.min(9, 15 - this.attachmentList.length), + sizeType: ['original', 'compressed'], + sourceType: ['album', 'camera'], + success: (res) => { + console.log('选择图片成功:', res); + const files = res.tempFilePaths.map((path, index) => { + const tempFile = res.tempFiles?.[index]; + return { + name: path.split('/').pop() || `image_${Date.now()}_${index}.jpg`, + path: path, + url: path, + tempFilePath: path, + size: tempFile?.size || 0, + type: tempFile?.type || 'image/jpeg', + }; + }); + + this.handleFileUpload({ tempFiles: files }); + }, + fail: (err) => { + console.error('选择图片失败:', err); + uni.showToast({ + title: '选择图片失败', + icon: 'none', + }); + }, + }); + }, + + // 选择文档文件 + chooseDocuments() { // 使用uni.chooseFile选择文档 if (typeof uni.chooseFile === 'function') { uni.chooseFile({ - count: 5, + count: Math.min(5, 15 - this.attachmentList.length), extension: ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.txt', '.zip', '.rar'], success: (res) => { console.log('选择文档成功:', res); if (res.tempFiles && res.tempFiles.length > 0) { - // 手动触发文件上传 this.handleFileUpload({ tempFiles: res.tempFiles, }); @@ -1599,95 +2626,13 @@ export default { } }, - // 测试uview2 upload组件事件(开发调试用) - testUview2Upload() { - console.log('=== 测试uview2 upload组件事件 ==='); - - // 模拟uview2 upload组件的after-read事件参数 - const mockEvent = { - file: { - url: '/static/logo.png', - size: 1024, - name: 'test-image.png', - type: 'image/png', - }, - }; - - console.log('模拟的uview2事件参数:', mockEvent); - this.handleFileUpload(mockEvent); - }, - - // 直接选择文件(调试用) - handleDirectChooseFile() { - console.log('=== 直接选择文件 ==='); - uni.chooseFile({ - count: 5, - extension: [ - '.jpg', - '.jpeg', - '.png', - '.gif', - '.pdf', - '.doc', - '.docx', - '.xls', - '.xlsx', - '.txt', - ], - success: (res) => { - console.log('选择文件成功:', res); - if (res.tempFiles && res.tempFiles.length > 0) { - // 检查文件大小 - const validFiles = res.tempFiles.filter((file) => { - if (!this.checkFileSize(file)) { - return false; - } - return true; - }); - - if (validFiles.length > 0) { - // 手动触发文件上传 - this.handleFileUpload({ - tempFiles: validFiles, - }); - } else { - console.log('所有文件都超过大小限制,取消上传'); - } - } - }, - fail: (err) => { - console.error('选择文件失败:', err); - uni.showToast({ - title: '选择文件失败', - icon: 'none', - }); - }, - }); - }, - - // 测试上传逻辑 - handleTestUpload() { - console.log('=== 测试上传逻辑 ==='); - const testFile = { - name: 'test.txt', - size: 1024, - type: 'text/plain', - url: '/static/logo.png', // 使用一个存在的静态文件测试 - path: '/static/logo.png', - }; - - this.handleFileUpload({ - tempFiles: [testFile], - }); - }, - // 备用上传方法(用户可见) handleBackupUpload() { console.log('=== 备用上传方法 ==='); // 先检查平台支持情况 uni.showActionSheet({ - itemList: ['选择图片', '选择视频', '选择文档', '测试上传(开发用)'], + itemList: ['选择图片', '选择视频', '选择文档'], success: (res) => { if (res.tapIndex === 0) { // 选择图片 @@ -1769,160 +2714,16 @@ export default { }, }); } else if (res.tapIndex === 2) { - // 选择文档(优先使用uni.chooseFile) - if (uni.chooseFile) { - this.handleDirectChooseFile(); - } else { - uni.showToast({ - title: '当前平台不支持文档选择', - icon: 'none', - }); - } - } else if (res.tapIndex === 3) { - // 测试上传功能(开发调试用) - this.testDirectUpload(); + // 选择文档 + this.handleDocUpload(); } }, }); }, - // 测试直接上传(开发调试用) - async testDirectUpload() { - console.log('=== 测试直接上传功能 ==='); - - try { - // 使用 uni.uploadFile 直接测试上传 - const token = uni.getStorageSync('access_token'); - const clientid = uni.getStorageSync('clientid') || REQUEST_CONFIG.clientId; - - // 先选择一个文件用于测试 - uni.chooseImage({ - count: 1, - success: (res) => { - console.log('测试选择图片结果:', res); - const filePath = res.tempFilePaths[0]; - console.log('测试文件路径:', filePath); - - uni.showLoading({ title: '测试上传中...' }); - - uni.uploadFile({ - url: `${REQUEST_CONFIG.serverUrl}/resource/oss/upload`, - filePath: filePath, - name: 'file', - formData: { - bizType: 'student_leave_attachment', - }, - header: { - authorization: `Bearer ${token}`, - clientid: clientid, - }, - success: (uploadRes) => { - uni.hideLoading(); - console.log('测试上传成功:', uploadRes); - try { - const data = JSON.parse(uploadRes.data); - console.log('解析后的响应数据:', data); - if (data.code === 200) { - uni.showToast({ - title: '测试上传成功', - icon: 'success', - }); - } else { - uni.showToast({ - title: `测试失败: ${data.msg}`, - icon: 'none', - }); - } - } catch (e) { - console.error('解析响应失败:', e); - uni.showToast({ - title: '解析响应失败', - icon: 'none', - }); - } - }, - fail: (err) => { - uni.hideLoading(); - console.error('测试上传失败:', err); - uni.showToast({ - title: `测试上传失败: ${err.errMsg}`, - icon: 'none', - }); - }, - }); - }, - fail: (err) => { - console.error('测试选择图片失败:', err); - uni.showToast({ - title: '选择图片失败', - icon: 'none', - }); - }, - }); - } catch (error) { - console.error('测试上传出错:', error); - uni.showToast({ - title: '测试上传出错', - icon: 'none', - }); - } - }, - - // 文件选择完成事件(用于调试) - handleChooseComplete(event) { - console.log('=== 文件选择完成事件 ===', event); - console.log('选择的文件列表:', event.tempFilePaths || event.tempFiles); - - // 显示选择成功提示 - uni.showToast({ - title: `已选择 ${ - event.tempFilePaths?.length || event.tempFiles?.length || 0 - } 个文件`, - icon: 'success', - }); - }, - // 文件上传 handleFileUpload(event) { - console.log('=== u-upload组件after-read事件触发 ==='); - console.log('🎯 事件对象完整信息:', JSON.stringify(event, null, 2)); - console.log('🎯 事件类型:', typeof event); - console.log('🎯 事件属性列表:', Object.keys(event)); - - // 增强调试:检查事件结构 - if (event.file) { - console.log('✅ 检测到event.file:', event.file); - console.log('📁 file类型:', typeof event.file); - console.log('📁 file是否为数组:', Array.isArray(event.file)); - - if (Array.isArray(event.file)) { - console.log('📁 文件数组长度:', event.file.length); - event.file.forEach((file, index) => { - console.log(`📁 文件${index + 1}:`, { - name: file.name, - url: file.url, - path: file.path, - tempFilePath: file.tempFilePath, - size: file.size, - type: file.type, - }); - }); - } else { - console.log('📁 单个文件对象:', { - name: event.file.name, - url: event.file.url, - path: event.file.path, - tempFilePath: event.file.tempFilePath, - size: event.file.size, - type: event.file.type, - }); - } - } else { - console.warn('⚠️ event.file不存在,可能的原因:'); - console.warn('1. u-upload组件版本问题'); - console.warn('2. 事件参数结构改变'); - console.warn('3. 组件配置问题'); - } + console.log('文件上传事件触发:', event); // 显示用户提示 uni.showToast({ @@ -1931,24 +2732,6 @@ export default { duration: 2000, }); - console.log('=== 开始处理文件上传 ===', event); - console.log('事件类型:', typeof event); - console.log('事件属性:', Object.keys(event)); - - // 详细分析uview2 upload组件的事件结构 - if (event.file) { - console.log('检测到event.file:', event.file); - console.log('event.file类型:', typeof event.file); - console.log('event.file是否为数组:', Array.isArray(event.file)); - if (Array.isArray(event.file)) { - console.log('event.file数组长度:', event.file.length); - console.log('event.file第一个元素:', event.file[0]); - } else { - console.log('event.file单个对象:', event.file); - console.log('event.file属性:', Object.keys(event.file)); - } - } - // 兼容不同的事件格式 let files = []; @@ -1996,10 +2779,10 @@ export default { } // 检查文件数量限制 - if (this.attachmentList.length + files.length > 5) { + if (this.attachmentList.length + files.length > 15) { console.warn('⚠️ 文件数量超过限制'); uni.showToast({ - title: '最多只能上传5个文件', + title: '最多只能上传15个文件', icon: 'none', }); return; @@ -2095,25 +2878,6 @@ export default { }); }, - // 图片过大处理(uview2 upload组件回调) - handleImageOversize(event) { - console.log('=== 图片过大事件 ===', event); - console.log('文件大小信息:', event); - - // 显示更详细的错误信息 - let errorMessage = '图片大小不能超过10MB'; - if (event.size) { - const fileSizeMB = (event.size / (1024 * 1024)).toFixed(2); - errorMessage = `文件大小 ${fileSizeMB}MB,超过10MB限制`; - } - - uni.showToast({ - title: errorMessage, - icon: 'none', - duration: 3000, - }); - }, - // 检查文件大小是否超过限制 checkFileSize(file) { const maxSize = 10 * 1024 * 1024; // 10MB @@ -2136,64 +2900,6 @@ export default { return true; }, - // 测试审批记录弹窗(开发调试用) - testApprovalRecord() { - console.log('=== 测试审批记录弹窗 ==='); - console.log('直接调用审批记录组件'); - console.log('approvalRecordRef是否存在:', !!this.$refs.approvalRecordRef); - - // 使用测试ID直接调用 - const testId = this.form.id || '123'; - console.log('使用测试ID:', testId); - - if (this.$refs.approvalRecordRef) { - this.$refs.approvalRecordRef.init(testId); - } else { - console.error('审批记录组件引用不存在'); - uni.showToast({ - title: '组件引用不存在', - icon: 'none', - }); - } - }, - - // 测试文件大小检查功能 - testFileSizeCheck() { - console.log('=== 测试文件大小检查功能 ==='); - - const testFiles = [ - { name: '小文件.jpg', size: 1024 * 1024 }, // 1MB - { name: '中等文件.jpg', size: 5 * 1024 * 1024 }, // 5MB - { name: '大文件.jpg', size: 15 * 1024 * 1024 }, // 15MB - { name: '超大文件.jpg', size: 50 * 1024 * 1024 }, // 50MB - ]; - - testFiles.forEach((file, index) => { - console.log(`测试文件 ${index + 1}:`, file.name); - const isValid = this.checkFileSize(file); - console.log(`文件 ${file.name} 检查结果:`, isValid ? '通过' : '拒绝'); - }); - }, - - // 调试图片文件处理情况 - debugImageFiles() { - console.log('=== 调试图片文件处理情况 ==='); - console.log('attachmentList:', this.attachmentList); - console.log('imageFiles:', this.imageFiles); - console.log('uploadFileList:', this.uploadFileList); - - // 检查每个图片文件的详细信息 - this.imageFiles.forEach((file, index) => { - console.log(`图片文件 ${index + 1}:`, { - 文件名: file.name, - 文件类型: file.type, - 文件URL: file.url, - 是否为图片: this.isImageFile(file), - 文件对象: file, - }); - }); - }, - // 预加载图片以确保能正确显示 async preloadImages() { console.log('=== 预加载图片 ==='); @@ -2305,22 +3011,6 @@ export default { }); }, - // 图片文件删除(uview2 upload组件回调) - handleImageFileDelete(event) { - console.log('=== 图片文件删除事件 ===', event); - const { index } = event; - const imageFiles = this.attachmentList.filter((file) => this.isImageFile(file)); - const fileToDelete = imageFiles[index]; - if (fileToDelete) { - const actualIndex = this.attachmentList.findIndex( - (file) => file.uid === fileToDelete.uid, - ); - if (actualIndex > -1) { - this.attachmentList.splice(actualIndex, 1); - } - } - }, - // 文件删除(通用方法,保留作为备用) handleFileDelete(event) { console.log('=== 文件删除事件 ===', event); @@ -2359,10 +3049,23 @@ export default { } }, - // 文件下载 + // 文件下载(微信小程序专用版本) async handleDownloadFile(file) { - console.log('=== 下载文件 ===', file); + console.log('=== 开始下载文件 ==='); + console.log('文件信息:', JSON.stringify(file, null, 2)); + console.log('当前时间:', new Date().toISOString()); + + // 检查环境信息 + const systemInfo = uni.getSystemInfoSync(); + console.log('运行环境信息:', { + platform: systemInfo.platform, + version: systemInfo.version, + model: systemInfo.model, + brand: systemInfo.brand, + }); + if (!file.ossId) { + console.warn('❌ 下载失败:文件ossId为空', file); uni.showToast({ title: '文件未上传完成', icon: 'none', @@ -2371,64 +3074,336 @@ export default { } try { + console.log('📤 开始显示下载Loading...'); uni.showLoading({ - title: '正在下载...', + title: `正在下载${file.name}...`, + mask: true, }); - const response = await request({ - url: `/resource/oss/download/${file.ossId}`, - method: 'GET', - responseType: 'arraybuffer', - }); + // 构建下载URL + const downloadUrl = `/resource/oss/download/${file.ossId}`; + console.log('下载URL:', downloadUrl); - console.log('下载响应:', response); + // 根据文件类型选择处理方式 + console.log('🔍 检查文件类型:', file.name, '是否为图片:', this.isImageFile(file)); - // 将arraybuffer转换为base64 - const bytes = new Uint8Array(response); - let binary = ''; - bytes.forEach((byte) => (binary += String.fromCharCode(byte))); - const base64 = btoa(binary); + if (this.isImageFile(file)) { + // 图片文件:下载并保存到相册 + console.log('🖼️ 图片文件,下载并保存到相册'); + await this.downloadAndSaveImage(downloadUrl, file.name); + } else { + // 非图片文件:下载并直接打开预览 + console.log('📄 非图片文件,下载并打开预览'); + await this.downloadAndOpenDocument(downloadUrl, file.name); + } + } catch (error) { + console.error('❌ 下载过程发生异常:', error); + uni.hideLoading(); - // 保存文件到本地 - const fs = uni.getFileSystemManager(); - const fileName = file.name || `attachment_${Date.now()}`; - const filePath = `${uni.env.USER_DATA_PATH}/${fileName}`; + let errorMsg = '下载失败,请重试'; + if (error.message.includes('timeout')) { + errorMsg = '下载超时,请检查网络后重试'; + } else if (error.message.includes('network')) { + errorMsg = '网络错误,请检查网络连接'; + } else if (error.message.includes('404')) { + errorMsg = '文件不存在,请联系管理员'; + } else if (error.message.includes('500')) { + errorMsg = '服务器错误,请稍后重试'; + } - fs.writeFile({ - filePath: filePath, - data: base64, - encoding: 'base64', - success: () => { - uni.hideLoading(); - uni.showToast({ - title: '下载完成', - icon: 'success', - }); - // 可以选择打开文件 - uni.openDocument({ - filePath: filePath, - success: () => { - console.log('打开文件成功'); - }, - fail: (err) => { - console.error('打开文件失败:', err); - }, - }); + uni.showModal({ + title: '下载失败', + content: errorMsg, + showCancel: true, + cancelText: '取消', + confirmText: '重试', + success: (res) => { + if (res.confirm) { + this.handleDownloadFile(file); + } }, - fail: (err) => { - console.error('保存文件失败:', err); + }); + } + + console.log('=== handleDownloadFile方法执行完成 ==='); + }, + + // 下载图片并保存到相册 + async downloadAndSaveImage(downloadUrl, fileName) { + return new Promise((resolve, reject) => { + // #ifdef MP-WEIXIN + // 微信小程序:使用downloadFile下载到临时文件,然后保存到相册 + uni.downloadFile({ + url: this.getFullApiUrl(downloadUrl), + header: this.getRequestHeaders(), + success: (res) => { + console.log('✅ 图片下载成功:', res); + if (res.statusCode === 200) { + // 保存到相册 + uni.saveImageToPhotosAlbum({ + filePath: res.tempFilePath, + success: () => { + console.log('✅ 图片保存到相册成功'); + uni.hideLoading(); + uni.showToast({ + title: '图片已保存到相册', + icon: 'success', + duration: 2000, + }); + resolve(); + }, + fail: (err) => { + console.error('❌ 保存图片到相册失败:', err); + uni.hideLoading(); + if (err.errMsg.includes('auth deny')) { + uni.showModal({ + title: '权限提示', + content: + '需要授权访问相册才能保存图片,请在设置中开启权限', + showCancel: false, + }); + } else { + uni.showToast({ + title: '保存图片失败', + icon: 'none', + }); + } + reject(err); + }, + }); + } else { + uni.hideLoading(); + uni.showToast({ + title: '图片下载失败', + icon: 'none', + }); + reject(new Error('下载失败')); + } + }, + fail: (error) => { + console.error('❌ 图片下载失败:', error); uni.hideLoading(); uni.showToast({ - title: '下载失败', + title: '图片下载失败', icon: 'none', }); + reject(error); }, }); - } catch (error) { - console.error('下载文件失败:', error); + // #endif + + // #ifdef H5 + // H5环境:直接使用浏览器下载 + this.downloadFileInBrowser(downloadUrl, fileName); + uni.hideLoading(); + resolve(); + // #endif + }); + }, + + // 下载文档并打开预览 + async downloadAndOpenDocument(downloadUrl, fileName) { + return new Promise((resolve, reject) => { + // #ifdef MP-WEIXIN + // 微信小程序:下载到临时文件并直接打开 + uni.downloadFile({ + url: this.getFullApiUrl(downloadUrl), + header: this.getRequestHeaders(), + success: (res) => { + console.log('✅ 文档下载成功:', res); + if (res.statusCode === 200) { + uni.hideLoading(); + // 直接打开文档预览 + uni.openDocument({ + filePath: res.tempFilePath, + fileType: this.getFileType(fileName), + success: () => { + console.log('✅ 文档打开成功'); + resolve(); + }, + fail: (err) => { + console.error('❌ 文档打开失败:', err); + // 如果无法直接打开,提供其他选项 + uni.showModal({ + title: '无法预览', + content: '该文件格式不支持预览,您可以选择其他操作', + showCancel: true, + cancelText: '取消', + confirmText: '分享文件', + success: (modalRes) => { + if (modalRes.confirm) { + // 可以通过分享功能让用户保存文件 + // #ifdef MP-WEIXIN + wx.shareFileMessage && + wx.shareFileMessage({ + filePath: res.tempFilePath, + fileName: fileName, + }); + // #endif + } + }, + }); + reject(err); + }, + }); + } else { + uni.hideLoading(); + uni.showToast({ + title: '文档下载失败', + icon: 'none', + }); + reject(new Error('下载失败')); + } + }, + fail: (error) => { + console.error('❌ 文档下载失败:', error); + uni.hideLoading(); + uni.showToast({ + title: '文档下载失败', + icon: 'none', + }); + reject(error); + }, + }); + // #endif + + // #ifdef H5 + // H5环境:直接使用浏览器下载 + this.downloadFileInBrowser(downloadUrl, fileName); + uni.hideLoading(); + resolve(); + // #endif + }); + }, + + // 获取完整的API地址 + getFullApiUrl(url) { + if (url.startsWith('http')) { + return url; + } + // 从配置中获取base URL + const baseURL = + process.env.NODE_ENV === 'development' + ? 'http://localhost:8080' + : 'https://your-api-domain.com'; + return baseURL + url; + }, + + // 获取请求头(包含认证信息) + getRequestHeaders() { + const headers = {}; + // 添加认证token + const token = uni.getStorageSync('token'); + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + // 添加其他必要的请求头 + headers['Content-Type'] = 'application/json'; + return headers; + }, + + // 根据文件名获取文件类型 + getFileType(fileName) { + const ext = fileName.split('.').pop().toLowerCase(); + const typeMap = { + pdf: 'pdf', + doc: 'doc', + docx: 'docx', + xls: 'xls', + xlsx: 'xlsx', + ppt: 'ppt', + pptx: 'pptx', + txt: 'txt', + }; + return typeMap[ext] || ''; + }, + + // H5环境下的文件下载 + downloadFileInBrowser(downloadUrl, fileName) { + const link = document.createElement('a'); + link.href = this.getFullApiUrl(downloadUrl); + link.download = fileName; + link.target = '_blank'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }, + + // 预览图片文件 + previewImageFile(file) { + console.log('=== 预览图片文件 ===', file); + + // 收集所有成功上传的图片URL + const successImageFiles = this.attachmentList.filter( + (f) => this.isImageFile(f) && f.status === 'success' && f.url, + ); + + const imageUrls = successImageFiles.map((f) => f.blobUrl || f.url); + const currentIndex = successImageFiles.findIndex((f) => f.uid === file.uid); + + if (imageUrls.length > 0 && currentIndex >= 0) { + uni.previewImage({ + urls: imageUrls, + current: imageUrls[currentIndex], + longPressActions: { + itemList: ['保存到相册'], + success: (res) => { + if (res.tapIndex === 0) { + // 保存图片到相册 + this.saveImageToAlbum(file); + } + }, + }, + }); + } else { + uni.showToast({ + title: '图片加载中,请稍后', + icon: 'none', + }); + } + }, + + // 保存图片到相册 + async saveImageToAlbum(file) { + try { + uni.showLoading({ + title: '正在保存...', + }); + + // 下载图片到临时文件 + const tempFilePath = await new Promise((resolve, reject) => { + uni.downloadFile({ + url: file.url, + success: (res) => { + if (res.statusCode === 200) { + resolve(res.tempFilePath); + } else { + reject(new Error('下载失败')); + } + }, + fail: reject, + }); + }); + + // 保存到相册 + await new Promise((resolve, reject) => { + uni.saveImageToPhotosAlbum({ + filePath: tempFilePath, + success: resolve, + fail: reject, + }); + }); + uni.hideLoading(); uni.showToast({ - title: '下载失败', + title: '已保存到相册', + icon: 'success', + }); + } catch (error) { + console.error('保存图片失败:', error); + uni.hideLoading(); + uni.showToast({ + title: '保存失败', icon: 'none', }); } @@ -2859,398 +3834,36 @@ export default { } }, - // 测试审批记录弹窗(开发调试用) - testApprovalRecord() { - console.log('=== 测试审批记录弹窗 ==='); - console.log('直接调用审批记录组件'); - console.log('approvalRecordRef是否存在:', !!this.$refs.approvalRecordRef); + // // 文件选择前事件 - 应该在用户点击选择图片后首先触发 + // handleBeforeRead(event) { + // console.log('文件选择前事件触发:', event); + // uni.showToast({ + // title: '正在选择文件...', + // icon: 'loading', + // duration: 1000, + // }); + // return true; // 允许继续选择文件 + // }, - // 使用测试ID直接调用 - const testId = this.form.id || '123'; - console.log('使用测试ID:', testId); + // // 预览点击事件 + // handleClickPreview(event) { + // console.log('点击预览事件:', event); + // }, - if (this.$refs.approvalRecordRef) { - this.$refs.approvalRecordRef.init(testId); - } else { - console.error('审批记录组件引用不存在'); - uni.showToast({ - title: '组件引用不存在', - icon: 'none', - }); - } - }, + // // 上传进度事件 + // handleUploadProgress(event) { + // console.log('上传进度事件:', event); + // }, - // 测试上传事件(开发调试用)- 修复微信浏览器兼容性 - testUploadEvent() { - console.log('🧪 === 测试上传事件流程(微信浏览器兼容版本)==='); + // // 上传成功事件 + // handleUploadSuccess(event) { + // console.log('上传成功事件:', event); + // }, - // 检测当前环境 - const isWechat = /micromessenger/i.test(navigator.userAgent || ''); - console.log('📱 是否为微信浏览器:', isWechat); - - // 方法1:在微信浏览器中使用Canvas生成测试文件 - if (isWechat || true) { - // 统一使用Canvas方案,更稳定 - this.createTestFileWithCanvas(); - } else { - // 方法2:非微信环境使用原有的静态文件方案 - this.createMockFileEvent(); - } - }, - - // 使用Canvas创建真实的测试文件(微信浏览器兼容) - createTestFileWithCanvas() { - console.log('🎨 使用Canvas创建测试文件...'); - console.log('📱 当前环境:', { - isWeChat: isWeChat(), - userAgent: - typeof navigator !== 'undefined' - ? navigator.userAgent.substring(0, 50) - : 'unknown', - }); - - try { - // 创建Canvas元素 - const canvas = document.createElement('canvas'); - canvas.width = 300; - canvas.height = 200; - const ctx = canvas.getContext('2d'); - - // 绘制测试图片内容 - 增强版 - ctx.fillStyle = '#4CAF50'; - ctx.fillRect(0, 0, 300, 200); - - ctx.fillStyle = '#FFFFFF'; - ctx.font = '18px Arial'; - ctx.textAlign = 'center'; - ctx.fillText('微信环境测试图片', 150, 60); - ctx.fillText(new Date().toLocaleString(), 150, 90); - - // 添加环境标识 - if (isWeChat()) { - ctx.fillText('微信浏览器', 150, 120); - } else { - ctx.fillText('标准浏览器', 150, 120); - } - - // 添加版本标识 - ctx.font = '12px Arial'; - ctx.fillText(`V${Date.now()}`, 150, 150); - - console.log('✅ Canvas内容绘制完成'); - - // 将Canvas转换为Blob - canvas.toBlob( - (blob) => { - if (blob) { - console.log('✅ Canvas转换为Blob成功:', blob); - console.log('📊 Blob信息:', { - size: blob.size, - type: blob.type, - environment: isWeChat() ? 'WeChat' : 'Standard', - }); - - // 创建临时URL - const tempUrl = URL.createObjectURL(blob); - console.log('🔗 创建临时URL:', tempUrl); - - // 🔧 微信环境增强处理 - const fileName = `wechat-test-${Date.now()}.png`; - - // 模拟真实的文件选择结果 - const mockEvent = { - file: { - url: tempUrl, - size: blob.size, - name: fileName, - type: 'image/png', - path: tempUrl, - tempFilePath: tempUrl, - _blob: blob, // 保存原始blob引用 - 微信环境关键 - // 微信环境标识 - isWeChatTest: isWeChat(), - timestamp: Date.now(), - }, - }; - - console.log('🧪 Canvas生成的模拟事件参数:', mockEvent); - console.log('🧪 开始调用handleFileUpload方法...'); - - // 显示微信环境特定提示 - if (isWeChat()) { - uni.showToast({ - title: '微信环境测试中...', - icon: 'loading', - duration: 2000, - }); - } - - // 调用实际的文件上传处理方法 - this.handleFileUpload(mockEvent); - console.log('✅ Canvas测试调用成功'); - } else { - console.error('❌ Canvas转换为Blob失败'); - uni.showToast({ - title: isWeChat() ? '微信环境Canvas不支持' : 'Canvas转换失败', - icon: 'none', - duration: 3000, - }); - } - }, - 'image/png', - 0.9, // 提高质量 - ); - } catch (error) { - console.error('❌ Canvas测试失败:', error); - - let errorMsg = 'Canvas测试失败'; - if (isWeChat()) { - errorMsg = '微信环境Canvas异常'; - } - - uni.showToast({ - title: errorMsg + ': ' + error.message, - icon: 'none', - duration: 4000, - }); - - // 降级到触发真实选择 - console.log('🔄 降级到真实文件选择...'); - setTimeout(() => { - this.triggerRealFileSelection(); - }, 1000); - } - }, - - // 创建模拟文件事件(原有方案,用于非微信环境) - createMockFileEvent() { - console.log('📁 使用静态文件模拟方案(非微信环境)'); - - const mockEvent = { - file: { - url: '/static/logo.png', - size: 1024 * 50, // 50KB - name: 'test-static-image.png', - type: 'image/png', - path: '/static/logo.png', - tempFilePath: '/static/logo.png', - }, - }; - - console.log('🧪 静态文件模拟事件参数:', mockEvent); - - try { - this.handleFileUpload(mockEvent); - console.log('✅ 静态文件测试调用成功'); - } catch (error) { - console.error('❌ 静态文件测试调用失败:', error); - uni.showToast({ - title: '测试调用失败: ' + error.message, - icon: 'none', - duration: 3000, - }); - } - }, - - // 触发真实的文件选择(最终方案)- 微信环境优化版 - triggerRealFileSelection() { - console.log('📷 触发真实文件选择...'); - console.log('📱 微信环境状态:', { - isWeChat: isWeChat(), - userAgent: - typeof navigator !== 'undefined' - ? navigator.userAgent.includes('MicroMessenger') - : false, - }); - - // 微信环境特殊提示 - const actionItems = isWeChat() ? ['微信相册选择', '微信拍照'] : ['从相册选择', '拍照']; - - uni.showActionSheet({ - itemList: actionItems, - success: (res) => { - const sourceText = res.tapIndex === 0 ? '相册' : '拍照'; - console.log('用户选择了:', sourceText); - - // 微信环境显示特定加载提示 - if (isWeChat()) { - uni.showLoading({ - title: `微信${sourceText}处理中...`, - mask: true, - }); - } - - uni.chooseImage({ - count: 1, - sourceType: res.tapIndex === 0 ? ['album'] : ['camera'], - sizeType: ['original', 'compressed'], // 支持压缩以兼容微信 - success: (chooseRes) => { - console.log('✅ 真实文件选择成功:', chooseRes); - console.log('📊 选择结果详情:', { - tempFilePaths: chooseRes.tempFilePaths, - tempFiles: chooseRes.tempFiles, - environment: isWeChat() ? 'WeChat' : 'Standard', - }); - - if (isWeChat()) { - uni.hideLoading(); - } - - // 构建文件对象 - 微信环境增强 - const tempFile = chooseRes.tempFiles && chooseRes.tempFiles[0]; - const fileName = isWeChat() - ? `wechat-real-${Date.now()}.jpg` - : `real-test-${Date.now()}.jpg`; - - const realEvent = { - file: { - url: chooseRes.tempFilePaths[0], - size: tempFile ? tempFile.size : 0, - name: fileName, - type: tempFile ? tempFile.type || 'image/jpeg' : 'image/jpeg', - path: chooseRes.tempFilePaths[0], - tempFilePath: chooseRes.tempFilePaths[0], - // 微信环境标识 - isWeChatReal: isWeChat(), - source: sourceText, - timestamp: Date.now(), - }, - }; - - console.log('🧪 真实文件事件参数:', realEvent); - - // 显示成功提示 - uni.showToast({ - title: isWeChat() - ? '微信选择成功,开始上传...' - : '文件选择成功,开始上传...', - icon: 'success', - duration: 1500, - }); - - // 延迟调用,确保提示显示 - setTimeout(() => { - this.handleFileUpload(realEvent); - }, 500); - }, - fail: (err) => { - console.error('❌ 真实文件选择失败:', err); - - if (isWeChat()) { - uni.hideLoading(); - } - - // 微信环境特殊错误处理 - let errorMessage = '文件选择失败'; - if (isWeChat()) { - if (err.errMsg && err.errMsg.includes('cancel')) { - errorMessage = '用户取消了选择'; - } else if (err.errMsg && err.errMsg.includes('permission')) { - errorMessage = '请在微信中允许访问相册权限'; - } else { - errorMessage = '微信选择文件失败,请重试'; - } - } - - uni.showToast({ - title: errorMessage, - icon: 'none', - duration: 3000, - }); - }, - }); - }, - fail: (err) => { - console.error('❌ 显示选择菜单失败:', err); - - // 直接选择相册作为备用方案 - console.log('🔄 直接选择相册作为备用方案...'); - this.directChooseFromAlbum(); - }, - }); - }, - - // 直接从相册选择(备用方案) - directChooseFromAlbum() { - console.log('📱 直接从相册选择文件...'); - - uni.chooseImage({ - count: 1, - sourceType: ['album'], - sizeType: ['original', 'compressed'], - success: (chooseRes) => { - console.log('✅ 直接相册选择成功:', chooseRes); - - const tempFile = chooseRes.tempFiles && chooseRes.tempFiles[0]; - const realEvent = { - file: { - url: chooseRes.tempFilePaths[0], - size: tempFile ? tempFile.size : 0, - name: `direct-album-${Date.now()}.jpg`, - type: tempFile ? tempFile.type || 'image/jpeg' : 'image/jpeg', - path: chooseRes.tempFilePaths[0], - tempFilePath: chooseRes.tempFilePaths[0], - isDirect: true, - environment: isWeChat() ? 'WeChat' : 'Standard', - }, - }; - - this.handleFileUpload(realEvent); - }, - fail: (err) => { - console.error('❌ 直接相册选择失败:', err); - uni.showToast({ - title: '相册访问失败,请检查权限设置', - icon: 'none', - duration: 3000, - }); - }, - }); - }, - - // ======== 🔍 新增调试事件监听器 ======== - - // 文件选择前事件 - 应该在用户点击选择图片后首先触发 - handleBeforeRead(event) { - console.log('🟡 ===== @before-read 事件触发 ====='); - console.log('🟡 用户点击了选择图片按钮!'); - console.log('🟡 before-read事件参数:', event); - console.log('🟡 事件类型:', typeof event); - console.log('🟡 事件属性:', Object.keys(event || {})); - - // 显示用户提示 - uni.showToast({ - title: '正在选择文件...', - icon: 'loading', - duration: 1000, - }); - - return true; // 允许继续选择文件 - }, - - // 预览点击事件 - handleClickPreview(event) { - console.log('🔵 ===== @click-preview 事件触发 ====='); - console.log('🔵 点击预览事件参数:', event); - }, - - // 上传进度事件 - handleUploadProgress(event) { - console.log('🟣 ===== @progress 事件触发 ====='); - console.log('🟣 上传进度事件参数:', event); - }, - - // 上传成功事件 - handleUploadSuccess(event) { - console.log('🟢 ===== @success 事件触发 ====='); - console.log('🟢 上传成功事件参数:', event); - }, - - // 上传错误事件 - handleUploadError(event) { - console.log('🔴 ===== @error 事件触发 ====='); - console.log('🔴 上传错误事件参数:', event); - }, + // // 上传错误事件 + // handleUploadError(event) { + // console.log('上传错误事件:', event); + // }, }, }; @@ -3490,8 +4103,20 @@ export default { } .file-actions .u-button { - min-width: 60rpx; - padding: 8rpx 16rpx; + min-width: auto; + padding: 6rpx 10rpx; + font-size: 24rpx; +} + +.image-actions { + display: flex; + gap: 10rpx; +} + +.image-actions .u-button { + min-width: auto; + padding: 6rpx 10rpx; + font-size: 24rpx; } .upload-status { @@ -3536,4 +4161,534 @@ export default { align-items: center; gap: 16rpx; } + +.location-info { + margin-top: 10rpx; + padding: 10rpx; + background-color: #f8f9fa; + border-radius: 6rpx; + display: flex; + align-items: center; +} + +.location-coords { + font-size: 24rpx; + color: #666; +} + +/* 微信SDK调试样式 */ +.wechat-debug-section { + background: #fff; + border-radius: 12rpx; + padding: 30rpx; + margin-bottom: 20rpx; + border: 2rpx solid #2979ff; +} + +.debug-controls { + display: flex; + gap: 10rpx; + align-items: center; + margin-top: 10rpx; + flex-wrap: wrap; +} + +.sdk-status { + margin: 20rpx 0; + padding: 15rpx; + background: #f8f9fa; + border-radius: 8rpx; +} + +.status-item { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10rpx; +} + +.status-label { + font-size: 26rpx; + color: #666; +} + +.status-value { + font-size: 26rpx; + font-weight: 500; +} + +.status-value.success { + color: #52c41a; +} + +.status-value.error { + color: #f5222d; +} + +.debug-logs { + margin-top: 20rpx; + border: 2rpx solid #e8e8e8; + border-radius: 8rpx; + background: #fafafa; +} + +.log-content { + max-height: 400rpx; + padding: 15rpx; +} + +.log-item { + margin-bottom: 10rpx; + padding: 8rpx 12rpx; + border-radius: 6rpx; + font-size: 24rpx; + line-height: 1.4; +} + +.log-item.info { + background: #e6f7ff; + border-left: 4rpx solid #1890ff; +} + +.log-item.success { + background: #f6ffed; + border-left: 4rpx solid #52c41a; +} + +.log-item.warning { + background: #fffbe6; + border-left: 4rpx solid #faad14; +} + +.log-item.error { + background: #fff2f0; + border-left: 4rpx solid #f5222d; +} + +.log-time { + color: #999; + font-size: 22rpx; + margin-right: 10rpx; +} + +.log-text { + color: #333; +} + +.log-text.info { + color: #1890ff; +} + +.log-text.success { + color: #52c41a; +} + +.log-text.warning { + color: #faad14; +} + +.log-text.error { + color: #f5222d; +} + +.log-detail { + margin-top: 5rpx; + padding: 8rpx; + background: rgba(0, 0, 0, 0.03); + border-radius: 4rpx; +} + +.detail-text { + font-size: 20rpx; + color: #666; + font-family: monospace; + word-break: break-all; +} + +.empty-logs { + text-align: center; + color: #999; + padding: 40rpx 0; + font-size: 26rpx; +} + +/* 浮动调试按钮 */ +.debug-fab { + position: fixed; + right: 30rpx; + bottom: 200rpx; + width: 80rpx; + height: 80rpx; + background: #2979ff; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 4rpx 12rpx rgba(41, 121, 255, 0.3); + z-index: 999; +} + +.debug-fab:active { + background: #1976d2; + transform: scale(0.95); +} + +/* 微信上传区域样式 */ +.wechat-upload-section { + margin-bottom: 20rpx; + padding: 20rpx; + background: #f0f9ff; + border: 2rpx dashed #2979ff; + border-radius: 10rpx; +} + +.upload-progress { + margin-top: 15rpx; +} + +.progress-text { + font-size: 24rpx; + color: #666; + margin-top: 8rpx; + text-align: center; + display: block; +} + +.upload-tips { + margin-top: 20rpx; + padding: 15rpx; + background: #f8f9fa; + border-radius: 8rpx; + border-left: 4rpx solid #17a2b8; +} + +.tip-item { + display: block; + font-size: 24rpx; + color: #666; + line-height: 1.5; + margin-bottom: 8rpx; +} + +.wechat-tip { + color: #2979ff; + font-weight: 500; +} + +/* 完善的上传进度条样式 */ +.upload-progress { + margin-top: 20rpx; + padding: 20rpx; + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); + border-radius: 12rpx; + border: 1px solid #e1e8ed; + box-shadow: 0 2px 8px rgba(41, 121, 255, 0.1); +} + +.progress-text { + display: block; + text-align: center; + font-size: 26rpx !important; + color: #2979ff !important; + font-weight: 500; + margin-top: 12rpx; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0%, + 100% { + opacity: 0.8; + } + 50% { + opacity: 1; + } +} + +/* 自定义文件列表样式 */ +.custom-file-list { + margin-top: 20rpx; +} + +.file-title { + font-size: 28rpx; + font-weight: 600; + color: #333; + margin-bottom: 20rpx; + padding-left: 10rpx; + border-left: 4rpx solid #2979ff; +} + +.file-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280rpx, 1fr)); + gap: 20rpx; +} + +.file-card { + position: relative; + background: #fff; + border-radius: 12rpx; + overflow: hidden; + border: 2rpx solid #f0f0f0; + transition: all 0.3s ease; +} + +.file-card:active { + transform: scale(0.98); + border-color: #2979ff; +} + +.file-icon-container { + position: relative; + width: 100%; + height: 200rpx; + border-radius: 8rpx; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + background: #f8f9fa; +} + +.file-info-card { + position: absolute; + bottom: 8rpx; + left: 8rpx; + right: 8rpx; + display: flex; + justify-content: space-between; + align-items: center; +} + +.file-ext-badge { + background: rgba(255, 255, 255, 0.9); + color: #333; + font-size: 22rpx; + font-weight: 600; + padding: 4rpx 8rpx; + border-radius: 4rpx; + backdrop-filter: blur(4rpx); +} + +.file-size { + background: rgba(0, 0, 0, 0.6); + color: #fff; + font-size: 20rpx; + padding: 2rpx 6rpx; + border-radius: 4rpx; +} + +.file-actions { + display: flex; + gap: 6rpx; + padding: 8rpx 12rpx; + background: #fafafa; + border-top: 1rpx solid #f0f0f0; + flex-wrap: nowrap; + justify-content: space-between; +} + +.file-actions .u-button { + flex: 1; + min-width: 60rpx; + font-size: 24rpx !important; +} + +.file-name-display { + padding: 8rpx 12rpx; + background: #fff; + font-size: 22rpx; + color: #666; + text-align: center; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +/* 上传状态样式 */ +.upload-status { + display: flex; + align-items: center; + justify-content: center; + margin-top: 15rpx; + padding: 10rpx; + border-radius: 8rpx; + font-size: 24rpx; + font-weight: 500; +} + +.upload-status.success { + color: #19be6b; + background-color: rgba(25, 190, 107, 0.1); +} + +.upload-status.error { + color: #fa3534; + background-color: rgba(250, 53, 52, 0.1); +} + +.upload-progress-small { + width: 100%; + margin-bottom: 8rpx; +} + +.upload-text { + font-size: 22rpx; + color: #2979ff; + text-align: center; +} + +/* 图片展示区域样式 */ +.image-section { + margin-bottom: 30rpx; +} + +.image-title { + font-size: 28rpx; + font-weight: 600; + color: #333; + margin-bottom: 20rpx; + padding-left: 10rpx; + border-left: 4rpx solid #2979ff; +} + +.image-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280rpx, 1fr)); + gap: 20rpx; +} + +.image-item { + position: relative; + background: #fff; + border-radius: 12rpx; + overflow: hidden; + border: 2rpx solid #f0f0f0; + transition: all 0.3s ease; +} + +.image-item:active { + transform: scale(0.98); + border-color: #2979ff; +} + +.image-container { + position: relative; + width: 100%; + height: 200rpx; + border-radius: 8rpx; + overflow: hidden; +} + +.attachment-image { + width: 100%; + height: 100%; + object-fit: cover; + background-color: #f5f5f5; +} + +.upload-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.6); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: #fff; +} + +.upload-text { + font-size: 24rpx; + margin-top: 8rpx; + font-weight: 500; +} + +.error-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(250, 53, 52, 0.8); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: #fff; +} + +.error-text { + font-size: 24rpx; + margin-top: 8rpx; + font-weight: 500; +} + +.success-badge { + position: absolute; + top: 8rpx; + right: 8rpx; + background-color: rgba(255, 255, 255, 0.9); + border-radius: 50%; + padding: 4rpx; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1); +} + +.image-actions { + display: flex; + gap: 6rpx; + padding: 8rpx 12rpx; + background: #fafafa; + border-top: 1rpx solid #f0f0f0; + flex-wrap: nowrap; + justify-content: space-between; +} + +.image-actions .u-button { + flex: 1; + min-width: 60rpx; + font-size: 24rpx !important; +} + +.image-name { + padding: 8rpx 12rpx; + background: #fff; + font-size: 22rpx; + color: #666; + text-align: center; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +/* 响应式适配 */ +@media screen and (max-width: 750rpx) { + .file-grid { + grid-template-columns: repeat(auto-fill, minmax(240rpx, 1fr)); + gap: 15rpx; + } + + .file-icon-container { + height: 160rpx; + } + + .image-grid { + grid-template-columns: repeat(auto-fill, minmax(240rpx, 1fr)); + gap: 15rpx; + } + + .image-container { + height: 160rpx; + } + + .file-actions .u-button, + .image-actions .u-button { + font-size: 22rpx !important; + padding: 6rpx 12rpx; + } +}