From 207f48f7d8dfe3b901b082f427ba06f9c0dfdefe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Sat, 5 Sep 2026 23:24:11 +0800 Subject: [PATCH] =?UTF-8?q?align(=E8=A7=86=E9=A2=91=E4=BB=BB=E5=8A=A1):=20?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E8=A1=A5=E4=BB=BB=E5=8A=A1=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?/=E7=94=9F=E6=88=90=E6=97=B6=E9=97=B4(completed||submitted)/?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E9=93=BE=E6=8E=A5=E5=A4=8D=E5=88=B6=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E8=A1=8C=E3=80=81=E4=B8=8B=E8=BD=BD=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E6=97=A0=E8=A7=86=E9=A2=91=E7=A6=81=E7=94=A8=E3=80=81=E6=98=8E?= =?UTF-8?q?=E7=BB=86=E8=A1=A5=E6=A8=A1=E5=BC=8F/=E7=94=9F=E6=88=90?= =?UTF-8?q?=E6=97=B6=E9=97=B4/=E8=B0=83=E8=AF=95=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=EF=BC=88=E5=AF=B9=E9=BD=90=20admin.js=20renderImageVideoCard/c?= =?UTF-8?q?opyImageVideoDebugUrl=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/tasks/ImageVideoTasksPage.vue | 31 ++++++--- .../src/pages/tasks/image-video-model.ts | 14 +++++ .../tests/align-image-video.test.ts | 63 +++++++++++++++++++ 3 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 admin-frontend-vue/tests/align-image-video.test.ts diff --git a/admin-frontend-vue/src/pages/tasks/ImageVideoTasksPage.vue b/admin-frontend-vue/src/pages/tasks/ImageVideoTasksPage.vue index 35a06c5d..e1043913 100644 --- a/admin-frontend-vue/src/pages/tasks/ImageVideoTasksPage.vue +++ b/admin-frontend-vue/src/pages/tasks/ImageVideoTasksPage.vue @@ -6,7 +6,7 @@ import { ElMessage } from 'element-plus' import { requestVideoZipDownload, fetchImageVideoTasks, fetchImageVideoTaskDetail } from './image-video-api.ts' import { fetchImageVideoPermissionUsers, saveImageVideoPermissions } from './image-video-api.ts' import { createImageVideoFilter, type ImageVideoFilter } from './image-video-filter.ts' -import type { ImageVideoRow } from './image-video-model.ts' +import { imageVideoGeneratedAt, type ImageVideoRow } from './image-video-model.ts' import type { ImageVideoVideo, ImageVideoDetail } from './image-video-model.ts' import { allCardsSelected, @@ -284,14 +284,23 @@ onMounted(load)
任务 {{ task.taskId }} - {{ task.username || '—' }} - {{ task.groupName }}
{{ statusLabel(task.status) }} - {{ formatDateTime(task.submittedAt) }} 明细 +
+
{{ task.username || '-' }}
+
{{ task.groupName || '-' }}
+
{{ task.mode || '-' }}
+
{{ formatDateTime(imageVideoGeneratedAt(task)) }}
+
+ + 复制链接 + - +
+
+
@@ -302,6 +311,7 @@ onMounted(load) text type="success" size="small" + :disabled="!video.displayUrl" :loading="downloadingKey === `${task.taskId}:${index}`" @click="downloadVideo(task, index, video)" > @@ -323,7 +333,13 @@ onMounted(load) {{ detail.taskId }} {{ detail.username }} {{ statusLabel(detail.status) }} + {{ detail.mode || '—' }} {{ formatDateTime(detail.submittedAt) }} + {{ formatDateTime(imageVideoGeneratedAt(detail)) }} + + 复制链接 + + {{ detail.cozeExecuteId || '—' }} {{ detail.errorMessage || '—' }} @@ -370,9 +386,10 @@ onMounted(load) .task-card-head { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--el-border-color-lighter); } .task-card-title { display: flex; align-items: center; gap: 10px; min-width: 0; } .task-id { font-weight: 600; color: var(--el-text-color-primary); } -.task-user { color: var(--el-text-color-regular); } -.task-group { color: var(--el-text-color-secondary); font-size: 12px; } -.task-time { margin-left: auto; color: var(--el-text-color-secondary); font-size: 12.5px; } +.task-card-info { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 6px 14px; padding: 12px 16px; border-bottom: 1px dashed var(--el-border-color-lighter); } +.task-info-row { display: flex; align-items: center; gap: 8px; font-size: 12.5px; min-width: 0; } +.task-info-row label { flex: none; color: var(--el-text-color-secondary); min-width: 60px; } +.task-info-row span { color: var(--el-text-color-regular); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .video-list { padding: 6px 16px; } .video-row { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px dashed var(--el-border-color-lighter); } .video-row:last-child { border-bottom: 0; } diff --git a/admin-frontend-vue/src/pages/tasks/image-video-model.ts b/admin-frontend-vue/src/pages/tasks/image-video-model.ts index 2f755166..1bb4d42f 100644 --- a/admin-frontend-vue/src/pages/tasks/image-video-model.ts +++ b/admin-frontend-vue/src/pages/tasks/image-video-model.ts @@ -22,6 +22,8 @@ export interface ImageVideoRow { status: TaskStatus cozeStatus: string cozeExecuteId: string + /** 任务模式(对齐 admin.js 卡片「任务模式」信息行)。 */ + mode?: string videos: ImageVideoVideo[] videoUrl: string debugUrl: string @@ -104,9 +106,21 @@ export function toImageVideoRow(raw: unknown): ImageVideoRow | null { if (submittedAt) task.submittedAt = submittedAt const completedAt = text(r.completed_at ?? r.completedAt) if (completedAt) task.completedAt = completedAt + const mode = text(r.mode) + if (mode) task.mode = mode return task } +/** 生成时间口径(对齐 admin.js:1331 completed_at || submitted_at || '-')。 */ +export function imageVideoGeneratedAt(task: Pick): string { + return task.completedAt || task.submittedAt || '-' +} + +/** 卡片是否带可播放/可下载视频(对齐 admin.js hasVideo 判定)。 */ +export function imageVideoHasDisplayUrl(card: Pick): boolean { + return !!(card.video && card.video.displayUrl) +} + /** 把任务行展平为“任务:视频下标”卡片;无 videos 的任务展为一张空卡。 */ export function flattenImageVideoTasks(items: readonly ImageVideoRow[]): ImageVideoCard[] { const cards: ImageVideoCard[] = [] diff --git a/admin-frontend-vue/tests/align-image-video.test.ts b/admin-frontend-vue/tests/align-image-video.test.ts new file mode 100644 index 00000000..89d4b09c --- /dev/null +++ b/admin-frontend-vue/tests/align-image-video.test.ts @@ -0,0 +1,63 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readSource } from './helpers.ts' +import { + imageVideoGeneratedAt, + imageVideoHasDisplayUrl, + toImageVideoRow, + type ImageVideoCard, + type ImageVideoRow, +} from '../src/pages/tasks/image-video-model.ts' + +/** 对齐 admin.js:1328-1353 renderImageVideoCard:任务模式/生成时间(completed||submitted)/调试链接复制/下载禁用。 */ + +function row(overrides: Partial = {}): ImageVideoRow { + return { + taskId: '1', + username: '张三', + groupName: 'A组', + status: 'RUNNING', + cozeStatus: '', + cozeExecuteId: '', + videos: [], + videoUrl: '', + debugUrl: '', + archiveStatus: '', + archiveError: '', + ...overrides, + } +} + +test('align_image_video_mode_parsed_from_row', () => { + const task = toImageVideoRow({ task_id: 7, mode: 'text2video' }) + assert.equal(task?.mode, 'text2video') + assert.equal(toImageVideoRow({ task_id: 8 })?.mode, undefined) +}) + +test('align_image_video_generated_at_completed_first', () => { + assert.equal(imageVideoGeneratedAt(row({ submittedAt: '2026-09-01 10:00:00', completedAt: '2026-09-01 10:05:00' })), '2026-09-01 10:05:00') + assert.equal(imageVideoGeneratedAt(row({ submittedAt: '2026-09-01 10:00:00' })), '2026-09-01 10:00:00') + assert.equal(imageVideoGeneratedAt(row()), '-') +}) + +test('align_image_video_has_display_url', () => { + const card: ImageVideoCard = { + key: '1:0', + task: row(), + video: { sourceUrl: '', archivedUrl: '', displayUrl: 'https://cdn/1.mp4', objectKey: '', archiveStatus: '', archiveError: '' }, + videoIndex: 0, + } + assert.equal(imageVideoHasDisplayUrl(card), true) + assert.equal(imageVideoHasDisplayUrl({ ...card, video: null }), false) +}) + +test('align_image_video_page_card_info_wiring', () => { + const page = readSource('src/pages/tasks/ImageVideoTasksPage.vue') + assert.match(page, /任务模式/, '卡片补任务模式信息行') + assert.match(page, /所属分组/, '卡片信息行对齐参考') + assert.match(page, /生成时间/, '生成时间口径 completed||submitted') + assert.match(page, /imageVideoGeneratedAt/, '生成时间走纯模型') + assert.match(page, /调试链接/, '卡片补调试链接行') + assert.match(page, /copyLink\(task\.debugUrl\)/, '调试链接复制接线') + assert.match(page, /:disabled="!video\.displayUrl"|!hasDisplayUrl/, '下载按钮无视频禁用') +})