update:1.更新根据分析建议重新生成章节内容
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Spin, Result, Button } from 'antd';
|
||||
import { Spin, Result, Button, Modal, Input, message } from 'antd';
|
||||
import { authApi } from '../services/api';
|
||||
import AnnouncementModal from '../components/AnnouncementModal';
|
||||
|
||||
@@ -9,6 +9,11 @@ export default function AuthCallback() {
|
||||
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [showAnnouncement, setShowAnnouncement] = useState(false);
|
||||
const [showPasswordModal, setShowPasswordModal] = useState(false);
|
||||
const [passwordStatus, setPasswordStatus] = useState<any>(null);
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [settingPassword, setSettingPassword] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleCallback = async () => {
|
||||
@@ -17,8 +22,21 @@ export default function AuthCallback() {
|
||||
// 这里只需要验证登录状态
|
||||
await authApi.getCurrentUser();
|
||||
|
||||
// 检查密码状态
|
||||
const pwdStatus = await authApi.getPasswordStatus();
|
||||
setPasswordStatus(pwdStatus);
|
||||
|
||||
setStatus('success');
|
||||
|
||||
// 只有在用户完全没有密码时才显示密码设置提示
|
||||
// 如果已经有密码(无论是默认密码还是自定义密码),都不再提示
|
||||
if (!pwdStatus.has_password) {
|
||||
setTimeout(() => {
|
||||
setShowPasswordModal(true);
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// 从 sessionStorage 获取重定向地址
|
||||
const redirect = sessionStorage.getItem('login_redirect') || '/';
|
||||
sessionStorage.removeItem('login_redirect');
|
||||
@@ -105,6 +123,70 @@ export default function AuthCallback() {
|
||||
localStorage.setItem('announcement_do_not_show_until', tomorrow.getTime().toString());
|
||||
};
|
||||
|
||||
const handleSetPassword = async () => {
|
||||
if (!newPassword) {
|
||||
message.error('请输入新密码');
|
||||
return;
|
||||
}
|
||||
if (newPassword.length < 6) {
|
||||
message.error('密码长度至少为6个字符');
|
||||
return;
|
||||
}
|
||||
if (newPassword !== confirmPassword) {
|
||||
message.error('两次输入的密码不一致');
|
||||
return;
|
||||
}
|
||||
|
||||
setSettingPassword(true);
|
||||
try {
|
||||
await authApi.setPassword(newPassword);
|
||||
message.success('密码设置成功');
|
||||
setShowPasswordModal(false);
|
||||
|
||||
// 继续后续流程
|
||||
const redirect = sessionStorage.getItem('login_redirect') || '/';
|
||||
sessionStorage.removeItem('login_redirect');
|
||||
|
||||
const doNotShowUntil = localStorage.getItem('announcement_do_not_show_until');
|
||||
const now = new Date().getTime();
|
||||
|
||||
if (!doNotShowUntil || now > parseInt(doNotShowUntil)) {
|
||||
setTimeout(() => {
|
||||
setShowAnnouncement(true);
|
||||
}, 500);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
navigate(redirect);
|
||||
}, 500);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('密码设置失败,请重试');
|
||||
} finally {
|
||||
setSettingPassword(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSkipPasswordSetting = () => {
|
||||
setShowPasswordModal(false);
|
||||
|
||||
// 继续后续流程
|
||||
const redirect = sessionStorage.getItem('login_redirect') || '/';
|
||||
sessionStorage.removeItem('login_redirect');
|
||||
|
||||
const doNotShowUntil = localStorage.getItem('announcement_do_not_show_until');
|
||||
const now = new Date().getTime();
|
||||
|
||||
if (!doNotShowUntil || now > parseInt(doNotShowUntil)) {
|
||||
setTimeout(() => {
|
||||
setShowAnnouncement(true);
|
||||
}, 500);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
navigate(redirect);
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnnouncementModal
|
||||
@@ -112,6 +194,62 @@ export default function AuthCallback() {
|
||||
onClose={handleAnnouncementClose}
|
||||
onDoNotShowToday={handleDoNotShowToday}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title="设置账号密码"
|
||||
open={showPasswordModal}
|
||||
centered
|
||||
onOk={handleSetPassword}
|
||||
onCancel={handleSkipPasswordSetting}
|
||||
confirmLoading={settingPassword}
|
||||
okText="设置密码"
|
||||
cancelText="暂不设置"
|
||||
width={500}
|
||||
>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<p>您已成功通过 Linux DO 授权登录!</p>
|
||||
<p>系统已为您自动生成默认密码,您可以选择设置自定义密码或继续使用默认密码。</p>
|
||||
{passwordStatus?.default_password && (
|
||||
<div style={{
|
||||
background: '#f0f2f5',
|
||||
padding: 12,
|
||||
borderRadius: 4,
|
||||
marginTop: 12
|
||||
}}>
|
||||
<strong>账号:</strong>{passwordStatus.username}<br/>
|
||||
<strong>默认密码:</strong><code style={{
|
||||
background: '#fff',
|
||||
padding: '2px 8px',
|
||||
borderRadius: 3,
|
||||
color: '#1890ff',
|
||||
fontSize: 14
|
||||
}}>{passwordStatus.default_password}</code>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<label>新密码(至少6个字符):</label>
|
||||
<Input.Password
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
placeholder="请输入新密码"
|
||||
style={{ marginTop: 4 }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>确认密码:</label>
|
||||
<Input.Password
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
placeholder="请再次输入密码"
|
||||
style={{ marginTop: 4 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
@@ -122,7 +260,7 @@ export default function AuthCallback() {
|
||||
<Result
|
||||
status="success"
|
||||
title="登录成功"
|
||||
subTitle={showAnnouncement ? "欢迎使用..." : "正在跳转..."}
|
||||
subTitle={showPasswordModal ? "请设置账号密码..." : (showAnnouncement ? "欢迎使用..." : "正在跳转...")}
|
||||
style={{ background: 'white', padding: 40, borderRadius: 8 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -600,6 +600,7 @@ export default function MCPPluginsPage() {
|
||||
<Modal
|
||||
title={editingPlugin ? '编辑插件' : '添加插件'}
|
||||
open={modalVisible}
|
||||
centered
|
||||
onCancel={() => {
|
||||
setModalVisible(false);
|
||||
form.resetFields();
|
||||
|
||||
@@ -73,23 +73,8 @@ export default function ProjectList() {
|
||||
};
|
||||
|
||||
const handleEnterProject = (id: string) => {
|
||||
const project = projects.find(p => p.id === id);
|
||||
if (project) {
|
||||
console.log('项目信息:', {
|
||||
id: project.id,
|
||||
title: project.title,
|
||||
wizard_status: project.wizard_status,
|
||||
wizard_step: project.wizard_step
|
||||
});
|
||||
|
||||
if (project.wizard_status === 'incomplete' || !project.wizard_status) {
|
||||
console.log('向导未完成,跳转到向导页面');
|
||||
navigate(`/wizard?projectId=${id}&step=${project.wizard_step || 0}`);
|
||||
} else {
|
||||
console.log('向导已完成,进入项目管理界面');
|
||||
navigate(`/project/${id}`);
|
||||
}
|
||||
}
|
||||
// 简化后直接进入项目,不再检查向导状态
|
||||
navigate(`/project/${id}`);
|
||||
};
|
||||
|
||||
const getStatusTag = (status: string) => {
|
||||
@@ -207,8 +192,8 @@ export default function ProjectList() {
|
||||
setSelectedProjectIds([]);
|
||||
};
|
||||
|
||||
// 获取可导出的项目(过滤掉向导未完成的项目)
|
||||
const exportableProjects = projects.filter(p => p.wizard_status === 'completed');
|
||||
// 获取所有可导出的项目
|
||||
const exportableProjects = projects;
|
||||
|
||||
// 关闭导出对话框
|
||||
const handleCloseExportModal = () => {
|
||||
@@ -631,12 +616,11 @@ export default function ProjectList() {
|
||||
<Row gutter={[16, 16]}>
|
||||
{projects.map((project) => {
|
||||
const progress = getProgress(project.current_words, project.target_words || 0);
|
||||
const isWizardComplete = project.wizard_status === 'completed';
|
||||
|
||||
return (
|
||||
<Col {...gridConfig} key={project.id}>
|
||||
<Badge.Ribbon
|
||||
text={isWizardComplete ? getStatusTag(project.status) : <Tag color="orange" icon={<RocketOutlined />}>创建中</Tag>}
|
||||
text={getStatusTag(project.status)}
|
||||
color="transparent"
|
||||
style={{ top: 12, right: 12 }}
|
||||
>
|
||||
@@ -680,69 +664,50 @@ export default function ProjectList() {
|
||||
{project.description || '暂无描述'}
|
||||
</Paragraph>
|
||||
|
||||
{isWizardComplete ? (
|
||||
<>
|
||||
{project.target_words && project.target_words > 0 && (
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>完成进度</Text>
|
||||
<Text strong style={{ fontSize: 12 }}>{progress}%</Text>
|
||||
</div>
|
||||
<Progress
|
||||
percent={progress}
|
||||
strokeColor={getProgressColor(progress)}
|
||||
showInfo={false}
|
||||
size={{ height: 8 }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Row gutter={12}>
|
||||
<Col span={12}>
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
padding: '12px 0',
|
||||
background: '#f5f5f5',
|
||||
borderRadius: 8
|
||||
}}>
|
||||
<div style={{ fontSize: 20, fontWeight: 'bold', color: '#1890ff' }}>
|
||||
{(project.current_words / 1000).toFixed(1)}K
|
||||
</div>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>已写字数</Text>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
padding: '12px 0',
|
||||
background: '#f5f5f5',
|
||||
borderRadius: 8
|
||||
}}>
|
||||
<div style={{ fontSize: 20, fontWeight: 'bold', color: '#52c41a' }}>
|
||||
{project.target_words ? (project.target_words / 1000).toFixed(0) + 'K' : '--'}
|
||||
</div>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>目标字数</Text>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
) : (
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
padding: '24px 0',
|
||||
background: '#f5f5f5',
|
||||
borderRadius: 8
|
||||
}}>
|
||||
<RocketOutlined style={{ fontSize: 32, color: '#faad14', marginBottom: 12 }} />
|
||||
<div style={{ color: '#faad14', fontWeight: 'bold', marginBottom: 4 }}>
|
||||
项目创建中
|
||||
{project.target_words && project.target_words > 0 && (
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>完成进度</Text>
|
||||
<Text strong style={{ fontSize: 12 }}>{progress}%</Text>
|
||||
</div>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
点击继续创建向导
|
||||
</Text>
|
||||
<Progress
|
||||
percent={progress}
|
||||
strokeColor={getProgressColor(progress)}
|
||||
showInfo={false}
|
||||
size={{ height: 8 }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Row gutter={12}>
|
||||
<Col span={12}>
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
padding: '12px 0',
|
||||
background: '#f5f5f5',
|
||||
borderRadius: 8
|
||||
}}>
|
||||
<div style={{ fontSize: 20, fontWeight: 'bold', color: '#1890ff' }}>
|
||||
{(project.current_words / 1000).toFixed(1)}K
|
||||
</div>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>已写字数</Text>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
padding: '12px 0',
|
||||
background: '#f5f5f5',
|
||||
borderRadius: 8
|
||||
}}>
|
||||
<div style={{ fontSize: 20, fontWeight: 'bold', color: '#52c41a' }}>
|
||||
{project.target_words ? (project.target_words / 1000).toFixed(0) + 'K' : '--'}
|
||||
</div>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>目标字数</Text>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<div style={{
|
||||
marginTop: 16,
|
||||
paddingTop: 16,
|
||||
|
||||
+307
-1182
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,18 @@
|
||||
import { Card, Descriptions, Empty, Typography } from 'antd';
|
||||
import { GlobalOutlined } from '@ant-design/icons';
|
||||
import { Card, Descriptions, Empty, Typography, Button, Modal, Form, Input, message } from 'antd';
|
||||
import { GlobalOutlined, EditOutlined } from '@ant-design/icons';
|
||||
import { useState } from 'react';
|
||||
import { useStore } from '../store';
|
||||
import { cardStyles } from '../components/CardStyles';
|
||||
import { projectApi } from '../services/api';
|
||||
|
||||
const { Title, Paragraph } = Typography;
|
||||
const { TextArea } = Input;
|
||||
|
||||
export default function WorldSetting() {
|
||||
const { currentProject } = useStore();
|
||||
const { currentProject, setCurrentProject } = useStore();
|
||||
const [isEditModalVisible, setIsEditModalVisible] = useState(false);
|
||||
const [editForm] = Form.useForm();
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
if (!currentProject) return null;
|
||||
|
||||
@@ -62,10 +68,28 @@ export default function WorldSetting() {
|
||||
marginBottom: 24,
|
||||
borderBottom: '1px solid #f0f0f0',
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between'
|
||||
}}>
|
||||
<GlobalOutlined style={{ fontSize: 24, marginRight: 12, color: '#1890ff' }} />
|
||||
<h2 style={{ margin: 0 }}>世界设定</h2>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<GlobalOutlined style={{ fontSize: 24, marginRight: 12, color: '#1890ff' }} />
|
||||
<h2 style={{ margin: 0 }}>世界设定</h2>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => {
|
||||
editForm.setFieldsValue({
|
||||
world_time_period: currentProject.world_time_period || '',
|
||||
world_location: currentProject.world_location || '',
|
||||
world_atmosphere: currentProject.world_atmosphere || '',
|
||||
world_rules: currentProject.world_rules || '',
|
||||
});
|
||||
setIsEditModalVisible(true);
|
||||
}}
|
||||
>
|
||||
编辑世界观
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 可滚动内容区域 */}
|
||||
@@ -182,6 +206,102 @@ export default function WorldSetting() {
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 编辑世界观模态框 */}
|
||||
<Modal
|
||||
title="编辑世界观"
|
||||
open={isEditModalVisible}
|
||||
centered
|
||||
onCancel={() => {
|
||||
setIsEditModalVisible(false);
|
||||
editForm.resetFields();
|
||||
}}
|
||||
onOk={async () => {
|
||||
try {
|
||||
const values = await editForm.validateFields();
|
||||
setIsSaving(true);
|
||||
|
||||
const updatedProject = await projectApi.updateProject(currentProject.id, {
|
||||
world_time_period: values.world_time_period,
|
||||
world_location: values.world_location,
|
||||
world_atmosphere: values.world_atmosphere,
|
||||
world_rules: values.world_rules,
|
||||
});
|
||||
|
||||
setCurrentProject(updatedProject);
|
||||
message.success('世界观更新成功');
|
||||
setIsEditModalVisible(false);
|
||||
editForm.resetFields();
|
||||
} catch (error) {
|
||||
console.error('更新世界观失败:', error);
|
||||
message.error('更新失败,请重试');
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
}}
|
||||
confirmLoading={isSaving}
|
||||
width={800}
|
||||
okText="保存"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Form
|
||||
form={editForm}
|
||||
layout="vertical"
|
||||
style={{ marginTop: 16 }}
|
||||
>
|
||||
<Form.Item
|
||||
label="时间设定"
|
||||
name="world_time_period"
|
||||
rules={[{ required: true, message: '请输入时间设定' }]}
|
||||
>
|
||||
<TextArea
|
||||
rows={4}
|
||||
placeholder="描述故事发生的时代背景..."
|
||||
showCount
|
||||
maxLength={1000}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="地点设定"
|
||||
name="world_location"
|
||||
rules={[{ required: true, message: '请输入地点设定' }]}
|
||||
>
|
||||
<TextArea
|
||||
rows={4}
|
||||
placeholder="描述故事发生的地理位置和环境..."
|
||||
showCount
|
||||
maxLength={1000}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="氛围设定"
|
||||
name="world_atmosphere"
|
||||
rules={[{ required: true, message: '请输入氛围设定' }]}
|
||||
>
|
||||
<TextArea
|
||||
rows={4}
|
||||
placeholder="描述故事的整体氛围和基调..."
|
||||
showCount
|
||||
maxLength={1000}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="规则设定"
|
||||
name="world_rules"
|
||||
rules={[{ required: true, message: '请输入规则设定' }]}
|
||||
>
|
||||
<TextArea
|
||||
rows={4}
|
||||
placeholder="描述这个世界的特殊规则和设定..."
|
||||
showCount
|
||||
maxLength={1000}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user