- 通知中心改为游标分页 + 加载更多,历史通知不再被 50 条上限截断; 加载失败显示错误态与重试 - AI 流式请求 401 被登出时,登录页提示"登录已过期",不再无声踢出 - AI 附件/引用来源打开失败时给出明确错误提示,不再"点了没反应" - 新增 useDownload hook:统一导出/下载的防重复、loading 与成功/失败反馈; 接入学生/账单/房间/入住/费用五个页面的模板下载与导出按钮 - 学生页移除不检查响应状态的私有下载实现,统一走 downloadBlob
157 lines
4.8 KiB
TypeScript
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>
|
|
);
|
|
};
|