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:
2026-06-09 14:20:07 +08:00
parent 53df926973
commit 7982b69219
12 changed files with 511 additions and 439 deletions

View File

@@ -17,7 +17,7 @@ import { setToken, clearToken, initTokenStore } from '@/services/request';
import { emit, EVENTS } from '@/utils/event-bus'; import { emit, EVENTS } from '@/utils/event-bus';
import { logger } from '@/utils/logger'; import { logger } from '@/utils/logger';
import { AuthAPI } from '@/services/modules'; import { AuthAPI } from '@/services/modules';
import { getAutoLoginFlag, clearAutoLoginFlag } from '@/pages/login/hooks/use-auth-state'; import { getAutoLoginFlag, clearAutoLoginFlag } from '@/services/auth-persistence';
import { import {
initAuthRefresh, initAuthRefresh,
getStoredRefreshToken, getStoredRefreshToken,

View File

@@ -12,7 +12,7 @@
import {useState, useEffect, useCallback, useRef, type ComponentType} from 'react'; import {useState, useEffect, useCallback, useRef, type ComponentType} from 'react';
import {Modal, Drawer} from 'antd'; import {Modal, Drawer} from 'antd';
import {on, off, EVENTS} from '@/utils/event-bus'; 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 {ComplianceAssetsPage} from '@/pages/compliance-assets/ComplianceAssetsPage';
import {AccountPage} from '@/pages/account/AccountPage'; import {AccountPage} from '@/pages/account/AccountPage';
import {VipPage} from '@/pages/vip/VipPage'; import {VipPage} from '@/pages/vip/VipPage';

View File

@@ -8,7 +8,7 @@ import { UserOutlined, LockOutlined } from '@ant-design/icons';
import { loginFields } from '../config/login-fields'; import { loginFields } from '../config/login-fields';
import { validateForm } from '../config/validators'; import { validateForm } from '../config/validators';
import { LegalTextModal } from '@/components/common/LegalTextModal'; import { LegalTextModal } from '@/components/ui/LegalTextModal';
import type { LoginFormValues } from '../types'; import type { LoginFormValues } from '../types';
const { Text } = Typography; const { Text } = Typography;

View File

@@ -5,7 +5,7 @@
import { useState, useCallback, useRef } from 'react'; import { useState, useCallback, useRef } from 'react';
import { Input, Button, Checkbox, Typography } from 'antd'; import { Input, Button, Checkbox, Typography } from 'antd';
import { LegalTextModal } from '@/components/common/LegalTextModal'; import { LegalTextModal } from '@/components/ui/LegalTextModal';
import { import {
UserOutlined, UserOutlined,
LockOutlined, LockOutlined,

View File

@@ -1,76 +1,34 @@
// ============================================================ // ============================================================
// useAuthState — 记住账号 / 自动登录 状态管理 // useAuthState — 记住账号 / 自动登录 表单状态 Hook
// //
// "记住密码":下次打开页面自动回填用户名(不再存储密码) // "记住密码":下次打开页面自动回填用户名(不再存储密码)
// "自动登录"登录成功时标记AppProvider 启动时用 refresh_token 静默续期 // "自动登录"登录成功时标记AppProvider 启动时用 refresh_token 静默续期
// //
// 持久化逻辑已提取到 services/auth-persistence.ts
// 此处仅负责 React 组件状态useState + useCallback
//
// 安全: // 安全:
// - 密码从不持久化存储access_token / refresh_token 由 safeStorage 加密保护) // - 密码从不持久化存储access_token / refresh_token 由 safeStorage 加密保护)
// - 安全性由 refresh_token + access_token 的 JWT 双重机制 + OS 密钥链保障 // - 安全性由 refresh_token + access_token 的 JWT 双重机制 + OS 密钥链保障
// ============================================================ // ============================================================
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import {
saveAuth,
clearSavedAuth,
getSavedUsername,
getSavedRemember,
getSavedAutoLogin,
} from '@/services/auth-persistence';
const STORAGE_KEY = 'ele-heixiu-auth'; export { getAutoLoginFlag, clearAutoLoginFlag, clearSavedAuth } from '@/services/auth-persistence';
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 {
/* 静默 */
}
}
// ---------- 工具函数(不依赖 ReactAppProvider 可直接调用)----------
/** 读取自动登录标记 */
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 {
/* 静默 */
}
}
// ---------- Hook ---------- // ---------- Hook ----------
export function useAuthState() { export function useAuthState() {
const saved = readAuth(); const [username, setUsername] = useState(getSavedUsername);
const [remember, setRemember] = useState(getSavedRemember);
const [username, setUsername] = useState(saved?.username || ''); const [autoLogin, setAutoLogin] = useState(getSavedAutoLogin);
const [remember, setRemember] = useState(saved?.remember || false);
const [autoLogin, setAutoLogin] = useState(saved?.autoLogin || false);
const handleRememberChange = useCallback((checked: boolean) => { const handleRememberChange = useCallback((checked: boolean) => {
setRemember(checked); setRemember(checked);
@@ -93,16 +51,11 @@ export function useAuthState() {
* @param doRemember - 是否勾选"记住用户名" * @param doRemember - 是否勾选"记住用户名"
* @param doAutoLogin - 是否勾选"自动登录" * @param doAutoLogin - 是否勾选"自动登录"
*/ */
const saveAuth = useCallback( const handleSaveAuth = useCallback(
(user: string, _password: string, doRemember: boolean, doAutoLogin: boolean) => { (user: string, _password: string, doRemember: boolean, doAutoLogin: boolean) => {
setUsername(user); setUsername(user);
if (doRemember || doAutoLogin) { if (doRemember || doAutoLogin) {
const payload: StoredAuth = { saveAuth(user, doRemember, doAutoLogin);
username: user,
remember: doRemember,
autoLogin: doAutoLogin,
};
writeAuth(payload);
} }
}, },
[], [],
@@ -115,6 +68,6 @@ export function useAuthState() {
setUsername, setUsername,
handleRememberChange, handleRememberChange,
handleAutoLoginChange, handleAutoLoginChange,
saveAuth, saveAuth: handleSaveAuth,
}; };
} }

View File

@@ -0,0 +1,84 @@
// ============================================================
// auth-persistence — 本地认证偏好持久化(记住账号 / 自动登录)
//
// 职责:
// - 存储/读取登录表单偏好(用户名回填、记住密码、自动登录标记)
// - 纯 localStorage 操作,不依赖 React
// - 密码从不持久化(安全性由 JWT + safeStorage 保障)
//
// 与 auth-token.ts 的分工:
// auth-token.ts → JWT token 生命周期refresh/accesssafeStorage 加密)
// 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
View 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;
}
}

View File

@@ -1,374 +1,14 @@
/* ============================================================ /* ============================================================
* 全局样式 — Tailwind CSS v4 指令 + 蓝紫渐变主题扩展 + 重置样式 * 全局样式入口 — Tailwind CSS v4 + 设计令牌 + 过渡 + 基础样式
* Tailwind v4 使用 CSS @theme 指令替代 tailwind.config.ts *
* 设计令牌需与 tokens.ts / antd-theme.ts 保持同步。 * 拆分为 4 个文件,按职责定位:
* tokens.css → 改颜色 / 品牌色 / 亮暗变量
* transitions.css → 改主题切换过渡参数
* base.css → 改重置 / 滚动条 / 链接 / 拖拽区域
* globals.css → 本文件(入口,只负责引入顺序)
* ============================================================ */ * ============================================================ */
@import 'tailwindcss'; @import 'tailwindcss';
@import './tokens.css';
/* ============================================================ @import './transitions.css';
* Tailwind CSS v4 主题扩展(用 @theme 指令) @import './base.css';
* 此处定义的自定义令牌会自动生成对应的 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
*
* 不覆盖交互组件自有 transitionButton / 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;
}
}

139
src/theme/tokens.css Normal file
View 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
View File

@@ -0,0 +1,126 @@
/* ============================================================
* 主题切换过渡 — View Transitions API + antd 容器精准覆盖
*
* 策略:
* Chromium/ElectronView Transitions API GPU cross-fade1 个动画)
* 其他浏览器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
*
* 不覆盖交互组件自有 transitionButton / 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;
}