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 { PlusOutlined, UploadOutlined, DeleteOutlined, FileTextOutlined } from '@ant-design/icons'; import dayjs, { Dayjs } from 'dayjs'; import api from '../../api'; import PermissionButton from '../../components/PermissionButton'; const ClassroomRentalsPage: React.FC = () => { const [data, setData] = useState([]); const [classrooms, setClassrooms] = useState([]); const [tenants, setTenants] = useState([]); const [loading, setLoading] = useState(false); const [modalOpen, setModalOpen] = useState(false); const [editing, setEditing] = useState(null); const [form] = Form.useForm(); const [filterMonth, setFilterMonth] = useState(null); const [searchText, setSearchText] = useState(''); const filteredData = useMemo(() => { if (!searchText) return data; const s = searchText.toLowerCase(); return data.filter((r: any) => { const matchClassroom = r.classroom?.name?.toLowerCase().includes(s); const matchTenant = r.tenant?.name?.toLowerCase().includes(s); return matchClassroom || matchTenant; }); }, [data, searchText]); const fetchData = async () => { setLoading(true); try { const params: any = {}; if (filterMonth) params.month = filterMonth.format('YYYY-MM'); const res: any = await api.get('/classroom-rentals', { params }); setData(res); } catch (e) { console.error(e); } setLoading(false); }; const fetchMeta = async () => { try { const [cr, tn]: any = await Promise.all([api.get('/classrooms'), api.get('/tenants')]); setClassrooms(cr); setTenants(tn); } catch (e) { console.error(e); } }; useEffect(() => { fetchMeta(); }, []); useEffect(() => { fetchData(); }, [filterMonth]); const handleSave = async () => { const values = await form.validateFields(); const payload = { classroomId: values.classroomId, tenantId: values.tenantId, startDate: values.dateRange[0].format('YYYY-MM-DD'), endDate: values.dateRange[1].format('YYYY-MM-DD'), dailyRate: values.dailyRate, totalAmount: values.totalAmount, notes: values.notes, }; try { if (editing) { await api.put(`/classroom-rentals/${editing.id}`, payload); message.success('更新成功'); } else { await api.post('/classroom-rentals', payload); message.success('创建成功'); } setModalOpen(false); form.resetFields(); setEditing(null); fetchData(); } catch (e: any) { if (e?.conflicts?.length) { const list = e.conflicts .map((c: any) => `${c.tenantName}(${c.startDate}~${c.endDate})`) .join('、'); message.error(`时间段冲突:${list}`); } else { message.error(e?.message || '操作失败'); } } }; const handleDelete = async (id: number) => { try { await api.delete(`/classroom-rentals/${id}`); message.success('已删除'); fetchData(); } 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 token = localStorage.getItem('token'); fetch(`${baseURL}/classroom-rentals/${id}/contract`, { headers: { Authorization: `Bearer ${token}` }, }) .then((res) => { if (!res.ok) throw new Error('下载失败'); return res.blob(); }) .then((blob) => { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename || `contract-${id}.pdf`; a.click(); URL.revokeObjectURL(url); }) .catch(() => message.error('下载失败(可能文件已丢失)')); }; const handleDeleteContract = async (id: number) => { try { await api.delete(`/classroom-rentals/${id}/contract`); message.success('合同已删除'); fetchData(); } catch (e: any) { message.error(e?.message || '删除失败'); } }; const openEdit = (record: any) => { setEditing(record); form.setFieldsValue({ classroomId: record.classroomId, tenantId: record.tenantId, dateRange: [dayjs(record.startDate), dayjs(record.endDate)], dailyRate: record.dailyRate ? Number(record.dailyRate) : undefined, totalAmount: record.totalAmount ? Number(record.totalAmount) : undefined, notes: record.notes, }); setModalOpen(true); }; const columns = [ { title: '教室', dataIndex: 'classroom', render: (c: any) => c ? ( {c.building ? `${c.building} · ` : ''} {c.name} ) : ( '-' ), }, { title: '租赁方', dataIndex: 'tenant', render: (t: any) => t ? ( {t.name} ) : ( '-' ), }, { title: '开始日期', dataIndex: 'startDate' }, { title: '结束日期', dataIndex: 'endDate' }, { 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: 'contractPath', render: (v: string, r: any) => v ? ( handleDeleteContract(r.id)}> ), }, { title: '操作', width: 150, render: (_: any, record: any) => ( openEdit(record)}> 编辑 handleDelete(record.id)} > 删除 ), }, ]; return (
setSearchText(v)} onChange={(e) => { if (!e.target.value) setSearchText(''); }} /> } onClick={() => { setEditing(null); form.resetFields(); setModalOpen(true); }} > 新增租赁
`共 ${total} 条` }} scroll={{ x: 1200 }} /> { setModalOpen(false); setEditing(null); }} okText="保存" width={600} >
({ value: t.id, label: t.name }))} />
); }; export default ClassroomRentalsPage;