yearning
19 小时以前 e3e6db2d9974588f91ac1567a58fa4846655ce1b
frontend/src/pages/ScoreQueryPage.tsx
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from "react";
import { Button, Card, Form, Input, Select, Table } from "antd";
import { Button, Card, Form, Input, message, Select, Table } from "antd";
import { useNavigate, useSearchParams } from "react-router-dom";
import { api } from "../api";
import {
@@ -8,12 +8,15 @@
  writeScoreListQuery,
  type ScoreListQuery,
} from "../scoreQueryParams";
import { useSystemSettings } from "../systemSettings";
import type { UserMe } from "../App";
import "./ScoreQueryPage.css";
type ScoreRow = {
  examinee_id: string;
  name: string;
  id_card: string;
  student_no: string;
  name: string | null;
  id_card: string | null;
  subject_name: string;
  score: number;
  attempt_id: string;
@@ -38,6 +41,18 @@
    subject: query.subject,
  });
  const [tableScrollY, setTableScrollY] = useState(320);
  const [user, setUser] = useState<UserMe | null>(null);
  const { settings } = useSystemSettings();
  const hidePrivacy =
    user?.role === "grader" && !settings.grader_can_see_privacy;
  useEffect(() => {
    api
      .get<UserMe>("/api/auth/me")
      .then((r) => setUser(r.data))
      .catch(() => setUser(null));
  }, []);
  useEffect(() => {
    api.get<{ items: string[] }>("/api/subjects").then((r) => setSubjects(r.data.items));
@@ -56,16 +71,20 @@
  }, [searchParams, form]);
  const load = async () => {
    const { data: res } = await api.get("/api/scores", {
      params: { page, page_size: pageSize, ...filters },
    });
    const params: Record<string, string | number> = { page, page_size: pageSize };
    if (!hidePrivacy) {
      if (filters.name) params.name = filters.name;
      if (filters.id_card) params.id_card = filters.id_card;
    }
    if (filters.subject) params.subject = filters.subject;
    const { data: res } = await api.get("/api/scores", { params });
    setData(res.items);
    setTotal(res.total);
  };
  useEffect(() => {
    load();
  }, [page, pageSize, filters]);
  }, [page, pageSize, filters, hidePrivacy]);
  useEffect(() => {
    const el = tableWrapRef.current;
@@ -90,6 +109,52 @@
    setSearchParams(writeScoreListQuery(q), { replace: true });
  };
  const [studentNo, setStudentNo] = useState("");
  const handleLookup = async () => {
    if (!studentNo.trim()) return;
    try {
      const { data } = await api.get<{ attempt_id: string }>(
        `/api/attempts/lookup/${encodeURIComponent(studentNo.trim().toUpperCase())}`
      );
      nav(attemptDetailPath(data.attempt_id, listQuery()));
    } catch {
      message.error("考生编号不存在");
    }
  };
  const columns = [
    {
      title: "序号",
      width: 64,
      fixed: "left" as const,
      render: (_: unknown, __: unknown, index: number) => (page - 1) * pageSize + index + 1,
    },
    { title: "考生编号", dataIndex: "student_no", width: 180, fixed: "left" as const },
    ...(hidePrivacy
      ? []
      : [
          { 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: string | null) => v || "—" },
    { title: "结束考试时间", dataIndex: "end_time", width: 170, render: (v: string | null) => v || "—" },
    { title: "成绩", dataIndex: "score", width: 80 },
    {
      title: "操作",
      width: 80,
      fixed: "right" as const,
      render: (_: unknown, row: ScoreRow) => (
        <Button
          type="link"
          onClick={() => nav(attemptDetailPath(row.attempt_id, listQuery()))}
        >
          查看
        </Button>
      ),
    },
  ];
  return (
    <div className="score-query-page">
      <Card bordered className="score-query-page__card">
@@ -101,12 +166,16 @@
            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>
          {!hidePrivacy && (
            <>
              <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
@@ -118,10 +187,21 @@
            <Button type="primary" htmlType="submit">查询</Button>
          </Form.Item>
        </Form>
        <div style={{ marginBottom: 12, display: "flex", gap: 8, alignItems: "center" }}>
          <Input
            placeholder="输入考生编号直接查看答卷"
            value={studentNo}
            onChange={(e) => setStudentNo(e.target.value.toUpperCase())}
            onPressEnter={handleLookup}
            style={{ width: 200, fontFamily: "monospace" }}
            maxLength={10}
          />
          <Button onClick={handleLookup} disabled={!studentNo.trim()}>查看答卷</Button>
        </div>
        <div ref={tableWrapRef} className="score-query-page__table-wrap">
          <Table
            rowKey="attempt_id"
            scroll={{ x: 1100, y: tableScrollY }}
            scroll={{ x: hidePrivacy ? 800 : 1100, y: tableScrollY }}
            dataSource={data}
            pagination={{
              current: page,
@@ -134,33 +214,7 @@
                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>
                ),
              },
            ]}
            columns={columns}
          />
        </div>
      </Card>