chore: commit oxfmt formatting changes and verify artifacts

This commit is contained in:
2026-07-02 15:55:09 +08:00
parent 34726cc46d
commit 4f4cee157a
77 changed files with 5576 additions and 1400 deletions

View File

@@ -1,5 +1,20 @@
import React, { useEffect, useState, useMemo } from 'react';
import { Table, Button, Modal, Form, Select, DatePicker, InputNumber, Input, Space, message, Tag, Popconfirm, Upload, Tooltip } from 'antd';
import {
Table,
Button,
Modal,
Form,
Select,
DatePicker,
InputNumber,
Input,
Space,
message,
Tag,
Popconfirm,
Upload,
Tooltip,
} from 'antd';
import { PlusOutlined, UploadOutlined, DeleteOutlined, FileTextOutlined } from '@ant-design/icons';
import dayjs, { Dayjs } from 'dayjs';
import api from '../../api';
@@ -33,23 +48,28 @@ const ClassroomRentalsPage: React.FC = () => {
if (filterMonth) params.month = filterMonth.format('YYYY-MM');
const res: any = await api.get('/classroom-rentals', { params });
setData(res);
} catch (e) { console.error(e); }
} catch (e) {
console.error(e);
}
setLoading(false);
};
const fetchMeta = async () => {
try {
const [cr, tn]: any = await Promise.all([
api.get('/classrooms'),
api.get('/tenants'),
]);
const [cr, tn]: any = await Promise.all([api.get('/classrooms'), api.get('/tenants')]);
setClassrooms(cr);
setTenants(tn);
} catch (e) { console.error(e); }
} catch (e) {
console.error(e);
}
};
useEffect(() => { fetchMeta(); }, []);
useEffect(() => { fetchData(); }, [filterMonth]);
useEffect(() => {
fetchMeta();
}, []);
useEffect(() => {
fetchData();
}, [filterMonth]);
const handleSave = async () => {
const values = await form.validateFields();
@@ -76,7 +96,9 @@ const ClassroomRentalsPage: React.FC = () => {
fetchData();
} catch (e: any) {
if (e?.conflicts?.length) {
const list = e.conflicts.map((c: any) => `${c.tenantName}(${c.startDate}~${c.endDate})`).join('、');
const list = e.conflicts
.map((c: any) => `${c.tenantName}(${c.startDate}~${c.endDate})`)
.join('、');
message.error(`时间段冲突:${list}`);
} else {
message.error(e?.message || '操作失败');
@@ -89,18 +111,24 @@ const ClassroomRentalsPage: React.FC = () => {
await api.delete(`/classroom-rentals/${id}`);
message.success('已删除');
fetchData();
} catch (e: any) { message.error(e?.message || '删除失败'); }
} catch (e: any) {
message.error(e?.message || '删除失败');
}
};
const handleDownloadContract = (id: number, filename?: string) => {
const baseURL = import.meta.env.PROD ? '/api' : `http://localhost:${import.meta.env.VITE_API_PORT || 3002}/api`;
const baseURL = import.meta.env.PROD
? '/api'
: `http://localhost:${import.meta.env.VITE_API_PORT || 3002}/api`;
const token = localStorage.getItem('token');
fetch(`${baseURL}/classroom-rentals/${id}/contract`, { headers: { Authorization: `Bearer ${token}` } })
.then(res => {
fetch(`${baseURL}/classroom-rentals/${id}/contract`, {
headers: { Authorization: `Bearer ${token}` },
})
.then((res) => {
if (!res.ok) throw new Error('下载失败');
return res.blob();
})
.then(blob => {
.then((blob) => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
@@ -116,7 +144,9 @@ const ClassroomRentalsPage: React.FC = () => {
await api.delete(`/classroom-rentals/${id}/contract`);
message.success('合同已删除');
fetchData();
} catch (e: any) { message.error(e?.message || '删除失败'); }
} catch (e: any) {
message.error(e?.message || '删除失败');
}
};
const openEdit = (record: any) => {
@@ -134,66 +164,107 @@ const ClassroomRentalsPage: React.FC = () => {
const columns = [
{
title: '教室', dataIndex: 'classroom',
render: (c: any) => c ? <span>{c.building ? `${c.building} · ` : ''}{c.name}</span> : '-',
title: '教室',
dataIndex: 'classroom',
render: (c: any) =>
c ? (
<span>
{c.building ? `${c.building} · ` : ''}
{c.name}
</span>
) : (
'-'
),
},
{
title: '租赁方', dataIndex: 'tenant',
render: (t: any) => t ? <Tag color={t.color} style={{ background: t.color, color: '#fff', borderColor: t.color }}>{t.name}</Tag> : '-',
title: '租赁方',
dataIndex: 'tenant',
render: (t: any) =>
t ? (
<Tag color={t.color} style={{ background: t.color, color: '#fff', borderColor: t.color }}>
{t.name}
</Tag>
) : (
'-'
),
},
{ title: '开始日期', dataIndex: 'startDate' },
{ title: '结束日期', dataIndex: 'endDate' },
{
title: '时长', render: (_: any, r: any) => {
title: '时长',
render: (_: any, r: any) => {
const d = dayjs(r.endDate).diff(dayjs(r.startDate), 'day') + 1;
return `${d}`;
},
},
{ title: '日租金', dataIndex: 'dailyRate', render: (v: any) => v ? `¥${v}` : '-' },
{ title: '总额', dataIndex: 'totalAmount', render: (v: any) => v ? `¥${v}` : '-' },
{ title: '日租金', dataIndex: 'dailyRate', render: (v: any) => (v ? `¥${v}` : '-') },
{ title: '总额', dataIndex: 'totalAmount', render: (v: any) => (v ? `¥${v}` : '-') },
{
title: '合同', dataIndex: 'contractPath',
render: (v: string, r: any) => v ? (
<Space>
<Tooltip title={r.contractOriginalName}>
<Button size="small" icon={<FileTextOutlined />} onClick={() => handleDownloadContract(r.id, r.contractOriginalName)}></Button>
</Tooltip>
<Popconfirm title="删除合同文件?" onConfirm={() => handleDeleteContract(r.id)}>
<Button size="small" danger icon={<DeleteOutlined />} />
</Popconfirm>
</Space>
) : (
<Upload
accept="application/pdf"
showUploadList={false}
customRequest={async ({ file, onSuccess, onError }: any) => {
if (file.size > 10 * 1024 * 1024) {
message.error('文件不能超过 10MB');
onError?.(new Error('size'));
return;
}
const formData = new FormData();
formData.append('file', file);
try {
await api.post(`/classroom-rentals/${r.id}/contract`, formData, { headers: { 'Content-Type': 'multipart/form-data' } });
message.success('合同已上传');
onSuccess?.({});
fetchData();
} catch (e: any) { message.error(e?.message || '上传失败'); onError?.(e); }
}}
>
<Button size="small" icon={<UploadOutlined />}>PDF</Button>
</Upload>
),
title: '合同',
dataIndex: 'contractPath',
render: (v: string, r: any) =>
v ? (
<Space>
<Tooltip title={r.contractOriginalName}>
<Button
size="small"
icon={<FileTextOutlined />}
onClick={() => handleDownloadContract(r.id, r.contractOriginalName)}
>
</Button>
</Tooltip>
<Popconfirm title="删除合同文件?" onConfirm={() => handleDeleteContract(r.id)}>
<Button size="small" danger icon={<DeleteOutlined />} />
</Popconfirm>
</Space>
) : (
<Upload
accept="application/pdf"
showUploadList={false}
customRequest={async ({ file, onSuccess, onError }: any) => {
if (file.size > 10 * 1024 * 1024) {
message.error('文件不能超过 10MB');
onError?.(new Error('size'));
return;
}
const formData = new FormData();
formData.append('file', file);
try {
await api.post(`/classroom-rentals/${r.id}/contract`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
message.success('合同已上传');
onSuccess?.({});
fetchData();
} catch (e: any) {
message.error(e?.message || '上传失败');
onError?.(e);
}
}}
>
<Button size="small" icon={<UploadOutlined />}>
PDF
</Button>
</Upload>
),
},
{
title: '操作', width: 150,
title: '操作',
width: 150,
render: (_: any, record: any) => (
<Space>
<PermissionButton permission="rental:edit" size="small" onClick={() => openEdit(record)}></PermissionButton>
<PermissionButton permission="rental:edit" size="small" onClick={() => openEdit(record)}>
</PermissionButton>
<PermissionButton permission="rental:delete">
<Popconfirm title="确定删除该租赁订单?合同文件将一并删除。" onConfirm={() => handleDelete(record.id)}>
<Button size="small" danger></Button>
<Popconfirm
title="确定删除该租赁订单?合同文件将一并删除。"
onConfirm={() => handleDelete(record.id)}
>
<Button size="small" danger>
</Button>
</Popconfirm>
</PermissionButton>
</Space>
@@ -203,31 +274,77 @@ const ClassroomRentalsPage: React.FC = () => {
return (
<div>
<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>
<Input.Search
placeholder="搜索教室/租赁方"
allowClear
style={{ width: 180 }}
onSearch={v => setSearchText(v)}
onChange={e => { if (!e.target.value) setSearchText(''); }}
onSearch={(v) => setSearchText(v)}
onChange={(e) => {
if (!e.target.value) setSearchText('');
}}
/>
<DatePicker
picker="month"
placeholder="按月筛选"
value={filterMonth}
onChange={setFilterMonth}
allowClear
format="YYYY-MM"
/>
<DatePicker picker="month" placeholder="按月筛选" value={filterMonth} onChange={setFilterMonth} allowClear format="YYYY-MM" />
</Space>
<PermissionButton permission="rental:create" type="primary" icon={<PlusOutlined />} onClick={() => { setEditing(null); form.resetFields(); setModalOpen(true); }}>
<PermissionButton
permission="rental:create"
type="primary"
icon={<PlusOutlined />}
onClick={() => {
setEditing(null);
form.resetFields();
setModalOpen(true);
}}
>
</PermissionButton>
</div>
<Table columns={columns} dataSource={filteredData} rowKey="id" loading={loading} pagination={{ pageSize: 15, showTotal: (total) => `${total}` }} scroll={{ x: 1200 }} />
<Table
columns={columns}
dataSource={filteredData}
rowKey="id"
loading={loading}
pagination={{ pageSize: 15, showTotal: (total) => `${total}` }}
scroll={{ x: 1200 }}
/>
<Modal title={editing ? '编辑租赁' : '新增租赁'} open={modalOpen} onOk={handleSave} onCancel={() => { setModalOpen(false); setEditing(null); }} okText="保存" width={600}>
<Modal
title={editing ? '编辑租赁' : '新增租赁'}
open={modalOpen}
onOk={handleSave}
onCancel={() => {
setModalOpen(false);
setEditing(null);
}}
okText="保存"
width={600}
>
<Form form={form} layout="vertical">
<Form.Item name="classroomId" label="教室" rules={[{ required: true }]}>
<Select
showSearch
optionFilterProp="label"
placeholder="选择教室"
options={classrooms.map(c => ({ value: c.id, label: `${c.building ? c.building + ' · ' : ''}${c.name}${c.roomType}` }))}
options={classrooms.map((c) => ({
value: c.id,
label: `${c.building ? c.building + ' · ' : ''}${c.name}${c.roomType}`,
}))}
/>
</Form.Item>
<Form.Item name="tenantId" label="租赁方" rules={[{ required: true }]}>
@@ -235,11 +352,15 @@ const ClassroomRentalsPage: React.FC = () => {
showSearch
optionFilterProp="label"
placeholder="选择租赁方"
options={tenants.map(t => ({ value: t.id, label: t.name }))}
options={tenants.map((t) => ({ value: t.id, label: t.name }))}
/>
</Form.Item>
<Form.Item name="dateRange" label="租赁起止日期" rules={[{ required: true }]}>
<DatePicker.RangePicker style={{ width: '100%' }} placeholder={['开始日期', '结束日期']} format="YYYY-MM-DD" />
<DatePicker.RangePicker
style={{ width: '100%' }}
placeholder={['开始日期', '结束日期']}
format="YYYY-MM-DD"
/>
</Form.Item>
<Form.Item name="dailyRate" label="日租金(可选)">
<InputNumber min={0} precision={2} style={{ width: '100%' }} prefix="¥" />
@@ -247,7 +368,9 @@ const ClassroomRentalsPage: React.FC = () => {
<Form.Item name="totalAmount" label="合同总额(可选)">
<InputNumber min={0} precision={2} style={{ width: '100%' }} prefix="¥" />
</Form.Item>
<Form.Item name="notes" label="备注"><Input.TextArea rows={2} /></Form.Item>
<Form.Item name="notes" label="备注">
<Input.TextArea rows={2} />
</Form.Item>
</Form>
</Modal>
</div>