乐于分享
好东西不私藏

基于JS实现的员工排班表(附源码)

基于JS实现的员工排班表(附源码)

演示效果

HTML 代码

<!DOCTYPE html><html lang="zh-CN"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>员工排班系统</title>    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">    <link href="css.css" rel="stylesheet" /></head><body>    <div class="container">        <header>            <h1><i class="fas fa-calendar-alt"></i>员工排班系统</h1>            <p class="header-subtitle">高效管理团队工作时间,优化人力资源分配</p>        </header>        <div class="controls">            <div class="date-navigation"><button id="prev-week" class="btn btn-circle"><i                        class="fas fa-chevron-left"></i></button>                <div class="current-week" id="current-week">2023年11月6日 - 2023年11月12日</div><button id="next-week"                    class="btn btn-circle"><i class="fas fa-chevron-right"></i></button>            </div><button id="add-employee" class="btn btn-primary"><i class="fas fa-user-plus"></i>添加员工</button>        </div>        <div class="schedule-container">            <div class="employee-list" id="employee-list">                <h3><i class="fas fa-users"></i>员工列表</h3>                <div id="employee-items-container"></div>            </div>            <div class="schedule-grid">                <div class="schedule-header" id="schedule-header"></div>                <div class="schedule-body" id="schedule-body"></div>            </div>        </div>    </div><!-- 添加/编辑班次模态框 -->    <div id="shift-modal" class="modal">        <div class="modal-content"><span class="close">&times;</span>            <div class="modal-header">                <h2><i class="fas fa-calendar-plus"></i><span id="modal-title">添加班次</span></h2>            </div>            <form id="shift-form"><input type="hidden" id="shift-id">                <div class="form-group"><label for="shift-employee"><i class="fas fa-user"></i>员工</label><select                        id="shift-employee" required>                        <option value="">选择员工</option>                    </select></div>                <div class="form-group"><label for="shift-type"><i class="fas fa-clock"></i>班次类型</label><select                        id="shift-type" required>                        <option value="">选择班次类型</option>                        <option value="morning">早班(8:00-16:00)</option>                        <option value="evening">晚班(16:00-24:00)</option>                        <option value="night">夜班(0:00-8:00)</option>                        <option value="custom">自定义时间</option>                    </select></div>                <div id="custom-time-container" style="display: none;">                    <div class="form-group"><label for="shift-start">开始时间</label><input type="time" id="shift-start">                    </div>                    <div class="form-group"><label for="shift-end">结束时间</label><input type="time" id="shift-end"></div>                </div>                <div class="form-group"><label for="shift-date"><i class="fas fa-calendar-day"></i>日期</label><input                        type="date" id="shift-date" required></div>                <div class="form-group"><label for="shift-notes"><i class="fas fa-sticky-note"></i>备注</label><textarea                        id="shift-notes" rows="3" placeholder="可添加班次备注信息"></textarea></div>                <div class="form-actions"><button type="button" id="delete-shift" class="btn btn-danger"                        style="display: none;"><i class="fas fa-trash"></i>删除</button><button type="submit"                        class="btn btn-primary"><i class="fas fa-save"></i>保存</button></div>            </form>        </div>    </div><!-- 添加员工模态框 -->    <div id="employee-modal" class="modal">        <div class="modal-content"><span class="close">&times;</span>            <div class="modal-header">                <h2><i class="fas fa-user-plus"></i>添加新员工</h2>            </div>            <form id="employee-form">                <div class="form-group"><label for="employee-name"><i class="fas fa-signature"></i>姓名</label><input                        type="text" id="employee-name" required></div>                <div class="form-group"><label for="employee-position"><i class="fas fa-briefcase"></i>职位</label><input                        type="text" id="employee-position"></div>                <div class="form-group"><label for="employee-phone"><i class="fas fa-phone"></i>电话</label><input                        type="tel" id="employee-phone"></div>                <div class="form-group"><label for="employee-email"><i class="fas fa-envelope"></i>邮箱</label><input                        type="email" id="employee-email"></div>                <div class="form-actions"><button type="submit" class="btn btn-primary"><i                            class="fas fa-plus"></i>添加员工</button></div>            </form>        </div>    </div>    <script src="js.js"></script></body></html>

CSS 代码

:root {  --primary-color: #4361ee;  --primary-light: #4895ef;  --secondary-color: #3f37c9;  --accent-color: #4cc9f0;  --success-color: #4ade80;  --warning-color: #fbbf24;  --danger-color: #f87171;  --light-color: #f8f9fa;  --dark-color: #212529;  --gray-color: #6c757d;  --border-radius: 12px;  --box-shadow:    0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);  --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);}* {  box-sizing: border-box;  margin: 0;  padding: 0;}body {  font-family: "Noto Sans SC", sans-serif;  background-color: #f1f5f9;  color: var(--dark-color);  line-height: 1.6;  padding: 20px;}.container {  max-width: 1400px;  margin: 0 auto;  background-color: white;  border-radius: var(--border-radius);  box-shadow: var(--box-shadow);  overflow: hidden;}header {  background: linear-gradient(    135deg,    var(--primary-color),    var(--secondary-color)  );  color: white;  padding: 25px 30px;  position: relative;  overflow: hidden;}header::before {  content: "";  position: absolute;  top: -50px;  right: -50px;  width: 200px;  height: 200px;  background: rgba(255, 255, 255, 0.1);  border-radius: 50%;}header::after {  content: "";  position: absolute;  bottom: -80px;  right: -30px;  width: 300px;  height: 300px;  background: rgba(255, 255, 255, 0.05);  border-radius: 50%;}h1 {  font-weight: 700;  font-size: 28px;  margin-bottom: 5px;  position: relative;  z-index: 1;}.header-subtitle {  font-weight: 300;  opacity: 0.9;  font-size: 16px;  position: relative;  z-index: 1;}.controls {  display: flex;  justify-content: space-between;  align-items: center;  padding: 20px 30px;  background-color: white;  border-bottom: 1px solid rgba(0, 0, 0, 0.05);}.date-navigation {  display: flex;  align-items: center;  gap: 10px;}.btn {  padding: 10px 20px;  border-radius: 50px;  border: 0;  font-weight: 500;  cursor: pointer;  transition: var(--transition);  display: inline-flex;  align-items: center;  gap: 8px;}.btn i {  font-size: 14px;}.btn-primary {  background-color: var(--primary-color);  color: white;}.btn-primary:hover {  background-color: var(--secondary-color);  transform: translateY(-2px);  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);}.btn-outline {  background-color: transparent;  border: 1px solid var(--gray-color);  color: var(--gray-color);}.btn-outline:hover {  border-color: var(--primary-color);  color: var(--primary-color);}.btn-circle {  width: 40px;  height: 40px;  border-radius: 50%;  padding: 0;  justify-content: center;  background-color: white;  border: 1px solid #e2e8f0;  color: var(--gray-color);}.btn-circle:hover {  background-color: var(--primary-color);  color: white;  border-color: var(--primary-color);}.current-week {  font-weight: 500;  min-width: 250px;  text-align: center;  font-size: 16px;  color: var(--dark-color);}.schedule-container {  display: flex;  min-height: 600px;}.employee-list {  width: 250px;  border-right: 1px solid #e2e8f0;  padding: 20px;  background-color: #f8fafc;}.employee-list h3 {  font-size: 18px;  margin-bottom: 20px;  color: var(--dark-color);  display: flex;  align-items: center;  gap: 10px;}.employee-list h3 i {  color: var(--primary-color);}.employee-item {  padding: 15px;  margin-bottom: 10px;  background-color: white;  border-radius: var(--border-radius);  cursor: pointer;  transition: var(--transition);  border-left: 4px solid var(--primary-color);  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);  display: flex;  align-items: center;  gap: 10px;}.employee-item:hover {  transform: translateX(5px);  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);}.employee-avatar {  width: 36px;  height: 36px;  border-radius: 50%;  background-color: var(--primary-light);  display: flex;  align-items: center;  justify-content: center;  color: white;  font-weight: 500;}.employee-info {  flex: 1;}.employee-name {  font-weight: 500;  margin-bottom: 2px;}.employee-position {  font-size: 12px;  color: var(--gray-color);}.schedule-grid {  flex: 1;  overflow-x: auto;}.schedule-header {  display: grid;  grid-template-columns: repeat(7, minmax(130px, 1fr));  background-color: white;  color: var(--dark-color);  text-align: center;  font-weight: 500;  border-bottom: 1px solid #e2e8f0;}.schedule-header div {  padding: 15px 10px;  border-right: 1px solid #e2e8f0;  transition: var(--transition);}.schedule-header div:hover {  background-color: #f8fafc;}.schedule-header div:last-child {  border-right: 0;}.day-name {  font-size: 16px;  margin-bottom: 5px;}.day-date {  font-size: 14px;  color: var(--gray-color);}.schedule-body {  display: grid;  grid-template-columns: repeat(7, minmax(130px, 1fr));  background-color: #f8fafc;}.day-column {  min-height: 500px;  border-right: 1px solid #e2e8f0;  padding: 10px;  transition: var(--transition);  position: relative;}.day-column:hover {  background-color: rgba(255, 255, 255, 0.7);}.day-column:last-child {  border-right: 0;}.day-column.today {  background-color: rgba(67, 97, 238, 0.05);}.day-column.today::after {  content: "今天";  position: absolute;  top: 10px;  right: 10px;  background-color: var(--primary-color);  color: white;  font-size: 12px;  padding: 2px 8px;  border-radius: 10px;}.shift-slot {  background-color: white;  border-radius: var(--border-radius);  padding: 12px;  margin-bottom: 10px;  cursor: pointer;  transition: var(--transition);  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);  border-left: 4px solid var(--primary-color);}.shift-slot:hover {  transform: translateY(-2px);  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);}.shift-morning {  border-left-color: var(--success-color);}.shift-evening {  border-left-color: var(--warning-color);}.shift-night {  border-left-color: var(--secondary-color);}.shift-header {  display: flex;  justify-content: space-between;  align-items: center;  margin-bottom: 8px;}.shift-type {  font-weight: 500;  font-size: 14px;  color: var(--gray-color);}.shift-time {  font-size: 12px;  background-color: #f1f5f9;  padding: 2px 6px;  border-radius: 4px;  color: var(--gray-color);}.shift-employee {  display: flex;  align-items: center;  gap: 8px;}.shift-avatar {  width: 28px;  height: 28px;  border-radius: 50%;  background-color: var(--primary-light);  display: flex;  align-items: center;  justify-content: center;  color: white;  font-size: 12px;  font-weight: 500;}.shift-notes {  font-size: 12px;  color: var(--gray-color);  margin-top: 8px;  padding-top: 8px;  border-top: 1px dashed #e2e8f0;}.empty-state {  text-align: center;  padding: 20px 10px;  color: var(--gray-color);  display: flex;  flex-direction: column;  align-items: center;  justify-content: center;  min-height: 100px;}.empty-state i {  font-size: 24px;  margin-bottom: 10px;  opacity: 0.5;}.empty-state p {  margin-bottom: 10px;  font-size: 12px;}.empty-state button {  padding: 6px 12px;  font-size: 12px;}.modal {  display: none;  position: fixed;  z-index: 100;  left: 0;  top: 0;  width: 100%;  height: 100%;  background-color: rgba(0, 0, 0, 0.5);  backdrop-filter: blur(5px);  animation: fadeIn 0.3s ease;}@keyframes fadeIn {  from {    opacity: 0;  }  to {    opacity: 1;  }}.modal-content {  background-color: white;  margin: 5% auto;  padding: 30px;  border-radius: var(--border-radius);  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);  width: 90%;  max-width: 500px;  animation: slideDown 0.4s ease;  position: relative;  overflow: hidden;}@keyframes slideDown {  from {    transform: translateY(-50px);    opacity: 0;  }  to {    transform: translateY(0);    opacity: 1;  }}.modal-header {  margin-bottom: 25px;  position: relative;}.modal-header h2 {  font-size: 24px;  color: var(--dark-color);  display: flex;  align-items: center;  gap: 10px;}.modal-header h2 i {  color: var(--primary-color);}.close {  position: absolute;  top: 10px;  right: 10px;  width: 36px;  height: 36px;  background-color: #f1f5f9;  border-radius: 50%;  display: flex;  align-items: center;  justify-content: center;  cursor: pointer;  transition: var(--transition);  color: var(--gray-color);  font-size: 18px;}.close:hover {  background-color: var(--danger-color);  color: white;  transform: rotate(90deg);}.form-group {  margin-bottom: 20px;}label {  display: block;  margin-bottom: 8px;  font-weight: 500;  color: var(--dark-color);  font-size: 14px;}select,input,textarea {  width: 100%;  padding: 12px 15px;  border: 1px solid #e2e8f0;  border-radius: var(--border-radius);  font-family: inherit;  transition: var(--transition);  background-color: white;}select:focus,input:focus,textarea:focus {  outline: 0;  border-color: var(--primary-color);  box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);}.form-actions {  display: flex;  justify-content: flex-end;  gap: 10px;  margin-top: 30px;}.btn-danger {  background-color: var(--danger-color);  color: white;}.btn-danger:hover {  background-color: #dc2626;}@media (max-width: 992px) {  .schedule-container {    flex-direction: column;  }  .employee-list {    width: 100%;    border-right: 0;    border-bottom: 1px solid #e2e8f0;  }  .schedule-header {    grid-template-columns: repeat(7, minmax(120px, 1fr));  }  .schedule-body {    grid-template-columns: repeat(7, minmax(120px, 1fr));  }}@media (max-width: 768px) {  .controls {    flex-direction: column;    gap: 15px;    align-items: flex-start;  }  .date-navigation {    width: 100%;    justify-content: space-between;  }  .modal-content {    margin: 20px auto;    padding: 20px;  }  .schedule-header {    grid-template-columns: repeat(7, minmax(80px, 1fr));  }  .schedule-body {    grid-template-columns: repeat(7, minmax(80px, 1fr));  }}

JS 代码

// 员工数据const employees = [    { id: '1', name: '张三', position: '高级工程师' },    { id: '2', name: '李四', position: '产品经理' },    { id: '3', name: '王五', position: '设计师' },    { id: '4', name: '赵六', position: '实习生' },    { id: '5', name: '钱七', position: '测试工程师' }];// 班次类型数据const shiftTypes = [    { name: '早班', value: 'morning', time: '8:00 - 16:00' },    { name: '晚班', value: 'evening', time: '16:00 - 24:00' },    { name: '夜班', value: 'night', time: '0:00 - 8:00' }];// 班次数据const shifts = [    { id: '1', day: 1, employeeId: '1', type: 'morning', notes: '负责前端开发工作' },    { id: '2', day: 1, employeeId: '2', type: 'evening', notes: '' },    { id: '3', day: 2, employeeId: '3', type: 'morning', notes: '' },    { id: '4', day: 2, employeeId: '4', type: 'night', notes: '需要交接给早班' },    { id: '5', day: 3, employeeId: '5', type: 'morning', notes: '' }];// 当前显示的周let currentWeekStart = new Date();currentWeekStart.setDate(currentWeekStart.getDate() - currentWeekStart.getDay() + 1);// 更新当前周显示function updateWeekDisplay() {    const weekEnd = new Date(currentWeekStart);    weekEnd.setDate(weekEnd.getDate() + 6);    const options = { year: 'numeric', month: 'long', day: 'numeric' };    const startStr = currentWeekStart.toLocaleDateString('zh-CN', options);    const endStr = weekEnd.toLocaleDateString('zh-CN', options);    document.getElementById('current-week').textContent = `${startStr} - ${endStr}`;    generateWeekHeader();    generateScheduleBody();}// 导航到上一周document.getElementById('prev-week').addEventListener('click', function() {    currentWeekStart.setDate(currentWeekStart.getDate() - 7);    updateWeekDisplay();    // 这里应该重新加载排班数据});// 导航到下一周document.getElementById('next-week').addEventListener('click', function() {    currentWeekStart.setDate(currentWeekStart.getDate() + 7);    updateWeekDisplay();    // 这里应该重新加载排班数据});// 初始化周显示updateWeekDisplay();// 页面加载时初始化动态内容document.addEventListener('DOMContentLoaded', function() {    initDynamicContent();});// 模态框控制const shiftModal = document.getElementById('shift-modal');const employeeModal = document.getElementById('employee-modal');const closeButtons = document.getElementsByClassName('close');// 关闭模态框Array.from(closeButtons).forEach(button => {    button.addEventListener('click', function() {        shiftModal.style.display = 'none';        employeeModal.style.display = 'none';    });});// 点击模态框外部关闭window.addEventListener('click', function(event) {    if (event.target == shiftModal) {        shiftModal.style.display = 'none';    }    if (event.target == employeeModal) {        employeeModal.style.display = 'none';    }});// 班次类型变化时显示/隐藏自定义时间document.getElementById('shift-type').addEventListener('change', function() {    const customTimeContainer = document.getElementById('custom-time-container');    if (this.value === 'custom') {        customTimeContainer.style.display = 'block';    } else {        customTimeContainer.style.display = 'none';    }});// 使用事件委托处理动态生成的元素document.addEventListener('click', function(e) {    // 处理班次编辑    if (e.target.closest('.shift-slot')) {        e.stopPropagation();        handleShiftEdit(e.target.closest('.shift-slot'));    }    // 处理添加班次    if (e.target.closest('.day-column') && (e.target.classList.contains('btn-outline') || e.target.closest('.empty-state'))) {        const column = e.target.closest('.day-column');        document.getElementById('modal-title').textContent = '添加班次';        document.getElementById('shift-id').value = '';        document.getElementById('shift-form').reset();        document.getElementById('delete-shift').style.display = 'none';        document.getElementById('custom-time-container').style.display = 'none';        const dayOffset = parseInt(column.dataset.day) - 1;        const date = getDateByDayOffset(dayOffset);        document.getElementById('shift-date').value = formatDate(date);        shiftModal.style.display = 'block';    }});// 根据星期几获取日期function getDateByDayOffset(dayOffset) {    const date = new Date(currentWeekStart);    date.setDate(date.getDate() + dayOffset);    return date;}// 格式化日期为YYYY-MM-DDfunction formatDate(date) {    const year = date.getFullYear();    const month = String(date.getMonth() + 1).padStart(2, '0');    const day = String(date.getDate()).padStart(2, '0');    return `${year}-${month}-${day}`;}// 生成员工列表function generateEmployeeList() {    const container = document.getElementById('employee-items-container');    container.innerHTML = '';    employees.forEach(employee => {        const employeeItem = document.createElement('div');        employeeItem.className = 'employee-item';        employeeItem.dataset.id = employee.id;        employeeItem.setAttribute('draggable', 'true');        employeeItem.innerHTML = `            <div class="employee-avatar">${employee.name.charAt(0)}</div>            <div class="employee-info">                <div class="employee-name">${employee.name}</div>                <div class="employee-position">${employee.position}</div>            </div>        `;        container.appendChild(employeeItem);    });}// 生成员工选择下拉框选项function generateEmployeeOptions() {    const select = document.getElementById('shift-employee');    const firstOption = select.firstElementChild;    select.innerHTML = '';    select.appendChild(firstOption);    employees.forEach(employee => {        const option = document.createElement('option');        option.value = employee.id;        option.textContent = `${employee.name} - ${employee.position}`;        select.appendChild(option);    });}// 生成星期日期显示function generateWeekHeader() {    const header = document.getElementById('schedule-header');    header.innerHTML = '';    const weekDays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];    const today = new Date();    weekDays.forEach((dayName, index) => {        const date = getDateByDayOffset(index);        const dateStr = `${date.getMonth() + 1}月 ${date.getDate()}日`;        const dayElement = document.createElement('div');        if (date.toDateString() === today.toDateString()) {            dayElement.className = 'today';        }        dayElement.innerHTML = `            <div class="day-name">${dayName}</div>            <div class="day-date">${dateStr}</div>        `;        header.appendChild(dayElement);    });}// 生成排班表格function generateScheduleBody() {    const body = document.getElementById('schedule-body');    body.innerHTML = '';    const today = new Date();    for (let day = 1; day <= 7; day++) {        const dayColumn = document.createElement('div');        dayColumn.className = 'day-column';        dayColumn.dataset.day = day;        const date = getDateByDayOffset(day - 1);        if (date.toDateString() === today.toDateString()) {            dayColumn.className = 'day-column today';        }        const dayShifts = shifts.filter(shift => shift.day === day);        if (dayShifts.length > 0) {            dayShifts.forEach(shift => {                const shiftType = shiftTypes.find(s => s.value === shift.type);                const employee = employees.find(e => e.id === shift.employeeId);                if (shiftType && employee) {                    const shiftSlot = document.createElement('div');                    shiftSlot.className = `shift-slot shift-${shift.type}`;                    shiftSlot.dataset.shiftId = shift.id;                    shiftSlot.innerHTML = `                        <div class="shift-header">                            <span class="shift-type">${shiftType.name}</span>                            <span class="shift-time">${shiftType.time}</span>                        </div>                        <div class="shift-employee">                            <div class="shift-avatar">${employee.name.charAt(0)}</div>                            <span>${employee.name}</span>                        </div>                        ${shift.notes ? `<div class="shift-notes">${shift.notes}</div>` : ''}                    `;                    dayColumn.appendChild(shiftSlot);                }            });        }        if (dayShifts.length === 0) {            const emptyState = document.createElement('div');            emptyState.className = 'empty-state';            const date = getDateByDayOffset(day - 1);            const today = new Date();            const isToday = date.toDateString() === today.toDateString();            emptyState.innerHTML = `                <i class="fas ${isToday ? 'fa-calendar-plus' : 'fa-user-clock'}"></i>                <p>${isToday ? '点击添加班次' : '暂无排班安排'}</p>                <button class="btn btn-outline"><i class="fas fa-plus"></i>添加班次</button>            `;            dayColumn.appendChild(emptyState);        }        body.appendChild(dayColumn);    }}// 初始化动态内容function initDynamicContent() {    generateEmployeeList();    generateEmployeeOptions();    generateWeekHeader();    generateScheduleBody();}// 处理班次编辑function handleShiftEdit(shiftSlot) {    document.getElementById('modal-title').textContent = '编辑班次';    document.getElementById('shift-id').value = shiftSlot.dataset.shiftId;    // 设置员工    const employeeName = shiftSlot.querySelector('.shift-employee span').textContent;    const employee = employees.find(emp => emp.name === employeeName);    document.getElementById('shift-employee').value = employee ? employee.id : '';    // 设置班次类型    const shiftType = shiftSlot.querySelector('.shift-type').textContent;    const selectedShift = shiftTypes.find(s => s.name === shiftType);    document.getElementById('shift-type').value = selectedShift ? selectedShift.value : '';    // 设置备注    const notesEl = shiftSlot.querySelector('.shift-notes');    document.getElementById('shift-notes').value = notesEl ? notesEl.textContent : '';    document.getElementById('delete-shift').style.display = 'inline-block';    // 设置日期    const dayOffset = parseInt(shiftSlot.closest('.day-column').dataset.day) - 1;    const date = getDateByDayOffset(dayOffset);    document.getElementById('shift-date').value = formatDate(date);    shiftModal.style.display = 'block';}// 提交班次表单document.getElementById('shift-form').addEventListener('submit', function(e) {    e.preventDefault();    const shiftId = document.getElementById('shift-id').value;    const employeeId = document.getElementById('shift-employee').value;    const shiftType = document.getElementById('shift-type').value;    const shiftDate = document.getElementById('shift-date').value;    const shiftNotes = document.getElementById('shift-notes').value;    if (!employeeId || !shiftType || !shiftDate) {        alert('请填写完整的班次信息');        return;    }    const date = new Date(shiftDate);    const weekStart = new Date(currentWeekStart);    date.setHours(0, 0, 0, 0);    weekStart.setHours(0, 0, 0, 0);    const dayOffset = Math.floor((date - weekStart) / (1000 * 60 * 60 * 24));    const day = dayOffset + 1;    if (shiftId) {        const existingShift = shifts.find(s => s.id === shiftId);        if (existingShift) {            existingShift.employeeId = employeeId;            existingShift.type = shiftType;            existingShift.notes = shiftNotes;        }    } else {        const newShift = {            id: String(Date.now()),            day: day,            employeeId: employeeId,            type: shiftType,            notes: shiftNotes        };        shifts.push(newShift);    }    generateScheduleBody();    document.getElementById('shift-form').reset();    document.getElementById('shift-id').value = '';    document.getElementById('delete-shift').style.display = 'none';    shiftModal.style.display = 'none';    alert('班次已保存');});// 删除班次document.getElementById('delete-shift').addEventListener('click', function() {    if (confirm('确定要删除这个班次吗?')) {        const shiftId = document.getElementById('shift-id').value;        if (shiftId) {            const shiftIndex = shifts.findIndex(s => s.id === shiftId);            if (shiftIndex > -1) {                shifts.splice(shiftIndex, 1);            }        }        generateScheduleBody();        document.getElementById('shift-form').reset();        document.getElementById('shift-id').value = '';        document.getElementById('delete-shift').style.display = 'none';        shiftModal.style.display = 'none';        alert('班次已删除');    }});// 添加员工按钮document.getElementById('add-employee').addEventListener('click', function() {    document.getElementById('employee-form').reset();    employeeModal.style.display = 'block';});// 提交员工表单document.getElementById('employee-form').addEventListener('submit', function(e) {    e.preventDefault();    const name = document.getElementById('employee-name').value;    const position = document.getElementById('employee-position').value;    const phone = document.getElementById('employee-phone').value;    const email = document.getElementById('employee-email').value;    if (!name.trim()) {        alert('请输入员工姓名');        return;    }    const newEmployee = {        id: String(Date.now()),        name: name,        position: position || '未设置职位',        phone: phone,        email: email    };    employees.push(newEmployee);    generateEmployeeList();    generateEmployeeOptions();    document.getElementById('employee-form').reset();    employeeModal.style.display = 'none';    alert('员工已添加');});// 拖放功能 (简化版)let draggedEmployee = null;document.addEventListener('dragstart', function(e) {    if (e.target.closest('.employee-item')) {        draggedEmployee = e.target.closest('.employee-item');        setTimeout(() => {            draggedEmployee.style.opacity = '0.4';        }, 0);    }});document.addEventListener('dragend', function(e) {    if (e.target.closest('.employee-item')) {        e.target.closest('.employee-item').style.opacity = '1';    }});document.addEventListener('dragover', function(e) {    if (e.target.closest('.day-column')) {        e.preventDefault();        e.target.closest('.day-column').style.backgroundColor = 'rgba(67, 97, 238, 0.1)';    }});document.addEventListener('dragleave', function(e) {    if (e.target.closest('.day-column')) {        e.target.closest('.day-column').style.backgroundColor = '';    }});document.addEventListener('drop', function(e) {    if (e.target.closest('.day-column')) {        e.preventDefault();        const column = e.target.closest('.day-column');        column.style.backgroundColor = '';        if (draggedEmployee) {            const shiftSlot = document.createElement('div');            shiftSlot.className = 'shift-slot shift-morning';            shiftSlot.dataset.shiftId = Date.now();            const employeeName = draggedEmployee.querySelector('.employee-name').textContent;            const employeeAvatar = draggedEmployee.querySelector('.employee-avatar').textContent;            shiftSlot.innerHTML = `                <div class="shift-header">                    <span class="shift-type">早班</span>                    <span class="shift-time">8:00 - 16:00</span>                </div>                <div class="shift-employee">                    <div class="shift-avatar">${employeeAvatar}</div>                    <span>${employeeName}</span>                </div>            `;            const emptyState = column.querySelector('.empty-state');            if (emptyState) {                column.removeChild(emptyState);            }            column.appendChild(shiftSlot);            draggedEmployee = null;        }    }});

源码获取

私信回复排班系统获取完整源码