diff --git a/app/Http/Controllers/WeekReportController.php b/app/Http/Controllers/WeekReportController.php index 1c290ef..d268055 100644 --- a/app/Http/Controllers/WeekReportController.php +++ b/app/Http/Controllers/WeekReportController.php @@ -58,7 +58,8 @@ class WeekReportController extends Controller $request->validate([ 'tasks' => 'required|array|min:1', 'tasks.*.description' => 'required|string|max:2000', - 'tasks.*.image' => 'nullable|string', + 'tasks.*.images' => 'nullable|array', // 支持多图 + 'tasks.*.images.*' => 'nullable|string', // 每张图片路径 'week_start' => 'nullable|date', 'week_end' => 'nullable|date', 'author' => 'nullable|string|max:100', @@ -106,7 +107,10 @@ class WeekReportController extends Controller foreach ($tasks as $index => $task) { $num = $index + 1; $desc = trim($task['description']); - $hasImage = !empty($task['image']) ? '(有截图)' : ''; + // 支持多图:检查 images 数组 + $images = $task['images'] ?? []; + $imageCount = count(array_filter($images)); + $hasImage = $imageCount > 0 ? "(有{$imageCount}张截图)" : ''; $list[] = "{$num}. {$desc}{$hasImage}"; } return implode("\n", $list); @@ -166,10 +170,11 @@ class WeekReportController extends Controller 5. 每个任务用简短的一两句话描述即可,说清楚做了什么 6. 可以按项目或类型简单分组,但不要过度分类 7. 【重要】只有标注了"(有截图)"的任务才在该任务描述后紧接着添加"[图片占位符-任务X]"(X是原始任务序号),其他任务不要添加{$nextWeekRequirement} -8. 【重要】图片占位符的序号要与原始任务序号的序号一致,不要出现错位 9. 【重要】不要添加任何总结、小结、回顾等内容 10. 不要出现具体的完成时间、耗时等信息 11. 整体篇幅适中,不要太长 +12. 【重要】图片占位符的序号要与原始任务序号的序号一致,不要出现错位 + 直接输出周报内容: PROMPT; @@ -225,7 +230,7 @@ PROMPT; } /** - * 在报告中插入图片 + * 在报告中插入图片(支持多图) */ private function insertImagesToReport(string $report, array $tasks): string { @@ -243,11 +248,22 @@ PROMPT; "【图片占位符{$num}】", ]; - if (!empty($task['image'])) { - $imageUrl = asset('storage/' . $task['image']); - $imageMarkdown = "\n\n![任务{$num}截图]({$imageUrl})\n"; + // 获取该任务的所有图片 + $images = $task['images'] ?? []; + $validImages = array_filter($images); + + if (!empty($validImages)) { + // 构建多图Markdown + $imagesMarkdown = "\n\n"; + foreach ($validImages as $imgIndex => $imagePath) { + $imageUrl = asset('storage/' . $imagePath); + $imgNum = $imgIndex + 1; + $imagesMarkdown .= "![任务{$num}-截图{$imgNum}]({$imageUrl})\n\n"; + } + + // 替换占位符 foreach ($placeholders as $placeholder) { - $report = str_replace($placeholder, $imageMarkdown, $report); + $report = str_replace($placeholder, $imagesMarkdown, $report); } } else { // 没有图片时移除所有占位符 diff --git a/resources/views/weekreport/index.blade.php b/resources/views/weekreport/index.blade.php index 0b38b78..35319ec 100644 --- a/resources/views/weekreport/index.blade.php +++ b/resources/views/weekreport/index.blade.php @@ -353,24 +353,32 @@ display: none; } - .image-preview { - position: relative; + .image-preview-list { + display: flex; + flex-wrap: wrap; + gap: 10px; margin-top: 10px; } - .image-preview img { - max-width: 100%; - max-height: 150px; - border-radius: var(--radius-sm); - border: 1px solid var(--border); + .image-preview-item { + position: relative; + display: inline-block; } - .image-preview .remove-image { + .image-preview-item img { + max-width: 120px; + max-height: 100px; + border-radius: var(--radius-sm); + border: 1px solid var(--border); + object-fit: cover; + } + + .image-preview-item .remove-image { position: absolute; top: -8px; right: -8px; - width: 24px; - height: 24px; + width: 22px; + height: 22px; background: var(--danger); border: none; border-radius: 50%; @@ -379,14 +387,26 @@ display: flex; align-items: center; justify-content: center; - font-size: 0.9rem; + font-size: 0.8rem; transition: transform 0.2s ease; } - .image-preview .remove-image:hover { + .image-preview-item .remove-image:hover { transform: scale(1.1); } + .image-count-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + background: var(--primary); + color: white; + border-radius: 10px; + font-size: 0.75rem; + margin-left: 8px; + } + /* 按钮样式 */ .btn { display: inline-flex; @@ -1140,8 +1160,8 @@ tasks.push({ id: Date.now() + Math.random(), description: task.description, - image: null, - imagePath: '', + images: [], + imagePaths: [], zentaoId: task.id }); }); @@ -1235,8 +1255,8 @@ tasks.push({ id: taskId, description: '', - image: null, - imagePath: '' + images: [], // 支持多图 + imagePaths: [] // 对应的URL列表 }); renderTasks(); updateTaskCount(); @@ -1260,11 +1280,35 @@ container.innerHTML = ''; tasks.forEach((task, index) => { + // 生成多图预览HTML + let imagesPreviewHtml = ''; + if (task.imagePaths && task.imagePaths.length > 0) { + imagesPreviewHtml = ` +
+ ${task.imagePaths.map((url, imgIndex) => ` +
+ 截图${imgIndex + 1} + +
+ `).join('')} +
+ `; + } + + const imageCountBadge = task.imagePaths && task.imagePaths.length > 0 + ? `${task.imagePaths.length}` + : ''; + const taskEl = document.createElement('div'); taskEl.className = 'task-item'; taskEl.innerHTML = `
- ${index + 1} +
+ ${index + 1} + ${imageCountBadge} +
@@ -1282,19 +1326,12 @@ ondragover="handleDragOver(event, this)" ondragleave="handleDragLeave(event, this)" ondrop="handleDrop(event, ${task.id}, this)"> - - 点击或拖拽上传任务截图(可选) + 点击或拖拽上传任务截图(支持多张,可选)
- ${task.imagePath ? ` -
- 任务截图 - -
- ` : ''} + ${imagesPreviewHtml} `; container.appendChild(taskEl); @@ -1323,56 +1360,76 @@ e.preventDefault(); el.classList.remove('dragover'); const files = e.dataTransfer.files; - if (files.length > 0 && files[0].type.startsWith('image/')) { - uploadImage(taskId, files[0]); + // 支持拖拽多张图片 + const imageFiles = Array.from(files).filter(f => f.type.startsWith('image/')); + if (imageFiles.length > 0) { + uploadImages(taskId, imageFiles); } } - // 选择图片 + // 选择图片(支持多选) function handleImageSelect(taskId, input) { if (input.files.length > 0) { - uploadImage(taskId, input.files[0]); + uploadImages(taskId, Array.from(input.files)); } } - // 上传图片 - async function uploadImage(taskId, file) { - const formData = new FormData(); - formData.append('image', file); - - try { - const response = await fetch('/upload', { - method: 'POST', - headers: { - 'X-CSRF-TOKEN': csrfToken - }, - body: formData - }); - - const data = await response.json(); - - if (data.success) { - const task = tasks.find(t => t.id === taskId); - if (task) { - task.image = data.path; - task.imagePath = data.url; - renderTasks(); - showToast('图片上传成功', 'success'); - } - } else { - showToast(data.message || '图片上传失败', 'error'); - } - } catch (error) { - showToast('图片上传失败: ' + error.message, 'error'); - } - } - - // 移除图片 - function removeImage(taskId) { + // 批量上传图片 + async function uploadImages(taskId, files) { const task = tasks.find(t => t.id === taskId); - if (task) { - task.image = null; - task.imagePath = ''; + if (!task) return; + + // 初始化数组(兼容旧数据) + if (!task.images) task.images = []; + if (!task.imagePaths) task.imagePaths = []; + + let successCount = 0; + let failCount = 0; + + for (const file of files) { + const formData = new FormData(); + formData.append('image', file); + + try { + const response = await fetch('/upload', { + method: 'POST', + headers: { + 'X-CSRF-TOKEN': csrfToken + }, + body: formData + }); + + const data = await response.json(); + + if (data.success) { + task.images.push(data.path); + task.imagePaths.push(data.url); + successCount++; + } else { + failCount++; + } + } catch (error) { + failCount++; + console.error('图片上传失败:', error); + } + } + + renderTasks(); + + if (successCount > 0) { + showToast(`成功上传 ${successCount} 张图片`, 'success'); + } + if (failCount > 0) { + showToast(`${failCount} 张图片上传失败`, 'error'); + } + } + + // 移除指定索引的图片 + function removeImage(taskId, imageIndex) { + const task = tasks.find(t => t.id === taskId); + if (task && task.images && task.imagePaths) { + task.images.splice(imageIndex, 1); + task.imagePaths.splice(imageIndex, 1); renderTasks(); } } @@ -1403,7 +1460,7 @@ body: JSON.stringify({ tasks: validTasks.map(t => ({ description: t.description, - image: t.image + images: t.images || [] // 支持多图 })), week_start: weekStart, week_end: weekEnd,