refactor: 导入导出功能增强:版本升级至1.1.0,新增职业系统、故事记忆、剧情分析的导出选项

This commit is contained in:
xiamuceer-j
2026-01-14 19:47:28 +08:00
parent 7ba2b2e5fa
commit fb16cc4072
3 changed files with 707 additions and 34 deletions
+73 -20
View File
@@ -16,6 +16,26 @@ import PromptTemplates from './PromptTemplates';
const { Title, Text, Paragraph } = Typography;
/**
* 格式化字数显示
* @param count 字数
* @returns 格式化后的字符串,如 "1.2K", "3.5W", "1.2M"
*/
const formatWordCount = (count: number): string => {
if (count < 1000) {
return count.toString();
} else if (count < 10000) {
// 1K - 9.9K
return (count / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
} else if (count < 1000000) {
// 1W - 99.9W (万)
return (count / 10000).toFixed(1).replace(/\.0$/, '') + 'W';
} else {
// 1M+ (百万)
return (count / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
}
};
export default function ProjectList() {
const navigate = useNavigate();
const { projects, loading } = useStore();
@@ -33,7 +53,10 @@ export default function ProjectList() {
const [selectedProjectIds, setSelectedProjectIds] = useState<string[]>([]);
const [exportOptions, setExportOptions] = useState({
includeWritingStyles: true,
includeGenerationHistory: true,
includeGenerationHistory: false,
includeCareers: true,
includeMemories: false,
includePlotAnalysis: false,
});
const { refreshProjects, deleteProject } = useProjectSync();
@@ -250,7 +273,10 @@ export default function ProjectList() {
const project = projects.find(p => p.id === projectId);
await projectApi.exportProjectData(projectId, {
include_generation_history: exportOptions.includeGenerationHistory,
include_writing_styles: exportOptions.includeWritingStyles
include_writing_styles: exportOptions.includeWritingStyles,
include_careers: exportOptions.includeCareers,
include_memories: exportOptions.includeMemories,
include_plot_analysis: exportOptions.includePlotAnalysis
});
message.success(`项目 "${project?.title}" 导出成功`);
} else {
@@ -260,7 +286,10 @@ export default function ProjectList() {
try {
await projectApi.exportProjectData(projectId, {
include_generation_history: exportOptions.includeGenerationHistory,
include_writing_styles: exportOptions.includeWritingStyles
include_writing_styles: exportOptions.includeWritingStyles,
include_careers: exportOptions.includeCareers,
include_memories: exportOptions.includeMemories,
include_plot_analysis: exportOptions.includePlotAnalysis
});
successCount++;
await new Promise(resolve => setTimeout(resolve, 500));
@@ -696,7 +725,7 @@ export default function ProjectList() {
{item.label}
</span>
<span style={{ fontSize: '15px', fontWeight: '600', color: '#fff', lineHeight: 1, fontFamily: 'Monaco, monospace' }}>
{item.value > 10000 ? (item.value / 10000).toFixed(1) + 'w' : item.value}
{item.label === '总字数' ? formatWordCount(item.value) : item.value}
{item.unit && <span style={{ fontSize: '10px', marginLeft: '2px', opacity: 0.8 }}>{item.unit}</span>}
</span>
</div>
@@ -763,10 +792,10 @@ export default function ProjectList() {
<Spin spinning={loading}>
<div style={{
...cardStyles.bookshelf,
// 移动端显示
// 移动端显示
...(isMobile && {
gridTemplateColumns: 'repeat(2, 1fr)',
gap: '12px',
gridTemplateColumns: '1fr',
gap: '16px',
padding: '16px 0',
})
}}>
@@ -988,9 +1017,7 @@ export default function ProjectList() {
lineHeight: 1.2,
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial'
}}>
{project.current_words >= 1000
? (project.current_words / 1000).toFixed(1) + 'K'
: (project.current_words || 0)}
{formatWordCount(project.current_words || 0)}
</div>
<div style={{
fontSize: isMobile ? 10 : 11,
@@ -1013,9 +1040,7 @@ export default function ProjectList() {
lineHeight: 1.2,
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial'
}}>
{(project.target_words || 0) >= 1000
? ((project.target_words || 0) / 1000).toFixed(1) + 'K'
: (project.target_words || 0)}
{formatWordCount(project.target_words || 0)}
</div>
<div style={{
fontSize: isMobile ? 10 : 11,
@@ -1120,12 +1145,31 @@ export default function ProjectList() {
)}
{validationResult.statistics && (
<div style={{ marginTop: 8 }}>
<Space size={[8, 8]} wrap>
<Text type="secondary" style={{ fontSize: 12, display: 'block', marginBottom: 6 }}></Text>
<Space size={[6, 6]} wrap>
{validationResult.statistics.chapters > 0 && <Tag color="blue">: {validationResult.statistics.chapters}</Tag>}
{validationResult.statistics.characters > 0 && <Tag color="green">: {validationResult.statistics.characters}</Tag>}
{validationResult.statistics.outlines > 0 && <Tag color="cyan">: {validationResult.statistics.outlines}</Tag>}
{validationResult.statistics.relationships > 0 && <Tag color="purple">: {validationResult.statistics.relationships}</Tag>}
{validationResult.statistics.organizations > 0 && <Tag color="orange">: {validationResult.statistics.organizations}</Tag>}
{validationResult.statistics.careers > 0 && <Tag color="magenta">: {validationResult.statistics.careers}</Tag>}
{validationResult.statistics.character_careers > 0 && <Tag color="geekblue">: {validationResult.statistics.character_careers}</Tag>}
{validationResult.statistics.writing_styles > 0 && <Tag color="lime">: {validationResult.statistics.writing_styles}</Tag>}
{validationResult.statistics.story_memories > 0 && <Tag color="gold">: {validationResult.statistics.story_memories}</Tag>}
{validationResult.statistics.plot_analysis > 0 && <Tag color="volcano">: {validationResult.statistics.plot_analysis}</Tag>}
{validationResult.statistics.generation_history > 0 && <Tag>: {validationResult.statistics.generation_history}</Tag>}
{validationResult.statistics.has_default_style && <Tag color="success"></Tag>}
</Space>
</div>
)}
{validationResult.warnings?.length > 0 && (
<div style={{ marginTop: 8 }}>
<Text type="warning" strong style={{ fontSize: 12 }}></Text>
<ul style={{ margin: '4px 0 0 0', paddingLeft: 20, color: '#faad14', fontSize: 12 }}>
{validationResult.warnings.map((w: string, i: number) => <li key={i}>{w}</li>)}
</ul>
</div>
)}
{validationResult.errors?.length > 0 && (
<div>
<Text type="danger" strong></Text>
@@ -1155,12 +1199,21 @@ export default function ProjectList() {
>
<Space direction="vertical" size={16} style={{ width: '100%' }}>
<Card size="small" style={{ background: '#f5f5f5' }}>
<Space direction="vertical" size={12}>
<Space direction="vertical" size={12} style={{ width: '100%' }}>
<Text strong></Text>
<Space size={24}>
<Checkbox checked={exportOptions.includeWritingStyles} onChange={e => setExportOptions(prev => ({...prev, includeWritingStyles: e.target.checked}))}></Checkbox>
<Checkbox checked={exportOptions.includeGenerationHistory} onChange={e => setExportOptions(prev => ({...prev, includeGenerationHistory: e.target.checked}))}></Checkbox>
</Space>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px 24px' }}>
<Checkbox checked={exportOptions.includeWritingStyles} onChange={e => setExportOptions(prev => ({...prev, includeWritingStyles: e.target.checked}))}></Checkbox>
<Checkbox checked={exportOptions.includeCareers} onChange={e => setExportOptions(prev => ({...prev, includeCareers: e.target.checked}))}></Checkbox>
<Tooltip title="包含生成历史记录,文件可能较大">
<Checkbox checked={exportOptions.includeGenerationHistory} onChange={e => setExportOptions(prev => ({...prev, includeGenerationHistory: e.target.checked}))}></Checkbox>
</Tooltip>
<Tooltip title="包含故事记忆数据,文件可能较大">
<Checkbox checked={exportOptions.includeMemories} onChange={e => setExportOptions(prev => ({...prev, includeMemories: e.target.checked}))}></Checkbox>
</Tooltip>
<Tooltip title="包含AI剧情分析数据">
<Checkbox checked={exportOptions.includePlotAnalysis} onChange={e => setExportOptions(prev => ({...prev, includePlotAnalysis: e.target.checked}))}></Checkbox>
</Tooltip>
</div>
</Space>
</Card>
@@ -1194,7 +1247,7 @@ export default function ProjectList() {
<Checkbox checked={selectedProjectIds.includes(p.id)} />
<div style={{ flex: 1 }}>
<div>{p.title}</div>
<div style={{ fontSize: 12, color: '#999' }}>{p.current_words || 0} · {getStatusTag(getDisplayStatus(p.status, getProgress(p.current_words || 0, p.target_words || 0)))}</div>
<div style={{ fontSize: 12, color: '#999' }}>{formatWordCount(p.current_words || 0)} · {getStatusTag(getDisplayStatus(p.status, getProgress(p.current_words || 0, p.target_words || 0)))}</div>
</div>
</div>
))}