style: 优化向导/灵感模式AI生成页面样式

This commit is contained in:
xiamuceer
2026-03-24 10:56:25 +08:00
parent d72dd1c555
commit f74f6641f2
+304 -92
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Card, Button, Space, Typography, message, Progress } from 'antd'; import { Card, Button, Space, Typography, message, Progress, theme } from 'antd';
import { CheckCircleOutlined, LoadingOutlined } from '@ant-design/icons'; import { CheckCircleOutlined, LoadingOutlined } from '@ant-design/icons';
import { wizardStreamApi } from '../services/api'; import { wizardStreamApi } from '../services/api';
import type { ApiError } from '../types'; import type { ApiError } from '../types';
@@ -53,6 +53,9 @@ export const AIProjectGenerator: React.FC<AIProjectGeneratorProps> = ({
resumeProjectId resumeProjectId
}) => { }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { token } = theme.useToken();
const alphaColor = (color: string, alpha: number) =>
`color-mix(in srgb, ${color} ${(alpha * 100).toFixed(0)}%, transparent)`;
// 状态管理 // 状态管理
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -947,12 +950,45 @@ export const AIProjectGenerator: React.FC<AIProjectGeneratorProps> = ({
}; };
// 获取步骤状态图标和颜色 // 获取步骤状态图标和样式
const getStepStatus = (step: GenerationStep) => { const getStepStatus = (step: GenerationStep) => {
if (step === 'completed') return { icon: <CheckCircleOutlined />, color: 'var(--color-success)' }; if (step === 'completed') {
if (step === 'processing') return { icon: <LoadingOutlined />, color: 'var(--color-primary)' }; return {
if (step === 'error') return { icon: '✗', color: 'var(--color-error)' }; icon: <CheckCircleOutlined />,
return { icon: '○', color: 'var(--color-text-quaternary)' }; color: token.colorSuccess,
text: '已完成',
background: `linear-gradient(135deg, ${alphaColor(token.colorSuccess, 0.12)} 0%, ${token.colorBgContainer} 100%)`,
borderColor: alphaColor(token.colorSuccess, 0.28),
};
}
if (step === 'processing') {
return {
icon: <LoadingOutlined spin />,
color: token.colorPrimary,
text: '进行中',
background: `linear-gradient(135deg, ${alphaColor(token.colorPrimary, 0.14)} 0%, ${token.colorBgContainer} 100%)`,
borderColor: alphaColor(token.colorPrimary, 0.32),
};
}
if (step === 'error') {
return {
icon: '✕',
color: token.colorError,
text: '失败',
background: `linear-gradient(135deg, ${alphaColor(token.colorError, 0.12)} 0%, ${token.colorBgContainer} 100%)`,
borderColor: alphaColor(token.colorError, 0.32),
};
}
return {
icon: '○',
color: token.colorTextQuaternary,
text: '等待中',
background: token.colorFillQuaternary,
borderColor: token.colorBorderSecondary,
};
}; };
const hasError = generationSteps.worldBuilding === 'error' || const hasError = generationSteps.worldBuilding === 'error' ||
@@ -960,95 +996,215 @@ export const AIProjectGenerator: React.FC<AIProjectGeneratorProps> = ({
generationSteps.characters === 'error' || generationSteps.characters === 'error' ||
generationSteps.outline === 'error'; generationSteps.outline === 'error';
const progressAccentColor = hasError
? token.colorError
: progress === 100
? token.colorSuccess
: token.colorPrimary;
const stepItems = [
{ key: 'worldBuilding', label: '生成世界观', step: generationSteps.worldBuilding },
{ key: 'careers', label: '生成职业体系', step: generationSteps.careers },
{ key: 'characters', label: '生成角色', step: generationSteps.characters },
{ key: 'outline', label: '生成大纲', step: generationSteps.outline },
];
const availableViewportHeight = isMobile
? 'calc(100dvh - 96px)'
: 'calc(100dvh - 128px)';
// 渲染生成进度页面 // 渲染生成进度页面
const renderGenerating = () => ( const renderGenerating = () => (
<div style={{ <div
textAlign: 'center', style={{
padding: isMobile ? '32px 16px' : '40px 20px', padding: isMobile ? '4px 0 8px' : '8px 0 12px',
maxWidth: '100%', maxWidth: 920,
overflow: 'hidden' margin: '0 auto',
}}> overflow: 'hidden',
<Title minHeight: availableViewportHeight,
level={isMobile ? 4 : 3} display: 'flex',
flexDirection: 'column',
justifyContent: hasError ? 'flex-start' : 'center',
}}
>
<div
style={{ style={{
marginBottom: 32, marginBottom: 14,
color: 'var(--color-text-primary)', padding: isMobile ? '18px 16px' : '24px 24px 20px',
wordBreak: 'break-word', borderRadius: 18,
whiteSpace: 'normal', border: `1px solid ${alphaColor(hasError ? token.colorError : token.colorPrimary, 0.18)}`,
overflowWrap: 'break-word' background: `linear-gradient(135deg, ${alphaColor(token.colorPrimary, 0.12)} 0%, ${token.colorBgContainer} 48%, ${alphaColor(hasError ? token.colorError : token.colorSuccess, hasError ? 0.08 : 0.04)} 100%)`,
boxShadow: `0 12px 28px ${alphaColor(token.colorText, 0.06)}`,
textAlign: 'center',
}} }}
> >
{config.title} <Title
</Title> level={isMobile ? 4 : 3}
style={{
<Card style={{ marginBottom: 24, maxWidth: '100%' }}> marginBottom: 8,
<Progress color: token.colorTextHeading,
percent={progress} wordBreak: 'break-word',
status={hasError ? 'exception' : (progress === 100 ? 'success' : 'active')} whiteSpace: 'normal',
strokeColor={{ overflowWrap: 'break-word',
'0%': 'var(--color-primary)',
'100%': 'var(--color-primary-active)',
}} }}
style={{ marginBottom: 24 }} >
/> {config.title}
</Title>
<Paragraph <Paragraph
style={{ style={{
fontSize: isMobile ? 14 : 16, maxWidth: 620,
marginBottom: 32, margin: '0 auto',
color: hasError ? 'var(--color-error)' : 'var(--color-text-secondary)', color: token.colorTextSecondary,
fontSize: isMobile ? 13 : 14,
lineHeight: 1.7,
wordBreak: 'break-word', wordBreak: 'break-word',
whiteSpace: 'normal', whiteSpace: 'normal',
overflowWrap: 'break-word' overflowWrap: 'break-word',
}} }}
> >
{progressMessage} {hasError
? '生成流程中断,已保留当前进度与上下文信息,可从失败步骤继续重试。'
: '系统会依次生成世界观、职业体系、角色与大纲,请耐心等待。'}
</Paragraph> </Paragraph>
</div>
{errorDetails && ( <Card
<Card style={{
size="small" marginBottom: 12,
borderRadius: 18,
border: `1px solid ${alphaColor(token.colorText, 0.08)}`,
background: `linear-gradient(180deg, ${alphaColor(token.colorBgContainer, 0.97)} 0%, ${alphaColor(token.colorPrimary, 0.03)} 100%)`,
boxShadow: `0 10px 24px ${alphaColor(token.colorText, 0.06)}`,
}}
styles={{
body: {
padding: isMobile ? 14 : 20,
}
}}
>
<div
style={{
padding: isMobile ? '14px 14px 16px' : '16px 18px 18px',
marginBottom: 16,
borderRadius: 14,
background: token.colorFillQuaternary,
border: `1px solid ${alphaColor(progressAccentColor, 0.18)}`,
}}
>
<div
style={{ style={{
marginBottom: 24, display: 'flex',
background: 'var(--color-error-bg)', justifyContent: 'space-between',
borderColor: 'var(--color-error-border)', alignItems: isMobile ? 'flex-start' : 'center',
textAlign: 'left', flexDirection: isMobile ? 'column' : 'row',
maxWidth: '100%', gap: 10,
overflow: 'hidden' marginBottom: 10,
}} }}
> >
<Text strong style={{ color: 'var(--color-error)' }}></Text> <div style={{ flex: 1, textAlign: 'left' }}>
<br /> <Text
style={{
display: 'block',
marginBottom: 6,
color: token.colorTextTertiary,
fontSize: 12,
letterSpacing: 0.4,
}}
>
</Text>
<Paragraph
style={{
margin: 0,
color: hasError ? token.colorError : token.colorText,
fontSize: isMobile ? 13 : 15,
lineHeight: 1.7,
wordBreak: 'break-word',
whiteSpace: 'normal',
overflowWrap: 'break-word',
}}
>
{progressMessage || '准备生成...'}
</Paragraph>
</div>
<div
style={{
minWidth: isMobile ? 'auto' : 96,
textAlign: isMobile ? 'left' : 'right',
}}
>
<Text
style={{
fontSize: isMobile ? 24 : 32,
lineHeight: 1,
fontWeight: 700,
color: progressAccentColor,
}}
>
{progress}%
</Text>
</div>
</div>
<Progress
percent={progress}
showInfo={false}
status={hasError ? 'exception' : (progress === 100 ? 'success' : 'active')}
strokeColor={progress === 100
? {
'0%': token.colorSuccess,
'100%': token.colorSuccessActive,
}
: {
'0%': token.colorPrimary,
'100%': token.colorPrimaryActive,
}}
trailColor={token.colorFillTertiary}
strokeLinecap="round"
style={{ marginBottom: 0 }}
/>
</div>
{errorDetails && (
<div
style={{
marginBottom: 16,
padding: isMobile ? '12px 14px' : '14px 16px',
borderRadius: 14,
background: `linear-gradient(135deg, ${alphaColor(token.colorError, 0.12)} 0%, ${token.colorBgContainer} 100%)`,
border: `1px solid ${alphaColor(token.colorError, 0.24)}`,
textAlign: 'left',
overflow: 'hidden',
}}
>
<Text strong style={{ color: token.colorError, display: 'block', marginBottom: 8 }}>
</Text>
<Text <Text
style={{ style={{
color: 'var(--color-text-secondary)', color: token.colorTextSecondary,
fontSize: 14, fontSize: 14,
lineHeight: 1.7,
wordBreak: 'break-word', wordBreak: 'break-word',
whiteSpace: 'normal', whiteSpace: 'normal',
overflowWrap: 'break-word', overflowWrap: 'break-word',
display: 'block' display: 'block',
}} }}
> >
{errorDetails} {errorDetails}
</Text> </Text>
</Card> </div>
)} )}
<Space <div
direction="vertical"
size={16}
style={{ style={{
width: '100%', display: 'grid',
maxWidth: isMobile ? '100%' : 400, gap: 10,
margin: '0 auto'
}} }}
> >
{[ {stepItems.map(({ key, label, step }) => {
{ key: 'worldBuilding', label: '生成世界观', step: generationSteps.worldBuilding },
{ key: 'careers', label: '生成职业体系', step: generationSteps.careers },
{ key: 'characters', label: '生成角色', step: generationSteps.characters },
{ key: 'outline', label: '生成大纲', step: generationSteps.outline },
].map(({ key, label, step }) => {
const status = getStepStatus(step); const status = getStepStatus(step);
return ( return (
<div <div
@@ -1057,71 +1213,127 @@ export const AIProjectGenerator: React.FC<AIProjectGeneratorProps> = ({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
padding: isMobile ? '10px 12px' : '12px 20px', padding: isMobile ? '10px 12px' : '12px 14px',
background: step === 'processing' ? 'var(--color-info-bg)' : (step === 'error' ? 'var(--color-error-bg)' : 'var(--color-bg-layout)'), background: status.background,
borderRadius: 8, borderRadius: 14,
border: `1px solid ${step === 'processing' ? 'var(--color-info-border)' : (step === 'error' ? 'var(--color-error-border)' : 'var(--color-border-secondary)')}`, border: `1px solid ${status.borderColor}`,
gap: '8px', gap: 12,
maxWidth: '100%', maxWidth: '100%',
overflow: 'hidden' overflow: 'hidden',
}} }}
> >
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 12,
flex: 1,
minWidth: 0,
}}
>
<span
style={{
width: 30,
height: 30,
borderRadius: '50%',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
color: status.color,
background: alphaColor(status.color, 0.12),
fontSize: 18,
flexShrink: 0,
}}
>
{status.icon}
</span>
<div
style={{
minWidth: 0,
flex: 1,
textAlign: 'left',
}}
>
<Text
style={{
display: 'block',
fontSize: isMobile ? 13 : 14,
fontWeight: step === 'processing' ? 600 : 500,
color: token.colorText,
wordBreak: 'break-word',
whiteSpace: 'normal',
overflowWrap: 'break-word',
}}
>
{label}
</Text>
<Text
style={{
fontSize: 12,
color: step === 'pending' ? token.colorTextTertiary : status.color,
}}
>
{status.text}
</Text>
</div>
</div>
<Text <Text
style={{ style={{
fontSize: isMobile ? 14 : 16, fontSize: 12,
fontWeight: step === 'processing' ? 600 : 400, fontWeight: 600,
wordBreak: 'break-word',
whiteSpace: 'normal',
overflowWrap: 'break-word',
flex: 1,
textAlign: 'left'
}}
>
{label}
</Text>
<span
style={{
fontSize: 20,
color: status.color, color: status.color,
flexShrink: 0 padding: '4px 10px',
borderRadius: 999,
background: alphaColor(status.color, 0.1),
whiteSpace: 'nowrap',
flexShrink: 0,
}} }}
> >
{status.icon} {status.text}
</span> </Text>
</div> </div>
); );
})} })}
</Space> </div>
</Card> </Card>
<Paragraph <Paragraph
type="secondary" type="secondary"
style={{ style={{
color: 'var(--color-text-secondary)', marginBottom: hasError ? 14 : 0,
opacity: 0.9, color: token.colorTextSecondary,
opacity: 0.92,
textAlign: 'center',
wordBreak: 'break-word', wordBreak: 'break-word',
whiteSpace: 'normal', whiteSpace: 'normal',
overflowWrap: 'break-word', overflowWrap: 'break-word',
fontSize: isMobile ? 14 : 16 fontSize: isMobile ? 13 : 14,
}} }}
> >
{hasError ? '生成过程中出现错误,请点击重试按钮重新生成' : '请耐心等待,AI正在为您精心创作...'} {hasError ? '可点击下方智能重试,从失败节点继续生成,避免重复执行已完成步骤。' : '请勿关闭页面,生成完成后将自动进入项目详情页。'}
</Paragraph> </Paragraph>
{hasError && ( {hasError && (
<Space style={{ marginTop: 16 }}> <Space style={{ width: '100%', justifyContent: 'center' }}>
<Button <Button
type="primary" type="primary"
size="large" size="large"
onClick={handleSmartRetry} onClick={handleSmartRetry}
loading={loading} loading={loading}
disabled={loading} disabled={loading}
style={{
minWidth: isMobile ? '100%' : 160,
height: 44,
borderRadius: 12,
boxShadow: `0 10px 24px ${alphaColor(token.colorPrimary, 0.22)}`,
}}
> >
</Button> </Button>
</Space> </Space>
)} )}
</div> </div>
); );