fix(admin): UX improvements — silent fetch failures, empty states, batch loading guards, dashboard refresh

- Replace console.error-only catches with message.error user-facing notifications
  across Bills, Classes, ClassroomRentals, ClassroomSchedule, Classrooms, Deposits,
  Expenses, OperationLogs, Permissions, Roles, RoomVisual, Rooms, Students,
  Tenants, Users
- Add Empty component via Table locale prop on list pages: Bills, Classes,
  ClassroomRentals, Classrooms, Deposits, Expenses (room+personal), Occupancies,
  Rooms, Students, Tenants, Roles
- Add batchLoading state to batch delete/update operations: Bills (batchDelete,
  batchUpdateStatus), Expenses (batchDeleteRoom, batchDeletePersonal),
  Occupancies (batchCheckOut, batchDelete), Rooms (batchDelete),
  Students (batchDelete)
- Add refreshLoading indicator to Dashboard header when re-fetching data
- Consistent error pattern: catch (e: unknown) { const err = e as { message?: string }; message.error(...); }
This commit is contained in:
2026-07-09 18:03:42 +08:00
parent 6029d8e2fd
commit c59fd6ce92
17 changed files with 141 additions and 59 deletions

View File

@@ -14,6 +14,7 @@ import {
Tabs,
Popconfirm,
Upload,
Empty,
} from 'antd';
import {
PlusOutlined,
@@ -51,6 +52,7 @@ const ExpensesPage: React.FC = () => {
const [selectedRoomKeys, setSelectedRoomKeys] = useState<number[]>([]);
const [selectedPersonalKeys, setSelectedPersonalKeys] = useState<number[]>([]);
const [saving, setSaving] = useState(false);
const [batchLoading, setBatchLoading] = useState(false);
// Dynamic expense type options from API
const [typeOptions, setTypeOptions] = useState<{ value: string; label: string }[]>([]);
@@ -78,6 +80,7 @@ const ExpensesPage: React.FC = () => {
}, []);
const handleBatchDeleteRoom = async () => {
setBatchLoading(true);
try {
const res: any = await api.post('/expenses/room/batch-delete', { ids: selectedRoomKeys });
message.success(res?.message || `已删除 ${selectedRoomKeys.length}`);
@@ -85,10 +88,13 @@ const ExpensesPage: React.FC = () => {
fetchData();
} catch (e: any) {
message.error(e?.message || '批量删除失败');
} finally {
setBatchLoading(false);
}
};
const handleBatchDeletePersonal = async () => {
setBatchLoading(true);
try {
const res: any = await api.post('/expenses/personal/batch-delete', {
ids: selectedPersonalKeys,
@@ -98,6 +104,8 @@ const ExpensesPage: React.FC = () => {
fetchData();
} catch (e: any) {
message.error(e?.message || '批量删除失败');
} finally {
setBatchLoading(false);
}
};
@@ -114,8 +122,9 @@ const ExpensesPage: React.FC = () => {
setPersonalExpenses(pe);
setRooms(rm);
setStudents(st);
} catch (e) {
console.error(e);
} catch (e: unknown) {
const err = e as { message?: string };
message.error(err?.message || '加载失败,请稍后重试');
}
setLoading(false);
}, []);
@@ -410,6 +419,7 @@ const ExpensesPage: React.FC = () => {
danger
icon={<DeleteOutlined />}
disabled={selectedRoomKeys.length === 0}
loading={batchLoading}
>
</PermissionButton>
@@ -433,6 +443,7 @@ const ExpensesPage: React.FC = () => {
dataSource={filteredRoomExpenses}
rowKey="id"
loading={loading}
locale={{ emptyText: <Empty description="暂无数据" /> }}
scroll={{ x: 1200 }}
pagination={{ pageSize: 15, showTotal: (total) => `${total}` }}
rowSelection={{
@@ -440,7 +451,6 @@ const ExpensesPage: React.FC = () => {
onChange: (keys) => setSelectedRoomKeys(keys as number[]),
}}
/>
</>
),
},
{
@@ -532,6 +542,7 @@ const ExpensesPage: React.FC = () => {
danger
icon={<DeleteOutlined />}
disabled={selectedPersonalKeys.length === 0}
loading={batchLoading}
>
</PermissionButton>
@@ -549,12 +560,12 @@ const ExpensesPage: React.FC = () => {
</PermissionButton>
</Space>
</div>
<Table
columns={personalColumns}
dataSource={filteredPersonalExpenses}
rowKey="id"
loading={loading}
locale={{ emptyText: <Empty description="暂无数据" /> }}
scroll={{ x: 1200 }}
pagination={{ pageSize: 15, showTotal: (total) => `${total}` }}
rowSelection={{