将待办截图改为任务内画廊 #672

Merged
yueyang merged 1 commits from fix/yueyang-systemization-todo-shot-gallery into main 2026-10-27 10:51:46 +08:00
2 changed files with 99 additions and 40 deletions

View File

@ -83,10 +83,12 @@
.sys-todo-badge { display:inline-flex; align-items:center; padding:.1rem .45rem; border:1px solid #dfd6c5; border-radius:999px; background:#faf6ee; color:#51483c; white-space:nowrap; }
.sys-todo-badge--incomplete { border-color:#dbbf7a; background:#fff7df; color:#935f07; }
.sys-todo-badge--completed { border-color:#bad0ac; background:#f2f8ed; color:#53793e; }
.sys-todo-shots { display:grid; grid-template-columns:repeat(auto-fit,minmax(7rem,1fr)); gap:.4rem; max-width:none; }
.sys-todo-shots button { display:block; width:100%; height:5.5rem; padding:0; border:1px solid #ddd4c4; border-radius:.4rem; overflow:hidden; background:#fff; cursor:zoom-in; }
.sys-todo-shots img { width:100%; height:100%; padding:.2rem; object-fit:contain; background:#fff; }
.sys-todo-shots-caption { display:-webkit-box; margin-top:.35rem; overflow:hidden; color:#6d5630; font-size:.68rem; line-height:1.45; overflow-wrap:anywhere; -webkit-box-orient:vertical; -webkit-line-clamp:2; }
.sys-todo-table__title-wrap { display:flex; flex-wrap:wrap; align-items:flex-start; gap:.35rem .55rem; }
.sys-todo-table__title-text { min-width:0; flex:1 1 12rem; }
.sys-todo-shot-trigger { display:inline-flex; flex:0 0 auto; align-items:center; gap:.25rem; min-height:1.8rem; padding:0 .5rem; border:1px solid #d8c9aa; border-radius:.4rem; background:#fffaf0; color:#6d5630; font:inherit; font-size:.7rem; font-weight:500; white-space:nowrap; cursor:pointer; }
.sys-todo-shot-trigger:hover { border-color:var(--sys-gold); background:var(--sys-gold-soft); color:#6a5129; }
.sys-todo-shot-trigger:focus-visible { outline:2px solid var(--sys-gold); outline-offset:2px; }
.sys-todo-shot-trigger .material-icons-outlined { font-size:1rem; }
.sys-todo-table__add { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:.5rem .75rem; margin-top:1rem; padding:.55rem .7rem; border:1px dashed #d8cdb8; border-radius:.55rem; background:#fcfaf5; color:#666; font-size:.72rem; }
.sys-todo-table__add-copy { min-width:16rem; flex:1 1 30rem; }
.sys-todo-table__reset-widths { min-height:1.8rem; padding:0 .55rem; border:1px solid var(--sys-border); border-radius:.4rem; background:#fff; color:#51483c; font:inherit; font-size:.7rem; cursor:pointer; }
@ -182,8 +184,11 @@
.sys-todo-image-dialog__head { display:flex; align-items:center; gap:1rem; padding:.65rem .85rem; background:#1d1d1f; }
.sys-todo-image-dialog__head h2 { margin:0; min-width:0; overflow:hidden; font-size:.82rem; text-overflow:ellipsis; white-space:nowrap; }
.sys-todo-image-dialog__head button { margin-left:auto; width:2rem; height:2rem; border:1px solid #5a554d; border-radius:999px; background:#2a2927; color:#fff; cursor:pointer; }
.sys-todo-image-dialog__body { display:grid; place-items:center; height:calc(92vh - 7rem); padding:.75rem; background:#111; overflow:auto; }
.sys-todo-image-dialog__body { display:grid; grid-template-columns:2.25rem minmax(0,1fr) 2.25rem; align-items:center; gap:.6rem; height:calc(92vh - 7rem); padding:.75rem; background:#111; overflow:auto; }
.sys-todo-image-dialog__body img { max-width:100%; max-height:100%; object-fit:contain; }
.sys-todo-image-dialog__body button { display:inline-flex; align-items:center; justify-content:center; width:2.25rem; height:2.75rem; padding:0; border:1px solid #5a554d; border-radius:999px; background:#2a2927; color:#fff; font-size:1.5rem; cursor:pointer; }
.sys-todo-image-dialog__body button:hover { border-color:#f0e4bf; color:#f0e4bf; }
.sys-todo-image-dialog__body button[hidden] { visibility:hidden; }
.sys-todo-image-dialog__foot { display:flex; justify-content:flex-end; padding:.55rem .85rem; background:#1d1d1f; }
.sys-todo-image-dialog__foot a { color:#f0e4bf; font-size:.75rem; }
@media (max-width:1300px) { .sys-todo-summary { grid-template-columns:repeat(2,minmax(0,1fr)); } }
@ -371,7 +376,6 @@
<col data-sys-todo-col="reminder" data-default-width="116" data-min-width="96" style="width:116px">
<col data-sys-todo-col="status" data-default-width="92" data-min-width="80" style="width:92px">
<col data-sys-todo-col="progress_notes" data-default-width="260" data-min-width="180" style="width:260px">
<col data-sys-todo-col="screenshots" data-default-width="180" data-min-width="140" style="width:180px">
</colgroup>
<thead>
<tr>
@ -386,7 +390,6 @@
<th scope="col" data-sys-todo-column="reminder">提醒<button type="button" class="sys-todo-table__col-resizer" data-sys-todo-col-resizer="reminder" aria-label="调整提醒列宽" title="拖动调整,双击恢复"></button></th>
<th scope="col" data-sys-todo-column="status">状态<button type="button" class="sys-todo-table__col-resizer" data-sys-todo-col-resizer="status" aria-label="调整状态列宽" title="拖动调整,双击恢复"></button></th>
<th scope="col" data-sys-todo-column="progress_notes">完成情况 / 备注<button type="button" class="sys-todo-table__col-resizer" data-sys-todo-col-resizer="progress_notes" aria-label="调整完成情况和备注列宽" title="拖动调整,双击恢复"></button></th>
<th scope="col" data-sys-todo-column="screenshots">完成截图<button type="button" class="sys-todo-table__col-resizer" data-sys-todo-col-resizer="screenshots" aria-label="调整完成截图列宽" title="拖动调整,双击恢复"></button></th>
</tr>
</thead>
<tbody>
@ -491,7 +494,19 @@
<td class="sys-todo-table__title"
tabindex="0"
title="点击编辑待办"
data-sys-todo-row-edit="{{ $todoPayloadEncoded }}">{{ $titleLabels[$loop->index] }}</td>
data-sys-todo-row-edit="{{ $todoPayloadEncoded }}">
<div class="sys-todo-table__title-wrap">
<span class="sys-todo-table__title-text">{{ $titleLabels[$loop->index] }}</span>
@if ($item->attachments->isNotEmpty())
<button type="button" class="sys-todo-shot-trigger"
data-sys-todo-image-gallery="{{ $todoPayloadEncoded }}"
title="查看这条待办的 {{ $item->attachments->count() }} 张截图">
<span class="material-icons-outlined" aria-hidden="true">photo_library</span>
<span>截图 {{ $item->attachments->count() }}</span>
</button>
@endif
</div>
</td>
<td>{{ $item->owner ?: '—' }}</td>
<td>{{ $item->assignee ?: '—' }}</td>
<td>{{ $item->start_date?->format('Y-m-d') ?: ($item->start_note ?: '—') }}</td>
@ -516,30 +531,9 @@
—
@endif
</td>
<td>
@if ($item->attachments->isNotEmpty())
<div class="sys-todo-shots">
@foreach ($item->attachments as $attachment)
@php
$attachmentUrl = route('business.systemization-todos.attachments.show', [
'todo' => $item->id,
'attachment' => $attachment->id,
]);
@endphp
<button type="button" data-sys-todo-image-url="{{ $attachmentUrl }}" data-sys-todo-image-name="{{ $attachment->original_name }}" title="点击预览:{{ $attachment->original_name }}">
<img src="{{ route('business.systemization-todos.attachments.show', ['todo' => $item->id, 'attachment' => $attachment->id]) }}"
alt="{{ $attachment->original_name }}" loading="lazy">
</button>
@endforeach
</div>
<span class="sys-todo-shots-caption" title="{{ $titleLabels[$loop->index] ?: '未填写事项' }}">所属待办:{{ $titleLabels[$loop->index] ?: '未填写事项' }}</span>
@else
—
@endif
</td>
</tr>
@empty
<tr><td colspan="12"><div class="sys-todo-empty">当前筛选条件下没有待办事项。</div></td></tr>
<tr><td colspan="11"><div class="sys-todo-empty">当前筛选条件下没有待办事项。</div></td></tr>
@endforelse
</tbody>
<tfoot>
@ -549,7 +543,7 @@
data-sys-todo-inline-add
data-sys-todo-add-mode="type">+ 类型</button>
</td>
<td colspan="11" class="sys-todo-table__footer-add sys-todo-table__footer-note">也可在现有类型下方新增内容,在内容下方新增待办。</td>
<td colspan="10" class="sys-todo-table__footer-add sys-todo-table__footer-note">也可在现有类型下方新增内容,在内容下方新增待办。</td>
</tr>
</tfoot>
</table>
@ -712,7 +706,9 @@
<button type="button" data-sys-todo-image-close aria-label="关闭截图预览">×</button>
</div>
<div class="sys-todo-image-dialog__body">
<button type="button" data-sys-todo-image-prev aria-label="上一张截图">‹</button>
<img src="" alt="" data-sys-todo-image-preview>
<button type="button" data-sys-todo-image-next aria-label="下一张截图">›</button>
</div>
<div class="sys-todo-image-dialog__foot">
<a href="" target="_blank" rel="noopener" data-sys-todo-image-open>在新页面打开原图</a>
@ -735,6 +731,10 @@
var imagePreview = imageViewer.querySelector('[data-sys-todo-image-preview]');
var imageTitle = imageViewer.querySelector('[data-sys-todo-image-title]');
var imageOpen = imageViewer.querySelector('[data-sys-todo-image-open]');
var imagePrevious = imageViewer.querySelector('[data-sys-todo-image-prev]');
var imageNext = imageViewer.querySelector('[data-sys-todo-image-next]');
var imageGalleryItems = [];
var imageGalleryIndex = 0;
var categorySelect = dialog.querySelector('[data-sys-todo-category-select]');
var categoryValue = dialog.querySelector('[data-sys-todo-category-value]');
var newCategoryWrap = dialog.querySelector('[data-sys-todo-new-category]');
@ -1193,19 +1193,58 @@
}
}
function openImageViewer(url, name) {
imageTitle.textContent = name || '完成截图预览';
imagePreview.src = url;
imagePreview.alt = name || '完成截图';
imageOpen.href = url;
function showImageViewerItem() {
var item = imageGalleryItems[imageGalleryIndex];
if (!item) return;
var position = imageGalleryItems.length > 1
? (imageGalleryIndex + 1) + ' / ' + imageGalleryItems.length + ' · '
: '';
imageTitle.textContent = position + (item.name || '完成截图预览');
imagePreview.src = item.url;
imagePreview.alt = item.name || '完成截图';
imageOpen.href = item.url;
imagePrevious.hidden = imageGalleryItems.length <= 1;
imageNext.hidden = imageGalleryItems.length <= 1;
}
function openImageViewerItems(items) {
imageGalleryItems = Array.isArray(items) && items.length
? items
: [];
imageGalleryIndex = 0;
if (!imageGalleryItems.length) return;
showImageViewerItem();
if (typeof imageViewer.showModal === 'function') imageViewer.showModal();
else imageViewer.setAttribute('open', '');
}
function openImageViewer(url, name) {
openImageViewerItems([{ url: url, name: name }]);
}
function showPreviousImage() {
if (imageGalleryItems.length < 2) return;
imageGalleryIndex = (imageGalleryIndex - 1 + imageGalleryItems.length) % imageGalleryItems.length;
showImageViewerItem();
}
function showNextImage() {
if (imageGalleryItems.length < 2) return;
imageGalleryIndex = (imageGalleryIndex + 1) % imageGalleryItems.length;
showImageViewerItem();
}
function closeImageViewer() {
if (typeof imageViewer.close === 'function') imageViewer.close();
else imageViewer.removeAttribute('open');
imagePreview.removeAttribute('src');
imageTitle.textContent = '完成截图预览';
imageGalleryItems = [];
imageGalleryIndex = 0;
imagePrevious.hidden = true;
imageNext.hidden = true;
}
function openGroupDialog(type, value, count) {
@ -1331,6 +1370,14 @@
}
document.addEventListener('click', function (event) {
var gallery = event.target.closest('[data-sys-todo-image-gallery]');
if (gallery) {
event.preventDefault();
event.stopPropagation();
var galleryPayload = decodePayload(gallery.getAttribute('data-sys-todo-image-gallery'));
openImageViewerItems(galleryPayload.attachments || []);
return;
}
var image = event.target.closest('[data-sys-todo-image-url]');
if (image) {
event.preventDefault();
@ -1397,6 +1444,8 @@
if (event.target.closest('[data-sys-todo-group-close]')) closeGroupDialog();
if (event.target.closest('[data-sys-todo-delete-close]')) closeDeleteConfirm();
if (event.target.closest('[data-sys-todo-image-close]')) closeImageViewer();
if (event.target.closest('[data-sys-todo-image-prev]')) showPreviousImage();
if (event.target.closest('[data-sys-todo-image-next]')) showNextImage();
if (event.target.closest('[data-sys-todo-choose-files]')) screenshotInput.click();
});
dialog.addEventListener('click', function (event) {
@ -1487,6 +1536,16 @@
closeImageViewer();
return;
}
if (imageViewer.open && event.key === 'ArrowLeft') {
event.preventDefault();
showPreviousImage();
return;
}
if (imageViewer.open && event.key === 'ArrowRight') {
event.preventDefault();
showNextImage();
return;
}
var groupCell = event.target.closest('[data-sys-todo-group-edit]');
if (groupCell && event.target === groupCell && (event.key === 'Enter' || event.key === ' ')) {
event.preventDefault();

View File

@ -68,8 +68,8 @@ class SystemizationTodosTest extends FeatureTestCase
->assertSee('id="sysTodoDeleteConfirm"', false)
->assertSee('data-sys-todo-table-wrap', false)
->assertSee('data-sys-todo-scrollrail', false)
->assertSee('object-fit:contain', false)
->assertSee('data-sys-todo-col="screenshots" data-default-width="180" data-min-width="140"', false)
->assertSee('data-sys-todo-image-prev', false)
->assertSee('data-sys-todo-image-next', false)
->assertDontSee('playlist_add', false)
->assertDontSee('<th scope="col">操作</th>', false)
->assertDontSeeText('下一页')
@ -113,8 +113,8 @@ class SystemizationTodosTest extends FeatureTestCase
]))
->assertOk()
->assertSee('data-sys-todo-row-edit', false)
->assertSee('data-sys-todo-image-url', false)
->assertSeeText('所属待办:核对淘系收入与平台账单差异');
->assertSee('data-sys-todo-image-gallery', false)
->assertSeeText('截图 2');
$firstAttachment = $todo->attachments()->firstOrFail();
$existingAttachment = $todo->attachments()->reorder()->orderByDesc('id')->firstOrFail();
Storage::disk('uploads')->assertExists($firstAttachment->path);