备份:大改造前的完整版本 - 修复合同细节/付款节点/文件上传/施工管理/项目保存等BUG
This commit is contained in:
@@ -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 />}
|
||||
|
||||
Reference in New Issue
Block a user