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

@@ -13,6 +13,7 @@ import {
Select,
Tooltip,
Spin,
Empty,
} from 'antd';
import {
FileTextOutlined,
@@ -54,6 +55,7 @@ const BillsPage: React.FC = () => {
const [generateForm] = Form.useForm();
const [saving, setSaving] = useState(false);
const [detailLoading, setDetailLoading] = useState(false);
const [batchLoading, setBatchLoading] = useState(false);
const fetchData = useCallback(async () => {
setLoading(true);
@@ -63,8 +65,8 @@ const BillsPage: React.FC = () => {
if (filterExpenseType) params.expenseType = filterExpenseType;
const res = await api.get('/bills', { params }) as unknown[];
setBills(res);
} catch (e) {
console.error(e);
} catch (e: any) {
message.error(e?.message || '加载失败,请稍后重试');
}
setLoading(false);
}, [filterStatus, filterExpenseType]);
@@ -110,8 +112,9 @@ const BillsPage: React.FC = () => {
try {
const res = await api.get(`/bills/${id}`);
setDetailModal(res);
} catch (e) {
console.error(e);
} catch (e: any) {
message.error(e?.message || '加载详情失败');
}
} finally {
setDetailLoading(false);
}
@@ -132,6 +135,7 @@ const BillsPage: React.FC = () => {
const batchUpdateStatus = async (status: string) => {
if (selectedRows.length === 0) return message.warning('请先选择账单');
setBatchLoading(true);
try {
await api.put('/bills/batch/status', { ids: selectedRows, status });
message.success(`已批量更新 ${selectedRows.length} 条账单`);
@@ -139,6 +143,8 @@ const BillsPage: React.FC = () => {
fetchData();
} catch (e: any) {
message.error(e?.message || '操作失败');
} finally {
setBatchLoading(false);
}
};
@@ -151,9 +157,9 @@ const BillsPage: React.FC = () => {
message.error(e?.message || '删除失败');
}
};
const batchDelete = async () => {
if (selectedRows.length === 0) return message.warning('请先选择账单');
setBatchLoading(true);
try {
await api.post('/bills/batch/delete', { ids: selectedRows });
message.success(`已删除 ${selectedRows.length} 条账单`);
@@ -161,6 +167,8 @@ const BillsPage: React.FC = () => {
fetchData();
} catch (e: any) {
message.error(e?.message || '操作失败');
} finally {
setBatchLoading(false);
}
};
@@ -334,6 +342,7 @@ const BillsPage: React.FC = () => {
permission="bill:confirm"
onClick={() => batchUpdateStatus('confirmed')}
disabled={selectedRows.length === 0}
loading={batchLoading}
>
</PermissionButton>
@@ -342,6 +351,7 @@ const BillsPage: React.FC = () => {
type="primary"
onClick={() => batchUpdateStatus('paid')}
disabled={selectedRows.length === 0}
loading={batchLoading}
>
</PermissionButton>
@@ -357,6 +367,7 @@ const BillsPage: React.FC = () => {
danger
disabled={selectedRows.length === 0}
icon={<DeleteOutlined />}
loading={batchLoading}
>
</PermissionButton>
@@ -389,6 +400,7 @@ const BillsPage: React.FC = () => {
dataSource={filteredBills}
rowKey="id"
loading={loading}
locale={{ emptyText: <Empty description="暂无数据" /> }}
pagination={{ pageSize: 15, showTotal: (total) => `${total}` }}
rowSelection={{
selectedRowKeys: selectedRows,