forked from wangziqi/gongxue-base
chore: commit oxfmt formatting changes and verify artifacts
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user