feat: 样式升级

This commit is contained in:
2026-03-16 23:54:01 +08:00
parent 110fe62404
commit 4a5fdd3961
42 changed files with 1931 additions and 1404 deletions

View File

@@ -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
}

View File

@@ -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('预览失败')
}
}

View File

@@ -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,

View File

@@ -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);
}
}
}