feat: 实现业务首页(三栏布局 + 轮播图)

## 首页整体架构
  - 新增 src/pages/home/ — 完整首页模块(10 个组件)
    - HomePage / HomeContent:三栏弹性布局,拖动分隔条调整列宽
    - BannerCarousel:顶部轮播图(antd Carousel,2280×90 长条图片适配)
    - LeftPanel:模型选择器(Menu 分组) + 任务记录(Table 分页),纵向拖拽调整比例
    - CenterPanel / ModelInputForm:动态表单,根据模型 param_schema/ui_config 渲染控件
    - RightPanel / OutputPreview:任务输出预览(Image/Video)
  - 新增 Layout 组件:h-screen flex 布局,首页 Banner + NavBar + Outlet

  ## 数据层(数据与视图分离)
  - 新增 src/services/modules/ — API 模块(banner/models/task/auth)
    - TaskAPI 静态方法,TaskInfoItemResponseBody 判别联合类型
    - ModelAPI / BannerAPI,const 断言路由常量
  - 新增 src/hooks/use-async.ts — 通用异步 Hook
    - useAsyncData:竞态处理、条件请求(enabled)、自动错误日志
    - useAsyncMutation:数据变更,onSuccess/onError
    - getErrorMessage():RequestErrorType → 可读错误信息
  - 新增 src/hooks/use-api.ts — 业务 Hook
    - useBannerList / useModelList / useTaskList / useTaskDetail / useSubmitTask

  ## 主题与交互
  - 重写暗色令牌:科技感蓝紫(#080C24 底 / #0F1340 面板 / #2A3278 边框)
  - 三栏分隔线:token.colorBorderSecondary 常驻 + 手柄竖线 hover 高亮
  - 左面板纵向拖拽:模型区/任务区可调比例,ResizeObserver 自适应
  - 暗色/亮色同步:tokens.ts + antd-theme.ts + globals.css 三处一致

  ## 基础设施
  - EventBus 新增 TASK_SUBMITTED 事件(替代 Context 数字递增 hack)
  - HomeContext 状态管理(选中模型、任务分页、折叠状态)
  - 所有组件边框使用 token.colorBorderSecondary(主题自适应)
  - Table 滚动高度 ResizeObserver 动态计算(修复分页器遮挡)
This commit is contained in:
2026-06-05 16:24:17 +08:00
parent 6204c14b88
commit 1da95eaef3
31 changed files with 2639 additions and 470 deletions

View File

@@ -1,277 +1,14 @@
// ============================================================
// HomePage — 首页仪表盘(从 App.tsx 提取
// HomePage — 首页(业务入口
// 组合BannerCarousel在 Layout 中)+ HomeContent三列正文
// ============================================================
import { useState } from 'react';
import {
Button,
Card,
Space,
Tag,
Typography,
Switch as AntSwitch,
Slider,
Progress,
theme as antTheme,
} from 'antd';
import {
ApiOutlined,
ThunderboltOutlined,
BulbOutlined,
BulbFilled,
DownloadOutlined,
SyncOutlined,
CheckCircleOutlined,
ExclamationCircleOutlined,
RocketOutlined,
} from '@ant-design/icons';
import { getPlatformName, isDev } from '@/utils/platform';
import { gradientPrimary } from '@/theme/tokens';
import { useTheme } from '@/hooks/use-theme';
import { useUpdater } from '@/hooks/use-updater';
import { useAppContext } from '@/contexts/app-context';
const { Title, Text } = Typography;
import { HomeContent } from './HomeContent';
export function HomePage() {
const { isDark, toggleTheme } = useTheme();
const { platform, edition, isLoggedIn, logout } = useAppContext();
const { token } = antTheme.useToken();
const [progress, setProgress] = useState(65);
const {
status: updateStatus,
progress: updateProgress,
info: updateInfo,
error: updateError,
checkForUpdates,
installUpdate,
} = useUpdater();
return (
<div className="flex-1 p-8">
<div className="mx-auto max-w-4xl space-y-6">
{/* ---- 标题区 + 主题开关 ---- */}
<div className="text-center space-y-2">
<div className="flex items-center justify-center gap-4">
<Title level={1} className="mb-0! gradient-primary-text">
HeiXiu ·
</Title>
<AntSwitch
checked={isDark}
onChange={toggleTheme}
checkedChildren={<BulbFilled />}
unCheckedChildren={<BulbOutlined />}
title={isDark ? '切换亮色模式' : '切换暗色模式'}
/>
</div>
<Text type="secondary">
Electron + React 19 + TypeScript + Ant Design + Tailwind CSS
</Text>
<br />
<Space size={4}>
<Tag color={isDark ? 'blue' : 'purple'}>
{isDark ? '🌙 暗色' : '☀️ 亮色'}
</Tag>
<Tag color={edition === 'enterprise' ? 'gold' : 'green'}>
{edition === 'enterprise' ? '企业版' : '个人版'}
</Tag>
{isLoggedIn ? (
<>
<Tag color="blue"></Tag>
<Button size="small" type="link" danger onClick={logout}>
退
</Button>
</>
) : (
<Tag color="default"></Tag>
)}
</Space>
</div>
{/* ---- 导航栏状态提示 ---- */}
<Card size="small" variant="borderless" className="rounded-lg">
<Text type="secondary" className="text-xs">
💡 使 <Text strong>H5 </Text>
{platform === 'darwin'
? ' macOS 保留了最小 Edit 菜单以支持 Cmd+C/V 等快捷键。'
: ' Windows/Linux 已完全移除系统菜单栏。'}
</Text>
</Card>
{/* ---- 主题色预览 ---- */}
<Card
title="🎨 蓝紫渐变主题色预览"
variant="borderless"
className="rounded-lg shadow-lg"
>
<Space orientation="vertical" size="large" className="w-full">
{/* 渐变展示 */}
<div>
<Text strong></Text>
<div className="flex gap-3 mt-2">
<div
className="flex-1 h-16 rounded-lg flex items-center justify-center text-white font-medium shadow-md"
style={{ background: gradientPrimary }}
>
135°
</div>
<div
className="flex-1 h-16 rounded-lg flex items-center justify-center text-white font-medium shadow-md"
style={{ background: 'linear-gradient(90deg, #4F46E5, #7C3AED)' }}
>
90°
</div>
</div>
</div>
{/* 组件示例 */}
<div>
<Text strong>Ant Design </Text>
<Space wrap className="mt-2">
<Button type="primary" icon={<ThunderboltOutlined />}>
</Button>
<Button></Button>
<Button type="dashed">线</Button>
<Button type="text"></Button>
<Button type="link"></Button>
</Space>
</div>
{/* 滑块 + 进度条 */}
<div>
<Text strong></Text>
<div className="flex items-center gap-4 mt-2">
<span className="text-sm" style={{ color: token.colorTextSecondary }}>
:
</span>
<Slider
className="flex-1"
value={progress}
onChange={setProgress}
min={0}
max={100}
/>
<Progress
type="circle"
percent={progress}
size={48}
strokeColor={{ '0%': '#4F46E5', '100%': '#7C3AED' }}
/>
</div>
</div>
</Space>
</Card>
{/* ---- 平台信息 ---- */}
<Card title="🖥️ 系统信息" variant="borderless" className="rounded-lg shadow-lg">
<Space orientation="vertical" size="middle" className="w-full">
<div className="flex items-center gap-2">
<ApiOutlined style={{ color: token.colorPrimary }} />
<Text strong></Text>
<Tag color="purple">{getPlatformName()}</Tag>
</div>
<div className="flex items-center gap-2">
<ApiOutlined style={{ color: token.colorPrimary }} />
<Text strong></Text>
<Tag color={isDev() ? 'blue' : 'green'}>
{isDev() ? '开发模式' : '生产模式'}
</Tag>
</div>
<div className="flex items-center gap-2">
<ApiOutlined style={{ color: token.colorPrimary }} />
<Text strong></Text>
<Tag color="purple">
H5 {edition === 'enterprise' ? '企业版' : '个人版'}
</Tag>
</div>
</Space>
</Card>
{/* ---- 版本更新 ---- */}
<Card title="🔄 版本更新" variant="borderless" className="rounded-lg shadow-lg">
<Space orientation="vertical" size="middle" className="w-full">
{/* 状态提示 */}
{updateStatus === 'checking' && (
<div className="flex items-center gap-2">
<SyncOutlined spin style={{ color: token.colorPrimary }} />
<Text type="secondary">...</Text>
</div>
)}
{updateStatus === 'no-update' && (
<div className="flex items-center gap-2">
<CheckCircleOutlined style={{ color: token.colorSuccess }} />
<Text type="success"></Text>
</div>
)}
{updateStatus === 'error' && (
<div className="flex items-center gap-2">
<ExclamationCircleOutlined style={{ color: token.colorError }} />
<Text type="danger">{updateError?.message || '检查更新失败'}</Text>
</div>
)}
{/* 发现新版本 */}
{(updateStatus === 'available' || updateStatus === 'downloading') && (
<>
<div className="flex items-center gap-2">
<RocketOutlined style={{ color: token.colorPrimary }} />
<Text strong>{updateInfo?.version}</Text>
{updateInfo?.forceUpdate && <Tag color="red"></Tag>}
</div>
{updateInfo?.releaseNotes && (
<Text type="secondary" className="text-xs whitespace-pre-line">
{updateInfo.releaseNotes}
</Text>
)}
</>
)}
{/* 下载进度 */}
{updateStatus === 'downloading' && (
<Progress
percent={updateProgress.percent}
strokeColor={{ '0%': '#4F46E5', '100%': '#7C3AED' }}
/>
)}
{/* 下载完成 */}
{updateStatus === 'downloaded' && (
<>
<div className="flex items-center gap-2">
<CheckCircleOutlined style={{ color: token.colorSuccess }} />
<Text></Text>
</div>
<Button
type="primary"
icon={<DownloadOutlined />}
onClick={installUpdate}
>
</Button>
</>
)}
{/* 操作按钮 */}
<div className="flex items-center gap-3">
<Button
icon={<SyncOutlined spin={updateStatus === 'checking'} />}
onClick={checkForUpdates}
disabled={updateStatus === 'checking'}
>
</Button>
{isDev() && (
<Text type="secondary" className="text-xs">
5
</Text>
)}
</div>
</Space>
</Card>
</div>
<div style={{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
<HomeContent />
</div>
);
}