forked from wangziqi/gongxue-base
feat: DingTalk attendance import + integration config + expense types + UI polish
Server: - Add DingTalk attendance import service with SSE progress streaming - Add IntegrationConfig entity & module for multi-tenant DingTalk setup - Add ExpenseType entity & ExpenseTypesModule - Add SeedModule for DB initialization - Add UserDingMapping entity for DingTalk user linkage - Attendance service: import flow with dedup & student auto-mapping - Rooms service: time-range overlap queries - Sync controller/service: DingTalk integration wiring - Permission guard: refactor to pure re-export - Campus scope middleware: tenant-aware filtering Admin UI: - Attendance page: import UI with progress & result summary - All pages: tableStyle/tablePagination standardization - Login page: responsive styling - Sensitive data: useViewSensitive hook for masked viewing - Vite config: path aliases, build optimization - Test infra: vitest config, test utilities Docs: PRD DingTalk batch 1 & 2 design docs
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import React, { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import { Table, Input, Button, Modal, Form, Select, DatePicker, Tag, Space, message } from 'antd';
|
||||
import { EditOutlined } from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -51,6 +51,7 @@ const TeachersPage: React.FC = () => {
|
||||
const [search, setSearch] = useState('');
|
||||
const [profileModal, setProfileModal] = useState<TeacherRow | null>(null);
|
||||
const [form] = Form.useForm<ProfileFormValues>();
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -73,6 +74,7 @@ const TeachersPage: React.FC = () => {
|
||||
const handleSaveProfile = async () => {
|
||||
const values = await form.validateFields();
|
||||
if (!profileModal) return;
|
||||
setSaving(true);
|
||||
try {
|
||||
await api.put(`/rbac/teachers/${profileModal.id}/profile`, {
|
||||
subjects: values.subjects || [],
|
||||
@@ -88,10 +90,12 @@ const TeachersPage: React.FC = () => {
|
||||
msg = String(e.message);
|
||||
}
|
||||
message.error(msg);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
const columns = useMemo(() => [
|
||||
{ title: '姓名', dataIndex: 'name', key: 'name', width: 120 },
|
||||
{ title: '用户名', dataIndex: 'username', key: 'username', width: 130 },
|
||||
{
|
||||
@@ -166,7 +170,7 @@ const TeachersPage: React.FC = () => {
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
], []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -216,6 +220,7 @@ const TeachersPage: React.FC = () => {
|
||||
onOk={handleSaveProfile}
|
||||
onCancel={() => setProfileModal(null)}
|
||||
okText="保存"
|
||||
confirmLoading={saving}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item name="subjects" label="任教学科">
|
||||
|
||||
Reference in New Issue
Block a user