first commit

This commit is contained in:
2026-03-17 14:30:02 +08:00
commit 313f5b3550
136 changed files with 57671 additions and 0 deletions

View File

@@ -0,0 +1,260 @@
import React, { useState, useEffect, useCallback } from 'react';
import { Input, Button, Table, message, Modal, Popconfirm } from 'antd';
import { ExclamationCircleOutlined, SettingOutlined, PlusOutlined } from '@ant-design/icons';
import { request } from '@umijs/max';
import type { ColumnsType } from 'antd/es/table';
import styles from '../index.less';
interface GradeRecord {
id: string;
name: string;
createTime: string;
graduated?: boolean;
}
interface GradeManageProps {
onBack: () => void;
}
const GradeManage: React.FC<GradeManageProps> = ({ onBack }) => {
const [filterName, setFilterName] = useState('');
const [list, setList] = useState<GradeRecord[]>([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [loading, setLoading] = useState(false);
// 新增/编辑弹窗
const [modalVisible, setModalVisible] = useState(false);
const [modalLoading, setModalLoading] = useState(false);
const [editingRecord, setEditingRecord] = useState<GradeRecord | null>(null);
const [gradeName, setGradeName] = useState('');
const fetchList = useCallback(async () => {
setLoading(true);
try {
const params: Record<string, any> = { page, pageSize };
if (filterName) params.name = filterName;
const res = await request('/api/admin/grade', { params });
if (res.code === 0) {
setList(res.data.list);
setTotal(res.data.total);
}
} catch {
message.error('获取数据失败');
} finally {
setLoading(false);
}
}, [page, pageSize, filterName]);
useEffect(() => {
fetchList();
}, [fetchList]);
const handleSearch = () => {
setPage(1);
fetchList();
};
const handleReset = () => {
setFilterName('');
setPage(1);
};
const handleOpenAdd = () => {
setEditingRecord(null);
setGradeName('');
setModalVisible(true);
};
const handleOpenEdit = (record: GradeRecord) => {
setEditingRecord(record);
setGradeName(record.name);
setModalVisible(true);
};
const handleModalSubmit = async () => {
if (!gradeName.trim()) {
message.warning('请输入年级名称');
return;
}
setModalLoading(true);
try {
const url = editingRecord ? '/api/admin/grade/edit' : '/api/admin/grade/add';
const data = editingRecord ? { id: editingRecord.id, name: gradeName } : { name: gradeName };
const res = await request(url, { method: 'POST', data });
if (res.code === 0) {
message.success(editingRecord ? '编辑成功' : '新增成功');
setModalVisible(false);
fetchList();
} else {
message.error(res.message);
}
} catch {
message.error('操作失败');
} finally {
setModalLoading(false);
}
};
const handleGraduateConfirm = async (record: GradeRecord) => {
const res = await request('/api/admin/grade/graduate', {
method: 'POST',
data: { id: record.id },
});
if (res.code === 0) {
message.success('转毕业成功');
fetchList();
} else {
message.error(res.message);
}
};
const handleRestoreConfirm = async (record: GradeRecord) => {
const res = await request('/api/admin/grade/restore', {
method: 'POST',
data: { id: record.id },
});
if (res.code === 0) {
message.success('还原成功');
fetchList();
} else {
message.error(res.message);
}
};
const columns: ColumnsType<GradeRecord> = [
{ title: '年级名称', dataIndex: 'name', key: 'name' },
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime' },
{
title: '操作',
key: 'action',
width: 160,
render: (_, record) => (
<span className={styles.actionCell}>
<a className={styles.actionLink} onClick={() => handleOpenEdit(record)}></a>
<span className={styles.actionDivider}>|</span>
{record.graduated ? (
<Popconfirm
title="还原"
description="确定要还原该年级吗?"
onConfirm={() => handleRestoreConfirm(record)}
okText="确定"
cancelText="取消"
overlayStyle={{ maxWidth: 200 }}
>
<a className={styles.actionLink}></a>
</Popconfirm>
) : (
<Popconfirm
title="转毕业"
description="转毕业后此年级下的所有学生将不再计入数据统计。"
onConfirm={() => handleGraduateConfirm(record)}
okText="确定"
cancelText="取消"
icon={<ExclamationCircleOutlined style={{ color: '#faad14' }} />}
overlayStyle={{ maxWidth: 200 }}
>
<a className={styles.actionLink}></a>
</Popconfirm>
)}
</span>
),
},
];
return (
<div className={styles.page}>
<h2 className={styles.title}></h2>
{/* 筛选区域 */}
<div className={styles.filterRow}>
<div className={styles.filterItem}>
<span className={styles.filterLabel}></span>
<Input
className={styles.filterInput}
placeholder="请输入"
value={filterName}
onChange={(e) => setFilterName(e.target.value)}
allowClear
/>
</div>
<div className={styles.filterBtns}>
<Button className={styles.searchBtn} onClick={handleSearch}></Button>
<Button className={styles.resetBtn} onClick={handleReset}></Button>
</div>
<div />
<div />
</div>
{/* 分割线 */}
<div className={styles.divider} />
{/* 操作按钮栏 */}
<div className={styles.actionBar}>
<div className={styles.actionBarLeft}>
<Button className={styles.primaryOutlineBtn} icon={<PlusOutlined />} onClick={handleOpenAdd}></Button>
</div>
<div className={styles.actionBarRight}>
<Button className={styles.defaultBtn} onClick={onBack} style={{ marginRight: 12 }}></Button>
<SettingOutlined className={styles.settingIcon} />
<span className={styles.settingText}></span>
</div>
</div>
{/* 数据表格 */}
<div className={styles.dataTable}>
<Table<GradeRecord>
columns={columns}
dataSource={list}
rowKey="id"
loading={loading}
pagination={{
current: page,
pageSize,
total,
showTotal: (t) => `${t}`,
showSizeChanger: true,
showQuickJumper: true,
pageSizeOptions: ['10', '20', '50', '100'],
onChange: (p, ps) => {
setPage(p);
setPageSize(ps);
},
}}
size="middle"
/>
</div>
{/* 新增/编辑年级弹窗 */}
<Modal
open={modalVisible}
title={editingRecord ? '编辑年级' : '新增年级'}
onCancel={() => setModalVisible(false)}
footer={null}
width={400}
centered
className={styles.addModal}
destroyOnClose
>
<div className={styles.addModalBody} style={{ padding: '24px 0' }}>
<div className={styles.formField}>
<label className={styles.formLabel}><span className={styles.formRequired}>*</span></label>
<Input
className={styles.formInput}
placeholder="请输入年级名称"
value={gradeName}
onChange={(e) => setGradeName(e.target.value)}
/>
</div>
</div>
<div className={styles.addModalFooter}>
<Button className={styles.confirmBtn} onClick={handleModalSubmit} loading={modalLoading}></Button>
<Button className={styles.cancelBtn} onClick={() => setModalVisible(false)}></Button>
</div>
</Modal>
</div>
);
};
export default GradeManage;