refactor: 前端重构为项目+任务双核心模型
- 新增 HomePage 项目画廊页,支持新建/删除工程 - 新增 ProjectDetailPage 三栏工作台(配置+生成+任务列表) - 新增 CustomTagsEditor 支持自定义风格标签(custom: 前缀存入 kvPairs) - 重构 project store 为 draft/commit 模式,支持手动保存/取消 - 新增 projectList store 管理工程列表 - 扩展 mock.ts 为多工程数据结构 - 扩展 api/project.ts 新增项目 CRUD API(mock-gated) - 简化 ResultPage,仅展示素材预览 - 删除 GeneratePage、ProjectPage 及未使用的 hooks - 更新 docs/ 文档(frontend.md、api.md、style-keys.md、_index.md)
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { getTasks } from '../api/project'
|
||||
import type { Task } from '../api/types'
|
||||
import StatusBadge from './StatusBadge'
|
||||
import Skeleton from './Skeleton'
|
||||
|
||||
const ASSET_TYPE_LABELS: Record<string, string> = {
|
||||
sprite: '精灵',
|
||||
background: '背景',
|
||||
ui: 'UI',
|
||||
animation: '动画',
|
||||
}
|
||||
|
||||
function formatRelativeTime(dateStr: string): string {
|
||||
const now = Date.now()
|
||||
const then = new Date(dateStr).getTime()
|
||||
const diff = now - then
|
||||
const minutes = Math.floor(diff / 60000)
|
||||
if (minutes < 1) return '刚刚'
|
||||
if (minutes < 60) return `${minutes}分钟前`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
if (hours < 24) return `${hours}小时前`
|
||||
const days = Math.floor(hours / 24)
|
||||
return `${days}天前`
|
||||
}
|
||||
|
||||
export default function TaskListPanel() {
|
||||
const { projectId = '' } = useParams()
|
||||
const [tasks, setTasks] = useState<Task[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
const fetchTasks = useCallback(async () => {
|
||||
if (!projectId) return
|
||||
try {
|
||||
const data = await getTasks(projectId)
|
||||
setTasks(data)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [projectId])
|
||||
|
||||
useEffect(() => {
|
||||
fetchTasks()
|
||||
const timer = setInterval(fetchTasks, 15000)
|
||||
return () => clearInterval(timer)
|
||||
}, [fetchTasks])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ padding: 16 }}>
|
||||
<Skeleton variant="text" lines={4} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<h3 style={{ fontSize: 14, fontWeight: 600, padding: '16px 16px 12px', borderBottom: '1px solid var(--border)' }}>
|
||||
任务列表 ({tasks.length})
|
||||
</h3>
|
||||
<div style={{ flex: 1, overflow: 'auto' }}>
|
||||
{tasks.length === 0 ? (
|
||||
<p style={{ padding: 16, fontSize: 13, color: 'var(--text-muted)', textAlign: 'center' }}>
|
||||
暂无任务
|
||||
</p>
|
||||
) : (
|
||||
tasks.map(task => (
|
||||
<a
|
||||
key={task.id}
|
||||
href={task.status === 'completed' ? `/projects/${projectId}/tasks/${task.id}` : undefined}
|
||||
style={{
|
||||
display: 'block',
|
||||
padding: '12px 16px',
|
||||
borderBottom: '1px solid var(--border)',
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
cursor: task.status === 'completed' ? 'pointer' : 'default',
|
||||
transition: 'background 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
if (task.status === 'completed')
|
||||
(e.currentTarget as HTMLElement).style.background = 'var(--accent-dim)'
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
(e.currentTarget as HTMLElement).style.background = ''
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}>
|
||||
<span style={{ fontSize: 12, color: 'var(--text-muted)' }}>
|
||||
{ASSET_TYPE_LABELS[task.assetType] || task.assetType}
|
||||
</span>
|
||||
<StatusBadge status={task.status} />
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: 13,
|
||||
lineHeight: 1.4,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{task.prompt}
|
||||
</p>
|
||||
<span style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 4, display: 'block' }}>
|
||||
{formatRelativeTime(task.createdAt)}
|
||||
</span>
|
||||
</a>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user