feat: 优化

This commit is contained in:
2025-11-30 18:06:54 +08:00
parent 853bedcb23
commit ac803ec03b
9 changed files with 1314 additions and 326 deletions

View File

@@ -16,7 +16,8 @@ const icons = {
wave: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="18" height="18"><path d="M2 12s2-4 5-4 3 8 6 8 3-8 6-8 3 4 3 4"/></svg>',
user: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="18" height="18"><circle cx="12" cy="7" r="4"/><path d="M5.5 21a8.38 8.38 0 0 1 13 0"/></svg>',
video: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="18" height="18"><path d="m22 8-6 4 6 4V8Z"/><rect x="2" y="6" width="14" height="12" rx="2" ry="2"/></svg>',
folder: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="18" height="18"><path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>'
folder: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="18" height="18"><path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>',
scissors: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="18" height="18"><circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M8.59 13.51 15.42 17.49"/><path d="M15.41 6.51 8.59 10.49"/></svg>'
}
const items = computed(() => {
@@ -42,6 +43,7 @@ const items = computed(() => {
title: '素材库',
children: [
{ path: '/material/list', label: '素材列表', icon: 'grid' },
{ path: '/material/mix-task', label: '混剪任务', icon: 'scissors' },
{ path: '/material/group', label: '素材分组', icon: 'folder' },
]
},

View File

@@ -58,126 +58,152 @@
<!-- 任务列表 -->
<div class="mix-task-list__content">
<a-spin :spinning="loading" tip="加载中...">
<template v-if="taskList.length > 0">
<div class="task-list">
<div
v-for="task in taskList"
:key="task.id"
class="task-item"
>
<div class="task-item__header">
<div class="task-item__title">
<h3>{{ task.title }}</h3>
<a-tag :color="getStatusColor(task.status)">
{{ getStatusText(task.status) }}
</a-tag>
</div>
<div class="task-item__actions">
<a-button
v-if="task.status === 'failed'"
type="link"
size="small"
@click="handleRetry(task.id)"
<a-table
:data-source="taskList"
:columns="columns"
:row-key="record => record.id"
:pagination="paginationConfig"
@change="handleTableChange"
:expanded-row-keys="expandedRowKeys"
@expandedRowsChange="handleExpandedRowsChange"
:scroll="{ x: 1000 }"
>
<!-- 标题列 -->
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'title'">
<div class="title-cell">
<strong>{{ record.title }}</strong>
<a-tag v-if="record.text" size="small" style="margin-left: 8px">有文案</a-tag>
</div>
</template>
<!-- 状态列 -->
<template v-else-if="column.key === 'status'">
<a-tag :color="getStatusColor(record.status)">
{{ getStatusText(record.status) }}
</a-tag>
</template>
<!-- 进度列 -->
<template v-else-if="column.key === 'progress'">
<div style="min-width: 100px">
<div style="font-size: 12px; margin-bottom: 4px">{{ record.progress }}%</div>
<a-progress
:percent="record.progress"
:status="getProgressStatus(record.status)"
size="small"
:show-info="false"
/>
</div>
</template>
<!-- 创建时间列 -->
<template v-else-if="column.key === 'createTime'">
{{ formatDate(record.createTime) }}
</template>
<!-- 完成时间列 -->
<template v-else-if="column.key === 'finishTime'">
{{ record.finishTime ? formatDate(record.finishTime) : '-' }}
</template>
<!-- 生成结果列 -->
<template v-else-if="column.key === 'outputUrls'">
<div v-if="record.outputUrls && record.outputUrls.length > 0">
<a-tag color="success">{{ record.outputUrls.length }} 个视频</a-tag>
</div>
<span v-else>-</span>
</template>
<!-- 操作列 -->
<template v-else-if="column.key === 'actions'">
<a-space>
<a-button
v-if="record.outputUrls && record.outputUrls.length > 0"
type="primary"
size="small"
@click="handleDownloadAll(record.outputUrls)"
>
<template #icon>
<DownloadOutlined />
</template>
<span>下载</span>
</a-button>
<a-button
v-if="record.status === 'failed'"
size="small"
@click="handleRetry(record.id)"
>
重新生成
</a-button>
<a-button
v-if="record.status === 'running'"
size="small"
@click="handleCancel(record.id)"
>
取消
</a-button>
<a-popconfirm
title="确定删除这个任务吗删除后无法恢复"
@confirm="() => handleDelete(record.id)"
>
<a-button size="small" danger>删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
<!-- 展开行内容 -->
<template #expandedRowRender="{ record }">
<div class="expanded-content">
<!-- 任务详情 -->
<div v-if="record.text" class="task-text">
<strong>文案内容:</strong>
<p>{{ record.text }}</p>
</div>
<!-- 生成结果 -->
<div v-if="record.outputUrls && record.outputUrls.length > 0" class="task-results">
<strong>生成结果:</strong>
<div class="result-list">
<div
v-for="(url, index) in record.outputUrls"
:key="index"
class="result-item"
>
重新生成
</a-button>
<a-button
v-if="task.status === 'running'"
type="link"
size="small"
@click="handleCancel(task.id)"
>
取消
</a-button>
<a-button
type="link"
size="small"
@click="handleDelete(task.id)"
>
删除
</a-button>
<a :href="url" target="_blank">
<PlayCircleOutlined />
视频 {{ index + 1 }}
</a>
<a-button
type="link"
size="small"
@click="handleDownload(url)"
>
<DownloadOutlined />
</a-button>
</div>
</div>
</div>
<div class="task-item__content">
<div class="task-item__progress">
<div class="progress-info">
<span>进度{{ task.progress }}%</span>
</div>
<a-progress
:percent="task.progress"
:status="getProgressStatus(task.status)"
:show-info="false"
/>
</div>
<div class="task-item__meta">
<span>创建时间{{ formatDate(task.createTime) }}</span>
<span v-if="task.finishTime">
完成时间{{ formatDate(task.finishTime) }}
</span>
</div>
<div class="task-item__text" v-if="task.text">
<p>{{ task.text }}</p>
</div>
<div class="task-item__results" v-if="task.outputUrls && task.outputUrls.length > 0">
<h4>生成结果 ({{ task.outputUrls.length }})</h4>
<div class="result-list">
<div
v-for="(url, index) in task.outputUrls"
:key="index"
class="result-item"
>
<a :href="url" target="_blank">
<PlayCircleOutlined />
视频 {{ index + 1 }}
</a>
<a-button
type="link"
size="small"
@click="handleDownload(url)"
>
<DownloadOutlined />
</a-button>
</div>
</div>
</div>
<div class="task-item__error" v-if="task.errorMsg">
<a-alert
type="error"
:message="task.errorMsg"
show-icon
/>
</div>
<!-- 错误信息 -->
<div v-if="record.errorMsg" class="task-error">
<a-alert
type="error"
:message="record.errorMsg"
show-icon
/>
</div>
</div>
</div>
</template>
<template v-else>
<a-empty description="暂无混剪任务" />
</template>
</template>
</a-table>
</a-spin>
</div>
<!-- 分页 -->
<div class="mix-task-list__pagination">
<a-pagination
v-model:current="pagination.pageNo"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:show-total="(total) => `${total}`"
:show-size-changer="true"
@change="handlePageChange"
/>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { ref, reactive, onMounted } from 'vue'
import { message, Modal } from 'ant-design-vue'
import {
ReloadOutlined,
@@ -191,7 +217,61 @@ import { formatDate } from '@/utils/file'
// 数据
const loading = ref(false)
const taskList = ref([])
const timer = ref(null) // 定时器
const expandedRowKeys = ref([])
// 表格列定义
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 80,
fixed: 'left'
},
{
title: '标题',
dataIndex: 'title',
key: 'title',
width: 250,
ellipsis: true
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 100
},
{
title: '进度',
dataIndex: 'progress',
key: 'progress',
width: 150
},
{
title: '生成结果',
dataIndex: 'outputUrls',
key: 'outputUrls',
width: 120
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
width: 180
},
{
title: '完成时间',
dataIndex: 'finishTime',
key: 'finishTime',
width: 180
},
{
title: '操作',
key: 'actions',
width: 300,
fixed: 'right'
}
]
// 筛选条件
const filters = reactive({
@@ -200,18 +280,32 @@ const filters = reactive({
createTime: undefined
})
// 分页
const pagination = reactive({
pageNo: 1,
// 分页配置
const paginationConfig = reactive({
current: 1,
pageSize: 10,
total: 0
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
pageSizeOptions: ['10', '20', '50', '100'],
onChange: (page, pageSize) => {
paginationConfig.current = page
paginationConfig.pageSize = pageSize
handlePageChange(page, pageSize)
},
onShowSizeChange: (current, size) => {
paginationConfig.current = 1
paginationConfig.pageSize = size
handlePageChange(1, size)
}
})
// 构建查询参数
const buildQueryParams = () => {
const params = {
pageNo: pagination.pageNo,
pageSize: pagination.pageSize,
pageNo: paginationConfig.current,
pageSize: paginationConfig.pageSize,
status: filters.status || undefined,
title: filters.title || undefined
}
@@ -232,7 +326,7 @@ const loadTaskList = async () => {
const res = await MixTaskService.getTaskPage(buildQueryParams())
if (res.code === 0) {
taskList.value = res.data.list || []
pagination.total = res.data.total || 0
paginationConfig.total = res.data.total || 0
} else {
message.error(res.msg || '加载失败')
}
@@ -246,7 +340,7 @@ const loadTaskList = async () => {
// 筛选
const handleFilterChange = () => {
pagination.pageNo = 1
paginationConfig.current = 1
loadTaskList()
}
@@ -254,20 +348,25 @@ const handleResetFilters = () => {
filters.status = ''
filters.title = ''
filters.createTime = undefined
pagination.pageNo = 1
paginationConfig.current = 1
loadTaskList()
}
// 分页
const handlePageChange = (page, pageSize) => {
pagination.pageNo = page
if (pageSize && pageSize !== pagination.pageSize) {
pagination.pageSize = pageSize
pagination.pageNo = 1
}
loadTaskList()
}
// 表格变化
const handleTableChange = (pag, filters, sorter) => {
console.log('表格变化:', pag, filters, sorter)
}
// 展开行变化
const handleExpandedRowsChange = (expandedRows) => {
expandedRowKeys.value = expandedRows
}
// 刷新
const handleRefresh = () => {
loadTaskList()
@@ -309,23 +408,17 @@ const handleCancel = (id) => {
// 删除任务
const handleDelete = (id) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除这个任务吗?删除后无法恢复。',
okType: 'danger',
onOk: async () => {
try {
await MixTaskService.deleteTask(id)
message.success('删除成功')
loadTaskList()
} catch (error) {
message.error('删除失败')
}
}
})
MixTaskService.deleteTask(id)
.then(() => {
message.success('删除成功')
loadTaskList()
})
.catch(() => {
message.error('删除失败')
})
}
// 下载
// 下载单个视频
const handleDownload = (url) => {
const link = document.createElement('a')
link.href = url
@@ -336,6 +429,27 @@ const handleDownload = (url) => {
document.body.removeChild(link)
}
// 批量下载所有视频
const handleDownloadAll = (urls) => {
if (!urls || urls.length === 0) {
message.warning('没有可下载的视频')
return
}
message.loading('正在准备下载...', 0)
// 逐个触发下载,避免浏览器阻止多个弹窗
urls.forEach((url, index) => {
setTimeout(() => {
console.log('下载视频:', url)
handleDownload(url)
}, index * 500) // 每个下载间隔500ms
})
message.destroy()
message.success(`已触发 ${urls.length} 个视频的下载`)
}
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
@@ -369,29 +483,9 @@ const getProgressStatus = (status) => {
return statusMap[status] || 'normal'
}
// 定时刷新
const startAutoRefresh = () => {
// 每5秒刷新一次
timer.value = setInterval(() => {
loadTaskList()
}, 5000)
}
const stopAutoRefresh = () => {
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
}
// 初始化
onMounted(() => {
loadTaskList()
startAutoRefresh()
})
onUnmounted(() => {
stopAutoRefresh()
})
</script>
@@ -431,93 +525,47 @@ onUnmounted(() => {
.mix-task-list__content {
flex: 1;
overflow-y: auto;
margin-bottom: 24px;
}
.task-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.task-item {
overflow: auto;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-card);
border: 1px solid var(--color-border);
padding: 16px;
}
.task-item__header {
.title-cell {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
.expanded-content {
padding: 16px;
background: var(--color-bg-2);
border-radius: var(--radius-card);
margin: 8px;
}
.task-text {
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--color-border);
}
.task-item__title {
display: flex;
align-items: center;
gap: 12px;
}
.task-item__title h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
}
.task-item__actions {
display: flex;
gap: 8px;
}
.task-item__content {
display: flex;
flex-direction: column;
gap: 12px;
}
.task-item__progress {
display: flex;
flex-direction: column;
gap: 8px;
}
.progress-info {
font-size: 13px;
color: var(--color-text-2);
}
.task-item__meta {
display: flex;
gap: 16px;
font-size: 13px;
color: var(--color-text-3);
}
.task-item__text {
font-size: 14px;
color: var(--color-text-2);
.task-text p {
margin: 8px 0 0 0;
padding: 8px;
background: var(--color-surface);
border-radius: var(--radius-card);
line-height: 1.6;
}
.task-item__text p {
margin: 0;
}
.task-item__results h4 {
margin: 0 0 8px 0;
font-size: 14px;
font-weight: 500;
.task-results {
margin-bottom: 16px;
}
.result-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 8px;
}
.result-item {
@@ -525,13 +573,17 @@ onUnmounted(() => {
align-items: center;
gap: 8px;
padding: 6px 12px;
background: var(--color-bg-2);
background: var(--color-surface);
border-radius: var(--radius-card);
font-size: 13px;
}
.mix-task-list__pagination {
display: flex;
justify-content: center;
.task-error {
margin-bottom: 8px;
}
/* 确保按钮内的图标和文字对齐 */
:deep(.ant-btn .anticon) {
vertical-align: middle;
}
</style>

View File

@@ -0,0 +1,532 @@
<template>
<div class="mix-task-list">
<div class="mix-task-list__header">
<h1 class="mix-task-list__title">混剪任务</h1>
<div class="mix-task-list__actions">
<a-button @click="handleRefresh">
<template #icon>
<ReloadOutlined />
</template>
刷新
</a-button>
</div>
</div>
<!-- 筛选条件 -->
<div class="mix-task-list__filters">
<a-space>
<a-select
v-model:value="filters.status"
style="width: 120px"
placeholder="任务状态"
@change="handleFilterChange"
allow-clear
>
<a-select-option value="">全部状态</a-select-option>
<a-select-option value="pending">待处理</a-select-option>
<a-select-option value="running">处理中</a-select-option>
<a-select-option value="success">已完成</a-select-option>
<a-select-option value="failed">失败</a-select-option>
</a-select>
<a-input
v-model="filters.title"
placeholder="搜索标题"
style="width: 200px"
allow-clear
@press-enter="handleFilterChange"
>
<template #prefix>
<SearchOutlined />
</template>
</a-input>
<a-range-picker
v-model:value="filters.createTime"
style="width: 300px"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
:placeholder="['开始日期', '结束日期']"
@change="handleFilterChange"
/>
<a-button type="primary" @click="handleFilterChange">查询</a-button>
<a-button @click="handleResetFilters">重置</a-button>
</a-space>
</div>
<!-- 任务列表 -->
<div class="mix-task-list__content">
<a-spin :spinning="loading" tip="加载中...">
<template v-if="taskList.length > 0">
<div class="task-list">
<div
v-for="task in taskList"
:key="task.id"
class="task-item"
>
<div class="task-item__header">
<div class="task-item__title">
<h3>{{ task.title }}</h3>
<a-tag :color="getStatusColor(task.status)">
{{ getStatusText(task.status) }}
</a-tag>
</div>
</div>
<div class="task-item__content">
<div class="task-item__progress">
<div class="progress-info">
<span>进度{{ task.progress }}%</span>
</div>
<a-progress
:percent="task.progress"
:status="getProgressStatus(task.status)"
:show-info="false"
/>
</div>
<div class="task-item__meta">
<span>创建时间{{ formatDate(task.createTime) }}</span>
<span v-if="task.finishTime">
完成时间{{ formatDate(task.finishTime) }}
</span>
</div>
<div class="task-item__text" v-if="task.text">
<p>{{ task.text }}</p>
</div>
<div class="task-item__results" v-if="task.outputUrls && task.outputUrls.length > 0">
<h4>生成结果 ({{ task.outputUrls.length }})</h4>
<div class="result-list">
<div
v-for="(url, index) in task.outputUrls"
:key="index"
class="result-item"
>
<a :href="url" target="_blank">
<PlayCircleOutlined />
视频 {{ index + 1 }}
</a>
<a-button
type="link"
size="small"
@click="handleDownload(url)"
>
<DownloadOutlined />
</a-button>
</div>
</div>
</div>
<div class="task-item__error" v-if="task.errorMsg">
<a-alert
type="error"
:message="task.errorMsg"
show-icon
/>
</div>
</div>
</div>
</div>
</template>
<template v-else>
<a-empty description="暂无混剪任务" />
</template>
</a-spin>
</div>
<!-- 分页 -->
<div class="mix-task-list__pagination">
<a-pagination
v-model:current="pagination.pageNo"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:show-total="(total) => `${total}`"
:show-size-changer="true"
@change="handlePageChange"
/>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { message, Modal } from 'ant-design-vue'
import {
ReloadOutlined,
SearchOutlined,
PlayCircleOutlined,
DownloadOutlined
} from '@ant-design/icons-vue'
import { MixTaskService } from '@/api/mixTask'
import { formatDate } from '@/utils/file'
// 数据
const loading = ref(false)
const taskList = ref([])
const timer = ref(null) // 定时器
// 筛选条件
const filters = reactive({
status: '',
title: '',
createTime: undefined
})
// 分页
const pagination = reactive({
pageNo: 1,
pageSize: 10,
total: 0
})
// 构建查询参数
const buildQueryParams = () => {
const params = {
pageNo: pagination.pageNo,
pageSize: pagination.pageSize,
status: filters.status || undefined,
title: filters.title || undefined
}
// 处理日期范围
if (filters.createTime && Array.isArray(filters.createTime) && filters.createTime.length === 2) {
params.createTimeStart = `${filters.createTime[0]} 00:00:00`
params.createTimeEnd = `${filters.createTime[1]} 23:59:59`
}
return params
}
// 加载任务列表
const loadTaskList = async () => {
loading.value = true
try {
const res = await MixTaskService.getTaskPage(buildQueryParams())
if (res.code === 0) {
taskList.value = res.data.list || []
pagination.total = res.data.total || 0
} else {
message.error(res.msg || '加载失败')
}
} catch (error) {
console.error('加载任务列表失败:', error)
message.error('加载失败,请重试')
} finally {
loading.value = false
}
}
// 筛选
const handleFilterChange = () => {
pagination.pageNo = 1
loadTaskList()
}
const handleResetFilters = () => {
filters.status = ''
filters.title = ''
filters.createTime = undefined
pagination.pageNo = 1
loadTaskList()
}
// 分页
const handlePageChange = (page, pageSize) => {
pagination.pageNo = page
if (pageSize && pageSize !== pagination.pageSize) {
pagination.pageSize = pageSize
pagination.pageNo = 1
}
loadTaskList()
}
// 刷新
const handleRefresh = () => {
loadTaskList()
}
// 重新生成
const handleRetry = (id) => {
Modal.confirm({
title: '确认重新生成',
content: '确定要重新生成这个任务吗?',
onOk: async () => {
try {
await MixTaskService.retryTask(id)
message.success('已重新提交任务')
loadTaskList()
} catch (error) {
message.error('操作失败')
}
}
})
}
// 取消任务
const handleCancel = (id) => {
Modal.confirm({
title: '确认取消',
content: '确定要取消这个任务吗?',
onOk: async () => {
try {
await MixTaskService.cancelTask(id)
message.success('已取消任务')
loadTaskList()
} catch (error) {
message.error('操作失败')
}
}
})
}
// 删除任务
const handleDelete = (id) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除这个任务吗?删除后无法恢复。',
okType: 'danger',
onOk: async () => {
try {
await MixTaskService.deleteTask(id)
message.success('删除成功')
loadTaskList()
} catch (error) {
message.error('删除失败')
}
}
})
}
// 下载单个视频
const handleDownload = (url) => {
const link = document.createElement('a')
link.href = url
link.download = 'video'
link.target = '_blank'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
// 批量下载所有视频
const handleDownloadAll = (urls) => {
if (!urls || urls.length === 0) {
message.warning('没有可下载的视频')
return
}
message.loading('正在准备下载...', 0)
// 逐个触发下载,避免浏览器阻止多个弹窗
urls.forEach((url, index) => {
setTimeout(() => {
handleDownload(url)
}, index * 500) // 每个下载间隔500ms
})
message.destroy()
message.success(`已触发 ${urls.length} 个视频的下载`)
}
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
pending: '待处理',
running: '处理中',
success: '已完成',
failed: '失败'
}
return statusMap[status] || status
}
// 获取状态颜色
const getStatusColor = (status) => {
const colorMap = {
pending: 'default',
running: 'processing',
success: 'success',
failed: 'error'
}
return colorMap[status] || 'default'
}
// 获取进度条状态
const getProgressStatus = (status) => {
const statusMap = {
pending: 'normal',
running: 'active',
success: 'success',
failed: 'exception'
}
return statusMap[status] || 'normal'
}
// 定时刷新
const startAutoRefresh = () => {
// 每5秒刷新一次
timer.value = setInterval(() => {
loadTaskList()
}, 5000)
}
const stopAutoRefresh = () => {
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
}
// 初始化
onMounted(() => {
loadTaskList()
startAutoRefresh()
})
onUnmounted(() => {
stopAutoRefresh()
})
</script>
<style scoped>
.mix-task-list {
padding: 24px;
height: 100%;
display: flex;
flex-direction: column;
}
.mix-task-list__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.mix-task-list__title {
font-size: 24px;
font-weight: 600;
margin: 0;
}
.mix-task-list__actions {
display: flex;
gap: 12px;
}
.mix-task-list__filters {
margin-bottom: 24px;
padding: 16px;
background: var(--color-surface);
border-radius: var(--radius-card);
border: 1px solid var(--color-border);
}
.mix-task-list__content {
flex: 1;
overflow-y: auto;
margin-bottom: 24px;
}
.task-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.task-item {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-card);
padding: 16px;
}
.task-item__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--color-border);
}
.task-item__title {
display: flex;
align-items: center;
gap: 12px;
}
.task-item__title h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
}
.task-item__actions {
display: flex;
gap: 8px;
}
.task-item__content {
display: flex;
flex-direction: column;
gap: 12px;
}
.task-item__progress {
display: flex;
flex-direction: column;
gap: 8px;
}
.progress-info {
font-size: 13px;
color: var(--color-text-2);
}
.task-item__meta {
display: flex;
gap: 16px;
font-size: 13px;
color: var(--color-text-3);
}
.task-item__text {
font-size: 14px;
color: var(--color-text-2);
line-height: 1.6;
}
.task-item__text p {
margin: 0;
}
.task-item__results h4 {
margin: 0 0 8px 0;
font-size: 14px;
font-weight: 500;
}
.result-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.result-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
background: var(--color-bg-2);
border-radius: var(--radius-card);
font-size: 13px;
}
.mix-task-list__pagination {
display: flex;
justify-content: center;
}
</style>