refactor: CSS 拆分 + auth 持久化提取 + 目录整理
- globals.css 拆分为 4 文件:tokens.css(颜色)/ transitions.css(过渡)/ base.css(重置)/ globals.css(入口) - 提取 auth-persistence.ts 到 services/,解除 AppProvider→pages/login/hooks 反向依赖 - use-auth-state.ts 精简为纯 React Hook,持久化逻辑委托给 services/auth-persistence.ts - components/common/ → components/ui/(目录名语义更清晰) - TypeScript + Vite 构建通过 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,7 @@ import { setToken, clearToken, initTokenStore } from '@/services/request';
|
||||
import { emit, EVENTS } from '@/utils/event-bus';
|
||||
import { logger } from '@/utils/logger';
|
||||
import { AuthAPI } from '@/services/modules';
|
||||
import { getAutoLoginFlag, clearAutoLoginFlag } from '@/pages/login/hooks/use-auth-state';
|
||||
import { getAutoLoginFlag, clearAutoLoginFlag } from '@/services/auth-persistence';
|
||||
import {
|
||||
initAuthRefresh,
|
||||
getStoredRefreshToken,
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import {useState, useEffect, useCallback, useRef, type ComponentType} from 'react';
|
||||
import {Modal, Drawer} from 'antd';
|
||||
import {on, off, EVENTS} from '@/utils/event-bus';
|
||||
import {FloatingPanel} from '@/components/common/FloatingPanel';
|
||||
import {FloatingPanel} from '@/components/ui/FloatingPanel';
|
||||
import {ComplianceAssetsPage} from '@/pages/compliance-assets/ComplianceAssetsPage';
|
||||
import {AccountPage} from '@/pages/account/AccountPage';
|
||||
import {VipPage} from '@/pages/vip/VipPage';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { UserOutlined, LockOutlined } from '@ant-design/icons';
|
||||
|
||||
import { loginFields } from '../config/login-fields';
|
||||
import { validateForm } from '../config/validators';
|
||||
import { LegalTextModal } from '@/components/common/LegalTextModal';
|
||||
import { LegalTextModal } from '@/components/ui/LegalTextModal';
|
||||
import type { LoginFormValues } from '../types';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { useState, useCallback, useRef } from 'react';
|
||||
import { Input, Button, Checkbox, Typography } from 'antd';
|
||||
|
||||
import { LegalTextModal } from '@/components/common/LegalTextModal';
|
||||
import { LegalTextModal } from '@/components/ui/LegalTextModal';
|
||||
import {
|
||||
UserOutlined,
|
||||
LockOutlined,
|
||||
|
||||
@@ -1,76 +1,34 @@
|
||||
// ============================================================
|
||||
// useAuthState — 记住账号 / 自动登录 状态管理
|
||||
// useAuthState — 记住账号 / 自动登录 表单状态 Hook
|
||||
//
|
||||
// "记住密码":下次打开页面自动回填用户名(不再存储密码)
|
||||
// "自动登录":登录成功时标记,AppProvider 启动时用 refresh_token 静默续期
|
||||
//
|
||||
// 持久化逻辑已提取到 services/auth-persistence.ts,
|
||||
// 此处仅负责 React 组件状态(useState + useCallback)。
|
||||
//
|
||||
// 安全:
|
||||
// - 密码从不持久化存储(access_token / refresh_token 由 safeStorage 加密保护)
|
||||
// - 安全性由 refresh_token + access_token 的 JWT 双重机制 + OS 密钥链保障
|
||||
// ============================================================
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import {
|
||||
saveAuth,
|
||||
clearSavedAuth,
|
||||
getSavedUsername,
|
||||
getSavedRemember,
|
||||
getSavedAutoLogin,
|
||||
} from '@/services/auth-persistence';
|
||||
|
||||
const STORAGE_KEY = 'ele-heixiu-auth';
|
||||
|
||||
interface StoredAuth {
|
||||
/** 上次登录的用户名(表单回填用) */
|
||||
username: string;
|
||||
/** 是否记住用户名(勾选后表单自动回填用户名) */
|
||||
remember: boolean;
|
||||
/** 是否勾选了自动登录(AppProvider 据此决定启动时是否刷新 token) */
|
||||
autoLogin: boolean;
|
||||
}
|
||||
|
||||
function readAuth(): StoredAuth | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return null;
|
||||
return JSON.parse(raw) as StoredAuth;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeAuth(auth: StoredAuth): void {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(auth));
|
||||
} catch {
|
||||
/* 静默 */
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 工具函数(不依赖 React,AppProvider 可直接调用)----------
|
||||
|
||||
/** 读取自动登录标记 */
|
||||
export function getAutoLoginFlag(): boolean {
|
||||
return readAuth()?.autoLogin || false;
|
||||
}
|
||||
|
||||
/** 仅清除自动登录标记(保留用户名,下次还能回填表单) */
|
||||
export function clearAutoLoginFlag(): void {
|
||||
const auth = readAuth();
|
||||
if (!auth) return;
|
||||
writeAuth({ ...auth, autoLogin: false });
|
||||
}
|
||||
|
||||
/** 清除所有保存的认证信息 */
|
||||
export function clearSavedAuth(): void {
|
||||
try {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
} catch {
|
||||
/* 静默 */
|
||||
}
|
||||
}
|
||||
export { getAutoLoginFlag, clearAutoLoginFlag, clearSavedAuth } from '@/services/auth-persistence';
|
||||
|
||||
// ---------- Hook ----------
|
||||
|
||||
export function useAuthState() {
|
||||
const saved = readAuth();
|
||||
|
||||
const [username, setUsername] = useState(saved?.username || '');
|
||||
const [remember, setRemember] = useState(saved?.remember || false);
|
||||
const [autoLogin, setAutoLogin] = useState(saved?.autoLogin || false);
|
||||
const [username, setUsername] = useState(getSavedUsername);
|
||||
const [remember, setRemember] = useState(getSavedRemember);
|
||||
const [autoLogin, setAutoLogin] = useState(getSavedAutoLogin);
|
||||
|
||||
const handleRememberChange = useCallback((checked: boolean) => {
|
||||
setRemember(checked);
|
||||
@@ -93,16 +51,11 @@ export function useAuthState() {
|
||||
* @param doRemember - 是否勾选"记住用户名"
|
||||
* @param doAutoLogin - 是否勾选"自动登录"
|
||||
*/
|
||||
const saveAuth = useCallback(
|
||||
const handleSaveAuth = useCallback(
|
||||
(user: string, _password: string, doRemember: boolean, doAutoLogin: boolean) => {
|
||||
setUsername(user);
|
||||
if (doRemember || doAutoLogin) {
|
||||
const payload: StoredAuth = {
|
||||
username: user,
|
||||
remember: doRemember,
|
||||
autoLogin: doAutoLogin,
|
||||
};
|
||||
writeAuth(payload);
|
||||
saveAuth(user, doRemember, doAutoLogin);
|
||||
}
|
||||
},
|
||||
[],
|
||||
@@ -115,6 +68,6 @@ export function useAuthState() {
|
||||
setUsername,
|
||||
handleRememberChange,
|
||||
handleAutoLoginChange,
|
||||
saveAuth,
|
||||
saveAuth: handleSaveAuth,
|
||||
};
|
||||
}
|
||||
|
||||
84
src/services/auth-persistence.ts
Normal file
84
src/services/auth-persistence.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
// ============================================================
|
||||
// auth-persistence — 本地认证偏好持久化(记住账号 / 自动登录)
|
||||
//
|
||||
// 职责:
|
||||
// - 存储/读取登录表单偏好(用户名回填、记住密码、自动登录标记)
|
||||
// - 纯 localStorage 操作,不依赖 React
|
||||
// - 密码从不持久化(安全性由 JWT + safeStorage 保障)
|
||||
//
|
||||
// 与 auth-token.ts 的分工:
|
||||
// auth-token.ts → JWT token 生命周期(refresh/access,safeStorage 加密)
|
||||
// auth-persistence.ts → 登录表单偏好(localStorage 明文,仅用户名/标记)
|
||||
// ============================================================ */
|
||||
|
||||
const STORAGE_KEY = 'ele-heixiu-auth';
|
||||
|
||||
interface StoredAuth {
|
||||
/** 上次登录的用户名(表单回填用) */
|
||||
username: string;
|
||||
/** 是否记住用户名(勾选后表单自动回填用户名) */
|
||||
remember: boolean;
|
||||
/** 是否勾选了自动登录(AppProvider 据此决定启动时是否刷新 token) */
|
||||
autoLogin: boolean;
|
||||
}
|
||||
|
||||
function readAuth(): StoredAuth | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return null;
|
||||
return JSON.parse(raw) as StoredAuth;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeAuth(auth: StoredAuth): void {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(auth));
|
||||
} catch {
|
||||
/* 静默 */
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 公共 API ----------
|
||||
|
||||
/** 读取自动登录标记(AppProvider 启动时调用) */
|
||||
export function getAutoLoginFlag(): boolean {
|
||||
return readAuth()?.autoLogin || false;
|
||||
}
|
||||
|
||||
/** 仅清除自动登录标记(保留用户名,下次还能回填表单) */
|
||||
export function clearAutoLoginFlag(): void {
|
||||
const auth = readAuth();
|
||||
if (!auth) return;
|
||||
writeAuth({ ...auth, autoLogin: false });
|
||||
}
|
||||
|
||||
/** 清除所有保存的认证偏好 */
|
||||
export function clearSavedAuth(): void {
|
||||
try {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
} catch {
|
||||
/* 静默 */
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存认证偏好(登录成功时调用) */
|
||||
export function saveAuth(username: string, remember: boolean, autoLogin: boolean): void {
|
||||
writeAuth({ username, remember, autoLogin });
|
||||
}
|
||||
|
||||
/** 读取已保存的用户名(表单回填用) */
|
||||
export function getSavedUsername(): string {
|
||||
return readAuth()?.username || '';
|
||||
}
|
||||
|
||||
/** 读取记住密码标记 */
|
||||
export function getSavedRemember(): boolean {
|
||||
return readAuth()?.remember || false;
|
||||
}
|
||||
|
||||
/** 读取自动登录标记 */
|
||||
export function getSavedAutoLogin(): boolean {
|
||||
return readAuth()?.autoLogin || false;
|
||||
}
|
||||
130
src/theme/base.css
Normal file
130
src/theme/base.css
Normal file
@@ -0,0 +1,130 @@
|
||||
/* ============================================================
|
||||
* 基础样式 — 重置、滚动条、聚焦环、选中文本、链接、拖拽区域
|
||||
*
|
||||
* 改全局基础样式 → 改这个文件。
|
||||
* ============================================================ */
|
||||
|
||||
/* ============================================================
|
||||
* 全局重置
|
||||
* ============================================================ */
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: var(--font-family-base);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
/* 亮/暗切换过渡 */
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: var(--color-text-base);
|
||||
background-color: var(--color-bg-base);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#root {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 滚动条美化
|
||||
* ============================================================ */
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #c7d2fe;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #a5b4fc;
|
||||
}
|
||||
|
||||
/* 暗色模式滚动条 */
|
||||
[data-theme='dark'] ::-webkit-scrollbar-thumb {
|
||||
background: #4338ca;
|
||||
}
|
||||
|
||||
[data-theme='dark'] ::-webkit-scrollbar-thumb:hover {
|
||||
background: #4f46e5;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 聚焦环 — 使用品牌色
|
||||
* ============================================================ */
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 选中文本 — 使用品牌色
|
||||
* ============================================================ */
|
||||
|
||||
::selection {
|
||||
background: rgba(99, 102, 241, 0.2);
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
[data-theme='dark'] ::selection {
|
||||
background: rgba(129, 140, 248, 0.3);
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 链接全局样式
|
||||
* ============================================================ */
|
||||
|
||||
a {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
transition: color var(--transition-duration) var(--transition-timing);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--color-primary-light);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 窗口拖拽区域(自定义导航栏)
|
||||
*
|
||||
* macOS hiddenInset 模式下,整个导航栏区域可拖拽移动窗口。
|
||||
* .navbar-drag-region → 可拖拽(背景区域)
|
||||
* .navbar-no-drag → 不可拖拽(按钮、链接等交互元素)
|
||||
* ============================================================ */
|
||||
|
||||
.navbar-drag-region {
|
||||
-webkit-app-region: drag;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.navbar-no-drag {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
/* macOS 红绿灯按钮区域不遮挡内容 */
|
||||
@supports (-webkit-app-region: drag) {
|
||||
.navbar-drag-region {
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
}
|
||||
@@ -1,374 +1,14 @@
|
||||
/* ============================================================
|
||||
* 全局样式 —— Tailwind CSS v4 指令 + 蓝紫渐变主题扩展 + 重置样式
|
||||
* Tailwind v4 使用 CSS @theme 指令替代 tailwind.config.ts,
|
||||
* 设计令牌需与 tokens.ts / antd-theme.ts 保持同步。
|
||||
* 全局样式入口 — Tailwind CSS v4 + 设计令牌 + 过渡 + 基础样式
|
||||
*
|
||||
* 拆分为 4 个文件,按职责定位:
|
||||
* tokens.css → 改颜色 / 品牌色 / 亮暗变量
|
||||
* transitions.css → 改主题切换过渡参数
|
||||
* base.css → 改重置 / 滚动条 / 链接 / 拖拽区域
|
||||
* globals.css → 本文件(入口,只负责引入顺序)
|
||||
* ============================================================ */
|
||||
|
||||
@import 'tailwindcss';
|
||||
|
||||
/* ============================================================
|
||||
* Tailwind CSS v4 主题扩展(用 @theme 指令)
|
||||
* 此处定义的自定义令牌会自动生成对应的 Tailwind 工具类。
|
||||
* 例如:colors.primary → bg-primary, text-primary, border-primary 等。
|
||||
* ============================================================ */
|
||||
|
||||
/* 品牌色 — 蓝紫渐变 */
|
||||
@theme {
|
||||
/* 主色系 */
|
||||
--color-primary: #6366f1; /* Indigo-500 */
|
||||
--color-primary-light: #818cf8; /* Indigo-400 */
|
||||
--color-primary-dark: #4f46e5; /* Indigo-600 */
|
||||
--color-primary-50: #eef2ff; /* Indigo-50 */
|
||||
--color-primary-100: #e0e7ff; /* Indigo-100 */
|
||||
--color-primary-200: #c7d2fe; /* Indigo-200 */
|
||||
--color-primary-300: #a5b4fc; /* Indigo-300 */
|
||||
--color-primary-400: #818cf8; /* Indigo-400 */
|
||||
--color-primary-500: #6366f1; /* Indigo-500 */
|
||||
--color-primary-600: #4f46e5; /* Indigo-600 */
|
||||
--color-primary-700: #4338ca; /* Indigo-700 */
|
||||
--color-primary-800: #3730a3; /* Indigo-800 */
|
||||
--color-primary-900: #312e81; /* Indigo-900 */
|
||||
|
||||
/* 辅助色系 — Violet */
|
||||
--color-secondary: #8b5cf6; /* Violet-500 */
|
||||
--color-secondary-light: #a78bfa; /* Violet-400 */
|
||||
--color-secondary-dark: #7c3aed; /* Violet-600 */
|
||||
|
||||
/* 功能色 */
|
||||
--color-success: #10b981;
|
||||
--color-success-light: #d1fae5;
|
||||
--color-warning: #f59e0b;
|
||||
--color-warning-light: #fef3c7;
|
||||
--color-error: #ef4444;
|
||||
--color-error-light: #fee2e2;
|
||||
--color-info: #3b82f6;
|
||||
--color-info-light: #dbeafe;
|
||||
|
||||
/* 圆角 */
|
||||
--radius-xs: 4px;
|
||||
--radius-sm: 6px;
|
||||
--radius-base: 8px;
|
||||
--radius-lg: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-2xl: 24px;
|
||||
|
||||
/* 字体 */
|
||||
--font-family-base:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans',
|
||||
'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
--font-family-mono:
|
||||
'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, 'Courier New', monospace;
|
||||
|
||||
/* 动画 */
|
||||
--transition-duration: 0.2s;
|
||||
--transition-timing: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 🌞 亮色模式 CSS 变量(默认)
|
||||
* 这些变量与 tokens.ts 中的亮色令牌同步
|
||||
* ============================================================ */
|
||||
|
||||
:root,
|
||||
[data-theme='light'] {
|
||||
/* 中性色 — 亮色 */
|
||||
--color-text-base: #1e1b4b;
|
||||
--color-text-secondary: #6b7280;
|
||||
--color-text-tertiary: #9ca3af;
|
||||
--color-bg-base: #ffffff;
|
||||
--color-bg-container: #f9fafb;
|
||||
--color-bg-layout: #f5f3ff;
|
||||
--color-bg-elevated: #ffffff;
|
||||
--color-border-base: #e5e7eb;
|
||||
--color-border-secondary: #f3f4f6;
|
||||
|
||||
/* 阴影 */
|
||||
--shadow-base: 0 1px 3px 0 rgba(99, 102, 241, 0.08), 0 1px 2px -1px rgba(99, 102, 241, 0.08);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(99, 102, 241, 0.1), 0 4px 6px -4px rgba(99, 102, 241, 0.1);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 🌙 暗色模式 CSS 变量
|
||||
* 当 <html data-theme="dark"> 时自动切换
|
||||
* 这些变量与 tokens.ts 中的暗色令牌同步
|
||||
* ============================================================ */
|
||||
|
||||
[data-theme='dark'] {
|
||||
/* 中性色 — 暗色(科技蓝紫) */
|
||||
--color-text-base: #e8e8fa;
|
||||
--color-text-secondary: #a0a8d8;
|
||||
--color-text-tertiary: #6e78b0;
|
||||
--color-bg-base: #080c24;
|
||||
--color-bg-container: #0f1340;
|
||||
--color-bg-layout: #050818;
|
||||
--color-bg-elevated: #171b52;
|
||||
--color-border-base: #2a3278;
|
||||
--color-border-secondary: #1c2250;
|
||||
|
||||
/* 暗色模式下品牌色提亮以保持对比度 */
|
||||
--color-primary: #818cf8;
|
||||
--color-primary-light: #a5b4fc;
|
||||
--color-primary-dark: #6366f1;
|
||||
|
||||
/* 阴影 — 暗色下用深投影 */
|
||||
--shadow-base: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px -1px rgba(0, 0, 0, 0.3);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -4px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 蓝紫渐变工具类
|
||||
* Tailwind v4 使用 @utility 定义自定义工具类
|
||||
* ============================================================ */
|
||||
|
||||
@utility gradient-primary {
|
||||
background: linear-gradient(135deg, #4f46e5, #7c3aed);
|
||||
}
|
||||
|
||||
@utility gradient-primary-hover {
|
||||
background: linear-gradient(135deg, #6366f1, #8b5cf6);
|
||||
}
|
||||
|
||||
@utility gradient-primary-text {
|
||||
background: linear-gradient(135deg, #4f46e5, #7c3aed);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
@utility gradient-border {
|
||||
border-image: linear-gradient(135deg, #4f46e5, #7c3aed) 1;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 全局重置样式
|
||||
* 仅放必要的重置,不堆积组件样式
|
||||
* ============================================================ */
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: var(--font-family-base);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
/* 亮/暗切换过渡 */
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: var(--color-text-base);
|
||||
background-color: var(--color-bg-base);
|
||||
min-height: 100vh;
|
||||
transition:
|
||||
background-color 0.15s linear,
|
||||
color 0.15s linear;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* View Transitions API — GPU 合成器驱动的主题切换动画
|
||||
* 替代 80+ 个独立 CSS 过渡,单次 cross-fade 消除 Electron 卡顿。
|
||||
* 仅在 Chromium 111+(Electron 28+)生效,其他浏览器回退 CSS 过渡。
|
||||
* ============================================================ */
|
||||
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
animation-duration: 0.15s;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 主题切换过渡 — 精准覆盖大面积视觉容器,避免 * 全局选择器
|
||||
* 在 Electron 中为数千个 DOM 节点同时启动过渡造成卡顿。
|
||||
*
|
||||
* 策略:
|
||||
* 1. 容器/布局组件(Card / Modal / Layout / Drawer 等)
|
||||
* 2. 表格 & 列表(Table / List / Timeline)
|
||||
* 3. 表单控件(Input / Select / Picker)
|
||||
* 4. 导航 & 标签(Menu / Tabs / Tag / Breadcrumb)
|
||||
* 5. 反馈 & 展示(Alert / Empty / Result / Statistic / Skeleton)
|
||||
*
|
||||
* 不覆盖交互组件自有 transition(Button / Switch / Slider 等
|
||||
* 均使用 transition: all,特异性更高不受影响)。
|
||||
* ============================================================ */
|
||||
|
||||
/* —— 布局 & 容器 —— */
|
||||
body,
|
||||
.ant-layout,
|
||||
.ant-layout-header,
|
||||
.ant-layout-sider,
|
||||
.ant-layout-content,
|
||||
.ant-card,
|
||||
.ant-card-head,
|
||||
.ant-card-body,
|
||||
.ant-card-actions,
|
||||
.ant-modal-content,
|
||||
.ant-modal-header,
|
||||
.ant-modal-body,
|
||||
.ant-modal-footer,
|
||||
.ant-drawer-content,
|
||||
.ant-drawer-header,
|
||||
.ant-drawer-body,
|
||||
.ant-drawer-footer,
|
||||
|
||||
/* —— 表格 & 列表 —— */
|
||||
.ant-table,
|
||||
.ant-table-thead > tr > th,
|
||||
.ant-table-tbody > tr > td,
|
||||
.ant-table-tbody > tr:hover > td,
|
||||
.ant-list,
|
||||
.ant-list-item,
|
||||
.ant-timeline-item,
|
||||
.ant-transfer-list,
|
||||
.ant-tree-node-content-wrapper,
|
||||
|
||||
/* —— 表单控件 —— */
|
||||
.ant-input,
|
||||
.ant-input-affix-wrapper,
|
||||
.ant-select-selector,
|
||||
.ant-select-dropdown,
|
||||
.ant-picker,
|
||||
.ant-picker-input,
|
||||
.ant-picker-dropdown,
|
||||
.ant-picker-panel-container,
|
||||
.ant-input-number,
|
||||
.ant-input-number-input,
|
||||
.ant-radio-button-wrapper,
|
||||
.ant-radio-group,
|
||||
.ant-checkbox-wrapper,
|
||||
.ant-segmented,
|
||||
.ant-segmented-item,
|
||||
.ant-upload-drag,
|
||||
.ant-upload-list-item,
|
||||
|
||||
/* —— 导航 & 标签 —— */
|
||||
.ant-menu,
|
||||
.ant-menu-item,
|
||||
.ant-menu-submenu-title,
|
||||
.ant-tabs-nav,
|
||||
.ant-tabs-nav-list,
|
||||
.ant-tabs-tab,
|
||||
.ant-tabs-content,
|
||||
.ant-tag,
|
||||
.ant-breadcrumb,
|
||||
.ant-pagination-item,
|
||||
.ant-dropdown-menu,
|
||||
|
||||
/* —— 反馈 & 展示 —— */
|
||||
.ant-alert,
|
||||
.ant-empty,
|
||||
.ant-result,
|
||||
.ant-statistic,
|
||||
.ant-statistic-content,
|
||||
.ant-badge,
|
||||
.ant-avatar,
|
||||
.ant-divider,
|
||||
.ant-skeleton,
|
||||
.ant-skeleton-input,
|
||||
.ant-skeleton-button,
|
||||
.ant-notification-notice,
|
||||
.ant-message-notice-content,
|
||||
.ant-popover-inner,
|
||||
.ant-tooltip-inner,
|
||||
.ant-collapse,
|
||||
.ant-collapse-item,
|
||||
.ant-collapse-header,
|
||||
.ant-collapse-content-box,
|
||||
.ant-descriptions-item-container {
|
||||
transition:
|
||||
color 0.15s linear,
|
||||
background-color 0.15s linear,
|
||||
border-color 0.15s linear,
|
||||
box-shadow 0.15s linear;
|
||||
}
|
||||
|
||||
#root {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 滚动条美化 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #c7d2fe;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #a5b4fc;
|
||||
}
|
||||
|
||||
/* 暗色模式滚动条 */
|
||||
[data-theme='dark'] ::-webkit-scrollbar-thumb {
|
||||
background: #4338ca;
|
||||
}
|
||||
|
||||
[data-theme='dark'] ::-webkit-scrollbar-thumb:hover {
|
||||
background: #4f46e5;
|
||||
}
|
||||
|
||||
/* 聚焦环 — 使用品牌色 */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* 选中文本 — 使用品牌色 */
|
||||
::selection {
|
||||
background: rgba(99, 102, 241, 0.2);
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
[data-theme='dark'] ::selection {
|
||||
background: rgba(129, 140, 248, 0.3);
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
/* 链接全局样式 */
|
||||
a {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
transition: color var(--transition-duration) var(--transition-timing);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--color-primary-light);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 窗口拖拽区域(自定义导航栏)
|
||||
*
|
||||
* macOS hiddenInset 模式下,整个导航栏区域可拖拽移动窗口。
|
||||
* .navbar-drag-region → 可拖拽(背景区域)
|
||||
* .navbar-no-drag → 不可拖拽(按钮、链接等交互元素)
|
||||
* ============================================================ */
|
||||
|
||||
.navbar-drag-region {
|
||||
-webkit-app-region: drag;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.navbar-no-drag {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
/* macOS 红绿灯按钮区域不遮挡内容 */
|
||||
@supports (-webkit-app-region: drag) {
|
||||
.navbar-drag-region {
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
}
|
||||
@import './tokens.css';
|
||||
@import './transitions.css';
|
||||
@import './base.css';
|
||||
|
||||
139
src/theme/tokens.css
Normal file
139
src/theme/tokens.css
Normal file
@@ -0,0 +1,139 @@
|
||||
/* ============================================================
|
||||
* 设计令牌 — Tailwind CSS v4 @theme 扩展 + 亮暗 CSS 变量
|
||||
*
|
||||
* 与 tokens.ts / antd-theme.ts 保持同步。
|
||||
* 改颜色 → 改这个文件。
|
||||
* ============================================================ */
|
||||
|
||||
/* ============================================================
|
||||
* Tailwind CSS v4 主题扩展(@theme 指令)
|
||||
* 此处定义的自定义令牌会自动生成对应的 Tailwind 工具类。
|
||||
* 例如:colors.primary → bg-primary, text-primary, border-primary 等。
|
||||
* ============================================================ */
|
||||
|
||||
/* 品牌色 — 蓝紫渐变 */
|
||||
@theme {
|
||||
/* 主色系 */
|
||||
--color-primary: #6366f1; /* Indigo-500 */
|
||||
--color-primary-light: #818cf8; /* Indigo-400 */
|
||||
--color-primary-dark: #4f46e5; /* Indigo-600 */
|
||||
--color-primary-50: #eef2ff; /* Indigo-50 */
|
||||
--color-primary-100: #e0e7ff; /* Indigo-100 */
|
||||
--color-primary-200: #c7d2fe; /* Indigo-200 */
|
||||
--color-primary-300: #a5b4fc; /* Indigo-300 */
|
||||
--color-primary-400: #818cf8; /* Indigo-400 */
|
||||
--color-primary-500: #6366f1; /* Indigo-500 */
|
||||
--color-primary-600: #4f46e5; /* Indigo-600 */
|
||||
--color-primary-700: #4338ca; /* Indigo-700 */
|
||||
--color-primary-800: #3730a3; /* Indigo-800 */
|
||||
--color-primary-900: #312e81; /* Indigo-900 */
|
||||
|
||||
/* 辅助色系 — Violet */
|
||||
--color-secondary: #8b5cf6; /* Violet-500 */
|
||||
--color-secondary-light: #a78bfa; /* Violet-400 */
|
||||
--color-secondary-dark: #7c3aed; /* Violet-600 */
|
||||
|
||||
/* 功能色 */
|
||||
--color-success: #10b981;
|
||||
--color-success-light: #d1fae5;
|
||||
--color-warning: #f59e0b;
|
||||
--color-warning-light: #fef3c7;
|
||||
--color-error: #ef4444;
|
||||
--color-error-light: #fee2e2;
|
||||
--color-info: #3b82f6;
|
||||
--color-info-light: #dbeafe;
|
||||
|
||||
/* 圆角 */
|
||||
--radius-xs: 4px;
|
||||
--radius-sm: 6px;
|
||||
--radius-base: 8px;
|
||||
--radius-lg: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-2xl: 24px;
|
||||
|
||||
/* 字体 */
|
||||
--font-family-base:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans',
|
||||
'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
--font-family-mono:
|
||||
'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, 'Courier New', monospace;
|
||||
|
||||
/* 动画 */
|
||||
--transition-duration: 0.2s;
|
||||
--transition-timing: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 🌞 亮色模式 CSS 变量(默认)
|
||||
* 这些变量与 tokens.ts 中的亮色令牌同步
|
||||
* ============================================================ */
|
||||
|
||||
:root,
|
||||
[data-theme='light'] {
|
||||
/* 中性色 — 亮色 */
|
||||
--color-text-base: #1e1b4b;
|
||||
--color-text-secondary: #6b7280;
|
||||
--color-text-tertiary: #9ca3af;
|
||||
--color-bg-base: #ffffff;
|
||||
--color-bg-container: #f9fafb;
|
||||
--color-bg-layout: #f5f3ff;
|
||||
--color-bg-elevated: #ffffff;
|
||||
--color-border-base: #e5e7eb;
|
||||
--color-border-secondary: #f3f4f6;
|
||||
|
||||
/* 阴影 */
|
||||
--shadow-base: 0 1px 3px 0 rgba(99, 102, 241, 0.08), 0 1px 2px -1px rgba(99, 102, 241, 0.08);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(99, 102, 241, 0.1), 0 4px 6px -4px rgba(99, 102, 241, 0.1);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 🌙 暗色模式 CSS 变量
|
||||
* 当 <html data-theme="dark"> 时自动切换
|
||||
* 这些变量与 tokens.ts 中的暗色令牌同步
|
||||
* ============================================================ */
|
||||
|
||||
[data-theme='dark'] {
|
||||
/* 中性色 — 暗色(科技蓝紫) */
|
||||
--color-text-base: #e8e8fa;
|
||||
--color-text-secondary: #a0a8d8;
|
||||
--color-text-tertiary: #6e78b0;
|
||||
--color-bg-base: #a0a8d8;
|
||||
--color-bg-container: #0f1340;
|
||||
--color-bg-layout: #a0a8d8;
|
||||
--color-bg-elevated: #171b52;
|
||||
--color-border-base: #2a3278;
|
||||
--color-border-secondary: #1c2250;
|
||||
|
||||
/* 暗色模式下品牌色提亮以保持对比度 */
|
||||
--color-primary: #818cf8;
|
||||
--color-primary-light: #a5b4fc;
|
||||
--color-primary-dark: #6366f1;
|
||||
|
||||
/* 阴影 — 暗色下用深投影 */
|
||||
--shadow-base: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px -1px rgba(0, 0, 0, 0.3);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -4px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 蓝紫渐变工具类
|
||||
* Tailwind v4 使用 @utility 定义自定义工具类
|
||||
* ============================================================ */
|
||||
|
||||
@utility gradient-primary {
|
||||
background: linear-gradient(135deg, #4f46e5, #7c3aed);
|
||||
}
|
||||
|
||||
@utility gradient-primary-hover {
|
||||
background: linear-gradient(135deg, #6366f1, #8b5cf6);
|
||||
}
|
||||
|
||||
@utility gradient-primary-text {
|
||||
background: linear-gradient(135deg, #4f46e5, #7c3aed);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
@utility gradient-border {
|
||||
border-image: linear-gradient(135deg, #4f46e5, #7c3aed) 1;
|
||||
}
|
||||
126
src/theme/transitions.css
Normal file
126
src/theme/transitions.css
Normal file
@@ -0,0 +1,126 @@
|
||||
/* ============================================================
|
||||
* 主题切换过渡 — View Transitions API + antd 容器精准覆盖
|
||||
*
|
||||
* 策略:
|
||||
* Chromium/Electron:View Transitions API GPU cross-fade(1 个动画)
|
||||
* 其他浏览器:CSS transition 精准覆盖 ~80 个 antd 容器类
|
||||
*
|
||||
* 改过渡参数 → 改这个文件。
|
||||
* ============================================================ */
|
||||
|
||||
/* ============================================================
|
||||
* View Transitions API — GPU 合成器驱动的主题切换动画
|
||||
* 替代 80+ 个独立 CSS 过渡,单次 cross-fade 消除 Electron 卡顿。
|
||||
* 仅在 Chromium 111+(Electron 28+)生效,其他浏览器回退 CSS 过渡。
|
||||
* ============================================================ */
|
||||
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
animation-duration: 0.15s;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 主题切换过渡 — 精准覆盖大面积视觉容器,避免 * 全局选择器
|
||||
* 在 Electron 中为数千个 DOM 节点同时启动过渡造成卡顿。
|
||||
*
|
||||
* 策略:
|
||||
* 1. 容器/布局组件(Card / Modal / Layout / Drawer 等)
|
||||
* 2. 表格 & 列表(Table / List / Timeline)
|
||||
* 3. 表单控件(Input / Select / Picker)
|
||||
* 4. 导航 & 标签(Menu / Tabs / Tag / Breadcrumb)
|
||||
* 5. 反馈 & 展示(Alert / Empty / Result / Statistic / Skeleton)
|
||||
*
|
||||
* 不覆盖交互组件自有 transition(Button / Switch / Slider 等
|
||||
* 均使用 transition: all,特异性更高不受影响)。
|
||||
* ============================================================ */
|
||||
|
||||
/* —— 布局 & 容器 —— */
|
||||
body,
|
||||
.ant-layout,
|
||||
.ant-layout-header,
|
||||
.ant-layout-sider,
|
||||
.ant-layout-content,
|
||||
.ant-card,
|
||||
.ant-card-head,
|
||||
.ant-card-body,
|
||||
.ant-card-actions,
|
||||
.ant-modal-content,
|
||||
.ant-modal-header,
|
||||
.ant-modal-body,
|
||||
.ant-modal-footer,
|
||||
.ant-drawer-content,
|
||||
.ant-drawer-header,
|
||||
.ant-drawer-body,
|
||||
.ant-drawer-footer,
|
||||
|
||||
/* —— 表格 & 列表 —— */
|
||||
.ant-table,
|
||||
.ant-table-thead > tr > th,
|
||||
.ant-table-tbody > tr > td,
|
||||
.ant-table-tbody > tr:hover > td,
|
||||
.ant-list,
|
||||
.ant-list-item,
|
||||
.ant-timeline-item,
|
||||
.ant-transfer-list,
|
||||
.ant-tree-node-content-wrapper,
|
||||
|
||||
/* —— 表单控件 —— */
|
||||
.ant-input,
|
||||
.ant-input-affix-wrapper,
|
||||
.ant-select-selector,
|
||||
.ant-select-dropdown,
|
||||
.ant-picker,
|
||||
.ant-picker-input,
|
||||
.ant-picker-dropdown,
|
||||
.ant-picker-panel-container,
|
||||
.ant-input-number,
|
||||
.ant-input-number-input,
|
||||
.ant-radio-button-wrapper,
|
||||
.ant-radio-group,
|
||||
.ant-checkbox-wrapper,
|
||||
.ant-segmented,
|
||||
.ant-segmented-item,
|
||||
.ant-upload-drag,
|
||||
.ant-upload-list-item,
|
||||
|
||||
/* —— 导航 & 标签 —— */
|
||||
.ant-menu,
|
||||
.ant-menu-item,
|
||||
.ant-menu-submenu-title,
|
||||
.ant-tabs-nav,
|
||||
.ant-tabs-nav-list,
|
||||
.ant-tabs-tab,
|
||||
.ant-tabs-content,
|
||||
.ant-tag,
|
||||
.ant-breadcrumb,
|
||||
.ant-pagination-item,
|
||||
.ant-dropdown-menu,
|
||||
|
||||
/* —— 反馈 & 展示 —— */
|
||||
.ant-alert,
|
||||
.ant-empty,
|
||||
.ant-result,
|
||||
.ant-statistic,
|
||||
.ant-statistic-content,
|
||||
.ant-badge,
|
||||
.ant-avatar,
|
||||
.ant-divider,
|
||||
.ant-skeleton,
|
||||
.ant-skeleton-input,
|
||||
.ant-skeleton-button,
|
||||
.ant-notification-notice,
|
||||
.ant-message-notice-content,
|
||||
.ant-popover-inner,
|
||||
.ant-tooltip-inner,
|
||||
.ant-collapse,
|
||||
.ant-collapse-item,
|
||||
.ant-collapse-header,
|
||||
.ant-collapse-content-box,
|
||||
.ant-descriptions-item-container {
|
||||
transition:
|
||||
color 0.15s linear,
|
||||
background-color 0.15s linear,
|
||||
border-color 0.15s linear,
|
||||
box-shadow 0.15s linear;
|
||||
}
|
||||
Reference in New Issue
Block a user