feat: add Claude AI skills for shadcn theming and image generation tools
This commit introduces comprehensive Claude AI skill configurations for: - shadcn/ui theming with OKLCH color space support - Gemini API integration for image generation and chat capabilities - Batch processing and multi-turn conversation features - File handling utilities for image processing workflows
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<div class="task-page">
|
||||
<!-- 筛选条件区域 -->
|
||||
<div v-if="$slots.filters" class="task-page__filters">
|
||||
<slot name="filters" />
|
||||
</div>
|
||||
|
||||
<!-- 任务列表内容区域 -->
|
||||
<div class="task-page__content">
|
||||
<!-- 批量操作栏 -->
|
||||
<div v-if="$slots['batch-actions']" class="batch-actions">
|
||||
<slot name="batch-actions" />
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="task-page__table">
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="task-page__loading">
|
||||
<Spinner class="size-8" />
|
||||
</div>
|
||||
|
||||
<!-- 表格内容 -->
|
||||
<div class="task-page__table-wrapper">
|
||||
<slot name="table" />
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<TablePagination
|
||||
v-if="showPagination && total > 0"
|
||||
:current="current"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
@change="$emit('page-change', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗插槽 -->
|
||||
<slot name="modals" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Spinner } from '@/components/ui/spinner'
|
||||
import { TablePagination } from '@/components/ui/pagination'
|
||||
|
||||
defineProps({
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showPagination: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
current: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
defineEmits(['page-change'])
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.task-page {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.task-page__filters {
|
||||
flex-shrink: 0;
|
||||
padding: var(--space-5);
|
||||
background: var(--card);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.task-page__content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background: var(--card);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border);
|
||||
padding: var(--space-5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.batch-actions {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.task-page__table {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.task-page__loading {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--background);
|
||||
opacity: 0.5;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.task-page__table-wrapper {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,13 @@
|
||||
<template>
|
||||
<div class="task-page">
|
||||
<TaskPageLayout
|
||||
:loading="loading"
|
||||
:current="paginationConfig.current"
|
||||
:page-size="paginationConfig.pageSize"
|
||||
:total="paginationConfig.total"
|
||||
@page-change="handlePageChange"
|
||||
>
|
||||
<!-- 筛选条件 -->
|
||||
<div class="task-page__filters">
|
||||
<template #filters>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<!-- 状态筛选 -->
|
||||
<Select v-model="filters.status" @update:model-value="handleFilterChange">
|
||||
@@ -60,148 +66,135 @@
|
||||
重置
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 任务列表 -->
|
||||
<div class="task-page__content">
|
||||
<!-- 批量操作栏 -->
|
||||
<div v-if="selectedRowKeys.length > 0" class="batch-actions">
|
||||
<Alert class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon icon="lucide:info" class="size-4" />
|
||||
<span>已选中 {{ selectedRowKeys.length }} 项</span>
|
||||
</div>
|
||||
<Button variant="destructive" size="sm" @click="confirmBatchDelete">
|
||||
<Icon icon="lucide:trash-2" class="mr-1 size-4" />
|
||||
批量删除
|
||||
</Button>
|
||||
</Alert>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<div v-if="loading" class="absolute inset-0 flex items-center justify-center bg-background/50 z-10">
|
||||
<Spinner class="size-8" />
|
||||
<!-- 批量操作栏 -->
|
||||
<template #batch-actions>
|
||||
<Alert v-if="selectedRowKeys.length > 0" class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon icon="lucide:info" class="size-4" />
|
||||
<span>已选中 {{ selectedRowKeys.length }} 项</span>
|
||||
</div>
|
||||
<Button variant="destructive" size="sm" @click="confirmBatchDelete">
|
||||
<Icon icon="lucide:trash-2" class="mr-1 size-4" />
|
||||
批量删除
|
||||
</Button>
|
||||
</Alert>
|
||||
</template>
|
||||
|
||||
<div class="overflow-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="w-[50px]">
|
||||
<Checkbox
|
||||
:checked="isAllSelected"
|
||||
@update:checked="handleSelectAll"
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead class="w-[80px]">ID</TableHead>
|
||||
<TableHead class="min-w-[200px]">任务名称</TableHead>
|
||||
<TableHead class="w-[100px]">状态</TableHead>
|
||||
<TableHead class="w-[150px]">进度</TableHead>
|
||||
<TableHead class="w-[180px]">创建时间</TableHead>
|
||||
<TableHead class="w-[180px] sticky right-0 bg-background">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="record in list" :key="record.id">
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
:checked="selectedRowKeys.includes(record.id)"
|
||||
@update:checked="handleSelectRow(record.id)"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{{ record.id }}</TableCell>
|
||||
<TableCell>
|
||||
<span class="font-medium">{{ record.taskName }}</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TaskStatusTag :status="record.status" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="w-[120px]">
|
||||
<Progress :model-value="record.progress" class="h-2" />
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{{ formatDate(record.createTime) }}</TableCell>
|
||||
<TableCell class="sticky right-0 bg-background">
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
v-if="isStatus(record.status, 'success')"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2 text-primary hover:text-primary/80"
|
||||
@click="openVideoUrl(record)"
|
||||
>
|
||||
<Icon icon="lucide:play-circle" class="mr-1 size-4" />
|
||||
预览
|
||||
</Button>
|
||||
<Button
|
||||
v-if="isStatus(record.status, 'success')"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2 text-success hover:text-success/80"
|
||||
@click="openVideoUrl(record)"
|
||||
>
|
||||
<Icon icon="lucide:download" class="mr-1 size-4" />
|
||||
下载
|
||||
</Button>
|
||||
<Button
|
||||
v-if="isStatus(record.status, 'running')"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2"
|
||||
@click="handleCancel(record.id)"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2 text-destructive hover:text-destructive/80 hover:bg-destructive/10"
|
||||
@click="handleDelete(record.id)"
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow v-if="list.length === 0 && !loading">
|
||||
<TableCell colspan="7" class="h-32 text-center text-muted-foreground">
|
||||
暂无数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<!-- 表格 -->
|
||||
<template #table>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="w-[50px]">
|
||||
<Checkbox
|
||||
:checked="isAllSelected"
|
||||
@update:checked="handleSelectAll"
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead class="w-[80px]">ID</TableHead>
|
||||
<TableHead class="min-w-[200px]">任务名称</TableHead>
|
||||
<TableHead class="w-[100px]">状态</TableHead>
|
||||
<TableHead class="w-[150px]">进度</TableHead>
|
||||
<TableHead class="w-[180px]">创建时间</TableHead>
|
||||
<TableHead class="w-[180px] sticky right-0 bg-background">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="record in list" :key="record.id">
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
:checked="selectedRowKeys.includes(record.id)"
|
||||
@update:checked="handleSelectRow(record.id)"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{{ record.id }}</TableCell>
|
||||
<TableCell>
|
||||
<span class="font-medium">{{ record.taskName }}</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TaskStatusTag :status="record.status" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="w-[120px]">
|
||||
<Progress :model-value="record.progress" class="h-2" />
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{{ formatDate(record.createTime) }}</TableCell>
|
||||
<TableCell class="sticky right-0 bg-background">
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
v-if="isStatus(record.status, 'success')"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2 text-primary hover:text-primary/80"
|
||||
@click="openVideoUrl(record)"
|
||||
>
|
||||
<Icon icon="lucide:play-circle" class="mr-1 size-4" />
|
||||
预览
|
||||
</Button>
|
||||
<Button
|
||||
v-if="isStatus(record.status, 'success')"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2 text-success hover:text-success/80"
|
||||
@click="openVideoUrl(record)"
|
||||
>
|
||||
<Icon icon="lucide:download" class="mr-1 size-4" />
|
||||
下载
|
||||
</Button>
|
||||
<Button
|
||||
v-if="isStatus(record.status, 'running')"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2"
|
||||
@click="handleCancel(record.id)"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2 text-destructive hover:text-destructive/80 hover:bg-destructive/10"
|
||||
@click="handleDelete(record.id)"
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow v-if="list.length === 0 && !loading">
|
||||
<TableCell colspan="7" class="h-32 text-center text-muted-foreground">
|
||||
暂无数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</template>
|
||||
|
||||
<!-- 分页 -->
|
||||
<TablePagination
|
||||
:current="paginationConfig.current"
|
||||
:page-size="paginationConfig.pageSize"
|
||||
:total="paginationConfig.total"
|
||||
@change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 确认删除对话框 -->
|
||||
<AlertDialog :open="deleteDialogOpen" @update:open="deleteDialogOpen = $event">
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>确认删除</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
确定要删除选中的任务吗?此操作无法撤销。
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel @click="deleteDialogOpen = false">取消</AlertDialogCancel>
|
||||
<AlertDialogAction @click="handleBatchDelete" :disabled="deleteLoading">
|
||||
<Spinner v-if="deleteLoading" class="mr-2 size-4" />
|
||||
确认删除
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
<!-- 弹窗 -->
|
||||
<template #modals>
|
||||
<!-- 确认删除对话框 -->
|
||||
<AlertDialog :open="deleteDialogOpen" @update:open="deleteDialogOpen = $event">
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>确认删除</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
确定要删除选中的任务吗?此操作无法撤销。
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel @click="deleteDialogOpen = false">取消</AlertDialogCancel>
|
||||
<AlertDialogAction @click="handleBatchDelete" :disabled="deleteLoading">
|
||||
<Spinner v-if="deleteLoading" class="mr-2 size-4" />
|
||||
确认删除
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</template>
|
||||
</TaskPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -218,7 +211,6 @@ import { Progress } from '@/components/ui/progress'
|
||||
import { Alert } from '@/components/ui/alert'
|
||||
import { Spinner } from '@/components/ui/spinner'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { TablePagination } from '@/components/ui/pagination'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -235,27 +227,20 @@ import { useTaskList } from '@/views/system/task-management/composables/useTaskL
|
||||
import { useTaskOperations } from '@/views/system/task-management/composables/useTaskOperations'
|
||||
import { useTaskPolling } from '@/views/system/task-management/composables/useTaskPolling'
|
||||
import TaskStatusTag from '@/views/system/task-management/components/TaskStatusTag.vue'
|
||||
import TaskPageLayout from '@/views/system/task-management/components/TaskPageLayout.vue'
|
||||
|
||||
// 日期选择器开关
|
||||
const datePickerOpen = ref(false)
|
||||
const selectedDateRange = ref(null)
|
||||
|
||||
// Composables
|
||||
const { loading, list, filters, paginationConfig, fetchList, handleFilterChange, handleResetFilters, handleTableChange } = useTaskList(getDigitalHumanTaskPage)
|
||||
const { loading, list, filters, paginationConfig, fetchList, handleFilterChange, handleResetFilters } = useTaskList(getDigitalHumanTaskPage)
|
||||
|
||||
// 初始化 filters.status 为 'all'
|
||||
if (!filters.status) {
|
||||
filters.status = 'all'
|
||||
}
|
||||
|
||||
// 包装 handleFilterChange 处理 'all' 值
|
||||
const wrappedHandleFilterChange = () => {
|
||||
const params = { ...filters }
|
||||
if (params.status === 'all') {
|
||||
params.status = undefined
|
||||
}
|
||||
handleFilterChange()
|
||||
}
|
||||
const { handleDelete: deleteTaskById, handleCancel } = useTaskOperations({ deleteApi: deleteTask, cancelApi: cancelTask }, fetchList)
|
||||
useTaskPolling(getDigitalHumanTaskPage, { onTaskUpdate: fetchList })
|
||||
|
||||
@@ -338,14 +323,14 @@ const handleDelete = (id) => {
|
||||
// 处理日期选择
|
||||
const handleDateSelect = (value) => {
|
||||
if (value?.start && value?.end) {
|
||||
const formatDate = (date) => {
|
||||
const formatDateStr = (date) => {
|
||||
const d = new Date(date)
|
||||
const year = d.getFullYear()
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
filters.dateRange = [formatDate(value.start), formatDate(value.end)]
|
||||
filters.dateRange = [formatDateStr(value.start), formatDateStr(value.end)]
|
||||
datePickerOpen.value = false
|
||||
handleFilterChange()
|
||||
}
|
||||
@@ -355,34 +340,4 @@ onMounted(fetchList)
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.task-page {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.task-page__filters {
|
||||
flex-shrink: 0;
|
||||
padding: var(--space-5);
|
||||
background: var(--card);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.task-page__content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background: var(--card);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border);
|
||||
padding: var(--space-5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.batch-actions {
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="task-layout">
|
||||
<div class="task-layout p-4">
|
||||
<!-- 顶部Tab栏 -->
|
||||
<div class="task-layout__header">
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -59,7 +59,7 @@ const NAV_ITEMS = [
|
||||
type: 'style-task',
|
||||
label: '风格任务',
|
||||
icon: 'lucide:palette',
|
||||
component: markRaw(defineAsyncComponent(() => import('../../../task-center/BenchmarkTaskList.vue')))
|
||||
component: markRaw(defineAsyncComponent(() => import('../task-center/BenchmarkTaskList.vue')))
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<template>
|
||||
<div class="task-page">
|
||||
<TaskPageLayout
|
||||
:loading="loading"
|
||||
:current="paginationConfig.current"
|
||||
:page-size="paginationConfig.pageSize"
|
||||
:total="paginationConfig.total"
|
||||
@page-change="handlePageChange"
|
||||
>
|
||||
<!-- 筛选条件 -->
|
||||
<div class="task-page__filters">
|
||||
<template #filters>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<!-- 状态筛选 -->
|
||||
<Select v-model="filters.status" @update:model-value="handleFilterChange">
|
||||
@@ -59,246 +65,236 @@
|
||||
重置
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 任务列表 -->
|
||||
<div class="task-page__content">
|
||||
<!-- 批量操作栏 -->
|
||||
<div v-if="selectedRowKeys.length > 0" class="batch-actions">
|
||||
<Alert class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon icon="lucide:info" class="size-4" />
|
||||
<span>已选中 {{ selectedRowKeys.length }} 项</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
:disabled="!hasDownloadableSelected"
|
||||
:loading="batchDownloading"
|
||||
size="sm"
|
||||
@click="handleBatchDownloadSelected"
|
||||
>
|
||||
<Icon icon="lucide:download" class="mr-1 size-4" />
|
||||
批量下载 ({{ downloadableCount }})
|
||||
</Button>
|
||||
<Button variant="destructive" size="sm" @click="handleBatchDeleteSelected">
|
||||
<Icon icon="lucide:trash-2" class="mr-1 size-4" />
|
||||
批量删除
|
||||
</Button>
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
|
||||
<div class="relative min-h-[200px]">
|
||||
<Spinner v-if="loading" class="absolute inset-0 z-10 m-auto size-8" />
|
||||
<div class="overflow-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="w-[50px]">
|
||||
<Checkbox
|
||||
:checked="isAllSelected"
|
||||
@update:checked="handleSelectAll"
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead class="w-[70px]">ID</TableHead>
|
||||
<TableHead>标题</TableHead>
|
||||
<TableHead class="w-[90px]">状态</TableHead>
|
||||
<TableHead class="w-[100px]">生成结果</TableHead>
|
||||
<TableHead class="w-[160px]">创建时间</TableHead>
|
||||
<TableHead class="w-[160px]">完成时间</TableHead>
|
||||
<TableHead class="w-[240px] sticky right-0 bg-background">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<template v-for="record in list" :key="record.id">
|
||||
<TableRow
|
||||
class="cursor-pointer hover:bg-muted/50"
|
||||
@click="toggleExpand(record.id)"
|
||||
>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
:checked="selectedRowKeys.includes(record.id)"
|
||||
@update:checked.stop="handleSelectRow(record.id)"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{{ record.id }}</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon
|
||||
:icon="expandedRowKeys.includes(record.id) ? 'lucide:chevron-down' : 'lucide:chevron-right'"
|
||||
class="size-4 text-muted-foreground"
|
||||
/>
|
||||
<span class="font-medium">{{ record.title }}</span>
|
||||
<Badge v-if="record.text" variant="secondary" class="text-xs">有文案</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TaskStatusTag :status="record.status" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge v-if="record.outputUrls?.length" variant="success">
|
||||
{{ record.outputUrls.length }} 个视频
|
||||
</Badge>
|
||||
<span v-else class="text-muted-foreground">-</span>
|
||||
</TableCell>
|
||||
<TableCell>{{ formatDate(record.createTime) }}</TableCell>
|
||||
<TableCell>{{ record.finishTime ? formatDate(record.finishTime) : '-' }}</TableCell>
|
||||
<TableCell @click.stop>
|
||||
<div class="flex items-center gap-1">
|
||||
<Button
|
||||
v-if="canOperate(record, 'preview')"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2 text-primary hover:text-primary/80"
|
||||
@click="openPreview(record)"
|
||||
>
|
||||
<Icon icon="lucide:play-circle" class="mr-1 size-4" />
|
||||
预览
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canOperate(record, 'download')"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2 text-success hover:text-success/80"
|
||||
@click="handleDownload(record)"
|
||||
>
|
||||
<Icon icon="lucide:download" class="mr-1 size-4" />
|
||||
下载
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canOperate(record, 'cancel')"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="h-7 px-2"
|
||||
@click="handleCancel(record.id)"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canOperate(record, 'retry')"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="h-7 px-2"
|
||||
@click="handleRetry(record.id)"
|
||||
>
|
||||
重试
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2 text-destructive hover:text-destructive/80 hover:bg-destructive/10"
|
||||
@click="handleDeleteClick(record.id)"
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<!-- 展开行 -->
|
||||
<TableRow v-if="expandedRowKeys.includes(record.id)" class="bg-muted/30">
|
||||
<TableCell colspan="8" class="p-0">
|
||||
<div class="expanded-content">
|
||||
<div v-if="record.text" class="task-text">
|
||||
<strong>文案内容:</strong>
|
||||
<p>{{ record.text }}</p>
|
||||
</div>
|
||||
<div v-if="record.outputUrls?.length" class="task-results">
|
||||
<div class="result-header">
|
||||
<strong>生成结果:</strong>
|
||||
<span class="result-count">{{ record.outputUrls.length }} 个视频</span>
|
||||
</div>
|
||||
<div class="result-list">
|
||||
<div v-for="(_, index) in record.outputUrls" :key="index" class="result-item">
|
||||
<Button
|
||||
v-if="isStatus(record.status, 'success')"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2"
|
||||
@click="previewVideo(record, index)"
|
||||
>
|
||||
<Icon icon="lucide:play-circle" class="mr-1 size-4" />
|
||||
视频 {{ index + 1 }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="isStatus(record.status, 'success')"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2"
|
||||
@click="downloadVideo(record.id, index)"
|
||||
>
|
||||
<Icon icon="lucide:download" class="size-4" />
|
||||
</Button>
|
||||
<span v-else class="text-muted-foreground text-sm">视频 {{ index + 1 }} (处理中...)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Alert v-if="record.errorMsg" variant="destructive" class="mt-3">
|
||||
<Icon icon="lucide:alert-circle" class="size-4" />
|
||||
<AlertDescription>{{ record.errorMsg }}</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
<TableRow v-if="list.length === 0 && !loading">
|
||||
<TableCell colspan="8" class="h-32 text-center text-muted-foreground">
|
||||
暂无数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<!-- 批量操作栏 -->
|
||||
<template #batch-actions>
|
||||
<Alert v-if="selectedRowKeys.length > 0" class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon icon="lucide:info" class="size-4" />
|
||||
<span>已选中 {{ selectedRowKeys.length }} 项</span>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<TablePagination
|
||||
:current="paginationConfig.current"
|
||||
:page-size="paginationConfig.pageSize"
|
||||
:total="paginationConfig.total"
|
||||
@change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预览模态框 -->
|
||||
<Dialog v-model:open="preview.visible">
|
||||
<DialogContent class="max-w-[800px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ preview.title }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div v-if="preview.url" class="preview-container">
|
||||
<video :src="preview.url" controls autoplay class="preview-video">
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
</div>
|
||||
<div v-else class="preview-loading">
|
||||
<Spinner class="size-6" />
|
||||
<span class="text-muted-foreground mt-2">正在加载预览...</span>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<!-- 删除确认对话框 -->
|
||||
<AlertDialog v-model:open="deleteDialogOpen">
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>确认批量删除</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
确定要删除选中的 {{ selectedRowKeys.length }} 个任务吗?删除后无法恢复。
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
:disabled="deleteLoading"
|
||||
class="bg-destructive hover:bg-destructive/90"
|
||||
@click="confirmBatchDelete"
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
:disabled="!hasDownloadableSelected"
|
||||
:loading="batchDownloading"
|
||||
size="sm"
|
||||
@click="handleBatchDownloadSelected"
|
||||
>
|
||||
确定删除
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
<Icon icon="lucide:download" class="mr-1 size-4" />
|
||||
批量下载 ({{ downloadableCount }})
|
||||
</Button>
|
||||
<Button variant="destructive" size="sm" @click="handleBatchDeleteSelected">
|
||||
<Icon icon="lucide:trash-2" class="mr-1 size-4" />
|
||||
批量删除
|
||||
</Button>
|
||||
</div>
|
||||
</Alert>
|
||||
</template>
|
||||
|
||||
<!-- 表格 -->
|
||||
<template #table>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="w-[50px]">
|
||||
<Checkbox
|
||||
:checked="isAllSelected"
|
||||
@update:checked="handleSelectAll"
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead class="w-[70px]">ID</TableHead>
|
||||
<TableHead>标题</TableHead>
|
||||
<TableHead class="w-[90px]">状态</TableHead>
|
||||
<TableHead class="w-[100px]">生成结果</TableHead>
|
||||
<TableHead class="w-[160px]">创建时间</TableHead>
|
||||
<TableHead class="w-[160px]">完成时间</TableHead>
|
||||
<TableHead class="w-[240px] sticky right-0 bg-background">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<template v-for="record in list" :key="record.id">
|
||||
<TableRow
|
||||
class="cursor-pointer hover:bg-muted/50"
|
||||
@click="toggleExpand(record.id)"
|
||||
>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
:checked="selectedRowKeys.includes(record.id)"
|
||||
@update:checked.stop="handleSelectRow(record.id)"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{{ record.id }}</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon
|
||||
:icon="expandedRowKeys.includes(record.id) ? 'lucide:chevron-down' : 'lucide:chevron-right'"
|
||||
class="size-4 text-muted-foreground"
|
||||
/>
|
||||
<span class="font-medium">{{ record.title }}</span>
|
||||
<Badge v-if="record.text" variant="secondary" class="text-xs">有文案</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TaskStatusTag :status="record.status" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge v-if="record.outputUrls?.length" variant="success">
|
||||
{{ record.outputUrls.length }} 个视频
|
||||
</Badge>
|
||||
<span v-else class="text-muted-foreground">-</span>
|
||||
</TableCell>
|
||||
<TableCell>{{ formatDate(record.createTime) }}</TableCell>
|
||||
<TableCell>{{ record.finishTime ? formatDate(record.finishTime) : '-' }}</TableCell>
|
||||
<TableCell @click.stop>
|
||||
<div class="flex items-center gap-1">
|
||||
<Button
|
||||
v-if="canOperate(record, 'preview')"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2 text-primary hover:text-primary/80"
|
||||
@click="openPreview(record)"
|
||||
>
|
||||
<Icon icon="lucide:play-circle" class="mr-1 size-4" />
|
||||
预览
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canOperate(record, 'download')"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2 text-success hover:text-success/80"
|
||||
@click="handleDownload(record)"
|
||||
>
|
||||
<Icon icon="lucide:download" class="mr-1 size-4" />
|
||||
下载
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canOperate(record, 'cancel')"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="h-7 px-2"
|
||||
@click="handleCancel(record.id)"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canOperate(record, 'retry')"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="h-7 px-2"
|
||||
@click="handleRetry(record.id)"
|
||||
>
|
||||
重试
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2 text-destructive hover:text-destructive/80 hover:bg-destructive/10"
|
||||
@click="handleDeleteClick(record.id)"
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<!-- 展开行 -->
|
||||
<TableRow v-if="expandedRowKeys.includes(record.id)" class="bg-muted/30">
|
||||
<TableCell colspan="8" class="p-0">
|
||||
<div class="expanded-content">
|
||||
<div v-if="record.text" class="task-text">
|
||||
<strong>文案内容:</strong>
|
||||
<p>{{ record.text }}</p>
|
||||
</div>
|
||||
<div v-if="record.outputUrls?.length" class="task-results">
|
||||
<div class="result-header">
|
||||
<strong>生成结果:</strong>
|
||||
<span class="result-count">{{ record.outputUrls.length }} 个视频</span>
|
||||
</div>
|
||||
<div class="result-list">
|
||||
<div v-for="(_, index) in record.outputUrls" :key="index" class="result-item">
|
||||
<Button
|
||||
v-if="isStatus(record.status, 'success')"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2"
|
||||
@click="previewVideo(record, index)"
|
||||
>
|
||||
<Icon icon="lucide:play-circle" class="mr-1 size-4" />
|
||||
视频 {{ index + 1 }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="isStatus(record.status, 'success')"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-7 px-2"
|
||||
@click="downloadVideo(record.id, index)"
|
||||
>
|
||||
<Icon icon="lucide:download" class="size-4" />
|
||||
</Button>
|
||||
<span v-else class="text-muted-foreground text-sm">视频 {{ index + 1 }} (处理中...)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Alert v-if="record.errorMsg" variant="destructive" class="mt-3">
|
||||
<Icon icon="lucide:alert-circle" class="size-4" />
|
||||
<AlertDescription>{{ record.errorMsg }}</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
<TableRow v-if="list.length === 0 && !loading">
|
||||
<TableCell colspan="8" class="h-32 text-center text-muted-foreground">
|
||||
暂无数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</template>
|
||||
|
||||
<!-- 弹窗 -->
|
||||
<template #modals>
|
||||
<!-- 预览模态框 -->
|
||||
<Dialog v-model:open="preview.visible">
|
||||
<DialogContent class="max-w-[800px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ preview.title }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div v-if="preview.url" class="preview-container">
|
||||
<video :src="preview.url" controls autoplay class="preview-video">
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
</div>
|
||||
<div v-else class="preview-loading">
|
||||
<Spinner class="size-6" />
|
||||
<span class="text-muted-foreground mt-2">正在加载预览...</span>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<!-- 删除确认对话框 -->
|
||||
<AlertDialog v-model:open="deleteDialogOpen">
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>确认批量删除</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
确定要删除选中的 {{ selectedRowKeys.length }} 个任务吗?删除后无法恢复。
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
:disabled="deleteLoading"
|
||||
class="bg-destructive hover:bg-destructive/90"
|
||||
@click="confirmBatchDelete"
|
||||
>
|
||||
确定删除
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</template>
|
||||
</TaskPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -331,26 +327,26 @@ import {
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import { TablePagination } from '@/components/ui/pagination'
|
||||
import { MixTaskService } from '@/api/mixTask'
|
||||
import { formatDate } from '@/utils/file'
|
||||
import { useTaskList } from '@/views/system/task-management/composables/useTaskList'
|
||||
import { useTaskOperations } from '@/views/system/task-management/composables/useTaskOperations'
|
||||
import { useTaskPolling } from '@/views/system/task-management/composables/useTaskPolling'
|
||||
import TaskStatusTag from '@/views/system/task-management/components/TaskStatusTag.vue'
|
||||
import TaskPageLayout from '@/views/system/task-management/components/TaskPageLayout.vue'
|
||||
|
||||
// 日期选择器开关
|
||||
const datePickerOpen = ref(false)
|
||||
const selectedDateRange = ref(null)
|
||||
|
||||
// Composables
|
||||
const { loading, list, filters, paginationConfig, fetchList, handleFilterChange, handleResetFilters, handleTableChange } = useTaskList(MixTaskService.getTaskPage)
|
||||
const { loading, list, filters, paginationConfig, fetchList, handleFilterChange, handleResetFilters } = useTaskList(MixTaskService.getTaskPage)
|
||||
|
||||
// 初始化 filters.status 为 'all'
|
||||
if (!filters.status) {
|
||||
filters.status = 'all'
|
||||
}
|
||||
const { handleDelete, handleCancel, handleRetry, handleBatchDownload } = useTaskOperations(
|
||||
const { handleCancel, handleRetry, handleBatchDownload } = useTaskOperations(
|
||||
{ deleteApi: MixTaskService.deleteTask, cancelApi: MixTaskService.cancelTask, retryApi: MixTaskService.retryTask, getSignedUrlsApi: MixTaskService.getSignedUrls },
|
||||
fetchList
|
||||
)
|
||||
@@ -555,14 +551,14 @@ const handleDownload = (record) => {
|
||||
// 处理日期选择
|
||||
const handleDateSelect = (value) => {
|
||||
if (value?.start && value?.end) {
|
||||
const formatDate = (date) => {
|
||||
const formatDateStr = (date) => {
|
||||
const d = new Date(date)
|
||||
const year = d.getFullYear()
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
filters.dateRange = [formatDate(value.start), formatDate(value.end)]
|
||||
filters.dateRange = [formatDateStr(value.start), formatDateStr(value.end)]
|
||||
datePickerOpen.value = false
|
||||
handleFilterChange()
|
||||
}
|
||||
@@ -578,34 +574,6 @@ onMounted(fetchList)
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.task-page {
|
||||
padding: var(--space-4);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.task-page__filters {
|
||||
padding: var(--space-4);
|
||||
background: var(--color-bg-card);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.task-page__content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background: var(--color-bg-card);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.batch-actions {
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.expanded-content {
|
||||
padding: var(--space-5);
|
||||
background: var(--muted);
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
<template>
|
||||
<TaskPageLayout
|
||||
:loading="loading"
|
||||
:current="pagination.current"
|
||||
:page-size="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
@page-change="handlePageChange"
|
||||
>
|
||||
<!-- 筛选条件 -->
|
||||
<template #filters>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<Select v-model="filterStatus" @update:model-value="handleFilterChange">
|
||||
<SelectTrigger class="w-[150px]">
|
||||
<SelectValue placeholder="全部状态" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="null">全部状态</SelectItem>
|
||||
<SelectItem :value="0">待处理</SelectItem>
|
||||
<SelectItem :value="1">处理中</SelectItem>
|
||||
<SelectItem :value="2">成功</SelectItem>
|
||||
<SelectItem :value="3">失败</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button @click="handleRefresh" :disabled="loading">
|
||||
<Icon v-if="loading" icon="lucide:loader-2" class="size-4 animate-spin" />
|
||||
<Icon v-else icon="lucide:refresh-cw" class="size-4" />
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 表格 -->
|
||||
<template #table>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="min-w-[200px]">任务名称</TableHead>
|
||||
<TableHead class="w-[100px]">视频数量</TableHead>
|
||||
<TableHead class="w-[100px]">状态</TableHead>
|
||||
<TableHead class="w-[150px]">进度</TableHead>
|
||||
<TableHead class="w-[180px]">创建时间</TableHead>
|
||||
<TableHead class="w-[180px]">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="record in taskList" :key="record.id">
|
||||
<TableCell class="font-medium">
|
||||
<span class="truncate block max-w-[200px]" :title="record.taskName">
|
||||
{{ record.taskName }}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>{{ record.videoCount }}</TableCell>
|
||||
<TableCell>
|
||||
<Badge :variant="getStatusVariant(record.status)" :class="STATUS_MAP.class[record.status]">
|
||||
{{ getStatusText(record.status) }}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Progress :model-value="record.progress" class="h-2" />
|
||||
</TableCell>
|
||||
<TableCell class="text-muted-foreground">
|
||||
{{ formatTime(record.createTime) }}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex gap-1">
|
||||
<Button
|
||||
v-if="record.status === 2 && record.generatedPrompt"
|
||||
variant="link"
|
||||
size="sm"
|
||||
class="h-auto p-0"
|
||||
@click="handleViewPrompt(record)"
|
||||
>
|
||||
查看
|
||||
</Button>
|
||||
<Button
|
||||
v-if="record.status === 2 && record.generatedPrompt"
|
||||
variant="link"
|
||||
size="sm"
|
||||
class="h-auto p-0"
|
||||
@click="handleCopyPrompt(record)"
|
||||
>
|
||||
复制
|
||||
</Button>
|
||||
<AlertDialog v-if="record.status !== 1">
|
||||
<AlertDialogTrigger as-child>
|
||||
<Button variant="link" size="sm" class="h-auto p-0 text-destructive">
|
||||
删除
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>确认删除</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
确定要删除这个任务吗?此操作无法撤销。
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogAction @click="handleDelete(record)">确认删除</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow v-if="taskList.length === 0 && !loading">
|
||||
<TableCell colspan="6" class="h-32 text-center text-muted-foreground">
|
||||
暂无任务数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</template>
|
||||
|
||||
<!-- 弹窗 -->
|
||||
<template #modals>
|
||||
<!-- 提示词弹窗 -->
|
||||
<Dialog v-model:open="promptModalVisible">
|
||||
<DialogContent class="sm:max-w-[700px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>生成的提示词</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="prompt-content">{{ currentPrompt }}</div>
|
||||
<DialogFooter>
|
||||
<Button @click="handleCopyCurrentPrompt">
|
||||
<Icon icon="lucide:copy" class="size-4" />
|
||||
复制到剪贴板
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
</TaskPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from '@/components/ui/table'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger
|
||||
} from '@/components/ui/alert-dialog'
|
||||
|
||||
import { BenchmarkTaskApi } from '@/api/benchmarkTask'
|
||||
import { copyToClipboard } from '@/utils/clipboard'
|
||||
import TaskPageLayout from '@/views/system/task-management/components/TaskPageLayout.vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const taskList = ref([])
|
||||
const filterStatus = ref(null)
|
||||
const promptModalVisible = ref(false)
|
||||
const currentPrompt = ref('')
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const STATUS_MAP = {
|
||||
// 0: 待处理(灰), 1: 处理中(蓝), 2: 成功(绿), 3: 失败(红)
|
||||
variant: { 0: 'secondary', 1: 'default', 2: 'outline', 3: 'destructive' },
|
||||
class: {
|
||||
2: 'border-green-500 text-green-600 bg-green-50'
|
||||
},
|
||||
text: { 0: '待处理', 1: '处理中', 2: '成功', 3: '失败' }
|
||||
}
|
||||
|
||||
function formatTime(time) {
|
||||
if (!time) return '-'
|
||||
return dayjs(time).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
async function loadTaskList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await BenchmarkTaskApi.getTaskPage({
|
||||
pageNo: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
status: filterStatus.value,
|
||||
})
|
||||
if (response?.data) {
|
||||
taskList.value = response.data.list || []
|
||||
pagination.total = response.data.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载失败:', error)
|
||||
toast.error('加载任务列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleRefresh() { loadTaskList() }
|
||||
|
||||
function handleFilterChange() {
|
||||
pagination.current = 1
|
||||
loadTaskList()
|
||||
}
|
||||
|
||||
function handlePageChange(page) {
|
||||
pagination.current = page
|
||||
loadTaskList()
|
||||
}
|
||||
|
||||
function getStatusVariant(status) {
|
||||
return STATUS_MAP.variant[status] || 'secondary'
|
||||
}
|
||||
|
||||
function getStatusText(status) {
|
||||
return STATUS_MAP.text[status] || '未知'
|
||||
}
|
||||
|
||||
function handleViewPrompt(record) {
|
||||
currentPrompt.value = record.generatedPrompt
|
||||
promptModalVisible.value = true
|
||||
}
|
||||
|
||||
async function copyPromptText(text) {
|
||||
const success = await copyToClipboard(text)
|
||||
toast[success ? 'success' : 'error'](success ? '已复制' : '复制失败')
|
||||
}
|
||||
|
||||
function handleCopyPrompt(record) {
|
||||
copyPromptText(record.generatedPrompt)
|
||||
}
|
||||
|
||||
function handleCopyCurrentPrompt() {
|
||||
copyPromptText(currentPrompt.value)
|
||||
}
|
||||
|
||||
async function handleDelete(record) {
|
||||
try {
|
||||
await BenchmarkTaskApi.deleteTask(record.id)
|
||||
toast.success('删除成功')
|
||||
loadTaskList()
|
||||
} catch {
|
||||
toast.error('删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadTaskList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.prompt-content {
|
||||
padding: var(--space-4);
|
||||
background: var(--muted);
|
||||
border-radius: var(--radius);
|
||||
white-space: pre-wrap;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
font-size: var(--font-size-base);
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user