diff --git a/urbanLifelineWeb/packages/workcase_wechat/api/file/file.ts b/urbanLifelineWeb/packages/workcase_wechat/api/file/file.ts index cb4aa8e8..3565b6cb 100644 --- a/urbanLifelineWeb/packages/workcase_wechat/api/file/file.ts +++ b/urbanLifelineWeb/packages/workcase_wechat/api/file/file.ts @@ -60,7 +60,7 @@ export const fileAPI = { * @returns Promise> */ getFileById(fileId: string): Promise> { - return request({ url: `${BASE_URL}${this.baseUrl}/${fileId}`, method: 'GET' }) + return request({ url: `${this.baseUrl}/${fileId}`, method: 'GET' }) }, /** diff --git a/urbanLifelineWeb/packages/workcase_wechat/pages/workcase/workcaseDetail/workcaseDetail.scss b/urbanLifelineWeb/packages/workcase_wechat/pages/workcase/workcaseDetail/workcaseDetail.scss index a69caacd..605a8cae 100644 --- a/urbanLifelineWeb/packages/workcase_wechat/pages/workcase/workcaseDetail/workcaseDetail.scss +++ b/urbanLifelineWeb/packages/workcase_wechat/pages/workcase/workcaseDetail/workcaseDetail.scss @@ -534,6 +534,50 @@ margin-left: 8rpx; } +// 处理记录附件 +.timeline-files { + margin-top: 16rpx; + padding-top: 16rpx; + border-top: 1rpx dashed #e5e7eb; +} + +.files-label { + font-size: 24rpx; + color: #9ca3af; + margin-bottom: 8rpx; + display: block; +} + +.files-list { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 12rpx; +} + +.file-link { + display: flex; + flex-direction: row; + align-items: center; + gap: 8rpx; + padding: 8rpx 16rpx; + background: #f3f4f6; + border-radius: 8rpx; +} + +.file-icon { + font-size: 28rpx; +} + +.file-name { + font-size: 24rpx; + color: #4b87ff; + max-width: 200rpx; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + // 底部占位 .footer-placeholder { height: 120rpx; @@ -587,4 +631,211 @@ font-size: 28rpx; color: #6b7280; font-weight: 500; -} \ No newline at end of file +} + + +// 弹窗样式 +.process-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.process-modal { + width: 90%; + max-width: 600rpx; + background: #fff; + border-radius: 16rpx; + overflow: hidden; +} + +.modal-header { + position: relative; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding: 32rpx 24rpx; + border-bottom: 1rpx solid #f0f0f0; +} + +.modal-title { + font-size: 32rpx; + font-weight: 600; + color: #333; +} + +.modal-close { + position: absolute; + right: 24rpx; + top: 50%; + transform: translateY(-50%); + width: 48rpx; + height: 48rpx; + display: flex; + align-items: center; + justify-content: center; + + text { + font-size: 40rpx; + color: #999; + line-height: 1; + } +} + +.modal-body { + padding: 24rpx; + max-height: 60vh; + overflow-y: auto; +} + +.modal-footer { + display: flex; + flex-direction: row; + border-top: 1rpx solid #f0f0f0; +} + +.modal-btn { + flex: 1; + height: 88rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 28rpx; + color: #666; + background: #fff; + border: none; + border-radius: 0; + + &.primary { + color: #fff; + background: #4b87ff; + } + + &::after { + border: none; + } +} + +// picker 箭头样式 +.picker-arrow { + font-size: 28rpx; + color: #9ca3af; + margin-left: 16rpx; +} + +.picker-arrow-icon { + width: 16rpx; + height: 16rpx; + border-right: 4rpx solid #9ca3af; + border-bottom: 4rpx solid #9ca3af; + transform: rotate(-45deg); + margin-left: 16rpx; + flex-shrink: 0; +} + +.picker-text.placeholder { + color: #9ca3af; +} + +// 加载提示 +.loading-tip { + padding: 16rpx 0; + + text { + font-size: 24rpx; + color: #9ca3af; + } +} + +// 字数统计 +.char-count { + display: block; + text-align: right; + font-size: 22rpx; + color: #9ca3af; + margin-top: 8rpx; +} + +// 文件上传区域 +.file-upload-section { + margin-top: 16rpx; +} + +.file-list { + margin-bottom: 16rpx; +} + +.file-item { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 16rpx; + background: #f9fafb; + border-radius: 8rpx; + margin-bottom: 8rpx; +} + +.file-item .file-name { + flex: 1; + font-size: 26rpx; + color: #333; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.file-delete { + width: 40rpx; + height: 40rpx; + display: flex; + align-items: center; + justify-content: center; + + text { + font-size: 32rpx; + color: #999; + } +} + +.upload-btn { + font-size: 24rpx; +} + +// 添加处理记录按钮区域 +.add-process-section { + display: flex; + justify-content: flex-end; + margin-bottom: 16rpx; +} + +.add-process-btn { + font-size: 24rpx; +} + +// 危险/警告按钮 +.action-button.danger { + background: #ef4444; + border-color: #ef4444; + + .button-text { + color: #fff; + } +} + +.action-button.warning { + background: #f59e0b; + border-color: #f59e0b; + + .button-text { + color: #fff; + } +} diff --git a/urbanLifelineWeb/packages/workcase_wechat/pages/workcase/workcaseDetail/workcaseDetail.uvue b/urbanLifelineWeb/packages/workcase_wechat/pages/workcase/workcaseDetail/workcaseDetail.uvue index 2b574a0c..a026582c 100644 --- a/urbanLifelineWeb/packages/workcase_wechat/pages/workcase/workcaseDetail/workcaseDetail.uvue +++ b/urbanLifelineWeb/packages/workcase_wechat/pages/workcase/workcaseDetail/workcaseDetail.uvue @@ -184,8 +184,20 @@ {{ getActionText(item.action) }}: {{ item.message }} - - + + + 附件: + + + {{ getFileIcon(fileId) }} + {{ getFileName(fileId) }} + + @@ -312,7 +324,7 @@ {{ assignForm.processorName }} 请选择工程师 - > + @@ -328,6 +340,28 @@ maxlength="200" /> + + 附件(可选) + + + + {{ file.name }} + + × + + + + + 最多上传5个文件 + + @@ -369,6 +403,9 @@ const workcase = reactive({}) // 处理记录 const processList = reactive([]) +// 文件信息缓存 +const fileInfoCache = reactive>(new Map()) + // 判断是否是处理人 const isProcessor = computed(() => { return workcase.processor === userId @@ -398,7 +435,8 @@ const selectedEngineerIndex = ref(-1) const assignForm = reactive({ processor: '', processorName: '', - message: '' + message: '', + files: [] as Array<{ name: string; fileId: string }> }) // 获取图片 URL(通过 fileId) @@ -513,12 +551,121 @@ async function loadProcessList(workcaseId: string) { processList.length = 0 processList.push(...res.dataList) console.log('处理记录:', processList) + // 加载文件信息 + await loadFilesInfo(res.dataList) } } catch (error) { console.error('加载处理记录失败:', error) } } +// 加载处理记录中的文件信息 +async function loadFilesInfo(processes: TbWorkcaseProcessDTO[]) { + // 收集所有文件ID + const fileIds: string[] = [] + processes.forEach(p => { + if (p.files) { + const filesArray = Array.isArray(p.files) ? p.files : [p.files] + filesArray.forEach(id => { + if (id && !fileInfoCache.has(id)) { + fileIds.push(id) + } + }) + } + }) + + if (fileIds.length === 0) return + + // 逐个查询文件信息 + for (const fileId of fileIds) { + try { + const res = await fileAPI.getFileById(fileId) + if (res.success && res.data) { + fileInfoCache.set(fileId, res.data) + } + } catch (error) { + console.error(`加载文件信息失败: ${fileId}`, error) + } + } +} + +// 获取文件名 +function getFileName(fileId: string): string { + const fileInfo = fileInfoCache.get(fileId) + return fileInfo?.name || fileId.substring(0, 8) + '...' +} + +// 获取文件图标 +function getFileIcon(fileId: string): string { + const fileInfo = fileInfoCache.get(fileId) + if (!fileInfo) return '📎' + + const ext = (fileInfo.extension || fileInfo.name?.split('.').pop() || '').toLowerCase() + const iconMap: Record = { + jpg: '🖼️', + jpeg: '🖼️', + png: '🖼️', + gif: '🖼️', + webp: '🖼️', + pdf: '📄', + doc: '📝', + docx: '📝', + xls: '📊', + xlsx: '📊' + } + return iconMap[ext] || '📎' +} + +// 预览或下载文件 +function previewOrDownloadFile(fileId: string) { + const fileInfo = fileInfoCache.get(fileId) + const fileUrl = fileAPI.getDownloadUrl(fileId) + + // 如果是图片,预览 + if (fileInfo) { + const ext = (fileInfo.extension || fileInfo.name?.split('.').pop() || '').toLowerCase() + if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext)) { + uni.previewImage({ + urls: [fileUrl], + current: 0 + }) + return + } + } + + // 其他文件,打开文档或复制链接 + uni.showActionSheet({ + itemList: ['复制下载链接', '打开文档'], + success: (res) => { + if (res.tapIndex === 0) { + uni.setClipboardData({ + data: fileUrl, + success: () => { + uni.showToast({ title: '链接已复制', icon: 'success' }) + } + }) + } else if (res.tapIndex === 1) { + uni.downloadFile({ + url: fileUrl, + success: (downloadRes) => { + if (downloadRes.statusCode === 200) { + uni.openDocument({ + filePath: downloadRes.tempFilePath, + fail: () => { + uni.showToast({ title: '无法打开此文件', icon: 'none' }) + } + }) + } + }, + fail: () => { + uni.showToast({ title: '下载失败', icon: 'none' }) + } + }) + } + } + }) +} + function getTime(datetime: string): string{ return datetime.split(" ")[1] } @@ -646,6 +793,7 @@ async function openAssignDialog() { assignForm.processor = '' assignForm.processorName = '' assignForm.message = '' + assignForm.files = [] selectedEngineerIndex.value = -1 showAssignDialog.value = true await loadAvailableEngineers() @@ -656,6 +804,74 @@ function closeAssignDialog() { showAssignDialog.value = false } +// 选择指派/转派附件 +function chooseAssignFile() { + uni.chooseMessageFile({ + count: 5 - assignForm.files.length, + type: 'all', + success: async (res) => { + uni.showLoading({ title: '上传中...' }) + try { + for (const file of res.tempFiles) { + const uploadRes = await fileAPI.uploadFile(file.path, { + module: 'workcase', + optsn: workcase.workcaseId || 'temp' + }) + if (uploadRes.success && uploadRes.data?.fileId) { + assignForm.files.push({ + name: file.name, + fileId: uploadRes.data.fileId + }) + } + } + uni.hideLoading() + uni.showToast({ title: '上传成功', icon: 'success' }) + } catch (error) { + uni.hideLoading() + console.error('上传文件失败:', error) + uni.showToast({ title: '上传失败', icon: 'none' }) + } + }, + fail: () => { + // 如果 chooseMessageFile 不支持,尝试使用 chooseImage + uni.chooseImage({ + count: 5 - assignForm.files.length, + sizeType: ['compressed'], + sourceType: ['album', 'camera'], + success: async (imgRes) => { + uni.showLoading({ title: '上传中...' }) + try { + for (const filePath of imgRes.tempFilePaths) { + const uploadRes = await fileAPI.uploadFile(filePath, { + module: 'workcase', + optsn: workcase.workcaseId || 'temp' + }) + if (uploadRes.success && uploadRes.data?.fileId) { + const fileName = filePath.split('/').pop() || '图片' + assignForm.files.push({ + name: fileName, + fileId: uploadRes.data.fileId + }) + } + } + uni.hideLoading() + uni.showToast({ title: '上传成功', icon: 'success' }) + } catch (error) { + uni.hideLoading() + console.error('上传文件失败:', error) + uni.showToast({ title: '上传失败', icon: 'none' }) + } + } + }) + } + }) +} + +// 删除指派/转派附件 +function deleteAssignFile(index: number) { + assignForm.files.splice(index, 1) +} + // 加载可用工程师列表(排除当前处理人) async function loadAvailableEngineers() { loadingEngineers.value = true @@ -707,7 +923,8 @@ async function submitAssign() { workcaseId: workcase.workcaseId, action: assignAction.value, processor: assignForm.processor, - message: assignForm.message || (assignAction.value === 'assign' ? '工单指派' : '工单转派') + message: assignForm.message || (assignAction.value === 'assign' ? '工单指派' : '工单转派'), + files: assignForm.files.map(f => f.fileId) } const res = await workcaseAPI.createWorkcaseProcess(params)