|
|
|
|
@ -1,12 +1,26 @@
|
|
|
|
|
import React, { useState } from 'react';
|
|
|
|
|
import { Button } from 'antd';
|
|
|
|
|
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
|
|
|
|
|
import { Button, DatePicker, Select, InputNumber, Table, Tag, Avatar } from 'antd';
|
|
|
|
|
import {
|
|
|
|
|
CalendarOutlined,
|
|
|
|
|
TeamOutlined,
|
|
|
|
|
UserOutlined,
|
|
|
|
|
PlusOutlined,
|
|
|
|
|
EditOutlined,
|
|
|
|
|
DeleteOutlined
|
|
|
|
|
} from '@ant-design/icons';
|
|
|
|
|
import moment from 'moment';
|
|
|
|
|
import styles from './DutySchedule.less';
|
|
|
|
|
|
|
|
|
|
const { RangePicker } = DatePicker;
|
|
|
|
|
const { Option } = Select;
|
|
|
|
|
|
|
|
|
|
const DutySchedule = () => {
|
|
|
|
|
// 当前周的起始日期
|
|
|
|
|
const [currentWeekStart, setCurrentWeekStart] = useState(moment().startOf('week'));
|
|
|
|
|
const [selectedRange, setSelectedRange] = useState([moment(), moment()]);
|
|
|
|
|
const [shiftType, setShiftType] = useState('早班');
|
|
|
|
|
const [personCount, setPersonCount] = useState(2);
|
|
|
|
|
const [specialty, setSpecialty] = useState('无特殊要求');
|
|
|
|
|
|
|
|
|
|
// 生成当前周的日期数组
|
|
|
|
|
const getWeekDates = (startDate) => {
|
|
|
|
|
@ -109,29 +123,142 @@ const DutySchedule = () => {
|
|
|
|
|
const cellData = getCellData(date, shift);
|
|
|
|
|
if (cellData.length === 0) return '';
|
|
|
|
|
|
|
|
|
|
// 如果有多个人,取第一个人的状态
|
|
|
|
|
const status = cellData[0].status;
|
|
|
|
|
|
|
|
|
|
// 检查是否所有人状态相同
|
|
|
|
|
const allSameStatus = cellData.every(item => item.status === status);
|
|
|
|
|
|
|
|
|
|
if (allSameStatus) {
|
|
|
|
|
return styles[`bg_${status}`];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果状态不同,返回混合状态类名
|
|
|
|
|
return styles.bg_mixed;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 底部表格数据
|
|
|
|
|
const tableColumns = [
|
|
|
|
|
{
|
|
|
|
|
title: '日期',
|
|
|
|
|
dataIndex: 'date',
|
|
|
|
|
key: 'date',
|
|
|
|
|
width: 120,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '班次',
|
|
|
|
|
dataIndex: 'shift',
|
|
|
|
|
key: 'shift',
|
|
|
|
|
width: 150,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '值班人员',
|
|
|
|
|
dataIndex: 'person',
|
|
|
|
|
key: 'person',
|
|
|
|
|
width: 100,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '职位',
|
|
|
|
|
dataIndex: 'position',
|
|
|
|
|
key: 'position',
|
|
|
|
|
width: 120,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '专业技能',
|
|
|
|
|
dataIndex: 'skills',
|
|
|
|
|
key: 'skills',
|
|
|
|
|
width: 150,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '岗位类型',
|
|
|
|
|
dataIndex: 'type',
|
|
|
|
|
key: 'type',
|
|
|
|
|
width: 100,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '联系方式',
|
|
|
|
|
dataIndex: 'contact',
|
|
|
|
|
key: 'contact',
|
|
|
|
|
width: 120,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '状态',
|
|
|
|
|
dataIndex: 'status',
|
|
|
|
|
key: 'status',
|
|
|
|
|
width: 80,
|
|
|
|
|
render: (status) => {
|
|
|
|
|
const colorMap = {
|
|
|
|
|
'请假': 'error',
|
|
|
|
|
'待岗': 'warning',
|
|
|
|
|
'在岗': 'success'
|
|
|
|
|
};
|
|
|
|
|
return <Tag color={colorMap[status]}>{status}</Tag>;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '操作',
|
|
|
|
|
key: 'action',
|
|
|
|
|
width: 150,
|
|
|
|
|
render: (_, record) => (
|
|
|
|
|
<div className={styles.tableActions}>
|
|
|
|
|
<Button type="link" size="small">编辑</Button>
|
|
|
|
|
<Button type="link" size="small" danger>删除</Button>
|
|
|
|
|
</div>
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const tableData = [
|
|
|
|
|
{
|
|
|
|
|
key: '1',
|
|
|
|
|
date: '2025-10-13',
|
|
|
|
|
shift: '早班 (08:00-16:00)',
|
|
|
|
|
person: '钱居西',
|
|
|
|
|
position: '应急部门专家',
|
|
|
|
|
skills: '网格安全、系统运维',
|
|
|
|
|
type: '常规岗',
|
|
|
|
|
contact: '15086756708',
|
|
|
|
|
status: '请假'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: '2',
|
|
|
|
|
date: '2025-10-12',
|
|
|
|
|
shift: '中班 (16:00-24:00)',
|
|
|
|
|
person: '冯俊',
|
|
|
|
|
position: '数据分析师',
|
|
|
|
|
skills: '数据分析、风险评估',
|
|
|
|
|
type: '专业岗',
|
|
|
|
|
contact: '15070198237',
|
|
|
|
|
status: '待岗'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: '3',
|
|
|
|
|
date: '2025-10-10',
|
|
|
|
|
shift: '晚班 (00:00-08:00)',
|
|
|
|
|
person: '何乐',
|
|
|
|
|
position: '系统管理员',
|
|
|
|
|
skills: '服务器管理、网络管理',
|
|
|
|
|
type: '常规岗',
|
|
|
|
|
contact: '13726421306',
|
|
|
|
|
status: '在岗'
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// 可用值班人员数据
|
|
|
|
|
const availablePersons = [
|
|
|
|
|
{ id: 1, name: '李华', dept: '信息安全', skills: '网络运维' },
|
|
|
|
|
{ id: 2, name: '王方', dept: '运营部', skills: '网络运维' },
|
|
|
|
|
{ id: 3, name: '孙敏', dept: '系统管理', skills: '由信息部' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className={styles.container}>
|
|
|
|
|
{/* 页面标题 */}
|
|
|
|
|
<div className={styles.header}>
|
|
|
|
|
<div className={styles.pageContainer}>
|
|
|
|
|
{/* 顶部区域 */}
|
|
|
|
|
<div className={styles.topSection}>
|
|
|
|
|
{/* 左侧:排班表 */}
|
|
|
|
|
<div className={styles.scheduleSection}>
|
|
|
|
|
<div className={styles.sectionHeader}>
|
|
|
|
|
<div className={styles.titleBar}></div>
|
|
|
|
|
<h2 className={styles.title}>值班排班表</h2>
|
|
|
|
|
<h2 className={styles.sectionTitle}>值班排班表</h2>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 顶部控制栏 */}
|
|
|
|
|
{/* 控制栏 */}
|
|
|
|
|
<div className={styles.toolbar}>
|
|
|
|
|
<div className={styles.toolbarLeft}>
|
|
|
|
|
<div className={styles.legend}>
|
|
|
|
|
@ -156,13 +283,13 @@ const DutySchedule = () => {
|
|
|
|
|
|
|
|
|
|
<div className={styles.toolbarRight}>
|
|
|
|
|
<span className={styles.dateRange}>
|
|
|
|
|
{currentWeekStart.format('YYYY-MM-DD')} 至 {moment(currentWeekStart).add(6, 'days').format('YYYY-MM-DD')}
|
|
|
|
|
{currentWeekStart.format('YYYY-M-D')} 至 {moment(currentWeekStart).add(6, 'days').format('YYYY-M-D')}
|
|
|
|
|
</span>
|
|
|
|
|
<div className={styles.weekNav}>
|
|
|
|
|
<Button onClick={handlePrevWeek} size="small" className={styles.navButton}>
|
|
|
|
|
上周
|
|
|
|
|
</Button>
|
|
|
|
|
<Button onClick={handleThisWeek} size="small" type="primary" className={styles.navButtonActive}>
|
|
|
|
|
<Button onClick={handleThisWeek} size="small" className={styles.navButtonActive}>
|
|
|
|
|
本周
|
|
|
|
|
</Button>
|
|
|
|
|
<Button onClick={handleNextWeek} size="small" className={styles.navButton}>
|
|
|
|
|
@ -178,34 +305,14 @@ const DutySchedule = () => {
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th className={styles.shiftHeader}>日期/班次</th>
|
|
|
|
|
<th className={styles.dayHeader}>
|
|
|
|
|
<div className={styles.dayName}>周一</div>
|
|
|
|
|
<div className={styles.dayDate}>{weekDates[0].format('MM/DD')}</div>
|
|
|
|
|
</th>
|
|
|
|
|
<th className={styles.dayHeader}>
|
|
|
|
|
<div className={styles.dayName}>周二</div>
|
|
|
|
|
<div className={styles.dayDate}>{weekDates[1].format('MM/DD')}</div>
|
|
|
|
|
</th>
|
|
|
|
|
<th className={styles.dayHeader}>
|
|
|
|
|
<div className={styles.dayName}>周三</div>
|
|
|
|
|
<div className={styles.dayDate}>{weekDates[2].format('MM/DD')}</div>
|
|
|
|
|
</th>
|
|
|
|
|
<th className={styles.dayHeader}>
|
|
|
|
|
<div className={styles.dayName}>周四</div>
|
|
|
|
|
<div className={styles.dayDate}>{weekDates[3].format('MM/DD')}</div>
|
|
|
|
|
</th>
|
|
|
|
|
<th className={styles.dayHeader}>
|
|
|
|
|
<div className={styles.dayName}>周五</div>
|
|
|
|
|
<div className={styles.dayDate}>{weekDates[4].format('MM/DD')}</div>
|
|
|
|
|
</th>
|
|
|
|
|
<th className={styles.dayHeader}>
|
|
|
|
|
<div className={styles.dayName}>周六</div>
|
|
|
|
|
<div className={styles.dayDate}>{weekDates[5].format('MM/DD')}</div>
|
|
|
|
|
</th>
|
|
|
|
|
<th className={styles.dayHeader}>
|
|
|
|
|
<div className={styles.dayName}>周日</div>
|
|
|
|
|
<div className={styles.dayDate}>{weekDates[6].format('MM/DD')}</div>
|
|
|
|
|
{weekDates.map((date, index) => (
|
|
|
|
|
<th key={index} className={styles.dayHeader}>
|
|
|
|
|
<div className={styles.dayName}>
|
|
|
|
|
{['周一', '周二', '周三', '周四', '周五', '周六', '周日'][index]}
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles.dayDate}>{date.format('M/D')}</div>
|
|
|
|
|
</th>
|
|
|
|
|
))}
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
@ -251,6 +358,166 @@ const DutySchedule = () => {
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 右侧区域 */}
|
|
|
|
|
<div className={styles.rightSection}>
|
|
|
|
|
{/* 统计卡片 */}
|
|
|
|
|
<div className={styles.statsCards}>
|
|
|
|
|
<div className={styles.statCard}>
|
|
|
|
|
<div className={styles.statValue}>24</div>
|
|
|
|
|
<div className={styles.statLabel}>值班人员</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles.statCard}>
|
|
|
|
|
<div className={styles.statValue}>156</div>
|
|
|
|
|
<div className={styles.statLabel}>本月班期</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles.statCard}>
|
|
|
|
|
<div className={styles.statValue}>12</div>
|
|
|
|
|
<div className={styles.statLabel}>待派人</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles.statCard}>
|
|
|
|
|
<div className={styles.statValue}>11</div>
|
|
|
|
|
<div className={styles.statLabel}>调班申请</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 排班数设置 */}
|
|
|
|
|
<div className={styles.settingPanel}>
|
|
|
|
|
<div className={styles.panelHeader}>
|
|
|
|
|
<div className={styles.titleBar}></div>
|
|
|
|
|
<h3 className={styles.panelTitle}>排班数设置</h3>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={styles.settingForm}>
|
|
|
|
|
<div className={styles.formItem}>
|
|
|
|
|
<label className={styles.formLabel}>选择排班</label>
|
|
|
|
|
<RangePicker
|
|
|
|
|
className={styles.formInput}
|
|
|
|
|
placeholder={['开始日期', '结束日期']}
|
|
|
|
|
format="YYYY/MM/DD"
|
|
|
|
|
suffixIcon={<CalendarOutlined />}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={styles.formItem}>
|
|
|
|
|
<label className={styles.formLabel}>日期</label>
|
|
|
|
|
<RangePicker
|
|
|
|
|
className={styles.formInput}
|
|
|
|
|
placeholder={['2025/10/16', '2025/10/16']}
|
|
|
|
|
format="YYYY/MM/DD"
|
|
|
|
|
suffixIcon={<CalendarOutlined />}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={styles.formItem}>
|
|
|
|
|
<label className={styles.formLabel}>每日班次</label>
|
|
|
|
|
<Select
|
|
|
|
|
className={styles.formInput}
|
|
|
|
|
value={shiftType}
|
|
|
|
|
onChange={setShiftType}
|
|
|
|
|
>
|
|
|
|
|
<Option value="早班">早班 (8:00-16:00)</Option>
|
|
|
|
|
<Option value="中班">中班 (16:00-24:00)</Option>
|
|
|
|
|
<Option value="晚班">晚班 (00:00-08:00)</Option>
|
|
|
|
|
</Select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={styles.formItem}>
|
|
|
|
|
<label className={styles.formLabel}>每日人数</label>
|
|
|
|
|
<InputNumber
|
|
|
|
|
className={styles.formInput}
|
|
|
|
|
value={personCount}
|
|
|
|
|
onChange={setPersonCount}
|
|
|
|
|
min={1}
|
|
|
|
|
max={10}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={styles.formItem}>
|
|
|
|
|
<label className={styles.formLabel}>专业要求</label>
|
|
|
|
|
<Select
|
|
|
|
|
className={styles.formInput}
|
|
|
|
|
value={specialty}
|
|
|
|
|
onChange={setSpecialty}
|
|
|
|
|
>
|
|
|
|
|
<Option value="无特殊要求">无特殊要求</Option>
|
|
|
|
|
<Option value="网络运维">网络运维</Option>
|
|
|
|
|
<Option value="系统管理">系统管理</Option>
|
|
|
|
|
</Select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={styles.formActions}>
|
|
|
|
|
<Button type="primary" icon={<CalendarOutlined />} block>
|
|
|
|
|
生成排班计划
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 可用值班人员 */}
|
|
|
|
|
<div className={styles.availablePersons}>
|
|
|
|
|
<div className={styles.personsHeader}>
|
|
|
|
|
<h4 className={styles.personsTitle}>可用值班人员</h4>
|
|
|
|
|
<Button type="primary" size="small" icon={<PlusOutlined />}>
|
|
|
|
|
添加
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={styles.personsList}>
|
|
|
|
|
{availablePersons.map(person => (
|
|
|
|
|
<div key={person.id} className={styles.personCard}>
|
|
|
|
|
<Avatar
|
|
|
|
|
size={40}
|
|
|
|
|
style={{ backgroundColor: '#87d068' }}
|
|
|
|
|
icon={<UserOutlined />}
|
|
|
|
|
/>
|
|
|
|
|
<div className={styles.personInfo}>
|
|
|
|
|
<div className={styles.personName}>{person.name}</div>
|
|
|
|
|
<div className={styles.personMeta}>
|
|
|
|
|
{person.dept} · {person.skills}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<Button
|
|
|
|
|
type="primary"
|
|
|
|
|
size="small"
|
|
|
|
|
icon={<PlusOutlined />}
|
|
|
|
|
className={styles.addButton}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 底部:近期值班信息人员列表 */}
|
|
|
|
|
<div className={styles.bottomSection}>
|
|
|
|
|
<div className={styles.sectionHeader}>
|
|
|
|
|
<div className={styles.titleBar}></div>
|
|
|
|
|
<h2 className={styles.sectionTitle}>近期值班信息人员列表</h2>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={styles.tableHeader}>
|
|
|
|
|
<Button type="primary" icon={<PlusOutlined />}>
|
|
|
|
|
添加人员
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Table
|
|
|
|
|
columns={tableColumns}
|
|
|
|
|
dataSource={tableData}
|
|
|
|
|
pagination={{
|
|
|
|
|
total: 48,
|
|
|
|
|
pageSize: 3,
|
|
|
|
|
current: 1,
|
|
|
|
|
showSizeChanger: false,
|
|
|
|
|
showQuickJumper: true,
|
|
|
|
|
showTotal: (total) => `共 ${total} 条`
|
|
|
|
|
}}
|
|
|
|
|
className={styles.personTable}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|