add function of username search in admin vocabulary scoreaudit

This commit is contained in:
2026-06-04 20:09:38 +08:00
parent 37e4837e3f
commit 8fd1606424
3 changed files with 27 additions and 3 deletions

View File

@@ -70,6 +70,7 @@ const INVALIDATING_VOCABULARY_RISK_FLAGS = [
]; ];
const getObjectIdString = (value: any): string => value?._id?.toString?.() || value?.toString?.() || ''; const getObjectIdString = (value: any): string => value?._id?.toString?.() || value?.toString?.() || '';
const escapeRegex = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const parseAdminDateRange = (source: Record<string, any>) => { const parseAdminDateRange = (source: Record<string, any>) => {
const start = new Date(String(source.start || source.startTime || '')); const start = new Date(String(source.start || source.startTime || ''));
@@ -544,6 +545,17 @@ router.get('/vocabulary/test-pass-summary', adminAuth, async (req, res) => {
} }
const { start, end } = range as { start: Date; end: Date }; const { start, end } = range as { start: Date; end: Date };
const studentNo = String(req.query.studentNo || '').trim();
const userMatchStages = studentNo
? [{
$match: {
'user.username': {
$regex: escapeRegex(studentNo),
$options: 'i'
}
}
}]
: [];
const validPassItems = await VocabularyTestRecord.aggregate([ const validPassItems = await VocabularyTestRecord.aggregate([
{ {
$match: { $match: {
@@ -575,6 +587,7 @@ router.get('/vocabulary/test-pass-summary', adminAuth, async (req, res) => {
} }
}, },
{ $unwind: { path: '$user', preserveNullAndEmptyArrays: true } }, { $unwind: { path: '$user', preserveNullAndEmptyArrays: true } },
...userMatchStages,
{ {
$project: { $project: {
_id: 0, _id: 0,
@@ -630,6 +643,7 @@ router.get('/vocabulary/test-pass-summary', adminAuth, async (req, res) => {
} }
}, },
{ $unwind: { path: '$user', preserveNullAndEmptyArrays: true } }, { $unwind: { path: '$user', preserveNullAndEmptyArrays: true } },
...userMatchStages,
{ {
$project: { $project: {
_id: 0, _id: 0,

View File

@@ -336,7 +336,7 @@ export const adminApi = {
return api.put('/api/vocabulary/words', { words }); return api.put('/api/vocabulary/words', { words });
}, },
getVocabularyPassSummary: async (params: { start: string; end: string }): Promise<VocabularyPassSummaryResponse> => { getVocabularyPassSummary: async (params: { start: string; end: string; studentNo?: string }): Promise<VocabularyPassSummaryResponse> => {
return api.get<VocabularyPassSummaryResponse>('/admin/vocabulary/test-pass-summary', { params }); return api.get<VocabularyPassSummaryResponse>('/admin/vocabulary/test-pass-summary', { params });
}, },

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Button, Card, Collapse, DatePicker, Descriptions, message, Modal, Popconfirm, Space, Statistic, Table, Tag, Typography } from 'antd'; import { Button, Card, Collapse, DatePicker, Descriptions, Input, message, Modal, Popconfirm, Space, Statistic, Table, Tag, Typography } from 'antd';
import type { ColumnsType } from 'antd/es/table'; import type { ColumnsType } from 'antd/es/table';
import { CheckCircleOutlined, DeleteOutlined, EyeOutlined, SearchOutlined } from '@ant-design/icons'; import { CheckCircleOutlined, DeleteOutlined, EyeOutlined, SearchOutlined } from '@ant-design/icons';
import { import {
@@ -93,6 +93,7 @@ const getAnswerRows = (record: VocabularyAuditRecordDetail): VocabularyAuditAnsw
const AdminVocabularyScoreAudit: React.FC = () => { const AdminVocabularyScoreAudit: React.FC = () => {
const [range, setRange] = useState<[string, string] | null>(null); const [range, setRange] = useState<[string, string] | null>(null);
const [studentNo, setStudentNo] = useState('');
const [summary, setSummary] = useState<VocabularyPassSummaryResponse | null>(null); const [summary, setSummary] = useState<VocabularyPassSummaryResponse | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [clearingUserId, setClearingUserId] = useState<string | null>(null); const [clearingUserId, setClearingUserId] = useState<string | null>(null);
@@ -112,7 +113,8 @@ const AdminVocabularyScoreAudit: React.FC = () => {
setLoading(true); setLoading(true);
const response = await adminApi.getVocabularyPassSummary({ const response = await adminApi.getVocabularyPassSummary({
start: range[0], start: range[0],
end: range[1] end: range[1],
studentNo: studentNo.trim() || undefined
}); });
setSummary(response); setSummary(response);
} catch (error) { } catch (error) {
@@ -550,6 +552,14 @@ const AdminVocabularyScoreAudit: React.FC = () => {
setRange([toIsoString(dates[0]), toIsoString(dates[1])]); setRange([toIsoString(dates[0]), toIsoString(dates[1])]);
}} }}
/> />
<Input
allowClear
placeholder="输入学生学号"
value={studentNo}
onChange={event => setStudentNo(event.target.value)}
onPressEnter={fetchSummary}
style={{ width: 220 }}
/>
<Button type="primary" icon={<SearchOutlined />} loading={loading} onClick={fetchSummary}> <Button type="primary" icon={<SearchOutlined />} loading={loading} onClick={fetchSummary}>
</Button> </Button>