add function of username search in admin vocabulary scoreaudit
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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 });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user