From 86f3a4bd93cf4fdc1e53ae80a7078fdf119afc75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=82=E7=B3=96?= Date: Sat, 12 Jul 2025 10:37:32 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E4=B8=AD=E5=BF=83=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/common/upload.js | 190 ++++- pages/workbench/task/components/TaskList.vue | 375 +++++++++ pages/workbench/task/create.vue | 283 ++++++- pages/workbench/task/reportTaskDetail.vue | 431 +++++++++- pages/workbench/task/task.vue | 828 +++++++------------ 5 files changed, 1537 insertions(+), 570 deletions(-) create mode 100644 pages/workbench/task/components/TaskList.vue diff --git a/api/common/upload.js b/api/common/upload.js index 1abaefd..9e4b7e3 100644 --- a/api/common/upload.js +++ b/api/common/upload.js @@ -1,4 +1,5 @@ import { getToken } from '@/util/auth' +import request from "@/util/oaRequest" const BASE_URL = 'http://110.41.139.73:8080' @@ -34,4 +35,191 @@ export function uploadImage(filePath) { } }) }) -} \ No newline at end of file +} + +/** + * 文件上传API + * @param {Object} file - 文件对象 + * @param {string} file.path - 文件路径 + * @param {string} file.name - 文件名 + * @param {number} file.size - 文件大小 + * @param {string} file.type - 文件类型 + * @param {Object} options - 上传选项 + * @param {Array} options.allowedTypes - 允许的文件类型扩展名数组 + * @param {number} options.maxSize - 最大文件大小(MB) + * @param {number} options.extraData - 额外数据,默认为1 + * @returns {Promise} 返回上传结果 { ossId, url, fileName, originalName } + */ +export function uploadFile(file, options = {}) { + const { + allowedTypes = ['doc', 'xls', 'ppt', 'txt', 'pdf', 'docx', 'xlsx', 'png', 'jpg', 'jpeg', 'zip', 'rar', '7z', 'dwg'], + maxSize = 200, // 默认200MB + extraData = 1 + } = options + + return new Promise((resolve, reject) => { + // 文件类型验证 + const ext = file.name.split('.').pop().toLowerCase() + if (allowedTypes && !allowedTypes.includes(ext)) { + reject(new Error(`文件格式不正确,请上传${allowedTypes.join('/')}格式文件!`)) + return + } + + // 文件大小验证 + if (maxSize && file.size / 1024 / 1024 > maxSize) { + reject(new Error(`上传文件大小不能超过 ${maxSize} MB!`)) + return + } + + // 验证文件路径 + if (!file.path) { + reject(new Error('文件路径为空')) + return + } + + console.log('[uploadFile] 开始上传文件:', { + name: file.name, + size: file.size, + path: file.path, + type: file.type + }) + + // 处理文件路径(安卓平台) + let filePath = file.path + // #ifdef APP-PLUS + if (typeof plus !== 'undefined' && plus.io) { + if (!filePath.startsWith('/') && !filePath.startsWith('file://')) { + filePath = plus.io.convertLocalFileSystemURL(filePath) + } + } + // #endif + + uni.uploadFile({ + url: BASE_URL + '/system/oss/upload', + filePath, + name: 'file', + formData: { + isPublic: extraData + }, + header: { + 'Authorization': 'Bearer ' + getToken() + }, + success: (res) => { + console.log('[uploadFile] 上传响应:', res) + try { + const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data + if (data.code === 200) { + console.log('[uploadFile] 上传成功:', data.data) + resolve({ + ossId: data.data.ossId, + url: data.data.url, + fileName: data.data.fileName, + originalName: file.name + }) + } else { + console.error('[uploadFile] 上传失败:', data) + reject(data.msg || '上传失败') + } + } catch (e) { + console.error('[uploadFile] 解析返回数据失败:', e, res.data) + reject('上传返回格式错误') + } + }, + fail: (err) => { + console.error('[uploadFile] 上传接口调用失败:', err) + reject(err) + } + }) + }) +} + +/** + * 批量上传文件 + * @param {Array} files - 文件数组 + * @param {Object} options - 上传选项 + * @returns {Promise} 返回上传结果数组 + */ +export function uploadFiles(files, options = {}) { + const { + allowedTypes = ['doc', 'xls', 'ppt', 'txt', 'pdf', 'docx', 'xlsx', 'png', 'jpg', 'jpeg', 'zip', 'rar', '7z', 'dwg'], + maxSize = 200, + extraData = 1, + onProgress = null + } = options + + return new Promise((resolve, reject) => { + if (!files || files.length === 0) { + resolve([]) + return + } + + const results = [] + let completedCount = 0 + let hasError = false + + files.forEach((file, index) => { + uploadFile(file, { allowedTypes, maxSize, extraData }) + .then(result => { + results[index] = result + completedCount++ + + // 调用进度回调 + if (onProgress) { + onProgress({ + index, + file, + result, + progress: (completedCount / files.length) * 100 + }) + } + + // 所有文件上传完成 + if (completedCount === files.length && !hasError) { + resolve(results) + } + }) + .catch(error => { + hasError = true + console.error(`文件 ${file.name} 上传失败:`, error) + reject(new Error(`文件 "${file.name}" 上传失败: ${error.message || error}`)) + }) + }) + }) +} + +/** + * 根据OSS ID列表获取文件信息 + * @param {Array} ossIds - OSS ID数组 + * @returns {Promise} 返回文件信息数组 + */ +// 查询OSS对象基于id串 +export function listByIds(ossId) { + return request({ + url: '/system/oss/listByIds/' + ossId, + method: 'get' + }) +} + +/** + * 根据OSS ID列表获取文件信息 + * @param {Array} ossIds - OSS ID数组 + * @returns {Promise} 返回文件信息数组 + */ +export function getFilesByIds(ossIds) { + if (!ossIds || ossIds.length === 0) { + return Promise.resolve([]) + } + + const ossIdString = Array.isArray(ossIds) ? ossIds.join(',') : ossIds + + return request({ + url: '/system/oss/listByIds/' + ossIdString, + method: 'get' + }).then(response => { + if (response.code === 200) { + return response.data || [] + } else { + throw new Error(response.msg || '获取文件信息失败') + } + }) +} \ No newline at end of file diff --git a/pages/workbench/task/components/TaskList.vue b/pages/workbench/task/components/TaskList.vue new file mode 100644 index 0000000..a13712a --- /dev/null +++ b/pages/workbench/task/components/TaskList.vue @@ -0,0 +1,375 @@ + + + + + \ No newline at end of file diff --git a/pages/workbench/task/create.vue b/pages/workbench/task/create.vue index d0758b6..9ca3b33 100644 --- a/pages/workbench/task/create.vue +++ b/pages/workbench/task/create.vue @@ -228,6 +228,7 @@ + 选择文件 + { + const processedFiles = res.tempFiles.map(file => ({ + name: file.name, + size: file.size, + path: file.path || file.tempFilePath, + type: file.type + })) + + // 验证文件 + const validFiles = this.validateFiles(processedFiles) + if (validFiles.length > 0) { + this.fileList = [...this.fileList, ...validFiles] + } + }, + fail: (err) => { + console.error('选择文件失败:', err) + uni.showToast({ + title: '选择文件失败', + icon: 'none' + }) + } + }) + return + } + + // H5平台使用 uni.chooseFile + // #ifdef H5 uni.chooseFile({ count: 5, type: 'all', success: (res) => { - this.fileList = [...this.fileList, ...res.tempFiles] + const processedFiles = res.tempFiles.map(file => ({ + name: file.name, + size: file.size, + path: file.path || file.tempFilePath || file.url, + type: file.type + })) + + // 验证文件 + const validFiles = this.validateFiles(processedFiles) + if (validFiles.length > 0) { + this.fileList = [...this.fileList, ...validFiles] + } + }, + fail: (err) => { + console.error('选择文件失败:', err) + uni.showToast({ + title: '选择文件失败', + icon: 'none' + }) } }) + // #endif + + // 其他平台提示不支持 + // #ifndef APP-PLUS || H5 || MP-WEIXIN + uni.showToast({ + title: '当前平台不支持文件选择', + icon: 'none' + }) + // #endif }, // 移除文件 @@ -424,6 +495,139 @@ export default { this.fileList.splice(index, 1) }, + // 根据文件名获取文件类型 + getFileType(fileName) { + const ext = fileName.split('.').pop().toLowerCase() + const typeMap = { + 'jpg': 'image/jpeg', + 'jpeg': 'image/jpeg', + 'png': 'image/png', + 'gif': 'image/gif', + 'bmp': 'image/bmp', + 'webp': 'image/webp', + 'pdf': 'application/pdf', + 'doc': 'application/msword', + 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'xls': 'application/vnd.ms-excel', + 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'ppt': 'application/vnd.ms-powerpoint', + 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'txt': 'text/plain', + 'zip': 'application/zip', + 'rar': 'application/x-rar-compressed', + '7z': 'application/x-7z-compressed' + } + return typeMap[ext] || 'application/octet-stream' + }, + + // 执行文件选择 + doPickFiles() { + try { + plus.io.pickFiles({ + multiple: true, + maximum: 5, + filter: 'none', + onSuccess: (files) => { + console.log('安卓平台选择文件成功:', files) + const processedFiles = files.map(file => ({ + name: file.name, + size: file.size, + path: file.path, + type: file.type || this.getFileType(file.name) + })) + + // 验证文件 + const validFiles = this.validateFiles(processedFiles) + if (validFiles.length > 0) { + this.fileList = [...this.fileList, ...validFiles] + console.log('处理后的文件列表:', this.fileList) + } + }, + onFail: (error) => { + console.error('安卓平台选择文件失败:', error) + // 尝试使用备用方法 + this.fallbackPickFiles() + } + }) + } catch (error) { + console.error('plus.io.pickFiles 不可用:', error) + // 尝试使用备用方法 + this.fallbackPickFiles() + } + }, + + // 备用文件选择方法 + fallbackPickFiles() { + // 使用 uni.chooseImage 作为备用方案 + uni.chooseImage({ + count: 5, + sizeType: ['original'], + sourceType: ['album'], + success: (res) => { + console.log('备用方法选择文件成功:', res) + const processedFiles = res.tempFilePaths.map((path, index) => ({ + name: `image_${Date.now()}_${index}.jpg`, + size: 0, + path: path, + type: 'image/jpeg' + })) + + // 验证文件 + const validFiles = this.validateFiles(processedFiles) + if (validFiles.length > 0) { + this.fileList = [...this.fileList, ...validFiles] + console.log('处理后的文件列表:', this.fileList) + } + }, + fail: (error) => { + console.error('备用方法选择文件失败:', error) + uni.showToast({ + title: '选择文件失败', + icon: 'none' + }) + } + }) + }, + + // 验证文件 + validateFiles(files) { + const allowedTypes = ['doc', 'xls', 'ppt', 'txt', 'pdf', 'docx', 'xlsx', 'png', 'jpg', 'jpeg', 'zip', 'rar', '7z', 'dwg'] + const maxSize = 200 * 1024 * 1024 // 200MB + + const validFiles = [] + const invalidFiles = [] + + files.forEach(file => { + // 文件类型验证 + const ext = file.name.split('.').pop().toLowerCase() + if (!allowedTypes.includes(ext)) { + invalidFiles.push(`${file.name} (格式不支持)`) + return + } + + // 文件大小验证 + if (file.size > maxSize) { + invalidFiles.push(`${file.name} (超过200MB)`) + return + } + + validFiles.push(file) + }) + + // 显示无效文件提示 + if (invalidFiles.length > 0) { + uni.showToast({ + title: `以下文件不符合要求:${invalidFiles.join(', ')}`, + icon: 'none', + duration: 3000 + }) + } + + return validFiles + }, + + + // 格式化文件大小 formatFileSize(size) { if (size < 1024) { @@ -437,21 +641,24 @@ export default { // 上传文件 async uploadFiles() { - const uploadPromises = this.fileList.map(async (file) => { - try { - const result = await uploadImage(file.path) - return { - originalName: file.name, - url: result.url, - ossId: result.ossId + if (this.fileList.length === 0) { + return [] + } + + try { + const results = await uploadFiles(this.fileList, { + allowedTypes: ['doc', 'xls', 'ppt', 'txt', 'pdf', 'docx', 'xlsx', 'png', 'jpg', 'jpeg', 'zip', 'rar', '7z', 'dwg'], + maxSize: 200, + extraData: 1, + onProgress: (progress) => { + console.log(`上传进度: ${progress.progress.toFixed(1)}% - ${progress.file.name}`) } - } catch (error) { - console.error('文件上传失败:', error) - throw error - } - }) - - return Promise.all(uploadPromises) + }) + + return results + } catch (error) { + throw error + } }, // 表单验证 @@ -510,12 +717,37 @@ export default { try { // 上传文件 if (this.fileList.length > 0) { + // 显示上传进度提示 + uni.showLoading({ + title: '正在上传文件...', + mask: true + }) + const uploadedFiles = await this.uploadFiles() - this.form.accessory = uploadedFiles.map(file => file.ossId).join(',') + + // 隐藏上传进度提示 + uni.hideLoading() + + // 提取ossId并拼接成字符串 + const ossIds = uploadedFiles.map(file => file.ossId).filter(Boolean) + this.form.accessory = ossIds.join(',') + + console.log('上传完成,ossId字符串:', this.form.accessory) + } else { + this.form.accessory = '' } + // 显示提交提示 + uni.showLoading({ + title: '正在创建任务...', + mask: true + }) + // 提交任务 await addTask(this.form) + + // 隐藏提交提示 + uni.hideLoading() uni.showToast({ title: '任务创建成功', @@ -529,9 +761,16 @@ export default { } catch (error) { console.error('创建任务失败:', error) + + // 隐藏所有loading + uni.hideLoading() + + // 显示错误信息 + const errorMessage = error.message || '创建任务失败' uni.showToast({ - title: '创建任务失败', - icon: 'none' + title: errorMessage, + icon: 'none', + duration: 3000 }) } finally { this.loading = false diff --git a/pages/workbench/task/reportTaskDetail.vue b/pages/workbench/task/reportTaskDetail.vue index d53be6d..ef3276a 100644 --- a/pages/workbench/task/reportTaskDetail.vue +++ b/pages/workbench/task/reportTaskDetail.vue @@ -2,16 +2,71 @@ + {{ taskDetail.taskTitle }} - 负责人:{{ taskDetail.workerNickName || '-' }} - 开始时间:{{ taskDetail.beginTime || '-' }} - 结束时间:{{ taskDetail.finishTime || '-' }} - 创建人:{{ taskDetail.createUserNickName || '-' }} - 创建时间:{{ taskDetail.createTime || '-' }} + + + 基本信息 + + + 任务状态: + + {{ getStatusText(taskDetail.state) }} + + + + 优先级: + + {{ getPriorityText(taskDetail.taskRank) }} + + + + 负责人: + {{ taskDetail.workerNickName || '-' }} + + + 创建人: + {{ taskDetail.createUserNickName || '-' }} + + + 所属项目: + {{ taskDetail.projectName || '-' }} + + + + + 时间信息 + + + 开始时间: + {{ formatDate(taskDetail.beginTime) }} + + + 结束时间: + {{ formatDate(taskDetail.finishTime) }} + + + 超期天数: + {{ taskDetail.overDays }}天 + + + 创建时间: + {{ formatDate(taskDetail.createTime) }} + + + + 任务描述 - - 报工进度 - + {{ taskDetail.content }} + 暂无任务描述 + + + 备注 + {{ taskDetail.remark }} + + + 报工进度 + 进度时间:{{ item.signTime || '-' }} 进度区间:{{ item.beginTime || '-' }} ~ {{ item.endTime || '-' }} @@ -20,11 +75,40 @@ 完成时间:{{ item.completedTime || '-' }} - 暂无报工进度 + + 暂无报工进度 + - 新增报工 + + 附件 + + + + + + + {{ getFileName(file.originalName) }} + {{ formatFileSize(file.fileSize) }} + + + + + + + + 暂无附件 + - + + 新增报工 + + + 新增报工 @@ -58,6 +142,7 @@