align(视频任务): 卡片补任务模式/生成时间(completed||submitted)/调试链接复制信息行、下载按钮无视频禁用、明细补模式/生成时间/调试链接(对齐 admin.js renderImageVideoCard/copyImageVideoDebugUrl)
This commit is contained in:
@@ -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)
|
||||
<el-checkbox :model-value="cardSelected(task)" :disabled="!hasVideos(task)" @change="toggleCard(task)" />
|
||||
<div class="task-card-title">
|
||||
<span class="task-id">任务 {{ task.taskId }}</span>
|
||||
<span class="task-user">{{ task.username || '—' }}</span>
|
||||
<span v-if="task.groupName" class="task-group">{{ task.groupName }}</span>
|
||||
</div>
|
||||
<el-tag :type="statusType(task.status)" size="small">{{ statusLabel(task.status) }}</el-tag>
|
||||
<span class="task-time">{{ formatDateTime(task.submittedAt) }}</span>
|
||||
<el-button text type="primary" size="small" @click="openDetail(task)">明细</el-button>
|
||||
</header>
|
||||
|
||||
<div class="task-card-info">
|
||||
<div class="task-info-row"><label>用户名</label><span>{{ task.username || '-' }}</span></div>
|
||||
<div class="task-info-row"><label>所属分组</label><span>{{ task.groupName || '-' }}</span></div>
|
||||
<div class="task-info-row"><label>任务模式</label><span>{{ task.mode || '-' }}</span></div>
|
||||
<div class="task-info-row"><label>生成时间</label><span>{{ formatDateTime(imageVideoGeneratedAt(task)) }}</span></div>
|
||||
<div class="task-info-row">
|
||||
<label>调试链接</label>
|
||||
<el-button v-if="task.debugUrl" text type="primary" size="small" @click="copyLink(task.debugUrl)">复制链接</el-button>
|
||||
<span v-else>-</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasVideos(task)" class="video-list">
|
||||
<div v-for="(video, index) in task.videos" :key="`${task.taskId}-${index}`" class="video-row">
|
||||
<el-checkbox :model-value="selection.has(`${task.taskId}:${index}`)" @change="toggleVideoKey(`${task.taskId}:${index}`)" />
|
||||
@@ -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)
|
||||
<el-descriptions-item label="任务ID">{{ detail.taskId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用户">{{ detail.username }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">{{ statusLabel(detail.status) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务模式">{{ detail.mode || '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="提交时间">{{ formatDateTime(detail.submittedAt) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="生成时间">{{ formatDateTime(imageVideoGeneratedAt(detail)) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="调试链接">
|
||||
<el-button v-if="detail.debugUrl" text type="primary" size="small" @click="copyLink(detail.debugUrl)">复制链接</el-button>
|
||||
<span v-else>—</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="执行ID">{{ detail.cozeExecuteId || '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="错误">{{ detail.errorMessage || '—' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
@@ -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; }
|
||||
|
||||
@@ -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<ImageVideoRow, 'completedAt' | 'submittedAt'>): string {
|
||||
return task.completedAt || task.submittedAt || '-'
|
||||
}
|
||||
|
||||
/** 卡片是否带可播放/可下载视频(对齐 admin.js hasVideo 判定)。 */
|
||||
export function imageVideoHasDisplayUrl(card: Pick<ImageVideoCard, 'video'>): boolean {
|
||||
return !!(card.video && card.video.displayUrl)
|
||||
}
|
||||
|
||||
/** 把任务行展平为“任务:视频下标”卡片;无 videos 的任务展为一张空卡。 */
|
||||
export function flattenImageVideoTasks(items: readonly ImageVideoRow[]): ImageVideoCard[] {
|
||||
const cards: ImageVideoCard[] = []
|
||||
|
||||
@@ -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> = {}): 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/, '下载按钮无视频禁用')
|
||||
})
|
||||
Reference in New Issue
Block a user