From b0f7883f331b51d78c80656d4e2eb5cb97c94c85 Mon Sep 17 00:00:00 2001 From: wangziqi Date: Thu, 9 Jul 2026 17:40:57 +0800 Subject: [PATCH] feat(admin): remove all department/campus scoping from frontend - Delete CampusSwitcher component and useCampus hook - Delete Departments page and its route - Remove campus header interceptor from API client - Remove departmentId from Class interfaces - Remove campusLocation from student profile - Remove department permissions from test fixtures - Remove currentCampusId from test cleanup TypeScript compiles clean. --- apps/admin/src/App.tsx | 9 - apps/admin/src/api/index.ts | 4 - apps/admin/src/components/CampusSwitcher.tsx | 36 -- .../StudentProfileContent/index.tsx | 8 - apps/admin/src/hooks/useCampus.ts | 40 -- apps/admin/src/layouts/MainLayout.tsx | 3 - apps/admin/src/pages/Classes/detail.tsx | 1 - apps/admin/src/pages/Classes/index.tsx | 1 - apps/admin/src/pages/Departments/index.tsx | 423 ------------------ apps/admin/src/test/fixtures.ts | 1 - apps/admin/src/test/helpers.ts | 1 - apps/admin/src/test/setup.ts | 1 - 12 files changed, 528 deletions(-) delete mode 100644 apps/admin/src/components/CampusSwitcher.tsx delete mode 100644 apps/admin/src/hooks/useCampus.ts delete mode 100644 apps/admin/src/pages/Departments/index.tsx diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx index eed9a23..a813b9a 100644 --- a/apps/admin/src/App.tsx +++ b/apps/admin/src/App.tsx @@ -28,7 +28,6 @@ import PermissionsPage from './pages/Permissions'; import AttendancePage from './pages/Attendance'; import TeacherWorkspacePage from './pages/TeacherWorkspace'; import NotificationsPage from './pages/Notifications'; -import DepartmentsPage from './pages/Departments'; import IntegrationConfigPage from './pages/IntegrationConfig'; import PermissionRoute from './components/PermissionRoute'; @@ -255,14 +254,6 @@ const App: React.FC = () => { } /> - - - - } - /> { if (token) { config.headers.Authorization = `Bearer ${token}`; } - const campusId = localStorage.getItem('currentCampusId'); - if (campusId) { - config.headers['X-Campus-Id'] = campusId; - } return config; }); diff --git a/apps/admin/src/components/CampusSwitcher.tsx b/apps/admin/src/components/CampusSwitcher.tsx deleted file mode 100644 index 9104a70..0000000 --- a/apps/admin/src/components/CampusSwitcher.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { Select, Typography } from 'antd'; -import { EnvironmentOutlined } from '@ant-design/icons'; -import { useCampus } from '../hooks/useCampus'; - -const CampusSwitcher: React.FC = () => { - const { campuses, currentId, switchCampus, loading } = useCampus(); - - if (campuses.length <= 1) { - return ( - - - {campuses[0]?.name || '主校区'} - - ); - } - - const options = [ - ...campuses.map((c) => ({ value: String(c.id), label: c.name })), - { value: '', label: '全部校区' }, - ]; - - return ( - - - - @@ -903,9 +898,6 @@ const StudentProfileContent: React.FC = ({ {profile?.subjectDirection && ( {profile.subjectDirection} )} - {profile?.campusLocation && ( - {profile.campusLocation} - )} ([]); - const [currentId, setCurrentId] = useState( - () => localStorage.getItem('currentCampusId') || '' - ); - const [loading, setLoading] = useState(true); - - const fetchCampuses = useCallback(async () => { - try { - const data = await api.get('/departments') as unknown as Department[]; - const campusList = data.filter((d) => d.type === 'campus'); - setCampuses(campusList); - if (!currentId && campusList.length > 0) { - setCurrentId(String(campusList[0].id)); - localStorage.setItem('currentCampusId', String(campusList[0].id)); - } - } catch { /* ignore */ } - finally { setLoading(false); } - }, [currentId]); - - useEffect(() => { fetchCampuses(); }, []); - - const switchCampus = useCallback((id: string) => { - setCurrentId(id); - localStorage.setItem('currentCampusId', id); - window.dispatchEvent(new CustomEvent('campus-changed', { detail: id })); - }, []); - - return { campuses, currentId, switchCampus, loading }; -} diff --git a/apps/admin/src/layouts/MainLayout.tsx b/apps/admin/src/layouts/MainLayout.tsx index 249763f..a057799 100644 --- a/apps/admin/src/layouts/MainLayout.tsx +++ b/apps/admin/src/layouts/MainLayout.tsx @@ -29,7 +29,6 @@ import { } from '@ant-design/icons'; import { usePermission } from '../hooks/usePermission'; import NotificationBell from '../components/NotificationBell'; -import CampusSwitcher from '../components/CampusSwitcher'; const { Header, Sider, Content } = Layout; @@ -109,7 +108,6 @@ const allMenuItems: MenuItemType[] = [ label: '系统管理', permission: 'log:view', children: [ - { key: '/departments', icon: , label: '校区/部门', permission: 'department:view' }, { key: '/notifications', icon: , label: '通知中心', permission: 'notification:view' }, { key: '/operation-logs', icon: , label: '操作日志', permission: 'log:view' }, { key: '/roles', icon: , label: '角色管理', permission: 'role:view' }, @@ -289,7 +287,6 @@ const MainLayout: React.FC = () => { } onClick={() => (isMobile || isTablet ? setDrawerOpen(true) : setCollapsed(!collapsed))} /> - {isDesktop && }
= { - campus: '校区', - department: '部门', -}; - -const TYPE_COLORS: Record = { - campus: 'blue', - department: 'green', -}; - -const DepartmentsPage: React.FC = () => { - const [treeData, setTreeData] = useState([]); - const [flatDepts, setFlatDepts] = useState([]); - const [selectedId, setSelectedId] = useState(null); - const [selectedDept, setSelectedDept] = useState(null); - const [members, setMembers] = useState([]); - const [membersLoading, setMembersLoading] = useState(false); - const [treeLoading, setTreeLoading] = useState(false); - const [modalOpen, setModalOpen] = useState(false); - const [editing, setEditing] = useState(null); - const [form] = Form.useForm(); - const [saving, setSaving] = useState(false); - - const fetchTree = useCallback(async () => { - setTreeLoading(true); - try { - const data = await api.get('/departments/tree') as unknown as DepartmentItem[]; - const flat = await api.get('/departments') as unknown as DepartmentItem[]; - setFlatDepts(flat); - setTreeData(data.map(departmentToTreeNode)); - } catch { - message.error('加载部门树失败'); - } finally { - setTreeLoading(false); - } - }, []); - - useEffect(() => { - fetchTree(); - }, [fetchTree]); - - const fetchDetail = useCallback(async (id: number) => { - try { - const dept = await api.get(`/departments/${id}`) as unknown as DepartmentItem; - setSelectedDept(dept); - setMembersLoading(true); - const users = await api.get(`/departments/${id}/users`) as unknown as DeptMember[]; - setMembers(users); - } catch { - message.error('加载部门详情失败'); - } finally { - setMembersLoading(false); - } - }, []); - - const handleSelect = useCallback( - (keys: React.Key[]) => { - if (keys.length === 0) { - setSelectedId(null); - setSelectedDept(null); - setMembers([]); - return; - } - const id = String(keys[0]); - setSelectedId(id); - fetchDetail(Number(id)); - }, - [fetchDetail], - ); - - const handleAdd = useCallback(() => { - setEditing(null); - form.resetFields(); - if (selectedId) { - form.setFieldsValue({ parentId: Number(selectedId) }); - } - setModalOpen(true); - }, [selectedId, form]); - - const handleAddChild = useCallback(() => { - if (!selectedDept) return; - setEditing(null); - form.resetFields(); - form.setFieldsValue({ parentId: selectedDept.id }); - setModalOpen(true); - }, [selectedDept, form]); - - const handleEdit = useCallback(() => { - if (!selectedDept) return; - setEditing(selectedDept); - form.setFieldsValue({ - name: selectedDept.name, - parentId: selectedDept.parentId, - type: selectedDept.type, - sortOrder: selectedDept.sortOrder, - }); - setModalOpen(true); - }, [selectedDept, form]); - - const handleSubmit = useCallback(async () => { - setSaving(true); - try { - const values = await form.validateFields(); - const payload = { - name: values.name, - parentId: values.parentId || null, - type: values.type || 'department', - sortOrder: values.sortOrder ?? 0, - }; - if (editing) { - await api.put(`/departments/${editing.id}`, payload); - message.success('更新成功'); - } else { - await api.post('/departments', payload); - message.success('创建成功'); - } - setModalOpen(false); - await fetchTree(); - if (editing && selectedId) { - fetchDetail(editing.id); - } - } catch (err: unknown) { - if ( - err && - typeof err === 'object' && - 'message' in err && - typeof (err as { message: string }).message === 'string' - ) { - message.error((err as { message: string }).message); - } - // form validation error falls through silently - } finally { - setSaving(false); - } - }, [editing, fetchTree, fetchDetail, selectedId, form]); - - const handleDelete = useCallback(async () => { - if (!selectedDept) return; - try { - await api.delete(`/departments/${selectedDept.id}`); - message.success('删除成功'); - setSelectedId(null); - setSelectedDept(null); - setMembers([]); - await fetchTree(); - } catch (err: unknown) { - const msg = - err && typeof err === 'object' && 'message' in err - ? (err as { message: string }).message - : '删除失败'; - message.error(msg); - } - }, [selectedDept, fetchTree]); - - const memberColumns = [ - { title: 'ID', dataIndex: 'userId', key: 'userId', width: 60 }, - { title: '用户名', dataIndex: ['user', 'username'], key: 'username', width: 120 }, - { title: '姓名', dataIndex: ['user', 'name'], key: 'name', width: 120 }, - { - title: '默认部门', - dataIndex: 'isDefault', - key: 'isDefault', - width: 80, - render: (v: boolean) => (v ? : null), - }, - ]; - - return ( -
-
-

组织架构管理

- - - } - onClick={handleAdd} - > - 新增 - - -
- - - - - {treeLoading ? ( - - ) : ( - - )} - - - - - {selectedDept ? ( - - - {selectedDept.name} - - {TYPE_LABELS[selectedDept.type] || selectedDept.type} - - - } - extra={ - - } - onClick={handleEdit} - > - 编辑 - - } - onClick={handleAddChild} - > - 新增子部门 - - 0 - ? '该部门下存在子部门,可能无法删除' - : undefined} - onConfirm={handleDelete} - okText="确定" - cancelText="取消" - > - - - - } - > - - {selectedDept.id} - - - {TYPE_LABELS[selectedDept.type] || selectedDept.type} - - - - {selectedDept.sortOrder} - - - {selectedDept.parentId ?? '无(顶级)'} - - - - {selectedDept.status} - - - - - - - - - - ) : ( - - - - )} - - - - setModalOpen(false)} - destroyOnHidden - confirmLoading={saving} - > -
- - - - - - - - - - - - -
- - ); -}; - -export default DepartmentsPage; diff --git a/apps/admin/src/test/fixtures.ts b/apps/admin/src/test/fixtures.ts index 73af018..ec2fed6 100644 --- a/apps/admin/src/test/fixtures.ts +++ b/apps/admin/src/test/fixtures.ts @@ -221,6 +221,5 @@ export const PERMISSION_NODES = [ 'log:view', 'role:view', 'role:add', 'role:update', 'role:delete', 'user:view', 'user:add', 'user:update', 'user:delete', - 'department:view', 'department:add', 'department:update', 'department:delete', 'dashboard:view', ] as const; diff --git a/apps/admin/src/test/helpers.ts b/apps/admin/src/test/helpers.ts index 20ad80c..81beded 100644 --- a/apps/admin/src/test/helpers.ts +++ b/apps/admin/src/test/helpers.ts @@ -47,7 +47,6 @@ export function logout(): void { localStorage.removeItem('token'); localStorage.removeItem('user'); localStorage.removeItem('permissions'); - localStorage.removeItem('currentCampusId'); } // ── API helpers (authenticated) ───────────────────────────────────── diff --git a/apps/admin/src/test/setup.ts b/apps/admin/src/test/setup.ts index 192da15..2094502 100644 --- a/apps/admin/src/test/setup.ts +++ b/apps/admin/src/test/setup.ts @@ -18,7 +18,6 @@ afterEach(() => { localStorage.removeItem('token'); localStorage.removeItem('user'); localStorage.removeItem('permissions'); - localStorage.removeItem('currentCampusId'); }); export { BASE };