任务排版 日历视图

This commit is contained in:
mmc
2026-07-20 15:53:43 +08:00
parent 68f63e520f
commit c602f08a98

View File

@@ -132,6 +132,75 @@ const WorkOrderPage = () => {
const [completeForm] = Form.useForm();
const messageApi = utils.message;
// 排班更多弹窗
const [scheduleModalVisible, setScheduleModalVisible] = useState(false);
// 排班日历选中日期,默认今天
const [scheduleDate, setScheduleDate] = useState(dayjs().format('YYYY-MM-DD'));
// 时间轴刻度
const timeTicks = ["08:00", "10:00", "12:00", "14:00", "16:00", "18:00"];
// 一天总分钟 08:00 ~ 18:00 = 600分钟
const dayTotalMin = (18 - 8) * 60;
const calcSchedulePosition = (planTime) => {
if (!planTime) return null;
const t = dayjs(planTime);
const hour = t.hour();
const minute = t.minute();
let currentMin;
// 早于8点统一按0分钟计算,贴最左侧
if (hour < 8) {
currentMin = 0;
} else if (hour >= 18) {
// 18点之后依旧不展示
return null;
} else {
currentMin = (hour - 8) * 60 + minute;
}
const leftPercent = (currentMin / dayTotalMin) * 100;
const widthPercent = 15;
return { left: `${leftPercent}%`, width: `${widthPercent}%` };
};
// 过滤所有带计划时间的工单
// 过滤【当前选中日期】且带计划时间的工单
const getScheduleOrderList = () => {
return workOrderList.filter(item => {
if (!item.planStartTime) return false;
// 匹配选中日期
const planDate = dayjs(item.planStartTime).format('YYYY-MM-DD');
return planDate === scheduleDate;
});
};
// 切换前一天
const prevScheduleDay = () => {
setScheduleDate(dayjs(scheduleDate).subtract(1, 'day').format('YYYY-MM-DD'));
};
// 切换后一天
const nextScheduleDay = () => {
setScheduleDate(dayjs(scheduleDate).add(1, 'day').format('YYYY-MM-DD'));
};
// 获取工单配色(优先级)
const getOrderScheduleStyle = (level) => {
const map = {
ERROR: { bg: "#FFF1F0", border: "#FF4D4F", color: "#FF4D4F" },
WARNING: { bg: "#FFF7E8", border: "#FA8C16", color: "#FA8C16" },
INFO: { bg: "#E8FFEA", border: "#00B42A", color: "#00B42A" },
};
return map[level] || { bg: "#E8F3FF", border: "#165DFF", color: "#165DFF" };
};
// 计算工单统计数据
const calcWorkOrderStat = (list) => {
@@ -862,39 +931,139 @@ const WorkOrderPage = () => {
</Card>
</Col>
<Col xs={24} md={15}>
<Card bordered={false} title={<Text strong style={{ fontSize: 14 }}>任务排班 / 日历视图</Text>}
extra={<Button type="link" size="small">更多 <ChevronRight size={14} /></Button>}
style={{ ...cardStyle, height: 380 }} bodyStyle={{ padding: '16px' }}>
<Card
bordered={false}
title={<Text strong style={{ fontSize: 14 }}>任务排班 / 日历视图</Text>}
extra={
<Button
type="link"
size="small"
onClick={() => setScheduleModalVisible(true)}
>
更多 <ChevronRight size={14} />
</Button>
}
style={{ ...cardStyle, height: 380 }}
bodyStyle={{ padding: '16px' }}
>
{/* 日期切换栏 */}
<div style={{ marginBottom: 16 }}>
<Space>
<Button size="small" icon={<ChevronRight style={{ transform: 'rotate(180deg)' }} size={14} />} />
<Text strong style={{ fontSize: 13 }}>2025-05-24 (今天)</Text>
<Button size="small" icon={<ChevronRight size={14} />} />
<Button size="small" icon={<ChevronRight style={{ transform: 'rotate(180deg)' }} size={14} />} onClick={prevScheduleDay} />
<Text strong style={{ fontSize: 13 }}>
{scheduleDate}
{scheduleDate === dayjs().format('YYYY-MM-DD') ? ' (今天)' : ''}
</Text>
<Button size="small" icon={<ChevronRight size={14} />} onClick={nextScheduleDay} />
</Space>
</div>
<div style={{ border: '1px solid #f0f0f0', borderRadius: 8, overflow: 'hidden' }}>
{/* 横向时间表头 */}
<div style={{ display: 'flex', background: '#f8fafc', borderBottom: '1px solid #f0f0f0' }}>
<div style={{ width: 60, padding: '10px 0', borderRight: '1px solid #f0f0f0' }}></div>
{['08:00', '10:00', '12:00', '14:00', '16:00', '18:00'].map(t => (
<div key={t} style={{ flex: 1, padding: '10px 0', textAlign: 'center', fontSize: 11, color: '#86909C', fontWeight: 500 }}>{t}</div>
{timeTicks.map(t => (
<div key={t} style={{ flex: 1, padding: '10px 0', textAlign: 'center', fontSize: 11, color: '#86909C', fontWeight: 500 }}>
{t}
</div>
))}
</div>
{['李工', '王工', '赵工', '陈工'].map((name, idx) => (
<div key={name} style={{ display: 'flex', borderBottom: idx === 3 ? 'none' : '1px solid #f0f0f0', height: 56, position: 'relative' }}>
<div style={{ width: 60, padding: '8px 0', borderRight: '1px solid #f0f0f0', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 12, fontWeight: 600, color: '#4E5969' }}>{name}</div>
<div style={{ flex: 1, position: 'relative' }}>
{idx === 0 && <div className="schedule-item" style={{ position: 'absolute', left: '5%', top: 8, width: '45%', height: 40, background: '#E8FFEA', border: '1px solid #00B42A', borderRadius: 6, padding: '4px 10px', fontSize: 10, color: '#00B42A' }}><b>INV-05 告警排查</b><br />08:30 - 12:00</div>}
{idx === 1 && <div className="schedule-item" style={{ position: 'absolute', left: '55%', top: 8, width: '25%', height: 40, background: '#E8F3FF', border: '1px solid #165DFF', borderRadius: 6, padding: '4px 10px', fontSize: 10, color: '#165DFF' }}><b>B-12 热斑复检</b><br />13:30 - 15:00</div>}
{idx === 1 && <div className="schedule-item" style={{ position: 'absolute', left: '82%', top: 8, width: '15%', height: 40, background: '#F5F8FF', border: '1px dashed #2F54EB', borderRadius: 6, padding: '4px 8px', fontSize: 10, color: '#2F54EB' }}><b>无人机巡检</b><br />16:00-18:00</div>}
{idx === 2 && <div className="schedule-item" style={{ position: 'absolute', left: '15%', top: 8, width: '35%', height: 40, background: '#F9F0FF', border: '1px solid #722ED1', borderRadius: 6, padding: '4px 10px', fontSize: 10, color: '#722ED1' }}><b>组件清洗 C-05</b><br />09:00 - 12:00</div>}
{idx === 2 && <div className="schedule-item" style={{ position: 'absolute', left: '70%', top: 8, width: '25%', height: 40, background: '#FFF7E8', border: '1px solid #F7BA1E', borderRadius: 6, padding: '4px 10px', fontSize: 10, color: '#F7BA1E' }}><b>清洗效果复检</b><br />15:00 - 17:00</div>}
{idx === 3 && <div className="schedule-item" style={{ position: 'absolute', left: '25%', top: 8, width: '30%', height: 40, background: '#FFF1F0', border: '1px solid #F53F3F', borderRadius: 6, padding: '4px 10px', fontSize: 10, color: '#F53F3F' }}><b>周界摄像头修复</b><br />10:00 - 12:30</div>}
{idx === 3 && <div className="schedule-item" style={{ position: 'absolute', left: '75%', top: 8, width: '22%', height: 40, background: '#EFFFF4', border: '1px solid #00B42A', borderRadius: 6, padding: '4px 10px', fontSize: 10, color: '#00B42A' }}><b>设备巡检</b><br />15:30 - 17:30</div>}
</div>
</div>
))}
{/* 单一行时间画布,不再区分人员 */}
<div style={{ height: 220, position: 'relative', padding: '10px 0', overflow: 'hidden' }}>
{getScheduleOrderList().length === 0 ? (
<Empty description={`${scheduleDate} 暂无设置计划执行时间的工单`} style={{ padding: "60px 0" }} />
) : (
<>
{getScheduleOrderList().map((order, index) => {
const pos = calcSchedulePosition(order.planStartTime);
if (!pos) return null;
const style = getOrderScheduleStyle(order.priorityLevel);
// 固定纵向排布,40px一行不重叠
const topPx = 10 + (index * 40);
return (
<div
key={order.id}
className="schedule-item"
onClick={() => handleRowSelect(order)}
style={{
position: 'absolute',
left: pos.left,
top: `${topPx}px`,
width: pos.width,
height: 40,
background: style.bg,
border: `1px solid ${style.border}`,
borderRadius: 6,
padding: '4px 8px',
fontSize: 10,
color: style.color,
overflow: "hidden",
cursor: "pointer",
zIndex: 2
}}
>
<b style={{ display: 'block', whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
{order.orderTitle}
</b>
<span style={{ fontSize: 9 }}>
{dayjs(order.planStartTime).format('HH:mm')}
</span>
</div>
);
})}
</>
)}
</div>
</div>
{/* 更多工单弹窗:弹窗内只展示当前选中日期工单 */}
<Modal
title={`${scheduleDate} 计划工单列表`}
open={scheduleModalVisible}
onCancel={() => setScheduleModalVisible(false)}
width={800}
footer={null}
>
<Table
dataSource={getScheduleOrderList()}
rowKey="id"
size="small"
columns={[
{ title: '工单编号', dataIndex: 'orderNo', width: 260 },
{ title: '工单标题', dataIndex: 'orderTitle', ellipsis: true },
{
title: '计划执行时间',
dataIndex: 'planStartTime',
render: t => dayjs(t).format('YYYY-MM-DD HH:mm')
},
{
title: '优先级',
dataIndex: 'priorityLevel',
render: p => (
<Tag
style={{
color: priorityConfig[p]?.color,
background: priorityConfig[p]?.bg,
border: `1px solid ${priorityConfig[p]?.color}`
}}
>
{priorityConfig[p]?.label}
</Tag>
)
},
{
title: '状态',
dataIndex: 'orderStatus',
render: s => orderStatusConfig[s]?.label
},
]}
/>
</Modal>
</Card>
</Col>
</Row>
</Col>