diff --git a/apps/admin/src/components/AiChat/useAiChatMessageActions.tsx b/apps/admin/src/components/AiChat/useAiChatMessageActions.tsx index 274633c..552cb4c 100644 --- a/apps/admin/src/components/AiChat/useAiChatMessageActions.tsx +++ b/apps/admin/src/components/AiChat/useAiChatMessageActions.tsx @@ -271,7 +271,7 @@ export function useAiChatMessageActions({ }, }); }, - [activeId, isRequesting, refreshConversations, removeMessage], + [activeId, isRequesting, refreshConversations, removeMessage, modal], ); const confirmEditMessage = useCallback( diff --git a/apps/admin/src/components/ECharts.tsx b/apps/admin/src/components/ECharts.tsx index 047acca..ab34151 100644 --- a/apps/admin/src/components/ECharts.tsx +++ b/apps/admin/src/components/ECharts.tsx @@ -50,13 +50,15 @@ interface EChartsProps { const ECharts: React.FC = ({ option, style, className, onReady }) => { const containerRef = useRef(null); + const optionRef = useRef(option); + optionRef.current = option; const onReadyRef = useRef(onReady); onReadyRef.current = onReady; useEffect(() => { if (!containerRef.current) return; const chart = echarts.init(containerRef.current); - chart.setOption(option); + chart.setOption(optionRef.current); onReadyRef.current?.(chart); const observer = new ResizeObserver(() => chart.resize()); observer.observe(containerRef.current); diff --git a/apps/admin/src/hooks/useViewSensitive.ts b/apps/admin/src/hooks/useViewSensitive.ts index 856f9e2..d1c2dfc 100644 --- a/apps/admin/src/hooks/useViewSensitive.ts +++ b/apps/admin/src/hooks/useViewSensitive.ts @@ -62,6 +62,6 @@ export function useViewSensitive(studentId: number, module: string, canLog: bool }, }); }, - [studentId, module], + [studentId, module, modal], ); } diff --git a/apps/admin/src/layouts/MainLayout.tsx b/apps/admin/src/layouts/MainLayout.tsx index 1d27a1d..89eff12 100644 --- a/apps/admin/src/layouts/MainLayout.tsx +++ b/apps/admin/src/layouts/MainLayout.tsx @@ -169,19 +169,22 @@ const MainLayout: React.FC = () => { navigate(key); if (usesDrawer) setDrawerOpen(false); }, - [navigate, usesDrawer], + [navigate, usesDrawer, setDrawerOpen], ); - const findSelectedKeys = (items: AppMenuItem[], pathname: string): string[] => { - for (const item of items) { - if (item.key === pathname) return [item.key]; - if (item.children) { - const found = findSelectedKeys(item.children, pathname); - if (found.length > 0) return found; + const findSelectedKeys = useCallback( + (items: AppMenuItem[], pathname: string): string[] => { + for (const item of items) { + if (item.key === pathname) return [item.key]; + if (item.children) { + const found = findSelectedKeys(item.children, pathname); + if (found.length > 0) return found; + } } - } - return [pathname]; - }; + return [pathname]; + }, + [], + ); const findOpenKeys = (items: AppMenuItem[], pathname: string): string[] => { for (const item of items) { @@ -201,7 +204,7 @@ const MainLayout: React.FC = () => { const selectedKeys = useMemo( () => findSelectedKeys(menuItems, location.pathname), - [menuItems, location.pathname], + [menuItems, location.pathname, findSelectedKeys], ); // 路径变化时同步展开的菜单(不干扰用户手动展开/收起) useEffect(() => { @@ -210,20 +213,20 @@ const MainLayout: React.FC = () => { const routeOpenKeys = findOpenKeys(menuItems, location.pathname); setOpenKeys((currentKeys) => [...new Set([...currentKeys, ...routeOpenKeys])]); } - }, [location.pathname, menuItems]); + }, [location.pathname, menuItems, setOpenKeys]); const handleOpenChange = useCallback((keys: string[]) => { setOpenKeys(keys); - }, []); + }, [setOpenKeys]); - const transformToMenuItems = (items: AppMenuItem[]): any[] => { + const transformToMenuItems = useCallback((items: AppMenuItem[]): any[] => { return items.map((item) => ({ key: item.key, icon: item.icon ? iconMap[item.icon] : undefined, label: item.label, children: item.children ? transformToMenuItems(item.children) : undefined, })); - }; + }, []); const menuContent = useMemo( () => ( { style={{ border: 'none' }} /> ), - [selectedKeys, openKeys, menuItems, handleMenuClick], + [selectedKeys, openKeys, menuItems, handleMenuClick, handleOpenChange, transformToMenuItems], ); return ( diff --git a/apps/admin/src/main.tsx b/apps/admin/src/main.tsx index 7d05a2d..9a36429 100644 --- a/apps/admin/src/main.tsx +++ b/apps/admin/src/main.tsx @@ -37,7 +37,10 @@ const queryClient = new QueryClient({ }, }); -ReactDOM.createRoot(document.getElementById('root')!).render( +const rootElement = document.getElementById('root'); +if (!rootElement) throw new Error('未找到 #root 挂载点'); + +ReactDOM.createRoot(rootElement).render( diff --git a/apps/admin/src/pages/AiConfig/index.tsx b/apps/admin/src/pages/AiConfig/index.tsx index 2347cc9..325b649 100644 --- a/apps/admin/src/pages/AiConfig/index.tsx +++ b/apps/admin/src/pages/AiConfig/index.tsx @@ -276,7 +276,7 @@ const AiConfigPage: React.FC = () => { } finally { setTesting(false); } - }, [formValues, form, currentProvider]); + }, [formValues, form, currentProvider, refreshConfig]); // ── Clear key ── @@ -299,7 +299,7 @@ const AiConfigPage: React.FC = () => { } }, }); - }, [config, modal]); + }, [config, modal, clearKeyMutation]); // ── Step navigation ── diff --git a/apps/admin/src/pages/Attendance/admin.tsx b/apps/admin/src/pages/Attendance/admin.tsx index 1960c35..c5dc68f 100644 --- a/apps/admin/src/pages/Attendance/admin.tsx +++ b/apps/admin/src/pages/Attendance/admin.tsx @@ -115,10 +115,11 @@ export const AdminAttendanceArchive: React.FC<{ canEdit: boolean }> = ({ canEdit enabled: !!classId && !!attendanceDate, queryFn: async () => { try { + if (!attendanceDate) return []; return validateResponse( attendanceScheduleOptionsSchema, await api.get('/attendance-records/schedules', { - params: { classId, date: attendanceDate!.format('YYYY-MM-DD') }, + params: { classId, date: attendanceDate.format('YYYY-MM-DD') }, }), ); } catch (error: unknown) { @@ -214,7 +215,7 @@ export const AdminAttendanceArchive: React.FC<{ canEdit: boolean }> = ({ canEdit if (effectiveScheduleId) params.scheduleId = effectiveScheduleId; return params; }, - [page, pageSize, classId, attendanceDate, status, session, scheduleId], + [page, pageSize, classId, attendanceDate, status, session, effectiveScheduleId], ); const { data: syncStatus = null, refetch: refetchSyncStatus } = useQuery({ @@ -334,7 +335,7 @@ export const AdminAttendanceArchive: React.FC<{ canEdit: boolean }> = ({ canEdit } finally { setRefreshingDingTalk(false); } - }, [attendanceDate, classId, loadRecords, loadSyncStatus, session]); + }, [attendanceDate, classId, loadRecords, loadSyncStatus, session, refreshDingTalkMutation]); const resetFilters = () => { setClassId(undefined); diff --git a/apps/admin/src/pages/Bills/index.tsx b/apps/admin/src/pages/Bills/index.tsx index 24106d8..608c861 100644 --- a/apps/admin/src/pages/Bills/index.tsx +++ b/apps/admin/src/pages/Bills/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo } from 'react'; +import React, { useState, useMemo, useCallback } from 'react'; import { App, Table, @@ -139,7 +139,7 @@ const BillsPage: React.FC = () => { } }; - const showDetail = async (id: number) => { + const showDetail = useCallback(async (id: number) => { setDetailLoading(true); try { const res = await api.get(`/bills/${id}`); @@ -149,60 +149,69 @@ const BillsPage: React.FC = () => { } finally { setDetailLoading(false); } - }; + }, []); - const handleCancel = async (id: number) => { - let reason = ''; - modal.confirm({ - title: '取消账单并退回已扣余额', - content: ( - { - reason = event.target.value; - }} - /> - ), - okText: '确认取消', - cancelText: '返回', - onOk: async () => { - if (!reason.trim()) { - message.error('请输入取消原因'); - throw new Error('reason required'); - } - await cancelMutation.mutateAsync({ id, reason: reason.trim() }); - message.success('账单已取消,已扣余额已冲正退回'); - }, - }); - }; + const handleCancel = useCallback( + (id: number) => { + let reason = ''; + modal.confirm({ + title: '取消账单并退回已扣余额', + content: ( + { + reason = event.target.value; + }} + /> + ), + okText: '确认取消', + cancelText: '返回', + onOk: async () => { + if (!reason.trim()) { + message.error('请输入取消原因'); + throw new Error('reason required'); + } + await cancelMutation.mutateAsync({ id, reason: reason.trim() }); + message.success('账单已取消,已扣余额已冲正退回'); + }, + }); + }, + [modal, cancelMutation], + ); - const handleArchive = async (id: number) => { - try { - await archiveMutation.mutateAsync(id); - message.success('账单已归档'); - } catch { - // 错误提示由 useApiMutation 统一处理 - } - }; + const handleArchive = useCallback( + async (id: number) => { + try { + await archiveMutation.mutateAsync(id); + message.success('账单已归档'); + } catch { + // 错误提示由 useApiMutation 统一处理 + } + }, + [archiveMutation], + ); - const handlePurge = (id: number, studentName: string, period: string) => { - modal.confirm({ - title: `永久删除账单(${studentName} ${period})?`, - content: '删除后不可恢复,该账单及其明细将被物理删除。确定继续?', - okText: '永久删除', - okButtonProps: { danger: true }, - cancelText: '取消', - onOk: async () => { - try { - await purgeMutation.mutateAsync(id); - message.success('已永久删除(不可恢复)'); - } catch { - // 错误提示由 useApiMutation 统一处理 - } - }, - }); - }; + const handlePurge = useCallback( + (id: number, studentName: string, period: string) => { + modal.confirm({ + title: `永久删除账单(${studentName} ${period})?`, + content: '删除后不可恢复,该账单及其明细将被物理删除。确定继续?', + okText: '永久删除', + okButtonProps: { danger: true }, + cancelText: '取消', + onOk: async () => { + try { + await purgeMutation.mutateAsync(id); + message.success('已永久删除(不可恢复)'); + } catch { + // 错误提示由 useApiMutation 统一处理 + } + }, + }); + }, + [modal, purgeMutation], + ); const batchArchive = async () => { if (selectedRows.length === 0) return message.warning('请先选择账单'); @@ -226,7 +235,7 @@ const BillsPage: React.FC = () => { ); }; - const handleExportPdf = async (billId: number) => { + const handleExportPdf = useCallback(async (billId: number) => { const printWindow = window.open('', '_blank'); if (!printWindow) { message.error('浏览器阻止了打印窗口,请允许弹出窗口后重试'); @@ -245,7 +254,7 @@ const BillsPage: React.FC = () => { printWindow.close(); message.error(error?.message || '账单加载失败'); } - }; + }, []); const columns = useMemo( () => [ diff --git a/apps/admin/src/pages/Classes/index.tsx b/apps/admin/src/pages/Classes/index.tsx index e12af51..460ca94 100644 --- a/apps/admin/src/pages/Classes/index.tsx +++ b/apps/admin/src/pages/Classes/index.tsx @@ -89,33 +89,6 @@ const ClassesPage: React.FC = () => { const [saving, setSaving] = useState(false); const [showArchived, setShowArchived] = useState(false); - const handleArchive = async (id: number, archive: boolean) => { - try { - await archiveMutation.mutateAsync({ id, archive }); - message.success(archive ? '已归档' : '已恢复'); - } catch { - // 错误提示由 useApiMutation 统一处理 - } - }; - - const handlePurge = (record: ClassItem) => { - modal.confirm({ - title: `永久删除班级「${record.name}」?`, - content: '删除后不可恢复,存在学生、教师、排课、考试或考勤关联时将无法删除。确定继续?', - okText: '永久删除', - okButtonProps: { danger: true }, - cancelText: '取消', - onOk: async () => { - try { - await purgeMutation.mutateAsync(record.id); - message.success('已永久删除(不可恢复)'); - } catch { - // 错误提示由 useApiMutation 统一处理 - } - }, - }); - }; - const { data = [], isLoading, @@ -160,6 +133,39 @@ const ClassesPage: React.FC = () => { { invalidate: [['classes']] }, ); + const handleArchive = useCallback( + async (id: number, archive: boolean) => { + try { + await archiveMutation.mutateAsync({ id, archive }); + message.success(archive ? '已归档' : '已恢复'); + } catch { + // 错误提示由 useApiMutation 统一处理 + } + }, + [archiveMutation], + ); + + const handlePurge = useCallback( + (record: ClassItem) => { + modal.confirm({ + title: `永久删除班级「${record.name}」?`, + content: '删除后不可恢复,存在学生、教师、排课、考试或考勤关联时将无法删除。确定继续?', + okText: '永久删除', + okButtonProps: { danger: true }, + cancelText: '取消', + onOk: async () => { + try { + await purgeMutation.mutateAsync(record.id); + message.success('已永久删除(不可恢复)'); + } catch { + // 错误提示由 useApiMutation 统一处理 + } + }, + }); + }, + [modal, purgeMutation], + ); + const filtered = useMemo(() => { if (!searchText) return data; const q = searchText.toLowerCase(); @@ -174,16 +180,19 @@ const ClassesPage: React.FC = () => { setModalOpen(true); }; - const handleEdit = (record: ClassItem) => { - setEditing(record); - form.setFieldsValue({ - ...record, - notes: record.notes ?? undefined, - startDate: record.startDate ? dayjs(record.startDate) : undefined, - endDate: record.endDate ? dayjs(record.endDate) : undefined, - }); - setModalOpen(true); - }; + const handleEdit = useCallback( + (record: ClassItem) => { + setEditing(record); + form.setFieldsValue({ + ...record, + notes: record.notes ?? undefined, + startDate: record.startDate ? dayjs(record.startDate) : undefined, + endDate: record.endDate ? dayjs(record.endDate) : undefined, + }); + setModalOpen(true); + }, + [form], + ); const handleSubmit = async () => { setSaving(true); @@ -369,7 +378,7 @@ const ClassesPage: React.FC = () => { ), }, ], - [saveCell, canPurgeClass, handlePurge], + [saveCell, canPurgeClass, handlePurge, navigate, handleEdit, handleArchive], ); return ( diff --git a/apps/admin/src/pages/ClassroomRentals/index.tsx b/apps/admin/src/pages/ClassroomRentals/index.tsx index d89d498..d2d5bc4 100644 --- a/apps/admin/src/pages/ClassroomRentals/index.tsx +++ b/apps/admin/src/pages/ClassroomRentals/index.tsx @@ -198,7 +198,7 @@ const ClassroomRentalsPage: React.FC = () => { } } }, - [], + [setUnavailableDates], ); const handleClassroomChange = (classroomId: number) => { diff --git a/apps/admin/src/pages/ClassroomSchedule/index.tsx b/apps/admin/src/pages/ClassroomSchedule/index.tsx index fd8101c..f6c2d10 100644 --- a/apps/admin/src/pages/ClassroomSchedule/index.tsx +++ b/apps/admin/src/pages/ClassroomSchedule/index.tsx @@ -65,7 +65,7 @@ const ClassroomSchedulePage: React.FC = () => { for (const c of data.classrooms) { const key = `${c.building || '其他'}${c.floor ? ` · ${c.floor}层` : ''}`; if (!map.has(key)) map.set(key, []); - map.get(key)!.push(c); + map.get(key)?.push(c); } return Array.from(map.entries()).map(([name, classrooms]) => ({ name, classrooms })); }, [data]); diff --git a/apps/admin/src/pages/Classrooms/index.tsx b/apps/admin/src/pages/Classrooms/index.tsx index ecc4e4f..869b901 100644 --- a/apps/admin/src/pages/Classrooms/index.tsx +++ b/apps/admin/src/pages/Classrooms/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo } from 'react'; +import React, { useState, useMemo, useCallback } from 'react'; import { useQuery } from '@tanstack/react-query'; import { useApiMutation } from '../../hooks/useApiMutation'; import { validateResponse } from '../../utils/validate'; @@ -147,50 +147,63 @@ const ClassroomsPage: React.FC = () => { } }; - const saveCell = async (record: any, field: string, value: unknown) => { - try { - await saveCellMutation.mutateAsync({ record, field, value }); - message.success('已保存'); - } catch { - // 错误提示由 useApiMutation 统一处理 - } - }; + const saveCell = useCallback( + async (record: any, field: string, value: unknown) => { + try { + await saveCellMutation.mutateAsync({ record, field, value }); + message.success('已保存'); + } catch { + // 错误提示由 useApiMutation 统一处理 + } + }, + [saveCellMutation], + ); - const handleArchive = async (id: number) => { - try { - await archiveMutation.mutateAsync(id); - message.success('已归档'); - } catch { - // 错误提示由 useApiMutation 统一处理 - } - }; + const handleArchive = useCallback( + async (id: number) => { + try { + await archiveMutation.mutateAsync(id); + message.success('已归档'); + } catch { + // 错误提示由 useApiMutation 统一处理 + } + }, + [archiveMutation], + ); - const handleRestore = async (id: number) => { - try { - await restoreMutation.mutateAsync(id); - message.success('已恢复'); - } catch { - // 错误提示由 useApiMutation 统一处理 - } - }; + const handleRestore = useCallback( + async (id: number) => { + try { + await restoreMutation.mutateAsync(id); + message.success('已恢复'); + } catch { + // 错误提示由 useApiMutation 统一处理 + } + }, + [restoreMutation], + ); - const handlePurge = (id: number, name: string) => { - modal.confirm({ - title: `永久删除教室「${name}」?`, - content: '删除后不可恢复,该教室及其排课、租赁、考勤机关联将被清除(存在关联数据时将无法删除)。确定继续?', - okText: '永久删除', - okButtonProps: { danger: true }, - cancelText: '取消', - onOk: async () => { - try { - await purgeMutation.mutateAsync(id); - message.success('已永久删除(不可恢复)'); - } catch { - // 错误提示由 useApiMutation 统一处理 - } - }, - }); - }; + const handlePurge = useCallback( + (id: number, name: string) => { + modal.confirm({ + title: `永久删除教室「${name}」?`, + content: + '删除后不可恢复,该教室及其排课、租赁、考勤机关联将被清除(存在关联数据时将无法删除)。确定继续?', + okText: '永久删除', + okButtonProps: { danger: true }, + cancelText: '取消', + onOk: async () => { + try { + await purgeMutation.mutateAsync(id); + message.success('已永久删除(不可恢复)'); + } catch { + // 错误提示由 useApiMutation 统一处理 + } + }, + }); + }, + [modal, purgeMutation], + ); const handleDownloadTemplate = () => { const baseURL = import.meta.env.PROD @@ -397,7 +410,7 @@ const ClassroomsPage: React.FC = () => { ), }, ], - [handlePurge, hasPermission], + [handlePurge, hasPermission, saveCell, handleArchive, handleRestore, form], ); return ( diff --git a/apps/admin/src/pages/Dashboard/index.tsx b/apps/admin/src/pages/Dashboard/index.tsx index 055555c..db8f3c1 100644 --- a/apps/admin/src/pages/Dashboard/index.tsx +++ b/apps/admin/src/pages/Dashboard/index.tsx @@ -186,7 +186,8 @@ const DashboardPage: React.FC = () => { aria-label="选择日期范围" value={[dayjs(period[0]), dayjs(period[1])]} onChange={(dates) => { - if (dates) setPeriod([dates[0]!.format('YYYY-MM-DD'), dates[1]!.format('YYYY-MM-DD')]); + if (dates?.[0] && dates?.[1]) + setPeriod([dates[0].format('YYYY-MM-DD'), dates[1].format('YYYY-MM-DD')]); }} /> diff --git a/apps/admin/src/pages/Occupancies/index.tsx b/apps/admin/src/pages/Occupancies/index.tsx index 5b5abc1..f4d1f64 100644 --- a/apps/admin/src/pages/Occupancies/index.tsx +++ b/apps/admin/src/pages/Occupancies/index.tsx @@ -369,23 +369,26 @@ const OccupanciesPage: React.FC = () => { } }; - const handlePurge = (id: number, studentName: string) => { - modal.confirm({ - title: `永久删除入住记录(${studentName})?`, - content: '删除后不可恢复,该入住记录将被物理删除。确定继续?', - okText: '永久删除', - okButtonProps: { danger: true }, - cancelText: '取消', - onOk: async () => { - try { - await purgeMutation.mutateAsync(id); - message.success('已永久删除(不可恢复)'); - } catch { - // 错误提示由 useApiMutation 统一处理 - } - }, - }); - }; + const handlePurge = useCallback( + (id: number, studentName: string) => { + modal.confirm({ + title: `永久删除入住记录(${studentName})?`, + content: '删除后不可恢复,该入住记录将被物理删除。确定继续?', + okText: '永久删除', + okButtonProps: { danger: true }, + cancelText: '取消', + onOk: async () => { + try { + await purgeMutation.mutateAsync(id); + message.success('已永久删除(不可恢复)'); + } catch { + // 错误提示由 useApiMutation 统一处理 + } + }, + }); + }, + [modal, purgeMutation], + ); const handleBatchPurge = async () => { if (batchLoading) return; diff --git a/apps/admin/src/pages/OperationLogs/index.tsx b/apps/admin/src/pages/OperationLogs/index.tsx index 60d83b2..55d7ea8 100644 --- a/apps/admin/src/pages/OperationLogs/index.tsx +++ b/apps/admin/src/pages/OperationLogs/index.tsx @@ -153,8 +153,8 @@ const OperationLogsPage: React.FC = () => { placeholder={['开始日期', '结束日期']} format="YYYY-MM-DD" onChange={(dates) => { - if (dates) - setDateRange([dates[0]!.format('YYYY-MM-DD'), dates[1]!.format('YYYY-MM-DD')]); + if (dates?.[0] && dates?.[1]) + setDateRange([dates[0].format('YYYY-MM-DD'), dates[1].format('YYYY-MM-DD')]); else setDateRange(null); setPage(1); }} diff --git a/apps/admin/src/pages/Roles/index.tsx b/apps/admin/src/pages/Roles/index.tsx index e3da544..aeaa261 100644 --- a/apps/admin/src/pages/Roles/index.tsx +++ b/apps/admin/src/pages/Roles/index.tsx @@ -92,12 +92,15 @@ const RolesPage: React.FC = () => { setModalOpen(true); }; - const handleEdit = (record: RoleItem) => { - setEditing(record); - form.setFieldsValue({ name: record.name, description: record.description }); - setSelectedPermIds(record.permissions.map((p) => p.id)); - setModalOpen(true); - }; + const handleEdit = useCallback( + (record: RoleItem) => { + setEditing(record); + form.setFieldsValue({ name: record.name, description: record.description }); + setSelectedPermIds(record.permissions.map((p) => p.id)); + setModalOpen(true); + }, + [form], + ); const handleSubmit = async () => { setSaving(true); @@ -117,14 +120,17 @@ const RolesPage: React.FC = () => { } }; - const handleDisable = async (id: number) => { - try { - await disableMutation.mutateAsync(id); - message.success('角色已停用'); - } catch { - // 错误提示由 useApiMutation 统一处理 - } - }; + const handleDisable = useCallback( + async (id: number) => { + try { + await disableMutation.mutateAsync(id); + message.success('角色已停用'); + } catch { + // 错误提示由 useApiMutation 统一处理 + } + }, + [disableMutation], + ); const groupNames: Record = { dashboard: '数据面板', @@ -264,7 +270,7 @@ const RolesPage: React.FC = () => { ), }, ], - [permissionOptions, saveCell], + [permissionOptions, saveCell, handleEdit, handleDisable], ); const handleGroupCheckAll = (group: string, checked: boolean) => { diff --git a/apps/admin/src/pages/RoomVisual/index.tsx b/apps/admin/src/pages/RoomVisual/index.tsx index 122471f..fd0c375 100644 --- a/apps/admin/src/pages/RoomVisual/index.tsx +++ b/apps/admin/src/pages/RoomVisual/index.tsx @@ -102,7 +102,7 @@ const RoomVisualPage: React.FC = () => { queryKey: ['rooms', 'visual', isHistorical, asOf], queryFn: async () => { try { - const params = isHistorical ? { asOf: asOf!.format('YYYY-MM-DD') } : undefined; + const params = asOf ? { asOf: asOf.format('YYYY-MM-DD') } : undefined; return await api.get('/rooms/visual', { params }); } catch (e: unknown) { message.error(getErrorMessage(e, '加载失败,请稍后重试')); @@ -229,7 +229,7 @@ const RoomVisualPage: React.FC = () => { showIcon icon={} style={{ marginBottom: 16 }} - title={`正在查看 ${asOf!.format('YYYY年M月D日')} 的历史入住情况(含当日已归档房间),非实时数据`} + title={`正在查看 ${asOf ? asOf.format('YYYY年M月D日') : ''} 的历史入住情况(含当日已归档房间),非实时数据`} action={