备份:修复前完整项目快照 2026-04-19
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Card, Typography, Button, Space, Tag, Progress, Empty, Spin, message, Row, Col, Divider } from 'antd';
|
||||
import { FileTextOutlined, CameraOutlined, ScheduleOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import dayjs from 'dayjs';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
|
||||
const { Title, Paragraph, Text } = Typography;
|
||||
|
||||
// 天气图标映射
|
||||
const WEATHER_ICONS: Record<string, string> = {
|
||||
sunny: '☀️ 晴',
|
||||
cloudy: '⛅ 多云',
|
||||
rainy: '🌧️ 雨',
|
||||
stormy: '⛈️ 雷暴',
|
||||
windy: '💨 大风',
|
||||
};
|
||||
|
||||
// 项目状态映射
|
||||
const STATUS_CONFIG: Record<string, { color: string; text: string }> = {
|
||||
pending: { color: 'default', text: '待开始' },
|
||||
active: { color: 'processing', text: '施工中' },
|
||||
completed: { color: 'success', text: '完工' },
|
||||
suspended: { color: 'warning', text: '暂停' },
|
||||
cancelled: { color: 'error', text: '已取消' },
|
||||
};
|
||||
|
||||
interface Project {
|
||||
id: number;
|
||||
project_code: string;
|
||||
name: string;
|
||||
customer_name: string;
|
||||
status: string;
|
||||
start_date: string;
|
||||
expected_end_date: string;
|
||||
contract_amount: number;
|
||||
currency: string;
|
||||
manager_name: string;
|
||||
progress_percentage: number;
|
||||
latest_log?: {
|
||||
id: number;
|
||||
log_date: string;
|
||||
weather: string;
|
||||
work_content: string;
|
||||
};
|
||||
}
|
||||
|
||||
const ConstructionList: React.FC = () => {
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuthStore();
|
||||
|
||||
useEffect(() => {
|
||||
const checkMobile = () => setIsMobile(window.innerWidth <= 768);
|
||||
checkMobile();
|
||||
window.addEventListener('resize', checkMobile);
|
||||
return () => window.removeEventListener('resize', checkMobile);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchProjects();
|
||||
}, []);
|
||||
|
||||
const fetchProjects = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await axios.get('/api/construction/my-projects');
|
||||
if (res.data.success) {
|
||||
setProjects(res.data.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取项目列表失败:', error);
|
||||
message.error('获取项目列表失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const formatCurrency = (amount: number, currency: string = 'CNY') => {
|
||||
const symbols: Record<string, string> = {
|
||||
CNY: '¥',
|
||||
USD: '$',
|
||||
LAK: '₭',
|
||||
THB: '฿',
|
||||
};
|
||||
const symbol = symbols[currency] || '¥';
|
||||
return `${symbol}${(amount || 0).toLocaleString('zh-CN', { minimumFractionDigits: 0 })}`;
|
||||
};
|
||||
|
||||
const isToday = (dateStr: string) => {
|
||||
return dayjs(dateStr).isSame(dayjs(), 'day');
|
||||
};
|
||||
|
||||
const renderProjectCard = (project: Project) => {
|
||||
const statusConfig = STATUS_CONFIG[project.status] || STATUS_CONFIG.pending;
|
||||
const hasTodayLog = project.latest_log && isToday(project.latest_log.log_date);
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={project.id}
|
||||
style={{
|
||||
marginBottom: isMobile ? 12 : 16,
|
||||
borderRadius: 12,
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
|
||||
}}
|
||||
styles={{ body: { padding: isMobile ? 16 : 20 } }}
|
||||
>
|
||||
{/* 项目头部 */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 12 }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
|
||||
<span style={{ fontSize: 20 }}>🎯</span>
|
||||
<Text strong style={{ fontSize: isMobile ? 15 : 16 }}>{project.name}</Text>
|
||||
</div>
|
||||
<Text type="secondary" style={{ fontSize: 13 }}>
|
||||
客户: {project.customer_name || '未指定'}
|
||||
</Text>
|
||||
</div>
|
||||
<Tag color={statusConfig.color} style={{ marginLeft: 8 }}>
|
||||
{statusConfig.text}
|
||||
</Tag>
|
||||
</div>
|
||||
|
||||
{/* 进度条 */}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 4 }}>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>施工进度</Text>
|
||||
<Text strong style={{ fontSize: 12 }}>{Math.round((project.progress_percentage || 0))}%</Text>
|
||||
</div>
|
||||
<Progress
|
||||
percent={Math.round((project.progress_percentage || 0))}
|
||||
showInfo={false}
|
||||
strokeColor={{
|
||||
'0%': '#108ee9',
|
||||
'100%': '#87d068',
|
||||
}}
|
||||
trailColor="#f0f0f0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 最新日志状态 */}
|
||||
{project.status === 'active' && (
|
||||
<div style={{
|
||||
padding: '8px 12px',
|
||||
background: hasTodayLog ? '#f6ffed' : '#fff7e6',
|
||||
borderRadius: 8,
|
||||
marginBottom: 12,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8
|
||||
}}>
|
||||
{hasTodayLog ? (
|
||||
<>
|
||||
<span>✅</span>
|
||||
<Text style={{ fontSize: 13 }}>
|
||||
今日日志: {project.latest_log?.work_content?.substring(0, 30)}...
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>⚠️</span>
|
||||
<Text type="warning" style={{ fontSize: 13 }}>今日日志: 未填写</Text>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Divider style={{ margin: '12px 0' }} />
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<Row gutter={[8, 8]}>
|
||||
<Col xs={24} sm={8}>
|
||||
<Button
|
||||
type={project.status === 'active' && !hasTodayLog ? 'primary' : 'default'}
|
||||
icon={<FileTextOutlined />}
|
||||
onClick={() => navigate(`/construction/${project.id}/logs`)}
|
||||
block
|
||||
size={isMobile ? 'large' : 'middle'}
|
||||
style={{ borderRadius: 8 }}
|
||||
>
|
||||
{project.status === 'active' && !hasTodayLog ? '📝 写今日日志' : '📝 施工日志'}
|
||||
</Button>
|
||||
</Col>
|
||||
<Col xs={24} sm={8}>
|
||||
<Button
|
||||
icon={<CameraOutlined />}
|
||||
onClick={() => navigate(`/construction/${project.id}/logs`)}
|
||||
block
|
||||
size={isMobile ? 'large' : 'middle'}
|
||||
style={{ borderRadius: 8 }}
|
||||
>
|
||||
📷 上传照片
|
||||
</Button>
|
||||
</Col>
|
||||
<Col xs={24} sm={8}>
|
||||
<Button
|
||||
icon={<ScheduleOutlined />}
|
||||
onClick={() => navigate(`/construction/${project.id}/milestones`)}
|
||||
block
|
||||
size={isMobile ? 'large' : 'middle'}
|
||||
style={{ borderRadius: 8 }}
|
||||
>
|
||||
📋 节点进度
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
padding: isMobile ? 12 : 24,
|
||||
maxWidth: 1200,
|
||||
margin: '0 auto'
|
||||
}}>
|
||||
{/* 页面标题 */}
|
||||
<div style={{ marginBottom: isMobile ? 16 : 24 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Title level={isMobile ? 4 : 3} style={{ marginBottom: 0 }}>施工管理</Title>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={fetchProjects}
|
||||
loading={loading}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
<Paragraph type="secondary" style={{ marginTop: 8, marginBottom: 0 }}>
|
||||
查看和管理您的施工项目
|
||||
</Paragraph>
|
||||
</div>
|
||||
|
||||
{/* 项目列表 */}
|
||||
{loading ? (
|
||||
<div style={{ textAlign: 'center', padding: 60 }}>
|
||||
<Spin size="large" />
|
||||
<Paragraph type="secondary" style={{ marginTop: 16 }}>加载中...</Paragraph>
|
||||
</div>
|
||||
) : projects.length === 0 ? (
|
||||
<Card style={{ borderRadius: 12 }}>
|
||||
<Empty
|
||||
description="暂无施工项目"
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
>
|
||||
<Text type="secondary">请联系管理员为您分配施工项目</Text>
|
||||
</Empty>
|
||||
</Card>
|
||||
) : (
|
||||
<div>
|
||||
<Text type="secondary" style={{ marginBottom: 12, display: 'block' }}>
|
||||
我的施工项目 ({projects.length})
|
||||
</Text>
|
||||
{projects.map(project => renderProjectCard(project))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConstructionList;
|
||||
Reference in New Issue
Block a user