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:
149
src/pages/login/index.tsx
Normal file
149
src/pages/login/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user