diff --git a/frontend/src/components/AnnotatedText.tsx b/frontend/src/components/AnnotatedText.tsx index 4578b58..e321934 100644 --- a/frontend/src/components/AnnotatedText.tsx +++ b/frontend/src/components/AnnotatedText.tsx @@ -1,4 +1,5 @@ import React, { useMemo, useEffect, useRef } from 'react'; +import { theme } from 'antd'; // 标注数据类型 export interface MemoryAnnotation { @@ -35,14 +36,6 @@ interface AnnotatedTextProps { style?: React.CSSProperties; } -// 类型颜色映射 -const TYPE_COLORS = { - hook: '#ff6b6b', - foreshadow: '#6b7bff', - plot_point: '#51cf66', - character_event: '#ffd93d', -}; - // 类型图标映射 const TYPE_ICONS = { hook: '🎣', @@ -65,6 +58,14 @@ const AnnotatedText: React.FC = ({ }) => { const annotationRefs = useRef>({}); + const { token } = theme.useToken(); + const typeColors: Record = { + hook: token.colorError, + foreshadow: token.colorInfo, + plot_point: token.colorSuccess, + character_event: token.colorWarning, + }; + // 当需要滚动到特定标注时 useEffect(() => { if (scrollToAnnotation && annotationRefs.current[scrollToAnnotation]) { @@ -214,7 +215,7 @@ const AnnotatedText: React.FC = ({ const { annotation, annotations } = segment; if (!annotation) return null; - const color = TYPE_COLORS[annotation.type]; + const color = typeColors[annotation.type]; const icon = TYPE_ICONS[annotation.type]; const isActive = activeAnnotationId === annotation.id; @@ -238,17 +239,17 @@ const AnnotatedText: React.FC = ({ position: 'relative', borderBottom: `2px solid ${color}`, cursor: 'pointer', - backgroundColor: isActive ? `${color}22` : 'transparent', + backgroundColor: isActive ? `color-mix(in srgb, ${color} 13%, transparent)` : 'transparent', transition: 'all 0.2s', padding: '2px 0', }} onClick={() => onAnnotationClick?.(annotation)} onMouseEnter={(e) => { - e.currentTarget.style.backgroundColor = `${color}33`; + e.currentTarget.style.backgroundColor = `color-mix(in srgb, ${color} 20%, transparent)`; }} onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = isActive - ? `${color}22` + ? `color-mix(in srgb, ${color} 13%, transparent)` : 'transparent'; }} > diff --git a/frontend/src/components/AnnouncementModal.tsx b/frontend/src/components/AnnouncementModal.tsx index 0dcbb12..3baba0b 100644 --- a/frontend/src/components/AnnouncementModal.tsx +++ b/frontend/src/components/AnnouncementModal.tsx @@ -1,4 +1,4 @@ -import { Modal, Button, Space } from 'antd'; +import { Modal, Button, Space, theme } from 'antd'; import { useEffect, useState } from 'react'; interface AnnouncementModalProps { @@ -11,6 +11,8 @@ interface AnnouncementModalProps { export default function AnnouncementModal({ visible, onClose, onDoNotShowToday, onNeverShow }: AnnouncementModalProps) { const [qqImageError, setQqImageError] = useState(false); const [wxImageError, setWxImageError] = useState(false); + const { token } = theme.useToken(); + const alphaColor = (color: string, alpha: number) => `color-mix(in srgb, ${color} ${(alpha * 100).toFixed(0)}%, transparent)`; useEffect(() => { if (visible) { @@ -35,7 +37,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
🎉 欢迎使用 AI小说创作助手 @@ -64,9 +66,9 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday, borderRadius: '8px', height: '40px', fontSize: '14px', - background: 'var(--color-primary)', - borderColor: 'var(--color-primary)', - boxShadow: 'var(--shadow-primary)', + background: token.colorPrimary, + borderColor: token.colorPrimary, + boxShadow: `0 8px 20px ${alphaColor(token.colorPrimary, 0.32)}`, }} > 永不再展示 @@ -78,16 +80,16 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday, styles={{ body: { padding: '20px', - background: 'var(--color-bg-container)', + background: token.colorBgContainer, }, header: { - background: 'linear-gradient(135deg, rgba(77, 128, 136, 0.08) 0%, rgba(248, 246, 241, 0.95) 100%)', - borderBottom: '1px solid var(--color-border-secondary)', + background: `linear-gradient(135deg, ${alphaColor(token.colorPrimary, 0.1)} 0%, ${alphaColor(token.colorBgContainer, 0.98)} 100%)`, + borderBottom: `1px solid ${token.colorBorderSecondary}`, padding: '16px 24px', }, footer: { - background: 'var(--color-bg-container)', - borderTop: '1px solid var(--color-border-secondary)', + background: token.colorBgContainer, + borderTop: `1px solid ${token.colorBorderSecondary}`, padding: '16px 24px', }, }} @@ -96,7 +98,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,

👋 欢迎加入我们的交流群!在这里你可以:

@@ -111,7 +113,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
  • 🐛 反馈问题和建议
  • 📚 分享创作经验和灵感
  • -

    +

    扫描下方二维码加入交流群:

    @@ -122,7 +124,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday, alignItems: 'flex-start', gap: '24px', padding: '16px', - background: 'var(--color-bg-layout)', + background: token.colorBgLayout, borderRadius: '8px', flexWrap: 'wrap', }}> @@ -133,7 +135,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday, alignItems: 'center', minWidth: '200px', }}> -

    +

    QQ交流群

    {!qqImageError ? ( @@ -141,10 +143,10 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday, display: 'flex', justifyContent: 'center', alignItems: 'center', - background: 'var(--color-bg-container)', + background: token.colorBgContainer, borderRadius: '8px', padding: '6px', - boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', + boxShadow: `0 2px 8px ${alphaColor(token.colorText, 0.12)}`, }}>

    二维码加载失败

    @@ -183,7 +185,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday, alignItems: 'center', minWidth: '200px', }}> -

    +

    微信交流群

    {!wxImageError ? ( @@ -191,10 +193,10 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday, display: 'flex', justifyContent: 'center', alignItems: 'center', - background: 'var(--color-bg-container)', + background: token.colorBgContainer, borderRadius: '8px', padding: '6px', - boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', + boxShadow: `0 2px 8px ${alphaColor(token.colorText, 0.12)}`, }}>

    二维码加载失败

    @@ -230,11 +232,11 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,
    💡 提示:选择"今日内不再展示"当天不再显示,选择"永不再展示"将永久隐藏此公告
    diff --git a/frontend/src/components/AppFooter.tsx b/frontend/src/components/AppFooter.tsx index d3de61b..5e016fc 100644 --- a/frontend/src/components/AppFooter.tsx +++ b/frontend/src/components/AppFooter.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { Typography, Space, Divider, Badge, Button, Grid } from 'antd'; +import { Typography, Space, Divider, Badge, Button, Grid, theme } from 'antd'; import { GithubOutlined, CopyrightOutlined, HeartFilled, ClockCircleOutlined, GiftOutlined } from '@ant-design/icons'; import { VERSION_INFO, getVersionString } from '../config/version'; import { checkLatestVersion } from '../services/versionService'; @@ -17,6 +17,8 @@ export default function AppFooter({ sidebarWidth = 0 }: AppFooterProps) { const [hasUpdate, setHasUpdate] = useState(false); const [latestVersion, setLatestVersion] = useState(''); const [releaseUrl, setReleaseUrl] = useState(''); + const { token } = theme.useToken(); + const alphaColor = (color: string, alpha: number) => `color-mix(in srgb, ${color} ${(alpha * 100).toFixed(0)}%, transparent)`; useEffect(() => { // 检查版本更新(每次都重新检查) @@ -55,11 +57,11 @@ export default function AppFooter({ sidebarWidth = 0 }: AppFooterProps) { right: 0, backdropFilter: 'blur(20px) saturate(180%)', WebkitBackdropFilter: 'blur(20px) saturate(180%)', - borderTop: '1px solid var(--color-border)', + borderTop: `1px solid ${token.colorBorder}`, padding: isMobile ? '8px 12px' : '10px 16px', zIndex: 100, - boxShadow: 'var(--shadow-card)', - backgroundColor: 'rgba(255, 255, 255, 0.8)', // 半透明背景以支持 backdrop-filter + boxShadow: `0 -2px 16px ${alphaColor(token.colorText, 0.08)}`, + backgroundColor: alphaColor(token.colorBgContainer, 0.82), // 半透明背景以支持 backdrop-filter transition: 'left 0.3s ease', // 平滑过渡 }} > @@ -87,23 +89,23 @@ export default function AppFooter({ sidebarWidth = 0 }: AppFooterProps) { display: 'flex', alignItems: 'center', gap: 4, - color: 'var(--color-primary)', + color: token.colorPrimary, cursor: hasUpdate ? 'pointer' : 'default', }} title={hasUpdate ? `发现新版本 v${latestVersion},点击查看` : '当前版本'} > - {VERSION_INFO.projectName} + {VERSION_INFO.projectName} {getVersionString()} - + - + @@ -132,7 +134,7 @@ export default function AppFooter({ sidebarWidth = 0 }: AppFooterProps) { @@ -144,7 +146,7 @@ export default function AppFooter({ sidebarWidth = 0 }: AppFooterProps) { } + split={} style={{ display: 'flex', justifyContent: 'center', @@ -160,7 +162,7 @@ export default function AppFooter({ sidebarWidth = 0 }: AppFooterProps) { display: 'flex', alignItems: 'center', gap: 6, - color: 'var(--color-text-secondary)', + color: token.colorTextSecondary, textShadow: 'none', cursor: hasUpdate ? 'pointer' : 'default', transition: 'all 0.3s', @@ -177,7 +179,7 @@ export default function AppFooter({ sidebarWidth = 0 }: AppFooterProps) { }} title={hasUpdate ? `发现新版本 v${latestVersion},点击查看` : '当前版本'} > - {VERSION_INFO.projectName} + {VERSION_INFO.projectName} {getVersionString()} @@ -192,7 +194,7 @@ export default function AppFooter({ sidebarWidth = 0 }: AppFooterProps) { display: 'flex', alignItems: 'center', gap: 6, - color: 'var(--color-text-secondary)', + color: token.colorTextSecondary, }} > @@ -206,7 +208,7 @@ export default function AppFooter({ sidebarWidth = 0 }: AppFooterProps) { rel="noopener noreferrer" style={{ fontSize: 12, - color: 'var(--color-text-secondary)', + color: token.colorTextSecondary, }} > LinuxDO 社区 @@ -218,9 +220,9 @@ export default function AppFooter({ sidebarWidth = 0 }: AppFooterProps) { icon={} onClick={() => window.open('https://mumuverse.space:1588/', '_blank')} style={{ - background: 'var(--color-primary)', + background: token.colorPrimary, border: 'none', - boxShadow: '0 4px 12px rgba(77, 128, 136, 0.3)', + boxShadow: `0 4px 12px ${alphaColor(token.colorPrimary, 0.35)}`, fontSize: 13, height: 32, padding: '0 20px', @@ -232,11 +234,11 @@ export default function AppFooter({ sidebarWidth = 0 }: AppFooterProps) { }} onMouseEnter={(e) => { e.currentTarget.style.transform = 'translateY(-2px)'; - e.currentTarget.style.boxShadow = '0 6px 16px rgba(102, 126, 234, 0.6)'; + e.currentTarget.style.boxShadow = `0 6px 16px ${alphaColor(token.colorPrimary, 0.5)}`; }} onMouseLeave={(e) => { e.currentTarget.style.transform = 'translateY(0)'; - e.currentTarget.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.5)'; + e.currentTarget.style.boxShadow = `0 4px 12px ${alphaColor(token.colorPrimary, 0.35)}`; }} > 赞助支持 @@ -252,7 +254,7 @@ export default function AppFooter({ sidebarWidth = 0 }: AppFooterProps) { display: 'flex', alignItems: 'center', gap: 6, - color: 'var(--color-text-secondary)', + color: token.colorTextSecondary, }} > @@ -266,7 +268,7 @@ export default function AppFooter({ sidebarWidth = 0 }: AppFooterProps) { display: 'flex', alignItems: 'center', gap: 4, - color: 'var(--color-text-tertiary)', + color: token.colorTextTertiary, }} > @@ -280,12 +282,12 @@ export default function AppFooter({ sidebarWidth = 0 }: AppFooterProps) { display: 'flex', alignItems: 'center', gap: 4, - color: 'var(--color-text-secondary)', - textShadow: '0 1px 3px rgba(0, 0, 0, 0.05)', + color: token.colorTextSecondary, + textShadow: `0 1px 3px ${alphaColor(token.colorText, 0.08)}`, }} > Made with - + by {VERSION_INFO.author} diff --git a/frontend/src/components/ChapterAnalysis.tsx b/frontend/src/components/ChapterAnalysis.tsx index 9810eab..914a9fb 100644 --- a/frontend/src/components/ChapterAnalysis.tsx +++ b/frontend/src/components/ChapterAnalysis.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { Modal, Spin, Alert, Tabs, Card, Tag, List, Empty, Statistic, Row, Col, Button } from 'antd'; +import { Modal, Spin, Alert, Tabs, Card, Tag, List, Empty, Statistic, Row, Col, Button, theme } from 'antd'; import { ThunderboltOutlined, BulbOutlined, @@ -27,6 +27,7 @@ interface ChapterAnalysisProps { } export default function ChapterAnalysis({ chapterId, visible, onClose }: ChapterAnalysisProps) { + const { token } = theme.useToken(); const [task, setTask] = useState(null); const [analysis, setAnalysis] = useState(null); const [loading, setLoading] = useState(false); @@ -258,7 +259,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter transition: 'all 0.3s ease', borderRadius: 6, boxShadow: task.progress > 0 && task.status !== 'failed' - ? '0 0 10px rgba(24, 144, 255, 0.3)' + ? `0 0 10px color-mix(in srgb, ${token.colorPrimary} 30%, transparent)` : 'none' }} /> diff --git a/frontend/src/components/ChapterContentComparison.tsx b/frontend/src/components/ChapterContentComparison.tsx index 267b2b7..333bc9d 100644 --- a/frontend/src/components/ChapterContentComparison.tsx +++ b/frontend/src/components/ChapterContentComparison.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Modal, Button, Card, Statistic, Row, Col, message } from 'antd'; +import { Modal, Button, Card, Statistic, Row, Col, message, theme } from 'antd'; import { CheckOutlined, CloseOutlined, SwapOutlined } from '@ant-design/icons'; import ReactDiffViewer from 'react-diff-viewer-continued'; @@ -26,6 +26,7 @@ const ChapterContentComparison: React.FC = ({ onApply, onDiscard }) => { + const { token } = theme.useToken(); const [applying, setApplying] = useState(false); const [viewMode, setViewMode] = useState<'split' | 'unified'>('split'); const [modal, contextHolder] = Modal.useModal(); @@ -195,7 +196,7 @@ const ChapterContentComparison: React.FC = ({ styles={{ variables: { light: { - diffViewerBackground: '#fff', // Keep white for diff viewer readability + diffViewerBackground: token.colorBgContainer, addedBackground: 'var(--color-success-bg)', addedColor: 'var(--color-text-primary)', removedBackground: 'var(--color-error-bg)', diff --git a/frontend/src/components/ChapterReader.tsx b/frontend/src/components/ChapterReader.tsx index 5a980ff..7ed59aa 100644 --- a/frontend/src/components/ChapterReader.tsx +++ b/frontend/src/components/ChapterReader.tsx @@ -3,7 +3,7 @@ * 提供沉浸式阅读体验,支持主题切换、字体调节、翻页导航等功能 */ import { useState, useEffect, useCallback } from 'react'; -import { Modal, Button, Slider, Radio, Space, Typography, Spin, message } from 'antd'; +import { Modal, Button, Slider, Radio, Space, Typography, Spin, message, theme } from 'antd'; import { LeftOutlined, RightOutlined, @@ -37,27 +37,12 @@ interface NavigationInfo { current: { id: string; chapter_number: number; title: string }; } -// 主题样式配置 -const themeStyles = { - light: { - bg: '#ffffff', - text: '#333333', - headerBg: '#fafafa', - border: '#e8e8e8' - }, - sepia: { - bg: '#f5e6c8', - text: '#5b4636', - headerBg: '#e8d9b8', - border: '#d4c5a5' - }, - dark: { - bg: '#1a1a1a', - text: '#cccccc', - headerBg: '#252525', - border: '#333333' - } -}; +interface ReaderThemeStyle { + bg: string; + text: string; + headerBg: string; + border: string; +} // 本地存储key const SETTINGS_STORAGE_KEY = 'chapter-reader-settings'; @@ -94,6 +79,8 @@ export default function ChapterReader({ onClose, onChapterChange }: ChapterReaderProps) { + const { token } = theme.useToken(); + // 阅读器设置 const [settings, setSettings] = useState(loadSettings); @@ -200,6 +187,26 @@ export default function ChapterReader({ }, [chapter?.id]); // 当前主题样式 + const themeStyles: Record = { + light: { + bg: token.colorBgContainer, + text: token.colorText, + headerBg: token.colorBgElevated, + border: token.colorBorderSecondary, + }, + sepia: { + bg: `color-mix(in srgb, ${token.colorWarningBg} 72%, ${token.colorBgContainer} 28%)`, + text: `color-mix(in srgb, ${token.colorText} 85%, ${token.colorTextSecondary} 15%)`, + headerBg: `color-mix(in srgb, ${token.colorWarningBg} 58%, ${token.colorBgElevated} 42%)`, + border: `color-mix(in srgb, ${token.colorWarningBorder} 65%, ${token.colorBorder} 35%)`, + }, + dark: { + bg: `color-mix(in srgb, ${token.colorTextBase} 92%, ${token.colorBgContainer} 8%)`, + text: `color-mix(in srgb, ${token.colorTextLightSolid} 82%, ${token.colorTextSecondary} 18%)`, + headerBg: `color-mix(in srgb, ${token.colorTextBase} 84%, ${token.colorBgElevated} 16%)`, + border: `color-mix(in srgb, ${token.colorTextBase} 60%, ${token.colorBorder} 40%)`, + }, + }; const currentTheme = themeStyles[settings.theme]; // 更新设置的便捷函数 diff --git a/frontend/src/components/CharacterCard.tsx b/frontend/src/components/CharacterCard.tsx index a438921..0441be0 100644 --- a/frontend/src/components/CharacterCard.tsx +++ b/frontend/src/components/CharacterCard.tsx @@ -1,6 +1,6 @@ -import { Card, Space, Tag, Typography, Popconfirm } from 'antd'; +import { Card, Space, Tag, Typography, Popconfirm, theme } from 'antd'; import { EditOutlined, DeleteOutlined, UserOutlined, BankOutlined, ExportOutlined } from '@ant-design/icons'; -import { cardStyles } from './CardStyles'; +import { characterCardStyles } from './CardStyles'; import type { Character } from '../types'; const { Text, Paragraph } = Typography; @@ -13,6 +13,8 @@ interface CharacterCardProps { } export const CharacterCard: React.FC = ({ character, onEdit, onDelete, onExport }) => { + const { token } = theme.useToken(); + const getRoleTypeColor = (roleType?: string) => { const roleColors: Record = { 'protagonist': 'blue', @@ -37,10 +39,10 @@ export const CharacterCard: React.FC = ({ character, onEdit, const getStatusTag = () => { const statusConfig: Record = { - deceased: { color: '#000000', label: '💀 已死亡' }, - missing: { color: '#faad14', label: '❓ 已失踪' }, - retired: { color: '#8c8c8c', label: '📤 已退场' }, - destroyed: { color: '#000000', label: '💀 已覆灭' }, + deceased: { color: token.colorTextBase, label: '💀 已死亡' }, + missing: { color: token.colorWarning, label: '❓ 已失踪' }, + retired: { color: token.colorTextTertiary, label: '📤 已退场' }, + destroyed: { color: token.colorTextBase, label: '💀 已覆灭' }, }; const config = statusConfig[charStatus]; if (!config) return null; @@ -51,7 +53,7 @@ export const CharacterCard: React.FC = ({ character, onEdit, = ({ character, onEdit, + ) : ( - + ) } title={ - {character.name} + {character.name} {isOrganization ? ( 组织 ) : ( @@ -103,7 +105,7 @@ export const CharacterCard: React.FC = ({ character, onEdit, } description={ -
    +
    {/* 角色特有字段 */} {!isOrganization && ( <> diff --git a/frontend/src/components/CharacterCareerCard.tsx b/frontend/src/components/CharacterCareerCard.tsx index 0176eb0..d585021 100644 --- a/frontend/src/components/CharacterCareerCard.tsx +++ b/frontend/src/components/CharacterCareerCard.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback } from 'react'; -import { Card, Button, Modal, Form, Select, InputNumber, Input, message, Progress, Tag, Space, Divider, Typography } from 'antd'; +import { Card, Button, Modal, Form, Select, InputNumber, Input, message, Progress, Tag, Space, Divider, Typography, theme } from 'antd'; import { EditOutlined, PlusOutlined, DeleteOutlined, TrophyOutlined } from '@ant-design/icons'; import axios from 'axios'; @@ -44,6 +44,7 @@ export const CharacterCareerCard: React.FC = ({ editable = false, onUpdate }) => { + const { token } = theme.useToken(); const [mainCareer, setMainCareer] = useState(null); const [subCareers, setSubCareers] = useState([]); const [allCareers, setAllCareers] = useState([]); @@ -190,7 +191,7 @@ export const CharacterCareerCard: React.FC = ({
    - + {career.career_name} {isMain && } diff --git a/frontend/src/components/FloatingIndexPanel.tsx b/frontend/src/components/FloatingIndexPanel.tsx index 89b2959..4f91d1e 100644 --- a/frontend/src/components/FloatingIndexPanel.tsx +++ b/frontend/src/components/FloatingIndexPanel.tsx @@ -1,5 +1,5 @@ import { useState, useMemo } from 'react'; -import { Drawer, Input, List, Typography, Empty, Tag } from 'antd'; +import { Drawer, Input, List, Typography, Empty, Tag, theme } from 'antd'; import { SearchOutlined } from '@ant-design/icons'; import type { Chapter } from '../types'; @@ -24,6 +24,7 @@ export default function FloatingIndexPanel({ groupedChapters, onChapterSelect, }: FloatingIndexPanelProps) { + const { token } = theme.useToken(); const [searchTerm, setSearchTerm] = useState(''); const filteredGroups = useMemo(() => { @@ -56,7 +57,7 @@ export default function FloatingIndexPanel({ body: { padding: 0 }, }} > -
    +
    } diff --git a/frontend/src/components/MemorySidebar.tsx b/frontend/src/components/MemorySidebar.tsx index c1b79bd..b548007 100644 --- a/frontend/src/components/MemorySidebar.tsx +++ b/frontend/src/components/MemorySidebar.tsx @@ -1,5 +1,5 @@ import React, { useMemo, useEffect, useRef } from 'react'; -import { Card, Tag, Badge, Empty, Collapse, Divider } from 'antd'; +import { Card, Tag, Badge, Empty, Collapse, Divider, theme } from 'antd'; import { FireOutlined, StarOutlined, @@ -22,22 +22,18 @@ const TYPE_CONFIG = { hook: { label: '钩子', icon: , - color: '#ff6b6b', }, foreshadow: { label: '伏笔', icon: , - color: '#6b7bff', }, plot_point: { label: '情节点', icon: , - color: '#51cf66', }, character_event: { label: '角色事件', icon: , - color: '#ffd93d', }, }; @@ -51,7 +47,14 @@ const MemorySidebar: React.FC = ({ onAnnotationClick, scrollToAnnotation, }) => { + const { token } = theme.useToken(); const cardRefs = useRef>({}); + const typeColors: Record = { + hook: token.colorError, + foreshadow: token.colorInfo, + plot_point: token.colorSuccess, + character_event: token.colorWarning, + }; // 当需要滚动到特定标注卡片时 useEffect(() => { @@ -100,6 +103,7 @@ const MemorySidebar: React.FC = ({ // 渲染单个记忆卡片 const renderMemoryCard = (annotation: MemoryAnnotation) => { const config = TYPE_CONFIG[annotation.type]; + const color = typeColors[annotation.type]; const isActive = activeAnnotationId === annotation.id; return ( @@ -115,8 +119,8 @@ const MemorySidebar: React.FC = ({ onClick={() => onAnnotationClick?.(annotation)} style={{ marginBottom: 12, - borderLeft: `4px solid ${config.color}`, - backgroundColor: isActive ? `${config.color}11` : 'transparent', + borderLeft: `4px solid ${color}`, + backgroundColor: isActive ? `color-mix(in srgb, ${color} 8%, transparent)` : 'transparent', cursor: 'pointer', transition: 'all 0.2s', }} @@ -126,7 +130,7 @@ const MemorySidebar: React.FC = ({ @@ -138,7 +142,7 @@ const MemorySidebar: React.FC = ({
    = ({ {/* 特殊元数据 */} {annotation.metadata.strength && ( -
    +
    强度: {annotation.metadata.strength}/10
    )} @@ -192,27 +196,27 @@ const MemorySidebar: React.FC = ({
    📊 分析概览
    -
    钩子
    -
    +
    钩子
    +
    {stats.hooks}
    -
    伏笔
    -
    +
    伏笔
    +
    {stats.foreshadows}
    -
    情节点
    -
    +
    情节点
    +
    {stats.plotPoints}
    -
    角色事件
    +
    角色事件
    {stats.characterEvents}
    diff --git a/frontend/src/components/PartialRegenerateModal.tsx b/frontend/src/components/PartialRegenerateModal.tsx index cb1f462..d324fad 100644 --- a/frontend/src/components/PartialRegenerateModal.tsx +++ b/frontend/src/components/PartialRegenerateModal.tsx @@ -1,5 +1,5 @@ import React, { useState, useRef, useEffect } from 'react'; -import { Modal, Input, Button, Space, Radio, InputNumber, Card, message, Alert, Spin, Typography, Divider } from 'antd'; +import { Modal, Input, Button, Space, Radio, InputNumber, Card, message, Alert, Spin, Typography, Divider, theme } from 'antd'; import { ThunderboltOutlined, CheckOutlined, ReloadOutlined, EditOutlined, LoadingOutlined } from '@ant-design/icons'; import { chapterApi } from '../services/api'; @@ -33,6 +33,7 @@ export const PartialRegenerateModal: React.FC = ({ onClose, onApply, }) => { + const { token } = theme.useToken(); const [userInstructions, setUserInstructions] = useState(''); const [lengthMode, setLengthMode] = useState('similar'); const [customWordCount, setCustomWordCount] = useState(selectedText.length); @@ -178,7 +179,7 @@ export const PartialRegenerateModal: React.FC = ({ - + AI局部重写 } @@ -202,9 +203,9 @@ export const PartialRegenerateModal: React.FC = ({ loading={isGenerating} disabled={!userInstructions.trim()} style={{ - background: 'linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-hover) 100%)', + background: `linear-gradient(135deg, ${token.colorPrimary} 0%, ${token.colorPrimaryHover} 100%)`, border: 'none', - boxShadow: '0 4px 12px rgba(77, 128, 136, 0.3)', + boxShadow: token.boxShadowSecondary, }} > {isGenerating ? '生成中...' : '开始重写'} @@ -221,7 +222,7 @@ export const PartialRegenerateModal: React.FC = ({ type="primary" icon={} onClick={handleAccept} - style={{ background: '#52c41a', borderColor: '#52c41a' }} + style={{ background: token.colorSuccess, borderColor: token.colorSuccess }} > 接受并应用 @@ -250,7 +251,7 @@ export const PartialRegenerateModal: React.FC = ({ body: { maxHeight: 150, overflowY: 'auto', - background: '#fafafa', + background: token.colorFillAlter, }, }} > @@ -258,7 +259,7 @@ export const PartialRegenerateModal: React.FC = ({ style={{ margin: 0, whiteSpace: 'pre-wrap', - color: '#595959', + color: token.colorText, lineHeight: 1.8, }} > @@ -352,7 +353,7 @@ export const PartialRegenerateModal: React.FC = ({
    = ({
    = ({ size="small" ref={generatedTextRef} style={{ - background: generatedText ? '#f6ffed' : '#fafafa', - border: generatedText ? '1px solid #b7eb8f' : '1px solid #d9d9d9', + background: generatedText ? token.colorSuccessBg : token.colorFillAlter, + border: generatedText ? `1px solid ${token.colorSuccessBorder}` : `1px solid ${token.colorBorder}`, }} styles={{ body: { @@ -400,7 +401,7 @@ export const PartialRegenerateModal: React.FC = ({ display: 'inline-block', width: 8, height: 16, - background: 'var(--color-primary)', + background: token.colorPrimary, marginLeft: 2, animation: 'blink 1s infinite', }} @@ -408,7 +409,7 @@ export const PartialRegenerateModal: React.FC = ({ )} ) : ( -
    +
    {isGenerating ? '正在生成内容...' : '等待生成...'}
    )} diff --git a/frontend/src/components/PartialRegenerateToolbar.tsx b/frontend/src/components/PartialRegenerateToolbar.tsx index c76a5c4..554f47b 100644 --- a/frontend/src/components/PartialRegenerateToolbar.tsx +++ b/frontend/src/components/PartialRegenerateToolbar.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Button, Tooltip } from 'antd'; +import { Button, Tooltip, theme } from 'antd'; import { EditOutlined } from '@ant-design/icons'; interface PartialRegenerateToolbarProps { @@ -19,6 +19,8 @@ export const PartialRegenerateToolbar: React.FC = onRegenerate, selectedText }) => { + const { token } = theme.useToken(); + if (!visible || !selectedText) return null; // 限制显示的选中文本长度 @@ -33,15 +35,15 @@ export const PartialRegenerateToolbar: React.FC = top: position.top, left: position.left, zIndex: 10000, - background: '#fff', + background: token.colorBgElevated, borderRadius: 8, - boxShadow: '0 4px 16px rgba(0, 0, 0, 0.15)', + boxShadow: token.boxShadow, padding: '6px 8px', display: 'flex', alignItems: 'center', gap: 8, animation: 'fadeIn 0.2s ease-out', - border: '1px solid #e8e8e8', + border: `1px solid ${token.colorBorderSecondary}`, }} > = background: 'linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-hover) 100%)', border: 'none', fontWeight: 500, - boxShadow: '0 4px 12px rgba(77, 128, 136, 0.3)', + boxShadow: token.boxShadowSecondary, }} > AI重写 @@ -69,7 +71,7 @@ export const PartialRegenerateToolbar: React.FC = = ({ progress, message }) => { + const { token } = theme.useToken(); + if (!loading) return null; return ( @@ -22,19 +24,19 @@ export const SSELoadingOverlay: React.FC = ({ left: 0, right: 0, bottom: 0, - background: 'rgba(0, 0, 0, 0.45)', + background: token.colorBgMask, display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 9999 }}>
    {/* 标题和图标 */}
    = ({ marginBottom: 24 }}> } + indicator={} />
    AI生成中...
    @@ -60,7 +62,7 @@ export const SSELoadingOverlay: React.FC = ({ }}>
    = ({
    0 ? 'var(--shadow-card)' : 'none' + boxShadow: progress > 0 ? token.boxShadow : 'none' }} />
    @@ -82,7 +84,7 @@ export const SSELoadingOverlay: React.FC = ({ textAlign: 'center', fontSize: 32, fontWeight: 'bold', - color: progress === 100 ? 'var(--color-success)' : 'var(--color-primary)', + color: progress === 100 ? token.colorSuccess : token.colorPrimary, marginBottom: 8 }}> {progress}% @@ -93,7 +95,7 @@ export const SSELoadingOverlay: React.FC = ({
    @@ -104,7 +106,7 @@ export const SSELoadingOverlay: React.FC = ({
    请勿关闭页面,生成过程需要一定时间 diff --git a/frontend/src/components/SSEProgressBar.tsx b/frontend/src/components/SSEProgressBar.tsx index 4465f37..e461d68 100644 --- a/frontend/src/components/SSEProgressBar.tsx +++ b/frontend/src/components/SSEProgressBar.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { theme } from 'antd'; interface SSEProgressBarProps { loading: boolean; @@ -11,6 +12,8 @@ export const SSEProgressBar: React.FC = ({ progress, message }) => { + const { token } = theme.useToken(); + if (!loading) return null; return ( @@ -18,14 +21,14 @@ export const SSEProgressBar: React.FC = ({ {/* 进度条 */}
    = ({ alignItems: 'center', fontSize: 14 }}> - + {message || '准备生成...'} {progress}% diff --git a/frontend/src/components/SSEProgressModal.tsx b/frontend/src/components/SSEProgressModal.tsx index b8b9192..2278dc1 100644 --- a/frontend/src/components/SSEProgressModal.tsx +++ b/frontend/src/components/SSEProgressModal.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Modal, Spin, Button } from 'antd'; +import { Modal, Spin, Button, theme } from 'antd'; import { LoadingOutlined, StopOutlined } from '@ant-design/icons'; interface SSEProgressModalProps { @@ -27,6 +27,8 @@ export const SSEProgressModal: React.FC = ({ onCancel, cancelButtonText = '取消任务', }) => { + const { token } = theme.useToken(); + if (!visible) return null; return ( @@ -53,13 +55,13 @@ export const SSEProgressModal: React.FC = ({ marginBottom: 24 }}> } + indicator={} />
    {title}
    @@ -72,7 +74,7 @@ export const SSEProgressModal: React.FC = ({ }}>
    = ({
    0 ? 'var(--shadow-card)' : 'none' + boxShadow: progress > 0 ? token.boxShadow : 'none' }} />
    @@ -95,7 +97,7 @@ export const SSEProgressModal: React.FC = ({ textAlign: 'center', fontSize: 32, fontWeight: 'bold', - color: progress === 100 ? 'var(--color-success)' : 'var(--color-primary)', + color: progress === 100 ? token.colorSuccess : token.colorPrimary, marginBottom: 8 }}> {progress}% @@ -107,7 +109,7 @@ export const SSEProgressModal: React.FC = ({
    = ({
    请勿关闭页面,生成过程需要一定时间 diff --git a/frontend/src/components/UserMenu.tsx b/frontend/src/components/UserMenu.tsx index 293dd3d..0a23a5b 100644 --- a/frontend/src/components/UserMenu.tsx +++ b/frontend/src/components/UserMenu.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { Dropdown, Avatar, Space, Typography, message, Modal, Form, Input, Button } from 'antd'; +import { Dropdown, Avatar, Space, Typography, message, Modal, Form, Input, Button, theme } from 'antd'; import { UserOutlined, LogoutOutlined, TeamOutlined, CrownOutlined, LockOutlined } from '@ant-design/icons'; import { authApi } from '../services/api'; import type { User } from '../types'; @@ -11,14 +11,18 @@ const { Text } = Typography; interface UserMenuProps { /** 是否总是显示完整信息(用于移动端侧边栏) */ showFullInfo?: boolean; + /** 紧凑模式(用于折叠侧边栏,仅展示头像) */ + compact?: boolean; } -export default function UserMenu({ showFullInfo = false }: UserMenuProps) { +export default function UserMenu({ showFullInfo = false, compact = false }: UserMenuProps) { const navigate = useNavigate(); const [currentUser, setCurrentUser] = useState(null); const [showChangePassword, setShowChangePassword] = useState(false); const [changePasswordForm] = Form.useForm(); const [changingPassword, setChangingPassword] = useState(false); + const { token } = theme.useToken(); + const alphaColor = (color: string, alpha: number) => `color-mix(in srgb, ${color} ${(alpha * 100).toFixed(0)}%, transparent)`; useEffect(() => { loadCurrentUser(); @@ -126,36 +130,36 @@ export default function UserMenu({ showFullInfo = false }: UserMenuProps) { cursor: 'pointer', display: 'flex', alignItems: 'center', - gap: 12, - padding: '8px 16px', - background: 'rgba(255, 255, 255, 0.6)', // 保持半透明以配合 Backdrop + gap: compact ? 0 : 12, + padding: compact ? '4px' : '8px 16px', + background: alphaColor(token.colorBgContainer, 0.65), // 保持半透明以配合 Backdrop backdropFilter: 'blur(10px)', WebkitBackdropFilter: 'blur(10px)', - borderRadius: 24, - border: '1px solid var(--color-border)', + borderRadius: compact ? 16 : 24, + border: `1px solid ${token.colorBorder}`, transition: 'all 0.3s ease', - boxShadow: 'var(--shadow-card)', + boxShadow: `0 8px 20px ${alphaColor(token.colorText, 0.08)}`, }} onMouseEnter={(e) => { - e.currentTarget.style.background = 'var(--color-bg-container)'; // 悬浮时变实 + e.currentTarget.style.background = token.colorBgContainer; // 悬浮时变实 e.currentTarget.style.transform = 'translateY(-2px)'; - e.currentTarget.style.boxShadow = 'var(--shadow-elevated)'; + e.currentTarget.style.boxShadow = `0 12px 28px ${alphaColor(token.colorText, 0.14)}`; }} onMouseLeave={(e) => { - e.currentTarget.style.background = 'rgba(255, 255, 255, 0.6)'; + e.currentTarget.style.background = alphaColor(token.colorBgContainer, 0.65); e.currentTarget.style.transform = 'translateY(0)'; - e.currentTarget.style.boxShadow = 'var(--shadow-card)'; + e.currentTarget.style.boxShadow = `0 8px 20px ${alphaColor(token.colorText, 0.08)}`; }} >
    } - size={40} + size={compact ? 32 : 40} style={{ - backgroundColor: 'var(--color-primary)', - border: '3px solid #fff', - boxShadow: 'var(--shadow-card)', + backgroundColor: token.colorPrimary, + border: `3px solid ${token.colorWhite}`, + boxShadow: `0 8px 20px ${alphaColor(token.colorText, 0.12)}`, }} /> {currentUser.is_admin && ( @@ -165,28 +169,28 @@ export default function UserMenu({ showFullInfo = false }: UserMenuProps) { right: -2, width: 18, height: 18, - background: 'linear-gradient(135deg, #ffd700 0%, #ffaa00 100%)', + background: `linear-gradient(135deg, ${token.colorWarning} 0%, ${token.colorWarningHover} 100%)`, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', - border: '2px solid white', - boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)', + border: `2px solid ${token.colorWhite}`, + boxShadow: `0 2px 4px ${alphaColor(token.colorText, 0.2)}`, }}> - +
    )}
    - + {currentUser.display_name || currentUser.username} diff --git a/frontend/src/index.css b/frontend/src/index.css index 1ab1b28..5bb84b7 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,70 +1,17 @@ +html, +body, +#root { + min-height: 100%; +} + :root { - /* --- 中国风配色方案 (Chinese Style Palette) --- */ - - /* 主色调:天青 (Cerulean / Azure) - 类似汝窑 */ - --color-primary: #4D8088; - --color-primary-hover: #5F9EA8; - --color-primary-active: #3A666C; - - /* 辅助色 */ - --color-success: #52C41A; - --color-warning: #FAAD14; - --color-error: #FF4D4F; - --color-info: #1890FF; - - /* 功能色背景/边框 */ - --color-success-bg: #F6FFED; - --color-success-border: #B7EB8F; - --color-warning-bg: #FFFBE6; - --color-warning-border: #FFE58F; - --color-error-bg: #FFF2F0; - --color-error-border: #FFCCC7; - --color-info-bg: #E6F7FF; - --color-info-border: #91D5FF; - - /* 背景色 */ - --color-bg-base: #F8F6F1; - /* 米汤色 (Rice Soup / Cream) - 用于页面背景 */ - --color-bg-container: #FFFFFF; - /* 纯白 - 用于卡片/容器 */ - --color-bg-layout: #F0F2F5; - --color-bg-spotlight: #3A666C; - --color-bg-mask: rgba(0, 0, 0, 0.45); - - /* 文本色 */ - --color-text-base: #2B2B2B; - /* 墨色 (Ink) */ - --color-text-primary: #2B2B2B; - --color-text-secondary: #595959; - /* 此时 (Secondary Text) */ - --color-text-tertiary: #8C8C8C; - --color-text-quaternary: #BFBFBF; - - /* 边框色 */ - --color-border: #D9D9D9; - --color-border-secondary: #F0F0F0; - - /* 阴影 */ - --shadow-card: 0 2px 8px rgba(0, 0, 0, 0.06); - --shadow-elevated: 0 8px 24px rgba(77, 128, 136, 0.15); - /* 带一点主色调的阴影 */ - --shadow-primary: 0 4px 16px rgba(77, 128, 136, 0.25); - --shadow-header: 0 2px 8px rgba(0, 0, 0, 0.05); - font-family: "PingFang SC", "Microsoft YaHei", "Heiti SC", Inter, system-ui, sans-serif; line-height: 1.5715; font-weight: 400; - - color-scheme: light; - color: var(--color-text-base); - background-color: var(--color-bg-base); - font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - - /* 移动端视口适配 */ -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } @@ -72,43 +19,123 @@ body { margin: 0; min-height: 100vh; - /* 禁止移动端双击缩放 */ touch-action: manipulation; } -#root { - min-height: 100vh; -} - * { box-sizing: border-box; } -/* 自定义滚动条样式 */ +/* 自定义滚动条样式(与主题弱耦合) */ ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { - background: #f1f1f1; + background: rgba(0, 0, 0, 0.06); border-radius: 4px; } ::-webkit-scrollbar-thumb { - background: #c1c1c1; + background: rgba(0, 0, 0, 0.24); border-radius: 4px; - transition: background 0.3s ease; + transition: background 0.2s ease; } ::-webkit-scrollbar-thumb:hover { - background: #a8a8a8; + background: rgba(0, 0, 0, 0.34); } -/* Firefox 滚动条样式 */ * { scrollbar-width: thin; - scrollbar-color: #c1c1c1 #f1f1f1; + scrollbar-color: rgba(0, 0, 0, 0.24) rgba(0, 0, 0, 0.06); +} + +[data-theme-resolved='dark'] ::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.08); +} + +[data-theme-resolved='dark'] ::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.28); +} + +[data-theme-resolved='dark'] ::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.4); +} + +[data-theme-resolved='dark'] * { + scrollbar-color: rgba(255, 255, 255, 0.28) rgba(255, 255, 255, 0.08); +} + +/* 主题切换转场(暗色扩散、亮色收缩) */ +:root { + --theme-transition-duration: 460ms; + --theme-transition-easing: cubic-bezier(0.22, 1, 0.36, 1); +} + +:root::view-transition-group(root) { + animation-duration: var(--theme-transition-duration); +} + +:root[data-theme-transition='to-dark']::view-transition-new(root) { + z-index: 2; + animation: theme-dark-reveal var(--theme-transition-duration) var(--theme-transition-easing) both; +} + +:root[data-theme-transition='to-dark']::view-transition-old(root) { + z-index: 1; + animation: theme-old-fade var(--theme-transition-duration) ease both; +} + +:root[data-theme-transition='to-light']::view-transition-old(root) { + z-index: 2; + animation: theme-light-collapse var(--theme-transition-duration) var(--theme-transition-easing) both; +} + +:root[data-theme-transition='to-light']::view-transition-new(root) { + z-index: 1; + animation: theme-new-fade var(--theme-transition-duration) ease both; +} + +@keyframes theme-dark-reveal { + from { + clip-path: circle(0% at 50% 50%); + opacity: 0.78; + } + to { + clip-path: circle(150% at 50% 50%); + opacity: 1; + } +} + +@keyframes theme-light-collapse { + from { + clip-path: circle(150% at 50% 50%); + opacity: 1; + } + to { + clip-path: circle(0% at 50% 50%); + opacity: 0.82; + } +} + +@keyframes theme-old-fade { + from { opacity: 1; } + to { opacity: 0.9; } +} + +@keyframes theme-new-fade { + from { opacity: 0.92; } + to { opacity: 1; } +} + +@media (prefers-reduced-motion: reduce) { + :root::view-transition-group(root), + :root::view-transition-old(root), + :root::view-transition-new(root) { + animation-duration: 1ms !important; + } } /* 移动端响应式样式 */ @@ -117,38 +144,17 @@ body { font-size: 14px; } - /* 移动端隐藏滚动条 */ ::-webkit-scrollbar { width: 4px; height: 4px; } - /* 移动端优化触摸区域 */ button, a, - [role="button"] { + [role='button'] { min-height: 44px; min-width: 44px; } -} - -@media (max-width: 576px) { - :root { - font-size: 13px; - } -} - -/* 移动端安全区域适配 (iPhone X+) */ -@supports (padding: max(0px)) { - body { - padding-left: env(safe-area-inset-left); - padding-right: env(safe-area-inset-right); - padding-bottom: env(safe-area-inset-bottom); - } -} - -/* 移动端禁止长按选择 */ -@media (max-width: 768px) { img, button { @@ -184,250 +190,55 @@ body { } } +@media (max-width: 576px) { + :root { + font-size: 13px; + } +} + +/* 移动端安全区域适配 (iPhone X+) */ +@supports (padding: max(0px)) { + body { + padding-left: env(safe-area-inset-left); + padding-right: env(safe-area-inset-right); + padding-bottom: env(safe-area-inset-bottom); + } +} + .ant-tabs-dropdown { z-index: 2000 !important; } -/* ===== 现代化侧边栏样式 (Modern Sidebar Styles) ===== */ - -/* 侧边栏容器 - 毛玻璃效果 */ -.modern-sider { - background: linear-gradient(180deg, - rgba(255, 255, 255, 0.95) 0%, - rgba(248, 246, 241, 0.98) 100%) !important; - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border-right: 1px solid rgba(77, 128, 136, 0.12); - box-shadow: - 4px 0 24px rgba(77, 128, 136, 0.08), - 1px 0 0 rgba(255, 255, 255, 0.8) inset; +.ant-tooltip { + max-width: min(420px, calc(100vw - 24px)); } -/* 侧边栏菜单整体样式 */ -.modern-sider .ant-menu { - background: transparent !important; - border-right: none !important; -} - -/* 菜单项基础样式 */ -.modern-sider .ant-menu-item { - margin: 6px 12px !important; - padding: 0 16px !important; - border-radius: 12px !important; - height: 48px !important; - line-height: 48px !important; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; - position: relative; - overflow: hidden; -} - -/* 折叠状态下的菜单项 */ -.modern-sider.ant-layout-sider-collapsed .ant-menu-item { - margin: 6px 8px !important; - padding: 0 !important; - display: flex; - align-items: center; - justify-content: center; -} - -/* 菜单项悬停效果 - 仅未选中项 */ -.modern-sider .ant-menu-item:not(.ant-menu-item-selected):hover { - background: linear-gradient(135deg, - rgba(77, 128, 136, 0.15) 0%, - rgba(95, 158, 168, 0.22) 100%) !important; - transform: translateX(6px); - box-shadow: - 0 2px 8px rgba(77, 128, 136, 0.15), - inset 0 0 0 1px rgba(77, 128, 136, 0.2); -} - -/* 折叠状态悬停效果 - 仅未选中项 */ -.modern-sider.ant-layout-sider-collapsed .ant-menu-item:not(.ant-menu-item-selected):hover { - transform: translateX(0) scale(1.08); - box-shadow: - 0 4px 12px rgba(77, 128, 136, 0.2), - inset 0 0 0 1px rgba(77, 128, 136, 0.25); -} - -/* 菜单项选中状态 - 增强版 */ -.modern-sider .ant-menu-item-selected { - background: linear-gradient(135deg, - var(--color-primary) 0%, - #5A9BA5 50%, - var(--color-primary-hover) 100%) !important; - box-shadow: - 0 6px 20px rgba(77, 128, 136, 0.45), - 0 3px 8px rgba(77, 128, 136, 0.25), - inset 0 1px 0 rgba(255, 255, 255, 0.2); - border: 1px solid rgba(255, 255, 255, 0.15); -} - -/* 选中项悬停 - 微发光效果 */ -.modern-sider .ant-menu-item-selected:hover { - transform: translateX(0) !important; - box-shadow: - 0 8px 24px rgba(77, 128, 136, 0.5), - 0 4px 10px rgba(77, 128, 136, 0.3), - inset 0 1px 0 rgba(255, 255, 255, 0.25); - filter: brightness(1.05); -} - -.modern-sider.ant-layout-sider-collapsed .ant-menu-item-selected:hover { - transform: scale(1.05) !important; -} - -/* 选中状态文字和图标颜色 */ -.modern-sider .ant-menu-item-selected, -.modern-sider .ant-menu-item-selected a, -.modern-sider .ant-menu-item-selected .anticon { - color: #fff !important; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); -} - -/* 未选中状态颜色 */ -.modern-sider .ant-menu-item:not(.ant-menu-item-selected), -.modern-sider .ant-menu-item:not(.ant-menu-item-selected) a { - color: var(--color-text-secondary) !important; -} - -.modern-sider .ant-menu-item:not(.ant-menu-item-selected) .anticon { - color: var(--color-primary) !important; -} - -/* 菜单项悬停时文字和图标颜色 */ -.modern-sider .ant-menu-item:not(.ant-menu-item-selected):hover, -.modern-sider .ant-menu-item:not(.ant-menu-item-selected):hover a { - color: var(--color-primary-active) !important; - font-weight: 600; -} - -.modern-sider .ant-menu-item:not(.ant-menu-item-selected):hover .anticon { - color: var(--color-primary-active) !important; -} - -/* 图标样式优化 */ -.modern-sider .ant-menu-item .anticon { - font-size: 18px !important; - transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) !important; -} - -.modern-sider.ant-layout-sider-collapsed .ant-menu-item .anticon { - font-size: 22px !important; -} - -/* 悬停时图标微动效 */ -.modern-sider .ant-menu-item:not(.ant-menu-item-selected):hover .anticon { - transform: scale(1.15); -} - -/* 折叠状态下图标去边距并隐藏文字容器,确保居中 */ -.modern-sider.ant-layout-sider-collapsed .ant-menu-item .anticon { - margin-right: 0 !important; - font-size: 22px !important; -} - -/* 折叠状态下隐藏文字但保持点击区域 */ -.modern-sider.ant-layout-sider-collapsed .ant-menu-item .ant-menu-title-content { - width: 0 !important; - overflow: hidden !important; - opacity: 0 !important; - margin: 0 !important; - padding: 0 !important; -} - -/* 确保折叠状态下的 Link 覆盖整个菜单项区域 */ -.modern-sider.ant-layout-sider-collapsed .ant-menu-item { - position: relative; -} - -.modern-sider.ant-layout-sider-collapsed .ant-menu-item a { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - display: flex; - align-items: center; - justify-content: center; -} - -/* 选中项左侧指示条 */ -.modern-sider .ant-menu-item-selected::before { - content: ''; - position: absolute; - left: 0; - top: 50%; - transform: translateY(-50%); - width: 4px; - height: 24px; - background: rgba(255, 255, 255, 0.9); - border-radius: 0 4px 4px 0; - opacity: 0; -} - -/* 折叠状态隐藏指示条 */ -.modern-sider.ant-layout-sider-collapsed .ant-menu-item-selected::before { - display: none; -} - -/* 链接文字样式 */ -.modern-sider .ant-menu-item a { - font-weight: 500; - letter-spacing: 0.5px; - transition: all 0.3s ease; -} - -/* 侧边栏顶部装饰线 */ -.modern-sider::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 3px; - background: linear-gradient(90deg, - var(--color-primary) 0%, - var(--color-primary-hover) 50%, - var(--color-primary) 100%); - opacity: 0.8; -} - -/* 侧边栏底部渐变遮罩 */ -.modern-sider::after { - content: ''; - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 60px; - background: linear-gradient(to top, - rgba(248, 246, 241, 0.95) 0%, - transparent 100%); - pointer-events: none; -} - -/* 菜单项波纹效果 */ -.modern-sider .ant-menu-item::after { - display: none !important; -} - -/* 菜单标题样式 (如果有分组) */ -.modern-sider .ant-menu-item-group-title { - color: var(--color-text-tertiary) !important; - font-size: 12px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 1px; - padding: 16px 24px 8px !important; -} - -/* Tooltip 样式优化 (折叠状态) */ +.ant-tooltip .ant-tooltip-content, .ant-tooltip .ant-tooltip-inner { - background: linear-gradient(135deg, - var(--color-primary) 0%, - var(--color-primary-hover) 100%); + max-width: inherit; +} + +.ant-tooltip .ant-tooltip-inner { + background: var(--app-tooltip-bg, #884d5c); border-radius: 8px; padding: 8px 16px; font-weight: 500; - box-shadow: 0 4px 12px rgba(77, 128, 136, 0.3); -} \ No newline at end of file + box-shadow: 0 4px 12px var(--app-tooltip-shadow, rgba(136, 77, 92, 0.3)); + white-space: normal; + word-break: break-word; + overflow-wrap: anywhere; + max-height: 50vh; + overflow-y: auto; +} + +@media (max-width: 768px) { + .ant-tooltip { + max-width: calc(100vw - 16px); + } + + .ant-tooltip .ant-tooltip-inner { + padding: 8px 12px; + max-height: 50vh; + } +} + diff --git a/frontend/src/pages/AuthCallback.tsx b/frontend/src/pages/AuthCallback.tsx index 8f2b9db..ed2786d 100644 --- a/frontend/src/pages/AuthCallback.tsx +++ b/frontend/src/pages/AuthCallback.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Spin, Result, Button, Modal, Input, message } from 'antd'; +import { Spin, Result, Button, Modal, Input, message, theme } from 'antd'; import { authApi } from '../services/api'; import AnnouncementModal from '../components/AnnouncementModal'; @@ -10,6 +10,8 @@ export default function AuthCallback() { const [errorMessage, setErrorMessage] = useState(''); const [showAnnouncement, setShowAnnouncement] = useState(false); const [showPasswordModal, setShowPasswordModal] = useState(false); + const { token } = theme.useToken(); + const alphaColor = (color: string, alpha: number) => `color-mix(in srgb, ${color} ${(alpha * 100).toFixed(0)}%, transparent)`; interface PasswordStatus { has_password: boolean; has_custom_password: boolean; @@ -92,11 +94,11 @@ export default function AuthCallback() { justifyContent: 'center', alignItems: 'center', minHeight: '100vh', - background: 'linear-gradient(135deg, #4D8088 0%, #5F9EA8 100%)', + background: `linear-gradient(135deg, ${token.colorPrimary} 0%, ${token.colorPrimaryHover} 100%)`, }}>
    -
    +
    正在处理登录...
    @@ -111,7 +113,7 @@ export default function AuthCallback() { justifyContent: 'center', alignItems: 'center', minHeight: '100vh', - background: 'linear-gradient(135deg, #4D8088 0%, #5F9EA8 100%)', + background: `linear-gradient(135deg, ${token.colorPrimary} 0%, ${token.colorPrimaryHover} 100%)`, }}> } - style={{ background: 'white', padding: 40, borderRadius: 8 }} + style={{ background: token.colorBgContainer, padding: 40, borderRadius: 8 }} />
    ); @@ -257,17 +259,17 @@ export default function AuthCallback() {

    系统已为您自动生成默认密码,您可以选择设置自定义密码或继续使用默认密码。

    {passwordStatus?.default_password && (
    账号:{passwordStatus.username}
    默认密码:{passwordStatus.default_password}
    @@ -301,13 +303,13 @@ export default function AuthCallback() { justifyContent: 'center', alignItems: 'center', minHeight: '100vh', - background: 'linear-gradient(135deg, #4D8088 0%, #5F9EA8 100%)', + background: `linear-gradient(135deg, ${token.colorPrimary} 0%, ${token.colorPrimaryHover} 100%)`, }}>
    diff --git a/frontend/src/pages/BookImport.tsx b/frontend/src/pages/BookImport.tsx index 18a3a3b..cb5b0e7 100644 --- a/frontend/src/pages/BookImport.tsx +++ b/frontend/src/pages/BookImport.tsx @@ -11,6 +11,7 @@ import { InputNumber, List, message, + Popconfirm, Progress, Row, Select, @@ -20,6 +21,7 @@ import { Tag, Typography, Upload, + theme, } from 'antd'; import type { UploadFile } from 'antd/es/upload/interface'; import { InboxOutlined, PlayCircleOutlined, ReloadOutlined, StopOutlined, WarningOutlined, RedoOutlined } from '@ant-design/icons'; @@ -31,7 +33,7 @@ import type { BookImportTask, } from '../types'; -const { Text } = Typography; +const { Text, Title } = Typography; const { Dragger } = Upload; const { TextArea } = Input; @@ -106,6 +108,8 @@ function isNotFoundError(error: unknown): boolean { export default function BookImport() { const navigate = useNavigate(); + const { token } = theme.useToken(); + const isMobile = window.innerWidth <= 768; const [file, setFile] = useState(null); const [taskId, setTaskId] = useState(null); @@ -140,6 +144,40 @@ export default function BookImport() { return 1; }, [taskId, taskStatus, preview, applying, isApplyComplete]); + const canRestart = useMemo(() => { + return Boolean( + file || + taskId || + taskStatus || + preview || + applyProgress > 0 || + applyMessage || + applyError || + isApplyComplete || + failedSteps.length > 0 || + retrying + ); + }, [ + file, + taskId, + taskStatus, + preview, + applyProgress, + applyMessage, + applyError, + isApplyComplete, + failedSteps, + retrying, + ]); + + const stepItems = [ + { title: '上传文件' }, + { title: '解析中' }, + { title: '预览修改' }, + { title: '生成导入' }, + ]; + const currentStepText = stepItems[currentStep]?.title || '上传文件'; + useEffect(() => { const cache = loadBookImportCache(); if (cache) { @@ -487,6 +525,31 @@ export default function BookImport() { } }, [navigate]); + const restartImport = useCallback(() => { + clearBookImportCache(); + importedProjectId.current = null; + + setFile(null); + setTaskId(null); + setTaskStatus(null); + setPreview(null); + + setCreatingTask(false); + setLoadingPreview(false); + setApplying(false); + setApplyProgress(0); + setApplyMessage(''); + setApplyError(null); + setIsApplyComplete(false); + + setFailedSteps([]); + setRetrying(false); + setRetryProgress(0); + setRetryMessage(''); + + message.success('已重新开始,请重新上传 TXT 并解析'); + }, []); + const updateChapter = (index: number, patch: Partial) => { setPreview(prev => { if (!prev) return prev; @@ -497,18 +560,100 @@ export default function BookImport() { }; return ( -
    - - - +
    +
    + +
    +
    + + + + + + <InboxOutlined style={{ color: token.colorWhite, opacity: 0.9, marginRight: 8 }} /> + 拆书导入 + + + 上传TXT并自动解析为章节、预览并导入项目 + + + + + + + 当前进度:{currentStepText} + + + + + + + + + + + + {currentStep === 0 && ( @@ -913,6 +1058,8 @@ export default function BookImport() {
    )} + +
    ); } \ No newline at end of file diff --git a/frontend/src/pages/ChapterAnalysis.tsx b/frontend/src/pages/ChapterAnalysis.tsx index 993e266..b4a1902 100644 --- a/frontend/src/pages/ChapterAnalysis.tsx +++ b/frontend/src/pages/ChapterAnalysis.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { Card, List, Button, Space, Empty, Tag, Spin, Alert, Switch, Drawer, message } from 'antd'; +import { Card, List, Button, Space, Empty, Tag, Spin, Alert, Switch, Drawer, message, theme } from 'antd'; import { EyeOutlined, EyeInvisibleOutlined, @@ -77,6 +77,7 @@ const ChapterAnalysis: React.FC = () => { const [scrollToContentAnnotation, setScrollToContentAnnotation] = useState(); const [scrollToSidebarAnnotation, setScrollToSidebarAnnotation] = useState(); const [isMobile, setIsMobile] = useState(window.innerWidth < 768); + const { token } = theme.useToken(); // 监听窗口大小变化 useEffect(() => { @@ -196,7 +197,7 @@ const ChapterAnalysis: React.FC = () => {

    @@ -231,8 +232,8 @@ const ChapterAnalysis: React.FC = () => { style={{ cursor: 'pointer', padding: '12px 16px', - background: selectedChapter?.id === chapter.id ? '#e6f7ff' : 'transparent', - borderLeft: selectedChapter?.id === chapter.id ? '3px solid #1890ff' : '3px solid transparent', + background: selectedChapter?.id === chapter.id ? token.colorPrimaryBg : 'transparent', + borderLeft: selectedChapter?.id === chapter.id ? `3px solid ${token.colorPrimary}` : '3px solid transparent', }} > { style={{ cursor: 'pointer', padding: '12px 16px', - background: selectedChapter?.id === chapter.id ? '#e6f7ff' : 'transparent', - borderLeft: selectedChapter?.id === chapter.id ? '3px solid #1890ff' : '3px solid transparent', + background: selectedChapter?.id === chapter.id ? token.colorPrimaryBg : 'transparent', + borderLeft: selectedChapter?.id === chapter.id ? `3px solid ${token.colorPrimary}` : '3px solid transparent', }} > { checkedChildren={} unCheckedChildren={} /> - 显示标注 + 显示标注 )} @@ -441,7 +442,7 @@ const ChapterAnalysis: React.FC = () => {
    共有 {annotationsData.summary.total_annotations} 个标注: diff --git a/frontend/src/pages/ChapterReader.tsx b/frontend/src/pages/ChapterReader.tsx index aaa1be3..0ac2bee 100644 --- a/frontend/src/pages/ChapterReader.tsx +++ b/frontend/src/pages/ChapterReader.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useCallback } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; -import { Card, Spin, Alert, Button, Space, Switch, Drawer, message, Progress } from 'antd'; +import { Card, Spin, Alert, Button, Space, Switch, Drawer, message, Progress, theme } from 'antd'; import { ArrowLeftOutlined, EyeOutlined, @@ -64,6 +64,8 @@ const ChapterReader: React.FC = () => { const { chapterId } = useParams<{ chapterId: string }>(); const navigate = useNavigate(); + const { token } = theme.useToken(); + const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [chapter, setChapter] = useState(null); @@ -303,7 +305,7 @@ const ChapterReader: React.FC = () => { checkedChildren={} unCheckedChildren={} /> - 显示标注 + 显示标注 @@ -1969,9 +1970,9 @@ export default function Chapters() { style={{ padding: '16px', marginBottom: 16, - background: '#fff', - borderRadius: 8, - border: '1px solid #f0f0f0', + background: token.colorBgContainer, + borderRadius: token.borderRadius, + border: `1px solid ${token.colorBorderSecondary}`, flexDirection: isMobile ? 'column' : 'row', alignItems: isMobile ? 'flex-start' : 'center', }} @@ -2025,7 +2026,7 @@ export default function Chapters() { >
    } + avatar={!isMobile && } title={
    {getStatusText(item.status)} - + {renderAnalysisStatus(item.id)} {!canGenerateChapter(item) && ( } color="warning" title={getGenerateDisabledReason(item)}> @@ -2051,12 +2052,12 @@ export default function Chapters() { } description={ item.content ? ( -
    +
    {item.content.substring(0, isMobile ? 80 : 150)} {item.content.length > (isMobile ? 80 : 150) && '...'}
    ) : ( - 暂无内容 + 暂无内容 ) } /> @@ -2134,19 +2135,19 @@ export default function Chapters() { sum + (ch.word_count || 0), 0)} 字`} - style={{ backgroundColor: 'var(--color-primary)' }} + style={{ backgroundColor: token.colorPrimary }} />
    } style={{ marginBottom: 16, - background: '#fff', - borderRadius: 8, - border: '1px solid #f0f0f0', + background: token.colorBgContainer, + borderRadius: token.borderRadius, + border: `1px solid ${token.colorBorderSecondary}`, }} >
    } + avatar={!isMobile && } title={
    {getStatusText(item.status)} - + {renderAnalysisStatus(item.id)} {!canGenerateChapter(item) && ( } color="warning" title={getGenerateDisabledReason(item)}> @@ -2255,7 +2256,7 @@ export default function Chapters() { {item.expansion_plan && ( { e.stopPropagation(); showExpansionPlanModal(item); @@ -2264,7 +2265,7 @@ export default function Chapters() { )} { e.stopPropagation(); handleOpenPlanEditor(item); @@ -2276,12 +2277,12 @@ export default function Chapters() { } description={ item.content ? ( -
    +
    {item.content.substring(0, isMobile ? 80 : 150)} {item.content.length > (isMobile ? 80 : 150) && '...'}
    ) : ( - 暂无内容 + 暂无内容 ) } /> @@ -2539,7 +2540,7 @@ export default function Chapters() { ))} {!selectedStyleId && ( -
    请选择写作风格
    +
    请选择写作风格
    )} @@ -2560,7 +2561,7 @@ export default function Chapters() { 全知视角 {temporaryNarrativePerspective && ( -
    +
    ✓ {getNarrativePerspectiveText(temporaryNarrativePerspective)}
    )} @@ -2703,7 +2704,7 @@ export default function Chapters() { - + 批量生成章节内容 } @@ -2879,7 +2880,7 @@ export default function Chapters() { > - ✓ 自动更新角色状态 + ✓ 自动更新角色状态 diff --git a/frontend/src/pages/Characters.tsx b/frontend/src/pages/Characters.tsx index ad42c93..e34f022 100644 --- a/frontend/src/pages/Characters.tsx +++ b/frontend/src/pages/Characters.tsx @@ -1,9 +1,9 @@ import { useState, useEffect, useRef } from 'react'; -import { Button, Modal, Form, Input, Select, message, Row, Col, Empty, Tabs, Divider, Typography, Space, InputNumber, Checkbox } from 'antd'; +import { Button, Modal, Form, Input, Select, message, Row, Col, Empty, Tabs, Divider, Typography, Space, InputNumber, Checkbox, theme } from 'antd'; import { ThunderboltOutlined, UserOutlined, TeamOutlined, PlusOutlined, ExportOutlined, ImportOutlined, DownloadOutlined } from '@ant-design/icons'; import { useStore } from '../store'; import { useCharacterSync } from '../store/hooks'; -import { characterGridConfig } from '../components/CardStyles'; +import { charactersPageGridConfig } from '../components/CardStyles'; import { CharacterCard } from '../components/CharacterCard'; import { SSELoadingOverlay } from '../components/SSELoadingOverlay'; import type { Character, ApiError } from '../types'; @@ -94,6 +94,7 @@ interface CharacterUpdateData { } export default function Characters() { + const { token } = theme.useToken(); const { currentProject, characters } = useStore(); const [isGenerating, setIsGenerating] = useState(false); const [progress, setProgress] = useState(0); @@ -390,7 +391,7 @@ export default function Characters() { content: (
    {validation.errors.map((error, index) => ( -
    • {error}
    +
    • {error}
    ))}
    ), @@ -415,10 +416,10 @@ export default function Characters() { {validation.warnings.length > 0 && ( <> -

    ⚠️ 警告:

    +

    ⚠️ 警告:

      {validation.warnings.map((warning, index) => ( -
    • {warning}
    • +
    • {warning}
    • ))}
    @@ -463,10 +464,10 @@ export default function Characters() { {result.statistics.skipped > 0 && ( <> -

    ⚠️ 跳过: {result.statistics.skipped} 个

    +

    ⚠️ 跳过: {result.statistics.skipped} 个

      {result.details.skipped.map((name, index) => ( -
    • {name}
    • +
    • {name}
    • ))}
    @@ -474,10 +475,10 @@ export default function Characters() { {result.warnings.length > 0 && ( <> -

    ⚠️ 警告:

    +

    ⚠️ 警告:

      {result.warnings.map((warning, index) => ( -
    • {warning}
    • +
    • {warning}
    • ))}
    @@ -485,10 +486,10 @@ export default function Characters() { {result.details.errors.length > 0 && ( <> -

    ❌ 失败: {result.statistics.errors} 个

    +

    ❌ 失败: {result.statistics.errors} 个

      {result.details.errors.map((error, index) => ( -
    • {error}
    • +
    • {error}
    • ))}
    @@ -777,7 +778,7 @@ export default function Characters() { ) : ( <> - + {activeTab === 'all' && ( <> {characterList.length > 0 && ( @@ -793,10 +794,10 @@ export default function Characters() { {characterList.map((character) => ( @@ -831,10 +832,10 @@ export default function Characters() { {organizationList.map((org) => ( @@ -861,10 +862,10 @@ export default function Characters() { {activeTab === 'character' && characterList.map((character) => ( @@ -887,10 +888,10 @@ export default function Characters() { {activeTab === 'organization' && organizationList.map((org) => ( @@ -1020,7 +1021,7 @@ export default function Characters() { value={editingCharacter.relationships} readOnly autoSize={{ minRows: 1, maxRows: 3 }} - style={{ backgroundColor: '#f5f5f5', cursor: 'default' }} + style={{ backgroundColor: token.colorFillTertiary, cursor: 'default' }} /> )} @@ -1195,10 +1196,10 @@ export default function Characters() { disabled autoSize={{ minRows: 1, maxRows: 4 }} placeholder="暂无成员,请在组织管理中添加" - style={{ color: '#333', backgroundColor: '#fafafa' }} + style={{ color: token.colorText, backgroundColor: token.colorFillAlter }} /> -
    +
    💡 请前往「组织管理」页面添加或管理组织成员
    diff --git a/frontend/src/pages/Foreshadows.tsx b/frontend/src/pages/Foreshadows.tsx index b33fc85..80353af 100644 --- a/frontend/src/pages/Foreshadows.tsx +++ b/frontend/src/pages/Foreshadows.tsx @@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom'; import { Card, Table, Button, Tag, Space, Modal, Form, Input, Select, InputNumber, Switch, message, Tooltip, Popconfirm, Statistic, - Row, Col, Empty, Divider, Badge, Alert, Pagination, Dropdown + Row, Col, Empty, Divider, Badge, Alert, Pagination, Dropdown, theme } from 'antd'; import type { MenuProps } from 'antd'; import { @@ -73,6 +73,7 @@ export default function Foreshadows() { // 表格容器引用,用于计算滚动高度 const tableContainerRef = useRef(null); const [tableScrollY, setTableScrollY] = useState(400); + const { token } = theme.useToken(); // 加载伏笔列表 const loadForeshadows = useCallback(async () => { @@ -508,22 +509,22 @@ export default function Foreshadows() { - + - + - + - + @@ -531,7 +532,7 @@ export default function Foreshadows() { 0 ? '#ff4d4f' : '#8c8c8c' }} + valueStyle={{ color: stats.overdue_count > 0 ? token.colorError : token.colorTextSecondary }} prefix={stats.overdue_count > 0 ? : null} /> @@ -659,12 +660,12 @@ export default function Foreshadows() { {/* 分页器 - 固定在底部居中 */}
    暗示文本: -

    +

    {currentForeshadow.hint_text}

    @@ -877,7 +878,7 @@ export default function Foreshadows() { {currentForeshadow.resolution_text && ( 揭示文本: -

    +

    {currentForeshadow.resolution_text}

    @@ -920,7 +921,7 @@ export default function Foreshadows() { {currentForeshadow.notes && ( 备注: -

    {currentForeshadow.notes}

    +

    {currentForeshadow.notes}

    )} diff --git a/frontend/src/pages/Inspiration.tsx b/frontend/src/pages/Inspiration.tsx index 1323ae6..87187a5 100644 --- a/frontend/src/pages/Inspiration.tsx +++ b/frontend/src/pages/Inspiration.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Card, Input, Button, Space, Typography, message, Spin, Modal } from 'antd'; +import { Card, Input, Button, Space, Typography, message, Spin, Modal, theme } from 'antd'; import { SendOutlined, ArrowLeftOutlined, ReloadOutlined } from '@ant-design/icons'; import { inspirationApi } from '../services/api'; import { AIProjectGenerator, type GenerationConfig } from '../components/AIProjectGenerator'; @@ -52,6 +52,7 @@ const Inspiration: React.FC = () => { const navigate = useNavigate(); const [currentStep, setCurrentStep] = useState('idea'); const [isMobile, setIsMobile] = useState(window.innerWidth <= 768); + const { token } = theme.useToken(); useEffect(() => { const handleResize = () => { @@ -852,7 +853,7 @@ const Inspiration: React.FC = () => { height: isMobile ? 'calc(100vh - 280px)' : 600, overflowY: 'auto', marginBottom: 16, - boxShadow: '0 8px 24px rgba(0,0,0,0.15)', + boxShadow: `0 8px 24px color-mix(in srgb, ${token.colorTextBase} 20%, transparent)`, scrollBehavior: 'smooth' }} > @@ -873,16 +874,16 @@ const Inspiration: React.FC = () => { maxWidth: '80%', padding: '12px 16px', borderRadius: 12, - background: msg.type === 'ai' ? 'var(--color-bg-container)' : 'var(--color-primary)', - color: msg.type === 'ai' ? 'var(--color-text-primary)' : '#fff', + background: msg.type === 'ai' ? token.colorBgContainer : token.colorPrimary, + color: msg.type === 'ai' ? token.colorText : token.colorWhite, boxShadow: msg.type === 'ai' - ? 'var(--shadow-card)' - : 'var(--shadow-primary)', + ? `0 2px 10px color-mix(in srgb, ${token.colorTextBase} 12%, transparent)` + : `0 4px 14px color-mix(in srgb, ${token.colorPrimary} 30%, transparent)`, }}> @@ -904,13 +905,13 @@ const Inspiration: React.FC = () => { style={{ cursor: msg.optionsDisabled ? 'not-allowed' : 'pointer', border: msg.isMultiSelect && selectedOptions.includes(option) - ? '2px solid var(--color-primary)' - : '1px solid var(--color-border)', + ? `2px solid ${token.colorPrimary}` + : `1px solid ${token.colorBorder}`, background: msg.optionsDisabled - ? 'var(--color-bg-layout)' + ? token.colorBgLayout : msg.isMultiSelect && selectedOptions.includes(option) - ? 'var(--color-bg-spotlight)' - : 'var(--color-bg-container)', + ? token.colorPrimaryBg + : token.colorBgContainer, opacity: msg.optionsDisabled ? 0.6 : 1, animation: 'floatIn 0.6s ease-out', animationDelay: `${optIndex * 0.1}s`, @@ -920,7 +921,7 @@ const Inspiration: React.FC = () => { onMouseEnter={(e) => { if (!msg.optionsDisabled) { e.currentTarget.style.transform = 'translateY(-2px) scale(1.02)'; - e.currentTarget.style.boxShadow = 'var(--shadow-elevated)'; + e.currentTarget.style.boxShadow = `0 8px 22px color-mix(in srgb, ${token.colorTextBase} 14%, transparent)`; } }} onMouseLeave={(e) => { @@ -947,7 +948,7 @@ const Inspiration: React.FC = () => { {/* 反馈优化区域 - 新增 */} {msg.canRefine && !msg.optionsDisabled && !msg.isMultiSelect && ( -
    +
    {showFeedbackInput === index ? (