feat: refine admin forms, attendance and finance workflows
Squash merge PR #23. Included changes: - complete occupancy check-in required fields/default payload - improve responsive admin management pages - fix attendance edge cases and attendance period config - refine wallet/finance-related workflow handling Checks: - npm run typecheck -w apps/admin - npm run typecheck -w apps/server
This commit is contained in:
@@ -70,8 +70,13 @@ function parseRoomNumber(input: string) {
|
||||
const floor = parseInt(roomPart.charAt(0), 10) || undefined;
|
||||
let roomType = '四人间';
|
||||
let capacity = 4;
|
||||
if (bldgNum === '2') { roomType = '单人间'; capacity = 1; }
|
||||
else if (bldgNum === '8') { roomType = '爆改房'; capacity = 2; }
|
||||
if (bldgNum === '2') {
|
||||
roomType = '单人间';
|
||||
capacity = 1;
|
||||
} else if (bldgNum === '8') {
|
||||
roomType = '爆改房';
|
||||
capacity = 2;
|
||||
}
|
||||
return { building: `${bldgNum}号楼`, floor, roomType, capacity };
|
||||
}
|
||||
return null;
|
||||
@@ -150,12 +155,19 @@ const RoomsPage: React.FC = () => {
|
||||
let result = data;
|
||||
if (searchText) {
|
||||
const keyword = searchText.toLowerCase();
|
||||
result = result.filter((r: Record<string, unknown>) => typeof r.roomNumber === 'string' && r.roomNumber.toLowerCase().includes(keyword));
|
||||
result = result.filter(
|
||||
(r: Record<string, unknown>) =>
|
||||
typeof r.roomNumber === 'string' && r.roomNumber.toLowerCase().includes(keyword),
|
||||
);
|
||||
}
|
||||
if (filterBuilding) result = result.filter((r: Record<string, unknown>) => r.building === filterBuilding);
|
||||
if (filterStatus) result = result.filter((r: Record<string, unknown>) => r.status === filterStatus);
|
||||
if (filterBuilding)
|
||||
result = result.filter((r: Record<string, unknown>) => r.building === filterBuilding);
|
||||
if (filterStatus)
|
||||
result = result.filter((r: Record<string, unknown>) => r.status === filterStatus);
|
||||
if (filterRentalCategory) {
|
||||
result = result.filter((r: Record<string, unknown>) => r.rentalCategory === filterRentalCategory);
|
||||
result = result.filter(
|
||||
(r: Record<string, unknown>) => r.rentalCategory === filterRentalCategory,
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}, [data, searchText, filterBuilding, filterStatus, filterRentalCategory]);
|
||||
@@ -221,8 +233,11 @@ const RoomsPage: React.FC = () => {
|
||||
bedForm.resetFields();
|
||||
setBedEditing(null);
|
||||
fetchBeds(drawerRoom.id);
|
||||
} catch (e: any) { message.error(e?.message || '操作失败'); }
|
||||
finally { setSavingBed(false); }
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '操作失败');
|
||||
} finally {
|
||||
setSavingBed(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteBed = async (id: number) => {
|
||||
@@ -230,7 +245,9 @@ const RoomsPage: React.FC = () => {
|
||||
await api.delete(`/rooms/${drawerRoom.id}/beds/${id}`);
|
||||
message.success('已归档');
|
||||
fetchBeds(drawerRoom.id);
|
||||
} catch (e: any) { message.error(e?.message || '归档失败'); }
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '归档失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleBatchBeds = async (count: number) => {
|
||||
@@ -238,7 +255,9 @@ const RoomsPage: React.FC = () => {
|
||||
await api.post(`/rooms/${drawerRoom.id}/beds/batch`, { count });
|
||||
message.success(`已生成 ${count} 张床位`);
|
||||
fetchBeds(drawerRoom.id);
|
||||
} catch (e: any) { message.error(e?.message || '批量生成失败'); }
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '批量生成失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveLocker = async () => {
|
||||
@@ -255,8 +274,11 @@ const RoomsPage: React.FC = () => {
|
||||
lockerForm.resetFields();
|
||||
setLockerEditing(null);
|
||||
fetchLockers(drawerRoom.id);
|
||||
} catch (e: any) { message.error(e?.message || '操作失败'); }
|
||||
finally { setSavingLocker(false); }
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '操作失败');
|
||||
} finally {
|
||||
setSavingLocker(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteLocker = async (id: number) => {
|
||||
@@ -264,7 +286,9 @@ const RoomsPage: React.FC = () => {
|
||||
await api.delete(`/rooms/${drawerRoom.id}/lockers/${id}`);
|
||||
message.success('已归档');
|
||||
fetchLockers(drawerRoom.id);
|
||||
} catch (e: any) { message.error(e?.message || '归档失败'); }
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '归档失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleBatchLockers = async (count: number) => {
|
||||
@@ -272,7 +296,9 @@ const RoomsPage: React.FC = () => {
|
||||
await api.post(`/rooms/${drawerRoom.id}/lockers/batch`, { count });
|
||||
message.success(`已生成 ${count} 个柜子`);
|
||||
fetchLockers(drawerRoom.id);
|
||||
} catch (e: any) { message.error(e?.message || '批量生成失败'); }
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '批量生成失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleArchive = async (id: number) => {
|
||||
@@ -304,109 +330,126 @@ const RoomsPage: React.FC = () => {
|
||||
downloadBlob('/rooms/export' + params, '房间列表.xlsx').catch(() => message.error('导出失败'));
|
||||
};
|
||||
|
||||
const columns = useMemo(() => [
|
||||
{
|
||||
title: '房间号',
|
||||
dataIndex: 'roomNumber',
|
||||
width: 100,
|
||||
sorter: (a: any, b: any) => a.roomNumber.localeCompare(b.roomNumber),
|
||||
},
|
||||
{ title: '楼栋', dataIndex: 'building', width: 80 },
|
||||
{ title: '楼层', dataIndex: 'floor', width: 80 },
|
||||
{ title: '类型', dataIndex: 'roomType', width: 90, render: (v: any) => v || '-' },
|
||||
{
|
||||
title: '租赁类型',
|
||||
dataIndex: 'rentalCategory',
|
||||
width: 100,
|
||||
render: (v: string) => {
|
||||
if (v === 'long') return <Tag color="blue">长租</Tag>;
|
||||
if (v === 'short') return <Tag color="green">短租</Tag>;
|
||||
return '-';
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: '房间号',
|
||||
dataIndex: 'roomNumber',
|
||||
width: 100,
|
||||
sorter: (a: any, b: any) => a.roomNumber.localeCompare(b.roomNumber),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '月租金',
|
||||
dataIndex: 'monthlyRate',
|
||||
width: 100,
|
||||
render: (v: number) => (v ? `¥${v}` : '-'),
|
||||
},
|
||||
{ title: '额定人数', dataIndex: 'capacity', width: 80 },
|
||||
{
|
||||
title: '当前入住',
|
||||
width: 80,
|
||||
render: (_: any, r: any) =>
|
||||
r.status === 'archived' ? (
|
||||
<Tag color="#999">-</Tag>
|
||||
) : (
|
||||
<Badge
|
||||
count={r.currentCount}
|
||||
showZero
|
||||
overflowCount={99}
|
||||
style={{ backgroundColor: r.currentCount >= r.capacity ? '#ff4d4f' : '#52c41a' }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 80,
|
||||
render: (s: string) => <Tag color={statusMap[s]?.color}>{statusMap[s]?.text || s}</Tag>,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 220,
|
||||
render: (_: unknown, record: unknown) => {
|
||||
const r = record as { status?: string; id: number };
|
||||
return (
|
||||
<Space>
|
||||
{r.status === 'archived' ? (
|
||||
<Popconfirm
|
||||
title="确定恢复此宿舍?"
|
||||
onConfirm={() => handleRestore(r.id)}
|
||||
>
|
||||
<PermissionButton permission="room:edit" size="small" icon={<UndoOutlined />} type="link">
|
||||
恢复
|
||||
</PermissionButton>
|
||||
</Popconfirm>
|
||||
) : (
|
||||
<>
|
||||
<PermissionButton
|
||||
permission="room:view"
|
||||
size="small"
|
||||
type="link"
|
||||
onClick={async () => {
|
||||
const rec = record as { id: number };
|
||||
setDrawerRoom(record);
|
||||
setDrawerOpen(true);
|
||||
await Promise.all([fetchBeds(rec.id), fetchLockers(rec.id)]);
|
||||
}}
|
||||
>
|
||||
查看
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
permission="room:edit"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
const rec = record as { id: number };
|
||||
setEditing(rec);
|
||||
form.setFieldsValue(rec);
|
||||
setModalOpen(true);
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</PermissionButton>
|
||||
<Popconfirm title="确定归档?" onConfirm={() => handleArchive(r.id)}>
|
||||
<PermissionButton permission="room:delete" size="small" icon={<InboxOutlined />}>
|
||||
归档
|
||||
{ title: '楼栋', dataIndex: 'building', width: 80 },
|
||||
{ title: '楼层', dataIndex: 'floor', width: 80 },
|
||||
{ title: '类型', dataIndex: 'roomType', width: 90, render: (v: any) => v || '-' },
|
||||
{
|
||||
title: '租赁类型',
|
||||
dataIndex: 'rentalCategory',
|
||||
width: 100,
|
||||
render: (v: string) => {
|
||||
if (v === 'long') return <Tag color="blue">长租</Tag>;
|
||||
if (v === 'short') return <Tag color="green">短租</Tag>;
|
||||
return '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '月租金',
|
||||
dataIndex: 'monthlyRate',
|
||||
width: 100,
|
||||
render: (v: number) => (v ? `¥${v}` : '-'),
|
||||
},
|
||||
{ title: '额定人数', dataIndex: 'capacity', width: 80 },
|
||||
{
|
||||
title: '当前入住',
|
||||
width: 80,
|
||||
render: (_: any, r: any) =>
|
||||
r.status === 'archived' ? (
|
||||
<Tag color="#999">-</Tag>
|
||||
) : (
|
||||
<Badge
|
||||
count={r.currentCount}
|
||||
showZero
|
||||
overflowCount={99}
|
||||
style={{ backgroundColor: r.currentCount >= r.capacity ? '#ff4d4f' : '#52c41a' }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 80,
|
||||
render: (s: string) => <Tag color={statusMap[s]?.color}>{statusMap[s]?.text || s}</Tag>,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 220,
|
||||
render: (_: unknown, record: unknown) => {
|
||||
const r = record as { status?: string; id: number };
|
||||
return (
|
||||
<Space>
|
||||
{r.status === 'archived' ? (
|
||||
<Popconfirm title="确定恢复此宿舍?" onConfirm={() => handleRestore(r.id)}>
|
||||
<PermissionButton
|
||||
permission="room:edit"
|
||||
size="small"
|
||||
icon={<UndoOutlined />}
|
||||
type="link"
|
||||
>
|
||||
恢复
|
||||
</PermissionButton>
|
||||
</Popconfirm>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
) : (
|
||||
<>
|
||||
<PermissionButton
|
||||
permission="room:view"
|
||||
size="small"
|
||||
type="link"
|
||||
onClick={async () => {
|
||||
const rec = record as { id: number };
|
||||
setDrawerRoom(record);
|
||||
setDrawerOpen(true);
|
||||
await Promise.all([fetchBeds(rec.id), fetchLockers(rec.id)]);
|
||||
}}
|
||||
>
|
||||
查看
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
permission="room:edit"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
const rec = record as { id: number };
|
||||
setEditing(rec);
|
||||
form.setFieldsValue(rec);
|
||||
setModalOpen(true);
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</PermissionButton>
|
||||
<Popconfirm title="确定归档?" onConfirm={() => handleArchive(r.id)}>
|
||||
<PermissionButton
|
||||
permission="room:delete"
|
||||
size="small"
|
||||
icon={<InboxOutlined />}
|
||||
>
|
||||
归档
|
||||
</PermissionButton>
|
||||
</Popconfirm>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
], [showArchived, buildings, handleBatchDelete, handleRestore, handleArchive, fetchBeds, fetchLockers]);
|
||||
],
|
||||
[
|
||||
showArchived,
|
||||
buildings,
|
||||
handleBatchDelete,
|
||||
handleRestore,
|
||||
handleArchive,
|
||||
fetchBeds,
|
||||
fetchLockers,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -427,8 +470,18 @@ const RoomsPage: React.FC = () => {
|
||||
onChange={(v) => setFilterBuilding(v)}
|
||||
options={buildings.map((b) => ({ value: b, label: b }))}
|
||||
/>
|
||||
<Select placeholder="状态" allowClear style={{ width: 110 }} value={filterStatus} onChange={setFilterStatus}
|
||||
options={[{value:'available',label:'可入住'},{value:'full',label:'已满'},{value:'maintenance',label:'维护中'}]} />
|
||||
<Select
|
||||
placeholder="状态"
|
||||
allowClear
|
||||
style={{ width: 110 }}
|
||||
value={filterStatus}
|
||||
onChange={setFilterStatus}
|
||||
options={[
|
||||
{ value: 'available', label: '可入住' },
|
||||
{ value: 'full', label: '已满' },
|
||||
{ value: 'maintenance', label: '维护中' },
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
placeholder="租赁类型"
|
||||
allowClear
|
||||
@@ -612,7 +665,10 @@ const RoomsPage: React.FC = () => {
|
||||
<Drawer
|
||||
title={`${drawerRoom?.roomNumber} 房间详情`}
|
||||
open={drawerOpen}
|
||||
onClose={() => { setDrawerOpen(false); setDrawerRoom(null); }}
|
||||
onClose={() => {
|
||||
setDrawerOpen(false);
|
||||
setDrawerRoom(null);
|
||||
}}
|
||||
width={640}
|
||||
destroyOnClose
|
||||
>
|
||||
@@ -624,14 +680,40 @@ const RoomsPage: React.FC = () => {
|
||||
label: '基本信息',
|
||||
children: drawerRoom && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<div><strong>房间号:</strong>{drawerRoom.roomNumber}</div>
|
||||
<div><strong>楼栋:</strong>{drawerRoom.building || '-'}</div>
|
||||
<div><strong>楼层:</strong>{drawerRoom.floor ?? '-'}</div>
|
||||
<div><strong>类型:</strong>{drawerRoom.roomType || '-'}</div>
|
||||
<div><strong>额定人数:</strong>{drawerRoom.capacity}</div>
|
||||
<div><strong>租赁类别:</strong>{drawerRoom.rentalCategory === 'long' ? '长租' : '短租'}</div>
|
||||
<div><strong>月租金:</strong>{drawerRoom.monthlyRate ? `¥${drawerRoom.monthlyRate}` : '-'}</div>
|
||||
<div><strong>状态:</strong><Tag color={statusMap[drawerRoom.status]?.color}>{statusMap[drawerRoom.status]?.text}</Tag></div>
|
||||
<div>
|
||||
<strong>房间号:</strong>
|
||||
{drawerRoom.roomNumber}
|
||||
</div>
|
||||
<div>
|
||||
<strong>楼栋:</strong>
|
||||
{drawerRoom.building || '-'}
|
||||
</div>
|
||||
<div>
|
||||
<strong>楼层:</strong>
|
||||
{drawerRoom.floor ?? '-'}
|
||||
</div>
|
||||
<div>
|
||||
<strong>类型:</strong>
|
||||
{drawerRoom.roomType || '-'}
|
||||
</div>
|
||||
<div>
|
||||
<strong>额定人数:</strong>
|
||||
{drawerRoom.capacity}
|
||||
</div>
|
||||
<div>
|
||||
<strong>租赁类别:</strong>
|
||||
{drawerRoom.rentalCategory === 'long' ? '长租' : '短租'}
|
||||
</div>
|
||||
<div>
|
||||
<strong>月租金:</strong>
|
||||
{drawerRoom.monthlyRate ? `¥${drawerRoom.monthlyRate}` : '-'}
|
||||
</div>
|
||||
<div>
|
||||
<strong>状态:</strong>
|
||||
<Tag color={statusMap[drawerRoom.status]?.color}>
|
||||
{statusMap[drawerRoom.status]?.text}
|
||||
</Tag>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@@ -646,25 +728,48 @@ const RoomsPage: React.FC = () => {
|
||||
size="small"
|
||||
icon={<PlusOutlined />}
|
||||
disabled={drawerRoom?.status === 'archived' || remainingBedSlots === 0}
|
||||
onClick={() => { setBedEditing(null); bedForm.resetFields(); setBedModalOpen(true); }}
|
||||
onClick={() => {
|
||||
setBedEditing(null);
|
||||
bedForm.resetFields();
|
||||
setBedModalOpen(true);
|
||||
}}
|
||||
>
|
||||
添加床位
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title={remainingBedSlots > 0 ? '批量生成床位' : '床位已达到额定人数'}
|
||||
description={
|
||||
remainingBedSlots > 0
|
||||
? <InputNumber min={1} max={remainingBedSlots} defaultValue={defaultBatchBedCount} id="batch-bed-count" style={{ width: 80 }} />
|
||||
: '如需增加床位,请先调整宿舍额定人数'
|
||||
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;
|
||||
handleBatchBeds(input ? parseInt(input.value) || defaultBatchBedCount : defaultBatchBedCount);
|
||||
const input = document.getElementById(
|
||||
'batch-bed-count',
|
||||
) as HTMLInputElement;
|
||||
handleBatchBeds(
|
||||
input
|
||||
? parseInt(input.value) || defaultBatchBedCount
|
||||
: defaultBatchBedCount,
|
||||
);
|
||||
}}
|
||||
okText="生成"
|
||||
disabled={drawerRoom?.status === 'archived' || remainingBedSlots === 0}
|
||||
>
|
||||
<Button size="small" disabled={drawerRoom?.status === 'archived' || remainingBedSlots === 0}>批量生成</Button>
|
||||
<Button
|
||||
size="small"
|
||||
disabled={drawerRoom?.status === 'archived' || remainingBedSlots === 0}
|
||||
>
|
||||
批量生成
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
<Table
|
||||
@@ -675,7 +780,9 @@ const RoomsPage: React.FC = () => {
|
||||
columns={[
|
||||
{ title: '编号', dataIndex: 'bedNumber', width: 80 },
|
||||
{
|
||||
title: '状态', dataIndex: 'status', width: 80,
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 80,
|
||||
render: (s: string) => {
|
||||
const map: Record<string, { text: string; color: string }> = {
|
||||
available: { text: '空闲', color: 'green' },
|
||||
@@ -687,7 +794,8 @@ const RoomsPage: React.FC = () => {
|
||||
},
|
||||
{ title: '备注', dataIndex: 'notes', render: (v: string) => v || '-' },
|
||||
{
|
||||
title: '操作', width: 120,
|
||||
title: '操作',
|
||||
width: 120,
|
||||
render: (_: any, r: any) => (
|
||||
<Space size="small">
|
||||
<PermissionButton
|
||||
@@ -695,12 +803,19 @@ const RoomsPage: React.FC = () => {
|
||||
size="small"
|
||||
type="link"
|
||||
disabled={drawerRoom?.status === 'archived'}
|
||||
onClick={() => { setBedEditing(r); bedForm.setFieldsValue(r); setBedModalOpen(true); }}
|
||||
onClick={() => {
|
||||
setBedEditing(r);
|
||||
bedForm.setFieldsValue(r);
|
||||
setBedModalOpen(true);
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</PermissionButton>
|
||||
{r.status !== 'occupied' && (
|
||||
<Popconfirm title="确定归档?" onConfirm={() => handleDeleteBed(r.id)}>
|
||||
<Popconfirm
|
||||
title="确定归档?"
|
||||
onConfirm={() => handleDeleteBed(r.id)}
|
||||
>
|
||||
<PermissionButton
|
||||
permission="room:edit"
|
||||
size="small"
|
||||
@@ -731,23 +846,37 @@ const RoomsPage: React.FC = () => {
|
||||
size="small"
|
||||
icon={<PlusOutlined />}
|
||||
disabled={drawerRoom?.status === 'archived'}
|
||||
onClick={() => { setLockerEditing(null); lockerForm.resetFields(); setLockerModalOpen(true); }}
|
||||
onClick={() => {
|
||||
setLockerEditing(null);
|
||||
lockerForm.resetFields();
|
||||
setLockerModalOpen(true);
|
||||
}}
|
||||
>
|
||||
添加柜子
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="批量生成柜子"
|
||||
description={
|
||||
<InputNumber min={1} max={20} defaultValue={4} id="batch-locker-count" style={{ width: 80 }} />
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={20}
|
||||
defaultValue={4}
|
||||
id="batch-locker-count"
|
||||
style={{ width: 80 }}
|
||||
/>
|
||||
}
|
||||
onConfirm={() => {
|
||||
const input = document.getElementById('batch-locker-count') as HTMLInputElement;
|
||||
const input = document.getElementById(
|
||||
'batch-locker-count',
|
||||
) as HTMLInputElement;
|
||||
handleBatchLockers(input ? parseInt(input.value) || 4 : 4);
|
||||
}}
|
||||
okText="生成"
|
||||
disabled={drawerRoom?.status === 'archived'}
|
||||
>
|
||||
<Button size="small" disabled={drawerRoom?.status === 'archived'}>批量生成</Button>
|
||||
<Button size="small" disabled={drawerRoom?.status === 'archived'}>
|
||||
批量生成
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
<Table
|
||||
@@ -758,7 +887,9 @@ const RoomsPage: React.FC = () => {
|
||||
columns={[
|
||||
{ title: '编号', dataIndex: 'lockerNumber', width: 80 },
|
||||
{
|
||||
title: '状态', dataIndex: 'status', width: 80,
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 80,
|
||||
render: (s: string) => {
|
||||
const map: Record<string, { text: string; color: string }> = {
|
||||
available: { text: '空闲', color: 'green' },
|
||||
@@ -770,7 +901,8 @@ const RoomsPage: React.FC = () => {
|
||||
},
|
||||
{ title: '备注', dataIndex: 'notes', render: (v: string) => v || '-' },
|
||||
{
|
||||
title: '操作', width: 120,
|
||||
title: '操作',
|
||||
width: 120,
|
||||
render: (_: any, r: any) => (
|
||||
<Space size="small">
|
||||
<PermissionButton
|
||||
@@ -778,12 +910,19 @@ const RoomsPage: React.FC = () => {
|
||||
size="small"
|
||||
type="link"
|
||||
disabled={drawerRoom?.status === 'archived'}
|
||||
onClick={() => { setLockerEditing(r); lockerForm.setFieldsValue(r); setLockerModalOpen(true); }}
|
||||
onClick={() => {
|
||||
setLockerEditing(r);
|
||||
lockerForm.setFieldsValue(r);
|
||||
setLockerModalOpen(true);
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</PermissionButton>
|
||||
{r.status !== 'occupied' && (
|
||||
<Popconfirm title="确定归档?" onConfirm={() => handleDeleteLocker(r.id)}>
|
||||
<Popconfirm
|
||||
title="确定归档?"
|
||||
onConfirm={() => handleDeleteLocker(r.id)}
|
||||
>
|
||||
<PermissionButton
|
||||
permission="room:edit"
|
||||
size="small"
|
||||
@@ -811,7 +950,10 @@ const RoomsPage: React.FC = () => {
|
||||
title={bedEditing ? '编辑床位' : '添加床位'}
|
||||
open={bedModalOpen}
|
||||
onOk={handleSaveBed}
|
||||
onCancel={() => { setBedModalOpen(false); setBedEditing(null); }}
|
||||
onCancel={() => {
|
||||
setBedModalOpen(false);
|
||||
setBedEditing(null);
|
||||
}}
|
||||
confirmLoading={savingBed}
|
||||
okText="保存"
|
||||
>
|
||||
@@ -838,7 +980,10 @@ const RoomsPage: React.FC = () => {
|
||||
title={lockerEditing ? '编辑柜子' : '添加柜子'}
|
||||
open={lockerModalOpen}
|
||||
onOk={handleSaveLocker}
|
||||
onCancel={() => { setLockerModalOpen(false); setLockerEditing(null); }}
|
||||
onCancel={() => {
|
||||
setLockerModalOpen(false);
|
||||
setLockerEditing(null);
|
||||
}}
|
||||
confirmLoading={savingLocker}
|
||||
okText="保存"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user