diff --git a/frontend/src/components/AIProjectGenerator.tsx b/frontend/src/components/AIProjectGenerator.tsx index c678e39..168d412 100644 --- a/frontend/src/components/AIProjectGenerator.tsx +++ b/frontend/src/components/AIProjectGenerator.tsx @@ -44,11 +44,11 @@ export const AIProjectGenerator: React.FC = ({ resumeProjectId }) => { const navigate = useNavigate(); - + // 状态管理 const [loading, setLoading] = useState(false); const [projectId, setProjectId] = useState(''); - + // SSE流式进度状态 const [progress, setProgress] = useState(0); const [progressMessage, setProgressMessage] = useState(''); @@ -58,7 +58,7 @@ export const AIProjectGenerator: React.FC = ({ characters: 'pending', outline: 'pending' }); - + // 保存生成数据,用于重试 const [generationData, setGenerationData] = useState(null); // 保存世界观生成结果,用于后续步骤 @@ -132,7 +132,7 @@ export const AIProjectGenerator: React.FC = ({ // 世界观已完成,从角色开始 message.info('世界观已完成,从角色步骤继续...'); setGenerationSteps({ worldBuilding: 'completed', characters: 'processing', outline: 'pending' }); - + // 获取世界观数据 const worldResult = { project_id: projectIdParam, @@ -143,7 +143,7 @@ export const AIProjectGenerator: React.FC = ({ }; setWorldBuildingResult(worldResult); setProgress(33); - + await resumeFromCharacters(data, worldResult); } else if (wizardStep === 2) { // 世界观和角色已完成,从大纲开始 @@ -297,7 +297,7 @@ export const AIProjectGenerator: React.FC = ({ message.success('项目创建成功!正在进入项目...'); clearStorage(); setLoading(false); - + onComplete(pid); setTimeout(() => { navigate(`/project/${pid}`); @@ -319,7 +319,7 @@ export const AIProjectGenerator: React.FC = ({ // 步骤1: 生成世界观并创建项目 setGenerationSteps(prev => ({ ...prev, worldBuilding: 'processing' })); setProgressMessage('正在生成世界观...'); - + const worldResult = await wizardStreamApi.generateWorldBuildingStream( { title: data.title, @@ -367,7 +367,7 @@ export const AIProjectGenerator: React.FC = ({ // 步骤2: 生成角色 setGenerationSteps(prev => ({ ...prev, characters: 'processing' })); setProgressMessage('正在生成角色...'); - + await wizardStreamApi.generateCharactersStream( { project_id: createdProjectId, @@ -406,7 +406,7 @@ export const AIProjectGenerator: React.FC = ({ // 步骤3: 生成大纲 setGenerationSteps(prev => ({ ...prev, outline: 'processing' })); setProgressMessage('正在生成大纲...'); - + await wizardStreamApi.generateCompleteOutlineStream( { project_id: createdProjectId, @@ -441,15 +441,15 @@ export const AIProjectGenerator: React.FC = ({ setProgressMessage('项目创建完成!正在跳转...'); message.success('项目创建成功!正在进入项目...'); clearStorage(); - + // 调用完成回调 onComplete(createdProjectId); - + // 延迟1秒后自动跳转到项目详情页 setTimeout(() => { navigate(`/project/${createdProjectId}`); }, 1000); - + } catch (error) { const apiError = error as ApiError; const errorMsg = apiError.response?.data?.detail || apiError.message || '未知错误'; @@ -634,11 +634,11 @@ export const AIProjectGenerator: React.FC = ({ setProgressMessage('项目创建完成!正在跳转...'); message.success('项目创建成功!正在进入项目...'); setLoading(false); - + // 调用完成回调 if (projectId) { onComplete(projectId); - + // 延迟1秒后自动跳转到项目详情页 setTimeout(() => { navigate(`/project/${projectId}`); @@ -733,11 +733,11 @@ export const AIProjectGenerator: React.FC = ({ setProgressMessage('项目创建完成!正在跳转...'); message.success('项目创建成功!正在进入项目...'); setLoading(false); - + // 调用完成回调 if (projectId) { onComplete(projectId); - + // 延迟1秒后自动跳转到项目详情页 setTimeout(() => { navigate(`/project/${projectId}`); @@ -748,15 +748,15 @@ export const AIProjectGenerator: React.FC = ({ // 获取步骤状态图标和颜色 const getStepStatus = (step: GenerationStep) => { - if (step === 'completed') return { icon: , color: '#52c41a' }; - if (step === 'processing') return { icon: , color: '#1890ff' }; - if (step === 'error') return { icon: '✗', color: '#ff4d4f' }; - return { icon: '○', color: '#d9d9d9' }; + if (step === 'completed') return { icon: , color: 'var(--color-success)' }; + if (step === 'processing') return { icon: , color: 'var(--color-primary)' }; + if (step === 'error') return { icon: '✗', color: 'var(--color-error)' }; + return { icon: '○', color: 'var(--color-text-quaternary)' }; }; const hasError = generationSteps.worldBuilding === 'error' || - generationSteps.characters === 'error' || - generationSteps.outline === 'error'; + generationSteps.characters === 'error' || + generationSteps.outline === 'error'; // 渲染生成进度页面 const renderGenerating = () => ( @@ -770,7 +770,7 @@ export const AIProjectGenerator: React.FC = ({ level={isMobile ? 4 : 3} style={{ marginBottom: 32, - color: '#fff', + color: 'var(--color-text-primary)', wordBreak: 'break-word', whiteSpace: 'normal', overflowWrap: 'break-word' @@ -784,8 +784,8 @@ export const AIProjectGenerator: React.FC = ({ percent={progress} status={hasError ? 'exception' : (progress === 100 ? 'success' : 'active')} strokeColor={{ - '0%': '#667eea', - '100%': '#764ba2', + '0%': 'var(--color-primary)', + '100%': 'var(--color-primary-active)', }} style={{ marginBottom: 24 }} /> @@ -794,7 +794,7 @@ export const AIProjectGenerator: React.FC = ({ style={{ fontSize: isMobile ? 14 : 16, marginBottom: 32, - color: hasError ? '#ff4d4f' : '#666', + color: hasError ? 'var(--color-error)' : 'var(--color-text-secondary)', wordBreak: 'break-word', whiteSpace: 'normal', overflowWrap: 'break-word' @@ -808,18 +808,18 @@ export const AIProjectGenerator: React.FC = ({ size="small" style={{ marginBottom: 24, - background: '#fff2f0', - borderColor: '#ffccc7', + background: 'var(--color-error-bg)', + borderColor: 'var(--color-error-border)', textAlign: 'left', maxWidth: '100%', overflow: 'hidden' }} > - 错误详情: + 错误详情:
= ({ alignItems: 'center', justifyContent: 'space-between', padding: isMobile ? '10px 12px' : '12px 20px', - background: step === 'processing' ? '#f0f5ff' : (step === 'error' ? '#fff2f0' : '#fafafa'), + background: step === 'processing' ? 'var(--color-info-bg)' : (step === 'error' ? 'var(--color-error-bg)' : 'var(--color-bg-layout)'), borderRadius: 8, - border: `1px solid ${step === 'processing' ? '#d6e4ff' : (step === 'error' ? '#ffccc7' : '#f0f0f0')}`, + border: `1px solid ${step === 'processing' ? 'var(--color-info-border)' : (step === 'error' ? 'var(--color-error-border)' : 'var(--color-border-secondary)')}`, gap: '8px', maxWidth: '100%', overflow: 'hidden' @@ -894,7 +894,7 @@ export const AIProjectGenerator: React.FC = ({ = ({ > {hasError ? '生成过程中出现错误,请点击重试按钮重新生成' : '请耐心等待,AI正在为您精心创作...'} - + {hasError && ( )} - + ); diff --git a/frontend/src/components/AnnouncementModal.tsx b/frontend/src/components/AnnouncementModal.tsx index b0811e5..3086392 100644 --- a/frontend/src/components/AnnouncementModal.tsx +++ b/frontend/src/components/AnnouncementModal.tsx @@ -56,7 +56,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday,

👋 欢迎加入我们的交流群!

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

    +

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

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

    +

    QQ交流群

    {!qqImageError ? ( @@ -102,7 +102,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday, display: 'flex', justifyContent: 'center', alignItems: 'center', - background: '#fff', + background: 'var(--color-bg-container)', borderRadius: '8px', padding: '8px', boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', @@ -128,7 +128,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday, display: 'flex', justifyContent: 'center', alignItems: 'center', - background: '#fff', + background: 'var(--color-bg-container)', borderRadius: '8px', color: '#999', }}> @@ -144,7 +144,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday, alignItems: 'center', minWidth: '280px', }}> -

    +

    微信交流群

    {!wxImageError ? ( @@ -152,7 +152,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday, display: 'flex', justifyContent: 'center', alignItems: 'center', - background: '#fff', + background: 'var(--color-bg-container)', borderRadius: '8px', padding: '8px', boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', @@ -178,7 +178,7 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday, display: 'flex', justifyContent: 'center', alignItems: 'center', - background: '#fff', + background: 'var(--color-bg-container)', borderRadius: '8px', color: '#999', }}> @@ -187,15 +187,15 @@ export default function AnnouncementModal({ visible, onClose, onDoNotShowToday, )}
    - +
    💡 提示:选择"今日内不再展示"当天不再显示,选择"永不再展示"将永久隐藏此公告
    diff --git a/frontend/src/components/AppFooter.tsx b/frontend/src/components/AppFooter.tsx index 97ddb6a..f8b1dcb 100644 --- a/frontend/src/components/AppFooter.tsx +++ b/frontend/src/components/AppFooter.tsx @@ -46,10 +46,11 @@ export default function AppFooter() { right: 0, backdropFilter: 'blur(20px) saturate(180%)', WebkitBackdropFilter: 'blur(20px) saturate(180%)', - borderTop: '1px solid rgba(255, 255, 255, 0.2)', + borderTop: '1px solid var(--color-border)', padding: isMobile ? '8px 12px' : '10px 16px', zIndex: 100, - boxShadow: '0 -4px 16px rgba(0, 0, 0, 0.1), 0 -1px 4px rgba(0, 0, 0, 0.06)', + boxShadow: 'var(--shadow-card)', + backgroundColor: 'rgba(255, 255, 255, 0.8)', // 半透明背景以支持 backdrop-filter }} >
    - {VERSION_INFO.projectName} + {VERSION_INFO.projectName} {getVersionString()} - + - + @@ -126,8 +123,7 @@ export default function AppFooter() { @@ -139,7 +135,7 @@ export default function AppFooter() { } + split={} style={{ display: 'flex', justifyContent: 'center', @@ -156,8 +152,8 @@ export default function AppFooter() { display: 'flex', alignItems: 'center', gap: 6, - color: '#fff', - textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)', + color: 'var(--color-text-secondary)', + textShadow: 'none', cursor: hasUpdate ? 'pointer' : 'default', transition: 'all 0.3s', }} @@ -172,7 +168,7 @@ export default function AppFooter() { } }} > - {VERSION_INFO.projectName} + {VERSION_INFO.projectName} {getVersionString()} @@ -188,8 +184,7 @@ export default function AppFooter() { display: 'flex', alignItems: 'center', gap: 6, - color: '#fff', - textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)', + color: 'var(--color-text-secondary)', }} > @@ -203,8 +198,7 @@ export default function AppFooter() { rel="noopener noreferrer" style={{ fontSize: 12, - color: '#fff', - textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)', + color: 'var(--color-text-secondary)', }} > LinuxDO 社区 @@ -216,9 +210,9 @@ export default function AppFooter() { icon={} onClick={() => window.open('https://mumuverse.space:1588/', '_blank')} style={{ - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', + background: 'var(--color-primary)', border: 'none', - boxShadow: '0 4px 12px rgba(102, 126, 234, 0.5)', + boxShadow: '0 4px 12px rgba(77, 128, 136, 0.3)', fontSize: 13, height: 32, padding: '0 20px', @@ -250,8 +244,7 @@ export default function AppFooter() { display: 'flex', alignItems: 'center', gap: 6, - color: '#fff', - textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)', + color: 'var(--color-text-secondary)', }} > @@ -265,8 +258,7 @@ export default function AppFooter() { display: 'flex', alignItems: 'center', gap: 4, - color: 'rgba(255, 255, 255, 0.9)', - textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)', + color: 'var(--color-text-tertiary)', }} > @@ -280,12 +272,12 @@ export default function AppFooter() { display: 'flex', alignItems: 'center', gap: 4, - color: '#fff', - textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)', + color: 'var(--color-text-secondary)', + textShadow: '0 1px 3px rgba(0, 0, 0, 0.05)', }} > Made with - + by {VERSION_INFO.author} diff --git a/frontend/src/components/CardStyles.tsx b/frontend/src/components/CardStyles.tsx index 6658a68..3c5a829 100644 --- a/frontend/src/components/CardStyles.tsx +++ b/frontend/src/components/CardStyles.tsx @@ -18,7 +18,7 @@ export const cardStyles = { // height: 320, display: 'flex', flexDirection: 'column', - borderColor: '#1890ff', + borderColor: 'var(--color-info)', borderRadius: 12, } as CSSProperties, @@ -27,19 +27,20 @@ export const cardStyles = { // height: 320, display: 'flex', flexDirection: 'column', - borderColor: '#52c41a', - backgroundColor: '#f6ffed', + borderColor: 'var(--color-success)', + backgroundColor: 'var(--color-bg-base)', // 使用柔和的背景色 borderRadius: 12, } as CSSProperties, - // 项目卡片样式 + // 项目卡片样式 - 现代化设计 project: { height: '100%', - borderRadius: 16, + borderRadius: 20, overflow: 'hidden', - background: '#fff', - boxShadow: '0 4px 16px rgba(0, 0, 0, 0.08)', - transition: 'all 0.3s ease', + background: 'var(--color-bg-container)', + boxShadow: '0 4px 20px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.04)', + transition: 'all 0.35s cubic-bezier(0.4, 0, 0.2, 1)', + border: '1px solid rgba(0, 0, 0, 0.04)', } as CSSProperties, // 卡片内容区域样式 @@ -73,17 +74,17 @@ export const cardStyles = { } as CSSProperties), }; -// 卡片悬浮动画 +// 卡片悬浮动画 - 增强版 export const cardHoverHandlers = { onMouseEnter: (e: React.MouseEvent) => { const target = e.currentTarget; - target.style.transform = 'translateY(-8px)'; - target.style.boxShadow = '0 12px 32px rgba(0, 0, 0, 0.15)'; + target.style.transform = 'translateY(-10px) scale(1.01)'; + target.style.boxShadow = '0 20px 40px rgba(77, 128, 136, 0.2), 0 8px 16px rgba(0, 0, 0, 0.08)'; }, onMouseLeave: (e: React.MouseEvent) => { const target = e.currentTarget; - target.style.transform = 'translateY(0)'; - target.style.boxShadow = '0 4px 16px rgba(0, 0, 0, 0.08)'; + target.style.transform = 'translateY(0) scale(1)'; + target.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.04)'; }, }; @@ -113,12 +114,12 @@ export const textStyles = { fontSize: 12, color: 'rgba(0, 0, 0, 0.45)', } as CSSProperties, - + value: { fontSize: 14, color: 'rgba(0, 0, 0, 0.85)', } as CSSProperties, - + description: { fontSize: 12, color: 'rgba(0, 0, 0, 0.45)', diff --git a/frontend/src/components/ChangelogModal.tsx b/frontend/src/components/ChangelogModal.tsx index eec22dd..9f1e7c6 100644 --- a/frontend/src/components/ChangelogModal.tsx +++ b/frontend/src/components/ChangelogModal.tsx @@ -16,10 +16,7 @@ import { import { fetchChangelog, groupChangelogByDate, - getCachedChangelog, cacheChangelog, - markChangelogFetched, - shouldFetchChangelog, clearChangelogCache, type ChangelogEntry, } from '../services/changelogService'; @@ -50,35 +47,15 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps const [hasMore, setHasMore] = useState(true); // 加载更新日志 + // 每次用户打开窗口时才同步获取最新数据,不自动刷新 const loadChangelog = async (pageNum: number = 1, append: boolean = false) => { setLoading(true); setError(null); try { - // 如果是第一页,先尝试使用缓存 - if (pageNum === 1 && !append) { - const cached = getCachedChangelog(); - if (cached && cached.length > 0) { - setChangelog(cached); - - // 后台刷新 - if (shouldFetchChangelog()) { - fetchChangelog(pageNum, 30) - .then(entries => { - setChangelog(entries); - cacheChangelog(entries); - markChangelogFetched(); - }) - .catch(console.error); - } - - setLoading(false); - return; - } - } - + // 每次打开都从网络获取最新数据 const entries = await fetchChangelog(pageNum, 30); - + if (entries.length === 0) { setHasMore(false); } else { @@ -86,10 +63,9 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps setChangelog(prev => [...prev, ...entries]); } else { setChangelog(entries); - // 缓存第一页数据 + // 缓存第一页数据(用于分页加载时的数据持久化) if (pageNum === 1) { cacheChangelog(entries); - markChangelogFetched(); } } } @@ -137,7 +113,7 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps if (diffDays === 0) return '今天'; if (diffDays === 1) return '昨天'; if (diffDays < 7) return `${diffDays} 天前`; - + return date.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' }); }; @@ -180,10 +156,10 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps
    {error}
    @@ -199,16 +175,16 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps <> {sortedDates.map(date => { const entries = groupedChangelog.get(date) || []; - + return (
    {formatDate(date)} @@ -217,7 +193,7 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps {entries.map(entry => { const config = typeConfig[entry.type] || typeConfig.other; - + return ( {entry.scope} )} - + {formatTime(entry.date)} @@ -254,7 +230,7 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps marginTop: '8px', fontSize: '14px', lineHeight: '1.6', - color: '#333', + color: 'var(--color-text-primary)', }}> {entry.message}
    @@ -263,7 +239,7 @@ export default function ChangelogModal({ visible, onClose }: ChangelogModalProps {entry.author.avatar && ( )} - + {entry.author.username || entry.author.name} - -
    - )} + { + hasMore && ( +
    + +
    + ) + } - {!hasMore && changelog.length > 0 && ( -
    - 已显示所有更新日志 -
    - )} + { + !hasMore && changelog.length > 0 && ( +
    + 已显示所有更新日志 +
    + ) + } )}
    - 💡 提示:更新日志每小时自动刷新一次,数据来源于 GitHub 提交历史 + 💡 提示:每次打开窗口时自动获取最新更新日志,数据来源于 GitHub 提交历史
    - + ); } \ No newline at end of file diff --git a/frontend/src/components/ChapterAnalysis.tsx b/frontend/src/components/ChapterAnalysis.tsx index 32337a7..2fee5fd 100644 --- a/frontend/src/components/ChapterAnalysis.tsx +++ b/frontend/src/components/ChapterAnalysis.tsx @@ -42,14 +42,14 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter if (visible && chapterId) { fetchAnalysisStatus(); } - + // 监听窗口大小变化 const handleResize = () => { setIsMobile(isMobileDevice()); }; - + window.addEventListener('resize', handleResize); - + // 清理函数:组件卸载或关闭时清除轮询 return () => { window.removeEventListener('resize', handleResize); @@ -79,33 +79,33 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter try { setLoading(true); setError(null); - + // 🔧 使用独立的章节加载函数 await loadChapterInfo(); - + const response = await fetch(`/api/chapters/${chapterId}/analysis/status`); - + if (response.status === 404) { setTask(null); setError('该章节还未进行分析'); return; } - + if (!response.ok) { throw new Error('获取分析状态失败'); } - + const taskData: AnalysisTask = await response.json(); - + // 如果状态为 none(无任务),设置 task 为 null,让前端显示"开始分析"按钮 if (taskData.status === 'none' || !taskData.has_task) { setTask(null); setError(null); // 清除错误,这不是错误状态 return; } - + setTask(taskData); - + if (taskData.status === 'completed') { await fetchAnalysisResult(); } else if (taskData.status === 'running' || taskData.status === 'pending') { @@ -137,10 +137,10 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter try { const response = await fetch(`/api/chapters/${chapterId}/analysis/status`); if (!response.ok) return; - + const taskData: AnalysisTask = await response.json(); setTask(taskData); - + if (taskData.status === 'completed') { clearInterval(pollInterval); await fetchAnalysisResult(); @@ -163,19 +163,19 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter try { setLoading(true); setError(null); - + // 🔧 触发分析前先刷新章节内容,确保分析的是最新内容 await loadChapterInfo(); - + const response = await fetch(`/api/chapters/${chapterId}/analyze`, { method: 'POST' }); - + if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || '触发分析失败'); } - + // 触发成功后立即关闭Modal,让父组件的状态管理接管 onClose(); } catch (err) { @@ -188,16 +188,16 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter const renderStatusIcon = () => { if (!task) return null; - + switch (task.status) { case 'pending': - return ; + return ; case 'running': return ; case 'completed': - return ; + return ; case 'failed': - return ; + return ; default: return null; } @@ -205,7 +205,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter const renderProgress = () => { if (!task || task.status === 'completed') return null; - + return (
    {task.status === 'pending' && '等待分析...'} {task.status === 'running' && 'AI正在分析中...'} @@ -241,7 +241,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter }}>
    - + {/* 进度百分比 */}
    {task.progress}% @@ -279,7 +279,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
    @@ -307,7 +307,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
    分析过程需要一定时间,请耐心等待 @@ -320,7 +320,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter // 将分析建议转换为重新生成组件需要的格式 const convertSuggestionsForRegeneration = () => { if (!analysis?.analysis?.suggestions) return []; - + return analysis.analysis.suggestions.map((suggestion, index) => ({ category: '改进建议', content: suggestion, @@ -330,9 +330,9 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter const renderAnalysisResult = () => { if (!analysis) return null; - + const { analysis: analysis_data, memories } = analysis; - + return ( )} - + @@ -374,7 +374,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter title="整体质量" value={analysis_data.overall_quality_score || 0} suffix="/ 10" - valueStyle={{ color: '#3f8600' }} + valueStyle={{ color: 'var(--color-success)' }} /> @@ -400,7 +400,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter - + {analysis_data.analysis_report && (
    @@ -408,7 +408,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter
                         
    )} - + {analysis_data.suggestions && analysis_data.suggestions.length > 0 && ( 改进建议} size={isMobile ? 'small' : 'default'}> - {analysis_data.hooks && analysis_data.hooks.length > 0 ? ( - ( - - - {hook.type} - {hook.position} - 强度: {hook.strength}/10 -
    - } - description={hook.content} - /> - - )} - /> - ) : ( - - )} + {analysis_data.hooks && analysis_data.hooks.length > 0 ? ( + ( + + + {hook.type} + {hook.position} + 强度: {hook.strength}/10 +
    + } + description={hook.content} + /> + + )} + /> + ) : ( + + )}
    ) @@ -463,32 +463,32 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter children: (
    - {analysis_data.foreshadows && analysis_data.foreshadows.length > 0 ? ( - ( - - - - {foreshadow.type === 'planted' ? '已埋下' : '已回收'} - - 强度: {foreshadow.strength}/10 - 隐藏度: {foreshadow.subtlety}/10 - {foreshadow.reference_chapter && ( - 呼应第{foreshadow.reference_chapter}章 - )} -
    - } - description={foreshadow.content} - /> - - )} - /> - ) : ( - - )} + {analysis_data.foreshadows && analysis_data.foreshadows.length > 0 ? ( + ( + + + + {foreshadow.type === 'planted' ? '已埋下' : '已回收'} + + 强度: {foreshadow.strength}/10 + 隐藏度: {foreshadow.subtlety}/10 + {foreshadow.reference_chapter && ( + 呼应第{foreshadow.reference_chapter}章 + )} +
    + } + description={foreshadow.content} + /> + + )} + /> + ) : ( + + )}
    ) @@ -500,41 +500,41 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter children: (
    - {analysis_data.emotional_tone ? ( -
    - - - - - - - - - -

    阶段:{analysis_data.plot_stage}

    -

    冲突等级:{analysis_data.conflict_level} / 10

    - {analysis_data.conflict_types && analysis_data.conflict_types.length > 0 && ( -
    - 冲突类型: - {analysis_data.conflict_types.map((type, idx) => ( - - {type} - - ))} -
    - )} -
    -
    - ) : ( - - )} + {analysis_data.emotional_tone ? ( +
    + + + + + + + + + +

    阶段:{analysis_data.plot_stage}

    +

    冲突等级:{analysis_data.conflict_level} / 10

    + {analysis_data.conflict_types && analysis_data.conflict_types.length > 0 && ( +
    + 冲突类型: + {analysis_data.conflict_types.map((type, idx) => ( + + {type} + + ))} +
    + )} +
    +
    + ) : ( + + )}
    ) @@ -546,37 +546,37 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter children: (
    - {analysis_data.character_states && analysis_data.character_states.length > 0 ? ( - ( - - -

    状态变化:{char.state_before} → {char.state_after}

    -

    心理变化:{char.psychological_change}

    -

    关键事件:{char.key_event}

    - {char.relationship_changes && Object.keys(char.relationship_changes).length > 0 && ( -
    - 关系变化: - {Object.entries(char.relationship_changes).map(([name, change]) => ( - - 与{name}: {change} - - ))} -
    - )} -
    -
    - )} - /> - ) : ( - - )} + {analysis_data.character_states && analysis_data.character_states.length > 0 ? ( + ( + + +

    状态变化:{char.state_before} → {char.state_after}

    +

    心理变化:{char.psychological_change}

    +

    关键事件:{char.key_event}

    + {char.relationship_changes && Object.keys(char.relationship_changes).length > 0 && ( +
    + 关系变化: + {Object.entries(char.relationship_changes).map(([name, change]) => ( + + 与{name}: {change} + + ))} +
    + )} +
    +
    + )} + /> + ) : ( + + )}
    ) @@ -588,38 +588,38 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter children: (
    - {memories && memories.length > 0 ? ( - ( - - - {memory.type} - 重要性: {memory.importance.toFixed(1)} - {memory.is_foreshadow === 1 && 已埋下伏笔} - {memory.is_foreshadow === 2 && 已回收伏笔} - {memory.title} -
    - } - description={ -
    -

    {memory.content}

    + {memories && memories.length > 0 ? ( + ( + + - {memory.tags.map((tag, idx) => ( - {tag} - ))} + {memory.type} + 重要性: {memory.importance.toFixed(1)} + {memory.is_foreshadow === 1 && 已埋下伏笔} + {memory.is_foreshadow === 2 && 已回收伏笔} + {memory.title}
    - - } - /> - - )} - /> - ) : ( - - )} + } + description={ +
    +

    {memory.content}

    +
    + {memory.tags.map((tag, idx) => ( + {tag} + ))} +
    +
    + } + /> + + )} + /> + ) : ( + + )} ) @@ -700,7 +700,7 @@ export default function ChapterAnalysis({ chapterId, visible, onClose }: Chapter

    加载中...

    )} - + {error && ( )} - + {task && task.status !== 'completed' && renderProgress()} {task && task.status === 'completed' && analysis && renderAnalysisResult()} - + {/* 重新生成Modal */} {chapterInfo && ( )} - + {/* 内容对比组件 */} {chapterInfo && comparisonModalVisible && ( = ({ } message.success('新内容已应用!'); - + // 先调用 onApply 通知父组件刷新 onApply(); - + // 延迟触发章节分析,给父组件时间刷新 setTimeout(async () => { try { @@ -75,7 +75,7 @@ const ChapterContentComparison: React.FC = ({ message.warning('章节分析触发失败,您可以手动触发分析'); } }, 500); - + onClose(); } catch (error: any) { message.error(error.message || '应用失败'); @@ -108,9 +108,9 @@ const ChapterContentComparison: React.FC = ({ centered style={{ maxWidth: 1600 }} footer={[ - , - -
    + +
    ✨ 灵感模式 通过对话快速创建你的小说项目
    -
    +
    + + + +
    {(currentStep === 'idea' || currentStep === 'title' || currentStep === 'description' || currentStep === 'theme' || currentStep === 'genre' || currentStep === 'perspective' || currentStep === 'outline_mode' || currentStep === 'confirm') && renderChat()} @@ -950,7 +971,7 @@ const Inspiration: React.FC = () => { storagePrefix="inspiration" onComplete={handleComplete} onBack={handleBackToChat} - isMobile={window.innerWidth <= 768} + isMobile={isMobile} /> )}
    @@ -959,4 +980,3 @@ const Inspiration: React.FC = () => { }; export default Inspiration; - \ No newline at end of file diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 42255ac..16517f5 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -46,15 +46,15 @@ export default function Login() { try { setLoading(true); const response = await authApi.localLogin(values.username, values.password); - + if (response.success) { message.success('登录成功!'); - + // 检查是否永久隐藏公告 const hideForever = localStorage.getItem('announcement_hide_forever'); const hideToday = localStorage.getItem('announcement_hide_today'); const today = new Date().toDateString(); - + // 如果永久隐藏或今日已隐藏,则不显示公告 if (hideForever === 'true' || hideToday === today) { const redirect = searchParams.get('redirect') || '/'; @@ -73,13 +73,13 @@ export default function Login() { try { setLoading(true); const response = await authApi.getLinuxDOAuthUrl(); - + // 保存重定向地址到 sessionStorage const redirect = searchParams.get('redirect'); if (redirect) { sessionStorage.setItem('login_redirect', redirect); } - + // 跳转到 LinuxDO 授权页面 window.location.href = response.auth_url; } catch (error) { @@ -96,9 +96,9 @@ export default function Login() { justifyContent: 'center', alignItems: 'center', minHeight: '100vh', - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', + background: 'var(--color-bg-base)', }}> - + ); } @@ -141,10 +141,10 @@ export default function Login() { height: 48, fontSize: 16, fontWeight: 600, - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', + background: 'var(--color-primary)', border: 'none', borderRadius: '12px', - boxShadow: '0 4px 16px rgba(102, 126, 234, 0.4)', + boxShadow: 'var(--shadow-primary)', }} > 登录 @@ -178,19 +178,19 @@ export default function Login() { height: 52, fontSize: 16, fontWeight: 600, - background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', + background: 'var(--color-primary)', border: 'none', borderRadius: '12px', - boxShadow: '0 4px 16px rgba(102, 126, 234, 0.4)', + boxShadow: 'var(--shadow-primary)', transition: 'all 0.3s ease', }} onMouseEnter={(e) => { e.currentTarget.style.transform = 'translateY(-2px)'; - e.currentTarget.style.boxShadow = '0 6px 24px rgba(102, 126, 234, 0.5)'; + e.currentTarget.style.boxShadow = 'var(--shadow-elevated)'; }} onMouseLeave={(e) => { e.currentTarget.style.transform = 'translateY(0)'; - e.currentTarget.style.boxShadow = '0 4px 16px rgba(102, 126, 234, 0.4)'; + e.currentTarget.style.boxShadow = 'var(--shadow-primary)'; }} > 使用 LinuxDO 登录 @@ -224,144 +224,143 @@ export default function Login() { onNeverShow={handleNeverShow} />
    - {/* 装饰性背景元素 */} -
    -
    - - - - {/* Logo区域 */} -
    -
    - Logo + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + minHeight: '100vh', + background: 'var(--color-bg-base)', + padding: '20px', + position: 'relative', + overflow: 'hidden', + }}> + {/* 装饰性背景元素 */} +
    +
    + + + + {/* Logo区域 */} +
    +
    + Logo +
    + + AI小说创作助手 + + + {localAuthEnabled && linuxdoEnabled ? '选择登录方式' : + localAuthEnabled ? '使用账户密码登录' : + '使用 LinuxDO 账号登录'} +
    - - AI小说创作助手 - - - {localAuthEnabled && linuxdoEnabled ? '选择登录方式' : - localAuthEnabled ? '使用账户密码登录' : - '使用 LinuxDO 账号登录'} - -
    - {/* 登录方式 */} - {localAuthEnabled && linuxdoEnabled ? ( - - ) : localAuthEnabled ? ( - renderLocalLogin() - ) : ( - renderLinuxDOLogin() - )} + {/* 登录方式 */} + {localAuthEnabled && linuxdoEnabled ? ( + + ) : localAuthEnabled ? ( + renderLocalLogin() + ) : ( + renderLinuxDOLogin() + )} - {/* 提示信息 */} -
    - - 🎉 首次登录将自动创建账号 -
    - 🔒 每个用户拥有独立的数据空间 -
    -
    - - -
    + + 🎉 首次登录将自动创建账号 +
    + 🔒 每个用户拥有独立的数据空间 +
    +
    + + +
    ); } \ No newline at end of file diff --git a/frontend/src/pages/MCPPlugins.tsx b/frontend/src/pages/MCPPlugins.tsx index ed771a7..d63b8d5 100644 --- a/frontend/src/pages/MCPPlugins.tsx +++ b/frontend/src/pages/MCPPlugins.tsx @@ -17,7 +17,8 @@ import { Empty, Alert, Descriptions, - Layout, + Row, + Col, } from 'antd'; import { PlusOutlined, @@ -35,7 +36,6 @@ import type { MCPPlugin, MCPTool } from '../types'; const { Paragraph, Text, Title } = Typography; const { TextArea } = Input; -const { Header, Content } = Layout; export default function MCPPluginsPage() { const navigate = useNavigate(); @@ -85,7 +85,7 @@ export default function MCPPluginsPage() { const handleEdit = (plugin: MCPPlugin) => { setEditingPlugin(plugin); - + // 重构为标准MCP配置格式 const mcpConfig: any = { mcpServers: { @@ -94,7 +94,7 @@ export default function MCPPluginsPage() { } } }; - + if (plugin.plugin_type === 'http') { mcpConfig.mcpServers[plugin.plugin_name].url = plugin.server_url; mcpConfig.mcpServers[plugin.plugin_name].headers = plugin.headers || {}; @@ -103,7 +103,7 @@ export default function MCPPluginsPage() { mcpConfig.mcpServers[plugin.plugin_name].args = plugin.args || []; mcpConfig.mcpServers[plugin.plugin_name].env = plugin.env || {}; } - + form.setFieldsValue({ config_json: JSON.stringify(mcpConfig, null, 2), enabled: plugin.enabled, @@ -145,10 +145,10 @@ export default function MCPPluginsPage() { setTestingPluginId(pluginId); try { const result = await mcpPluginApi.testPlugin(pluginId); - + // 测试完成后,无论成功失败都刷新插件列表以更新状态 await loadPlugins(); - + if (result.success) { Modal.success({ title: '✅ 测试成功', @@ -156,7 +156,7 @@ export default function MCPPluginsPage() { content: (

    {result.message}

    - + {/* 显示详细的测试结果 */} {result.suggestions && result.suggestions.length > 0 && (
    @@ -184,21 +184,21 @@ export default function MCPPluginsPage() {
    )} - + {/* 显示工具数量 */} {result.tools_count !== undefined && (
    🔧 可用工具数: {result.tools_count}
    )} - + {/* 显示响应时间 */} {result.response_time_ms !== undefined && (
    ⏱️ 响应时间: {result.response_time_ms}ms
    )} - +

    {result.message}

    - + {/* 显示错误信息 */} {result.error && (
    @@ -228,9 +228,9 @@ export default function MCPPluginsPage() {
    )} - + {/* 显示建议 */} {result.suggestions && result.suggestions.length > 0 && (
    @@ -256,12 +256,12 @@ export default function MCPPluginsPage() {
    )} - +
    @@ -340,267 +340,293 @@ export default function MCPPluginsPage() { }; return ( - - {/* 顶部导航栏 */} -
    - - - - MCP插件管理 - - -
    - - {/* 主内容区 */} - +
    + {/* 顶部导航卡片 */} -
    - - 我的插件 - - -
    + {/* 装饰性背景元素 */} +
    +
    +
    + + + + + + <ToolOutlined style={{ color: 'rgba(255,255,255,0.9)', marginRight: 8 }} /> + MCP插件管理 + + + + 扩展AI能力,连接外部工具与服务 + + + + + + + + + + + + {/* 使用提示 */} + + 什么是 MCP 插件? + + } description={ -
    -

    - MCP (Model Context Protocol) 是一个标准化的协议,允许 AI 调用外部工具获取数据。 -

    -

    - 通过添加 MCP 插件,AI 可以访问搜索引擎、数据库、API 等外部服务,增强创作能力。 -

    +
    + + • MCP (Model Context Protocol) 是一个标准化的协议,允许 AI 调用外部工具获取数据。 + + + • 通过添加 MCP 插件,AI 可以访问搜索引擎、数据库、API 等外部服务,增强创作能力。 +
    } type="info" - showIcon - icon={} - style={{ marginBottom: isMobile ? 16 : 20 }} + showIcon={false} + style={{ + marginTop: isMobile ? 16 : 24, + borderRadius: 12, + background: 'rgba(230, 247, 255, 0.6)', + border: '1px solid rgba(145, 213, 255, 0.6)', + backdropFilter: 'blur(5px)' + }} /> - {/* 插件列表 */} - - {plugins.length === 0 ? ( - - - - ) : ( - - {plugins.map((plugin) => ( - -
    + + {/* 插件列表 */} + + {plugins.length === 0 ? ( + + + + ) : ( + + {plugins.map((plugin) => ( + -
    - -
    - - {plugin.display_name || plugin.plugin_name} - - {getStatusTag(plugin)} - - {plugin.plugin_type?.toUpperCase() || 'UNKNOWN'} - - {plugin.category && plugin.category !== 'general' && ( - {plugin.category} +
    +
    + +
    + + {plugin.display_name || plugin.plugin_name} + + {getStatusTag(plugin)} + + {plugin.plugin_type?.toUpperCase() || 'UNKNOWN'} + + {plugin.category && plugin.category !== 'general' && ( + {plugin.category} + )} +
    + {plugin.description && ( + + {plugin.description} + )} -
    - {plugin.description && ( - + + {(() => { + // 脱敏处理:隐藏URL中的API Key + const url = plugin.server_url; + try { + const urlObj = new URL(url); + // 替换查询参数中的敏感信息 + const params = new URLSearchParams(urlObj.search); + let maskedUrl = `${urlObj.protocol}//${urlObj.host}${urlObj.pathname}`; + + const sensitiveKeys = ['apiKey', 'api_key', 'key', 'token', 'secret', 'password', 'auth']; + let hasParams = false; + + params.forEach((value, key) => { + const isSensitive = sensitiveKeys.some(k => key.toLowerCase().includes(k.toLowerCase())); + const maskedValue = isSensitive ? '***' : value; + maskedUrl += (hasParams ? '&' : '?') + `${key}=${maskedValue}`; + hasParams = true; + }); + + return maskedUrl; + } catch { + // 如果URL解析失败,尝试简单替换 + return url.replace(/([?&])(apiKey|api_key|key|token|secret|password|auth)=([^&]+)/gi, '$1$2=***'); + } + })()} + +
    + )} + + {plugin.plugin_type === 'stdio' && plugin.command && ( +
    + + {plugin.command} {plugin.args?.join(' ')} + +
    + )} + + {/* 显示最后错误信息 */} + {plugin.last_error && ( + + 错误: {plugin.last_error} + + )} + +
    + + + + handleToggle(plugin, checked)} + size={isMobile ? 'small' : 'default'} style={{ - margin: 0, - fontSize: isMobile ? '12px' : '13px', + flexShrink: 0, + height: isMobile ? 16 : 22, + minHeight: isMobile ? 16 : 22, + lineHeight: isMobile ? '16px' : '22px' }} - ellipsis={{ rows: 2 }} - > - {plugin.description} - - )} - - {/* 只显示有值的URL或命令,脱敏处理敏感信息 */} - {plugin.plugin_type === 'http' && plugin.server_url && ( -
    - - {(() => { - // 脱敏处理:隐藏URL中的API Key - const url = plugin.server_url; - try { - const urlObj = new URL(url); - // 替换查询参数中的敏感信息 - const params = new URLSearchParams(urlObj.search); - let maskedUrl = `${urlObj.protocol}//${urlObj.host}${urlObj.pathname}`; - - const sensitiveKeys = ['apiKey', 'api_key', 'key', 'token', 'secret', 'password', 'auth']; - let hasParams = false; - - params.forEach((value, key) => { - const isSensitive = sensitiveKeys.some(k => key.toLowerCase().includes(k.toLowerCase())); - const maskedValue = isSensitive ? '***' : value; - maskedUrl += (hasParams ? '&' : '?') + `${key}=${maskedValue}`; - hasParams = true; - }); - - return maskedUrl; - } catch { - // 如果URL解析失败,尝试简单替换 - return url.replace(/([?&])(apiKey|api_key|key|token|secret|password|auth)=([^&]+)/gi, '$1$2=***'); - } - })()} - -
    - )} - - {plugin.plugin_type === 'stdio' && plugin.command && ( -
    - - {plugin.command} {plugin.args?.join(' ')} - -
    - )} - - {/* 显示最后错误信息 */} - {plugin.last_error && ( - - 错误: {plugin.last_error} - - )} + /> +
    + +
    - - - - handleToggle(plugin, checked)} - size={isMobile ? 'small' : 'default'} - style={{ - flexShrink: 0, - height: isMobile ? 16 : 22, - minHeight: isMobile ? 16 : 22, - lineHeight: isMobile ? '16px' : '22px' - }} - /> - - -
    -
    - ))} -
    - )} -
    - + + ))} + + )} + +
    +
    {/* 创建/编辑插件模态框 */} )} - +
    ); } \ No newline at end of file diff --git a/frontend/src/pages/Organizations.tsx b/frontend/src/pages/Organizations.tsx index ea02dbc..ddb6b0e 100644 --- a/frontend/src/pages/Organizations.tsx +++ b/frontend/src/pages/Organizations.tsx @@ -54,6 +54,7 @@ export default function Organizations() { const [editMemberForm] = Form.useForm(); const [editOrgForm] = Form.useForm(); const [isMobile, setIsMobile] = useState(window.innerWidth <= 768); + const [modal, contextHolder] = Modal.useModal(); useEffect(() => { const handleResize = () => { @@ -129,7 +130,7 @@ export default function Organizations() { }; const handleRemoveMember = async (memberId: string) => { - Modal.confirm({ + modal.confirm({ title: '确认移除', content: '确定要移除该成员吗?', centered: true, @@ -294,6 +295,7 @@ export default function Organizations() { return (
    + {contextHolder} @@ -326,7 +328,7 @@ export default function Organizations() { hoverable style={{ cursor: 'pointer', - border: selectedOrg?.id === org.id ? '2px solid #1890ff' : '1px solid #d9d9d9' + border: selectedOrg?.id === org.id ? '2px solid var(--color-primary)' : '1px solid var(--color-border-secondary)' }} onClick={() => handleSelectOrganization(org)} > diff --git a/frontend/src/pages/Outline.tsx b/frontend/src/pages/Outline.tsx index 517124d..5818c30 100644 --- a/frontend/src/pages/Outline.tsx +++ b/frontend/src/pages/Outline.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; import { Button, List, Modal, Form, Input, message, Empty, Space, Popconfirm, Card, Select, Radio, Tag, InputNumber, Tooltip, Tabs } from 'antd'; import { EditOutlined, DeleteOutlined, ThunderboltOutlined, BranchesOutlined, AppstoreAddOutlined, CheckCircleOutlined, ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { useStore } from '../store'; @@ -41,6 +41,7 @@ export default function Outline() { const [editForm] = Form.useForm(); const [generateForm] = Form.useForm(); const [expansionForm] = Form.useForm(); + const [modalApi, contextHolder] = Modal.useModal(); const [batchExpansionForm] = Form.useForm(); const [manualCreateForm] = Form.useForm(); const [isMobile, setIsMobile] = useState(window.innerWidth <= 768); @@ -135,7 +136,7 @@ export default function Outline() { const outline = outlines.find(o => o.id === id); if (outline) { editForm.setFieldsValue(outline); - Modal.confirm({ + modalApi.confirm({ title: '编辑大纲', width: 600, centered: true, @@ -335,7 +336,7 @@ export default function Outline() { } } - Modal.confirm({ + modalApi.confirm({ title: hasOutlines ? ( AI生成/续写大纲 @@ -504,7 +505,7 @@ export default function Outline() { console.log('已同步到Form,当前Form值:', generateForm.getFieldsValue()); }} /> -
    +
    {defaultModel ? `当前默认模型: ${loadedModels.find(m => m.value === defaultModel)?.label || defaultModel}` : '未配置默认模型'}
    @@ -526,7 +527,7 @@ export default function Outline() { ? Math.max(...outlines.map(o => o.order_index)) + 1 : 1; - Modal.confirm({ + modalApi.confirm({ title: '手动创建大纲', width: 600, centered: true, @@ -574,26 +575,26 @@ export default function Outline() { // 校验序号是否重复 const existingOutline = outlines.find(o => o.order_index === values.order_index); if (existingOutline) { - Modal.warning({ + modalApi.warning({ title: '序号冲突', content: (

    序号 {values.order_index} 已被使用:

    -
    +
    {currentProject?.outline_mode === 'one-to-one' ? `第${existingOutline.order_index}章` : `第${existingOutline.order_index}卷` }:{existingOutline.title}
    -

    +

    💡 建议使用序号 {nextOrderIndex},或选择其他未使用的序号

    @@ -644,7 +645,7 @@ export default function Outline() { if (!prevChapters.has_chapters) { // 如果前面有未展开的大纲,显示提示并阻止操作 setIsExpanding(false); - Modal.warning({ + modalApi.warning({ title: '请按顺序展开大纲', width: 600, centered: true, @@ -655,18 +656,18 @@ export default function Outline() {

    -
    +
    ⚠️ 需要先展开:
    -
    +
    第{prevOutline.order_index}卷:《{prevOutline.title}》
    -

    +

    💡 提示:您也可以使用「批量展开」功能,系统会自动按顺序处理所有大纲。

    @@ -694,7 +695,7 @@ export default function Outline() { // 如果没有章节,显示展开表单 setIsExpanding(false); - Modal.confirm({ + modalApi.confirm({ title: ( @@ -705,9 +706,9 @@ export default function Outline() { centered: true, content: (
    -
    +
    大纲标题
    -
    {outlineTitle}
    +
    {outlineTitle}
    | null; } ) => { - const modal = Modal.info({ + modalApi.info({ title: ( - + 已存在的展开章节 ), @@ -876,20 +877,20 @@ export default function Outline() { overflowY: 'auto' } }, - footer: (_, { OkBtn }) => ( + footer: (_: any, { OkBtn }: any) => (
    ) }))} /> -
    +
    ), }); }; @@ -1093,10 +1095,10 @@ export default function Outline() { // 缓存AI生成的规划数据 const cachedPlans = response.chapter_plans; - Modal.confirm({ + modalApi.confirm({ title: ( - + 展开规划预览 ), @@ -1222,7 +1224,7 @@ export default function Outline() { return; } - Modal.confirm({ + modalApi.confirm({ title: ( @@ -1233,7 +1235,7 @@ export default function Outline() { centered: true, content: (
    -
    +
    ⚠️ 将对当前项目的所有 {outlines.length} 个大纲进行展开
    @@ -1361,11 +1363,11 @@ export default function Outline() {
    -
    +
    ⚠️ 以下大纲已展开过,已自动跳过:
    @@ -1404,7 +1406,7 @@ export default function Outline() { background: selectedOutlineIdx === idx ? '#e6f7ff' : 'transparent', borderRadius: 4, marginBottom: 4, - border: selectedOutlineIdx === idx ? '1px solid #1890ff' : '1px solid transparent' + border: selectedOutlineIdx === idx ? '1px solid var(--color-primary)' : '1px solid transparent' }} >
    @@ -1445,7 +1447,7 @@ export default function Outline() { background: selectedChapterIdx === idx ? '#e6f7ff' : 'transparent', borderRadius: 4, marginBottom: 4, - border: selectedChapterIdx === idx ? '1px solid #1890ff' : '1px solid transparent' + border: selectedChapterIdx === idx ? '1px solid var(--color-primary)' : '1px solid transparent' }} >
    @@ -1719,7 +1721,7 @@ export default function Outline() { - + 确认引入新角色 } @@ -1731,7 +1733,7 @@ export default function Outline() { handleConfirmCharacters(selectedCharacters); }} onCancel={() => { - Modal.confirm({ + modalApi.confirm({ title: '确认操作', content: '是否跳过角色创建,直接续写大纲?', okText: '跳过角色,继续续写', @@ -1745,7 +1747,7 @@ export default function Outline() { cancelText="跳过角色创建" >
    -
    +
    AI 分析结果
    @@ -1785,7 +1787,7 @@ export default function Outline() { padding: 12, borderRadius: 4, marginBottom: 8, - border: selectedCharacterIndices.includes(index) ? '1px solid #1890ff' : '1px solid #f0f0f0', + border: selectedCharacterIndices.includes(index) ? '1px solid var(--color-primary)' : '1px solid var(--color-border-secondary)', cursor: 'pointer' }} onClick={() => { @@ -1859,7 +1861,7 @@ export default function Outline() { - + 批量展开规划预览 } @@ -1875,6 +1877,7 @@ export default function Outline() { {renderBatchPreviewContent()} + {contextHolder} {/* SSE进度Modal - 使用统一组件 */} - + {currentProject?.outline_mode === 'one-to-one' ? `第${item.order_index || '?'}章` : `第${item.order_index || '?'}卷` @@ -2058,4 +2061,4 @@ export default function Outline() {
    ); -} \ No newline at end of file +} diff --git a/frontend/src/pages/ProjectDetail.tsx b/frontend/src/pages/ProjectDetail.tsx index 31bf035..9dc02e7 100644 --- a/frontend/src/pages/ProjectDetail.tsx +++ b/frontend/src/pages/ProjectDetail.tsx @@ -193,7 +193,7 @@ export default function ProjectDetail() { return (
    @@ -279,7 +279,7 @@ export default function ProjectDetail() { 大纲} + title={大纲} value={outlines.length} suffix="条" - valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#667eea' }} + valueStyle={{ fontSize: '16px', fontWeight: 600, color: 'var(--color-primary)' }} /> @@ -300,7 +300,7 @@ export default function ProjectDetail() { 角色} + title={角色} value={characters.length} suffix="个" - valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#52c41a' }} + valueStyle={{ fontSize: '16px', fontWeight: 600, color: 'var(--color-success)' }} /> @@ -321,7 +321,7 @@ export default function ProjectDetail() { 章节} + title={章节} value={chapters.length} suffix="章" - valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#1890ff' }} + valueStyle={{ fontSize: '16px', fontWeight: 600, color: 'var(--color-info)' }} /> @@ -342,7 +342,7 @@ export default function ProjectDetail() { 已写} + title={已写} value={currentProject.current_words} suffix="字" - valueStyle={{ fontSize: '16px', fontWeight: 600, color: '#fa8c16' }} + valueStyle={{ fontSize: '16px', fontWeight: 600, color: 'var(--color-warning)' }} /> @@ -384,15 +384,14 @@ export default function ProjectDetail() { trigger={null} width={220} collapsedWidth={60} + className="modern-sider" style={{ - background: '#fff', position: 'fixed', left: 0, top: 70, bottom: 0, overflow: 'hidden', - boxShadow: '2px 0 12px rgba(0,0,0,0.08)', - transition: 'all 0.2s', + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', height: 'calc(100vh - 70px)' }} > @@ -412,7 +411,7 @@ export default function ProjectDetail() { }}>
    { const isMobile = window.innerWidth <= 768; - Modal.confirm({ + modal.confirm({ title: '确认删除', content: '删除项目将同时删除所有相关数据,此操作不可恢复。确定要删除吗?', okText: '确定', @@ -96,12 +97,12 @@ export default function ProjectList() { try { const values = await editForm.validateFields(); setUpdating(true); - + await projectApi.updateProject(editingProject.id, { description: values.description, target_words: values.target_words, }); - + message.success('项目更新成功'); handleCloseEditModal(); await refreshProjects(); @@ -159,7 +160,7 @@ export default function ProjectList() { const now = new Date(); const diff = now.getTime() - date.getTime(); const days = Math.floor(diff / (1000 * 60 * 60 * 24)); - + if (days === 0) return '今天'; if (days === 1) return '昨天'; if (days < 7) return `${days}天前`; @@ -174,13 +175,13 @@ export default function ProjectList() { const handleFileSelect = async (file: File) => { setSelectedFile(file); setValidationResult(null); - + // 验证文件 try { setValidating(true); const result = await projectApi.validateImportFile(file); setValidationResult(result); - + if (!result.valid) { message.error('文件验证失败'); } @@ -190,7 +191,7 @@ export default function ProjectList() { } finally { setValidating(false); } - + return false; // 阻止自动上传 }; @@ -204,16 +205,16 @@ export default function ProjectList() { try { setImporting(true); const result = await projectApi.importProject(selectedFile); - + if (result.success) { message.success(`项目导入成功!${result.message}`); setImportModalVisible(false); setSelectedFile(null); setValidationResult(null); - + // 刷新项目列表 await refreshProjects(); - + // 跳转到新项目 if (result.project_id) { navigate(`/project/${result.project_id}`); @@ -278,7 +279,7 @@ export default function ProjectList() { try { setExporting(true); - + if (selectedProjectIds.length === 1) { // 单个项目导出 const projectId = selectedProjectIds[0]; @@ -292,7 +293,7 @@ export default function ProjectList() { // 批量导出 let successCount = 0; let failCount = 0; - + for (const projectId of selectedProjectIds) { try { await projectApi.exportProjectData(projectId, { @@ -307,14 +308,14 @@ export default function ProjectList() { failCount++; } } - + if (failCount === 0) { message.success(`成功导出 ${successCount} 个项目`); } else { message.warning(`导出完成:成功 ${successCount} 个,失败 ${failCount} 个`); } } - + handleCloseExportModal(); } catch (error) { console.error('导出失败:', error); @@ -324,916 +325,1097 @@ export default function ProjectList() { } }; + // 计算页脚高度和页面内边距 + const isMobile = window.innerWidth <= 768; + const footerHeight = isMobile ? 48 : 52; + const topPadding = isMobile ? 20 : 32; + const sidePadding = isMobile ? 16 : 24; + return (
    + {contextHolder} + + {/* 固定头部区域 */}
    - - - - - - <FireOutlined style={{ color: '#ff4d4f', marginRight: 8 }} /> - 我的创作空间 - - - 开启你的小说创作之旅 - - - - - {window.innerWidth <= 768 ? ( - // 移动端:优化布局 - - {/* 第一行:主要创建按钮 */} - - - - - - - - - {/* 第二行:功能按钮 */} - - - - - - , - onClick: handleOpenExportModal, - disabled: exportableProjects.length === 0 - }, - { - key: 'import', - label: '导入项目', - icon: , - onClick: () => setImportModalVisible(true) - }, - { - type: 'divider' - }, - { - key: 'prompt-templates', - label: '提示词管理', - icon: , - onClick: () => navigate('/prompt-templates') - }, - { - key: 'mcp', - label: 'MCP插件', - icon: , - onClick: () => navigate('/mcp-plugins') - } - ] - }} - placement="bottomRight" - trigger={['click']} - > +
    + {/* 现代化头部区域 */} + + {/* 装饰性背景元素 */} +
    +
    +
    + + + + + <FireOutlined style={{ color: 'rgba(255,255,255,0.9)', marginRight: 12 }} /> + 我的创作空间 + + + ✨ 开启你的小说创作之旅 + + + + + {window.innerWidth <= 768 ? ( + // 移动端:优化布局 + + {/* 第一行:主要创建按钮 */} + + - - - -
    - -
    - -
    -
    - ) : ( - // PC端:优化后的布局 - 主要按钮 + 下拉菜单 - - - - - , - onClick: handleOpenExportModal, - disabled: exportableProjects.length === 0 - }, - { - key: 'import', - label: '导入项目', - icon: , - onClick: () => setImportModalVisible(true) - }, - { - type: 'divider' - }, - { - key: 'prompt-templates', - label: '提示词管理', - icon: , - onClick: () => navigate('/prompt-templates') - }, - { - key: 'mcp', - label: 'MCP插件', - icon: , - onClick: () => navigate('/mcp-plugins') - } - ] - }} - placement="bottomRight" - > - - - - - )} - -
    - - {showApiTip && projects.length === 0 && ( - - - - 首次使用提示 - - - } - description={ - - - 在开始创作之前,请先配置您的AI接口。系统支持OpenAI和Anthropic两种接口。 - - + + + + + + {/* 第二行:功能按钮 */} + + + + + + , + onClick: handleOpenExportModal, + disabled: exportableProjects.length === 0 + }, + { + key: 'import', + label: '导入项目', + icon: , + onClick: () => setImportModalVisible(true) + }, + { + type: 'divider' + }, + { + key: 'prompt-templates', + label: '提示词管理', + icon: , + onClick: () => navigate('/prompt-templates') + }, + { + key: 'mcp', + label: 'MCP插件', + icon: , + onClick: () => navigate('/mcp-plugins') + } + ] + }} + placement="bottomRight" + trigger={['click']} + > + + + + +
    + +
    + +
    +
    + ) : ( + // PC端:优化后的布局 - 主要按钮 + 下拉菜单 + + + - - -
    - } - type="info" - showIcon={false} - closable - closeIcon={} - onClose={() => setShowApiTip(false)} - style={{ - marginTop: window.innerWidth <= 768 ? 16 : 24, - borderRadius: 12, - background: 'linear-gradient(135deg, #e6f7ff 0%, #f0f5ff 100%)', - border: '1px solid #91d5ff' - }} - /> - )} - - {projects.length > 0 && ( - - - - 总项目数} - value={projects.length} - prefix={} - suffix="个" - valueStyle={{ color: '#1890ff', fontSize: window.innerWidth <= 768 ? 20 : 28, fontWeight: 'bold' }} - /> - - - - - 创作中} - value={activeProjects} - prefix={} - suffix="个" - valueStyle={{ color: '#52c41a', fontSize: window.innerWidth <= 768 ? 20 : 28, fontWeight: 'bold' }} - /> - - - - - 总字数} - value={totalWords} - prefix={} - suffix="字" - valueStyle={{ color: '#faad14', fontSize: window.innerWidth <= 768 ? 20 : 28, fontWeight: 'bold' }} - /> - - - - )} - -
    - -
    - - {!Array.isArray(projects) || projects.length === 0 ? ( - - - - 还没有项目,开始创建你的第一个小说项目吧! - - + + + + )} + + + + {showApiTip && projects.length === 0 && ( + + + + 首次使用提示 + + + } + description={ + + + 在开始创作之前,请先配置您的AI接口。系统支持OpenAI和Anthropic两种接口。 + + + } - style={{ padding: '80px 0' }} + type="info" + showIcon={false} + closable + closeIcon={} + onClose={() => setShowApiTip(false)} + style={{ + marginTop: window.innerWidth <= 768 ? 16 : 24, + borderRadius: 12, + background: 'linear-gradient(135deg, #e6f7ff 0%, #f0f5ff 100%)', + border: '1px solid #91d5ff' + }} /> - - ) : ( - - {projects.map((project) => { - const progress = getProgress(project.current_words, project.target_words || 0); - - return ( - - }>生成中断 - ) : getStatusTag(project.status)} - color="transparent" - style={{ top: 12, right: 12 }} - > - handleEnterProject(project)} - style={cardStyles.project} - styles={{ body: { padding: 0, overflow: 'hidden' } }} - {...cardHoverHandlers} - > -
    - -
    - - - {project.title} - -
    - {project.genre && ( - - {project.genre} - - )} -
    + )} + + {projects.length > 0 && ( + + + + + 📚 + {!isMobile && 总项目数} + {isMobile &&
    项目
    }
    - -
    - - {project.description || '暂无描述'} - - - {project.target_words && project.target_words > 0 && ( -
    -
    - 完成进度 - {progress}% -
    - -
    - )} - - - -
    -
    - {project.current_words >= 1000000 - ? (project.current_words / 1000000).toFixed(1) + 'M' - : project.current_words >= 1000 - ? (project.current_words / 1000).toFixed(1) + 'K' - : project.current_words - } -
    - 已写字数 -
    - - -
    -
    - {project.target_words - ? (project.target_words >= 1000000 - ? (project.target_words / 1000000).toFixed(1) + 'M' - : project.target_words >= 1000 - ? (project.target_words / 1000).toFixed(1) + 'K' - : project.target_words) - : '--' - } -
    - 目标字数 -
    - -
    - -
    - - - {formatDate(project.updated_at)} - - - -
    + } + value={projects.length} + valueStyle={{ + color: '#fff', + fontSize: isMobile ? 18 : 32, + fontWeight: 'bold', + textShadow: '0 1px 2px rgba(0,0,0,0.1)', + textAlign: 'center' + }} + /> + + + + + + ✍️ + {!isMobile && 创作中} + {isMobile &&
    创作
    }
    -
    -
    - - ); - })} -
    - )} -
    + } + value={activeProjects} + valueStyle={{ + color: '#fff', + fontSize: isMobile ? 18 : 32, + fontWeight: 'bold', + textShadow: '0 1px 2px rgba(0,0,0,0.1)', + textAlign: 'center' + }} + /> + + + + + + 📝 + {!isMobile && 总字数} + {isMobile &&
    字数
    } +
    + } + value={totalWords} + formatter={(value) => { + const val = Number(value); + return isMobile && val > 10000 ? `${(val / 10000).toFixed(1)}w` : val; + }} + valueStyle={{ + color: '#fff', + fontSize: isMobile ? 18 : 32, + fontWeight: 'bold', + textShadow: '0 1px 2px rgba(0,0,0,0.1)', + textAlign: 'center' + }} + /> + + + + )} + +
    - {/* 导入项目对话框 */} - - -
    -

    - 选择之前导出的 JSON 格式项目文件 -

    - { - setSelectedFile(null); - setValidationResult(null); - }} - fileList={selectedFile ? [{ uid: '-1', name: selectedFile.name, status: 'done' }] as any : []} - > - - -
    + {/* 可滚动的项目列表区域 */} +
    +
    + + {!Array.isArray(projects) || projects.length === 0 ? ( + + + + 还没有项目,开始创建你的第一个小说项目吧! + + + + + + + } + style={{ padding: '80px 0' }} + /> + + ) : ( + + {projects.map((project) => { + const progress = getProgress(project.current_words, project.target_words || 0); - {validating && ( -
    - + return ( + + }>生成中断 + ) : getStatusTag(project.status)} + color="transparent" + style={{ top: 12, right: 12 }} + > + handleEnterProject(project)} + style={cardStyles.project} + styles={{ body: { padding: 0, overflow: 'hidden' } }} + {...cardHoverHandlers} + > + {/* 项目卡片头部 - 添加装饰元素 */} +
    + {/* 装饰性圆圈 */} +
    + +
    +
    + +
    + + {project.title} + +
    + {project.genre && ( + + {project.genre} + + )} +
    +
    + +
    + + {project.description || '暂无描述'} + + + {project.target_words && project.target_words > 0 && ( +
    +
    + 完成进度 + {progress}% +
    + +
    + )} + + + +
    +
    + {project.current_words >= 1000000 + ? (project.current_words / 1000000).toFixed(1) + 'M' + : project.current_words >= 1000 + ? (project.current_words / 1000).toFixed(1) + 'K' + : project.current_words + } +
    + 已写字数 +
    + + +
    +
    + {project.target_words + ? (project.target_words >= 1000000 + ? (project.target_words / 1000000).toFixed(1) + 'M' + : project.target_words >= 1000 + ? (project.target_words / 1000).toFixed(1) + 'K' + : project.target_words) + : '--' + } +
    + 目标字数 +
    + +
    + +
    + + + {formatDate(project.updated_at)} + + + +
    +
    + + + + ); + })} + + )} + +
    + + {/* 导入项目对话框 */} + + +
    +

    + 选择之前导出的 JSON 格式项目文件 +

    + { + setSelectedFile(null); + setValidationResult(null); + }} + fileList={selectedFile ? [{ uid: '-1', name: selectedFile.name, status: 'done' }] as any : []} + > + +
    - )} - {validationResult && ( - - -
    - - {validationResult.valid ? '✓ 文件验证通过' : '✗ 文件验证失败'} - -
    + {validating && ( +
    + +
    + )} - {validationResult.project_name && ( + {validationResult && ( + +
    - 项目名称: - {validationResult.project_name} + + {validationResult.valid ? '✓ 文件验证通过' : '✗ 文件验证失败'} +
    - )} - {validationResult.statistics && Object.keys(validationResult.statistics).length > 0 && ( -
    - 数据统计: -
    - - {validationResult.statistics.chapters > 0 && ( - - 章节: {validationResult.statistics.chapters} - - )} - {validationResult.statistics.characters > 0 && ( - - 角色: {validationResult.statistics.characters} - - )} - {validationResult.statistics.outlines > 0 && ( - - 大纲: {validationResult.statistics.outlines} - - )} - {validationResult.statistics.relationships > 0 && ( - - 关系: {validationResult.statistics.relationships} - - )} - + {validationResult.project_name && ( +
    + 项目名称: + {validationResult.project_name}
    -
    - )} + )} - {validationResult.errors && validationResult.errors.length > 0 && ( -
    - 错误: -
      - {validationResult.errors.map((error: string, index: number) => ( -
    • {error}
    • - ))} -
    -
    - )} + {validationResult.statistics && Object.keys(validationResult.statistics).length > 0 && ( +
    + 数据统计: +
    + + {validationResult.statistics.chapters > 0 && ( + + 章节: {validationResult.statistics.chapters} + + )} + {validationResult.statistics.characters > 0 && ( + + 角色: {validationResult.statistics.characters} + + )} + {validationResult.statistics.outlines > 0 && ( + + 大纲: {validationResult.statistics.outlines} + + )} + {validationResult.statistics.relationships > 0 && ( + + 关系: {validationResult.statistics.relationships} + + )} + +
    +
    + )} - {validationResult.warnings && validationResult.warnings.length > 0 && ( -
    - 警告: -
      - {validationResult.warnings.map((warning: string, index: number) => ( -
    • {warning}
    • - ))} -
    -
    - )} + {validationResult.errors && validationResult.errors.length > 0 && ( +
    + 错误: +
      + {validationResult.errors.map((error: string, index: number) => ( +
    • {error}
    • + ))} +
    +
    + )} + + {validationResult.warnings && validationResult.warnings.length > 0 && ( +
    + 警告: +
      + {validationResult.warnings.map((warning: string, index: number) => ( +
    • {warning}
    • + ))} +
    +
    + )} + + + )} + + + + {/* 导出项目对话框 */} + 0 ? `导出 (${selectedProjectIds.length})` : '导出'} + cancelText="取消" + width={window.innerWidth <= 768 ? '90%' : 700} + centered + okButtonProps={{ disabled: selectedProjectIds.length === 0 }} + styles={{ + body: { + maxHeight: window.innerWidth <= 768 ? '70vh' : 'auto', + overflowY: 'auto', + padding: window.innerWidth <= 768 ? '16px' : '24px' + } + }} + > + + {/* 导出选项 */} + + + 导出选项 +
    + setExportOptions(prev => ({ ...prev, includeWritingStyles: checked }))} + style={{ + flexShrink: 0, + height: window.innerWidth <= 768 ? 16 : 22, + minHeight: window.innerWidth <= 768 ? 16 : 22, + lineHeight: window.innerWidth <= 768 ? '16px' : '22px' + }} + /> + 包含写作风格 + + + +
    +
    + setExportOptions(prev => ({ ...prev, includeGenerationHistory: checked }))} + style={{ + flexShrink: 0, + height: window.innerWidth <= 768 ? 16 : 22, + minHeight: window.innerWidth <= 768 ? 16 : 22, + lineHeight: window.innerWidth <= 768 ? '16px' : '22px' + }} + /> + 包含生成历史 + + + +
    - )} -
    -
    - {/* 导出项目对话框 */} - 0 ? `导出 (${selectedProjectIds.length})` : '导出'} - cancelText="取消" - width={window.innerWidth <= 768 ? '90%' : 700} - centered - okButtonProps={{ disabled: selectedProjectIds.length === 0 }} - styles={{ - body: { - maxHeight: window.innerWidth <= 768 ? '70vh' : 'auto', - overflowY: 'auto', - padding: window.innerWidth <= 768 ? '16px' : '24px' - } - }} - > - - {/* 导出选项 */} - - - 导出选项 -
    - setExportOptions(prev => ({ ...prev, includeWritingStyles: checked }))} - style={{ - flexShrink: 0, - height: window.innerWidth <= 768 ? 16 : 22, - minHeight: window.innerWidth <= 768 ? 16 : 22, - lineHeight: window.innerWidth <= 768 ? '16px' : '22px' - }} - /> - 包含写作风格 - - - + + + {/* 项目列表 */} +
    +
    + + 选择要导出的项目 {exportableProjects.length > 0 && ({exportableProjects.length}个可导出)} + + 0} + indeterminate={selectedProjectIds.length > 0 && selectedProjectIds.length < exportableProjects.length} + onChange={handleToggleAll} + style={{ fontSize: window.innerWidth <= 768 ? 13 : 14 }} + > + 全选 +
    -
    - setExportOptions(prev => ({ ...prev, includeGenerationHistory: checked }))} - style={{ - flexShrink: 0, - height: window.innerWidth <= 768 ? 16 : 22, - minHeight: window.innerWidth <= 768 ? 16 : 22, - lineHeight: window.innerWidth <= 768 ? '16px' : '22px' - }} - /> - 包含生成历史 - - - -
    - - - - - {/* 项目列表 */} -
    -
    - - 选择要导出的项目 {exportableProjects.length > 0 && ({exportableProjects.length}个可导出)} - - 0} - indeterminate={selectedProjectIds.length > 0 && selectedProjectIds.length < exportableProjects.length} - onChange={handleToggleAll} - style={{ fontSize: window.innerWidth <= 768 ? 13 : 14 }} - > - 全选 - -
    - -
    - {exportableProjects.length === 0 ? ( - - ) : ( - - {exportableProjects.map((project) => ( - handleToggleProject(project.id)} - > -
    - handleToggleProject(project.id)} - onClick={(e) => e.stopPropagation()} - /> - -
    -
    - {project.title} - {project.genre && ( - {project.genre} +
    + {exportableProjects.length === 0 ? ( + + ) : ( + + {exportableProjects.map((project) => ( + handleToggleProject(project.id)} + > +
    + handleToggleProject(project.id)} + onClick={(e) => e.stopPropagation()} + /> + +
    +
    + {project.title} + {project.genre && ( + {project.genre} + )} + {getStatusTag(project.status)} +
    + + {project.current_words || 0} 字 + {project.description && ` · ${project.description.substring(0, window.innerWidth <= 768 ? 30 : 50)}${project.description.length > (window.innerWidth <= 768 ? 30 : 50) ? '...' : ''}`} + +
    + {window.innerWidth > 768 && ( + + {formatDate(project.updated_at)} + )} - {getStatusTag(project.status)}
    - - {project.current_words || 0} 字 - {project.description && ` · ${project.description.substring(0, window.innerWidth <= 768 ? 30 : 50)}${project.description.length > (window.innerWidth <= 768 ? 30 : 50) ? '...' : ''}`} - -
    - {window.innerWidth > 768 && ( - - {formatDate(project.updated_at)} - - )} -
    - - ))} - - )} + + ))} + + )} +
    -
    - {selectedProjectIds.length > 0 && ( - - )} - - + {selectedProjectIds.length > 0 && ( + + )} + + - {/* 编辑项目对话框 */} - - - - - + + + - - - - - + + + + + - + +
    ); } \ No newline at end of file diff --git a/frontend/src/pages/ProjectWizardNew.tsx b/frontend/src/pages/ProjectWizardNew.tsx index a5dc70b..2ce313c 100644 --- a/frontend/src/pages/ProjectWizardNew.tsx +++ b/frontend/src/pages/ProjectWizardNew.tsx @@ -18,7 +18,7 @@ export default function ProjectWizardNew() { const [searchParams] = useSearchParams(); const [form] = Form.useForm(); const [isMobile, setIsMobile] = useState(window.innerWidth <= 768); - + // 状态管理 const [currentStep, setCurrentStep] = useState<'form' | 'generating'>('form'); const [generationConfig, setGenerationConfig] = useState(null); @@ -51,7 +51,7 @@ export default function ProjectWizardNew() { throw new Error('获取项目信息失败'); } const project = await response.json(); - + const config: GenerationConfig = { title: project.title, description: project.description || '', @@ -62,7 +62,7 @@ export default function ProjectWizardNew() { chapter_count: 3, character_count: project.character_count || 5, }; - + setGenerationConfig(config); setCurrentStep('generating'); } catch (error) { @@ -85,7 +85,7 @@ export default function ProjectWizardNew() { character_count: values.character_count || 5, outline_mode: values.outline_mode || 'one-to-many', // 添加大纲模式 }; - + setGenerationConfig(config); setCurrentStep('generating'); }; @@ -195,7 +195,7 @@ export default function ProjectWizardNew() {
    - + 传统模式 (1→1)
    @@ -217,12 +217,12 @@ export default function ProjectWizardNew() { - +
    - + 细化模式 (1→N) 推荐
    @@ -321,17 +321,15 @@ export default function ProjectWizardNew() { return (
    {/* 顶部标题栏 - 固定不滚动 */}
    {isMobile ? '返回' : '返回首页'} - + 项目创建向导 - +
    diff --git a/frontend/src/pages/PromptTemplates.tsx b/frontend/src/pages/PromptTemplates.tsx index 48a41d2..2eca8cf 100644 --- a/frontend/src/pages/PromptTemplates.tsx +++ b/frontend/src/pages/PromptTemplates.tsx @@ -193,50 +193,87 @@ export default function PromptTemplates() { return (
    - {/* 头部卡片 */}
    + {/* 顶部导航卡片 */} - + {/* 装饰性背景元素 */} +
    +
    +
    + + - - @@ -248,7 +285,14 @@ export default function PromptTemplates() { @@ -261,7 +305,7 @@ export default function PromptTemplates() { - + 使用说明 } @@ -280,151 +324,151 @@ export default function PromptTemplates() { style={{ marginTop: isMobile ? 16 : 24, borderRadius: 12, - background: 'linear-gradient(135deg, #e6f7ff 0%, #f0f5ff 100%)', - border: '1px solid #91d5ff' + background: 'var(--color-info-bg)', + border: '1px solid var(--color-info-border)' }} /> -
    - {/* 主内容区 */} -
    - - {/* 分类标签 */} - {categories.length > 0 && ( - - sum + cat.count, 0)})` }, - ...categories.map((cat, index) => ({ - key: (index + 1).toString(), - label: `${cat.category} (${cat.count})` - })) - ]} - /> - - )} + {/* 主内容区 */} +
    + + {/* 分类标签 */} + {categories.length > 0 && ( + + sum + cat.count, 0)})` }, + ...categories.map((cat, index) => ({ + key: (index + 1).toString(), + label: `${cat.category} (${cat.count})` + })) + ]} + /> + + )} - {/* 模板列表 */} - {currentTemplates.length === 0 ? ( - - - - ) : ( - - {currentTemplates.map(template => ( - - - {/* 头部 */} -
    - -
    - - {template.template_name} - - {!template.is_system_default && ( - handleToggleActive(template, checked)} - size={isMobile ? 'small' : 'default'} - style={{ marginLeft: 8 }} - /> - )} -
    - - - {template.category} - - - {template.is_system_default ? '系统默认' : '已自定义'} + {/* 模板列表 */} + {currentTemplates.length === 0 ? ( + + + + ) : ( + + {currentTemplates.map(template => ( + + + {/* 头部 */} +
    + +
    + + {template.template_name} + + {!template.is_system_default && ( + handleToggleActive(template, checked)} + size={isMobile ? 'small' : 'default'} + style={{ marginLeft: 8 }} + /> + )} +
    + + + {template.category} + + + {template.is_system_default ? '系统默认' : '已自定义'} + + +
    +
    + + {/* 内容 */} +
    + + {template.description || '暂无描述'} + + + + } + color={template.is_system_default || template.is_active ? 'success' : 'default'} + > + {template.is_system_default ? '始终启用' : (template.is_active ? '已启用' : '已禁用')} - -
    - {/* 内容 */} -
    - - {template.description || '暂无描述'} - + + 模板键: {template.template_key} + - - } - color={template.is_system_default || template.is_active ? 'success' : 'default'} - > - {template.is_system_default ? '始终启用' : (template.is_active ? '已启用' : '已禁用')} - - - - - 模板键: {template.template_key} - - - {/* 操作按钮 */} - - - - -
    -
    - - ))} -
    - )} - + {/* 操作按钮 */} + + + + +
    +
    + + ))} +
    + )} +
    +
    {/* 编辑对话框 */} @@ -486,6 +530,6 @@ export default function PromptTemplates() { /> -
    +
    ); } \ No newline at end of file diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 1e8336f..65a3f26 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -42,7 +42,7 @@ export default function SettingsPage() { try { const settings = await settingsApi.getSettings(); form.setFieldsValue(settings); - + // 判断是否为默认设置(id='0'表示来自.env的默认配置) if (settings.id === '0' || !settings.id) { setIsDefaultSettings(true); @@ -164,7 +164,7 @@ export default function SettingsPage() { api_base_url: apiBaseUrl, provider: provider || 'openai' }); - + setModelOptions(response.models); setModelsFetched(true); if (!silent) { @@ -202,7 +202,7 @@ export default function SettingsPage() { setTestingApi(true); setTestResult(null); - + try { const result = await settingsApi.testApiConnection({ api_key: apiKey, @@ -210,10 +210,10 @@ export default function SettingsPage() { provider: provider, llm_model: modelName }); - + setTestResult(result); setShowTestResult(true); - + if (result.success) { message.success(`测试成功!响应时间: ${result.response_time_ms}ms`); } else { @@ -238,7 +238,7 @@ export default function SettingsPage() { return (
    - + {isMobile ? 'API 设置' : 'AI API 设置'} @@ -341,7 +341,7 @@ export default function SettingsPage() { API 提供商 - + } @@ -362,7 +362,7 @@ export default function SettingsPage() { API 密钥 - + } @@ -381,7 +381,7 @@ export default function SettingsPage() { API 地址 - + } @@ -402,7 +402,7 @@ export default function SettingsPage() { 模型名称 - + } @@ -424,7 +424,7 @@ export default function SettingsPage() { <> {menu} {fetchingModels && ( -
    +
    正在获取模型列表...
    )} @@ -434,7 +434,7 @@ export default function SettingsPage() {
    )} {!fetchingModels && modelOptions.length === 0 && !modelsFetched && ( -
    +
    点击输入框自动获取模型列表
    )} @@ -446,7 +446,7 @@ export default function SettingsPage() { 加载中...
    ) : ( -
    +
    未找到匹配的模型
    ) @@ -506,7 +506,7 @@ export default function SettingsPage() { 温度参数 - + } @@ -530,7 +530,7 @@ export default function SettingsPage() { 最大 Token 数 - + } @@ -554,9 +554,9 @@ export default function SettingsPage() { message={ {testResult.success ? ( - + ) : ( - + )} {testResult.message} @@ -585,7 +585,7 @@ export default function SettingsPage() {
    {testResult.response_preview}
    )} -
    +
    ✓ API 配置正确,可以正常使用
    @@ -604,7 +604,7 @@ export default function SettingsPage() {
    )} {testResult.error_type && ( -
    +
    错误类型: {testResult.error_type}
    )} @@ -639,61 +639,61 @@ export default function SettingsPage() { {/* 操作按钮 */} {isMobile ? ( - // 移动端:垂直堆叠布局 - - + // 移动端:垂直堆叠布局 + + + + - + {hasSettings && ( - {hasSettings && ( - - )} - + )} + ) : ( // 桌面端:删除在左边,测试、重置和保存在右边
    // 占位符,保持右侧按钮位置 )} - + {/* 右侧:测试、重置和保存按钮组 */}