feat: 增加了任务列表

This commit is contained in:
张德辉 2025-06-27 17:56:24 +08:00
parent 0414f0b654
commit 9b7f68b86f
4 changed files with 123 additions and 65 deletions

View File

@ -1,12 +1,14 @@
import { ConfirmDialog } from '@/components/block/confirm-dialog';
import { Sidebar, SidebarContent, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider } from '@/components/ui/sidebar';
import { Home, Users } from 'lucide-react';
import { Home, List, Users } from 'lucide-react';
import { Link, Route, Routes, useLocation } from 'react-router-dom';
import ModelPage from './pages/ModelPage';
import TryOnPage from './pages/TryOnPage';
import TasksPage from './pages/TasksPage';
const menu = [
{ path: '/', label: '首页', icon: <Home className='mr-2' /> },
{ path: '/tasks', label: '任务列表', icon: <List className='mr-2' /> },
{ path: '/models', label: '模特维护', icon: <Users className='mr-2' /> },
];
@ -36,6 +38,7 @@ function App() {
<Routes>
<Route path='/' element={<TryOnPage />} />
<Route path='/models' element={<ModelPage />} />
<Route path='/tasks' element={<TasksPage />} />
</Routes>
</main>
<ConfirmDialog />

View File

@ -227,13 +227,13 @@ export class DefaultService {
* @returns PaginationResponse Successful Response
* @throws ApiError
*/
public static getImageDataListApiImageDataListGet({
public static getImageDataListApiImageDataListPost({
requestBody,
}: {
requestBody: VideoDataPaginationRequest,
}): CancelablePromise<PaginationResponse> {
return __request(OpenAPI, {
method: 'GET',
method: 'POST',
url: '/api/image/data/list',
body: requestBody,
mediaType: 'application/json',

View File

@ -75,75 +75,24 @@ const ModelPage: React.FC = () => {
<h1 className='text-2xl font-bold'></h1>
<Button onClick={handleAdd}></Button>
</div>
<div className='grid grid-cols-1 sm:grid-cols-3 lg:grid-cols-4 gap-6'>
<div className='grid gap-6 grid-cols-[repeat(auto-fit,minmax(500px,500px))]'>
{models.map(item => (
<div
key={item.id}
className='
relative
rounded-2xl
bg-gradient-to-br from-white/80 via-zinc-100/60 to-blue-50/60
dark:from-zinc-900/80 dark:via-zinc-800/60 dark:to-blue-950/60
shadow-xl
border border-zinc-200 dark:border-zinc-800
backdrop-blur-md
p-5 flex gap-4
transition-transform hover:-translate-y-1 hover:shadow-2xl
group
'
>
<div className='relative'>
<img
src={item.cover_url}
alt='cover'
className='
w-[120px] aspect-[9/16] object-cover rounded-xl
shadow-md
transition-transform group-hover:scale-105
'
/>
<span className='absolute top-2 left-2 bg-blue-500/80 text-white text-xs px-2 py-0.5 rounded-full shadow'>
{item.status === 1 ? '启用' : '禁用'}
</span>
</div>
<div key={item.id} className='w-[500px] bg-white dark:bg-zinc-900 rounded-lg shadow p-4 flex gap-3'>
<img src={item.cover_url} alt='cover' className='w-[200px] object-cover rounded aspect-[9/16]' />
<div className='flex-1 flex flex-col gap-2'>
<div className='flex-1 flex flex-col gap-1'>
<div className='text-xs text-zinc-400'>ID: {item.id}</div>
<div className='text-sm font-medium text-zinc-700 dark:text-zinc-200 truncate'>
:
{Object.values(item.tag).map((t, i) => (
<span
key={i}
className='inline-block bg-blue-100 dark:bg-blue-900/40 text-blue-600 dark:text-blue-300 rounded px-2 py-0.5 mx-1 text-xs'
>
{t}
</span>
))}
<div className='text-sm text-muted-foreground'>ID: {item.id}</div>
<div className='text-sm truncate'>: {Object.values(item.tag).join('_')}</div>
<div className='text-sm'>
: <span className={item.status === 1 ? 'text-green-600' : 'text-red-500'}>{item.status === 1 ? '启用' : '禁用'}</span>
</div>
<div className='text-xs text-zinc-400'>: {item.create_time}</div>
<div className='text-xs text-muted-foreground'>: {item.create_time}</div>
</div>
<div className='mt-4 flex gap-2'>
<Button
variant='outline'
onClick={() => handleEdit(item)}
className='
flex-1 rounded-lg border-blue-400
hover:border-blue-600 hover:bg-blue-50
transition-all
'
>
<div className='mt-4 flex flex-col gap-2'>
<Button variant='outline' onClick={() => handleEdit(item)} className='flex-1'>
</Button>
<Button
variant='destructive'
onClick={() => handleDelete(item.id)}
className='
flex-1 rounded-lg border-pink-400
hover:border-pink-600 hover:bg-pink-50
transition-all
'
disabled={item.status === 1}
>
<Button variant='destructive' onClick={() => handleDelete(item.id)} className='flex-1' disabled={item.status === 1}>
</Button>
</div>

View File

@ -0,0 +1,106 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useApi } from '@/hooks/useApi';
import { api } from '@/lib/api';
import type { VideoDataPaginationRequest } from '@/api/models/VideoDataPaginationRequest';
import type { PaginationResponse } from '@/api/models/PaginationResponse';
import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table';
import { Button } from '@/components/ui/button';
import { Skeleton } from '@/components/ui/skeleton';
import { Dialog, DialogContent } from '@/components/ui/dialog';
const PAGE_SIZE = 10;
const TasksPage: React.FC = () => {
const [page, setPage] = useState(1);
const [previewImg, setPreviewImg] = useState<string | null>(null);
const [dialogOpen, setDialogOpen] = useState(false);
const r = useCallback(async (params: VideoDataPaginationRequest) => {
const res = await api.DefaultService.getImageDataListApiImageDataListPost({ requestBody: params });
return res;
}, []);
const { data, loading, error, execute } = useApi<PaginationResponse>(r, null);
useEffect(() => {
execute({ page, page_size: PAGE_SIZE });
}, [page, execute]);
const handlePrev = () => setPage(p => Math.max(1, p - 1));
const handleNext = () => setPage(p => p + 1);
return (
<div className='p-6'>
{/* 图片预览 Dialog */}
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent style={{ maxHeight: '80vh' }} className='flex items-center justify-center' showCloseButton={false}>
{previewImg && <img src={previewImg} alt='预览图片' className='object-contain rounded shadow-lg max-h-[calc(80vh-100px)]' />}
</DialogContent>
</Dialog>
<h2 className='text-xl font-bold mb-4'></h2>
{loading ? (
<Skeleton className='h-40 w-full mb-4' />
) : error ? (
<div className='text-red-500 mb-4'>{error}</div>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead>ID</TableHead>
<TableHead>ID</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data?.data?.length ? (
data.data.map(item => (
<TableRow key={item.id}>
<TableCell>
{item.img_url ? (
<img
src={item.img_url}
alt='img'
className='w-16 h-16 object-cover rounded cursor-pointer transition hover:scale-105'
onClick={() => {
setPreviewImg(item.img_url!);
setDialogOpen(true);
}}
/>
) : (
<span className='text-gray-400'></span>
)}
</TableCell>
<TableCell>{item.id}</TableCell>
<TableCell>{item.task_id}</TableCell>
<TableCell>{item.img_status || '-'}</TableCell>
<TableCell>{item.video_status || '-'}</TableCell>
<TableCell>{item.create_time || '-'}</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={6} className='text-center text-gray-400'>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
)}
<div className='flex justify-between items-center mt-4'>
<Button variant='outline' onClick={handlePrev} disabled={page === 1 || loading}>
</Button>
<span> {page} </span>
<Button variant='outline' onClick={handleNext} disabled={loading || (data?.data && data.data.length < PAGE_SIZE)}>
</Button>
</div>
</div>
);
};
export default TasksPage;