AnCao/web/src/pages/Home.tsx

1070 lines
37 KiB
TypeScript
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 React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Typography, message, Space, Avatar, Button, Modal, Form, Radio, Alert, Input, Switch, InputNumber, Dropdown, Row, Col, Card } from 'antd'
import type { MenuProps } from 'antd'
import {
FileTextOutlined,
CheckCircleOutlined,
UnorderedListOutlined,
EditOutlined,
RocketOutlined,
BookOutlined,
UserOutlined,
LogoutOutlined,
SettingOutlined,
UnorderedListOutlined as ListOutlined,
LockOutlined,
IdcardOutlined,
MoreOutlined,
CloseCircleOutlined,
FormOutlined,
FileMarkdownOutlined,
TeamOutlined,
TrophyOutlined,
CrownOutlined,
} from '@ant-design/icons'
import * as questionApi from '../api/question'
import { fetchWithAuth } from '../utils/request'
import type { Statistics } from '../types/question'
import styles from './Home.module.less'
const { Title, Paragraph, Text } = Typography
// 题型配置 - 使用数据库中的实际类型,采用明快的配色方案
const questionTypes = [
{
key: 'multiple-choice',
title: '选择题',
icon: <CheckCircleOutlined />,
color: '#1890ff', // 明亮的蓝色
description: '基础知识考察',
},
{
key: 'multiple-selection',
title: '多选题',
icon: <UnorderedListOutlined />,
color: '#52c41a', // 明亮的绿色
description: '综合能力提升',
},
{
key: 'true-false',
title: '判断题',
icon: <CloseCircleOutlined />,
color: '#fa8c16', // 明亮的橙色
description: '快速判断训练',
},
{
key: 'fill-in-blank',
title: '填空题',
icon: <FormOutlined />,
color: '#faad14', // 明亮的金色
description: '填空补充练习',
},
{
key: 'short-answer',
title: '简答题',
icon: <EditOutlined />,
color: '#722ed1', // 明亮的紫色
description: '深度理解练习',
},
{
key: 'essay', // 特殊标识,根据用户类型动态路由
title: '论述题',
icon: <FileMarkdownOutlined />,
color: '#eb2f96', // 明亮的粉色
description: '深度分析与表达',
},
]
interface UserInfo {
username: string
nickname: string
avatar: string
user_type?: string // 用户类型
}
const Home: React.FC = () => {
const navigate = useNavigate()
const [userInfo, setUserInfo] = useState<UserInfo | null>(null)
const [userTypeModalVisible, setUserTypeModalVisible] = useState(false) // 用户类型补充模态框
const [editProfileVisible, setEditProfileVisible] = useState(false) // 修改用户信息模态框
const [changePasswordVisible, setChangePasswordVisible] = useState(false) // 修改密码模态框
const [practiceSettingsVisible, setPracticeSettingsVisible] = useState(false) // 答题设置模态框
const [loading, setLoading] = useState(false)
const [userTypeForm] = Form.useForm()
const [editProfileForm] = Form.useForm()
const [changePasswordForm] = Form.useForm()
const [statistics, setStatistics] = useState<Statistics>({
total_questions: 0,
answered_questions: 0,
correct_answers: 0,
wrong_questions: 0,
total_answers: 0,
accuracy: 0,
})
// 排行榜状态
const [dailyRanking, setDailyRanking] = useState<questionApi.UserStats[]>([])
const [totalRanking, setTotalRanking] = useState<questionApi.UserStats[]>([])
const [rankingLoading, setRankingLoading] = useState(false)
const [rankingType, setRankingType] = useState<'daily' | 'total'>('daily') // 排行榜类型:每日或总榜
const [sliderPosition, setSliderPosition] = useState<'left' | 'right'>('left') // 滑块位置
// 答题设置状态
const [autoNext, setAutoNext] = useState(() => {
const saved = localStorage.getItem('autoNextEnabled')
return saved !== null ? saved === 'true' : true
})
const [autoNextDelay, setAutoNextDelay] = useState(() => {
const saved = localStorage.getItem('autoNextDelay')
return saved !== null ? parseInt(saved, 10) : 2
})
const [randomMode, setRandomMode] = useState(() => {
const saved = localStorage.getItem('randomModeEnabled')
return saved !== null ? saved === 'true' : false
})
// 加载统计数据
const loadStatistics = async () => {
try {
const res = await questionApi.getStatistics()
if (res.success && res.data) {
setStatistics(res.data)
}
} catch (error) {
console.error('加载统计失败:', error)
}
}
// 加载排行榜数据
const loadDailyRanking = async () => {
setRankingLoading(true)
try {
const res = await questionApi.getDailyRanking(10)
if (res.success && res.data) {
setDailyRanking(res.data)
}
} catch (error) {
console.error('加载排行榜失败:', error)
} finally {
setRankingLoading(false)
}
}
// 加载总排行榜数据
const loadTotalRanking = async () => {
setRankingLoading(true)
try {
const res = await questionApi.getTotalRanking(10)
if (res.success && res.data) {
setTotalRanking(res.data)
}
} catch (error) {
console.error('加载总排行榜失败:', error)
} finally {
setRankingLoading(false)
}
}
// 加载当前选中的排行榜数据
const loadCurrentRanking = async () => {
if (rankingType === 'daily') {
await loadDailyRanking()
} else {
await loadTotalRanking()
}
}
// 切换排行榜类型
const switchRankingType = (type: 'daily' | 'total') => {
setRankingType(type)
setSliderPosition(type === 'daily' ? 'left' : 'right')
}
// 加载用户信息
useEffect(() => {
const token = localStorage.getItem('token')
const savedUserInfo = localStorage.getItem('user')
if (token && savedUserInfo) {
try {
const user = JSON.parse(savedUserInfo)
setUserInfo(user)
// 检查用户是否有用户类型,如果没有则显示强制选择模态框
if (!user.user_type) {
setUserTypeModalVisible(true)
}
} catch (e) {
console.error('解析用户信息失败', e)
}
}
}, [])
useEffect(() => {
loadStatistics()
loadCurrentRanking()
}, [rankingType])
// 动态加载聊天插件(仅在首页加载)
useEffect(() => {
const script = document.createElement('script')
script.src = 'http://xiwang.nianliuxi.com:8282/chat/api/embed?protocol=http&host=xiwang.nianliuxi.com:8282&token=f131cc7227f4ee8e'
script.async = true
script.defer = true
script.id = 'chat-embed-script' // 添加 ID 以便于查找和清理
document.body.appendChild(script)
// 组件卸载时清理脚本和聊天插件元素
return () => {
// 移除脚本标签
const scriptElement = document.getElementById('chat-embed-script')
if (scriptElement) {
document.body.removeChild(scriptElement)
}
// 清理聊天插件可能创建的 DOM 元素
// 根据聊天插件的实际 DOM 结构调整选择器
const chatElements = document.querySelectorAll('[id*="chat"], [class*="chat"], [id*="embed"], [class*="embed"]')
chatElements.forEach(element => {
if (element.parentNode) {
element.parentNode.removeChild(element)
}
})
}
}, [])
// 处理用户类型更新
const handleUpdateUserType = async (values: { user_type: string }) => {
setLoading(true)
try {
const response = await fetchWithAuth('/api/user/type', {
method: 'PUT',
body: JSON.stringify(values),
})
const data = await response.json()
if (data.success) {
// 更新本地存储的用户信息
const user = JSON.parse(localStorage.getItem('user') || '{}')
user.user_type = data.data.user_type
localStorage.setItem('user', JSON.stringify(user))
setUserInfo(user)
message.success('身份类型设置成功')
setUserTypeModalVisible(false)
} else {
message.error(data.message || '更新失败')
}
} catch (err) {
message.error('网络错误,请稍后重试')
console.error('更新用户类型错误:', err)
} finally {
setLoading(false)
}
}
// 修改用户信息
const handleEditProfile = () => {
if (userInfo) {
editProfileForm.setFieldsValue({
nickname: userInfo.nickname,
user_type: userInfo.user_type,
})
setEditProfileVisible(true)
}
}
const handleUpdateProfile = async (values: { nickname: string; user_type: string }) => {
setLoading(true)
try {
const response = await fetchWithAuth('/api/user/profile', {
method: 'PUT',
body: JSON.stringify(values),
})
const data = await response.json()
if (data.success) {
// 更新本地存储的用户信息
const user = JSON.parse(localStorage.getItem('user') || '{}')
user.nickname = data.data.nickname
user.user_type = data.data.user_type
localStorage.setItem('user', JSON.stringify(user))
setUserInfo(user)
message.success('个人信息更新成功')
setEditProfileVisible(false)
} else {
message.error(data.message || '更新失败')
}
} catch (err) {
message.error('网络错误,请稍后重试')
console.error('更新用户信息错误:', err)
} finally {
setLoading(false)
}
}
// 修改密码
const handleChangePassword = async (values: { old_password: string; new_password: string }) => {
setLoading(true)
try {
const response = await fetchWithAuth('/api/user/password', {
method: 'PUT',
body: JSON.stringify(values),
})
const data = await response.json()
if (data.success) {
message.success('密码修改成功,请重新登录')
changePasswordForm.resetFields()
setChangePasswordVisible(false)
// 清除登录信息并跳转到登录页
setTimeout(() => {
localStorage.removeItem('token')
localStorage.removeItem('user')
navigate('/login')
}, 1000)
} else {
message.error(data.message || '修改失败')
}
} catch (err) {
message.error('网络错误,请稍后重试')
console.error('修改密码错误:', err)
} finally {
setLoading(false)
}
}
// 答题设置相关函数
const toggleAutoNext = () => {
const newValue = !autoNext
setAutoNext(newValue)
localStorage.setItem('autoNextEnabled', String(newValue))
}
const toggleRandomMode = () => {
const newValue = !randomMode
setRandomMode(newValue)
localStorage.setItem('randomModeEnabled', String(newValue))
}
const handleDelayChange = (value: number | null) => {
if (value !== null && value >= 1 && value <= 10) {
setAutoNextDelay(value)
localStorage.setItem('autoNextDelay', String(value))
}
}
// 获取用户类型显示文本
const getUserTypeText = (type?: string) => {
if (!type) return '未设置'
return type === 'ordinary-person' ? '普通涉密人员' : '保密管理人员'
}
// 退出登录
const handleLogout = () => {
Modal.confirm({
title: '确定要退出登录吗?',
onOk: () => {
localStorage.removeItem('token')
localStorage.removeItem('user')
setUserInfo(null)
message.success('已退出登录')
// 跳转到登录页并强制刷新,确保清理聊天插件
window.location.href = '/login'
},
})
}
// 用户菜单项
const userMenuItems: MenuProps['items'] = [
{
key: 'edit-profile',
icon: <IdcardOutlined />,
label: '修改个人信息',
onClick: handleEditProfile,
},
{
key: 'change-password',
icon: <LockOutlined />,
label: '修改密码',
onClick: () => setChangePasswordVisible(true),
},
{
key: 'practice-settings',
icon: <SettingOutlined />,
label: '答题设置',
onClick: () => setPracticeSettingsVisible(true),
},
{
type: 'divider',
},
{
key: 'logout',
icon: <LogoutOutlined />,
label: '退出登录',
danger: true,
onClick: handleLogout,
},
]
// 点击题型卡片
const handleTypeClick = async (type: string) => {
try {
// 如果是论述题,根据用户类型动态确定题型
let actualType = type
if (type === 'essay') {
if (!userInfo?.user_type) {
message.warning('请先设置您的身份类型')
return
}
// 根据用户类型选择对应的论述题
actualType = userInfo.user_type === 'ordinary-person' ? 'ordinary-essay' : 'management-essay'
}
// 加载该题型的题目列表
const res = await questionApi.getQuestions({ type: actualType })
if (res.success && res.data && res.data.length > 0) {
// 跳转到答题页面,并传递题型参数
navigate(`/question?type=${actualType}`)
} else {
message.warning('该题型暂无题目')
}
} catch (error) {
message.error('加载题目失败')
}
}
return (
<div className={styles.container}>
{/* 头部 */}
<div className={styles.header}>
<div className={styles.headerLeft}>
<div className={styles.logoArea}>
<img src="/icon.svg" alt="AnKao Logo" className={styles.logo} />
<div>
<div className={styles.titleRow}>
<Title level={2} className={styles.title}>AnKao </Title>
<span className={styles.totalBadge}>{statistics.total_questions} </span>
</div>
<Paragraph className={styles.subtitle}></Paragraph>
</div>
</div>
</div>
{/* 用户信息 */}
{userInfo && (
<div className={styles.userInfo}>
<div className={styles.userInfoContent}>
<div className={styles.avatarWrapper}>
<Avatar
src={userInfo.avatar || undefined}
size={56}
icon={<UserOutlined />}
className={styles.userAvatar}
/>
<div className={styles.avatarBadge}>
{userInfo.user_type ? (
<CheckCircleOutlined />
) : (
<SettingOutlined />
)}
</div>
</div>
<div className={styles.userDetails}>
<Text strong className={styles.userNickname}>{userInfo.nickname}</Text>
<Text type="secondary" className={styles.userUsername}>@{userInfo.username}</Text>
<div className={styles.userTypeBadge}>
<IdcardOutlined className={styles.badgeIcon} />
<Text className={styles.userTypeText}>
{getUserTypeText(userInfo.user_type)}
</Text>
</div>
</div>
<Dropdown menu={{ items: userMenuItems }} trigger={['click']} placement="bottomRight">
<div className={styles.dropdownTrigger}>
<MoreOutlined className={styles.dropdownIcon} />
</div>
</Dropdown>
</div>
<div className={styles.userStatsRow}>
<div className={styles.statItem}>
<div className={styles.statValue}>{statistics.total_answers}</div>
<div className={styles.statLabel}></div>
</div>
<div className={styles.statDivider}></div>
<div className={styles.statItem}>
<div className={styles.statValue}>{statistics.answered_questions}</div>
<div className={styles.statLabel}></div>
</div>
<div className={styles.statDivider}></div>
<div className={styles.statItem}>
<div className={styles.statValue}>{statistics.wrong_questions}</div>
<div className={styles.statLabel}></div>
</div>
<div className={styles.statDivider}></div>
<div className={styles.statItem}>
<div className={styles.statValue}>{statistics.accuracy.toFixed(0)}%</div>
<div className={styles.statLabel}></div>
</div>
</div>
</div>
)}
</div>
{/* 题型选择 */}
<div className={styles.typeSection}>
<Title level={4} className={styles.sectionTitle}>
<FileTextOutlined />
</Title>
<Row gutter={[12, 12]}>
{questionTypes.map(type => (
<Col xs={12} sm={12} md={8} lg={6} xl={4} key={type.key}>
<Card
hoverable
className={styles.typeCard}
onClick={() => handleTypeClick(type.key)}
styles={{
body: {
textAlign: 'center',
padding: '20px 12px',
}
}}
>
<div
className={styles.typeIconWrapper}
style={{
background: `linear-gradient(135deg, ${type.color}15 0%, ${type.color}08 100%)`,
borderColor: `${type.color}30`
}}
>
<div className={styles.typeIcon} style={{ color: type.color }}>
{type.icon}
</div>
</div>
<Title level={5} className={styles.typeTitle}>{type.title}</Title>
<Paragraph type="secondary" className={styles.typeDesc}>{type.description}</Paragraph>
</Card>
</Col>
))}
</Row>
</div>
{/* 快速开始 */}
<div className={styles.quickStart}>
<Title level={4} className={styles.sectionTitle}>
<RocketOutlined />
</Title>
<Row gutter={[12, 12]}>
<Col xs={24} sm={24} md={12} lg={8}>
<Card
hoverable
className={styles.quickCard}
onClick={() => navigate('/wrong-questions')}
>
<Space align="center" size="middle" style={{ width: '100%' }}>
<div
className={styles.quickIconWrapper}
style={{
background: 'linear-gradient(135deg, #fff1f0 0%, #ffe7e6 100%)',
borderColor: '#ffccc7'
}}
>
<BookOutlined className={styles.quickIcon} style={{ color: '#ff4d4f' }} />
</div>
<div style={{ flex: 1 }}>
<Title level={5} style={{ margin: 0 }}></Title>
<Paragraph type="secondary" style={{ margin: 0, fontSize: '13px' }}></Paragraph>
</div>
</Space>
</Card>
</Col>
<Col xs={24} sm={24} md={12} lg={8}>
<Card
hoverable
className={styles.quickCard}
onClick={() => navigate('/question-list')}
>
<Space align="center" size="middle" style={{ width: '100%' }}>
<div
className={styles.quickIconWrapper}
style={{
background: 'linear-gradient(135deg, #e6f7ff 0%, #d6f0ff 100%)',
borderColor: '#91caff'
}}
>
<ListOutlined className={styles.quickIcon} style={{ color: '#1890ff' }} />
</div>
<div style={{ flex: 1 }}>
<Title level={5} style={{ margin: 0 }}></Title>
<Paragraph type="secondary" style={{ margin: 0, fontSize: '13px' }}></Paragraph>
</div>
</Space>
</Card>
</Col>
<Col xs={24} sm={24} md={12} lg={8}>
<Card
hoverable
className={styles.quickCard}
onClick={() => navigate('/exam/management')}
>
<Space align="center" size="middle" style={{ width: '100%' }}>
<div
className={styles.quickIconWrapper}
style={{
background: 'linear-gradient(135deg, #fff7e6 0%, #ffe7ba 100%)',
borderColor: '#ffd591'
}}
>
<FileTextOutlined className={styles.quickIcon} style={{ color: '#fa8c16' }} />
</div>
<div style={{ flex: 1 }}>
<Title level={5} style={{ margin: 0 }}></Title>
<Paragraph type="secondary" style={{ margin: 0, fontSize: '13px' }}>线</Paragraph>
</div>
</Space>
</Card>
</Col>
{/* 仅 yanlongqi 用户显示题库管理 */}
{userInfo?.username === 'yanlongqi' && (
<>
<Col xs={24} sm={24} md={12} lg={8}>
<Card
hoverable
className={styles.quickCard}
onClick={() => navigate('/question-management')}
>
<Space align="center" size="middle" style={{ width: '100%' }}>
<div
className={styles.quickIconWrapper}
style={{
background: 'linear-gradient(135deg, #e6fffb 0%, #d6f5f0 100%)',
borderColor: '#87e8de'
}}
>
<SettingOutlined className={styles.quickIcon} style={{ color: '#36cfc9' }} />
</div>
<div style={{ flex: 1 }}>
<Title level={5} style={{ margin: 0 }}></Title>
<Paragraph type="secondary" style={{ margin: 0, fontSize: '13px' }}></Paragraph>
</div>
</Space>
</Card>
</Col>
<Col xs={24} sm={24} md={12} lg={8}>
<Card
hoverable
className={styles.quickCard}
onClick={() => navigate('/user-management')}
>
<Space align="center" size="middle" style={{ width: '100%' }}>
<div
className={styles.quickIconWrapper}
style={{
background: 'linear-gradient(135deg, #f0f5ff 0%, #e6edff 100%)',
borderColor: '#adc6ff'
}}
>
<TeamOutlined className={styles.quickIcon} style={{ color: '#597ef7' }} />
</div>
<div style={{ flex: 1 }}>
<Title level={5} style={{ margin: 0 }}></Title>
<Paragraph type="secondary" style={{ margin: 0, fontSize: '13px' }}></Paragraph>
</div>
</Space>
</Card>
</Col>
</>
)}
</Row>
</div>
{/* 排行榜 */}
<div className={styles.rankingSection}>
<Title level={4} className={styles.sectionTitle}>
<TrophyOutlined />
</Title>
<div className={styles.rankingSwitch}>
<div
className={`${styles.rankingSwitchButton} ${rankingType === 'daily' ? styles.active : ''}`}
onClick={() => switchRankingType('daily')}
>
</div>
<div
className={`${styles.rankingSwitchButton} ${rankingType === 'total' ? styles.active : ''}`}
onClick={() => switchRankingType('total')}
>
</div>
<div
className={styles.rankingSwitchSlider}
style={{
width: 'calc(50% - 4px)',
left: sliderPosition === 'left' ? '4px' : 'calc(50% + 0px)',
}}
/>
</div>
{rankingLoading ? (
<Card className={styles.rankingCard} loading={true} />
) : rankingType === 'daily' ? (
dailyRanking.length === 0 ? (
<Card className={styles.rankingCard}>
<div style={{ textAlign: 'center', padding: '40px 20px', color: '#8c8c8c' }}>
<TrophyOutlined style={{ fontSize: 48, marginBottom: 16, opacity: 0.3 }} />
<div></div>
<div style={{ fontSize: 13, marginTop: 8 }}></div>
</div>
</Card>
) : (
<Card className={styles.rankingCard}>
<div className={styles.rankingList}>
{dailyRanking.map((user, index) => (
<div key={user.user_id} className={styles.rankingItem}>
<div className={styles.rankingLeft}>
{index < 3 ? (
<div className={`${styles.rankBadge} ${styles[`rank${index + 1}`]}`}>
{index === 0 && <CrownOutlined />}
{index === 1 && <CrownOutlined />}
{index === 2 && <CrownOutlined />}
</div>
) : (
<div className={styles.rankNumber}>{index + 1}</div>
)}
<Avatar
src={user.avatar || undefined}
size={40}
icon={<UserOutlined />}
className={styles.rankAvatar}
/>
<div className={styles.rankUserInfo}>
<div className={styles.rankNickname}>{user.nickname}</div>
<div className={styles.rankUsername}>@{user.username}</div>
</div>
</div>
<div className={styles.rankingRight}>
<div className={styles.rankStat}>
<div className={styles.rankStatValue}>{user.total_answers}</div>
<div className={styles.rankStatLabel}></div>
</div>
<div className={styles.rankDivider}></div>
<div className={styles.rankStat}>
<div className={styles.rankStatValue} style={{ color: user.accuracy >= 80 ? '#52c41a' : user.accuracy >= 60 ? '#faad14' : '#ff4d4f' }}>
{user.accuracy.toFixed(0)}%
</div>
<div className={styles.rankStatLabel}></div>
</div>
</div>
</div>
))}
</div>
</Card>
)
) : totalRanking.length === 0 ? (
<Card className={styles.rankingCard}>
<div style={{ textAlign: 'center', padding: '40px 20px', color: '#8c8c8c' }}>
<TrophyOutlined style={{ fontSize: 48, marginBottom: 16, opacity: 0.3 }} />
<div></div>
<div style={{ fontSize: 13, marginTop: 8 }}></div>
</div>
</Card>
) : (
<Card className={styles.rankingCard}>
<div className={styles.rankingList}>
{totalRanking.map((user, index) => (
<div key={user.user_id} className={styles.rankingItem}>
<div className={styles.rankingLeft}>
{index < 3 ? (
<div className={`${styles.rankBadge} ${styles[`rank${index + 1}`]}`}>
{index === 0 && <CrownOutlined />}
{index === 1 && <CrownOutlined />}
{index === 2 && <CrownOutlined />}
</div>
) : (
<div className={styles.rankNumber}>{index + 1}</div>
)}
<Avatar
src={user.avatar || undefined}
size={40}
icon={<UserOutlined />}
className={styles.rankAvatar}
/>
<div className={styles.rankUserInfo}>
<div className={styles.rankNickname}>{user.nickname}</div>
<div className={styles.rankUsername}>@{user.username}</div>
</div>
</div>
<div className={styles.rankingRight}>
<div className={styles.rankStat}>
<div className={styles.rankStatValue}>{user.total_answers}</div>
<div className={styles.rankStatLabel}></div>
</div>
<div className={styles.rankDivider}></div>
<div className={styles.rankStat}>
<div className={styles.rankStatValue} style={{ color: user.accuracy >= 80 ? '#52c41a' : user.accuracy >= 60 ? '#faad14' : '#ff4d4f' }}>
{user.accuracy.toFixed(0)}%
</div>
<div className={styles.rankStatLabel}></div>
</div>
</div>
</div>
))}
</div>
</Card>
)}
</div>
{/* 用户类型补充模态框 */}
<Modal
title="请选择您的身份类型"
open={userTypeModalVisible}
closable={false}
maskClosable={false}
keyboard={false}
footer={null}
destroyOnClose
>
<Alert
message="论述题需要使用"
description="为了更好地为您提供相应的论述题内容,请选择您的身份类型。"
type="info"
showIcon
style={{ marginBottom: 24 }}
/>
<Form
form={userTypeForm}
name="userType"
onFinish={handleUpdateUserType}
autoComplete="off"
size="large"
>
<Form.Item
name="user_type"
label="身份类型"
rules={[{ required: true, message: '请选择身份类型' }]}
>
<Radio.Group>
<Radio value="ordinary-person"></Radio>
<Radio value="management-person"></Radio>
</Radio.Group>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" block loading={loading}>
</Button>
</Form.Item>
</Form>
</Modal>
{/* 修改个人信息模态框 */}
<Modal
title="修改个人信息"
open={editProfileVisible}
onCancel={() => setEditProfileVisible(false)}
footer={null}
destroyOnClose
>
<Form
form={editProfileForm}
name="editProfile"
onFinish={handleUpdateProfile}
autoComplete="off"
layout="vertical"
>
<Form.Item
name="nickname"
label="姓名"
rules={[{ required: true, message: '请输入姓名' }]}
>
<Input prefix={<UserOutlined />} placeholder="请输入姓名" />
</Form.Item>
<Form.Item
name="user_type"
label="身份类型"
rules={[{ required: true, message: '请选择身份类型' }]}
>
<Radio.Group>
<Radio value="ordinary-person"></Radio>
<Radio value="management-person"></Radio>
</Radio.Group>
</Form.Item>
<Form.Item>
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
<Button onClick={() => setEditProfileVisible(false)}>
</Button>
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
</Space>
</Form.Item>
</Form>
</Modal>
{/* 修改密码模态框 */}
<Modal
title="修改密码"
open={changePasswordVisible}
onCancel={() => setChangePasswordVisible(false)}
footer={null}
destroyOnClose
>
<Alert
message="密码修改后需要重新登录"
type="warning"
showIcon
style={{ marginBottom: 16 }}
/>
<Form
form={changePasswordForm}
name="changePassword"
onFinish={handleChangePassword}
autoComplete="off"
layout="vertical"
>
<Form.Item
name="old_password"
label="当前密码"
rules={[{ required: true, message: '请输入当前密码' }]}
>
<Input.Password prefix={<LockOutlined />} placeholder="请输入当前密码" />
</Form.Item>
<Form.Item
name="new_password"
label="新密码"
rules={[
{ required: true, message: '请输入新密码' },
{ min: 6, message: '密码长度至少为6位' },
]}
>
<Input.Password prefix={<LockOutlined />} placeholder="请输入新密码至少6位" />
</Form.Item>
<Form.Item
name="confirm_password"
label="确认新密码"
dependencies={['new_password']}
rules={[
{ required: true, message: '请确认新密码' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('new_password') === value) {
return Promise.resolve()
}
return Promise.reject(new Error('两次输入的密码不一致'))
},
}),
]}
>
<Input.Password prefix={<LockOutlined />} placeholder="请再次输入新密码" />
</Form.Item>
<Form.Item>
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
<Button onClick={() => setChangePasswordVisible(false)}>
</Button>
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
</Space>
</Form.Item>
</Form>
</Modal>
{/* 答题设置模态框 */}
<Modal
title="答题设置"
open={practiceSettingsVisible}
onCancel={() => setPracticeSettingsVisible(false)}
footer={[
<Button key="close" type="primary" onClick={() => setPracticeSettingsVisible(false)}>
</Button>
]}
width={480}
>
<Space direction="vertical" style={{ width: '100%' }} size="large">
<Alert
description="这些设置会在答题页面中生效,帮助您更高效地刷题。"
type="info"
showIcon
/>
<div>
<div style={{ marginBottom: 12 }}>
<span style={{ fontSize: 15, fontWeight: 500 }}></span>
<span style={{ marginLeft: 8, fontSize: 13, color: '#8c8c8c' }}>
</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, paddingLeft: 8 }}>
<Switch
checked={autoNext}
onChange={toggleAutoNext}
/>
<span style={{ fontSize: 14, color: autoNext ? '#52c41a' : '#8c8c8c' }}>
{autoNext ? '已开启' : '已关闭'}
</span>
</div>
</div>
{autoNext && (
<div>
<div style={{ marginBottom: 12 }}>
<span style={{ fontSize: 15, fontWeight: 500 }}></span>
<span style={{ marginLeft: 8, fontSize: 13, color: '#8c8c8c' }}>
</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, paddingLeft: 8 }}>
<InputNumber
min={1}
max={10}
value={autoNextDelay}
onChange={handleDelayChange}
style={{ width: 80 }}
/>
<span style={{ fontSize: 14 }}></span>
</div>
</div>
)}
<div>
<div style={{ marginBottom: 12 }}>
<span style={{ fontSize: 15, fontWeight: 500 }}></span>
<span style={{ marginLeft: 8, fontSize: 13, color: '#8c8c8c' }}>
</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, paddingLeft: 8 }}>
<Switch
checked={randomMode}
onChange={toggleRandomMode}
/>
<span style={{ fontSize: 14, color: randomMode ? '#52c41a' : '#8c8c8c' }}>
{randomMode ? '已开启' : '已关闭'}
</span>
</div>
</div>
</Space>
</Modal>
</div>
)
}
export default Home