Files
gongxue-base/apps/admin/src/pages/Occupancies/OccupanciesToolbar.tsx
wangziqi 00e2bc5acf fix(admin): 通知分页、登录过期提示、附件打开反馈与导出统一 loading
- 通知中心改为游标分页 + 加载更多,历史通知不再被 50 条上限截断;
  加载失败显示错误态与重试
- AI 流式请求 401 被登出时,登录页提示"登录已过期",不再无声踢出
- AI 附件/引用来源打开失败时给出明确错误提示,不再"点了没反应"
- 新增 useDownload hook:统一导出/下载的防重复、loading 与成功/失败反馈;
  接入学生/账单/房间/入住/费用五个页面的模板下载与导出按钮
- 学生页移除不检查响应状态的私有下载实现,统一走 downloadBlob
2026-08-07 17:33:58 +08:00

157 lines
4.8 KiB
TypeScript

import React from 'react';
import { Button, DatePicker, Input, InputNumber, Space, Switch, Tooltip, Upload } from 'antd';
import {
DownloadOutlined,
ExportOutlined,
PlusOutlined,
UploadOutlined,
} from '@ant-design/icons';
import type { Dayjs } from 'dayjs';
import PermissionButton from '../../components/PermissionButton';
import type { OccupancyView } from '../archive-view';
const { RangePicker } = DatePicker;
export const OccupanciesToolbar: React.FC<{
viewMode: OccupancyView;
onChangeViewMode: (mode: OccupancyView) => void;
onSearch: (value: string) => void;
dateRange: [Dayjs | null, Dayjs | null] | null;
onChangeDateRange: (dates: [Dayjs | null, Dayjs | null] | null) => void;
canCheckIn: boolean;
onCheckIn: () => void;
onImport: (options: any) => void;
autoDeposit: boolean;
onAutoDepositChange: (value: boolean) => void;
depositAmount: number;
onDepositAmountChange: (value: number) => void;
onDownloadTemplate: () => void;
onExport: () => void;
templateLoading?: boolean;
exportLoading?: boolean;
}> = ({
viewMode,
onChangeViewMode,
onSearch,
dateRange,
onChangeDateRange,
canCheckIn,
onCheckIn,
onImport,
autoDeposit,
onAutoDepositChange,
depositAmount,
onDepositAmountChange,
onDownloadTemplate,
onExport,
templateLoading,
exportLoading,
}) => {
return (
<div className="responsive-toolbar">
<Space wrap className="responsive-toolbar__group">
<Button
type={viewMode === 'active' ? 'primary' : 'default'}
onClick={() => onChangeViewMode('active')}
>
</Button>
<Button
type={viewMode === 'all' ? 'primary' : 'default'}
onClick={() => onChangeViewMode('all')}
>
</Button>
<Button
type={viewMode === 'archived' ? 'primary' : 'default'}
onClick={() => onChangeViewMode('archived')}
>
</Button>
<Input.Search
placeholder="搜索学生姓名或房间号"
onSearch={onSearch}
allowClear
style={{ width: 200 }}
/>
<RangePicker
value={dateRange}
onChange={(dates) => onChangeDateRange(dates ? [dates[0], dates[1]] : null)}
placeholder={['入住开始', '入住结束']}
style={{ width: 240 }}
/>
</Space>
<Space wrap className="responsive-toolbar__group">
{viewMode !== 'archived' ? (
<PermissionButton
permission="occupancy:checkin"
type="primary"
icon={<PlusOutlined />}
onClick={onCheckIn}
>
</PermissionButton>
) : null}
{viewMode !== 'archived' && canCheckIn ? (
<>
<Upload accept=".xlsx,.xls" showUploadList={false} customRequest={onImport}>
<Tooltip title="按手机号关联学生,并自动创建缺失的学生、宿舍和入住记录">
<Button type="primary" ghost icon={<UploadOutlined />}>
</Button>
</Tooltip>
</Upload>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 13 }}>
<Switch size="small" checked={autoDeposit} onChange={onAutoDepositChange} />
{autoDeposit && (
<Space.Compact>
<InputNumber
size="small"
min={0}
value={depositAmount}
onChange={(v) => onDepositAmountChange(v || 500)}
style={{ width: 60 }}
/>
<span
style={{
padding: '0 8px',
display: 'flex',
alignItems: 'center',
border: '1px solid #d9d9d9',
backgroundColor: '#fafafa',
fontSize: 12,
}}
>
</span>
</Space.Compact>
)}
</span>
</>
) : null}
{viewMode !== 'archived' ? (
<PermissionButton
permission="occupancy:view"
icon={<DownloadOutlined />}
loading={templateLoading}
onClick={onDownloadTemplate}
>
</PermissionButton>
) : null}
{viewMode !== 'archived' ? (
<PermissionButton
permission="occupancy:view"
icon={<ExportOutlined />}
loading={exportLoading}
onClick={onExport}
>
</PermissionButton>
) : null}
</Space>
</div>
);
};