import { useEffect, useState } from "react";
|
import { Button, Card, Form, Input, Modal, Select, Space, Switch, Table, Tag, message } from "antd";
|
import { api } from "../api";
|
|
type Grader = {
|
id: number;
|
username: string;
|
display_name: string;
|
is_active: boolean;
|
created_at: string;
|
subjects: string[];
|
};
|
|
type FormValues = {
|
username: string;
|
password?: string;
|
display_name?: string;
|
is_active?: boolean;
|
subjects: string[];
|
};
|
|
export default function AdminGradersPage() {
|
const [items, setItems] = useState<Grader[]>([]);
|
const [loading, setLoading] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
const [editing, setEditing] = useState<Grader | null>(null);
|
const [form] = Form.useForm<FormValues>();
|
const [subjectOptions, setSubjectOptions] = useState<string[]>([]);
|
|
const loadSubjects = async () => {
|
const { data } = await api.get<{ items: string[] }>("/api/subjects");
|
setSubjectOptions(data.items);
|
};
|
|
const load = async () => {
|
setLoading(true);
|
try {
|
const { data } = await api.get<{ items: Grader[] }>("/api/admin/graders");
|
setItems(data.items);
|
} finally {
|
setLoading(false);
|
}
|
};
|
|
useEffect(() => {
|
load();
|
loadSubjects();
|
}, []);
|
|
const openCreate = () => {
|
setEditing(null);
|
form.resetFields();
|
form.setFieldsValue({ is_active: true });
|
setModalOpen(true);
|
};
|
|
const openEdit = (row: Grader) => {
|
setEditing(row);
|
form.setFieldsValue({
|
username: row.username,
|
display_name: row.display_name,
|
is_active: row.is_active,
|
subjects: row.subjects ?? [],
|
password: "",
|
});
|
setModalOpen(true);
|
};
|
|
const submit = async () => {
|
const v = await form.validateFields();
|
try {
|
if (editing) {
|
const body: Record<string, unknown> = {
|
username: v.username,
|
display_name: v.display_name ?? "",
|
is_active: v.is_active,
|
subjects: v.subjects,
|
};
|
if (v.password) body.password = v.password;
|
await api.put(`/api/admin/graders/${editing.id}`, body);
|
message.success("已更新");
|
} else {
|
await api.post("/api/admin/graders", {
|
username: v.username,
|
password: v.password,
|
display_name: v.display_name ?? "",
|
subjects: v.subjects,
|
});
|
message.success("已创建");
|
}
|
setModalOpen(false);
|
load();
|
} catch (e: unknown) {
|
const msg =
|
(e as { response?: { data?: { detail?: string } } })?.response?.data?.detail || "操作失败";
|
message.error(typeof msg === "string" ? msg : "操作失败");
|
}
|
};
|
|
return (
|
<Card
|
title="考评员管理"
|
extra={
|
<Button type="primary" onClick={openCreate}>
|
新增考评员
|
</Button>
|
}
|
>
|
<Table
|
rowKey="id"
|
loading={loading}
|
dataSource={items}
|
scroll={{ x: 720 }}
|
columns={[
|
{ title: "账号", dataIndex: "username" },
|
{ title: "姓名", dataIndex: "display_name" },
|
{
|
title: "关联科目",
|
dataIndex: "subjects",
|
render: (subs: string[]) =>
|
subs?.length
|
? subs.map((s) => <Tag key={s}>{s}</Tag>)
|
: <span style={{ color: "#999" }}>未配置</span>,
|
},
|
{
|
title: "状态",
|
dataIndex: "is_active",
|
render: (v: boolean) => (v ? "启用" : "停用"),
|
},
|
{ title: "创建时间", dataIndex: "created_at" },
|
{
|
title: "操作",
|
render: (_, row) => (
|
<Space>
|
<Button type="link" onClick={() => openEdit(row)}>编辑</Button>
|
<Button
|
type="link"
|
danger
|
onClick={() => {
|
Modal.confirm({
|
title: "删除该考评员?",
|
content: `账号:${row.username}`,
|
onOk: async () => {
|
await api.delete(`/api/admin/graders/${row.id}`);
|
message.success("已删除");
|
load();
|
},
|
});
|
}}
|
>
|
删除
|
</Button>
|
</Space>
|
),
|
},
|
]}
|
/>
|
|
<Modal
|
title={editing ? "编辑考评员" : "新增考评员"}
|
open={modalOpen}
|
onCancel={() => setModalOpen(false)}
|
onOk={submit}
|
destroyOnClose
|
>
|
<Form form={form} layout="vertical">
|
<Form.Item name="username" label="账号" rules={[{ required: true, min: 2 }]}>
|
<Input autoComplete="off" />
|
</Form.Item>
|
<Form.Item
|
name="password"
|
label={editing ? "新密码(留空不修改)" : "密码"}
|
rules={editing ? [] : [{ required: true, min: 6 }]}
|
>
|
<Input.Password autoComplete="new-password" />
|
</Form.Item>
|
<Form.Item name="display_name" label="姓名">
|
<Input />
|
</Form.Item>
|
<Form.Item
|
name="subjects"
|
label="关联科目"
|
rules={[{ required: true, message: "请至少选择一个科目" }]}
|
>
|
<Select
|
mode="multiple"
|
placeholder="选择可查看成绩的科目"
|
options={subjectOptions.map((s) => ({ label: s, value: s }))}
|
optionFilterProp="label"
|
/>
|
</Form.Item>
|
{editing && (
|
<Form.Item name="is_active" label="启用" valuePropName="checked">
|
<Switch />
|
</Form.Item>
|
)}
|
</Form>
|
</Modal>
|
</Card>
|
);
|
}
|