feat: 初始化船长·HeiXiu 桌面应用项目(错误是svg的问题)

Electron + React 19 + TypeScript + Ant Design 6 + Tailwind CSS 4

  【核心架构】
  - Electron 主进程(窗口管理、平台检测、图标适配)
  - Vite 构建链(双产物 dist/ + dist-electron/)
  - 共享层 shared/(类型、常量、工具函数)
  - IPC 通信框架 + 安全守卫

  【主题系统】
  - 蓝紫渐变主题(亮色/暗色双模式)
  - Ant Design ConfigProvider 适配(lightAlgorithm / darkAlgorithm)
  - Tailwind CSS 4 @theme 指令 + CSS 变量
  - 设计令牌三处同步(tokens.ts / globals.css / antd-theme.ts)

  【导航栏】
  - 自定义 H5 导航栏替代系统菜单
  - 个人版/企业版双布局
  - 未登录拦截 + 事件总线驱动登录弹窗

  【登录注册】
  - Modal 弹层(Portal)+ Tabs 切换
  - 登录/注册表单动态字段渲染
  - 记住密码 / 自动登录 / 用户协议

  【更新系统】
  - 自定义 API 版本检查(替代 electron-updater generic provider)
  - 手动下载 + SHA512 校验 + spawn NSIS 安装
  - 渲染进程更新状态机 Hook(useUpdater)
  - 检查更新按钮 + 下载进度展示

  【构建工具链】
  - build.mjs 构建总管(--win/--mac/--linux --personal/--enterprise --build/--clean/--sign/--upload/--publish)
  - scripts/ 自动化脚本(clean / generate-update-info / upload-oss / publish-release)
  - electron-builder NSIS 安装器(可选路径、多版本打包)
  - update-info.json 自动生成(fileSize/sha512/changelog/releaseDate)

  【代码质量】
  - ESLint + TypeScript strict + Prettier
  - husky + lint-staged(提交前自动检查)
  - commitlint(规范提交信息)
  - Playwright E2E + Vitest 单元测试框架
  - rollup-plugin-visualizer 打包体积分析

  【文档】
  - README.md(快速开始 + 目录结构 + 命令速查)
  - UpdateA.md(发布手册:API/签名/发版流程/数据库)
  - CHANGELOG.md(更新日志)
  - .env.example(构建环境变量模板)
This commit is contained in:
2026-06-03 15:00:51 +08:00
commit 767bb8b297
95 changed files with 20814 additions and 0 deletions

149
src/pages/login/index.tsx Normal file
View File

@@ -0,0 +1,149 @@
// ============================================================
// LoginPage — 登录/注册 Modal 弹层
//
// 行为:
// - 未登录时弹出不可操作主页Modal 遮罩)
// - 可关闭(右上 X关闭后主页可操作但 API 401 时会再次弹出
// - 登录成功 → 关闭 Modal更新全局登录态
// ============================================================
import { useState, useCallback } from 'react';
import { Modal, Tabs, Typography, App } from 'antd';
import { useAppContext } from '@/contexts/app-context.ts';
import { useTheme } from '@/hooks/use-theme.ts';
import { LoginForm } from './components/LoginForm';
import { RegisterForm } from './components/RegisterForm';
import { useAuthState } from './hooks/use-auth-state';
import type { LoginFormValues, RegisterFormValues } from './types';
const { Text } = Typography;
interface LoginPageProps {
open: boolean;
onClose: () => void;
}
export function LoginPage({ open, onClose }: LoginPageProps) {
const { edition, login } = useAppContext();
const { isDark } = useTheme();
const { message } = App.useApp();
const isEnterprise = edition === 'enterprise';
const [activeTab, setActiveTab] = useState<string>('login');
const [submitting, setSubmitting] = useState(false);
const authState = useAuthState();
// ========== 登录 ==========
const handleLogin = useCallback(
async (values: LoginFormValues) => {
setSubmitting(true);
try {
// TODO: 接入登录 API
console.log('[Login]', values);
authState.saveAuth(values.username, values.password);
message.success('登录成功Demo');
login();
onClose();
} catch (err) {
message.error((err as Error).message || '登录失败');
} finally {
setSubmitting(false);
}
},
[authState, message, login, onClose],
);
// ========== 注册 ==========
const handleRegister = useCallback(
async (values: RegisterFormValues) => {
setSubmitting(true);
try {
console.log('[Register]', values);
message.success('注册成功Demo请登录');
setActiveTab('login');
} catch (err) {
message.error((err as Error).message || '注册失败');
} finally {
setSubmitting(false);
}
},
[message],
);
const handleSendSms = useCallback(
(phone: string) => {
console.log('[SMS]', phone);
message.info(`验证码已发送到 ${phone}Demo`);
},
[message],
);
// ========== 渲染 ==========
const loginTab = (
<LoginForm
initialUsername={authState.username}
initialPassword={authState.password}
initialRemember={authState.remember}
initialAutoLogin={authState.autoLogin}
submitting={submitting}
onSubmit={handleLogin}
onRememberChange={authState.handleRememberChange}
onAutoLoginChange={authState.handleAutoLoginChange}
/>
);
const registerTab = (
<RegisterForm submitting={submitting} onSubmit={handleRegister} onSendSms={handleSendSms} />
);
return (
<Modal
open={open}
onCancel={onClose}
footer={null}
width={440}
centered
destroyOnHidden={false}
mask={{
closable: false,
}}
styles={{
body: {
padding: '24px 32px 16px',
background: isDark ? '#1A1730' : '#FFFFFF',
},
}}
>
{/* 标题 */}
<div className="text-center mb-5">
<Text strong className="text-lg gradient-primary-text block">
· HeiXiu
</Text>
<Text type="secondary" className="text-xs">
{isEnterprise ? '企业版 — 员工账号登录' : '个人版'}
</Text>
</div>
{/* 个人版 Tab | 企业版仅登录 */}
{isEnterprise ? (
loginTab
) : (
<Tabs
activeKey={activeTab}
onChange={setActiveTab}
centered
size="small"
items={[
{ key: 'login', label: '登录', children: loginTab },
{ key: 'register', label: '注册', children: registerTab },
]}
/>
)}
</Modal>
);
}