feat: 样式升级
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { ref, reactive } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
/**
|
||||
* 任务列表通用逻辑 Composable
|
||||
@@ -89,11 +89,11 @@ export function useTaskList(fetchApi, options = {}) {
|
||||
total.value = res.data.total || 0
|
||||
paginationConfig.total = res.data.total || 0
|
||||
} else {
|
||||
message.error(res.msg || '加载失败')
|
||||
toast.error(res.msg || '加载失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载任务列表失败:', error)
|
||||
message.error('加载失败,请重试')
|
||||
toast.error('加载失败,请重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { confirmDialog } from '@/utils/confirmDialog'
|
||||
|
||||
// 配置常量
|
||||
const API_SUCCESS_CODE = 0
|
||||
@@ -44,78 +45,95 @@ export function useTaskOperations(apiHandlers, onSuccess) {
|
||||
getSignedUrlsApi
|
||||
} = apiHandlers
|
||||
|
||||
// 通用模态框确认
|
||||
function confirmModal({ title, content, okType = 'primary', onOk }) {
|
||||
Modal.confirm({
|
||||
title,
|
||||
content,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
okType,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await onOk()
|
||||
onSuccess && onSuccess()
|
||||
} catch (error) {
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 执行 API 操作并显示成功消息
|
||||
async function executeApiOperation(apiFn, successMessage) {
|
||||
await apiFn()
|
||||
message.success(successMessage)
|
||||
toast.success(successMessage)
|
||||
}
|
||||
|
||||
// 删除任务
|
||||
function deleteTask(id) {
|
||||
confirmModal({
|
||||
async function deleteTask(id) {
|
||||
const confirmed = await confirmDialog({
|
||||
title: '确认删除',
|
||||
content: '确定删除这个任务吗?删除后无法恢复。',
|
||||
okType: 'danger',
|
||||
onOk: () => executeApiOperation(() => deleteApi(id), '删除成功')
|
||||
okText: '确认',
|
||||
cancelText: '取消'
|
||||
})
|
||||
|
||||
if (confirmed) {
|
||||
try {
|
||||
await executeApiOperation(() => deleteApi(id), '删除成功')
|
||||
onSuccess && onSuccess()
|
||||
} catch (error) {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 取消任务
|
||||
function cancelTask(id) {
|
||||
confirmModal({
|
||||
async function cancelTask(id) {
|
||||
const confirmed = await confirmDialog({
|
||||
title: '确认取消',
|
||||
content: '确定要取消这个任务吗?',
|
||||
onOk: () => executeApiOperation(() => cancelApi(id), '已取消任务')
|
||||
okText: '确认',
|
||||
cancelText: '取消'
|
||||
})
|
||||
|
||||
if (confirmed) {
|
||||
try {
|
||||
await executeApiOperation(() => cancelApi(id), '已取消任务')
|
||||
onSuccess && onSuccess()
|
||||
} catch (error) {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重试任务
|
||||
function retryTask(id) {
|
||||
confirmModal({
|
||||
async function retryTask(id) {
|
||||
const confirmed = await confirmDialog({
|
||||
title: '确认重试',
|
||||
content: '确定要重新生成这个任务吗?',
|
||||
onOk: () => executeApiOperation(() => retryApi(id), '已重新提交任务')
|
||||
okText: '确认',
|
||||
cancelText: '取消'
|
||||
})
|
||||
|
||||
if (confirmed) {
|
||||
try {
|
||||
await executeApiOperation(() => retryApi(id), '已重新提交任务')
|
||||
onSuccess && onSuccess()
|
||||
} catch (error) {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
async function batchDeleteTasks(ids, deleteApiFn) {
|
||||
if (!ids || ids.length === 0) {
|
||||
message.warning('请选择要删除的任务')
|
||||
toast.warning('请选择要删除的任务')
|
||||
return
|
||||
}
|
||||
|
||||
confirmModal({
|
||||
const confirmed = await confirmDialog({
|
||||
title: '确认批量删除',
|
||||
content: `确定要删除选中的 ${ids.length} 个任务吗?删除后无法恢复。`,
|
||||
okType: 'danger',
|
||||
onOk: async () => {
|
||||
okText: '确认',
|
||||
cancelText: '取消'
|
||||
})
|
||||
|
||||
if (confirmed) {
|
||||
try {
|
||||
const deleteFn = deleteApiFn || deleteApi
|
||||
for (const id of ids) {
|
||||
await deleteFn(id)
|
||||
}
|
||||
message.success(`成功删除 ${ids.length} 个任务`)
|
||||
toast.success(`成功删除 ${ids.length} 个任务`)
|
||||
onSuccess && onSuccess()
|
||||
} catch (error) {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 下载单个文件(使用 fetch + blob 强制下载)
|
||||
@@ -151,7 +169,8 @@ export function useTaskOperations(apiHandlers, onSuccess) {
|
||||
// 批量下载
|
||||
async function batchDownload(urls, getSignedUrlsApi, taskId) {
|
||||
try {
|
||||
message.loading('正在准备下载...', 0)
|
||||
toast.loading('正在准备下载...')
|
||||
|
||||
let downloadUrls = urls
|
||||
|
||||
// 如果需要获取签名URL
|
||||
@@ -161,12 +180,11 @@ export function useTaskOperations(apiHandlers, onSuccess) {
|
||||
|
||||
// 检查下载链接
|
||||
if (!downloadUrls || downloadUrls.length === 0) {
|
||||
message.warning('没有可下载的文件')
|
||||
toast.warning('没有可下载的文件')
|
||||
return
|
||||
}
|
||||
|
||||
message.destroy()
|
||||
message.loading(`正在下载 ${downloadUrls.length} 个文件...`, 0)
|
||||
toast.loading(`正在下载 ${downloadUrls.length} 个文件...`)
|
||||
|
||||
// 逐个下载文件
|
||||
for (let i = 0; i < downloadUrls.length; i++) {
|
||||
@@ -179,11 +197,9 @@ export function useTaskOperations(apiHandlers, onSuccess) {
|
||||
}
|
||||
}
|
||||
|
||||
message.destroy()
|
||||
message.success(`成功下载 ${downloadUrls.length} 个文件`)
|
||||
toast.success(`成功下载 ${downloadUrls.length} 个文件`)
|
||||
} catch (error) {
|
||||
message.destroy()
|
||||
message.error('下载失败,请稍后重试')
|
||||
toast.error('下载失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,10 +210,10 @@ export function useTaskOperations(apiHandlers, onSuccess) {
|
||||
if (res.code === API_SUCCESS_CODE && res.data && res.data[index]) {
|
||||
return res.data[index]
|
||||
}
|
||||
message.warning('获取预览链接失败')
|
||||
toast.warning('获取预览链接失败')
|
||||
return null
|
||||
} catch (error) {
|
||||
message.error('获取预览链接失败')
|
||||
toast.error('获取预览链接失败')
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -205,16 +221,14 @@ export function useTaskOperations(apiHandlers, onSuccess) {
|
||||
// 预览文件
|
||||
async function previewFile(getSignedUrlsApi, taskId, index) {
|
||||
try {
|
||||
message.loading('正在获取预览链接...', 0)
|
||||
toast.loading('正在获取预览链接...')
|
||||
const url = await getSignedUrl(getSignedUrlsApi, taskId, index)
|
||||
message.destroy()
|
||||
|
||||
if (url) {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
} catch (error) {
|
||||
message.destroy()
|
||||
message.error('预览失败')
|
||||
toast.error('预览失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
<template>
|
||||
<div class="task-layout">
|
||||
<!-- 顶部Tab栏 -->
|
||||
<!-- 顶部Tab栏 - 现代化设计 -->
|
||||
<div class="task-layout__header">
|
||||
<Tabs v-model:model-value="currentType" class="w-full">
|
||||
<TabsList class="h-12 bg-transparent p-0 gap-1">
|
||||
<TabsTrigger
|
||||
v-for="item in NAV_ITEMS"
|
||||
:key="item.type"
|
||||
:value="item.type"
|
||||
class="h-10 px-4 gap-2 rounded-lg transition-all data-[state=active]:bg-foreground data-[state=active]:!text-white data-[state=active]:shadow-sm data-[state=inactive]:text-muted-foreground data-[state=inactive]:hover:text-foreground data-[state=inactive]:hover:bg-muted"
|
||||
>
|
||||
<Icon :icon="item.icon" class="size-4" />
|
||||
<span>{{ item.label }}</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<div class="flex items-center justify-between">
|
||||
<Tabs v-model:model-value="currentType" class="w-auto">
|
||||
<TabsList class="h-11 bg-muted/50 p-1 gap-1">
|
||||
<TabsTrigger
|
||||
v-for="item in NAV_ITEMS"
|
||||
:key="item.type"
|
||||
:value="item.type"
|
||||
class="h-9 px-4 gap-2 rounded-md transition-all data-[state=active]:bg-background data-[state=active]:shadow-sm data-[state=inactive]:text-muted-foreground data-[state=inactive]:hover:text-foreground"
|
||||
>
|
||||
<Icon :icon="item.icon" class="size-4" />
|
||||
<span class="font-medium">{{ item.label }}</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区 -->
|
||||
@@ -71,21 +73,23 @@ const currentComponent = computed(() => {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--color-bg-card);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-sm);
|
||||
gap: var(--space-4);
|
||||
background: transparent;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.task-layout__header {
|
||||
flex-shrink: 0;
|
||||
padding: 0 var(--space-4);
|
||||
background: var(--color-bg-card);
|
||||
padding: var(--space-4) var(--space-6);
|
||||
background: var(--card);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.task-layout__content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
|
||||
@@ -620,7 +620,7 @@ onMounted(fetchList)
|
||||
|
||||
<style scoped lang="less">
|
||||
.task-page {
|
||||
padding: var(--space-4);
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -628,29 +628,35 @@ onMounted(fetchList)
|
||||
}
|
||||
|
||||
.task-page__filters {
|
||||
padding: var(--space-4);
|
||||
background: var(--color-bg-card);
|
||||
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(--color-bg-card);
|
||||
background: var(--card);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--border);
|
||||
padding: var(--space-5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.batch-toolbar {
|
||||
flex-shrink: 0;
|
||||
padding-bottom: var(--space-4);
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.expanded-content {
|
||||
padding: var(--space-4);
|
||||
background: var(--color-gray-50);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-5);
|
||||
background: var(--muted);
|
||||
border-radius: var(--radius);
|
||||
margin: var(--space-2);
|
||||
}
|
||||
|
||||
@@ -659,10 +665,10 @@ onMounted(fetchList)
|
||||
|
||||
p {
|
||||
margin: var(--space-2) 0 0;
|
||||
padding: var(--space-3);
|
||||
background: var(--color-gray-100);
|
||||
border-radius: var(--radius-md);
|
||||
line-height: 1.5;
|
||||
padding: var(--space-4);
|
||||
background: var(--muted);
|
||||
border-radius: var(--radius);
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,19 +679,19 @@ onMounted(fetchList)
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-2);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.result-count {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-gray-500);
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.result-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
margin-top: var(--space-2);
|
||||
gap: var(--space-3);
|
||||
margin-top: var(--space-3);
|
||||
}
|
||||
|
||||
.result-item {
|
||||
@@ -693,12 +699,14 @@ onMounted(fetchList)
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--color-gray-100);
|
||||
border-radius: var(--radius-md);
|
||||
transition: box-shadow var(--duration-fast) ease;
|
||||
background: var(--muted);
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--border);
|
||||
transition: all var(--duration-fast);
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow-sm);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user