chore: commit oxfmt formatting changes and verify artifacts
This commit is contained in:
@@ -1,6 +1,31 @@
|
||||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import { Table, Button, Modal, Form, Select, DatePicker, Input, InputNumber, Space, message, Tag, Popconfirm, Upload, Switch, Alert, Tooltip } from 'antd';
|
||||
import { PlusOutlined, SwapOutlined, LogoutOutlined, DeleteOutlined, UploadOutlined, DownloadOutlined, ExportOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Table,
|
||||
Button,
|
||||
Modal,
|
||||
Form,
|
||||
Select,
|
||||
DatePicker,
|
||||
Input,
|
||||
InputNumber,
|
||||
Space,
|
||||
message,
|
||||
Tag,
|
||||
Popconfirm,
|
||||
Upload,
|
||||
Switch,
|
||||
Alert,
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
import {
|
||||
PlusOutlined,
|
||||
SwapOutlined,
|
||||
LogoutOutlined,
|
||||
DeleteOutlined,
|
||||
UploadOutlined,
|
||||
DownloadOutlined,
|
||||
ExportOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import api from '../../api';
|
||||
import PermissionButton from '../../components/PermissionButton';
|
||||
@@ -35,18 +60,24 @@ const OccupanciesPage: React.FC = () => {
|
||||
setData(occ);
|
||||
setStudents(stu);
|
||||
setRooms(rm);
|
||||
} catch (e) { console.error(e); }
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => { fetchData(); setSelectedRowKeys([]); }, [showActive]);
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
setSelectedRowKeys([]);
|
||||
}, [showActive]);
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
if (!searchText) return data;
|
||||
const keyword = searchText.toLowerCase();
|
||||
return data.filter((r: any) =>
|
||||
r.student?.name?.toLowerCase().includes(keyword) ||
|
||||
r.room?.roomNumber?.toLowerCase().includes(keyword)
|
||||
return data.filter(
|
||||
(r: any) =>
|
||||
r.student?.name?.toLowerCase().includes(keyword) ||
|
||||
r.room?.roomNumber?.toLowerCase().includes(keyword),
|
||||
);
|
||||
}, [data, searchText]);
|
||||
|
||||
@@ -64,7 +95,9 @@ const OccupanciesPage: React.FC = () => {
|
||||
setCheckInModal(false);
|
||||
checkInForm.resetFields();
|
||||
fetchData();
|
||||
} catch (e: any) { message.error(e?.message || '操作失败'); }
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '操作失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleCheckOut = async () => {
|
||||
@@ -79,7 +112,9 @@ const OccupanciesPage: React.FC = () => {
|
||||
setCheckOutModal(null);
|
||||
checkOutForm.resetFields();
|
||||
fetchData();
|
||||
} catch (e: any) { message.error(e?.message || '操作失败'); }
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '操作失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleTransfer = async () => {
|
||||
@@ -96,7 +131,9 @@ const OccupanciesPage: React.FC = () => {
|
||||
setTransferModal(null);
|
||||
transferForm.resetFields();
|
||||
fetchData();
|
||||
} catch (e: any) { message.error(e?.message || '操作失败'); }
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '操作失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleBatchCheckOut = async () => {
|
||||
@@ -113,7 +150,9 @@ const OccupanciesPage: React.FC = () => {
|
||||
batchCheckOutForm.resetFields();
|
||||
setSelectedRowKeys([]);
|
||||
fetchData();
|
||||
} catch (e: any) { message.error(e?.message || '批量退宿失败'); }
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '批量退宿失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleBatchDelete = async () => {
|
||||
@@ -122,7 +161,9 @@ const OccupanciesPage: React.FC = () => {
|
||||
message.success(res?.message || `已删除 ${selectedRowKeys.length} 条`);
|
||||
setSelectedRowKeys([]);
|
||||
fetchData();
|
||||
} catch (e: any) { message.error(e?.message || '批量删除失败'); }
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '批量删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
@@ -130,26 +171,63 @@ const OccupanciesPage: React.FC = () => {
|
||||
{ title: '宿舍', render: (_: any, r: any) => r.room?.roomNumber || '-' },
|
||||
{ title: '入住日期', dataIndex: 'checkInDate' },
|
||||
{ title: '计费起始', dataIndex: 'billingStartDate' },
|
||||
{ title: '退宿日期', dataIndex: 'checkOutDate', render: (v: any) => v || <Tag color="green">在住</Tag> },
|
||||
{
|
||||
title: '退宿日期',
|
||||
dataIndex: 'checkOutDate',
|
||||
render: (v: any) => v || <Tag color="green">在住</Tag>,
|
||||
},
|
||||
{ title: '计费截止', dataIndex: 'billingEndDate', render: (v: any) => v || '-' },
|
||||
{ title: '退宿原因', dataIndex: 'checkOutReason', render: (v: any) => v || '-' },
|
||||
{
|
||||
title: '操作', width: 200,
|
||||
render: (_: any, record: any) => !record.checkOutDate ? (
|
||||
<Space>
|
||||
<PermissionButton permission="occupancy:checkout" size="small" icon={<LogoutOutlined />} onClick={() => { setCheckOutModal(record); checkOutForm.setFieldsValue({ checkOutDate: dayjs() }); }}>退宿</PermissionButton>
|
||||
<PermissionButton permission="occupancy:transfer" size="small" icon={<SwapOutlined />} onClick={() => { setTransferModal(record); transferForm.setFieldsValue({ transferDate: dayjs() }); }}>换房</PermissionButton>
|
||||
</Space>
|
||||
) : (
|
||||
<Space>
|
||||
<Tag>已退宿</Tag>
|
||||
<PermissionButton permission="occupancy:delete">
|
||||
<Popconfirm title="确定删除此记录?" onConfirm={async () => { try { await api.delete(`/occupancies/${record.id}`); message.success('删除成功'); fetchData(); } catch (e: any) { message.error(e?.message || '删除失败'); } }}>
|
||||
<Button size="small" danger icon={<DeleteOutlined />} />
|
||||
</Popconfirm>
|
||||
</PermissionButton>
|
||||
</Space>
|
||||
),
|
||||
title: '操作',
|
||||
width: 200,
|
||||
render: (_: any, record: any) =>
|
||||
!record.checkOutDate ? (
|
||||
<Space>
|
||||
<PermissionButton
|
||||
permission="occupancy:checkout"
|
||||
size="small"
|
||||
icon={<LogoutOutlined />}
|
||||
onClick={() => {
|
||||
setCheckOutModal(record);
|
||||
checkOutForm.setFieldsValue({ checkOutDate: dayjs() });
|
||||
}}
|
||||
>
|
||||
退宿
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
permission="occupancy:transfer"
|
||||
size="small"
|
||||
icon={<SwapOutlined />}
|
||||
onClick={() => {
|
||||
setTransferModal(record);
|
||||
transferForm.setFieldsValue({ transferDate: dayjs() });
|
||||
}}
|
||||
>
|
||||
换房
|
||||
</PermissionButton>
|
||||
</Space>
|
||||
) : (
|
||||
<Space>
|
||||
<Tag>已退宿</Tag>
|
||||
<PermissionButton permission="occupancy:delete">
|
||||
<Popconfirm
|
||||
title="确定删除此记录?"
|
||||
onConfirm={async () => {
|
||||
try {
|
||||
await api.delete(`/occupancies/${record.id}`);
|
||||
message.success('删除成功');
|
||||
fetchData();
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '删除失败');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button size="small" danger icon={<DeleteOutlined />} />
|
||||
</Popconfirm>
|
||||
</PermissionButton>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -157,7 +235,7 @@ const OccupanciesPage: React.FC = () => {
|
||||
selectedRowKeys,
|
||||
onChange: (keys: any[]) => setSelectedRowKeys(keys),
|
||||
// 「在住记录」Tab:禁用已退宿(防止误选用于批量退宿);「全部记录」Tab:均可选用于批量删除
|
||||
getCheckboxProps: (record: any) => showActive ? { disabled: !!record.checkOutDate } : {},
|
||||
getCheckboxProps: (record: any) => (showActive ? { disabled: !!record.checkOutDate } : {}),
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -170,14 +248,40 @@ const OccupanciesPage: React.FC = () => {
|
||||
closable
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', gap: 8 }}>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 16,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
flexWrap: 'wrap',
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<Space wrap>
|
||||
<Button type={showActive ? 'primary' : 'default'} onClick={() => setShowActive(true)}>在住记录</Button>
|
||||
<Button type={!showActive ? 'primary' : 'default'} onClick={() => setShowActive(false)}>全部记录</Button>
|
||||
<Input.Search placeholder="搜索学生姓名或房间号" onSearch={setSearchText} allowClear style={{ width: 200 }} />
|
||||
<Button type={showActive ? 'primary' : 'default'} onClick={() => setShowActive(true)}>
|
||||
在住记录
|
||||
</Button>
|
||||
<Button type={!showActive ? 'primary' : 'default'} onClick={() => setShowActive(false)}>
|
||||
全部记录
|
||||
</Button>
|
||||
<Input.Search
|
||||
placeholder="搜索学生姓名或房间号"
|
||||
onSearch={setSearchText}
|
||||
allowClear
|
||||
style={{ width: 200 }}
|
||||
/>
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<PermissionButton permission="occupancy:checkin" type="primary" icon={<PlusOutlined />} onClick={() => { checkInForm.resetFields(); checkInForm.setFieldsValue({ checkInDate: dayjs() }); setCheckInModal(true); }}>
|
||||
<PermissionButton
|
||||
permission="occupancy:checkin"
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
checkInForm.resetFields();
|
||||
checkInForm.setFieldsValue({ checkInDate: dayjs() });
|
||||
setCheckInModal(true);
|
||||
}}
|
||||
>
|
||||
入住登记
|
||||
</PermissionButton>
|
||||
<PermissionButton permission="occupancy:checkin">
|
||||
@@ -193,57 +297,99 @@ const OccupanciesPage: React.FC = () => {
|
||||
params.set('depositAmount', String(depositAmount));
|
||||
}
|
||||
try {
|
||||
const res: any = await api.post(`/occupancies/import?${params.toString()}`, formData, { headers: { 'Content-Type': 'multipart/form-data' } });
|
||||
const res: any = await api.post(
|
||||
`/occupancies/import?${params.toString()}`,
|
||||
formData,
|
||||
{ headers: { 'Content-Type': 'multipart/form-data' } },
|
||||
);
|
||||
if (res.errors?.length > 0) {
|
||||
Modal.warning({ title: res.message, content: res.errors.join('\n'), width: 500 });
|
||||
Modal.warning({
|
||||
title: res.message,
|
||||
content: res.errors.join('\n'),
|
||||
width: 500,
|
||||
});
|
||||
} else {
|
||||
message.success(res.message);
|
||||
}
|
||||
onSuccess?.(res);
|
||||
fetchData();
|
||||
} catch (e: any) { message.error(e?.message || '导入失败'); onError?.(e); }
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '导入失败');
|
||||
onError?.(e);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Tooltip title="导入时自动创建学生、宿舍和入住记录">
|
||||
<Button type="primary" ghost icon={<UploadOutlined />}>导入入住名单</Button>
|
||||
<Button type="primary" ghost icon={<UploadOutlined />}>
|
||||
导入入住名单
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Upload>
|
||||
</PermissionButton>
|
||||
<PermissionButton permission="occupancy:view" icon={<DownloadOutlined />} onClick={() => {
|
||||
const baseURL = import.meta.env.PROD ? '/api' : `http://localhost:${import.meta.env.VITE_API_PORT || 3002}/api`;
|
||||
const token = localStorage.getItem('token');
|
||||
fetch(`${baseURL}/occupancies/template`, { headers: { Authorization: `Bearer ${token}` } })
|
||||
.then(res => res.blob())
|
||||
.then(blob => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = '入住名单导入模板.xlsx';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
<PermissionButton
|
||||
permission="occupancy:view"
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={() => {
|
||||
const baseURL = import.meta.env.PROD
|
||||
? '/api'
|
||||
: `http://localhost:${import.meta.env.VITE_API_PORT || 3002}/api`;
|
||||
const token = localStorage.getItem('token');
|
||||
fetch(`${baseURL}/occupancies/template`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
.catch(() => message.error('下载失败'));
|
||||
}}>下载模板</PermissionButton>
|
||||
<PermissionButton permission="occupancy:view" icon={<ExportOutlined />} onClick={() => {
|
||||
const baseURL = import.meta.env.PROD ? '/api' : `http://localhost:${import.meta.env.VITE_API_PORT || 3002}/api`;
|
||||
const token = localStorage.getItem('token');
|
||||
const params = showActive ? '?active=true' : '';
|
||||
fetch(`${baseURL}/occupancies/export${params}`, { headers: { Authorization: `Bearer ${token}` } })
|
||||
.then(res => res.blob())
|
||||
.then(blob => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = showActive ? '在住记录.xlsx' : '全部入住记录.xlsx';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
.then((res) => res.blob())
|
||||
.then((blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = '入住名单导入模板.xlsx';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
})
|
||||
.catch(() => message.error('下载失败'));
|
||||
}}
|
||||
>
|
||||
下载模板
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
permission="occupancy:view"
|
||||
icon={<ExportOutlined />}
|
||||
onClick={() => {
|
||||
const baseURL = import.meta.env.PROD
|
||||
? '/api'
|
||||
: `http://localhost:${import.meta.env.VITE_API_PORT || 3002}/api`;
|
||||
const token = localStorage.getItem('token');
|
||||
const params = showActive ? '?active=true' : '';
|
||||
fetch(`${baseURL}/occupancies/export${params}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
.catch(() => message.error('导出失败'));
|
||||
}}>导出记录</PermissionButton>
|
||||
.then((res) => res.blob())
|
||||
.then((blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = showActive ? '在住记录.xlsx' : '全部入住记录.xlsx';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
})
|
||||
.catch(() => message.error('导出失败'));
|
||||
}}
|
||||
>
|
||||
导出记录
|
||||
</PermissionButton>
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 13 }}>
|
||||
<Switch size="small" checked={autoDeposit} onChange={setAutoDeposit} />
|
||||
导入时自动收押金
|
||||
{autoDeposit && <InputNumber size="small" min={0} value={depositAmount} onChange={(v) => setDepositAmount(v || 500)} style={{ width: 80 }} addonAfter="元" />}
|
||||
{autoDeposit && (
|
||||
<InputNumber
|
||||
size="small"
|
||||
min={0}
|
||||
value={depositAmount}
|
||||
onChange={(v) => setDepositAmount(v || 500)}
|
||||
style={{ width: 80 }}
|
||||
addonAfter="元"
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</Space>
|
||||
</div>
|
||||
@@ -253,15 +399,42 @@ const OccupanciesPage: React.FC = () => {
|
||||
<span>
|
||||
已选 <strong>{selectedRowKeys.length}</strong> 条记录
|
||||
{showActive ? (
|
||||
<PermissionButton permission="occupancy:checkout" type="primary" size="small" icon={<LogoutOutlined />} onClick={() => { batchCheckOutForm.resetFields(); batchCheckOutForm.setFieldsValue({ checkOutDate: dayjs() }); setBatchCheckOutModal(true); }} style={{ marginLeft: 12 }}>批量退宿</PermissionButton>
|
||||
<PermissionButton
|
||||
permission="occupancy:checkout"
|
||||
type="primary"
|
||||
size="small"
|
||||
icon={<LogoutOutlined />}
|
||||
onClick={() => {
|
||||
batchCheckOutForm.resetFields();
|
||||
batchCheckOutForm.setFieldsValue({ checkOutDate: dayjs() });
|
||||
setBatchCheckOutModal(true);
|
||||
}}
|
||||
style={{ marginLeft: 12 }}
|
||||
>
|
||||
批量退宿
|
||||
</PermissionButton>
|
||||
) : (
|
||||
<PermissionButton permission="occupancy:delete">
|
||||
<Popconfirm title={`确定删除选中的 ${selectedRowKeys.length} 条入住记录?在住记录会自动跳过`} onConfirm={handleBatchDelete} okText="删除" cancelText="取消">
|
||||
<Button danger size="small" icon={<DeleteOutlined />} style={{ marginLeft: 12 }}>批量删除</Button>
|
||||
<Popconfirm
|
||||
title={`确定删除选中的 ${selectedRowKeys.length} 条入住记录?在住记录会自动跳过`}
|
||||
onConfirm={handleBatchDelete}
|
||||
okText="删除"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Button
|
||||
danger
|
||||
size="small"
|
||||
icon={<DeleteOutlined />}
|
||||
style={{ marginLeft: 12 }}
|
||||
>
|
||||
批量删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</PermissionButton>
|
||||
)}
|
||||
<Button size="small" onClick={() => setSelectedRowKeys([])} style={{ marginLeft: 8 }}>取消选择</Button>
|
||||
<Button size="small" onClick={() => setSelectedRowKeys([])} style={{ marginLeft: 8 }}>
|
||||
取消选择
|
||||
</Button>
|
||||
</span>
|
||||
}
|
||||
type="info"
|
||||
@@ -278,21 +451,61 @@ const OccupanciesPage: React.FC = () => {
|
||||
/>
|
||||
|
||||
{/* 入住登记弹窗 */}
|
||||
<Modal title="入住登记" open={checkInModal} onOk={handleCheckIn} onCancel={() => setCheckInModal(false)} okText="确认入住" width={500}>
|
||||
<Modal
|
||||
title="入住登记"
|
||||
open={checkInModal}
|
||||
onOk={handleCheckIn}
|
||||
onCancel={() => setCheckInModal(false)}
|
||||
okText="确认入住"
|
||||
width={500}
|
||||
>
|
||||
<Form form={checkInForm} layout="vertical">
|
||||
<Form.Item name="studentId" label="选择学生" rules={[{ required: true, message: '请选择学生' }]}>
|
||||
<Select showSearch optionFilterProp="label" placeholder="搜索并选择学生"
|
||||
options={students.filter((s: any) => s.status === 'active').map((s: any) => ({ value: s.id, label: `${s.name} (${s.idNumber || s.phone || ''})` }))} />
|
||||
<Form.Item
|
||||
name="studentId"
|
||||
label="选择学生"
|
||||
rules={[{ required: true, message: '请选择学生' }]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder="搜索并选择学生"
|
||||
options={students
|
||||
.filter((s: any) => s.status === 'active')
|
||||
.map((s: any) => ({
|
||||
value: s.id,
|
||||
label: `${s.name} (${s.idNumber || s.phone || ''})`,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="roomId" label="选择宿舍" rules={[{ required: true, message: '请选择宿舍' }]}>
|
||||
<Select showSearch optionFilterProp="label" placeholder="搜索并选择宿舍"
|
||||
options={rooms.map((r: any) => ({ value: r.id, label: `${r.roomNumber} (${r.building || ''}) [${r.currentCount}/${r.capacity}]`, disabled: r.currentCount >= r.capacity }))} />
|
||||
<Form.Item
|
||||
name="roomId"
|
||||
label="选择宿舍"
|
||||
rules={[{ required: true, message: '请选择宿舍' }]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder="搜索并选择宿舍"
|
||||
options={rooms.map((r: any) => ({
|
||||
value: r.id,
|
||||
label: `${r.roomNumber} (${r.building || ''}) [${r.currentCount}/${r.capacity}]`,
|
||||
disabled: r.currentCount >= r.capacity,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="checkInDate" label="入住日期" rules={[{ required: true }]}>
|
||||
<DatePicker style={{ width: '100%' }} placeholder="选择入住日期" format="YYYY-MM-DD" />
|
||||
</Form.Item>
|
||||
<Form.Item name="billingStartDate" label="计费起始日" extra="默认与入住日期相同,可调整(如学生要求从次日开始计费)">
|
||||
<DatePicker style={{ width: '100%' }} placeholder="选择计费起始日" format="YYYY-MM-DD" />
|
||||
<Form.Item
|
||||
name="billingStartDate"
|
||||
label="计费起始日"
|
||||
extra="默认与入住日期相同,可调整(如学生要求从次日开始计费)"
|
||||
>
|
||||
<DatePicker
|
||||
style={{ width: '100%' }}
|
||||
placeholder="选择计费起始日"
|
||||
format="YYYY-MM-DD"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="notes" label="备注">
|
||||
<Input.TextArea rows={2} />
|
||||
@@ -301,67 +514,132 @@ const OccupanciesPage: React.FC = () => {
|
||||
</Modal>
|
||||
|
||||
{/* 退宿弹窗 */}
|
||||
<Modal title={`退宿 - ${checkOutModal?.student?.name}`} open={!!checkOutModal} onOk={handleCheckOut} onCancel={() => setCheckOutModal(null)} okText="确认退宿">
|
||||
<Modal
|
||||
title={`退宿 - ${checkOutModal?.student?.name}`}
|
||||
open={!!checkOutModal}
|
||||
onOk={handleCheckOut}
|
||||
onCancel={() => setCheckOutModal(null)}
|
||||
okText="确认退宿"
|
||||
>
|
||||
<Form form={checkOutForm} layout="vertical">
|
||||
<Form.Item name="checkOutDate" label="退宿日期" rules={[{ required: true }]}>
|
||||
<DatePicker style={{ width: '100%' }} placeholder="选择退宿日期" format="YYYY-MM-DD" />
|
||||
</Form.Item>
|
||||
<Form.Item name="billingEndDate" label="计费截止日" extra="默认与退宿日期相同">
|
||||
<DatePicker style={{ width: '100%' }} placeholder="选择计费截止日" format="YYYY-MM-DD" />
|
||||
<DatePicker
|
||||
style={{ width: '100%' }}
|
||||
placeholder="选择计费截止日"
|
||||
format="YYYY-MM-DD"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="checkOutReason" label="退宿原因">
|
||||
<Select allowClear options={[
|
||||
{ value: '换房', label: '换房' },
|
||||
{ value: '退训', label: '退训' },
|
||||
{ value: '结业', label: '结业' },
|
||||
{ value: '毕业', label: '毕业' },
|
||||
{ value: '其他', label: '其他' },
|
||||
]} />
|
||||
<Select
|
||||
allowClear
|
||||
options={[
|
||||
{ value: '换房', label: '换房' },
|
||||
{ value: '退训', label: '退训' },
|
||||
{ value: '结业', label: '结业' },
|
||||
{ value: '毕业', label: '毕业' },
|
||||
{ value: '其他', label: '其他' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 批量退宿弹窗 */}
|
||||
<Modal title={`批量退宿(${selectedRowKeys.length} 人)`} open={batchCheckOutModal} onOk={handleBatchCheckOut} onCancel={() => setBatchCheckOutModal(false)} okText="确认批量退宿" width={500}>
|
||||
<Modal
|
||||
title={`批量退宿(${selectedRowKeys.length} 人)`}
|
||||
open={batchCheckOutModal}
|
||||
onOk={handleBatchCheckOut}
|
||||
onCancel={() => setBatchCheckOutModal(false)}
|
||||
okText="确认批量退宿"
|
||||
width={500}
|
||||
>
|
||||
<Form form={batchCheckOutForm} layout="vertical">
|
||||
<Form.Item name="checkOutDate" label="退宿日期" rules={[{ required: true }]}>
|
||||
<DatePicker style={{ width: '100%' }} placeholder="选择退宿日期" format="YYYY-MM-DD" />
|
||||
</Form.Item>
|
||||
<Form.Item name="billingEndDate" label="计费截止日" extra="默认与退宿日期相同">
|
||||
<DatePicker style={{ width: '100%' }} placeholder="选择计费截止日" format="YYYY-MM-DD" />
|
||||
<DatePicker
|
||||
style={{ width: '100%' }}
|
||||
placeholder="选择计费截止日"
|
||||
format="YYYY-MM-DD"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="checkOutReason" label="退宿原因">
|
||||
<Select allowClear options={[
|
||||
{ value: '结业', label: '结业' },
|
||||
{ value: '退训', label: '退训' },
|
||||
{ value: '毕业', label: '毕业' },
|
||||
{ value: '其他', label: '其他' },
|
||||
]} />
|
||||
<Select
|
||||
allowClear
|
||||
options={[
|
||||
{ value: '结业', label: '结业' },
|
||||
{ value: '退训', label: '退训' },
|
||||
{ value: '毕业', label: '毕业' },
|
||||
{ value: '其他', label: '其他' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<div style={{ marginTop: 12, padding: '8px 12px', background: '#f5f5f5', borderRadius: 6, maxHeight: 150, overflow: 'auto' }}>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 12,
|
||||
padding: '8px 12px',
|
||||
background: '#f5f5f5',
|
||||
borderRadius: 6,
|
||||
maxHeight: 150,
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: 12, color: '#666', marginBottom: 4 }}>即将退宿的学生:</div>
|
||||
{data.filter((r: any) => selectedRowKeys.includes(r.id)).map((r: any) => (
|
||||
<Tag key={r.id} style={{ marginBottom: 4 }}>{r.student?.name} ({r.room?.roomNumber})</Tag>
|
||||
))}
|
||||
{data
|
||||
.filter((r: any) => selectedRowKeys.includes(r.id))
|
||||
.map((r: any) => (
|
||||
<Tag key={r.id} style={{ marginBottom: 4 }}>
|
||||
{r.student?.name} ({r.room?.roomNumber})
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
{/* 换房弹窗 */}
|
||||
<Modal title={`换房 - ${transferModal?.student?.name}`} open={!!transferModal} onOk={handleTransfer} onCancel={() => setTransferModal(null)} okText="确认换房" width={500}>
|
||||
<Modal
|
||||
title={`换房 - ${transferModal?.student?.name}`}
|
||||
open={!!transferModal}
|
||||
onOk={handleTransfer}
|
||||
onCancel={() => setTransferModal(null)}
|
||||
okText="确认换房"
|
||||
width={500}
|
||||
>
|
||||
<Form form={transferForm} layout="vertical">
|
||||
<Form.Item name="newRoomId" label="目标宿舍" rules={[{ required: true }]}>
|
||||
<Select showSearch optionFilterProp="label" placeholder="选择目标宿舍"
|
||||
options={rooms.filter((r: any) => r.id !== transferModal?.roomId).map((r: any) => ({ value: r.id, label: `${r.roomNumber} (${r.building || ''}) [${r.currentCount}/${r.capacity}]`, disabled: r.currentCount >= r.capacity }))} />
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder="选择目标宿舍"
|
||||
options={rooms
|
||||
.filter((r: any) => r.id !== transferModal?.roomId)
|
||||
.map((r: any) => ({
|
||||
value: r.id,
|
||||
label: `${r.roomNumber} (${r.building || ''}) [${r.currentCount}/${r.capacity}]`,
|
||||
disabled: r.currentCount >= r.capacity,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="transferDate" label="换房日期" rules={[{ required: true }]}>
|
||||
<DatePicker style={{ width: '100%' }} placeholder="选择换房日期" format="YYYY-MM-DD" />
|
||||
</Form.Item>
|
||||
<Form.Item name="oldBillingEndDate" label="旧房计费截止日" extra="默认为换房当天">
|
||||
<DatePicker style={{ width: '100%' }} placeholder="选择旧房计费截止日" format="YYYY-MM-DD" />
|
||||
<DatePicker
|
||||
style={{ width: '100%' }}
|
||||
placeholder="选择旧房计费截止日"
|
||||
format="YYYY-MM-DD"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="newBillingStartDate" label="新房计费起始日" extra="默认为换房次日">
|
||||
<DatePicker style={{ width: '100%' }} placeholder="选择新房计费起始日" format="YYYY-MM-DD" />
|
||||
<DatePicker
|
||||
style={{ width: '100%' }}
|
||||
placeholder="选择新房计费起始日"
|
||||
format="YYYY-MM-DD"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="reason" label="换房原因">
|
||||
<Input />
|
||||
|
||||
Reference in New Issue
Block a user