任务排版 日历视图
This commit is contained in:
@@ -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>
|
||||
))}
|
||||
</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>
|
||||
{timeTicks.map(t => (
|
||||
<div key={t} style={{ flex: 1, padding: '10px 0', textAlign: 'center', fontSize: 11, color: '#86909C', fontWeight: 500 }}>
|
||||
{t}
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user