feat: 为批量归档补充批量恢复

This commit is contained in:
2026-07-25 09:34:03 +08:00
parent 1da763109b
commit 0ef86e65ce
19 changed files with 1365 additions and 317 deletions

View File

@@ -32,6 +32,7 @@ import PermissionButton from '../../components/PermissionButton';
import EditableCell from '../../components/EditableCell';
import { message } from '../../ui/app-message';
import { usePermission } from '../../hooks/usePermission';
import { selectArchiveRecords } from '../archive-view';
const statusMap: Record<string, { text: string; color: string }> = {
available: { text: '可入住', color: 'green' },
@@ -95,7 +96,6 @@ const RoomsPage: React.FC = () => {
const [editing, setEditing] = useState<any>(null);
const canSaveRoom = editing ? canEditRooms : canCreateRooms;
const [showArchived, setShowArchived] = useState(false);
const [archivedCount, setArchivedCount] = useState(0);
const [searchText, setSearchText] = useState('');
const [filterBuilding, setFilterBuilding] = useState<string | undefined>(undefined);
const [filterStatus, setFilterStatus] = useState<string | undefined>(undefined);
@@ -118,9 +118,16 @@ const RoomsPage: React.FC = () => {
const [batchLoading, setBatchLoading] = useState(false);
// Close modals when the required permission is lost
useEffect(() => { if (!canSaveRoom && modalOpen) { setModalOpen(false); setEditing(null); form.resetFields(); } }, [canSaveRoom, modalOpen, form]);
useEffect(() => {
if (!canSaveRoom && modalOpen) {
setModalOpen(false);
setEditing(null);
form.resetFields();
}
}, [canSaveRoom, modalOpen, form]);
const handleBatchDelete = async () => {
if (batchLoading) return;
setBatchLoading(true);
try {
const res: any = await api.post('/rooms/batch-delete', { ids: selectedRowKeys });
@@ -134,14 +141,32 @@ const RoomsPage: React.FC = () => {
}
};
const handleBatchRestore = async () => {
if (batchLoading) return;
setBatchLoading(true);
try {
const res = await api.put<{ message?: string; restored: number; skipped: number }>(
'/rooms/batch-restore',
{ ids: selectedRowKeys },
);
message.success(
`已批量恢复 ${res.restored}${res.skipped ? `,跳过 ${res.skipped}` : ''}`,
);
setSelectedRowKeys([]);
fetchData();
} catch (e: any) {
message.error(e?.message || '批量恢复失败');
} finally {
setBatchLoading(false);
}
};
const fetchData = async () => {
setLoading(true);
try {
const params: any = { includeArchived: 'true' };
const params: any = { includeArchived: showArchived ? 'true' : undefined };
const res: any = await api.get('/rooms/overview', { params });
const archived = res.filter((r: any) => r.status === 'archived');
setArchivedCount(archived.length);
const filtered = showArchived ? res : res.filter((r: any) => r.status !== 'archived');
const filtered = selectArchiveRecords(res, showArchived ? 'archived' : 'active');
setData(filtered);
} catch (e: unknown) {
const err = e as { message?: string };
@@ -530,11 +555,7 @@ const RoomsPage: React.FC = () => {
{r.status === 'archived' ? (
canEditRooms ? (
<Popconfirm title="确定恢复此宿舍?" onConfirm={() => handleRestore(r.id)}>
<Button
size="small"
icon={<UndoOutlined />}
type="link"
>
<Button size="small" icon={<UndoOutlined />} type="link">
</Button>
</Popconfirm>
@@ -568,10 +589,7 @@ const RoomsPage: React.FC = () => {
</PermissionButton>
{canDeleteRooms ? (
<Popconfirm title="确定归档?" onConfirm={() => handleArchive(r.id)}>
<Button
size="small"
icon={<InboxOutlined />}
>
<Button size="small" icon={<InboxOutlined />}>
</Button>
</Popconfirm>
@@ -638,15 +656,34 @@ const RoomsPage: React.FC = () => {
/>
<Button
type={showArchived ? 'primary' : 'default'}
onClick={() => setShowArchived(!showArchived)}
onClick={() => {
setShowArchived(!showArchived);
setFilterStatus(undefined);
setSelectedRowKeys([]);
}}
>
{showArchived
? '隐藏已归档'
: `显示已归档${archivedCount > 0 ? ` (${archivedCount})` : ''}`}
{showArchived ? '返回正常数据' : '查看已归档'}
</Button>
</Space>
<Space wrap className="responsive-toolbar__group">
{canDeleteRooms ? (
{showArchived && canEditRooms ? (
<Popconfirm
title={`确定批量恢复选中的 ${selectedRowKeys.length} 间宿舍?`}
onConfirm={handleBatchRestore}
okText="恢复"
cancelText="取消"
disabled={selectedRowKeys.length === 0}
>
<Button
type="primary"
icon={<UndoOutlined />}
disabled={selectedRowKeys.length === 0}
loading={batchLoading}
>
</Button>
</Popconfirm>
) : !showArchived && canDeleteRooms ? (
<Popconfirm
title={`确定批量归档选中的 ${selectedRowKeys.length} 间宿舍?(有在住人员的会跳过)`}
onConfirm={handleBatchDelete}
@@ -664,19 +701,21 @@ const RoomsPage: React.FC = () => {
</Button>
</Popconfirm>
) : null}
<PermissionButton
permission="room:create"
type="primary"
icon={<PlusOutlined />}
onClick={() => {
setEditing(null);
form.resetFields();
setModalOpen(true);
}}
>
宿
</PermissionButton>
{hasPermission('room:create') ? (
{!showArchived ? (
<PermissionButton
permission="room:create"
type="primary"
icon={<PlusOutlined />}
onClick={() => {
setEditing(null);
form.resetFields();
setModalOpen(true);
}}
>
宿
</PermissionButton>
) : null}
{!showArchived && hasPermission('room:create') ? (
<Upload
accept=".xlsx,.xls"
showUploadList={false}
@@ -734,7 +773,6 @@ const RoomsPage: React.FC = () => {
rowSelection={{
selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys as number[]),
getCheckboxProps: (record: any) => ({ disabled: record.status === 'archived' }),
}}
/>
<style>{`.archived-row { opacity: 0.6; background: #fafafa !important; }`}</style>