Files
happy-life-star/life-script/src/views/ScriptView.jsx
T

220 lines
7.8 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react';
import { UserCog, PenTool, Sparkles, BookOpen, Loader2 } from 'lucide-react';
import { GlassCard, GlassButton, GlassInput, GlassSelect } from '../components/ui';
import useStore from '../store/useStore';
import { generateEpicScript } from '../services/ai';
import { scriptStyles, scriptLengths } from '../utils/constants';
/**
* ScriptView 组件
* 爽文剧本视图,包含角色设定、创作需求和剧本展示
* @param {Object} props
* @param {Function} props.onOpenProfile - 打开用户资料模态框回调
*/
const ScriptView = ({ onOpenProfile }) => {
const {
registrationData,
lifeEvents,
scripts,
selectedScriptId,
addScript,
setSelectedScriptId,
getSelectedScript,
loadScripts
} = useStore();
// 加载剧本列表
useEffect(() => {
loadScripts().catch(() => {
// 后端不可用时忽略错误
});
}, [loadScripts]);
// 表单状态
const [theme, setTheme] = useState('');
const [style, setStyle] = useState(scriptStyles[0].value);
const [length, setLength] = useState(scriptLengths[0].value);
const [isLoading, setIsLoading] = useState(false);
/**
* 处理剧本生成
*/
const handleGenerate = async () => {
if (!theme) {
alert('请输入主题');
return;
}
setIsLoading(true);
try {
const content = await generateEpicScript(
{ theme, style, length, character: registrationData },
lifeEvents
);
addScript({ theme, style, length, content });
setTheme('');
} catch (error) {
console.error('Failed to generate script:', error);
} finally {
setIsLoading(false);
}
};
/**
* 格式化剧本内容,高亮【标题】
*/
const formatScriptContent = (content) => {
if (!content) return '';
return content.replace(
/【([^】]+)】/g,
'<div class="mt-8 mb-4 text-orange-100 font-bold text-lg border-l-2 border-orange-400 pl-4">【$1】</div>'
);
};
const selectedScript = getSelectedScript();
return (
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8">
{/* 左侧面板 */}
<div className="lg:col-span-4 space-y-6">
{/* 角色设定卡片 */}
<GlassCard className="border-white/10 space-y-4">
<div className="flex items-center gap-2 pb-2 border-b border-white/5">
<UserCog className="w-4 h-4 text-orange-200" />
<h4 className="text-sm font-bold tracking-widest text-white/80 uppercase">角色设定</h4>
</div>
<div className="grid grid-cols-2 gap-x-2 gap-y-4 text-[11px]">
<div>
<label className="text-white/20 block">昵称</label>
<span className="text-white/70">{registrationData.nickname || '-'}</span>
</div>
<div>
<label className="text-white/20 block">星座</label>
<span className="text-white/70">{registrationData.zodiac || '-'}</span>
</div>
<div>
<label className="text-white/20 block">MBTI</label>
<span className="text-white/70">{registrationData.mbti || '-'}</span>
</div>
<div className="col-span-2">
<label className="text-white/20 block">兴趣爱好</label>
<span className="text-white/70">
{registrationData.hobbies?.join(', ') || '-'}
</span>
</div>
</div>
<button
onClick={onOpenProfile}
className="w-full py-2 text-[10px] text-orange-200/50 hover:text-orange-200 border border-white/5 rounded-xl transition-all"
>
修改人设
</button>
</GlassCard>
{/* 创作需求表单 */}
<GlassCard className="border-white/10 space-y-6">
<div className="flex items-center gap-2 pb-2 border-b border-white/5">
<PenTool className="w-4 h-4 text-orange-200" />
<h4 className="text-sm font-bold tracking-widest text-white/80 uppercase">创作需求</h4>
</div>
<GlassInput
label="剧本主题"
placeholder="例如:我在职场逆袭了"
value={theme}
onChange={setTheme}
/>
<GlassSelect
label="叙事风格"
options={scriptStyles}
value={style}
onChange={setStyle}
/>
<GlassSelect
label="剧本篇幅"
options={scriptLengths}
value={length}
onChange={setLength}
/>
<GlassButton
onClick={handleGenerate}
disabled={isLoading}
className="w-full py-4 bg-orange-200/5 text-orange-200 font-bold text-sm tracking-widest border-orange-200/20"
>
{isLoading ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
编撰中...
</>
) : (
'生成爽文人生'
)}
</GlassButton>
</GlassCard>
{/* 历史卷轴列表 */}
<div className="space-y-4">
<h5 className="text-[10px] text-white/20 uppercase tracking-widest font-bold px-2">
历史卷轴
</h5>
<div className="space-y-2 max-h-[25vh] overflow-y-auto custom-scrollbar">
{scripts.length > 0 ? (
scripts.map((script) => (
<div
key={script.id}
onClick={() => setSelectedScriptId(script.id)}
className={`
p-3 glass-card text-left cursor-pointer hover:bg-white/5 border-white/5 transition-all
${script.id === selectedScriptId ? 'border-orange-200/30 bg-orange-200/5' : ''}
`}
>
<div className="text-[11px] text-white/80 truncate">{script.theme}</div>
<div className="text-[9px] text-white/30 flex justify-between mt-1">
<span>{script.style} | {script.length}</span>
<span>{script.date}</span>
</div>
</div>
))
) : (
<p className="text-center text-xs text-white/10 py-4 italic">暂无卷轴</p>
)}
</div>
</div>
</div>
{/* 右侧剧本展示区 */}
<div className="lg:col-span-8">
<div className="h-full">
{selectedScript ? (
<GlassCard className="h-full overflow-y-auto custom-scrollbar border-orange-200/20 shadow-2xl animate-fade-in" padding="lg">
<div className="prose prose-invert max-w-none">
<div className="flex justify-between items-center mb-8 pb-4 border-b border-white/5">
<div>
<h4 className="text-2xl font-serif text-orange-200">{selectedScript.theme}</h4>
<p className="text-[10px] text-white/30 mt-1 uppercase tracking-widest">
{selectedScript.style} · {selectedScript.length}
</p>
</div>
<BookOpen className="text-white/20" />
</div>
<div
className="text-white/70 leading-loose whitespace-pre-wrap space-y-6 text-sm"
dangerouslySetInnerHTML={{ __html: formatScriptContent(selectedScript.content) }}
/>
</div>
</GlassCard>
) : (
<div className="flex flex-col items-center justify-center h-full text-center opacity-20 py-32">
<Sparkles className="w-20 h-20 mb-6" />
<p className="text-xl font-serif">请在左侧设定需求开启你的爽文人生</p>
</div>
)}
</div>
</div>
</div>
);
};
export default ScriptView;