From 3f96c9c6502243b06efd4bb34a189e72e3a5557c Mon Sep 17 00:00:00 2001
From: yearning <10538594+wangweifeng1999@user.noreply.gitee.com>
Date: 星期一, 28 九月 2026 19:24:55 +0800
Subject: [PATCH] 复制项目
---
frontend/src/pages/ScoreQueryPage.tsx | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 169 insertions(+), 0 deletions(-)
diff --git a/frontend/src/pages/ScoreQueryPage.tsx b/frontend/src/pages/ScoreQueryPage.tsx
new file mode 100644
index 0000000..e3f3141
--- /dev/null
+++ b/frontend/src/pages/ScoreQueryPage.tsx
@@ -0,0 +1,169 @@
+import { useEffect, useRef, useState } from "react";
+import { Button, Card, Form, Input, Select, Table } from "antd";
+import { useNavigate, useSearchParams } from "react-router-dom";
+import { api } from "../api";
+import {
+ attemptDetailPath,
+ readScoreListQuery,
+ writeScoreListQuery,
+ type ScoreListQuery,
+} from "../scoreQueryParams";
+import "./ScoreQueryPage.css";
+
+type ScoreRow = {
+ examinee_id: string;
+ name: string;
+ id_card: string;
+ subject_name: string;
+ score: number;
+ attempt_id: string;
+ start_time?: string | null;
+ end_time?: string | null;
+};
+
+export default function ScoreQueryPage() {
+ const nav = useNavigate();
+ const [searchParams, setSearchParams] = useSearchParams();
+ const query = readScoreListQuery(searchParams);
+ const [form] = Form.useForm<ScoreListQuery>();
+ const tableWrapRef = useRef<HTMLDivElement>(null);
+ const [subjects, setSubjects] = useState<string[]>([]);
+ const [data, setData] = useState<ScoreRow[]>([]);
+ const [total, setTotal] = useState(0);
+ const [page, setPage] = useState(query.page ?? 1);
+ const [pageSize, setPageSize] = useState(query.page_size ?? 20);
+ const [filters, setFilters] = useState<Pick<ScoreListQuery, "name" | "id_card" | "subject">>({
+ name: query.name,
+ id_card: query.id_card,
+ subject: query.subject,
+ });
+ const [tableScrollY, setTableScrollY] = useState(320);
+
+ useEffect(() => {
+ api.get<{ items: string[] }>("/api/subjects").then((r) => setSubjects(r.data.items));
+ }, []);
+
+ useEffect(() => {
+ const q = readScoreListQuery(searchParams);
+ setPage(q.page ?? 1);
+ setPageSize(q.page_size ?? 20);
+ setFilters({ name: q.name, id_card: q.id_card, subject: q.subject });
+ form.setFieldsValue({
+ name: q.name,
+ id_card: q.id_card,
+ subject: q.subject,
+ });
+ }, [searchParams, form]);
+
+ const load = async () => {
+ const { data: res } = await api.get("/api/scores", {
+ params: { page, page_size: pageSize, ...filters },
+ });
+ setData(res.items);
+ setTotal(res.total);
+ };
+
+ useEffect(() => {
+ load();
+ }, [page, pageSize, filters]);
+
+ useEffect(() => {
+ const el = tableWrapRef.current;
+ if (!el) return;
+ const update = () => {
+ const h = el.clientHeight;
+ setTableScrollY(Math.max(120, h - 56 - 52));
+ };
+ update();
+ const ro = new ResizeObserver(update);
+ ro.observe(el);
+ return () => ro.disconnect();
+ }, []);
+
+ const listQuery = (): ScoreListQuery => ({
+ ...filters,
+ page,
+ page_size: pageSize,
+ });
+
+ const pushQuery = (q: ScoreListQuery) => {
+ setSearchParams(writeScoreListQuery(q), { replace: true });
+ };
+
+ return (
+ <div className="score-query-page">
+ <Card bordered className="score-query-page__card">
+ <Form
+ form={form}
+ layout="inline"
+ className="score-query-page__form"
+ onFinish={(v) => {
+ pushQuery({ ...v, page: 1, page_size: pageSize });
+ }}
+ >
+ <Form.Item name="name" label="濮撳悕">
+ <Input allowClear style={{ width: 120 }} />
+ </Form.Item>
+ <Form.Item name="id_card" label="璇佷欢鍙�">
+ <Input allowClear style={{ width: 180 }} />
+ </Form.Item>
+ <Form.Item name="subject" label="绉戠洰">
+ <Select
+ allowClear
+ style={{ width: 220 }}
+ options={subjects.map((s) => ({ value: s, label: s }))}
+ />
+ </Form.Item>
+ <Form.Item>
+ <Button type="primary" htmlType="submit">鏌ヨ</Button>
+ </Form.Item>
+ </Form>
+ <div ref={tableWrapRef} className="score-query-page__table-wrap">
+ <Table
+ rowKey="attempt_id"
+ scroll={{ x: 1100, y: tableScrollY }}
+ dataSource={data}
+ pagination={{
+ current: page,
+ pageSize,
+ total,
+ showSizeChanger: true,
+ showTotal: (t) => `鍏� ${t} 鏉,
+ position: ["bottomRight"],
+ onChange: (p, ps) => {
+ pushQuery({ ...filters, page: p, page_size: ps });
+ },
+ }}
+ columns={[
+ {
+ title: "搴忓彿",
+ width: 64,
+ fixed: "left",
+ render: (_, __, index) => (page - 1) * pageSize + index + 1,
+ },
+ { title: "濮撳悕", dataIndex: "name", width: 100 },
+ { title: "璇佷欢鍙�", dataIndex: "id_card", width: 180 },
+ { title: "绉戠洰", dataIndex: "subject_name", width: 200, ellipsis: true },
+ { title: "寮�濮嬭�冭瘯鏃堕棿", dataIndex: "start_time", width: 170, render: (v) => v || "鈥�" },
+ { title: "缁撴潫鑰冭瘯鏃堕棿", dataIndex: "end_time", width: 170, render: (v) => v || "鈥�" },
+ { title: "鎴愮哗", dataIndex: "score", width: 80 },
+ {
+ title: "鎿嶄綔",
+ width: 80,
+ fixed: "right",
+ render: (_, row) => (
+ <Button
+ type="link"
+ onClick={() => nav(attemptDetailPath(row.attempt_id, listQuery()))}
+ >
+ 鏌ョ湅
+ </Button>
+ ),
+ },
+ ]}
+ />
+ </div>
+ </Card>
+ </div>
+ );
+}
--
Gitblit v1.8.0