From f0c85a60f99f642e707d401751ceb919dcb899bd Mon Sep 17 00:00:00 2001 From: hebo Date: Mon, 20 Oct 2025 11:51:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=99=E7=A0=94=E6=88=90=E6=9E=9C=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/base/jycgApi.ts | 45 + .../ImageVideoUpload/ImageVideoUpload.vue | 242 +++-- src/components/ImageVideoUpload/types.ts | 9 +- src/pages.json | 14 + src/pages/base/service/index.vue | 8 + src/pages/view/routine/jiaoyan/addMember.vue | 2 +- .../routine/jiaoyan/cg/achievementForm.vue | 882 ++++++++++++++++++ .../routine/jiaoyan/cg/achievementList.vue | 832 +++++++++++++++++ src/pages/view/routine/jiaoyan/detail.vue | 2 +- src/pages/view/routine/jiaoyan/index.vue | 16 +- .../view/routine/kefuxuncha/kyXkkcDetail.vue | 4 +- .../view/routine/kefuxuncha/xcXkkcDetail.vue | 4 +- .../view/routine/yishiyice/addMember.vue | 2 +- src/pages/view/routine/yishiyice/detail.vue | 2 +- src/static/base/home/jycg.png | Bin 0 -> 8507 bytes src/static/base/view/image.png | Bin 0 -> 8191 bytes src/static/base/view/rgzn.png | Bin 0 -> 12612 bytes 17 files changed, 1979 insertions(+), 85 deletions(-) create mode 100644 src/api/base/jycgApi.ts create mode 100644 src/pages/view/routine/jiaoyan/cg/achievementForm.vue create mode 100644 src/pages/view/routine/jiaoyan/cg/achievementList.vue create mode 100644 src/static/base/home/jycg.png create mode 100644 src/static/base/view/image.png create mode 100644 src/static/base/view/rgzn.png diff --git a/src/api/base/jycgApi.ts b/src/api/base/jycgApi.ts new file mode 100644 index 0000000..d348562 --- /dev/null +++ b/src/api/base/jycgApi.ts @@ -0,0 +1,45 @@ +import { get, post } from "@/utils/request"; + +/** + * 获取教研成果列表 + */ +export function jycgFindPageApi(params: any) { + return get('/api/jycg/findPage', params); +} + +/** + * 获取教研成果详情 + */ +export function jycgFindByIdApi(params: any) { + return get('/api/jycg/findById', params); +} + +/** + * 保存教研成果(新增/修改) + */ +export function jycgSaveApi(params: any) { + return post('/api/jycg/save', params); +} + +/** + * 删除教研成果 + */ +export function jycgDeleteApi(params: any) { + return post('/api/jycg/delete', params); +} + +/** + * 批量删除教研成果 + */ +export function jycgBatchDeleteApi(params: any) { + return post('/api/jycg/batchDelete', params); +} + +/** + * 上传文件 + */ +export function jycgUploadFileApi(file: File) { + const formData = new FormData(); + formData.append('file', file); + return post('/api/jycg/upload', formData); +} diff --git a/src/components/ImageVideoUpload/ImageVideoUpload.vue b/src/components/ImageVideoUpload/ImageVideoUpload.vue index fe38088..0c593a7 100644 --- a/src/components/ImageVideoUpload/ImageVideoUpload.vue +++ b/src/components/ImageVideoUpload/ImageVideoUpload.vue @@ -72,7 +72,7 @@ - {{ video.name }} + {{ video.originalName || video.name }} {{ formatFileSize(video.size) }} @@ -103,10 +103,14 @@ > - + - {{ file.name }} + {{ file.originalName || file.name }} {{ formatFileSize(file.size) }} {{ file.extension?.toUpperCase() || 'FILE' }} @@ -160,6 +164,7 @@ interface FileItem { tempPath?: string url?: string name?: string + originalName?: string type?: string size?: number extension?: string @@ -433,40 +438,43 @@ const checkVideoSize = async (filePath: string): Promise => { // 选择图片 const chooseImage = () => { + // 使用 chooseImage 支持拍照和相册选择 uni.chooseImage({ count: props.maxImageCount - imageList.value.length, - sizeType: ['original'], - sourceType: ['album', 'camera'], + sourceType: ['camera', 'album'], // 支持拍照和相册 + sizeType: ['original', 'compressed'], // 可以选择原图或压缩图 success: async (res) => { - const tempFilePaths = res.tempFilePaths as string[] - - showLoading('压缩图片中...') - - try { - const compressedImages = [] + const tempFilePaths = res.tempFilePaths + if (Array.isArray(tempFilePaths) && tempFilePaths.length > 0) { + showLoading('压缩图片中...') - // 并行压缩所有图片 - const compressPromises = tempFilePaths.map(async (path, index) => { - try { - showLoading(`压缩图片中... (${index + 1}/${tempFilePaths.length})`) - - const compressedPath = await smartCompressImage(path) - - return { - tempPath: compressedPath, - name: path.split('/').pop() || 'image.jpg', - originalPath: path, - isCompressed: true + try { + const compressedImages = [] + + // 并行压缩所有图片 + const compressPromises = tempFilePaths.map(async (filePath: string, index) => { + try { + showLoading(`压缩图片中... (${index + 1}/${tempFilePaths.length})`) + + const compressedPath = await smartCompressImage(filePath) + + return { + tempPath: compressedPath, + name: filePath.split('/').pop() || 'image.jpg', + originalName: filePath.split('/').pop() || 'image.jpg', + originalPath: filePath, + isCompressed: true + } + } catch (error) { + console.error(`图片 ${filePath} 压缩失败:`, error) + return { + tempPath: filePath, + name: filePath.split('/').pop() || 'image.jpg', + originalName: filePath.split('/').pop() || 'image.jpg', + isCompressed: false + } } - } catch (error) { - console.error(`图片 ${path} 压缩失败:`, error) - return { - tempPath: path, - name: path.split('/').pop() || 'image.jpg', - isCompressed: false - } - } - }) + }) const results = await Promise.all(compressPromises) compressedImages.push(...results) @@ -496,8 +504,9 @@ const chooseImage = () => { showToast({ title: '图片处理失败', icon: 'none' }) console.error('图片处理失败:', error) } + } }, - fail: (error) => { + fail: (error: any) => { console.error('选择图片失败:', error) showToast({ title: '选择图片失败', icon: 'none' }) } @@ -534,6 +543,7 @@ const chooseVideo = () => { const newVideo = { tempPath: tempFilePath, name: tempFilePath.split('/').pop() || 'video.mp4', + originalName: res.name || 'video.mp4', // 保存原始文件名 duration: res.duration, size: res.size } @@ -584,18 +594,29 @@ const chooseFile = () => { // 确定文件类型 let fileType = 'document' - if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'].includes(fileExtension)) { + if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg', 'tiff', 'ico'].includes(fileExtension)) { fileType = 'image' - } else if (['mp4', 'mov', 'avi', 'wmv', 'flv'].includes(fileExtension)) { + } else if (['mp4', 'mov', 'avi', 'wmv', 'flv', 'mkv', 'webm', '3gp', 'm4v'].includes(fileExtension)) { fileType = 'video' - } else if (['mp3', 'wav', 'aac', 'ogg'].includes(fileExtension)) { + } else if (['mp3', 'wav', 'aac', 'ogg', 'flac', 'm4a', 'wma'].includes(fileExtension)) { fileType = 'audio' + } else if (['pdf', 'doc', 'docx', 'txt', 'rtf', 'wps'].includes(fileExtension)) { + fileType = 'document' + } else if (['xls', 'xlsx', 'csv', 'et'].includes(fileExtension)) { + fileType = 'spreadsheet' + } else if (['ppt', 'pptx', 'dps'].includes(fileExtension)) { + fileType = 'presentation' + } else if (['zip', 'rar', '7z', 'tar', 'gz'].includes(fileExtension)) { + fileType = 'archive' + } else if (['html', 'htm', 'css', 'js', 'json', 'xml'].includes(fileExtension)) { + fileType = 'code' } // 创建文件项 const fileItem: FileItem = { tempPath: fileInfo.path, name: fileName, + originalName: fileName, // 保存原始文件名 type: fileType, size: fileInfo.size, extension: fileExtension, @@ -634,15 +655,29 @@ const uploadFile = async (fileItem: FileItem, index: number) => { if (!props.uploadApi || !fileItem.tempPath) return try { + // 触发上传开始事件 + emit('upload-progress', 'file', 1, 1) + const result = await props.uploadApi(fileItem.tempPath) if (result && result.resultCode === 1 && result.result && result.result.length > 0) { const uploadedFile = result.result[0] - // 更新文件项 - fileItem.url = imagUrl(uploadedFile.filePath) + // 更新文件项 - 直接使用相对路径,不添加域名前缀 + fileItem.url = uploadedFile.filePath + // 如果服务器返回了文件名,更新name字段,但保留originalName + if (uploadedFile.fileName) { + fileItem.name = uploadedFile.fileName + } fileItem.tempPath = undefined // 清除临时路径 + // 同步更新 fileList 中的对应项 + if (fileList.value[index]) { + fileList.value[index].url = fileItem.url + fileList.value[index].name = fileItem.name + fileList.value[index].tempPath = undefined + } + // 触发成功事件 emit('file-upload-success', fileItem, index) @@ -703,32 +738,92 @@ const removeFile = (index: number) => { // 获取文件图标 const getFileIcon = (extension: string): string => { - const iconMap: { [key: string]: string } = { - // 文档 - 'pdf': 'book', - 'doc': 'book', - 'docx': 'book', - 'txt': 'book', - // 表格 - 'xls': 'table', - 'xlsx': 'table', - // 演示文稿 - 'ppt': 'slideshow', - 'pptx': 'slideshow', - // 音频 - 'mp3': 'mic', - 'wav': 'mic', - 'aac': 'mic', - 'ogg': 'mic', + const type = extension.toLowerCase(); + switch (type) { + // PDF文档 + case 'pdf': + return '/static/base/view/pdf.png'; + + // Word文档 + case 'doc': + case 'docx': + case 'rtf': + case 'wps': // WPS文字 + return '/static/base/view/word.png'; + + // Excel表格 + case 'xls': + case 'xlsx': + case 'csv': + case 'et': // WPS表格 + return '/static/base/view/excel.png'; + + // PowerPoint演示 + case 'ppt': + case 'pptx': + case 'dps': // WPS演示 + return '/static/base/view/ppt.png'; + // 压缩文件 - 'zip': 'folder', - 'rar': 'folder', - '7z': 'folder', - // 其他 - 'default': 'paperclip' + case 'zip': + case 'rar': + case '7z': + case 'tar': + case 'gz': + return '/static/base/view/zip.png'; + + // 图片文件 + case 'jpg': + case 'jpeg': + case 'png': + case 'gif': + case 'bmp': + case 'webp': + case 'svg': + case 'tiff': + case 'ico': + return '/static/base/view/image.png'; + + // 视频文件 + case 'mp4': + case 'avi': + case 'mov': + case 'wmv': + case 'flv': + case 'mkv': + case 'webm': + case '3gp': + case 'm4v': + return '/static/base/view/video.png'; + + // 音频文件 + case 'mp3': + case 'wav': + case 'aac': + case 'ogg': + case 'flac': + case 'm4a': + case 'wma': + return '/static/base/view/audio.png'; + + // 文本文件 + case 'txt': + case 'md': + return '/static/base/view/text.png'; + + // 代码文件 + case 'html': + case 'htm': + case 'css': + case 'js': + case 'json': + case 'xml': + return '/static/base/view/code.png'; + + // 默认文件 + default: + return '/static/base/view/more.png'; } - - return iconMap[extension.toLowerCase()] || iconMap['default'] } // 上传图片 @@ -753,10 +848,14 @@ const uploadImages = async (images: ImageItem[]) => { if (uploadResult && uploadResult.resultCode === 1 && uploadResult.result && uploadResult.result.length > 0) { const serverPath = uploadResult.result[0].filePath - // 更新图片对象 + // 更新图片对象 - 直接使用相对路径,不添加域名前缀 const index = imageList.value.findIndex(img => img.tempPath === image.tempPath) if (index !== -1) { - imageList.value[index].url = imagUrl(serverPath) + imageList.value[index].url = serverPath + // 如果服务器返回了文件名,更新name字段,但保留originalName + if (uploadResult.result[0].fileName) { + imageList.value[index].name = uploadResult.result[0].fileName + } delete imageList.value[index].tempPath delete imageList.value[index].originalPath } @@ -821,10 +920,14 @@ const uploadVideos = async (videos: VideoItem[]) => { if (uploadResult && uploadResult.resultCode === 1 && uploadResult.result && uploadResult.result.length > 0) { const serverPath = uploadResult.result[0].filePath - // 更新视频对象 + // 更新视频对象 - 直接使用相对路径,不添加域名前缀 const index = videoList.value.findIndex(v => v.tempPath === video.tempPath) if (index !== -1) { - videoList.value[index].url = imagUrl(serverPath) + videoList.value[index].url = serverPath + // 如果服务器返回了文件名,更新name字段,但保留originalName + if (uploadResult.result[0].fileName) { + videoList.value[index].name = uploadResult.result[0].fileName + } delete videoList.value[index].tempPath } @@ -941,7 +1044,7 @@ defineExpose({ }) - diff --git a/src/pages/view/routine/jiaoyan/cg/achievementList.vue b/src/pages/view/routine/jiaoyan/cg/achievementList.vue new file mode 100644 index 0000000..de97f0d --- /dev/null +++ b/src/pages/view/routine/jiaoyan/cg/achievementList.vue @@ -0,0 +1,832 @@ + + + + + diff --git a/src/pages/view/routine/jiaoyan/detail.vue b/src/pages/view/routine/jiaoyan/detail.vue index 2813adc..cfe329c 100644 --- a/src/pages/view/routine/jiaoyan/detail.vue +++ b/src/pages/view/routine/jiaoyan/detail.vue @@ -655,7 +655,7 @@ const deleteMember = (member: MemberItem) => { }; -