后台基本功能完成,待完善:普通用户前端页面

This commit is contained in:
2025-12-25 16:55:32 +08:00
parent dc9fc169ec
commit de0c7377c9
9 changed files with 360 additions and 82 deletions

View File

@@ -85,3 +85,80 @@
.bg-mars {
background-color: #008C8C;
}
.qt-text-import-th-content,
.qt-text-import-td-content {
width: 850px !important;
min-width: 850px !important;
max-width: 850px !important;
}
.qt-text-import-th-analysis,
.qt-text-import-td-analysis {
width: 320px !important;
min-width: 320px !important;
max-width: 320px !important;
}
@media (max-width: 1200px) {
.qt-text-import-th-content,
.qt-text-import-td-content {
width: 600px !important;
min-width: 600px !important;
max-width: 600px !important;
}
.qt-text-import-th-analysis,
.qt-text-import-td-analysis {
width: 260px !important;
min-width: 260px !important;
max-width: 260px !important;
}
}
@media (max-width: 768px) {
.qt-text-import-th-content,
.qt-text-import-td-content {
width: 360px !important;
min-width: 360px !important;
max-width: 360px !important;
}
.qt-text-import-th-analysis,
.qt-text-import-td-analysis {
width: 220px !important;
min-width: 220px !important;
max-width: 220px !important;
}
}
@media (max-width: 480px) {
.qt-text-import-th-content,
.qt-text-import-td-content {
width: 260px !important;
min-width: 260px !important;
max-width: 260px !important;
}
.qt-text-import-th-analysis,
.qt-text-import-td-analysis {
width: 180px !important;
min-width: 180px !important;
max-width: 180px !important;
}
}
.qc-question-count-td {
text-align: center !important;
vertical-align: middle;
padding: 8px 12px !important;
}
.qc-question-count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 90px;
margin: 0 auto;
text-align: center;
}

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Table, Button, Input, Space, message, Popconfirm, Modal, Form, Tag } from 'antd';
import { Table, Button, Input, Space, message, Popconfirm, Modal, Form, Tag, Tooltip } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
import { useLocation } from 'react-router-dom';
import api from '../../services/api';
@@ -9,6 +9,7 @@ interface QuestionCategory {
id: string;
name: string;
createdAt: string;
questionCount?: number;
}
const QuestionCategoryPage = () => {
@@ -93,6 +94,22 @@ const QuestionCategoryPage = () => {
</div>
),
},
{
title: (
<Tooltip title="该类别下题目数量(按题目表 category 统计)">
<span></span>
</Tooltip>
),
dataIndex: 'questionCount',
key: 'questionCount',
align: 'center' as const,
width: 120,
className: 'qc-question-count-td',
sorter: (a: QuestionCategory, b: QuestionCategory) => (a.questionCount ?? 0) - (b.questionCount ?? 0),
render: (count: number | undefined) => (
<div className="qc-question-count">{count ?? 0}</div>
),
},
{
title: '创建时间',
dataIndex: 'createdAt',
@@ -172,4 +189,4 @@ const QuestionCategoryPage = () => {
);
};
export default QuestionCategoryPage;
export default QuestionCategoryPage;

View File

@@ -1,5 +1,5 @@
import { useState } from 'react';
import { Alert, Button, Card, Input, Modal, Radio, Space, Table, Tag, Typography, message } from 'antd';
import { Alert, Button, Card, Input, Modal, Radio, Space, Statistic, Table, Tag, Typography, message } from 'antd';
import { ArrowLeftOutlined, DeleteOutlined, FileTextOutlined, ImportOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { questionAPI } from '../../services/api';
@@ -16,6 +16,7 @@ const QuestionTextImportPage = () => {
const [parseErrors, setParseErrors] = useState<string[]>([]);
const [questions, setQuestions] = useState<ImportQuestion[]>([]);
const [loading, setLoading] = useState(false);
const [tablePagination, setTablePagination] = useState({ current: 1, pageSize: 10 });
const exampleText = [
'题型|题目类别|分值|题目内容|选项|答案|解析',
@@ -29,6 +30,7 @@ const QuestionTextImportPage = () => {
const result = parseTextQuestions(rawText);
setParseErrors(result.errors);
setQuestions(result.questions);
setTablePagination((prev) => ({ ...prev, current: 1 }));
if (result.questions.length === 0) {
message.error(result.errors.length ? '解析失败,请检查格式' : '未解析到任何题目');
return;
@@ -44,6 +46,11 @@ const QuestionTextImportPage = () => {
setQuestions((prev) => prev.filter((_, i) => i !== idx));
};
const indexBase = (tablePagination.current - 1) * tablePagination.pageSize;
const totalCount = questions.length + parseErrors.length;
const validCount = questions.length;
const invalidCount = parseErrors.length;
const handleImport = async () => {
if (questions.length === 0) return;
Modal.confirm({
@@ -72,13 +79,16 @@ const QuestionTextImportPage = () => {
title: '序号',
key: 'index',
width: 60,
render: (_: any, __: any, index: number) => index + 1,
render: (_: any, __: any, index: number) => indexBase + index + 1,
},
{
title: '题目内容',
dataIndex: 'content',
key: 'content',
width: 850,
ellipsis: true,
onHeaderCell: () => ({ className: 'qt-text-import-th-content' }),
onCell: () => ({ className: 'qt-text-import-td-content' }),
},
{
title: '题型',
@@ -116,8 +126,10 @@ const QuestionTextImportPage = () => {
title: '解析',
dataIndex: 'analysis',
key: 'analysis',
width: 220,
width: 320,
ellipsis: true,
onHeaderCell: () => ({ className: 'qt-text-import-th-analysis' }),
onCell: () => ({ className: 'qt-text-import-td-analysis' }),
render: (analysis: ImportQuestion['analysis']) => (
<span>
<Tag color="blue"></Tag>
@@ -130,7 +142,7 @@ const QuestionTextImportPage = () => {
key: 'action',
width: 80,
render: (_: any, __: any, index: number) => (
<Button type="text" danger icon={<DeleteOutlined />} onClick={() => handleRemove(index)} />
<Button type="text" danger icon={<DeleteOutlined />} onClick={() => handleRemove(indexBase + index)} />
),
},
];
@@ -166,14 +178,25 @@ const QuestionTextImportPage = () => {
<Card className="shadow-sm mb-4">
<Space direction="vertical" style={{ width: '100%' }} size="middle">
<Space>
<Button icon={<FileTextOutlined />} onClick={() => setRawText(exampleText)}>
</Button>
<Button type="primary" onClick={handleParse} disabled={!rawText.trim()}>
</Button>
</Space>
<div className="flex items-center justify-between">
<Space>
<Button icon={<FileTextOutlined />} onClick={() => setRawText(exampleText)}>
</Button>
<Button type="primary" onClick={handleParse} disabled={!rawText.trim()}>
</Button>
</Space>
<Space size="large">
<Statistic title="本次导入题目总数" value={totalCount} valueStyle={{ color: '#1677ff', fontWeight: 700 }} />
<Statistic title="有效题目数量" value={validCount} valueStyle={{ color: '#52c41a', fontWeight: 700 }} />
<Statistic
title="无效题目数量"
value={invalidCount}
valueStyle={{ color: invalidCount > 0 ? '#ff4d4f' : '#8c8c8c', fontWeight: 700 }}
/>
</Space>
</div>
<TextArea
value={rawText}
@@ -203,7 +226,15 @@ const QuestionTextImportPage = () => {
<Table
columns={columns as any}
dataSource={questions.map((q, idx) => ({ ...q, key: `${q.content}-${idx}` }))}
pagination={{ pageSize: 10, showSizeChanger: true }}
pagination={{ ...tablePagination, showSizeChanger: true }}
tableLayout="fixed"
scroll={{ x: 1800 }}
onChange={(pagination) =>
setTablePagination({
current: pagination.current ?? 1,
pageSize: pagination.pageSize ?? 10,
})
}
/>
</Card>
</div>