feat: 重构各业务模块管理页面与服务

This commit is contained in:
2026-08-05 17:12:00 +08:00
parent 80e6fccf05
commit fd39e1686a
163 changed files with 18409 additions and 13449 deletions

View File

@@ -0,0 +1,382 @@
// aislop-ignore-file: duplicate-block -- 床位/柜子表格声明结构相似且字段不同,渲染逻辑已共享 EditableRoomCell
import React from 'react';
import {
Button,
Drawer,
InputNumber,
Popconfirm,
Space,
Table,
Tabs,
Tag,
} from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import PermissionButton from '../../components/PermissionButton';
import {
BED_STATUS_MAP,
BED_STATUS_OPTIONS,
EditableRoomCell,
statusMap,
type BedItem,
type LockerItem,
} from './RoomColumns';
export interface RoomDrawerProps {
open: boolean;
room: any;
beds: BedItem[];
lockers: LockerItem[];
canEditRooms: boolean;
remainingBedSlots: number;
defaultBatchBedCount: number;
onClose: () => void;
onAddBed: () => void;
onBatchBeds: (count: number) => void;
onEditBed: (record: BedItem) => void;
onDeleteBed: (id: number) => void;
onSaveBedCell: (record: BedItem, field: string, value: unknown) => void;
onAddLocker: () => void;
onBatchLockers: (count: number) => void;
onEditLocker: (record: LockerItem) => void;
onDeleteLocker: (id: number) => void;
onSaveLockerCell: (record: LockerItem, field: string, value: unknown) => void;
}
export const RoomDrawer: React.FC<RoomDrawerProps> = ({
open,
room,
beds,
lockers,
canEditRooms,
remainingBedSlots,
defaultBatchBedCount,
onClose,
onAddBed,
onBatchBeds,
onEditBed,
onDeleteBed,
onSaveBedCell,
onAddLocker,
onBatchLockers,
onEditLocker,
onDeleteLocker,
onSaveLockerCell,
}) => {
const roomItemActions = (kind: 'bed' | 'locker') => (r: any) => {
const isBed = kind === 'bed';
const handleDelete = isBed ? onDeleteBed : onDeleteLocker;
const handleEdit = isBed ? onEditBed : onEditLocker;
return (
<Space size="small">
<PermissionButton
permission="room:edit"
size="small"
type="link"
disabled={room?.status === 'archived'}
onClick={() => handleEdit(r)}
>
</PermissionButton>
{r.status !== 'occupied' && canEditRooms && (
<Popconfirm title="确定归档?" onConfirm={() => handleDelete(r.id)}>
<Button size="small" type="link" danger disabled={room?.status === 'archived'}>
</Button>
</Popconfirm>
)}
</Space>
);
};
return (
<Drawer
title={`${room?.roomNumber} 房间详情`}
open={open}
onClose={onClose}
size={640}
destroyOnClose
>
<Tabs
defaultActiveKey="info"
items={[
{
key: 'info',
label: '基本信息',
children:
room && (
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<div>
<strong></strong>
{room.roomNumber}
</div>
<div>
<strong></strong>
{room.building || '-'}
</div>
<div>
<strong></strong>
{room.floor ?? '-'}
</div>
<div>
<strong></strong>
{room.roomType || '-'}
</div>
<div>
<strong></strong>
{room.capacity}
</div>
<div>
<strong></strong>
{room.rentalCategory === 'long' ? '长租' : '短租'}
</div>
<div>
<strong></strong>
{room.monthlyRate ? `¥${room.monthlyRate}` : '-'}
</div>
<div>
<strong></strong>
<Tag color={statusMap[room.status]?.color}>
{statusMap[room.status]?.text}
</Tag>
</div>
</div>
),
},
{
key: 'beds',
label: `床位管理 (${beds.length})`,
children: (
<div>
{canEditRooms ? (
<div style={{ marginBottom: 12, display: 'flex', gap: 8 }}>
<Button
type="primary"
size="small"
icon={<PlusOutlined />}
disabled={room?.status === 'archived' || remainingBedSlots === 0}
onClick={onAddBed}
>
</Button>
<Popconfirm
title={remainingBedSlots > 0 ? '批量生成床位' : '床位已达到额定人数'}
description={
remainingBedSlots > 0 ? (
<InputNumber
min={1}
max={remainingBedSlots}
defaultValue={defaultBatchBedCount}
id="batch-bed-count"
style={{ width: 80 }}
/>
) : (
'如需增加床位,请先调整宿舍额定人数'
)
}
onConfirm={() => {
const input = document.getElementById(
'batch-bed-count',
) as HTMLInputElement;
onBatchBeds(
input
? parseInt(input.value) || defaultBatchBedCount
: defaultBatchBedCount,
);
}}
okText="生成"
disabled={room?.status === 'archived' || remainingBedSlots === 0}
>
<Button
size="small"
disabled={room?.status === 'archived' || remainingBedSlots === 0}
>
</Button>
</Popconfirm>
</div>
) : null}
<Table
dataSource={beds}
rowKey="id"
pagination={false}
size="small"
columns={[
{
title: '编号',
dataIndex: 'bedNumber',
width: 80,
render: (v: string, r: BedItem) => (
<EditableRoomCell
value={v}
field="bedNumber"
record={r}
required
archived={room?.status === 'archived'}
onSave={onSaveBedCell}
>
{v}
</EditableRoomCell>
),
},
{
title: '状态',
dataIndex: 'status',
width: 80,
render: (s: string, r: BedItem) => (
<EditableRoomCell
value={s}
field="status"
record={r}
editor="select"
options={BED_STATUS_OPTIONS}
archived={room?.status === 'archived'}
onSave={onSaveBedCell}
>
<Tag color={BED_STATUS_MAP[s]?.color}>
{BED_STATUS_MAP[s]?.text || s}
</Tag>
</EditableRoomCell>
),
},
{
title: '备注',
dataIndex: 'notes',
render: (v: string, r: BedItem) => (
<EditableRoomCell
value={v}
field="notes"
record={r}
editor="textarea"
archived={room?.status === 'archived'}
onSave={onSaveBedCell}
>
{v || '-'}
</EditableRoomCell>
),
},
{
title: '操作',
width: 120,
render: roomItemActions('bed'),
},
]}
/>
</div>
),
},
{
key: 'lockers',
label: `柜子管理 (${lockers.length})`,
children: (
<div>
{canEditRooms ? (
<div style={{ marginBottom: 12, display: 'flex', gap: 8 }}>
<Button
type="primary"
size="small"
icon={<PlusOutlined />}
disabled={room?.status === 'archived'}
onClick={onAddLocker}
>
</Button>
<Popconfirm
title="批量生成柜子"
description={
<InputNumber
min={1}
max={20}
defaultValue={4}
id="batch-locker-count"
style={{ width: 80 }}
/>
}
onConfirm={() => {
const input = document.getElementById(
'batch-locker-count',
) as HTMLInputElement;
onBatchLockers(input ? parseInt(input.value) || 4 : 4);
}}
okText="生成"
disabled={room?.status === 'archived'}
>
<Button size="small" disabled={room?.status === 'archived'}>
</Button>
</Popconfirm>
</div>
) : null}
<Table
dataSource={lockers}
rowKey="id"
pagination={false}
size="small"
columns={[
{
title: '编号',
dataIndex: 'lockerNumber',
width: 80,
render: (v: string, r: LockerItem) => (
<EditableRoomCell
value={v}
field="lockerNumber"
record={r}
required
archived={room?.status === 'archived'}
onSave={onSaveLockerCell}
>
{v}
</EditableRoomCell>
),
},
{
title: '状态',
dataIndex: 'status',
width: 80,
render: (s: string, r: LockerItem) => (
<EditableRoomCell
value={s}
field="status"
record={r}
editor="select"
options={BED_STATUS_OPTIONS}
archived={room?.status === 'archived'}
onSave={onSaveLockerCell}
>
<Tag color={BED_STATUS_MAP[s]?.color}>
{BED_STATUS_MAP[s]?.text || s}
</Tag>
</EditableRoomCell>
),
},
{
title: '备注',
dataIndex: 'notes',
render: (v: string, r: LockerItem) => (
<EditableRoomCell
value={v}
field="notes"
record={r}
editor="textarea"
archived={room?.status === 'archived'}
onSave={onSaveLockerCell}
>
{v || '-'}
</EditableRoomCell>
),
},
{
title: '操作',
width: 120,
render: roomItemActions('locker'),
},
]}
/>
</div>
),
},
]}
/>
</Drawer>
);
};