大改造:施工进度系统+模板管理+物流收货+分包评分
- 新增5张数据库表:logistics_records, receiving_confirms, project_type_templates, project_phases, subcontractor_events - 修改3张表:projects(增加type_template_id/current_phase/phase_progress), purchase_orders(增加expected_delivery_date/receiving_status), subcontractors(增加score/initial_score) - 插入5套预设工程模板(配电安装/架空线路/电缆敷设/变电站/小型工程) - 后端新增:模板管理API、项目阶段管理API、收货确认API、分包商事件API - 后端修改:项目创建/预算签约支持模板选择,自动初始化阶段 - 前端新增:工程模板设计器(后台管理)、施工总览页、施工进度页(手机友好) - 前端修改:项目详情页重构为7个Tab,增加施工进度预览条和进入施工管理按钮 - 菜单更新:施工管理增加施工总览子菜单,后台管理增加工程模板管理
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Card, Button, Progress, Tag, List, Spin, message, Empty, Space, Typography } from 'antd';
|
||||
import { ArrowLeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import apiClient from '../../utils/request';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
interface ProjectPhase {
|
||||
id: number;
|
||||
phase_name: string;
|
||||
phase_order: number;
|
||||
phase_type: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
interface Project {
|
||||
id: number;
|
||||
name: string;
|
||||
status: string;
|
||||
current_phase: string;
|
||||
phase_progress: number;
|
||||
contract_amount: number;
|
||||
phases: ProjectPhase[];
|
||||
}
|
||||
|
||||
const ConstructionOverview: React.FC = () => {
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => { fetchProjects(); }, []);
|
||||
|
||||
const fetchProjects = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await apiClient.get('/construction/my-projects');
|
||||
if (res.data.success) {
|
||||
const projectsData = res.data.data || [];
|
||||
const enriched = await Promise.all(projectsData.map(async (p: Project) => {
|
||||
try {
|
||||
const phaseRes = await apiClient.get(`/projects/${p.id}/phases`);
|
||||
return { ...p, phases: phaseRes.data.data || [] };
|
||||
} catch { return { ...p, phases: [] }; }
|
||||
}));
|
||||
setProjects(enriched);
|
||||
}
|
||||
} catch (e) { message.error('获取项目列表失败'); }
|
||||
finally { setLoading(false); }
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
const map: Record<string, string> = { active: 'green', planning: 'blue', completed: 'default', suspended: 'orange' };
|
||||
return map[status] || 'default';
|
||||
};
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
const map: Record<string, string> = { active: '施工中', planning: '待开工', completed: '已完工', suspended: '暂停' };
|
||||
return map[status] || status;
|
||||
};
|
||||
|
||||
const activeProjects = projects.filter(p => p.status !== 'completed');
|
||||
const completedProjects = projects.filter(p => p.status === 'completed');
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<Card title={<Title level={4} style={{ margin: 0 }}>施工总览</Title>}>
|
||||
<Spin spinning={loading}>
|
||||
{activeProjects.length === 0 && completedProjects.length === 0 ? (
|
||||
<Empty description="暂无施工项目" />
|
||||
) : (
|
||||
<>
|
||||
{activeProjects.length > 0 && (
|
||||
<>
|
||||
<Text strong style={{ fontSize: 16 }}>施工中项目:{activeProjects.length}个</Text>
|
||||
<List
|
||||
style={{ marginTop: 16 }}
|
||||
dataSource={activeProjects}
|
||||
renderItem={(project) => (
|
||||
<List.Item
|
||||
actions={[<Button type="primary" icon={<RightOutlined />} onClick={() => navigate(`/construction/progress/${project.id}`)}>进入</Button>]}
|
||||
>
|
||||
<List.Item.Meta
|
||||
title={<Space><Text strong>{project.name}</Text><Tag color={getStatusColor(project.status)}>{getStatusText(project.status)}</Tag></Space>}
|
||||
description={
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<div style={{ marginBottom: 4 }}>
|
||||
<Text type="secondary">当前阶段:{project.current_phase || '未设置'}</Text>
|
||||
{project.phases.length > 0 && <Text type="secondary" style={{ marginLeft: 16 }}>({project.phases.filter(p => p.status === 'completed').length}/{project.phases.length})</Text>}
|
||||
</div>
|
||||
<Progress percent={project.phase_progress || 0} size="small" strokeColor="#1890ff" />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{completedProjects.length > 0 && (
|
||||
<>
|
||||
<Text strong style={{ fontSize: 16, marginTop: 24, display: 'block' }}>已完工项目:{completedProjects.length}个</Text>
|
||||
<List
|
||||
style={{ marginTop: 16 }}
|
||||
dataSource={completedProjects}
|
||||
renderItem={(project) => (
|
||||
<List.Item
|
||||
actions={[<Button icon={<RightOutlined />} onClick={() => navigate(`/construction/progress/${project.id}`)}>查看</Button>]}
|
||||
>
|
||||
<List.Item.Meta
|
||||
title={<Space><Text>{project.name}</Text><Tag>已完工</Tag></Space>}
|
||||
description={<Progress percent={100} size="small" />}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Spin>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConstructionOverview;
|
||||
Reference in New Issue
Block a user