优化答题页面导航系统和UI体验

主要改进:
- 新增题目导航抽屉组件,支持快速跳转到任意题目
- 新增悬浮球导航按钮,实时显示答题进度和统计信息
- 优化顶部导航栏,移除进度条,简化为返回、标题和设置三个按钮
- 将答题设置改为弹窗模式,提供更好的交互体验
- 优化题目列表卡片设计,减小高度使其更紧凑
- 题目列表显示题号、分类标签、题目内容和答题状态
- 支持答题进度持久化,刷新页面不丢失进度

技术细节:
- 使用 Ant Design 的 Drawer、Modal、Tag 等组件
- 采用 CSS Modules 实现样式隔离
- 使用 LocalStorage 保存答题进度和设置
- 响应式设计,适配移动端和PC端
- 修复 TypeScript 编译错误

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
yanlongqi 2025-11-07 17:45:53 +08:00
parent 3b7133d9de
commit fabc5c8f3e
8 changed files with 670 additions and 137 deletions

View File

@ -0,0 +1,136 @@
.drawer {
:global {
.ant-drawer-body {
padding: 0;
background: #fafafa;
}
.ant-drawer-header {
border-bottom: 1px solid #e8e8e8;
background: #fff;
padding: 16px 20px;
}
.ant-drawer-title {
font-weight: 600;
font-size: 16px;
}
}
}
.listItem {
padding: 0;
margin: 8px 12px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 12px;
background: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
overflow: visible;
border: 1px solid transparent;
position: relative;
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
transform: translateY(-2px);
border-color: rgba(22, 119, 255, 0.2);
}
&.current {
background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%);
border-color: #1677ff;
box-shadow: 0 4px 16px rgba(22, 119, 255, 0.15);
&:hover {
box-shadow: 0 6px 20px rgba(22, 119, 255, 0.2);
transform: translateY(-2px);
}
}
}
.questionItem {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 10px 12px;
}
.questionNumber {
flex-shrink: 0;
width: 26px;
height: 26px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 600;
color: #595959;
background: rgba(0, 0, 0, 0.04);
border-radius: 6px;
}
.current .questionNumber {
color: #1677ff;
background: rgba(22, 119, 255, 0.1);
font-weight: 700;
}
.questionContent {
flex: 1;
min-width: 0;
}
.questionStatus {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
:global {
.anticon {
font-size: 20px;
}
}
}
// 移动端适配
@media (max-width: 768px) {
.drawer {
:global {
.ant-drawer {
width: 90vw !important;
max-width: 450px;
}
.ant-drawer-header {
padding: 14px 16px;
}
}
}
.listItem {
margin: 6px 10px;
border-radius: 10px;
}
.questionItem {
padding: 8px 10px;
gap: 6px;
}
.questionNumber {
width: 24px;
height: 24px;
font-size: 12px;
border-radius: 5px;
}
.questionStatus {
:global {
.anticon {
font-size: 18px;
}
}
}
}

View File

@ -0,0 +1,108 @@
import React from 'react'
import { Drawer, List, Tag, Typography, Space } from 'antd'
import {
CheckCircleOutlined,
CloseCircleOutlined,
MinusCircleOutlined,
BookOutlined,
} from '@ant-design/icons'
import type { Question } from '../types/question'
import styles from './QuestionDrawer.module.less'
const { Text } = Typography
interface QuestionDrawerProps {
visible: boolean
onClose: () => void
questions: Question[]
currentIndex: number
onQuestionSelect: (index: number) => void
answeredStatus: Map<number, boolean | null> // null: 未答, true: 正确, false: 错误
}
const QuestionDrawer: React.FC<QuestionDrawerProps> = ({
visible,
onClose,
questions,
currentIndex,
onQuestionSelect,
answeredStatus,
}) => {
// 获取题目状态
const getQuestionStatus = (index: number) => {
const status = answeredStatus.get(index)
if (status === null || status === undefined) {
return { icon: <MinusCircleOutlined />, color: '#d9d9d9', text: '未答' }
}
if (status) {
return { icon: <CheckCircleOutlined />, color: '#52c41a', text: '正确' }
}
return { icon: <CloseCircleOutlined />, color: '#ff4d4f', text: '错误' }
}
return (
<Drawer
title={
<Space>
<BookOutlined />
<span></span>
<Text type="secondary" style={{ fontSize: 14, fontWeight: 'normal' }}>
{questions.length}
</Text>
</Space>
}
placement="right"
onClose={onClose}
open={visible}
width={450}
className={styles.drawer}
>
<List
dataSource={questions}
renderItem={(question, index) => {
const status = getQuestionStatus(index)
const isCurrent = index === currentIndex
return (
<List.Item
key={question.id}
className={`${styles.listItem} ${isCurrent ? styles.current : ''}`}
onClick={() => {
onQuestionSelect(index)
onClose()
}}
>
<div className={styles.questionItem}>
{/* 题号 */}
<div className={styles.questionNumber}>
{index + 1}
</div>
{/* 分类标签 */}
<Tag color="blue" style={{ margin: 0, flexShrink: 0 }}>
{question.category}
</Tag>
{/* 题目内容 */}
<div className={styles.questionContent}>
<Text ellipsis={{ tooltip: question.content }} style={{ fontSize: 14, color: '#262626' }}>
{question.content}
</Text>
</div>
{/* 右侧状态 */}
<div className={styles.questionStatus}>
<div style={{ color: status.color, fontSize: 20 }}>
{status.icon}
</div>
</div>
</div>
</List.Item>
)
}}
/>
</Drawer>
)
}
export default QuestionDrawer

View File

@ -0,0 +1,184 @@
.floatButtonWrapper {
position: fixed;
right: 20px;
bottom: 90px;
z-index: 999;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.statsCard {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-radius: 12px;
padding: 8px 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08),
0 2px 6px rgba(0, 0, 0, 0.04);
display: flex;
align-items: center;
gap: 12px;
opacity: 0;
transform: translateY(10px);
animation: slideIn 0.3s ease forwards 0.2s;
border: 1px solid rgba(0, 0, 0, 0.06);
}
@keyframes slideIn {
to {
opacity: 1;
transform: translateY(0);
}
}
.statsRow {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
}
.statsLabel {
font-size: 11px;
color: #8c8c8c;
font-weight: 500;
line-height: 1;
}
.statsValue {
font-size: 16px;
font-weight: 700;
color: #1d1d1f;
line-height: 1;
}
.statsDivider {
width: 1px;
height: 20px;
background: rgba(0, 0, 0, 0.08);
}
.floatButton {
width: 64px;
height: 64px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.4),
0 8px 32px rgba(102, 126, 234, 0.25);
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
justify-content: center;
user-select: none;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
position: relative;
&:hover {
transform: scale(1.1) translateY(-4px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5),
0 12px 40px rgba(102, 126, 234, 0.35);
}
&:active {
transform: scale(1.05) translateY(-2px);
transition: all 0.1s ease;
}
}
.progressRing {
position: absolute;
top: 0;
left: 0;
width: 64px;
height: 64px;
transform: rotate(-90deg);
pointer-events: none;
}
.progressRingCircle {
opacity: 0.15;
stroke: #fff;
}
.progressRingCircleProgress {
transition: stroke-dashoffset 0.6s cubic-bezier(0.4, 0, 0.2, 1);
stroke-linecap: round;
stroke: #fff;
filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.5));
}
.floatButtonContent {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #fff;
z-index: 1;
position: relative;
}
.icon {
font-size: 24px;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
}
// 移动端适配
@media (max-width: 768px) {
.floatButtonWrapper {
right: 16px;
bottom: 80px;
}
.statsCard {
padding: 6px 12px;
gap: 10px;
border-radius: 10px;
}
.statsLabel {
font-size: 10px;
}
.statsValue {
font-size: 14px;
}
.statsDivider {
height: 18px;
}
.floatButton {
width: 58px;
height: 58px;
}
.progressRing {
width: 58px;
height: 58px;
}
.icon {
font-size: 22px;
}
}
// 添加微妙的脉冲动画
@keyframes subtlePulse {
0%, 100% {
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.4),
0 8px 32px rgba(102, 126, 234, 0.25);
}
50% {
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.5),
0 8px 32px rgba(102, 126, 234, 0.35);
}
}
.floatButton {
animation: subtlePulse 3s ease-in-out infinite;
}

View File

@ -0,0 +1,89 @@
import React from 'react'
import { UnorderedListOutlined } from '@ant-design/icons'
import styles from './QuestionFloatButton.module.less'
interface QuestionFloatButtonProps {
currentIndex: number
totalQuestions: number
onClick: () => void
correctCount: number
wrongCount: number
}
const QuestionFloatButton: React.FC<QuestionFloatButtonProps> = ({
currentIndex,
totalQuestions,
onClick,
correctCount,
wrongCount,
}) => {
if (totalQuestions === 0) return null
const answeredCount = correctCount + wrongCount
const progress = Math.round((answeredCount / totalQuestions) * 100)
const radius = 28.5
const circumference = 2 * Math.PI * radius
return (
<div className={styles.floatButtonWrapper}>
{/* 统计信息卡片 */}
<div className={styles.statsCard}>
<div className={styles.statsRow}>
<span className={styles.statsLabel}></span>
<span className={styles.statsValue}>
{currentIndex + 1}/{totalQuestions}
</span>
</div>
<div className={styles.statsDivider} />
<div className={styles.statsRow}>
<span className={styles.statsLabel}></span>
<span className={styles.statsValue} style={{ color: '#52c41a' }}>
{correctCount}
</span>
</div>
<div className={styles.statsDivider} />
<div className={styles.statsRow}>
<span className={styles.statsLabel}></span>
<span className={styles.statsValue} style={{ color: '#ff4d4f' }}>
{wrongCount}
</span>
</div>
</div>
{/* 悬浮球 */}
<div className={styles.floatButton} onClick={onClick}>
{/* 进度环 */}
<svg className={styles.progressRing} width="64" height="64">
<circle
className={styles.progressRingCircle}
strokeWidth="3"
fill="transparent"
r={radius}
cx="32"
cy="32"
/>
<circle
className={styles.progressRingCircleProgress}
strokeWidth="3"
fill="transparent"
r={radius}
cx="32"
cy="32"
style={{
strokeDasharray: `${circumference}`,
strokeDashoffset: `${circumference * (1 - progress / 100)}`,
}}
/>
</svg>
{/* 中心图标 */}
<div className={styles.floatButtonContent}>
<UnorderedListOutlined className={styles.icon} />
</div>
</div>
</div>
)
}
export default QuestionFloatButton

View File

@ -27,7 +27,7 @@ const Login: React.FC = () => {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [registerModalVisible, setRegisterModalVisible] = useState(false) const [registerModalVisible, setRegisterModalVisible] = useState(false)
const [userTypeModalVisible, setUserTypeModalVisible] = useState(false) // 用户类型补充模态框 const [userTypeModalVisible, setUserTypeModalVisible] = useState(false) // 用户类型补充模态框
const [userType, setUserType] = useState<string>('') // 临时存储用户选择的类型 // const [userType, setUserType] = useState<string>('') // 临时存储用户选择的类型
const [loginForm] = Form.useForm() const [loginForm] = Form.useForm()
const [registerForm] = Form.useForm() const [registerForm] = Form.useForm()
const [userTypeForm] = Form.useForm() const [userTypeForm] = Form.useForm()

View File

@ -31,14 +31,13 @@
max-width: 900px; max-width: 900px;
margin: 0 auto; margin: 0 auto;
padding: 0 20px; padding: 0 20px;
padding-top: 140px; // 为固定顶栏留出空间,减少距离 padding-top: 80px; // 减少顶部空间,因为去掉了进度条
} }
.header { .header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 16px;
.backButton { .backButton {
color: #007aff; color: #007aff;
@ -60,31 +59,15 @@
text-align: center; text-align: center;
} }
.statsGroup { .settingsButton {
display: flex; color: #8c8c8c;
gap: 16px;
align-items: center;
}
.statItem {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
}
.statLabel {
font-size: 11px;
color: #86868b;
font-weight: 500; font-weight: 500;
text-transform: uppercase; padding: 4px 12px;
letter-spacing: 0.5px;
}
.statValue { &:hover {
font-size: 20px; color: #1d1d1f;
font-weight: 700; background: rgba(0, 0, 0, 0.04);
line-height: 1; }
} }
} }
@ -145,49 +128,22 @@
.content { .content {
padding: 0 12px; padding: 0 12px;
padding-top: 160px; // 移动端顶栏更高,调整距离 padding-top: 70px; // 移动端减少顶部距离
} }
.header { .header {
flex-wrap: wrap;
gap: 10px;
margin-bottom: 12px;
.backButton { .backButton {
order: 1;
flex: 0 0 auto;
font-size: 14px; font-size: 14px;
padding: 4px 8px; padding: 4px 8px;
} }
.title { .title {
order: 2;
flex: 1;
text-align: left;
font-size: 16px !important; font-size: 16px !important;
margin: 0 !important;
} }
.statsGroup { .settingsButton {
order: 3; font-size: 14px;
flex: 1 1 100%; padding: 4px 8px;
justify-content: center;
gap: 32px;
margin-top: 4px;
}
.statItem {
flex-direction: row;
gap: 6px;
align-items: center;
}
.statLabel {
font-size: 12px;
}
.statValue {
font-size: 18px;
} }
} }
@ -216,7 +172,7 @@
.content { .content {
padding: 0 24px; padding: 0 24px;
padding-top: 135px; padding-top: 75px;
} }
.header { .header {
@ -234,16 +190,12 @@
.content { .content {
padding: 0 32px; padding: 0 32px;
padding-top: 135px; padding-top: 85px;
} }
.header { .header {
.title { .title {
font-size: 22px !important; font-size: 22px !important;
} }
.statValue {
font-size: 24px;
}
} }
} }

View File

@ -1,12 +1,13 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useSearchParams, useNavigate } from "react-router-dom"; import { useSearchParams, useNavigate } from "react-router-dom";
import { Button, message, Typography, Switch, InputNumber, Popover, Space } from "antd"; import { Button, message, Typography, Switch, InputNumber, Modal, Space } from "antd";
import { ArrowLeftOutlined, SettingOutlined } from "@ant-design/icons"; import { ArrowLeftOutlined, SettingOutlined } from "@ant-design/icons";
import type { Question, AnswerResult } from "../types/question"; import type { Question, AnswerResult } from "../types/question";
import * as questionApi from "../api/question"; import * as questionApi from "../api/question";
import QuestionProgress from "../components/QuestionProgress";
import QuestionCard from "../components/QuestionCard"; import QuestionCard from "../components/QuestionCard";
import CompletionSummary from "../components/CompletionSummary"; import CompletionSummary from "../components/CompletionSummary";
import QuestionDrawer from "../components/QuestionDrawer";
import QuestionFloatButton from "../components/QuestionFloatButton";
import styles from "./Question.module.less"; import styles from "./Question.module.less";
const { Title } = Typography; const { Title } = Typography;
@ -28,6 +29,13 @@ const QuestionPage: React.FC = () => {
const [wrongCount, setWrongCount] = useState(0); const [wrongCount, setWrongCount] = useState(0);
const [showSummary, setShowSummary] = useState(false); const [showSummary, setShowSummary] = useState(false);
// 题目导航抽屉
const [drawerVisible, setDrawerVisible] = useState(false);
const [answeredStatus, setAnsweredStatus] = useState<Map<number, boolean | null>>(new Map());
// 设置弹窗
const [settingsVisible, setSettingsVisible] = useState(false);
// 自动跳转开关(默认开启) // 自动跳转开关(默认开启)
const [autoNext, setAutoNext] = useState(() => { const [autoNext, setAutoNext] = useState(() => {
const saved = localStorage.getItem('autoNextEnabled'); const saved = localStorage.getItem('autoNextEnabled');
@ -63,14 +71,23 @@ const QuestionPage: React.FC = () => {
}; };
// 保存答题进度 // 保存答题进度
const saveProgress = (index: number, correct: number, wrong: number) => { const saveProgress = (index: number, correct: number, wrong: number, statusMap?: Map<number, boolean | null>) => {
const key = getStorageKey(); const key = getStorageKey();
const answeredStatusObj: Record<number, boolean | null> = {};
// 将 Map 转换为普通对象以便 JSON 序列化
const mapToSave = statusMap || answeredStatus;
mapToSave.forEach((value, key) => {
answeredStatusObj[key] = value;
});
localStorage.setItem( localStorage.setItem(
key, key,
JSON.stringify({ JSON.stringify({
currentIndex: index, currentIndex: index,
correctCount: correct, correctCount: correct,
wrongCount: wrong, wrongCount: wrong,
answeredStatus: answeredStatusObj,
timestamp: Date.now(), timestamp: Date.now(),
}) })
); );
@ -86,6 +103,16 @@ const QuestionPage: React.FC = () => {
setCurrentIndex(progress.currentIndex || 0); setCurrentIndex(progress.currentIndex || 0);
setCorrectCount(progress.correctCount || 0); setCorrectCount(progress.correctCount || 0);
setWrongCount(progress.wrongCount || 0); setWrongCount(progress.wrongCount || 0);
// 恢复答题状态
if (progress.answeredStatus) {
const statusMap = new Map<number, boolean | null>();
Object.entries(progress.answeredStatus).forEach(([index, status]) => {
statusMap.set(Number(index), status as boolean | null);
});
setAnsweredStatus(statusMap);
}
return progress.currentIndex || 0; return progress.currentIndex || 0;
} catch (e) { } catch (e) {
console.error("恢复进度失败", e); console.error("恢复进度失败", e);
@ -196,15 +223,20 @@ const QuestionPage: React.FC = () => {
setAnswerResult(res.data); setAnswerResult(res.data);
setShowResult(true); setShowResult(true);
// 更新答题状态
const newStatusMap = new Map(answeredStatus);
newStatusMap.set(currentIndex, res.data.correct);
setAnsweredStatus(newStatusMap);
// 更新统计 // 更新统计
if (res.data.correct) { if (res.data.correct) {
const newCorrect = correctCount + 1; const newCorrect = correctCount + 1;
setCorrectCount(newCorrect); setCorrectCount(newCorrect);
saveProgress(currentIndex, newCorrect, wrongCount); saveProgress(currentIndex, newCorrect, wrongCount, newStatusMap);
} else { } else {
const newWrong = wrongCount + 1; const newWrong = wrongCount + 1;
setWrongCount(newWrong); setWrongCount(newWrong);
saveProgress(currentIndex, correctCount, newWrong); saveProgress(currentIndex, correctCount, newWrong, newStatusMap);
} }
// 如果答案正确且开启自动跳转,根据设置的延迟时间后自动进入下一题 // 如果答案正确且开启自动跳转,根据设置的延迟时间后自动进入下一题
@ -252,6 +284,25 @@ const QuestionPage: React.FC = () => {
} }
}; };
// 跳转到指定题目
const handleQuestionSelect = (index: number) => {
if (index >= 0 && index < allQuestions.length) {
setCurrentIndex(index);
setCurrentQuestion(allQuestions[index]);
setSelectedAnswer(
allQuestions[index].type === "multiple-selection" ? [] : ""
);
setShowResult(false);
setAnswerResult(null);
// 保存进度
saveProgress(index, correctCount, wrongCount);
// 滚动到页面顶部
window.scrollTo({ top: 0, behavior: 'smooth' });
}
};
// 初始化 // 初始化
useEffect(() => { useEffect(() => {
const typeParam = searchParams.get("type"); const typeParam = searchParams.get("type");
@ -297,73 +348,15 @@ const QuestionPage: React.FC = () => {
<Title level={3} className={styles.title}> <Title level={3} className={styles.title}>
AnKao AnKao
</Title> </Title>
<div className={styles.statsGroup}> {/* 设置按钮 */}
<div className={styles.statItem}> <Button
<span className={styles.statLabel}></span> type="text"
<span className={styles.statValue} style={{ color: '#52c41a' }}>{correctCount}</span> icon={<SettingOutlined />}
</div> onClick={() => setSettingsVisible(true)}
<div className={styles.statItem}> className={styles.settingsButton}
<span className={styles.statLabel}></span> >
<span className={styles.statValue} style={{ color: '#ff4d4f' }}>{wrongCount}</span>
</div> </Button>
{/* 设置按钮 */}
<Popover
content={
<div style={{ width: 200 }}>
<Space direction="vertical" style={{ width: '100%' }}>
<div>
<div style={{ marginBottom: 8, fontWeight: 500 }}></div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Switch
checked={autoNext}
onChange={toggleAutoNext}
size="small"
/>
<span style={{ fontSize: 14 }}>
{autoNext ? '已开启' : '已关闭'}
</span>
</div>
</div>
{autoNext && (
<div>
<div style={{ marginBottom: 8, fontWeight: 500 }}></div>
<Space>
<InputNumber
min={1}
max={10}
value={autoNextDelay}
onChange={handleDelayChange}
size="small"
style={{ width: 60 }}
/>
<span style={{ fontSize: 14 }}></span>
</Space>
</div>
)}
</Space>
</div>
}
title="答题设置"
trigger="click"
placement="bottomRight"
>
<Button
type="text"
icon={<SettingOutlined />}
style={{ marginLeft: 8 }}
/>
</Popover>
</div>
</div>
{/* 进度条 */}
<div className={styles.progressWrapper}>
<QuestionProgress
currentIndex={currentIndex}
totalQuestions={allQuestions.length}
correctCount={correctCount}
wrongCount={wrongCount}
/>
</div> </div>
</div> </div>
</div> </div>
@ -400,6 +393,77 @@ const QuestionPage: React.FC = () => {
}} }}
onRetry={handleRetry} onRetry={handleRetry}
/> />
{/* 题目导航抽屉 */}
<QuestionDrawer
visible={drawerVisible}
onClose={() => setDrawerVisible(false)}
questions={allQuestions}
currentIndex={currentIndex}
onQuestionSelect={handleQuestionSelect}
answeredStatus={answeredStatus}
/>
{/* 悬浮球 - 题目导航 */}
{allQuestions.length > 0 && (
<QuestionFloatButton
currentIndex={currentIndex}
totalQuestions={allQuestions.length}
correctCount={correctCount}
wrongCount={wrongCount}
onClick={() => setDrawerVisible(true)}
/>
)}
{/* 设置弹窗 */}
<Modal
title="答题设置"
open={settingsVisible}
onCancel={() => setSettingsVisible(false)}
footer={null}
width={400}
>
<Space direction="vertical" style={{ width: '100%' }} size="large">
<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>
)}
</Space>
</Modal>
</div> </div>
); );
}; };

View File

@ -93,9 +93,9 @@ export const fetchWithAuth = async (
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
// 合并 headers // 合并 headers
const headers: HeadersInit = { const headers: Record<string, string> = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...options.headers, ...(options.headers as Record<string, string>),
} }
// 如果有 token添加到请求头 // 如果有 token添加到请求头