fix:初始化
This commit is contained in:
414
src/pages/NovelList.tsx
Normal file
414
src/pages/NovelList.tsx
Normal file
@@ -0,0 +1,414 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Card, Button, Modal, Form, Input, message, Popconfirm, Row, Col, Tag, Select } from 'antd';
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined, BookOutlined, EyeOutlined } from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { storage } from '../utils/indexedDB';
|
||||
import { Novel } from '../types';
|
||||
|
||||
const { TextArea } = Input;
|
||||
const { Option } = Select;
|
||||
|
||||
// 小说题材选项
|
||||
const NOVEL_GENRES = [
|
||||
'穿越',
|
||||
'都市',
|
||||
'修仙',
|
||||
'武侠',
|
||||
'玄幻',
|
||||
'科幻',
|
||||
'言情',
|
||||
'历史',
|
||||
'游戏',
|
||||
'灵异',
|
||||
'军事',
|
||||
'悬疑',
|
||||
'其他'
|
||||
];
|
||||
|
||||
const NovelList: React.FC = () => {
|
||||
const [novels, setNovels] = useState<Novel[]>([]);
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [viewModalVisible, setViewModalVisible] = useState(false);
|
||||
const [editingNovel, setEditingNovel] = useState<Novel | null>(null);
|
||||
const [viewingNovel, setViewingNovel] = useState<Novel | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
loadNovels();
|
||||
}, []);
|
||||
|
||||
const loadNovels = async () => {
|
||||
try {
|
||||
const loadedNovels = await storage.getNovels();
|
||||
setNovels(loadedNovels);
|
||||
} catch (error) {
|
||||
console.error('加载小说列表失败:', error);
|
||||
message.error('加载小说列表失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddNovel = () => {
|
||||
setEditingNovel(null);
|
||||
form.resetFields();
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const handleViewNovel = (novel: Novel) => {
|
||||
setViewingNovel(novel);
|
||||
setViewModalVisible(true);
|
||||
};
|
||||
|
||||
const handleEditNovel = (novel: Novel) => {
|
||||
setEditingNovel(novel);
|
||||
form.setFieldsValue({
|
||||
title: novel.title,
|
||||
genre: novel.genre === '未分类' ? undefined : novel.genre,
|
||||
outline: novel.outline
|
||||
});
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const handleDeleteNovel = async (id: string) => {
|
||||
try {
|
||||
await storage.deleteNovel(id);
|
||||
await loadNovels();
|
||||
message.success('小说删除成功');
|
||||
} catch (error) {
|
||||
console.error('删除小说失败:', error);
|
||||
message.error('删除失败,请重试');
|
||||
}
|
||||
};
|
||||
|
||||
const handleModalOk = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
|
||||
if (editingNovel) {
|
||||
await storage.updateNovel(editingNovel.id, {
|
||||
...values,
|
||||
genre: values.genre || '未分类'
|
||||
});
|
||||
message.success('小说更新成功');
|
||||
} else {
|
||||
const newNovel: Novel = {
|
||||
id: Date.now().toString(),
|
||||
title: values.title,
|
||||
genre: values.genre || '未分类',
|
||||
outline: values.outline || '',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
await storage.addNovel(newNovel);
|
||||
message.success('小说创建成功,请前往AI生成页面完善设定');
|
||||
}
|
||||
|
||||
await loadNovels();
|
||||
setIsModalVisible(false);
|
||||
form.resetFields();
|
||||
} catch (error) {
|
||||
console.error('保存小说失败:', error);
|
||||
message.error('保存失败,请重试');
|
||||
}
|
||||
};
|
||||
|
||||
const handleModalCancel = () => {
|
||||
setIsModalVisible(false);
|
||||
form.resetFields();
|
||||
setEditingNovel(null);
|
||||
};
|
||||
|
||||
const handleCardClick = (id: string) => {
|
||||
navigate(`/novels/${id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: '24px' }}>
|
||||
<h2 style={{ margin: 0, fontSize: '20px', fontWeight: 600, color: '#262626' }}>我的小说</h2>
|
||||
<p style={{ margin: '8px 0 0 0', color: '#8c8c8c', fontSize: '14px' }}>管理和创作您的 AI 小说作品</p>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
|
||||
<div style={{ fontSize: '14px', color: '#595959' }}>
|
||||
共 {novels.length} 部小说
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleAddNovel}
|
||||
size="large"
|
||||
style={{ borderRadius: '6px' }}
|
||||
>
|
||||
新建小说
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{novels.length === 0 ? (
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
padding: '80px 20px',
|
||||
background: 'white',
|
||||
borderRadius: '8px',
|
||||
border: '1px dashed #d9d9d9'
|
||||
}}>
|
||||
<BookOutlined style={{ fontSize: '48px', color: '#d9d9d9', marginBottom: '16px' }} />
|
||||
<div style={{ fontSize: '16px', color: '#595959', marginBottom: '8px' }}>
|
||||
还没有小说,开始创作您的第一部作品吧!
|
||||
</div>
|
||||
<div style={{ fontSize: '14px', color: '#8c8c8c' }}>
|
||||
点击"新建小说"按钮开始您的创作之旅
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Row gutter={[16, 16]}>
|
||||
{novels.map((novel) => (
|
||||
<Col xs={24} sm={12} md={8} lg={6} key={novel.id}>
|
||||
<Card
|
||||
hoverable
|
||||
style={{
|
||||
height: '100%',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #f0f0f0',
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
onClick={() => handleCardClick(novel.id)}
|
||||
actions={[
|
||||
<EyeOutlined
|
||||
key="view"
|
||||
style={{ color: '#52c41a' }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleViewNovel(novel);
|
||||
}}
|
||||
/>,
|
||||
<EditOutlined
|
||||
key="edit"
|
||||
style={{ color: '#1890ff' }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleEditNovel(novel);
|
||||
}}
|
||||
/>,
|
||||
<Popconfirm
|
||||
title="确认删除"
|
||||
description="确定要删除这部小说吗?所有章节内容也将被删除。"
|
||||
onConfirm={(e) => {
|
||||
e?.stopPropagation();
|
||||
handleDeleteNovel(novel.id);
|
||||
}}
|
||||
onCancel={(e) => e?.stopPropagation()}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<DeleteOutlined
|
||||
key="delete"
|
||||
style={{ color: '#ff4d4f' }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</Popconfirm>
|
||||
]}
|
||||
>
|
||||
<Card.Meta
|
||||
avatar={
|
||||
<div style={{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '8px',
|
||||
background: '#e6f7ff',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<BookOutlined style={{ fontSize: '20px', color: '#1890ff' }} />
|
||||
</div>
|
||||
}
|
||||
title={
|
||||
<div style={{
|
||||
fontSize: '16px',
|
||||
fontWeight: 500,
|
||||
color: '#262626',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
}}>
|
||||
{novel.title}
|
||||
</div>
|
||||
}
|
||||
description={
|
||||
<div>
|
||||
<div style={{ marginBottom: '8px' }}>
|
||||
<Tag color="blue">{novel.genre}</Tag>
|
||||
{novel.generatedSettings && (
|
||||
<Tag color="green">已完善设定</Tag>
|
||||
)}
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: '12px',
|
||||
color: '#8c8c8c',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
lineHeight: '1.5'
|
||||
}}>
|
||||
{novel.outline || '暂无大纲'}
|
||||
</div>
|
||||
<div style={{ marginTop: '8px', fontSize: '11px', color: '#bfbfbf' }}>
|
||||
创建于 {new Date(novel.createdAt).toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
)}
|
||||
|
||||
<Modal
|
||||
title={editingNovel ? '编辑小说' : '新建小说'}
|
||||
open={isModalVisible}
|
||||
onOk={handleModalOk}
|
||||
onCancel={handleModalCancel}
|
||||
width={600}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item
|
||||
label="书名"
|
||||
name="title"
|
||||
rules={[{ required: true, message: '请输入书名' }]}
|
||||
>
|
||||
<Input placeholder="请输入书名" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="题材类型"
|
||||
name="genre"
|
||||
rules={[{ required: true, message: '请选择题材类型' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择题材类型"
|
||||
size="large"
|
||||
showSearch
|
||||
allowClear
|
||||
>
|
||||
{NOVEL_GENRES.map(genre => (
|
||||
<Option key={genre} value={genre}>
|
||||
{genre}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="初步想法"
|
||||
name="outline"
|
||||
extra="可以简单描述您的初步想法,也可以留空,后续让AI帮您生成"
|
||||
>
|
||||
<TextArea
|
||||
rows={4}
|
||||
placeholder="简单描述您的创作想法..."
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="小说详情"
|
||||
open={viewModalVisible}
|
||||
onCancel={() => setViewModalVisible(false)}
|
||||
width={700}
|
||||
footer={[
|
||||
<Button key="close" onClick={() => setViewModalVisible(false)}>
|
||||
关闭
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
{viewingNovel && (
|
||||
<div style={{ padding: '16px 0' }}>
|
||||
<div style={{ marginBottom: '24px' }}>
|
||||
<div style={{ fontSize: '14px', color: '#595959', marginBottom: '8px' }}>
|
||||
书名
|
||||
</div>
|
||||
<div style={{
|
||||
padding: '12px',
|
||||
background: '#f5f5f5',
|
||||
borderRadius: '6px',
|
||||
fontSize: '16px',
|
||||
fontWeight: 500
|
||||
}}>
|
||||
{viewingNovel.title}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '24px' }}>
|
||||
<div style={{ fontSize: '14px', color: '#595959', marginBottom: '8px' }}>
|
||||
题材
|
||||
</div>
|
||||
<div style={{
|
||||
padding: '12px',
|
||||
background: '#f5f5f5',
|
||||
borderRadius: '6px'
|
||||
}}>
|
||||
<Tag color="blue">{viewingNovel.genre}</Tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{viewingNovel.generatedSettings && (
|
||||
<>
|
||||
<div style={{ marginBottom: '24px' }}>
|
||||
<div style={{ fontSize: '14px', color: '#595959', marginBottom: '8px' }}>
|
||||
AI生成设定
|
||||
</div>
|
||||
<div style={{
|
||||
padding: '12px',
|
||||
background: '#f0f9ff',
|
||||
borderRadius: '6px',
|
||||
marginBottom: '12px'
|
||||
}}>
|
||||
<div><strong>目标字数:</strong>{viewingNovel.generatedSettings.targetWordCount}字</div>
|
||||
<div><strong>章节数量:</strong>{viewingNovel.generatedSettings.chapterCount}章</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div style={{ marginBottom: '24px' }}>
|
||||
<div style={{ fontSize: '14px', color: '#595959', marginBottom: '8px' }}>
|
||||
大纲
|
||||
</div>
|
||||
<div style={{
|
||||
padding: '12px',
|
||||
background: '#f5f5f5',
|
||||
borderRadius: '6px',
|
||||
lineHeight: '1.8',
|
||||
maxHeight: '200px',
|
||||
overflowY: 'auto'
|
||||
}}>
|
||||
{viewingNovel.outline || '暂无大纲'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '24px', paddingTop: '16px', borderTop: '1px solid #f0f0f0' }}>
|
||||
<div style={{ fontSize: '12px', color: '#8c8c8c' }}>
|
||||
创建时间:{new Date(viewingNovel.createdAt).toLocaleString()}
|
||||
</div>
|
||||
<div style={{ fontSize: '12px', color: '#8c8c8c', marginTop: '4px' }}>
|
||||
更新时间:{new Date(viewingNovel.updatedAt).toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NovelList;
|
||||
Reference in New Issue
Block a user