You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2908 lines
111 KiB
HTML

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!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.4.0/css/all.min.css">
<!-- <link rel="stylesheet" href="styles.css"> -->
<link rel="stylesheet" href="all.min.css">
<script src="chart.js"></script>
<style>
/* ==== 全局样式 ==== */
body {
margin: 0;
padding: 0;
font-family: 'Arial', sans-serif;
background-color: #f5f9ff;
color: #333;
}
/* ==== 主容器 ==== */
.main-container {
display: flex;
min-height: 100vh;
padding: 20px;
box-sizing: border-box;
gap: 20px;
}
/* ==== 左侧内容区域 ==== */
.content-area {
flex: 1;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 75, 150, 0.1);
padding: 20px;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* ==== 右侧侧边栏 ==== */
.sidebar-area {
width: 350px;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 75, 150, 0.1);
padding: 20px;
display: flex;
flex-direction: column;
overflow-y: auto;
max-height: calc(100vh - 40px);
}
/* ==== 头部区域 ==== */
.personnel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #e1e9f5;
}
.personnel-title {
color: #1a4b8c;
font-size: 24px;
font-weight: 600;
}
.personnel-actions {
display: flex;
gap: 10px;
}
.action-btn {
background: linear-gradient(135deg, #2a7de1, #1a4b8c);
color: #fff;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
box-shadow: 0 2px 8px rgba(42, 125, 225, 0.2);
}
.action-btn:hover {
background: linear-gradient(135deg, #1a4b8c, #2a7de1);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(42, 125, 225, 0.3);
}
/* ==== 标签页 ==== */
.personnel-tabs {
display: flex;
background: #e1e9f5;
border-radius: 10px;
padding: 5px;
margin-bottom: 20px;
}
.tab-btn {
flex: 1;
padding: 12px 20px;
background: transparent;
color: #4a6fa5;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
font-weight: 500;
}
.tab-btn.active {
background: #ffffff;
color: #1a4b8c;
font-weight: 600;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.tab-content {
display: none;
flex: 1;
overflow-y: auto;
padding-right: 5px;
}
.tab-content.active {
display: block;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* ==== 卡片样式 ==== */
.personnel-card {
background: #ffffff;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid #e1e9f5;
box-shadow: 0 2px 10px rgba(0, 75, 150, 0.05);
}
.card-title {
color: #1a4b8c;
font-size: 18px;
font-weight: 600;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
}
.card-title i {
color: #2a7de1;
}
/* ==== 表单样式 ==== */
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.form-group {
display: flex;
flex-direction: column;
}
.form-group label {
color: #4a6fa5;
font-size: 14px;
margin-bottom: 5px;
font-weight: 500;
}
.form-group input,
.form-group select,
.form-group textarea {
background: #f5f9ff;
border: 1px solid #d1e0f5;
border-radius: 6px;
padding: 10px;
color: #1a4b8c;
font-size: 14px;
transition: all 0.3s ease;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
border-color: #2a7de1;
box-shadow: 0 0 0 3px rgba(42, 125, 225, 0.2);
outline: none;
}
/* ==== 表格样式 ==== */
.personnel-table {
width: 100%;
background: #ffffff;
border-radius: 10px;
overflow: hidden;
border-collapse: separate;
border-spacing: 0;
box-shadow: 0 2px 10px rgba(0, 75, 150, 0.05);
}
.personnel-table th,
.personnel-table td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #e1e9f5;
}
.personnel-table th {
background: #f5f9ff;
color: #1a4b8c;
font-weight: 600;
font-size: 14px;
}
.personnel-table td {
color: #4a6fa5;
font-size: 13px;
}
.personnel-table tr:last-child td {
border-bottom: none;
}
/* ==== 状态标签 ==== */
.status-badge {
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
text-align: center;
display: inline-block;
min-width: 60px;
}
.status-online {
background: rgba(40, 167, 69, 0.1);
color: #28a745;
border: 1px solid rgba(40, 167, 69, 0.3);
}
.status-offline {
background: rgba(220, 53, 69, 0.1);
color: #dc3545;
border: 1px solid rgba(220, 53, 69, 0.3);
}
.status-normal {
background: rgba(40, 167, 69, 0.1);
color: #28a745;
border: 1px solid rgba(40, 167, 69, 0.3);
}
.status-late {
background: rgba(255, 193, 7, 0.1);
color: #ffc107;
border: 1px solid rgba(255, 193, 7, 0.3);
}
.status-absent {
background: rgba(220, 53, 69, 0.1);
color: #dc3545;
border: 1px solid rgba(220, 53, 69, 0.3);
}
/* ==== 侧边栏样式 ==== */
.sidebar-section {
background: #f5f9ff;
border-radius: 12px;
padding: 15px;
margin-bottom: 20px;
border: 1px solid #e1e9f5;
}
.sidebar-title {
color: #1a4b8c;
font-size: 16px;
font-weight: 600;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.sidebar-title i {
color: #2a7de1;
}
/* ==== 统计卡片 ==== */
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 15px;
}
.stat-item {
background: #ffffff;
padding: 15px;
border-radius: 8px;
text-align: center;
box-shadow: 0 2px 8px rgba(0, 75, 150, 0.05);
border: 1px solid #e1e9f5;
}
.stat-value {
color: #2a7de1;
font-size: 24px;
font-weight: 700;
display: block;
}
.stat-label {
color: #4a6fa5;
font-size: 12px;
margin-top: 5px;
}
/* ==== 图表容器 ==== */
.chart-container {
background: #ffffff;
border-radius: 8px;
padding: 15px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
color: #a8c0e0;
border: 1px solid #e1e9f5;
box-shadow: 0 2px 8px rgba(0, 75, 150, 0.05);
}
/* ==== 人员网格 ==== */
.personnel-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.personnel-item {
background: #ffffff;
border-radius: 12px;
padding: 20px;
border: 1px solid #e1e9f5;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 75, 150, 0.05);
}
.personnel-item:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(0, 75, 150, 0.1);
}
.personnel-info {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 15px;
}
.personnel-icon {
width: 50px;
height: 50px;
background: linear-gradient(135deg, #e1e9f5, #d1e0f5);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: #2a7de1;
}
.personnel-details h4 {
color: #1a4b8c;
margin: 0 0 5px 0;
font-size: 16px;
}
.personnel-details p {
color: #4a6fa5;
margin: 0;
font-size: 14px;
}
.personnel-params {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 15px;
}
.param-item {
background: #f5f9ff;
padding: 8px;
border-radius: 6px;
text-align: center;
border: 1px solid #e1e9f5;
}
.param-value {
color: #2a7de1;
font-weight: 600;
display: block;
}
.param-label {
color: #4a6fa5;
font-size: 12px;
}
/* ==== 告警列表 ==== */
.alert-list {
max-height: 300px;
overflow-y: auto;
}
.alert-item {
background: #ffffff;
padding: 12px;
border-radius: 8px;
margin-bottom: 10px;
border-left: 4px solid #dc3545;
box-shadow: 0 2px 8px rgba(0, 75, 150, 0.05);
}
.alert-title {
color: #1a4b8c;
font-size: 14px;
font-weight: 600;
margin-bottom: 5px;
}
.alert-info {
color: #4a6fa5;
font-size: 12px;
display: flex;
justify-content: space-between;
}
/* ==== 按钮样式 ==== */
.btn {
background: #f5f9ff;
color: #2a7de1;
border: 1px solid #d1e0f5;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 12px;
margin-right: 5px;
}
.btn:hover {
background: #e1e9f5;
}
.btn-primary {
background: rgba(42, 125, 225, 0.1);
color: #2a7de1;
border-color: rgba(42, 125, 225, 0.3);
}
.btn-primary:hover {
background: rgba(42, 125, 225, 0.2);
}
.btn-success {
background: rgba(40, 167, 69, 0.1);
color: #28a745;
border-color: rgba(40, 167, 69, 0.3);
}
.btn-success:hover {
background: rgba(40, 167, 69, 0.2);
}
.btn-danger {
background: rgba(220, 53, 69, 0.1);
color: #dc3545;
border-color: rgba(220, 53, 69, 0.3);
}
.btn-danger:hover {
background: rgba(220, 53, 69, 0.2);
}
.btn-warning {
background: rgba(255, 193, 7, 0.1);
color: #ffc107;
border-color: rgba(255, 193, 7, 0.3);
}
.btn-warning:hover {
background: rgba(255, 193, 7, 0.2);
}
/* ==== 模态框 ==== */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
backdrop-filter: blur(5px);
}
.modal-content {
background: #ffffff;
margin: 5% auto;
padding: 30px;
border-radius: 15px;
width: 80%;
max-width: 800px;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 10px 30px rgba(0, 75, 150, 0.2);
}
.modal h2 {
color: #1a4b8c;
margin-bottom: 20px;
font-size: 20px;
border-bottom: 1px solid #e1e9f5;
padding-bottom: 10px;
}
.close {
color: #4a6fa5;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
margin-top: -10px;
transition: all 0.3s ease;
}
.close:hover {
color: #2a7de1;
}
/* ==== 滚动条样式 ==== */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f5f9ff;
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: #d1e0f5;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #a8c0e0;
}
/* ==== 新增模态框样式 ==== */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
backdrop-filter: blur(5px);
}
.modal-overlay .modal-content {
background: #ffffff;
border-radius: 12px;
padding: 25px;
width: 90%;
max-width: 700px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 10px 30px rgba(0, 75, 150, 0.2);
position: relative;
}
.modal-overlay .modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #e1e9f5;
}
.modal-overlay .modal-header h3 {
color: #1a4b8c;
font-size: 18px;
margin: 0;
}
.modal-overlay .modal-close {
color: #4a6fa5;
font-size: 24px;
font-weight: bold;
cursor: pointer;
background: none;
border: none;
padding: 0;
margin: 0;
transition: all 0.3s ease;
}
.modal-overlay .modal-close:hover {
color: #2a7de1;
transform: scale(1.1);
}
.modal-overlay .modal-body {
padding: 10px 0;
}
/* ==== 新增排班日历样式 ==== */
.schedule-calendar {
background: #f5f9ff;
border-radius: 12px;
padding: 20px;
margin-top: 20px;
border: 1px solid #e1e9f5;
}
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.calendar-header h3 {
color: #1a4b8c;
margin: 0;
font-size: 16px;
font-weight: 600;
}
.calendar-nav {
display: flex;
gap: 10px;
}
.calendar-nav-btn {
background: #e1e9f5;
border: none;
border-radius: 6px;
padding: 8px 12px;
cursor: pointer;
color: #1a4b8c;
font-size: 14px;
transition: all 0.3s ease;
}
.calendar-nav-btn:hover {
background: #d1e0f5;
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
background: #e1e9f5;
border-radius: 8px;
overflow: hidden;
}
.calendar-day {
background: white;
padding: 10px;
min-height: 80px;
display: flex;
flex-direction: column;
}
.calendar-day.header {
background: #f5f9ff;
color: #1a4b8c;
font-weight: 600;
text-align: center;
min-height: 40px;
justify-content: center;
}
.calendar-day.today {
background: rgba(255, 193, 7, 0.1);
border: 1px solid rgba(255, 193, 7, 0.3);
}
.calendar-day.scheduled {
background: rgba(40, 167, 69, 0.1);
border: 1px solid rgba(40, 167, 69, 0.3);
}
.day-number {
font-weight: 600;
margin-bottom: 5px;
color: #1a4b8c;
}
.day-staff {
font-size: 11px;
color: #4a6fa5;
flex: 1;
overflow-y: auto;
}
.day-staff-item {
margin-bottom: 3px;
padding: 2px 5px;
background: rgba(42, 125, 225, 0.1);
border-radius: 4px;
}
/* 更新日历样式 */
.schedule-calendar {
background: #ffffff;
border-radius: 12px;
padding: 20px;
margin-top: 20px;
border: 1px solid #e1e9f5;
}
.calendar-day {
background: white;
padding: 10px;
min-height: 100px;
display: flex;
flex-direction: column;
border: 1px solid #e1e9f5;
position: relative;
}
.calendar-day.header {
background: #f5f9ff;
color: #1a4b8c;
font-weight: 600;
text-align: center;
min-height: 40px;
justify-content: center;
}
.calendar-day.today {
background: rgba(255, 193, 7, 0.1);
border: 2px solid #ffc107;
}
.calendar-day.scheduled {
background: rgba(42, 125, 225, 0.05);
}
.calendar-day.scheduled .day-number {
color: #2a7de1;
font-weight: bold;
}
.day-number {
font-weight: 600;
margin-bottom: 5px;
color: #4a6fa5;
align-self: flex-end;
}
.day-staff {
font-size: 11px;
color: #4a6fa5;
flex: 1;
overflow-y: auto;
width: 100%;
}
.day-staff-item {
margin-bottom: 3px;
padding: 3px 5px;
background: rgba(42, 125, 225, 0.1);
border-radius: 4px;
color: #1a4b8c;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.day-staff-item i {
margin-right: 3px;
color: #2a7de1;
}
.calendar-day.other-month {
background: #f9f9f9;
color: #a8c0e0;
}
.calendar-day.other-month .day-number {
color: #a8c0e0;
}
/* 网格管理按钮样式 */
.grid-actions {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
/* 网格管理模态框样式 */
.grid-modal-content {
width: 800px;
max-width: 90vw;
}
.grid-table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
}
.grid-table th,
.grid-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e1e9f5;
}
.grid-table th {
background: #f5f9ff;
color: #1a4b8c;
font-weight: 600;
}
/* 新增网格表单样式 */
.grid-form .form-group {
margin-bottom: 15px;
}
.grid-form label {
display: block;
margin-bottom: 5px;
color: #4a6fa5;
font-weight: 500;
}
.grid-form input,
.grid-form textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid #d1e0f5;
border-radius: 6px;
font-size: 14px;
}
.grid-form textarea {
min-height: 80px;
}
</style>
</head>
<body>
<div class="main-container">
<!-- 左侧内容区域 -->
<div class="content-area">
<div class="personnel-header">
<h1 class="personnel-title"><i class="fas fa-users"></i> 人员管理系统</h1>
<div class="personnel-actions">
<button class="action-btn" onclick="openPersonnelModal()"><i class="fas fa-user-plus"></i>
新增人员</button>
<button class="action-btn" onclick="openScheduleModal()"><i class="fas fa-calendar-alt"></i>
排班管理</button>
<button class="action-btn" onclick="exportReport()"><i class="fas fa-file-export"></i> 导出报表</button>
<button class="action-btn" onclick="location.href='index.html'"><i class="fas fa-home"
style="color: orange;"></i> 回到首页</button>
</div>
</div>
<div class="personnel-tabs">
<button class="tab-btn active" onclick="switchTab('overview')">概览统计</button>
<button class="tab-btn" onclick="switchTab('personnel')">人员档案</button>
<button class="tab-btn" onclick="switchTab('grid')">网格分配</button>
<button class="tab-btn" onclick="switchTab('schedule')">排班日历</button>
<button class="tab-btn" onclick="switchTab('attendance')">考勤管理</button>
<button class="tab-btn" onclick="switchTab('performance')">绩效考核</button>
</div>
<!-- 概览统计 -->
<div id="overview" class="tab-content active">
<div class="personnel-card">
<div class="card-title">
<i class="fas fa-tachometer-alt"></i>
人员概览统计
</div>
<div class="stats-grid">
<div class="stat-item">
<span class="stat-value" id="totalPersonnel">28</span>
<div class="stat-label">总人数</div>
</div>
<div class="stat-item">
<span class="stat-value" id="onDutyPersonnel">26</span>
<div class="stat-label">在岗人数</div>
</div>
<div class="stat-item">
<span class="stat-value" id="gridCount">12</span>
<div class="stat-label">网格数量</div>
</div>
<div class="stat-item">
<span class="stat-value" id="attendanceRate">92.8%</span>
<div class="stat-label">出勤率</div>
</div>
<div class="stat-item">
<span class="stat-value" id="workDistance">156.8km</span>
<div class="stat-label">作业里程</div>
</div>
<div class="stat-item">
<span class="stat-value" id="completionRate">94.2%</span>
<div class="stat-label">任务完成率</div>
</div>
</div>
<div class="chart-container" style="height: 420px;;">
<canvas id="performanceChart"></canvas>
</div>
</div>
</div>
<!-- 人员档案 -->
<div id="personnel" class="tab-content">
<div class="personnel-card">
<div class="card-title">
<i class="fas fa-user-friends"></i>
人员档案管理
</div>
<div class="form-grid">
<div class="form-group">
<label>岗位类型</label>
<select id="positionFilter">
<option value="all">全部岗位</option>
<option value="cleaner">保洁员</option>
<option value="supervisor">班长</option>
<option value="manager">主管</option>
</select>
</div>
<div class="form-group">
<label>所属网格</label>
<select id="gridFilter">
<option value="all">全部网格</option>
<option value="grid1">网格A1</option>
<option value="grid2">网格A2</option>
<option value="grid3">网格B1</option>
<option value="grid4">网格B2</option>
</select>
</div>
<div class="form-group">
<label>在岗状态</label>
<select id="statusFilter">
<option value="all">全部状态</option>
<option value="online">在岗</option>
<option value="offline">离岗</option>
</select>
</div>
<div class="form-group">
<label>搜索人员</label>
<input type="text" id="personnelSearch" placeholder="输入姓名或工号">
</div>
</div>
<table class="personnel-table">
<thead>
<tr>
<th>工号</th>
<th>姓名</th>
<th>岗位</th>
<th>所属网格</th>
<th>联系电话</th>
<th>在岗状态</th>
<th>本月考勤</th>
<th>绩效评分</th>
<th>操作</th>
</tr>
</thead>
<tbody id="personnelTableBody">
<!-- 人员数据将通过JS动态加载 -->
</tbody>
</table>
</div>
</div>
<div id="grid" class="tab-content">
<div class="personnel-card">
<div class="card-title" style="display: flex; justify-content: space-between; align-items: center;">
<div>
<i class="fas fa-th"></i>
网格人员分配
</div>
<div class="grid-actions">
<button class="btn btn-primary" onclick="optimizeAllocation()">
<i class="fas fa-magic"></i> 智能分配
</button>
<button class="btn btn-success" onclick="manageGrids()">
<i class="fas fa-cogs"></i> 网格管理
</button>
</div>
</div>
<div class="personnel-grid" id="gridAllocationGrid">
<!-- 网格分配卡片将通过JS动态加载 -->
</div>
</div>
</div>
<!-- 修改后的排班日历部分 -->
<div id="schedule" class="tab-content">
<div class="personnel-card">
<div class="card-title">
<i class="fas fa-calendar-alt"></i>
排班日历
</div>
<div class="schedule-calendar">
<div class="calendar-header">
<h3 id="currentMonthTitle">2024年1月</h3>
<div class="calendar-nav">
<button class="calendar-nav-btn" onclick="prevMonth()"><i
class="fas fa-chevron-left"></i> 上月</button>
<button class="calendar-nav-btn" onclick="nextMonth()">下月 <i
class="fas fa-chevron-right"></i></button>
</div>
</div>
<div class="calendar-grid">
<div class="calendar-day header">周日</div>
<div class="calendar-day header">周一</div>
<div class="calendar-day header">周二</div>
<div class="calendar-day header">周三</div>
<div class="calendar-day header">周四</div>
<div class="calendar-day header">周五</div>
<div class="calendar-day header">周六</div>
<!-- 日期将通过JS动态加载 -->
</div>
</div>
</div>
</div>
<!-- 考勤管理 -->
<div id="attendance" class="tab-content">
<div class="personnel-card">
<div class="card-title">
<i class="fas fa-clock"></i>
考勤管理
</div>
<div class="form-grid">
<div class="form-group">
<label>日期范围</label>
<input type="date" id="attendanceDate" value="2024-01-17">
</div>
<div class="form-group">
<label>网格筛选</label>
<select id="attendanceGridFilter">
<option value="all">全部网格</option>
<option value="grid1">网格A1</option>
<option value="grid2">网格A2</option>
<option value="grid3">网格B1</option>
<option value="grid4">网格B2</option>
</select>
</div>
<div class="form-group">
<label>考勤状态</label>
<select id="attendanceStatusFilter">
<option value="all">全部状态</option>
<option value="normal">正常</option>
<option value="late">迟到</option>
<option value="absent">缺勤</option>
</select>
</div>
</div>
<table class="personnel-table">
<thead>
<tr>
<th>姓名</th>
<th>工号</th>
<th>网格</th>
<th>上班打卡</th>
<th>下班打卡</th>
<th>工作时长</th>
<th>作业里程</th>
<th>考勤状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="attendanceTableBody">
<!-- 考勤数据将通过JS动态加载 -->
</tbody>
</table>
</div>
</div>
<!-- 绩效考核 -->
<div id="performance" class="tab-content">
<div class="personnel-card">
<div class="card-title">
<i class="fas fa-chart-bar"></i>
绩效考核
</div>
<div class="form-grid">
<div class="form-group">
<label>考核周期</label>
<select id="performancePeriod">
<option value="month">本月</option>
<option value="quarter">本季度</option>
<option value="year">本年度</option>
</select>
</div>
<div class="form-group">
<label>岗位类型</label>
<select id="performancePositionFilter">
<option value="all">全部岗位</option>
<option value="cleaner">保洁员</option>
<option value="supervisor">班长</option>
</select>
</div>
<div class="form-group">
<label>网格筛选</label>
<select id="performanceGridFilter">
<option value="all">全部网格</option>
<option value="grid1">网格A1</option>
<option value="grid2">网格A2</option>
<option value="grid3">网格B1</option>
<option value="grid4">网格B2</option>
</select>
</div>
</div>
<table class="personnel-table">
<thead>
<tr>
<th>排名</th>
<th>姓名</th>
<th>工号</th>
<th>出勤率</th>
<th>作业时长</th>
<th>作业里程</th>
<th>完成率</th>
<th>质量评分</th>
<th>综合评分</th>
<th>操作</th>
</tr>
</thead>
<tbody id="performanceTableBody">
<!-- 绩效数据将通过JS动态加载 -->
</tbody>
</table>
</div>
</div>
</div>
<!-- 右侧侧边栏 -->
<div class="sidebar-area">
<div class="sidebar-section">
<div class="sidebar-title">
<i class="fas fa-tachometer-alt"></i>
人员概览
</div>
<div class="stats-grid">
<div class="stat-item">
<span class="stat-value">28</span>
<div class="stat-label">总人数</div>
</div>
<div class="stat-item">
<span class="stat-value">26</span>
<div class="stat-label">在岗人数</div>
</div>
<div class="stat-item">
<span class="stat-value">1</span>
<div class="stat-label">迟到人数</div>
</div>
<div class="stat-item">
<span class="stat-value">1</span>
<div class="stat-label">缺勤人数</div>
</div>
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-title">
<i class="fas fa-exclamation-triangle"></i>
考勤异常
</div>
<div class="alert-list" id="attendanceAlerts">
<!-- 考勤异常列表 -->
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-title">
<i class="fas fa-chart-pie"></i>
岗位分布
</div>
<div class="chart-container">
<canvas id="positionChart"></canvas>
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-title">
<i class="fas fa-calendar-check"></i>
排班提醒
</div>
<div class="alert-list" id="scheduleReminders">
<!-- 排班提醒列表 -->
</div>
</div>
</div>
</div>
<!-- 新增人员模态框 -->
<div id="personnelModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closePersonnelModal()">&times;</span>
<h2>新增人员档案</h2>
<form id="personnelForm">
<div class="form-grid">
<div class="form-group">
<label>工号</label>
<input type="text" name="personnelId" placeholder="如CL001" required>
</div>
<div class="form-group">
<label>姓名</label>
<input type="text" name="personnelName" placeholder="输入人员姓名" required>
</div>
<div class="form-group">
<label>岗位</label>
<select name="position" required>
<option value="">请选择岗位</option>
<option value="cleaner">保洁员</option>
<option value="supervisor">班长</option>
<option value="manager">主管</option>
</select>
</div>
<div class="form-group">
<label>所属网格</label>
<select name="grid" required>
<option value="">请选择网格</option>
<option value="grid1">网格A1</option>
<option value="grid2">网格A2</option>
<option value="grid3">网格B1</option>
<option value="grid4">网格B2</option>
</select>
</div>
<div class="form-group">
<label>联系电话</label>
<input type="tel" name="phone" placeholder="输入联系电话" required>
</div>
<div class="form-group">
<label>入职日期</label>
<input type="date" name="joinDate" required>
</div>
</div>
<div class="form-group">
<label>工作职责</label>
<textarea name="responsibilities" rows="3" placeholder="输入工作职责描述"></textarea>
</div>
<div style="text-align: right; margin-top: 20px;">
<button type="button" class="btn" onclick="closePersonnelModal()">取消</button>
<button type="submit" class="btn btn-primary">保存人员</button>
</div>
</form>
</div>
</div>
<!-- 排班管理模态框 -->
<div id="scheduleModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeScheduleModal()">&times;</span>
<h2>排班管理</h2>
<form id="scheduleForm">
<div class="form-grid">
<div class="form-group">
<label>选择日期</label>
<input type="date" name="scheduleDate" required>
</div>
<div class="form-group">
<label>班次</label>
<select name="shift" required>
<option value="">请选择班次</option>
<option value="morning">早班 (08:00-17:00)</option>
<option value="afternoon">晚班 (14:00-23:00)</option>
<option value="night">夜班 (23:00-08:00)</option>
</select>
</div>
<div class="form-group">
<label>所属网格</label>
<select name="scheduleGrid" required>
<option value="">请选择网格</option>
<option value="grid1">网格A1</option>
<option value="grid2">网格A2</option>
<option value="grid3">网格B1</option>
<option value="grid4">网格B2</option>
</select>
</div>
</div>
<div class="form-group">
<label>选择人员</label>
<div
style="max-height: 200px; overflow-y: auto; border: 1px solid #d1e0f5; border-radius: 6px; padding: 10px;">
<!-- 人员选择列表将通过JS动态加载 -->
<div id="personnelChecklist"></div>
</div>
</div>
<div style="text-align: right; margin-top: 20px;">
<button type="button" class="btn" onclick="closeScheduleModal()">取消</button>
<button type="submit" class="btn btn-primary">保存排班</button>
</div>
</form>
</div>
</div>
<!-- 新增网格管理模态框 -->
<div id="gridManageModal" class="modal">
<div class="modal-content grid-modal-content">
<span class="close" onclick="closeModal2('gridManageModal')">&times;</span>
<h2>网格管理</h2>
<div style="margin-bottom: 20px;">
<button class="btn btn-primary" onclick="showAddGridForm()">
<i class="fas fa-plus"></i> 新增网格
</button>
</div>
<table class="grid-table">
<thead>
<tr>
<th>网格ID</th>
<th>网格名称</th>
<th>覆盖面积</th>
<th>描述</th>
<th>操作</th>
</tr>
</thead>
<tbody id="gridTableBody">
<!-- 网格数据将通过JS动态加载 -->
</tbody>
</table>
</div>
</div>
<!-- 新增/编辑网格表单模态框 -->
<div id="gridFormModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal2('gridFormModal')">&times;</span>
<h2 id="gridFormTitle">新增网格</h2>
<form id="gridForm" class="grid-form">
<input type="hidden" id="gridId">
<div class="form-group">
<label for="gridName">网格名称</label>
<input type="text" id="gridName" required>
</div>
<div class="form-group">
<label for="gridArea">覆盖面积</label>
<input type="text" id="gridArea" placeholder="例如: 0.8km²" required>
</div>
<div class="form-group">
<label for="gridDescription">描述</label>
<textarea id="gridDescription" required></textarea>
</div>
<div style="text-align: right; margin-top: 20px;">
<button type="button" class="btn" onclick="closeModal2('gridFormModal')">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
</div>
</div>
<script>
// 模拟数据
const personnelData = [
{ id: 'CL001', name: '张三', position: 'cleaner', grid: 'grid1', phone: '138****1234', status: 'online', attendance: '22/22', score: 96, lastUpdate: '2024-01-17 14:30' },
{ id: 'CL002', name: '李四', position: 'cleaner', grid: 'grid2', phone: '139****5678', status: 'offline', attendance: '21/22', score: 75, lastUpdate: '2024-01-17 12:15' },
{ id: 'SV001', name: '王五', position: 'supervisor', grid: 'grid1', phone: '137****9012', status: 'online', attendance: '22/22', score: 91, lastUpdate: '2024-01-17 14:35' },
{ id: 'CL006', name: '赵六', position: 'cleaner', grid: 'grid1', phone: '136****3456', status: 'online', attendance: '22/22', score: 88, lastUpdate: '2024-01-17 10:00' },
{ id: 'CL007', name: '钱七', position: 'cleaner', grid: 'grid3', phone: '135****7890', status: 'online', attendance: '21/22', score: 87, lastUpdate: '2024-01-17 09:30' }
];
const gridData = [
{ id: 'grid1', name: '网格A1', area: '0.8km²', personnel: ['CL001', 'SV001', 'CL006'], stats: { distance: '6.2km', completion: '95%' } },
{ id: 'grid2', name: '网格A2', area: '0.9km²', personnel: ['CL002'], stats: { distance: '3.1km', completion: '68%' } },
{ id: 'grid3', name: '网格B1', area: '1.2km²', personnel: ['CL007'], stats: { distance: '8.7km', completion: '92%' } },
{ id: 'grid4', name: '网格B2', area: '1.1km²', personnel: [], stats: { distance: '0km', completion: '0%' } }
];
const attendanceData = [
{ id: 'A001', personnelId: 'CL001', date: '2024-01-17', clockIn: '08:00', clockOut: '17:30', status: 'normal', distance: '6.2km', hours: '8.5小时' },
{ id: 'A002', personnelId: 'CL002', date: '2024-01-17', clockIn: '08:15', clockOut: '--', status: 'late', distance: '3.1km', hours: '--' },
{ id: 'A003', personnelId: 'SV001', date: '2024-01-17', clockIn: '07:45', clockOut: '18:00', status: 'normal', distance: '12.5km', hours: '9.2小时' },
{ id: 'A004', personnelId: 'CL006', date: '2024-01-17', clockIn: '--', clockOut: '--', status: 'absent', distance: '0km', hours: '0小时' },
{ id: 'A005', personnelId: 'CL007', date: '2024-01-17', clockIn: '08:05', clockOut: '17:45', status: 'normal', distance: '8.7km', hours: '8.7小时' }
];
const performanceData = [
{ id: 'P001', personnelId: 'CL001', period: '2024-01', attendanceRate: '100%', workHours: '187小时', workDistance: '136.4km', completionRate: '98%', qualityScore: '95分', totalScore: '96分' },
{ id: 'P002', personnelId: 'SV001', period: '2024-01', attendanceRate: '100%', workHours: '202小时', workDistance: '275km', completionRate: '96%', qualityScore: '92分', totalScore: '91分' },
{ id: 'P003', personnelId: 'CL007', period: '2024-01', attendanceRate: '95%', workHours: '178小时', workDistance: '191.3km', completionRate: '94%', qualityScore: '88分', totalScore: '87分' },
{ id: 'P004', personnelId: 'CL002', period: '2024-01', attendanceRate: '95%', workHours: '165小时', workDistance: '68.2km', completionRate: '85%', qualityScore: '82分', totalScore: '75分' },
{ id: 'P005', personnelId: 'CL006', period: '2024-01', attendanceRate: '100%', workHours: '180小时', workDistance: '120.5km', completionRate: '90%', qualityScore: '85分', totalScore: '88分' }
];
// 标签页切换
function switchTab(tabName) {
// 隐藏所有标签页内容
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
// 移除所有标签按钮的激活状态
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('active');
});
// 显示选中的标签页内容
document.getElementById(tabName).classList.add('active');
// 激活选中的标签按钮
event.target.classList.add('active');
// 根据标签页加载对应数据
switch (tabName) {
case 'overview':
loadOverviewData();
break;
case 'personnel':
loadPersonnelData();
break;
case 'grid':
loadGridData();
break;
case 'attendance':
loadAttendanceData();
break;
case 'performance':
loadPerformanceData();
break;
}
}
// 加载概览数据
function loadOverviewData() {
// 更新统计数据
document.getElementById('totalPersonnel').textContent = personnelData.length;
document.getElementById('onDutyPersonnel').textContent = personnelData.filter(p => p.status === 'online').length;
document.getElementById('gridCount').textContent = gridData.length;
// 计算平均出勤率
const totalPresent = personnelData.reduce((sum, p) => {
const [present] = p.attendance.split('/').map(Number);
return sum + present;
}, 0);
const totalDays = personnelData.reduce((sum, p) => {
const [, total] = p.attendance.split('/').map(Number);
return sum + total;
}, 0);
const avgAttendance = (totalPresent / totalDays * 100).toFixed(1);
document.getElementById('attendanceRate').textContent = `${avgAttendance}%`;
// 计算平均工作距离
const totalDistance = attendanceData.reduce((sum, a) => {
return sum + (parseFloat(a.distance) || 0);
}, 0);
const avgDistance = (totalDistance / attendanceData.length).toFixed(1);
document.getElementById('workDistance').textContent = `${avgDistance}km`;
// 计算平均完成率
const totalCompletion = gridData.reduce((sum, g) => {
return sum + (parseFloat(g.stats.completion) || 0);
}, 0);
const avgCompletion = (totalCompletion / gridData.length).toFixed(1);
document.getElementById('completionRate').textContent = `${avgCompletion}%`;
// 绘制图表
drawPerformanceChart();
}
// 加载人员数据
function loadPersonnelData() {
const personnelTableBody = document.getElementById('personnelTableBody');
if (!personnelTableBody) return;
const statusMap = {
'online': { text: '在岗', class: 'status-online' },
'offline': { text: '离岗', class: 'status-offline' }
};
const positionMap = {
'cleaner': '保洁员',
'supervisor': '班长',
'manager': '主管'
};
personnelTableBody.innerHTML = personnelData.map(person => {
const grid = gridData.find(g => g.id === person.grid);
return `
<tr>
<td>${person.id}</td>
<td>${person.name}</td>
<td>${positionMap[person.position]}</td>
<td>${grid ? grid.name : '未分配'}</td>
<td>${person.phone}</td>
<td><span class="status-badge ${statusMap[person.status].class}">${statusMap[person.status].text}</span></td>
<td>${person.attendance}</td>
<td>${person.score}分</td>
<td>
<button class="btn btn-primary" onclick="viewPersonnelDetails('${person.id}')">查看</button>
<button class="btn btn-success" onclick="editPersonnel('${person.id}')">编辑</button>
</td>
</tr>
`;
}).join('');
// 设置筛选器事件监听
setupFilterEvents();
}
// 设置筛选事件
function setupFilterEvents() {
document.getElementById('positionFilter').addEventListener('change', filterPersonnel);
document.getElementById('gridFilter').addEventListener('change', filterPersonnel);
document.getElementById('statusFilter').addEventListener('change', filterPersonnel);
document.getElementById('personnelSearch').addEventListener('input', filterPersonnel);
}
// 人员筛选功能
function filterPersonnel() {
const positionFilter = document.getElementById('positionFilter').value;
const gridFilter = document.getElementById('gridFilter').value;
const statusFilter = document.getElementById('statusFilter').value;
const searchText = document.getElementById('personnelSearch').value.toLowerCase();
const rows = document.querySelectorAll('#personnelTableBody tr');
rows.forEach(row => {
const name = row.cells[1].textContent.toLowerCase();
const id = row.cells[0].textContent.toLowerCase();
const position = row.cells[2].textContent;
const grid = row.cells[3].textContent;
const status = row.cells[5].textContent;
const matchesSearch = name.includes(searchText) || id.includes(searchText);
const matchesPosition = positionFilter === 'all' || position.includes(getPositionText(positionFilter));
const matchesGrid = gridFilter === 'all' || grid.includes(getGridText(gridFilter));
const matchesStatus = statusFilter === 'all' || status.includes(getStatusText(statusFilter));
row.style.display = matchesSearch && matchesPosition && matchesGrid && matchesStatus ? '' : 'none';
});
}
function getPositionText(value) {
const positions = { 'cleaner': '保洁员', 'supervisor': '班长', 'manager': '主管' };
return positions[value] || value;
}
function getGridText(value) {
const grid = gridData.find(g => g.id === value);
return grid ? grid.name : value;
}
function getStatusText(value) {
const statuses = { 'online': '在岗', 'offline': '离岗' };
return statuses[value] || value;
}
// 加载网格数据
function loadGridData() {
const gridAllocationGrid = document.getElementById('gridAllocationGrid');
if (!gridAllocationGrid) return;
gridAllocationGrid.innerHTML = gridData.map(grid => {
const personnelList = grid.personnel.map(id => {
const person = personnelData.find(p => p.id === id);
if (!person) return '';
return `
<div class="personnel-info">
<div class="personnel-icon"><i class="fas fa-user"></i></div>
<div class="personnel-details">
<h4>${person.name}</h4>
<p>${person.id} - ${getPositionText(person.position)}</p>
</div>
<span class="status-badge ${person.status === 'online' ? 'status-online' : 'status-offline'}">
${person.status === 'online' ? '在岗' : '离岗'}
</span>
</div>
`;
}).join('');
return `
<div class="personnel-item">
<div class="card-title">
<i class="fas fa-th"></i>
${grid.name} (${grid.area})
</div>
${personnelList || '<p style="color: #4a6fa5; text-align: center;">暂无人员分配</p>'}
<div class="personnel-params">
<div class="param-item">
<span class="param-value">${grid.stats.distance}</span>
<div class="param-label">今日作业</div>
</div>
<div class="param-item">
<span class="param-value">${grid.stats.completion}</span>
<div class="param-label">完成率</div>
</div>
</div>
<div style="margin-top: 15px;">
<button class="btn btn-primary" onclick="manageGrid('${grid.id}')"><i class="fas fa-cogs"></i> 网格管理</button>
<button class="btn btn-success" onclick="assignPersonnel('${grid.id}')"><i class="fas fa-hand-pointer"></i> 手动调整</button>
</div>
</div>
`;
}).join('');
}
// 更新日历相关函数
const scheduleData = {
currentDate: new Date()
};
// 加载月历
function loadMonthCalendar() {
const currentMonthTitle = document.getElementById('currentMonthTitle');
if (!currentMonthTitle) return;
const year = scheduleData.currentDate.getFullYear();
const month = scheduleData.currentDate.getMonth();
// 更新月份标题
currentMonthTitle.textContent = `${year}${month + 1}`;
// 获取当月第一天和最后一天
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
// 获取第一天是星期几 (0是周日)
const firstDayOfWeek = firstDay.getDay();
// 获取上个月的最后几天
const prevMonthLastDay = new Date(year, month, 0).getDate();
// 生成日历网格
const calendarGrid = document.querySelector('#schedule .calendar-grid');
if (!calendarGrid) return;
// 清空除表头外的所有日期单元格
while (calendarGrid.children.length > 7) {
calendarGrid.removeChild(calendarGrid.lastChild);
}
// 添加上个月的日期
for (let i = firstDayOfWeek - 1; i >= 0; i--) {
const day = prevMonthLastDay - i;
addDayCell(calendarGrid, new Date(year, month - 1, day), true);
}
// 添加当月的日期
for (let day = 1; day <= lastDay.getDate(); day++) {
const date = new Date(year, month, day);
addDayCell(calendarGrid, date, false);
}
// 添加下个月的日期 (补齐最后一行)
const totalCells = Math.ceil((firstDayOfWeek + lastDay.getDate()) / 7) * 7;
const nextMonthDays = totalCells - (firstDayOfWeek + lastDay.getDate());
for (let day = 1; day <= nextMonthDays; day++) {
addDayCell(calendarGrid, new Date(year, month + 1, day), true);
}
}
// 添加日期单元格
function addDayCell(calendarGrid, date, isOtherMonth) {
const dayNumber = date.getDate();
const dayKey = formatDate(date);
const today = new Date();
// 检查是否是今天
const isToday = date.getDate() === today.getDate() &&
date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear();
// 检查是否有排班
const hasSchedule = scheduleData.schedules &&
scheduleData.schedules[dayKey] &&
scheduleData.schedules[dayKey].length > 0;
// 创建日期单元格
const dayCell = document.createElement('div');
dayCell.className = 'calendar-day';
if (isToday) dayCell.classList.add('today');
if (hasSchedule) dayCell.classList.add('scheduled');
if (isOtherMonth) dayCell.classList.add('other-month');
// 添加日期数字
const dayNumberDiv = document.createElement('div');
dayNumberDiv.className = 'day-number';
dayNumberDiv.textContent = dayNumber;
dayCell.appendChild(dayNumberDiv);
// 添加人员列表
const dayStaffDiv = document.createElement('div');
dayStaffDiv.className = 'day-staff';
if (hasSchedule) {
scheduleData.schedules[dayKey].forEach(personId => {
const person = personnelData.find(p => p.id === personId);
if (person) {
const staffItem = document.createElement('div');
staffItem.className = 'day-staff-item';
staffItem.innerHTML = `<i class="fas fa-user"></i>${person.name} (${person.id})`;
dayStaffDiv.appendChild(staffItem);
}
});
} else if (!isOtherMonth) {
dayStaffDiv.textContent = '待排班';
}
dayCell.appendChild(dayStaffDiv);
calendarGrid.appendChild(dayCell);
}
// 上个月
function prevMonth() {
scheduleData.currentDate.setMonth(scheduleData.currentDate.getMonth() - 1);
loadMonthCalendar();
}
// 下个月
function nextMonth() {
scheduleData.currentDate.setMonth(scheduleData.currentDate.getMonth() + 1);
loadMonthCalendar();
}
// 初始化日历数据
scheduleData.schedules = {
'2024-01-15': ['CL001', 'CL002'],
'2024-01-16': ['CL001', 'SV001'],
'2024-01-17': ['CL002', 'SV001'],
'2024-01-22': ['CL001', 'CL007'],
'2024-01-23': ['CL002', 'SV001'],
'2024-01-24': ['CL006', 'SV001']
};
// 页面加载完成后初始化日历
document.addEventListener('DOMContentLoaded', function() {
loadMonthCalendar();
});
// 格式化日期为YYYY-MM-DD
function 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 loadAttendanceData() {
const attendanceTableBody = document.getElementById('attendanceTableBody');
if (!attendanceTableBody) return;
const statusMap = {
'normal': { text: '正常', class: 'status-normal' },
'late': { text: '迟到', class: 'status-late' },
'absent': { text: '缺勤', class: 'status-absent' }
};
attendanceTableBody.innerHTML = attendanceData.map(record => {
const person = personnelData.find(p => p.id === record.personnelId);
if (!person) return '';
return `
<tr>
<td>${person.name}</td>
<td>${record.personnelId}</td>
<td>${getGridText(person.grid)}</td>
<td>${record.clockIn}</td>
<td>${record.clockOut}</td>
<td>${record.hours}</td>
<td>${record.distance}</td>
<td><span class="status-badge ${statusMap[record.status].class}">${statusMap[record.status].text}</span></td>
<td>
<button class="btn btn-primary" onclick="viewAttendanceDetails('${record.id}')">详情</button>
</td>
</tr>
`;
}).join('');
// 设置筛选器事件监听
setupAttendanceFilterEvents();
}
// 设置考勤筛选事件
function setupAttendanceFilterEvents() {
document.getElementById('attendanceDate').addEventListener('change', filterAttendance);
document.getElementById('attendanceGridFilter').addEventListener('change', filterAttendance);
document.getElementById('attendanceStatusFilter').addEventListener('change', filterAttendance);
}
// 考勤筛选功能
function filterAttendance() {
const dateFilter = document.getElementById('attendanceDate').value;
const gridFilter = document.getElementById('attendanceGridFilter').value;
const statusFilter = document.getElementById('attendanceStatusFilter').value;
const rows = document.querySelectorAll('#attendanceTableBody tr');
rows.forEach(row => {
const grid = row.cells[2].textContent;
const status = row.cells[7].textContent;
const matchesGrid = gridFilter === 'all' || grid.includes(getGridText(gridFilter));
const matchesStatus = statusFilter === 'all' || status.includes(getAttendanceStatusText(statusFilter));
row.style.display = matchesGrid && matchesStatus ? '' : 'none';
});
}
function getAttendanceStatusText(value) {
const statuses = { 'normal': '正常', 'late': '迟到', 'absent': '缺勤' };
return statuses[value] || value;
}
// 加载绩效数据
function loadPerformanceData() {
const performanceTableBody = document.getElementById('performanceTableBody');
if (!performanceTableBody) return;
performanceTableBody.innerHTML = performanceData.map(record => {
const person = personnelData.find(p => p.id === record.personnelId);
if (!person) return '';
return `
<tr>
<td>${record.id.replace('P', '')}</td>
<td>${person.name}</td>
<td>${record.personnelId}</td>
<td>${record.attendanceRate}</td>
<td>${record.workHours}</td>
<td>${record.workDistance}</td>
<td>${record.completionRate}</td>
<td>${record.qualityScore}</td>
<td><strong style="color: ${getScoreColor(record.totalScore)};">${record.totalScore}</strong></td>
<td>
<button class="btn btn-primary" onclick="viewPerformanceDetails('${record.id}')">详情</button>
</td>
</tr>
`;
}).join('');
// 设置筛选器事件监听
setupPerformanceFilterEvents();
}
// 设置绩效筛选事件
function setupPerformanceFilterEvents() {
document.getElementById('performancePeriod').addEventListener('change', filterPerformance);
document.getElementById('performancePositionFilter').addEventListener('change', filterPerformance);
document.getElementById('performanceGridFilter').addEventListener('change', filterPerformance);
}
// 绩效筛选功能
function filterPerformance() {
const periodFilter = document.getElementById('performancePeriod').value;
const positionFilter = document.getElementById('performancePositionFilter').value;
const gridFilter = document.getElementById('performanceGridFilter').value;
const rows = document.querySelectorAll('#performanceTableBody tr');
rows.forEach(row => {
const id = row.cells[2].textContent;
const person = personnelData.find(p => p.id === id);
const matchesPosition = positionFilter === 'all' ||
(positionFilter === 'cleaner' && person.position === 'cleaner') ||
(positionFilter === 'supervisor' && person.position === 'supervisor');
const matchesGrid = gridFilter === 'all' ||
(person && person.grid === gridFilter);
row.style.display = matchesPosition && matchesGrid ? '' : 'none';
});
}
function getScoreColor(score) {
const num = parseInt(score);
if (num >= 90) return '#28a745';
if (num >= 80) return '#ffc107';
return '#dc3545';
}
// 绘制绩效图表
function drawPerformanceChart() {
const ctx = document.getElementById('performanceChart').getContext('2d');
if (!ctx) return;
// 生成1-12月的随机绩效数据(80-100之间)
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
const scores = months.map(() => Math.floor(80 + Math.random() * 20));
new Chart(ctx, {
type: 'line',
data: {
labels: months,
datasets: [{
label: '绩效分数',
data: scores,
borderColor: '#2a7de1',
backgroundColor: 'rgba(42, 125, 225, 0.1)',
tension: 0.4,
borderWidth: 2,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: {
color: '#1a4b8c',
font: {
weight: 'bold'
}
}
}
},
scales: {
x: {
ticks: { color: '#4a6fa5' },
grid: { color: 'rgba(209, 224, 245, 0.5)' }
},
y: {
min: 70,
max: 100,
ticks: { color: '#4a6fa5' },
grid: { color: 'rgba(209, 224, 245, 0.5)' }
}
}
}
});
}
// 绘制岗位分布图表
function drawPositionChart() {
const ctx = document.getElementById('positionChart').getContext('2d');
if (!ctx) return;
// 统计各岗位人数
const positionCounts = {
'cleaner': personnelData.filter(p => p.position === 'cleaner').length,
'supervisor': personnelData.filter(p => p.position === 'supervisor').length,
'manager': personnelData.filter(p => p.position === 'manager').length
};
new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['保洁员', '班长', '主管'],
datasets: [{
data: [positionCounts.cleaner, positionCounts.supervisor, positionCounts.manager],
backgroundColor: ['#2a7de1', '#28a745', '#ffc107'],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: {
color: '#1a4b8c',
font: {
weight: 'bold'
}
}
}
}
}
});
}
// 模态框操作
function openPersonnelModal() {
document.getElementById('personnelModal').style.display = 'block';
}
function closePersonnelModal() {
document.getElementById('personnelModal').style.display = 'none';
}
function openScheduleModal() {
document.getElementById('scheduleModal').style.display = 'block';
loadPersonnelChecklist();
}
function closeScheduleModal() {
document.getElementById('scheduleModal').style.display = 'none';
}
// 加载人员选择列表
function loadPersonnelChecklist() {
const personnelChecklist = document.getElementById('personnelChecklist');
if (!personnelChecklist) return;
personnelChecklist.innerHTML = personnelData.map(person => `
<div style="margin-bottom: 10px; padding: 8px; background: #f5f9ff; border-radius: 6px;">
<label style="display: flex; align-items: center; gap: 10px;">
<input type="checkbox" name="selectedPersonnel" value="${person.id}">
<div style="flex: 1;">
<div style="font-weight: 500; color: #1a4b8c;">${person.name} (${person.id})</div>
<div style="font-size: 12px; color: #4a6fa5;">
${getPositionText(person.position)} | ${getGridText(person.grid)} |
<span class="status-badge ${person.status === 'online' ? 'status-online' : 'status-offline'}"
style="font-size: 11px; padding: 2px 6px;">
${person.status === 'online' ? '在岗' : '离岗'}
</span>
</div>
</div>
</label>
</div>
`).join('');
}
// 人员操作函数
function viewPersonnelDetails(id) {
const person = personnelData.find(p => p.id === id);
if (!person) return;
const modal = createDetailsModal(
`${person.name} (${person.id})`,
`
<div class="form-grid">
<div class="form-group">
<label>岗位</label>
<div>${getPositionText(person.position)}</div>
</div>
<div class="form-group">
<label>所属网格</label>
<div>${getGridText(person.grid)}</div>
</div>
<div class="form-group">
<label>联系电话</label>
<div>${person.phone}</div>
</div>
<div class="form-group">
<label>在岗状态</label>
<div><span class="status-badge ${person.status === 'online' ? 'status-online' : 'status-offline'}">
${person.status === 'online' ? '在岗' : '离岗'}
</span></div>
</div>
<div class="form-group">
<label>本月考勤</label>
<div>${person.attendance}</div>
</div>
<div class="form-group">
<label>绩效评分</label>
<div><strong style="color: ${getScoreColor(person.score)};">${person.score}分</strong></div>
</div>
</div>
<div class="form-group">
<label>最后更新</label>
<div>${person.lastUpdate}</div>
</div>
`
);
document.body.appendChild(modal);
}
function editPersonnel(id) {
const person = personnelData.find(p => p.id === id);
if (!person) return;
const modal = createEditModal(
`编辑人员 - ${person.name} (${person.id})`,
`
<form id="editPersonnelForm">
<div class="form-grid">
<div class="form-group">
<label>姓名</label>
<input type="text" name="name" value="${person.name}" required>
</div>
<div class="form-group">
<label>岗位</label>
<select name="position" required>
<option value="cleaner" ${person.position === 'cleaner' ? 'selected' : ''}>保洁员</option>
<option value="supervisor" ${person.position === 'supervisor' ? 'selected' : ''}>班长</option>
<option value="manager" ${person.position === 'manager' ? 'selected' : ''}>主管</option>
</select>
</div>
<div class="form-group">
<label>所属网格</label>
<select name="grid" required>
${gridData.map(grid => `
<option value="${grid.id}" ${person.grid === grid.id ? 'selected' : ''}>
${grid.name}
</option>
`).join('')}
</select>
</div>
<div class="form-group">
<label>联系电话</label>
<input type="tel" name="phone" value="${person.phone}" required>
</div>
<div class="form-group">
<label>在岗状态</label>
<select name="status" required>
<option value="online" ${person.status === 'online' ? 'selected' : ''}>在岗</option>
<option value="offline" ${person.status === 'offline' ? 'selected' : ''}>离岗</option>
</select>
</div>
</div>
<div style="text-align: right; margin-top: 20px;">
<button type="button" class="btn" onclick="closeModal(this)">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
`
);
document.body.appendChild(modal);
// 表单提交处理
document.getElementById('editPersonnelForm').addEventListener('submit', function (e) {
e.preventDefault();
alert('人员信息已更新');
closeModal(document.querySelector('.modal-overlay'));
loadPersonnelData();
});
}
// 网格操作函数
function manageGrid(id) {
const grid = gridData.find(g => g.id === id);
if (!grid) return;
const modal = createDetailsModal(
`管理网格 - ${grid.name}`,
`
<div class="form-grid">
<div class="form-group">
<label>网格ID</label>
<div>${grid.id}</div>
</div>
<div class="form-group">
<label>覆盖面积</label>
<div>${grid.area}</div>
</div>
<div class="form-group">
<label>分配人员数</label>
<div>${grid.personnel.length}人</div>
</div>
<div class="form-group">
<label>今日作业</label>
<div>${grid.stats.distance}</div>
</div>
<div class="form-group">
<label>完成率</label>
<div>${grid.stats.completion}</div>
</div>
</div>
<div class="form-group">
<label>分配人员</label>
<div style="max-height: 150px; overflow-y: auto; border: 1px solid #e1e9f5; border-radius: 6px; padding: 10px;">
${grid.personnel.length > 0 ?
grid.personnel.map(id => {
const person = personnelData.find(p => p.id === id);
return person ? `
<div style="padding: 8px; border-bottom: 1px solid #e1e9f5; display: flex; justify-content: space-between; align-items: center;">
<span>${person.name} (${person.id})</span>
<button class="btn btn-danger" style="padding: 2px 8px; font-size: 12px;"
onclick="removeFromGrid('${grid.id}', '${person.id}')">
移除
</button>
</div>
` : '';
}).join('') :
'<div style="text-align: center; color: #4a6fa5; padding: 10px;">暂无分配人员</div>'
}
</div>
</div>
<div style="text-align: right; margin-top: 20px;">
<button class="btn btn-primary" onclick="assignPersonnel('${grid.id}')">分配人员</button>
</div>
`
);
document.body.appendChild(modal);
}
function assignPersonnel(gridId) {
const grid = gridData.find(g => g.id === gridId);
if (!grid) return;
const modal = createEditModal(
`${grid.name} 分配人员`,
`
<div style="max-height: 400px; overflow-y: auto;">
${personnelData.map(person => `
<div style="margin-bottom: 10px; padding: 10px; background: #f5f9ff; border-radius: 6px;
display: flex; align-items: center; justify-content: space-between;">
<div>
<div style="font-weight: 500; color: #1a4b8c;">${person.name} (${person.id})</div>
<div style="font-size: 12px; color: #4a6fa5;">
${getPositionText(person.position)} | ${getGridText(person.grid)}
</div>
</div>
<label style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" ${grid.personnel.includes(person.id) ? 'checked' : ''}
onchange="toggleGridAssignment('${gridId}', '${person.id}', this.checked)">
<span style="font-size: 12px;">分配</span>
</label>
</div>
`).join('')}
</div>
<div style="text-align: right; margin-top: 20px;">
<button type="button" class="btn" onclick="closeModal(this)">完成</button>
</div>
`
);
document.body.appendChild(modal);
}
function toggleGridAssignment(gridId, personnelId, assign) {
const grid = gridData.find(g => g.id === gridId);
if (!grid) return;
if (assign) {
if (!grid.personnel.includes(personnelId)) {
grid.personnel.push(personnelId);
}
} else {
grid.personnel = grid.personnel.filter(id => id !== personnelId);
}
// 更新网格数据
loadGridData();
}
function removeFromGrid(gridId, personnelId) {
const grid = gridData.find(g => g.id === gridId);
if (!grid) return;
grid.personnel = grid.personnel.filter(id => id !== personnelId);
// 关闭当前模态框并重新打开管理界面
closeModal(document.querySelector('.modal-overlay'));
manageGrid(gridId);
}
// 考勤操作函数
function viewAttendanceDetails(id) {
const record = attendanceData.find(a => a.id === id);
if (!record) return;
const person = personnelData.find(p => p.id === record.personnelId);
if (!person) return;
const modal = createDetailsModal(
`考勤详情 - ${person.name} (${record.personnelId})`,
`
<div class="form-grid">
<div class="form-group">
<label>日期</label>
<div>${record.date}</div>
</div>
<div class="form-group">
<label>上班打卡</label>
<div>${record.clockIn}</div>
</div>
<div class="form-group">
<label>下班打卡</label>
<div>${record.clockOut}</div>
</div>
<div class="form-group">
<label>工作时长</label>
<div>${record.hours}</div>
</div>
<div class="form-group">
<label>作业里程</label>
<div>${record.distance}</div>
</div>
<div class="form-group">
<label>考勤状态</label>
<div><span class="status-badge ${getAttendanceStatusClass(record.status)}">
${getAttendanceStatusText(record.status)}
</span></div>
</div>
</div>
<div style="text-align: right; margin-top: 20px;">
<button class="btn btn-primary" onclick="editAttendance('${record.id}')">编辑</button>
</div>
`
);
document.body.appendChild(modal);
}
function editAttendance(id) {
const record = attendanceData.find(a => a.id === id);
if (!record) return;
const person = personnelData.find(p => p.id === record.personnelId);
if (!person) return;
const modal = createEditModal(
`编辑考勤 - ${person.name} (${record.personnelId})`,
`
<form id="editAttendanceForm">
<div class="form-grid">
<div class="form-group">
<label>日期</label>
<input type="date" name="date" value="${record.date}" required>
</div>
<div class="form-group">
<label>上班打卡</label>
<input type="time" name="clockIn" value="${record.clockIn === '--' ? '' : record.clockIn}">
</div>
<div class="form-group">
<label>下班打卡</label>
<input type="time" name="clockOut" value="${record.clockOut === '--' ? '' : record.clockOut}">
</div>
<div class="form-group">
<label>考勤状态</label>
<select name="status" required>
<option value="normal" ${record.status === 'normal' ? 'selected' : ''}>正常</option>
<option value="late" ${record.status === 'late' ? 'selected' : ''}>迟到</option>
<option value="absent" ${record.status === 'absent' ? 'selected' : ''}>缺勤</option>
</select>
</div>
<div class="form-group">
<label>作业里程</label>
<input type="text" name="distance" value="${record.distance}" required>
</div>
<div class="form-group">
<label>工作时长</label>
<input type="text" name="hours" value="${record.hours}" required>
</div>
</div>
<div style="text-align: right; margin-top: 20px;">
<button type="button" class="btn" onclick="closeModal(this)">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
`
);
document.body.appendChild(modal);
// 表单提交处理
document.getElementById('editAttendanceForm').addEventListener('submit', function (e) {
e.preventDefault();
alert('考勤记录已更新');
closeModal(document.querySelector('.modal-overlay'));
loadAttendanceData();
});
}
function getAttendanceStatusClass(status) {
return {
'normal': 'status-normal',
'late': 'status-late',
'absent': 'status-absent'
}[status] || '';
}
// 绩效操作函数
function viewPerformanceDetails(id) {
const record = performanceData.find(p => p.id === id);
if (!record) return;
const person = personnelData.find(p => p.id === record.personnelId);
if (!person) return;
const modal = createDetailsModal(
`绩效详情 - ${person.name} (${record.personnelId})`,
`
<div class="form-grid">
<div class="form-group">
<label>考核周期</label>
<div>${record.period}</div>
</div>
<div class="form-group">
<label>出勤率</label>
<div>${record.attendanceRate}</div>
</div>
<div class="form-group">
<label>作业时长</label>
<div>${record.workHours}</div>
</div>
<div class="form-group">
<label>作业里程</label>
<div>${record.workDistance}</div>
</div>
<div class="form-group">
<label>任务完成率</label>
<div>${record.completionRate}</div>
</div>
<div class="form-group">
<label>质量评分</label>
<div>${record.qualityScore}</div>
</div>
<div class="form-group">
<label>综合评分</label>
<div><strong style="color: ${getScoreColor(record.totalScore)}; font-size: 18px;">
${record.totalScore}
</strong></div>
</div>
</div>
<div style="text-align: right; margin-top: 20px;">
<button class="btn btn-primary" onclick="editPerformance('${record.id}')">编辑</button>
</div>
`
);
document.body.appendChild(modal);
}
function editPerformance(id) {
const record = performanceData.find(p => p.id === id);
if (!record) return;
const person = personnelData.find(p => p.id === record.personnelId);
if (!person) return;
const modal = createEditModal(
`编辑绩效 - ${person.name} (${record.personnelId})`,
`
<form id="editPerformanceForm">
<div class="form-grid">
<div class="form-group">
<label>考核周期</label>
<input type="text" name="period" value="${record.period}" required>
</div>
<div class="form-group">
<label>出勤率</label>
<input type="text" name="attendanceRate" value="${record.attendanceRate}" required>
</div>
<div class="form-group">
<label>作业时长</label>
<input type="text" name="workHours" value="${record.workHours}" required>
</div>
<div class="form-group">
<label>作业里程</label>
<input type="text" name="workDistance" value="${record.workDistance}" required>
</div>
<div class="form-group">
<label>任务完成率</label>
<input type="text" name="completionRate" value="${record.completionRate}" required>
</div>
<div class="form-group">
<label>质量评分</label>
<input type="text" name="qualityScore" value="${record.qualityScore}" required>
</div>
<div class="form-group">
<label>综合评分</label>
<input type="text" name="totalScore" value="${record.totalScore.replace('分', '')}" required>
</div>
</div>
<div style="text-align: right; margin-top: 20px;">
<button type="button" class="btn" onclick="closeModal(this)">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
`
);
document.body.appendChild(modal);
// 表单提交处理
document.getElementById('editPerformanceForm').addEventListener('submit', function (e) {
e.preventDefault();
alert('绩效记录已更新');
closeModal(document.querySelector('.modal-overlay'));
loadPerformanceData();
});
}
// 通用模态框函数
function createDetailsModal(title, content) {
const modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.innerHTML = `
<div class="modal-content" style="max-width: 600px;">
<div class="modal-header">
<h3>${title}</h3>
<button class="modal-close" onclick="closeModal(this)">&times;</button>
</div>
<div class="modal-body">
${content}
</div>
</div>
`;
return modal;
}
function createEditModal(title, content) {
const modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.innerHTML = `
<div class="modal-content" style="max-width: 700px;">
<div class="modal-header">
<h3>${title}</h3>
<button class="modal-close" onclick="closeModal(this)">&times;</button>
</div>
<div class="modal-body">
${content}
</div>
</div>
`;
return modal;
}
function closeModal(element) {
const modal = element.closest('.modal-overlay');
modal.remove();
}
function closeModal2(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
modal.style.display = 'none';
}
}
// 表单提交
document.getElementById('personnelForm').addEventListener('submit', function (e) {
e.preventDefault();
alert('人员档案创建成功!');
closePersonnelModal();
loadPersonnelData();
});
document.getElementById('scheduleForm').addEventListener('submit', function (e) {
e.preventDefault();
alert('排班计划创建成功!');
closeScheduleModal();
loadGridData();
});
// 导出报表
function exportReport() {
alert('导出人员报表功能将在新窗口打开');
// 实际应用中这里会生成并下载报表
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function () {
// 初始化加载概览数据
loadOverviewData();
// 加载侧边栏数据
loadSidebarData();
// 设置筛选器事件监听
setupFilterEvents();
setupAttendanceFilterEvents();
setupPerformanceFilterEvents();
// 绘制岗位分布图表
drawPositionChart();
});
// 加载侧边栏数据
function loadSidebarData() {
const attendanceAlerts = document.getElementById('attendanceAlerts');
const scheduleReminders = document.getElementById('scheduleReminders');
if (attendanceAlerts) {
attendanceAlerts.innerHTML = `
<div class="alert-item">
<div class="alert-title">李四 (CL002) 迟到</div>
<div class="alert-info">
<span>2024-01-17 08:15</span>
<span>迟到15分钟</span>
</div>
</div>
<div class="alert-item">
<div class="alert-title">赵六 (CL006) 未打卡</div>
<div class="alert-info">
<span>2024-01-17</span>
<span>缺勤</span>
</div>
</div>
`;
}
if (scheduleReminders) {
scheduleReminders.innerHTML = `
<div class="alert-item">
<div class="alert-title">明日早班排班</div>
<div class="alert-info">
<span>2024-01-18 08:00</span>
<span>网格A1</span>
</div>
</div>
<div class="alert-item">
<div class="alert-title">下周排班计划</div>
<div class="alert-info">
<span>2024-01-22</span>
<span>待确认</span>
</div>
</div>
`;
}
}
// 点击模态框外部关闭
window.onclick = function (event) {
const modals = document.querySelectorAll('.modal-overlay');
modals.forEach(modal => {
if (event.target === modal) {
modal.remove();
}
});
}
// 网格数据
let gridData1 = [
{ id: 'grid1', name: '网格A1', area: '0.8km²', description: '主要负责A区东侧清洁', personnel: ['CL001', 'SV001', 'CL006'], stats: { distance: '6.2km', completion: '95%' } },
{ id: 'grid2', name: '网格A2', area: '0.9km²', description: '主要负责A区西侧清洁', personnel: ['CL002'], stats: { distance: '3.1km', completion: '68%' } },
{ id: 'grid3', name: '网格B1', area: '1.2km²', description: '主要负责B区北侧清洁', personnel: ['CL007'], stats: { distance: '8.7km', completion: '92%' } },
{ id: 'grid4', name: '网格B2', area: '1.1km²', description: '主要负责B区南侧清洁', personnel: [], stats: { distance: '0km', completion: '0%' } }
];
// 智能分配
function optimizeAllocation() {
showNotification('正在进行智能分配...', 'success');
// 模拟智能分配过程
setTimeout(() => {
// 随机分配人员到网格
gridData1.forEach(grid => {
// 清空当前分配
grid.personnel = [];
// 随机分配1-3人
const randomCount = Math.floor(Math.random() * 3) + 1;
const availablePersonnel = personnelData.filter(p =>
p.position === 'cleaner' &&
!gridData1.some(g => g.id !== grid.id && g.personnel.includes(p.id))
);
// 随机选择人员
for (let i = 0; i < randomCount && i < availablePersonnel.length; i++) {
const randomIndex = Math.floor(Math.random() * availablePersonnel.length);
grid.personnel.push(availablePersonnel[randomIndex].id);
availablePersonnel.splice(randomIndex, 1);
}
// 更新网格统计
grid.stats.distance = (Math.random() * 10 + 1).toFixed(1) + 'km';
grid.stats.completion = Math.floor(Math.random() * 30 + 70) + '%';
});
// 更新显示
loadGridData();
showNotification('智能分配完成', 'success');
}, 1500);
}
// 网格管理
function manageGrids() {
document.getElementById('gridManageModal').style.display = 'block';
loadGridTable();
}
// 加载网格表格数据
function loadGridTable() {
const gridTableBody = document.getElementById('gridTableBody');
if (!gridTableBody) return;
gridTableBody.innerHTML = gridData1.map(grid => `
<tr>
<td>${grid.id}</td>
<td>${grid.name}</td>
<td>${grid.area}</td>
<td>${grid.description}</td>
<td>
<button class="btn btn-primary" onclick="editGrid('${grid.id}')">编辑</button>
<button class="btn btn-danger" onclick="deleteGrid('${grid.id}')">删除</button>
</td>
</tr>
`).join('');
}
// 显示新增网格表单
function showAddGridForm() {
document.getElementById('gridFormTitle').textContent = '新增网格';
document.getElementById('gridForm').reset();
document.getElementById('gridId').value = '';
document.getElementById('gridName').value = '';
document.getElementById('gridArea').value = '';
document.getElementById('gridDescription').value = '';
document.getElementById('gridManageModal').style.display = 'none';
document.getElementById('gridFormModal').style.display = 'block';
}
// 编辑网格
function editGrid(id) {
const grid = gridData1.find(g => g.id === id);
if (!grid) return;
document.getElementById('gridFormTitle').textContent = '编辑网格';
document.getElementById('gridId').value = grid.id;
document.getElementById('gridName').value = grid.name;
document.getElementById('gridArea').value = grid.area;
document.getElementById('gridDescription').value = grid.description;
document.getElementById('gridManageModal').style.display = 'none';
document.getElementById('gridFormModal').style.display = 'block';
}
// 删除网格
function deleteGrid(id) {
if (confirm('确定要删除此网格吗?删除后无法恢复!')) {
const index = gridData1.findIndex(g => g.id === id);
if (index !== -1) {
gridData1.splice(index, 1);
loadGridTable();
loadGridData(); // 更新网格分配显示
showNotification('网格删除成功', 'success');
}
}
}
// 保存网格表单
document.getElementById('gridForm').addEventListener('submit', function (e) {
e.preventDefault();
const id = document.getElementById('gridId').value;
const name = document.getElementById('gridName').value;
const area = document.getElementById('gridArea').value;
const description = document.getElementById('gridDescription').value;
if (id) {
// 编辑现有网格
const grid = gridData1.find(g => g.id === id);
if (grid) {
grid.name = name;
grid.area = area;
grid.description = description;
showNotification('网格信息已更新', 'success');
}
} else {
// 新增网格
const newId = 'grid' + (gridData1.length + 1);
gridData1.push({
id: newId,
name: name,
area: area,
description: description,
personnel: [],
stats: { distance: '0km', completion: '0%' }
});
showNotification('网格添加成功', 'success');
}
// 关闭表单模态框,重新打开网格管理模态框
document.getElementById('gridFormModal').style.display = 'none';
document.getElementById('gridManageModal').style.display = 'block';
// 刷新数据
loadGridTable();
loadGridData();
});
// // 加载网格分配数据
// function loadGridData() {
// const gridAllocationGrid = document.getElementById('gridAllocationGrid');
// if (!gridAllocationGrid) return;
// gridAllocationGrid.innerHTML = gridData1.map(grid => {
// const personnelList = grid.personnel.map(id => {
// const person = personnelData.find(p => p.id === id);
// if (!person) return '';
// return `
// <div class="personnel-info">
// <div class="personnel-icon"><i class="fas fa-user"></i></div>
// <div class="personnel-details">
// <h4>${person.name}</h4>
// <p>${person.id} - ${getPositionText(person.position)}</p>
// </div>
// <span class="status-badge ${person.status === 'online' ? 'status-online' : 'status-offline'}">
// ${person.status === 'online' ? '在岗' : '离岗'}
// </span>
// </div>
// `;
// }).join('');
// return `
// <div class="personnel-item">
// <div class="card-title">
// <i class="fas fa-th"></i>
// ${grid.name} (${grid.area})
// </div>
// ${personnelList || '<p style="color: #4a6fa5; text-align: center;">暂无人员分配</p>'}
// <div class="personnel-params">
// <div class="param-item">
// <span class="param-value">${grid.stats.distance}</span>
// <div class="param-label">今日作业</div>
// </div>
// <div class="param-item">
// <span class="param-value">${grid.stats.completion}</span>
// <div class="param-label">完成率</div>
// </div>
// </div>
// <div style="margin-top: 15px;">
// <button class="btn btn-primary" onclick="manageGridPersonnel('${grid.id}')">管理</button>
// </div>
// </div>
// `;
// }).join('');
// }
// 管理网格人员
function manageGridPersonnel(gridId) {
const grid = gridData1.find(g => g.id === gridId);
if (!grid) return;
const modal = createDetailsModal(
`管理网格人员 - ${grid.name}`,
`
<div style="max-height: 400px; overflow-y: auto;">
${personnelData.map(person => `
<div style="margin-bottom: 10px; padding: 10px; background: #f5f9ff; border-radius: 6px;
display: flex; align-items: center; justify-content: space-between;">
<div>
<div style="font-weight: 500; color: #1a4b8c;">${person.name} (${person.id})</div>
<div style="font-size: 12px; color: #4a6fa5;">
${getPositionText(person.position)} | ${getGridText(person.grid)}
</div>
</div>
<label style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" ${grid.personnel.includes(person.id) ? 'checked' : ''}
onchange="toggleGridAssignment('${gridId}', '${person.id}', this.checked)">
<span style="font-size: 12px;">分配</span>
</label>
</div>
`).join('')}
</div>
<div style="text-align: right; margin-top: 20px;">
<button type="button" class="btn" onclick="closeModal(this)">完成</button>
</div>
`
);
document.body.appendChild(modal);
}
// 切换网格分配
function toggleGridAssignment(gridId, personnelId, assign) {
const grid = gridData1.find(g => g.id === gridId);
if (!grid) return;
if (assign) {
if (!grid.personnel.includes(personnelId)) {
grid.personnel.push(personnelId);
// 从其他网格中移除该人员
gridData1.forEach(g => {
if (g.id !== gridId && g.personnel.includes(personnelId)) {
g.personnel = g.personnel.filter(id => id !== personnelId);
}
});
}
} else {
grid.personnel = grid.personnel.filter(id => id !== personnelId);
}
// 更新网格数据
loadGridData();
}
// 显示通知
function showNotification(message, type = 'success') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
</script>
</body>
</html>