备份:大改造前的完整版本 - 修复合同细节/付款节点/文件上传/施工管理/项目保存等BUG

This commit is contained in:
root
2026-05-15 12:02:24 +08:00
parent 1cad1e438d
commit fad28741bd
6157 changed files with 5147 additions and 877912 deletions
+110 -15
View File
@@ -1,11 +1,12 @@
import React, { useState, useEffect } from 'react';
import { Card, Typography, Button, Form, Input, Select, DatePicker, InputNumber, Radio, Space, message, Divider, Row, Col } from 'antd';
import React, { useState, useEffect, useCallback } from 'react';
import { Card, Typography, Button, Form, Input, Select, DatePicker, InputNumber, Radio, Space, message, Divider, Row, Col, Modal } from 'antd';
import { SaveOutlined, ArrowLeftOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import apiClient from '../../utils/request';
import dayjs from 'dayjs';
import FileUpload from '../../components/FileUpload';
import { useAuthStore } from '../../store/authStore';
import useFormDraft from '../../hooks/useFormDraft';
const { Title, Paragraph } = Typography;
const { Option } = Select;
@@ -31,9 +32,71 @@ const BudgetProjectCreate: React.FC = () => {
const [attachments, setAttachments] = useState<string[]>([]);
const [surveyPhotos, setSurveyPhotos] = useState<string[]>([]);
const navigate = useNavigate();
const { user: currentUser } = useAuthStore();
const isAdmin = currentUser?.role === 'admin';
// 表单草稿保护
const { save: saveDraft, restore: restoreDraft, clear: clearDraft, hasDraft } = useFormDraft({
form,
storageKey: 'budget_project_create',
onRestore: (data) => {
if (data.attachments) setAttachments(data.attachments);
if (data.surveyPhotos) setSurveyPhotos(data.surveyPhotos);
},
});
// 保存草稿(包含外部状态)
const handleFormChange = useCallback(() => {
saveDraft({ attachments, surveyPhotos });
}, [saveDraft, attachments, surveyPhotos]);
// 浏览器刷新/关闭拦截
useEffect(() => {
const handler = (e: BeforeUnloadEvent) => {
if (form.isFieldsTouched()) {
e.preventDefault();
}
};
window.addEventListener('beforeunload', handler);
return () => window.removeEventListener('beforeunload', handler);
}, [form]);
// 移动端:页面切到后台时保存草稿
useEffect(() => {
const handler = () => {
if (document.visibilityState === 'hidden' && form.isFieldsTouched()) {
saveDraft({ attachments, surveyPhotos });
}
};
document.addEventListener('visibilitychange', handler);
const pageHideHandler = () => {
if (form.isFieldsTouched()) saveDraft({ attachments, surveyPhotos });
};
window.addEventListener('pagehide', pageHideHandler);
return () => {
document.removeEventListener('visibilitychange', handler);
window.removeEventListener('pagehide', pageHideHandler);
};
}, [form, saveDraft, attachments, surveyPhotos]);
// 页面加载时检查草稿
useEffect(() => {
if (hasDraft()) {
Modal.confirm({
title: '发现未完成的草稿',
content: '检测到上次未提交的商谈项目,是否恢复?',
okText: '恢复草稿',
cancelText: '重新填写',
onOk: () => {
restoreDraft();
},
onCancel: () => {
clearDraft();
},
});
}
}, []);
// 检查权限,如果不是管理员,重定向到列表页面
useEffect(() => {
@@ -62,7 +125,7 @@ const BudgetProjectCreate: React.FC = () => {
const fetchCustomers = async () => {
try {
const res = await axios.get('/api/customers');
const res = await apiClient.get('/customers');
if (res.data.success) setCustomers(res.data.data);
} catch (error) {
console.error('获取客户列表失败:', error);
@@ -71,7 +134,7 @@ const BudgetProjectCreate: React.FC = () => {
const fetchUsers = async () => {
try {
const res = await axios.get('/api/users');
const res = await apiClient.get('/users');
if (res.data.success) setUsers(res.data.data);
} catch (error) {
console.error('获取用户列表失败:', error);
@@ -91,13 +154,14 @@ const BudgetProjectCreate: React.FC = () => {
status: 'negotiating',
};
const res = await axios.post('/api/budget-projects', projectData, {
const res = await apiClient.post('/budget-projects', projectData, {
headers: {
'x-user-role': 'admin' // 创建预算项目需要管理员权限
'x-user-role': 'admin'
}
});
if (res.data.success) {
message.success('创建成功');
clearDraft();
navigate('/budget-projects');
}
} catch (error: any) {
@@ -117,7 +181,22 @@ const BudgetProjectCreate: React.FC = () => {
<div style={{ display: 'flex', alignItems: 'center', gap: 16, marginBottom: 8 }}>
<Button
icon={<ArrowLeftOutlined />}
onClick={() => navigate('/budget-projects')}
onClick={() => {
if (form.isFieldsTouched()) {
Modal.confirm({
title: '确认离开',
content: '表单数据尚未保存,离开后可通过草稿恢复。确定离开吗?',
okText: '离开',
cancelText: '继续编辑',
onOk: () => {
saveDraft({ attachments, surveyPhotos });
navigate('/budget-projects');
},
});
} else {
navigate('/budget-projects');
}
}}
>
</Button>
@@ -127,10 +206,11 @@ const BudgetProjectCreate: React.FC = () => {
</div>
<Card>
<Form
form={form}
<Form
form={form}
layout="vertical"
initialValues={{
onValuesChange={handleFormChange}
initialValues={{
intermediary_fee_type: 'fixed',
survey_date: dayjs(), // 勘察日期默认为当天
attachments: [],
@@ -256,7 +336,7 @@ const BudgetProjectCreate: React.FC = () => {
<Form.Item label="附件上传">
<FileUpload
value={attachments}
onChange={setAttachments}
onChange={(urls) => { setAttachments(urls); saveDraft({ attachments: urls, surveyPhotos }); }}
accept=".pdf,.doc,.docx,.jpg,.jpeg,.png,.xlsx,.xls"
/>
</Form.Item>
@@ -265,7 +345,7 @@ const BudgetProjectCreate: React.FC = () => {
<Form.Item label="勘察照片">
<FileUpload
value={surveyPhotos}
onChange={setSurveyPhotos}
onChange={(urls) => { setSurveyPhotos(urls); saveDraft({ attachments, surveyPhotos: urls }); }}
accept="image/*"
/>
</Form.Item>
@@ -275,7 +355,22 @@ const BudgetProjectCreate: React.FC = () => {
{/* 提交按钮 */}
<div style={{ marginTop: 24, textAlign: 'right' }}>
<Space>
<Button onClick={() => navigate('/budget-projects')}></Button>
<Button onClick={() => {
if (form.isFieldsTouched()) {
Modal.confirm({
title: '确认离开',
content: '表单数据尚未保存,离开后可通过草稿恢复。确定离开吗?',
okText: '离开',
cancelText: '继续编辑',
onOk: () => {
saveDraft({ attachments, surveyPhotos });
navigate('/budget-projects');
},
});
} else {
navigate('/budget-projects');
}
}}></Button>
<Button
type="primary"
icon={<SaveOutlined />}