fix: resolve all admin typecheck errors (typed api wrapper, unused imports, missing hooks, PermissionButton children)

This commit is contained in:
2026-07-06 01:02:26 +08:00
parent 4a48a65a48
commit 6b0a21d266
22 changed files with 4051 additions and 273 deletions

View File

@@ -11,6 +11,7 @@ import {
Tag,
Popconfirm,
Upload,
App,
} from 'antd';
import {
PlusOutlined,
@@ -20,10 +21,21 @@ import {
InboxOutlined,
ExportOutlined,
DeleteOutlined,
EyeOutlined,
} from '@ant-design/icons';
import api from '../../api';
import PermissionButton from '../../components/PermissionButton';
const maskPhone = (phone: string) => {
if (!phone || phone.length < 7) return phone || '-';
return phone.slice(0, 3) + '****' + phone.slice(-4);
};
const maskIdNumber = (id: string) => {
if (!id || id.length < 8) return id || '-';
return id.slice(0, 3) + '***********' + id.slice(-4);
};
const statusMap: Record<string, { text: string; color: string }> = {
active: { text: '在读', color: 'green' },
graduated: { text: '已毕业', color: 'blue' },
@@ -32,9 +44,11 @@ const statusMap: Record<string, { text: string; color: string }> = {
};
const StudentsPage: React.FC = () => {
const { modal } = App.useApp();
const [data, setData] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const [tenants, setTenants] = useState<any[]>([]);
const [editing, setEditing] = useState<any>(null);
const [searchName, setSearchName] = useState('');
const [showArchived, setShowArchived] = useState(false);
@@ -42,6 +56,29 @@ const StudentsPage: React.FC = () => {
const [selectedRowKeys, setSelectedRowKeys] = useState<number[]>([]);
const [form] = Form.useForm();
const handleViewSensitive = (studentId: number, field: string, value: string) => {
modal.confirm({
title: '查看敏感信息',
content: `您即将查看 "${field}" 的完整信息。此操作将被记录。`,
okText: '确认查看',
cancelText: '取消',
onOk: async () => {
api.post('/operation-logs/audit', {
module: '学生管理',
action: '查看敏感信息',
targetId: studentId,
targetType: 'student',
detail: `查看${field}`,
}).catch(() => {});
modal.info({
title: field,
content: value,
okText: '关闭',
});
},
});
};
const handleBatchDelete = async () => {
try {
const res: any = await api.post('/students/batch-delete', { ids: selectedRowKeys });
@@ -72,6 +109,12 @@ const StudentsPage: React.FC = () => {
fetchData();
}, [searchName, showArchived]);
useEffect(() => {
api.get('/tenants', { params: { includeArchived: 'false' } }).then((res: unknown) => {
setTenants(res as Array<{ id: number; name: string }>);
}).catch(() => {});
}, []);
const handleSave = async () => {
const values = await form.validateFields();
try {
@@ -152,7 +195,23 @@ const StudentsPage: React.FC = () => {
{ title: 'ID', dataIndex: 'id', width: 60 },
{ title: '姓名', dataIndex: 'name', ellipsis: true },
{ title: '性别', dataIndex: 'gender', width: 60 },
{ title: '电话', dataIndex: 'phone', ellipsis: true },
{
title: '电话',
dataIndex: 'phone',
width: 140,
ellipsis: true,
render: (v: string, record: any) => {
if (!v) return '-';
return (
<span>
<span style={{ marginRight: 4 }}>{maskPhone(v)}</span>
<a onClick={() => handleViewSensitive(record.id, '电话', v)} title="点击查看完整号码">
<EyeOutlined style={{ fontSize: 12, color: '#999' }} />
</a>
</span>
);
},
},
{
title: '学号',
dataIndex: 'studentNumber',
@@ -165,15 +224,26 @@ const StudentsPage: React.FC = () => {
dataIndex: 'idNumber',
width: 180,
ellipsis: true,
render: (v: string) => v || '-',
render: (v: string, record: any) => {
if (!v) return '-';
return (
<span>
<span style={{ marginRight: 4 }}>{maskIdNumber(v)}</span>
<a onClick={() => handleViewSensitive(record.id, '身份证号', v)} title="点击查看完整号码">
<EyeOutlined style={{ fontSize: 12, color: '#999' }} />
</a>
</span>
);
},
},
{ title: '民族', dataIndex: 'ethnicity', width: 80 },
{ title: '紧急联系人', dataIndex: 'emergencyContact', ellipsis: true },
{ title: '紧急联系人电话', dataIndex: 'emergencyPhone', ellipsis: true },
{
title: '所属机构',
dataIndex: 'organization',
render: (v: string) => (v ? <Tag color="purple">{v}</Tag> : '-'),
dataIndex: 'tenant',
render: (tenant: { name?: string } | null) =>
tenant?.name ? <Tag color="purple">{tenant.name}</Tag> : '-',
},
{ title: '负责人', dataIndex: 'supervisor', ellipsis: true },
{
@@ -369,11 +439,18 @@ const StudentsPage: React.FC = () => {
<Input />
</Form.Item>
<Form.Item
name="organization"
name="tenantId"
label="所属机构"
tooltip="外部合作公司/机构名称,留空表示本机构"
tooltip="选择租赁方,留空表示本机构"
>
<Input placeholder="如XXX教育科技公司" />
<Select
allowClear
placeholder="选择租赁方"
options={tenants.map((t: { id: number; name: string }) => ({
value: t.id,
label: t.name,
}))}
/>
</Form.Item>
<Form.Item name="supervisor" label="负责人/班主任">
<Input />