From 7982b69219266ed4e4cdeaef1a037fdf50f5edb6 Mon Sep 17 00:00:00 2001 From: YoungestSongMo <2130460579@qq.com> Date: Tue, 9 Jun 2026 14:20:07 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20CSS=20=E6=8B=86=E5=88=86=20+=20auth?= =?UTF-8?q?=20=E6=8C=81=E4=B9=85=E5=8C=96=E6=8F=90=E5=8F=96=20+=20?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/components/AppProvider.tsx | 2 +- src/components/PageDispatcher.tsx | 2 +- .../{common => ui}/FloatingPanel.tsx | 0 .../{common => ui}/LegalTextModal.tsx | 0 src/pages/login/components/LoginForm.tsx | 2 +- src/pages/login/components/RegisterForm.tsx | 2 +- src/pages/login/hooks/use-auth-state.ts | 83 +--- src/services/auth-persistence.ts | 84 ++++ src/theme/base.css | 130 ++++++ src/theme/globals.css | 380 +----------------- src/theme/tokens.css | 139 +++++++ src/theme/transitions.css | 126 ++++++ 12 files changed, 511 insertions(+), 439 deletions(-) rename src/components/{common => ui}/FloatingPanel.tsx (100%) rename src/components/{common => ui}/LegalTextModal.tsx (100%) create mode 100644 src/services/auth-persistence.ts create mode 100644 src/theme/base.css create mode 100644 src/theme/tokens.css create mode 100644 src/theme/transitions.css diff --git a/src/components/AppProvider.tsx b/src/components/AppProvider.tsx index b4788ea..4fa2a8c 100644 --- a/src/components/AppProvider.tsx +++ b/src/components/AppProvider.tsx @@ -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, diff --git a/src/components/PageDispatcher.tsx b/src/components/PageDispatcher.tsx index f6ae8a7..b7b2275 100644 --- a/src/components/PageDispatcher.tsx +++ b/src/components/PageDispatcher.tsx @@ -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'; diff --git a/src/components/common/FloatingPanel.tsx b/src/components/ui/FloatingPanel.tsx similarity index 100% rename from src/components/common/FloatingPanel.tsx rename to src/components/ui/FloatingPanel.tsx diff --git a/src/components/common/LegalTextModal.tsx b/src/components/ui/LegalTextModal.tsx similarity index 100% rename from src/components/common/LegalTextModal.tsx rename to src/components/ui/LegalTextModal.tsx diff --git a/src/pages/login/components/LoginForm.tsx b/src/pages/login/components/LoginForm.tsx index bc479b6..4097294 100644 --- a/src/pages/login/components/LoginForm.tsx +++ b/src/pages/login/components/LoginForm.tsx @@ -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; diff --git a/src/pages/login/components/RegisterForm.tsx b/src/pages/login/components/RegisterForm.tsx index fffe8bd..11d3cb6 100644 --- a/src/pages/login/components/RegisterForm.tsx +++ b/src/pages/login/components/RegisterForm.tsx @@ -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, diff --git a/src/pages/login/hooks/use-auth-state.ts b/src/pages/login/hooks/use-auth-state.ts index 7d2c6fc..60241ee 100644 --- a/src/pages/login/hooks/use-auth-state.ts +++ b/src/pages/login/hooks/use-auth-state.ts @@ -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, }; } diff --git a/src/services/auth-persistence.ts b/src/services/auth-persistence.ts new file mode 100644 index 0000000..44af72f --- /dev/null +++ b/src/services/auth-persistence.ts @@ -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; +} diff --git a/src/theme/base.css b/src/theme/base.css new file mode 100644 index 0000000..a0974a8 --- /dev/null +++ b/src/theme/base.css @@ -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; + } +} diff --git a/src/theme/globals.css b/src/theme/globals.css index 6e76d1b..aafdbf4 100644 --- a/src/theme/globals.css +++ b/src/theme/globals.css @@ -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 变量 - * 当 时自动切换 - * 这些变量与 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'; diff --git a/src/theme/tokens.css b/src/theme/tokens.css new file mode 100644 index 0000000..8e14a26 --- /dev/null +++ b/src/theme/tokens.css @@ -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 变量 + * 当 时自动切换 + * 这些变量与 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; +} diff --git a/src/theme/transitions.css b/src/theme/transitions.css new file mode 100644 index 0000000..0e569f9 --- /dev/null +++ b/src/theme/transitions.css @@ -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; +}