From e5a8d7e322c6ac6bfda61bec171c835685ce86f4 Mon Sep 17 00:00:00 2001 From: hb Date: Sun, 27 Jul 2025 21:56:11 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AD=BE=E5=88=B0?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/base/server.ts | 8 +- src/config.ts | 8 +- src/pages.json | 18 + src/pages/view/notice/index.vue | 20 +- .../view/routine/JiaoXueZiYuan/index.vue | 16 + .../view/routine/JiaoXueZiYuan/indexList.vue | 914 +++++++++++++++--- src/pages/view/routine/qd/confirm.vue | 142 ++- src/pages/view/routine/qd/qr-code.vue | 30 +- src/utils/filePreview.ts | 419 ++++++++ src/utils/request/index.ts | 2 +- 10 files changed, 1357 insertions(+), 220 deletions(-) create mode 100644 src/utils/filePreview.ts diff --git a/src/api/base/server.ts b/src/api/base/server.ts index 1cc16a9..6c2c56f 100644 --- a/src/api/base/server.ts +++ b/src/api/base/server.ts @@ -163,9 +163,13 @@ export const typesFindTreeApi = async () => { export const resourcesFindPageApi = async (params: any) => { return await get("/api/resources/findPage", params); }; -// 获取资源分页 + +export const resourcesSaveApi = async (params: any) => { + return await post("/api/resources/save", params); +}; + export const resourcesAddNumByTypeApi = async (params: any) => { - return await post("/api/resources/addNumByType", params); + return await post("/api/resources/addNumByType", params); }; // 获取工作量 diff --git a/src/config.ts b/src/config.ts index 6a814b0..a58b5aa 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,7 +1,7 @@ const ip: string = "127.0.0.1:8897"; // const ip: string = "yufangzc.com"; -// const ip: string = "yufangzc.com"; -const fwqip: string = "yufangzc.com"; +// const ip: string = "lzcxsx.cn"; +const fwqip: string = "lzcxsx.cn"; //打包服务器接口代理标识 const SERVERAGENT: string = "/jsd-api"; //本地代理url地址,配置了就启动代理,没配置就不启动代理 @@ -13,7 +13,9 @@ export const BASE_URL: string = export const BASE_WS_URL: string = `wss://${ip}`; //图片地址 // export const BASE_IMAGE_URL: string = process.env.NODE_ENV == "development" ? `https://${ip}` : `https://${fwqip}`; -export const BASE_IMAGE_URL = `http://${fwqip}`; +export const BASE_IMAGE_URL = `https://${fwqip}`; +// kkFileView预览服务地址 +export const KK_FILE_VIEW_URL: string = `http://${fwqip}:8891`; //存token的key export const AUTH_KEY: string = "satoken"; //token过期返回状态码 diff --git a/src/pages.json b/src/pages.json index 36dccee..087006a 100644 --- a/src/pages.json +++ b/src/pages.json @@ -51,6 +51,24 @@ "backgroundColor": "#ffffff" } }, + { + "path": "pages/system/web-viewer/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "文件预览", + "enablePullDownRefresh": false, + "backgroundColor": "#f5f5f5" + } + }, + { + "path": "pages/system/video-player/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "视频播放", + "enablePullDownRefresh": false, + "backgroundColor": "#000000" + } + }, { "path": "pages/system/updatePopup/updatePopup", "style": { diff --git a/src/pages/view/notice/index.vue b/src/pages/view/notice/index.vue index 7361f24..9eb2b05 100644 --- a/src/pages/view/notice/index.vue +++ b/src/pages/view/notice/index.vue @@ -27,20 +27,20 @@ >范围: {{ data.njmc + data.bjmc }} - - diff --git a/src/pages/view/routine/JiaoXueZiYuan/index.vue b/src/pages/view/routine/JiaoXueZiYuan/index.vue index 34324d5..49d5a3c 100644 --- a/src/pages/view/routine/JiaoXueZiYuan/index.vue +++ b/src/pages/view/routine/JiaoXueZiYuan/index.vue @@ -111,6 +111,22 @@ onMounted(async () => { const res = await typesFindTreeApi(); typeTree.value = res.result || []; console.log('教学资源页面加载完成', res); + + // 默认选择语文科目 + if (typeTree.value.length > 0) { + // 查找语文科目 + const chineseSubject = typeTree.value.find(item => + item.title && (item.title.includes('语文') || item.title.includes('语文')) + ); + + if (chineseSubject) { + // 选择语文科目 + selectType(chineseSubject); + } else { + // 如果没找到语文,选择第一个科目 + selectType(typeTree.value[0]); + } + } }); diff --git a/src/pages/view/routine/JiaoXueZiYuan/indexList.vue b/src/pages/view/routine/JiaoXueZiYuan/indexList.vue index 0ef3ccf..cb6d996 100644 --- a/src/pages/view/routine/JiaoXueZiYuan/indexList.vue +++ b/src/pages/view/routine/JiaoXueZiYuan/indexList.vue @@ -16,40 +16,265 @@ + + + + + + + {{ modalTitle }} + + + + + + + + + 资源目录 * + + + + {{ getResourceTypeText() || '请选择资源目录' }} + + + + + + + + + + + 课题名称 * + + + + + + + + 课时 * + + + + + + + + 资源类别 * + + + + {{ getCategoryText() || '请选择资源类别' }} + + + + + + + + + + + 资源描述 + + + + + + + + 上传资源 * + + + + 点击或拖拽文件到此区域上传 + 支持 .doc、.docx、.pdf、.ppt、.pptx 等格式 + + + + {{ formData.fileName }} + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pages/view/routine/qd/confirm.vue b/src/pages/view/routine/qd/confirm.vue index 9379db6..e36d011 100644 --- a/src/pages/view/routine/qd/confirm.vue +++ b/src/pages/view/routine/qd/confirm.vue @@ -115,7 +115,7 @@ 二维码信息 二维码有效期: - 60秒 + {{ qrExpireTime }}秒 当前时间: @@ -126,6 +126,38 @@ + + + + + + 您已签到成功 + 您已经完成本次签到,请勿重复签到 + 如需重新签到,请联系会议组织者 + + + 会议信息 + + 会议名称: + {{ meetingInfo?.qdmc || '未设置' }} + + + 会议时间: + {{ formatTime(meetingInfo?.qdkstime) }} - {{ formatTime(meetingInfo?.qdjstime) }} + + + 会议地点: + {{ meetingInfo?.qdwz || '未设置' }} + + + 签到时间: + {{ formatTime(qdzxRecord?.qdwctime) }} + + + + + + @@ -139,11 +171,20 @@ import { qdFindByIdApi, qdzxSignInApi, qdzxFindByQdParamsApi, qdzxFindByQdAndJsA import { useUserStore } from '@/store/modules/user'; // 页面参数 +const options = ref({}); + +// 页面加载时接收参数 +onLoad((params) => { + options.value = params; +}); + +// 签到相关变量 const qdId = ref(''); const qdzxId = ref(''); +const qrExpireTime = ref(60); // 默认60秒 // 当前步骤 -const currentStep = ref<'sign' | 'confirm' | 'success' | 'notInList' | 'timeExpired' | 'qrExpired'>('confirm'); +const currentStep = ref<'sign' | 'confirm' | 'success' | 'notInList' | 'timeExpired' | 'qrExpired' | 'alreadySigned'>('confirm'); // 用户信息 const userInfo = ref(null); @@ -163,21 +204,26 @@ const showSignature = ref(false); // 获取用户store const userStore = useUserStore(); -onLoad(async (options) => { - if (options?.qdId) { - qdId.value = options.qdId; +// 页面初始化 +onMounted(async () => { + // 获取签到ID + if (options.value?.qdId) { + qdId.value = options.value.qdId; + } else { + uni.showToast({ title: '参数错误', icon: 'none' }); + return; } - if (options?.qdzxId) { - qdzxId.value = options.qdzxId; + + // 获取二维码过期时间参数 + if (options.value?.rqgqtime) { + qrExpireTime.value = parseInt(options.value.rqgqtime) || 60; } // 第一步:获取缓存的教师信息 const jsData = userStore.getJs; if (jsData && Object.keys(jsData).length > 0) { userInfo.value = jsData; - console.log('获取到教师信息:', jsData); } else { - console.error('未找到教师信息,请先登录'); uni.showToast({ title: '请先登录', icon: 'none' }); // 跳转到登录页面 setTimeout(() => { @@ -189,21 +235,13 @@ onLoad(async (options) => { } // 验证二维码时间戳是否过期 - if (options?.timestamp) { - const qrTimestamp = parseInt(options.timestamp); + if (options.value?.timestamp) { + const qrTimestamp = parseInt(options.value.timestamp); const currentTimestamp = Date.now(); const timeDiff = currentTimestamp - qrTimestamp; - const maxValidTime = 60 * 1000; // 60秒有效期 - - console.log('二维码时间戳验证:', { - qrTimestamp, - currentTimestamp, - timeDiff, - maxValidTime - }); + const maxValidTime = qrExpireTime.value * 1000; // 转换为毫秒 if (timeDiff > maxValidTime) { - console.error('二维码已过期'); currentStep.value = 'qrExpired'; return; } @@ -220,23 +258,24 @@ const checkTeacherSignInPermission = async () => { qdId: qdId.value, jsId: userInfo.value.id }); - + if (result && result.resultCode === 1 && result.result) { // 找到签到记录 qdzxRecord.value = result.result; qdzxId.value = result.result.id; - console.log('找到教师签到记录:', result.result); // 第三步:获取签到详情,验证是否需要签名 await loadMeetingInfo(); - } else { - // 没有找到签到记录,显示"不在签到名单中"界面 + } else if (result && result.resultCode === 0) { + // 后端明确返回未找到记录 currentStep.value = 'notInList'; await loadMeetingInfo(); // 仍然加载会议信息用于显示 + } else { + // 其他错误情况 + uni.showToast({ title: '查询签到权限失败', icon: 'none' }); } } catch (error) { - console.error('查询教师签到权限失败:', error); - uni.showToast({ title: '查询签到权限失败', icon: 'none' }); + uni.showToast({ title: '网络异常,请重试', icon: 'none' }); } }; @@ -259,6 +298,13 @@ const loadMeetingInfo = async () => { // 只有在找到qdzxId时才检查是否需要签名 if (qdzxId.value) { + // 检查是否已经签到 + if (qdzxRecord.value?.qdStatus === '1') { + // 已经签到,跳转到重复签到界面 + currentStep.value = 'alreadySigned'; + return; + } + // 检查是否需要签名 if (meetingInfo.value?.mdqz === '1') { // 需要签名,调用签名组件 @@ -270,7 +316,6 @@ const loadMeetingInfo = async () => { } } } catch (error) { - console.error('加载会议信息失败:', error); uni.showToast({ title: '加载会议信息失败', icon: 'none' }); } }; @@ -292,7 +337,6 @@ const handleSignature = async () => { currentStep.value = 'confirm'; } catch (error) { showSignature.value = false; - console.error('签名失败:', error); uni.showToast({ title: '签名失败', icon: 'none' }); } }; @@ -310,24 +354,15 @@ const submitSignIn = async () => { params.signatureImage = sign_file.value; } - console.log('提交签到参数:', JSON.stringify(params, null, 2)); - console.log('参数类型检查:', { - qdzxId: typeof params.qdzxId, - signInTime: typeof params.signInTime, - signatureImage: typeof params.signatureImage - }); - const result = await qdzxSignInApi(params); - console.log('签到结果:', result); if (result && result.resultCode === 1) { currentStep.value = 'success'; } else { - const errorMsg = result?.msg || result?.message || '签到失败'; + const errorMsg = result?.message || '签到失败'; uni.showToast({ title: errorMsg, icon: 'none' }); } } catch (error) { - console.error('签到失败:', error); uni.showToast({ title: '网络异常,请重试', icon: 'none' }); } }; @@ -627,4 +662,35 @@ const goBack = () => { } } } + +/* 重复签到阶段 */ +.already-signed-step { + background: white; + border-radius: 16px; + padding: 40px 24px; + margin-top: 40px; + text-align: center; +} + +.already-signed-content { + .already-signed-title { + display: block; + font-size: 24px; + font-weight: 600; + color: #333; + margin: 16px 0 8px; + } + + .already-signed-subtitle { + font-size: 14px; + color: #666; + margin-bottom: 16px; + } + + .already-signed-tip { + font-size: 14px; + color: #999; + margin-bottom: 24px; + } +} \ No newline at end of file diff --git a/src/pages/view/routine/qd/qr-code.vue b/src/pages/view/routine/qd/qr-code.vue index eda6df9..e75441a 100644 --- a/src/pages/view/routine/qd/qr-code.vue +++ b/src/pages/view/routine/qd/qr-code.vue @@ -57,7 +57,7 @@ 使用说明 - 1. 二维码有效期为60秒,请及时扫描 + 1. 二维码有效期为{{ qrExpireTime }}秒,请及时扫描 2. 使用微信扫一扫功能扫描二维码 3. 扫描后将跳转到签到确认页面 4. 请确保网络连接正常 @@ -74,6 +74,7 @@ import { qdFindByIdApi, generateQRCodeApi } from '@/api/base/server'; // 页面参数 const qdId = ref(''); +const qrExpireTime = ref(60); // 默认60秒 // 会议信息 const meetingInfo = ref(null); @@ -88,6 +89,10 @@ onLoad((options) => { if (options?.qdId) { qdId.value = options.qdId; } + + // 启动默认倒计时,等待二维码数据解析后更新 + startTimer(); + loadMeetingInfo(); }); @@ -118,14 +123,24 @@ const initQRCode = async () => { if (result && result.resultCode === 1) { // 确保二维码数据是字符串格式 const qrData = result.result || ''; - console.log('二维码数据:', qrData); + + // 从二维码数据中解析rqgqtime参数 + const urlParams = new URLSearchParams(qrData.split('?')[1] || ''); + const rqgqtime = urlParams.get('rqgqtime'); + if (rqgqtime) { + const expireTime = parseInt(rqgqtime) || 60; + qrExpireTime.value = expireTime; + qrTimer.value = expireTime; + // 重新启动倒计时 + startTimer(); + } + generateQRCodeImage(qrData); } else { throw new Error('生成二维码失败'); } - // 启动倒计时 - startTimer(); + // 倒计时已在onLoad中启动,这里不需要重复启动 } catch (error) { console.error('生成二维码失败:', error); uni.showToast({ title: '生成二维码失败', icon: 'none' }); @@ -171,7 +186,6 @@ const generateQRCodeImage = (qrData: string) => { qrCanvas.drawImage(url, 0, 0, qrSize.value, qrSize.value); qrCanvas.draw(); - console.log('二维码生成成功'); }); }).catch((error) => { console.error('加载qrcode库失败:', error); @@ -186,8 +200,6 @@ const generateQRCodeImage = (qrData: string) => { qrCanvas.draw(); }); - console.log('二维码数据:', qrData); - } catch (error) { console.error('生成二维码异常:', error); // 异常处理,显示错误信息 @@ -204,7 +216,7 @@ const generateQRCodeImage = (qrData: string) => { // 启动倒计时 const startTimer = () => { - qrTimer.value = 60; + qrTimer.value = qrExpireTime.value; if (qrTimerInterval) { clearInterval(qrTimerInterval); } @@ -224,6 +236,8 @@ const startTimer = () => { const refreshQRCode = async () => { try { await initQRCode(); + // 重新启动倒计时 + startTimer(); uni.showToast({ title: '二维码已刷新', icon: 'success' }); } catch (error) { uni.showToast({ title: '刷新失败', icon: 'none' }); diff --git a/src/utils/filePreview.ts b/src/utils/filePreview.ts new file mode 100644 index 0000000..751cde3 --- /dev/null +++ b/src/utils/filePreview.ts @@ -0,0 +1,419 @@ +/** + * 文件预览工具函数 + * 提供跨平台的文件预览功能 + */ + +import { KK_FILE_VIEW_URL } from '@/config'; + +// 文件类型判断 +export const isVideo = (fileType: string): boolean => { + const videoTypes = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv', '3gp', 'm4v']; + return videoTypes.includes(fileType.toLowerCase()); +}; + +export const isImage = (fileType: string): boolean => { + const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']; + return imageTypes.includes(fileType.toLowerCase()); +}; + +// 检查是否是压缩包文件 +export const isCompressedFile = (fileName: string): boolean => { + const compressedExtensions = ['zip', 'rar', '7z', 'tar', 'gz', 'bz2']; + const extension = fileName.split('.').pop()?.toLowerCase(); + return compressedExtensions.includes(extension || ''); +}; + +// 检查文件是否可以预览 +export const canPreview = (fileName: string): boolean => { + const previewableExtensions = [ + 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', + 'txt', 'rtf', 'odt', 'ods', 'odp', + 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', + 'mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', + 'mp3', 'wav', 'ogg', 'aac', 'm4a' + ]; + const extension = fileName.split('.').pop()?.toLowerCase(); + return previewableExtensions.includes(extension || ''); +}; + +// 获取文件图标 +export const getFileIcon = (fileName: string): string => { + const extension = fileName.split('.').pop()?.toLowerCase(); + + // 压缩包文件显示下载图标 + if (isCompressedFile(fileName)) { + return '📥'; + } + + switch (extension) { + case 'pdf': + return '📄'; + case 'doc': + case 'docx': + return '📝'; + case 'xls': + case 'xlsx': + return '📊'; + case 'ppt': + case 'pptx': + return '📽️'; + case 'txt': + return '📃'; + case 'jpg': + case 'jpeg': + case 'png': + case 'gif': + case 'bmp': + case 'webp': + return '🖼️'; + case 'mp4': + case 'avi': + case 'mov': + case 'wmv': + case 'flv': + case 'webm': + return '🎥'; + case 'mp3': + case 'wav': + case 'ogg': + case 'aac': + case 'm4a': + return '🎵'; + default: + return '📄'; + } +}; + +// 获取文件类型名称 +export const getFileTypeName = (fileName: string): string => { + const extension = fileName.split('.').pop()?.toLowerCase(); + + // 压缩包文件 + if (isCompressedFile(fileName)) { + switch (extension) { + case 'zip': + return 'ZIP压缩包'; + case 'rar': + return 'RAR压缩包'; + case '7z': + return '7Z压缩包'; + case 'tar': + return 'TAR压缩包'; + case 'gz': + return 'GZ压缩包'; + case 'bz2': + return 'BZ2压缩包'; + default: + return '压缩包'; + } + } + + switch (extension) { + case 'pdf': + return 'PDF文档'; + case 'doc': + case 'docx': + return 'Word文档'; + case 'xls': + case 'xlsx': + return 'Excel表格'; + case 'ppt': + case 'pptx': + return 'PowerPoint演示'; + case 'txt': + return '文本文件'; + case 'jpg': + case 'jpeg': + case 'png': + case 'gif': + case 'bmp': + case 'webp': + return '图片文件'; + case 'mp4': + case 'avi': + case 'mov': + case 'wmv': + case 'flv': + case 'webm': + return '视频文件'; + case 'mp3': + case 'wav': + case 'ogg': + case 'aac': + case 'm4a': + return '音频文件'; + default: + return '未知文件'; + } +}; + +// 跨平台文件预览 +export const previewFile = (fileUrl: string, fileName: string, fileType: string) => { + return new Promise((resolve, reject) => { + const systemInfo = uni.getSystemInfoSync(); + const { platform } = systemInfo; + const type = fileType.toLowerCase(); + + console.log('=== 文件预览调试信息 ==='); + console.log('平台:', platform); + console.log('系统信息:', systemInfo); + console.log('原始文件URL:', fileUrl); + console.log('文件名:', fileName); + console.log('文件类型:', fileType); + + // 检查是否是压缩包文件,如果是则直接下载 + if (isCompressedFile(fileName)) { + console.log('=== 检测到压缩包文件,直接下载 ==='); + uni.showToast({ title: '压缩包文件不支持预览,将为您下载', icon: 'none' }); + downloadFile(fileUrl, fileName).then(resolve).catch(resolve); + return; + } + + // 检查是否支持预览的文件格式 + const canPreviewType = canPreview(fileName); + + if (!canPreviewType) { + // 不支持预览的文件格式,直接下载 + console.log('不支持预览的文件格式,执行下载'); + uni.showToast({ title: '该文件格式暂不支持预览,将为您下载', icon: 'none' }); + downloadFile(fileUrl, fileName).then(resolve).catch(resolve); + return; + } + + // 检查URL是否已经是kkFileView生成的压缩包内文件URL + const isCompressedFileUrl = fileUrl.includes('kkCompressfileKey') && fileUrl.includes('kkCompressfilepath'); + + console.log('=== 压缩包内文件检测 ==='); + console.log('URL包含kkCompressfileKey:', fileUrl.includes('kkCompressfileKey')); + console.log('URL包含kkCompressfilepath:', fileUrl.includes('kkCompressfilepath')); + console.log('是否为压缩包内文件URL:', isCompressedFileUrl); + + if (isCompressedFileUrl) { + console.log('=== 压缩包内文件处理 ==='); + console.log('检测到压缩包内文件URL,直接使用'); + console.log('原始压缩包内文件URL:', fileUrl); + + const previewUrl = fileUrl; // 直接使用这个URL,不重新编码 + console.log('最终预览URL (压缩包内文件):', previewUrl); + + const needLandscape = ['ppt', 'pptx'].includes(type); + console.log('是否需要横屏显示:', needLandscape); + + const targetPageUrl = `/pages/system/web-viewer/index?url=${encodeURIComponent(previewUrl)}&title=${encodeURIComponent(fileName)}&landscape=${needLandscape ? '1' : '0'}`; + console.log('跳转目标页面URL:', targetPageUrl); + + uni.navigateTo({ + url: targetPageUrl, + success: () => { + console.log('跳转到压缩包内文件预览页面成功'); + resolve(true); + }, + fail: (err) => { + console.error('跳转到压缩包内文件预览页面失败:', err); + console.log('错误详情:', JSON.stringify(err)); + uni.showToast({ title: '预览加载失败,尝试下载', icon: 'none' }); + setTimeout(() => { + downloadFile(fileUrl, fileName).then(resolve).catch(resolve); + }, 1000); + } + }); + return; // 退出函数 + } + + console.log('=== 普通文件处理 ==='); + + // 处理普通文件预览 + const finalFileUrl = fileUrl; + console.log('DEBUG: 最终用于编码的URL (finalFileUrl):', finalFileUrl); + + // 使用kkFileView进行预览 + const encodedUrl = btoa(finalFileUrl); + console.log('使用kkFileView预览,Base64编码后的URL:', encodedUrl); + + const previewUrl = `http://yufangzc.com:8891/onlinePreview?url=${encodeURIComponent(encodedUrl)}`; + console.log('最终预览URL (普通文件):', previewUrl); + + const needLandscape = ['ppt', 'pptx'].includes(type); + console.log('是否需要横屏显示:', needLandscape); + + const targetPageUrl = `/pages/system/web-viewer/index?url=${encodeURIComponent(previewUrl)}&title=${encodeURIComponent(fileName)}&landscape=${needLandscape ? '1' : '0'}`; + console.log('跳转目标页面URL:', targetPageUrl); + + uni.navigateTo({ + url: targetPageUrl, + success: () => { + console.log('跳转到预览页面成功'); + resolve(true); + }, + fail: (err) => { + console.error('跳转到预览页面失败:', err); + console.log('错误详情:', JSON.stringify(err)); + uni.showToast({ title: '预览加载失败,尝试下载', icon: 'none' }); + setTimeout(() => { + downloadFile(fileUrl, fileName).then(resolve).catch(resolve); + }, 1000); + } + }); + }); +}; + +// 下载文件 +export const downloadFile = (fileUrl: string, fileName: string) => { + const { platform } = uni.getSystemInfoSync(); + + if (platform === 'web') { + return downloadForWeb(fileUrl, fileName); + } else { + return downloadForNative(fileUrl, fileName); + } +}; + +// H5平台下载 +const downloadForWeb = (url: string, filename: string) => { + return new Promise((resolve, reject) => { + fetch(url) + .then((response) => response.blob()) + .then((blob) => { + const downloadUrl = window.URL.createObjectURL(new Blob([blob])); + const link = document.createElement('a'); + link.href = downloadUrl; + link.setAttribute('download', filename); + document.body.appendChild(link); + link.click(); + link.remove(); + window.URL.revokeObjectURL(downloadUrl); + uni.showToast({ title: "开始下载", icon: "success" }); + resolve(true); + }) + .catch((err) => { + console.error("H5下载失败:", err); + uni.showToast({ title: "下载失败", icon: "none" }); + reject(err); + }); + }); +}; + +// 原生平台下载 +const downloadForNative = (url: string, filename: string) => { + return new Promise((resolve, reject) => { + uni.showLoading({ title: "下载中..." }); + + uni.downloadFile({ + url: url, + success: async (res) => { + if (res.statusCode === 200) { + try { + const saved = await saveFile(res.tempFilePath, filename); + if (saved) { + // 尝试打开文件 + uni.openDocument({ + filePath: saved, + success: () => { + uni.showToast({ title: "文件已打开", icon: "success" }); + resolve(true); + }, + fail: (err) => { + console.warn('文件打开失败:', err); + uni.showToast({ title: "文件已下载,但无法打开", icon: "none" }); + resolve(true); // 下载成功,即使打开失败也算成功 + } + }); + } else { + reject(new Error('保存失败')); + } + } catch (error) { + console.error('处理下载文件时出错:', error); + reject(error); + } + } else { + uni.showToast({ title: "下载失败", icon: "none" }); + reject(new Error('下载失败')); + } + }, + fail: (err) => { + console.error("下载失败:", err); + uni.showToast({ title: "下载失败", icon: "none" }); + reject(err); + }, + complete: () => uni.hideLoading() + }); + }); +}; + +// 保存文件 +const saveFile = (tempPath: string, filename: string): Promise => { + return new Promise((resolve) => { + try { + // 检查是否支持文件系统管理器 + if (typeof uni.getFileSystemManager === 'function') { + const fs = uni.getFileSystemManager(); + const appPath = `${filename}`; + fs.rename({ + oldPath: tempPath, + newPath: appPath, + success: () => resolve(appPath), + fail: () => { + console.warn('文件重命名失败,使用临时路径'); + resolve(tempPath); + } + }); + } else { + // 不支持文件系统管理器时,直接使用临时路径 + console.warn('当前平台不支持文件系统管理器,使用临时路径'); + resolve(tempPath); + } + } catch (error) { + console.error('保存文件时出错:', error); + uni.showToast({ title: "保存失败", icon: "none" }); + resolve(null); + } + }); +}; + +// 视频预览 +export const previewVideo = (videoUrl: string, videoTitle: string) => { + console.log('=== 视频预览调试信息 ==='); + console.log('视频URL:', videoUrl); + console.log('视频标题:', videoTitle); + + return new Promise((resolve) => { + const targetUrl = `/pages/system/video-player/index?url=${encodeURIComponent(videoUrl)}&title=${encodeURIComponent(videoTitle)}`; + console.log('跳转目标URL:', targetUrl); + + uni.navigateTo({ + url: targetUrl, + success: () => { + console.log('跳转到视频播放页面成功'); + resolve(true); + }, + fail: (err) => { + console.error('跳转到视频播放页面失败:', err); + console.log('错误详情:', JSON.stringify(err)); + + // 跳转失败时尝试使用系统播放器 + uni.showToast({ + title: '跳转失败,尝试使用系统播放器', + icon: 'none', + duration: 2000 + }); + + // 延迟后尝试下载视频 + setTimeout(() => { + downloadFile(videoUrl, videoTitle + '.mp4').then(resolve).catch(resolve); + }, 2000); + } + }); + }); +}; + +// 图片预览 +export const previewImage = (imageUrl: string) => { + return new Promise((resolve) => { + uni.previewImage({ + urls: [imageUrl], + current: imageUrl, + success: resolve, + fail: resolve + }); + }); +}; \ No newline at end of file diff --git a/src/utils/request/index.ts b/src/utils/request/index.ts index e859b8b..91f935f 100644 --- a/src/utils/request/index.ts +++ b/src/utils/request/index.ts @@ -89,7 +89,7 @@ export function get( options ) ).then((res) => { - if (res.resultCode == 1) { + if (res.resultCode == 1 || res.resultCode == 0) { resolve(res); } else { if (res.resultCode) { From 2e74e9db08e127525fcacde33f00bc3d1e0b8418 Mon Sep 17 00:00:00 2001 From: hb Date: Sun, 27 Jul 2025 21:57:06 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E9=A2=84=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/system/video-player/index.vue | 159 ++++++++++++++++++++++++ src/pages/system/web-viewer/index.vue | 111 +++++++++++++++++ 2 files changed, 270 insertions(+) create mode 100644 src/pages/system/video-player/index.vue create mode 100644 src/pages/system/web-viewer/index.vue diff --git a/src/pages/system/video-player/index.vue b/src/pages/system/video-player/index.vue new file mode 100644 index 0000000..7ba44f3 --- /dev/null +++ b/src/pages/system/video-player/index.vue @@ -0,0 +1,159 @@ + + + + + \ No newline at end of file diff --git a/src/pages/system/web-viewer/index.vue b/src/pages/system/web-viewer/index.vue new file mode 100644 index 0000000..2b3e578 --- /dev/null +++ b/src/pages/system/web-viewer/index.vue @@ -0,0 +1,111 @@ + + + + + \ No newline at end of file