Merge pull request 'feat: 优化入住办理与名单导入' (#9) from codex/wzq into main

This commit is contained in:
2026-07-14 04:26:53 +00:00
11 changed files with 322 additions and 78 deletions

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState, useMemo, useCallback } from 'react';
import {
Card, Form, Input, Button, Space, Spin, Alert, Descriptions, Tag, Divider,
Drawer, Tree, Select, TreeSelect, Modal, DatePicker, InputNumber,
Drawer, Tree, Select, TreeSelect, Modal, DatePicker,
Row, Col, List,
} from 'antd';
import {
@@ -59,7 +59,6 @@ interface ClassItem {
classType?: string;
startDate?: string;
endDate?: string;
maxStudents?: number;
notes?: string;
}
@@ -477,9 +476,6 @@ const IntegrationConfigPage: React.FC = () => {
<Form.Item name="endDate" label="结束日期">
<DatePicker style={{ width: '100%' }} />
</Form.Item>
<Form.Item name="maxStudents" label="最大人数">
<InputNumber min={0} style={{ width: '100%' }} placeholder="0=不限制" />
</Form.Item>
<Form.Item name="notes" label="备注">
<Input.TextArea rows={2} />
</Form.Item>

View File

@@ -40,7 +40,6 @@ const OccupanciesPage: React.FC = () => {
const [data, setData] = useState<any[]>([]);
const [students, setStudents] = useState<any[]>([]);
const [rooms, setRooms] = useState<any[]>([]);
const [organizations, setOrganizations] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [checkInModal, setCheckInModal] = useState(false);
const [checkOutModal, setCheckOutModal] = useState<any>(null);
@@ -66,14 +65,13 @@ const OccupanciesPage: React.FC = () => {
const fetchData = useCallback(async () => {
setLoading(true);
try {
const [occRes, stuRes, rmRes, tnRes] = (await Promise.allSettled([
const [occRes, stuRes, rmRes] = (await Promise.allSettled([
api.get('/occupancies', { params: { active: showActive ? 'true' : undefined, dateFrom: dateRange?.[0]?.format('YYYY-MM-DD'), dateTo: dateRange?.[1]?.format('YYYY-MM-DD') } }),
api.get('/students/basic-lookups'),
api.get('/rooms/overview'),
api.get('/organizations'),
])) as PromiseSettledResult<any>[];
const labels = ['入住数据', '学生列表', '房间列表', '机构列表'];
[occRes, stuRes, rmRes, tnRes].forEach((res, i) => {
const labels = ['入住数据', '学生列表', '房间列表'];
[occRes, stuRes, rmRes].forEach((res, i) => {
if (res.status === 'rejected') {
message.warning(`${labels[i]}加载失败`);
}
@@ -81,7 +79,6 @@ const OccupanciesPage: React.FC = () => {
setData(occRes.status === 'fulfilled' ? occRes.value : []);
setStudents(stuRes.status === 'fulfilled' ? stuRes.value : []);
setRooms(rmRes.status === 'fulfilled' ? rmRes.value : []);
setOrganizations(tnRes.status === 'fulfilled' ? tnRes.value : []);
} catch (e) {
console.error(e);
message.error('数据加载异常');
@@ -157,7 +154,8 @@ const OccupanciesPage: React.FC = () => {
checkInDate: values.checkInDate.format('YYYY-MM-DD'),
billingStartDate: values.billingStartDate?.format('YYYY-MM-DD'),
stayType: values.stayType,
responsibleOrganizationId: values.responsibleOrganizationId,
collectDeposit: values.collectDeposit,
depositAmount: values.collectDeposit ? values.depositAmount : undefined,
notes: values.notes,
bedId: values.bedId,
lockerId: values.lockerId || undefined,
@@ -330,7 +328,7 @@ const OccupanciesPage: React.FC = () => {
<div>
<Alert
title="一站式导入"
description="导入入住名单时会自动创建学生和宿舍,无需单独在「学生管理」或「宿舍管理」中手动添加。后续仅需在此页面处理换房/退宿等日常操作即可。"
description="导入入住名单时会优先按手机号关联已有学生,所属机构自动取学生档案;未找到学生或宿舍时会自动创建。后续仅需在此页面处理换房/退宿等日常操作即可。"
type="info"
showIcon
closable
@@ -367,7 +365,7 @@ const OccupanciesPage: React.FC = () => {
icon={<PlusOutlined />}
onClick={() => {
checkInForm.resetFields();
checkInForm.setFieldsValue({ checkInDate: dayjs() });
checkInForm.setFieldsValue({ checkInDate: dayjs(), collectDeposit: true, depositAmount: 500 });
setCheckInModal(true);
}}
>
@@ -407,7 +405,7 @@ const OccupanciesPage: React.FC = () => {
}
}}
>
<Tooltip title="导入时自动创建学生、宿舍和入住记录">
<Tooltip title="按手机号关联学生,并自动创建缺失的学生、宿舍和入住记录">
<Button type="primary" ghost icon={<UploadOutlined />}>
</Button>
@@ -598,18 +596,6 @@ const OccupanciesPage: React.FC = () => {
placeholder="默认为短租"
/>
</Form.Item>
<Form.Item name="responsibleOrganizationId" label="所属机构">
<Select
showSearch
allowClear
optionFilterProp="label"
placeholder="默认取学生所属机构"
options={organizations.map((t: { id: number; name: string }) => ({
value: t.id,
label: t.name,
}))}
/>
</Form.Item>
<Form.Item
name="bedId"
label="床位"
@@ -630,7 +616,10 @@ const OccupanciesPage: React.FC = () => {
{availableBeds.length}
</div>
)}
<Form.Item name="lockerId" label="柜子(可选)">
<Form.Item
name="lockerId"
label="柜子(可选)"
>
<Select
allowClear
placeholder="可选分配柜子"
@@ -641,6 +630,32 @@ const OccupanciesPage: React.FC = () => {
}))}
/>
</Form.Item>
<Form.Item
name="collectDeposit"
label="押金缴纳"
valuePropName="checked"
extra="开启后,确认入住时同步生成已缴押金记录;已有已缴押金时不会重复创建"
>
<Switch checkedChildren="已缴" unCheckedChildren="不缴" />
</Form.Item>
<Form.Item noStyle shouldUpdate={(prev, current) => prev.collectDeposit !== current.collectDeposit}>
{({ getFieldValue }) =>
getFieldValue('collectDeposit') ? (
<Form.Item
name="depositAmount"
label="押金金额"
rules={[{ required: true, message: '请输入押金金额' }]}
>
<InputNumber
min={0.01}
precision={2}
addonAfter="元"
style={{ width: '100%' }}
/>
</Form.Item>
) : null
}
</Form.Item>
<Form.Item name="notes" label="备注">
<Input.TextArea rows={2} />
</Form.Item>