0909
This commit is contained in:
		| @ -11,3 +11,34 @@ export const keyIndex = () => { | |||||||
|     method: 'get' |     method: 'get' | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 每个项目的出勤人数 | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | export const projectAttendanceCount = () => { | ||||||
|  |   return request({ | ||||||
|  |     url: '/enterprise/big/screen/projectAttendanceCount', | ||||||
|  |     method: 'get' | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | /** | ||||||
|  |  * 人数统计 | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | export const peopleCount = () => { | ||||||
|  |   return request({ | ||||||
|  |     url: '/enterprise/big/screen/peopleCount', | ||||||
|  |     method: 'get' | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | /** | ||||||
|  |  * 出勤人数统计 | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | export const allAttendanceCount = () => { | ||||||
|  |   return request({ | ||||||
|  |     url: '/enterprise/big/screen/allAttendanceCount', | ||||||
|  |     method: 'get' | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								src/assets/images/man.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/images/man.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 26 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/images/map.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/images/map.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 360 KiB | 
| @ -18,43 +18,73 @@ | |||||||
|  |  | ||||||
|     <div class="endPage"> |     <div class="endPage"> | ||||||
|       <Title style="font-size: 22px" title="人员情况" /> |       <Title style="font-size: 22px" title="人员情况" /> | ||||||
|       <div class="map"> |       <!-- 人员总览区域 --> | ||||||
|         <div class="project_attendance_chart"> |       <div class="people_overview"> | ||||||
|           <Title style="font-size: 22px" title="各项目人员出勤率" /> |         <div class="people_overview_content"> | ||||||
|           <div class="chart_content" ref="attendanceChartRef"></div> |           <div class="people_image"> | ||||||
|  |             <!-- 本地图片占位,后续可替换为实际图片路径 --> | ||||||
|  |             <img src="@/assets/images/man.png" alt="人员总览" class="placeholder_image" /> | ||||||
|           </div> |           </div> | ||||||
|         <div class="attendance_tag"> |           <div class="people_stats"> | ||||||
|           <div class="tag_item"> |             <div class="stat_item"> | ||||||
|             <img src="@/assets/projectLarge/people.svg" alt="" /> |               <div class="stat_label">出勤人数</div> | ||||||
|             <div class="tag_title">出勤人</div> |               <div class="stat_value">{{ attendanceCount }}</div> | ||||||
|             <div class="tag_info"> |             </div> | ||||||
|               {{ attendanceCount }} |             <div class="stat_item"> | ||||||
|               <span style="font-size: 14px">人</span> |               <div class="stat_label">出勤率</div> | ||||||
|  |               <div class="stat_rate">{{ attendanceRate }}%</div> | ||||||
|             </div> |             </div> | ||||||
|           </div> |           </div> | ||||||
|           <div class="tag_item"> |  | ||||||
|             <img src="@/assets/projectLarge/people.svg" alt="" /> |  | ||||||
|             <div class="tag_title">在岗人</div> |  | ||||||
|             <div class="tag_info"> |  | ||||||
|               {{ peopleCount }} |  | ||||||
|               <span style="font-size: 14px">人</span> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|           <div class="tag_item"> |  | ||||||
|             <img src="@/assets/projectLarge/people.svg" alt="" /> |  | ||||||
|             <div class="tag_title">出勤率</div> |  | ||||||
|             <div class="tag_info"> |  | ||||||
|               {{ attendanceRate }} |  | ||||||
|               <span style="font-size: 14px">%</span> |  | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |       <!-- 点阵地图 --> | ||||||
|  |       <div class="people_map"> | ||||||
|  |         <div class="map_container"> | ||||||
|  |           <img src="@/assets/images/map.png" /> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|       <div class="attendance_tag"></div> |       <!-- 人员分类统计 --> | ||||||
|  |       <div class="people_categories"> | ||||||
|  |         <div class="category_item"> | ||||||
|  |           <div class="category_icon"> | ||||||
|  |             <!-- 本地图片占位,后续可替换为实际图片路径 --> | ||||||
|  |             <img src="@/assets/images/constructor.png" alt="施工人员" class="category_image" /> | ||||||
|  |           </div> | ||||||
|  |           <div class="category_info"> | ||||||
|  |             <div class="category_label">施工人员</div> | ||||||
|  |             <div class="category_count">{{ constructionPersonnelCount }}</div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="category_item"> | ||||||
|  |           <div class="category_icon"> | ||||||
|  |             <!-- 本地图片占位,后续可替换为实际图片路径 --> | ||||||
|  |             <img src="@/assets/images/subcontractor.png" alt="分包人员" class="category_image" /> | ||||||
|  |           </div> | ||||||
|  |           <div class="category_info"> | ||||||
|  |             <div class="category_label">分包人员</div> | ||||||
|  |             <div class="category_count">{{ subcontractorsCount }}</div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="category_item"> | ||||||
|  |           <div class="category_icon"> | ||||||
|  |             <!-- 本地图片占位,后续可替换为实际图片路径 --> | ||||||
|  |             <img src="@/assets/images/manager.png" alt="管理人员" class="category_image" /> | ||||||
|  |           </div> | ||||||
|  |           <div class="category_info"> | ||||||
|  |             <div class="category_label">管理人员</div> | ||||||
|  |             <div class="category_count">{{ managersCount }}</div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|       <!-- 项目出勤率柱状图 --> |       <!-- 项目出勤率柱状图 --> | ||||||
|  |       <div class="project_attendance_chart"> | ||||||
|  |         <Title style="font-size: 22px" title="项目出勤率统计" /> | ||||||
|  |  | ||||||
|  |         <div class="chart_content" ref="attendanceChartRef"></div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| @ -62,7 +92,7 @@ | |||||||
| import { ref } from 'vue'; | import { ref } from 'vue'; | ||||||
| import Title from './title.vue'; | import Title from './title.vue'; | ||||||
| import { getScreenNews, getScreenPeople } from '@/api/projectScreen'; | import { getScreenNews, getScreenPeople } from '@/api/projectScreen'; | ||||||
| import { keyIndex } from '@/api/enterpriseLarge/index'; | import { keyIndex, projectAttendanceCount, peopleCount, allAttendanceCount } from '@/api/enterpriseLarge/index'; | ||||||
| import { mapOption } from './optionList'; | import { mapOption } from './optionList'; | ||||||
| import * as echarts from 'echarts'; | import * as echarts from 'echarts'; | ||||||
|  |  | ||||||
| @ -114,9 +144,14 @@ const newDetail = ref({ | |||||||
| const newId = ref(''); | const newId = ref(''); | ||||||
| const attendanceCount = ref(0); | const attendanceCount = ref(0); | ||||||
| const attendanceRate = ref(0); | const attendanceRate = ref(0); | ||||||
| const peopleCount = ref(0); | const totalPeopleCount = ref(0); | ||||||
| const teamAttendanceList = ref([{ id: '', teamName: '', attendanceNumber: 0, allNumber: 0, attendanceRate: 0, attendanceTime: '' }]); | const teamAttendanceList = ref([{ id: '', teamName: '', attendanceNumber: 0, allNumber: 0, attendanceRate: 0, attendanceTime: '' }]); | ||||||
|  |  | ||||||
|  | // 人员分类统计数据 | ||||||
|  | const constructionPersonnelCount = ref(0); | ||||||
|  | const managersCount = ref(0); | ||||||
|  | const subcontractorsCount = ref(0); | ||||||
|  |  | ||||||
| // 项目出勤率数据 | // 项目出勤率数据 | ||||||
| const projectAttendanceData = ref([ | const projectAttendanceData = ref([ | ||||||
|   { name: 'A项目', value: 62 }, |   { name: 'A项目', value: 62 }, | ||||||
| @ -129,6 +164,11 @@ const projectAttendanceData = ref([ | |||||||
| let attendanceChart = null; | let attendanceChart = null; | ||||||
| const attendanceChartRef = ref<HTMLDivElement | null>(null); | const attendanceChartRef = ref<HTMLDivElement | null>(null); | ||||||
|  |  | ||||||
|  | // 滚动相关状态 | ||||||
|  | const scrollInterval = ref<number | null>(null); | ||||||
|  | const currentScrollIndex = ref(0); | ||||||
|  | const scrollSpeed = 1000; // 滚动间隔时间(ms) | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 获取项目人员出勤数据 |  * 获取项目人员出勤数据 | ||||||
|  */ |  */ | ||||||
| @ -136,13 +176,189 @@ const getPeopleData = async () => { | |||||||
|   const res = await getScreenPeople(props.projectId); |   const res = await getScreenPeople(props.projectId); | ||||||
|   const { data, code } = res; |   const { data, code } = res; | ||||||
|   if (code === 200) { |   if (code === 200) { | ||||||
|     attendanceCount.value = data.attendanceCount; |     totalPeopleCount.value = data.peopleCount; | ||||||
|     attendanceRate.value = data.attendanceRate; |  | ||||||
|     peopleCount.value = data.peopleCount; |  | ||||||
|     teamAttendanceList.value = data.teamAttendanceList; |     teamAttendanceList.value = data.teamAttendanceList; | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 获取人员分类统计数据 | ||||||
|  |  */ | ||||||
|  | const getPeopleCategoryData = async () => { | ||||||
|  |   try { | ||||||
|  |     const res = await peopleCount(); | ||||||
|  |     const { data, code } = res; | ||||||
|  |     if (code === 200 && data) { | ||||||
|  |       constructionPersonnelCount.value = data.constructionPersonnelCount || 0; | ||||||
|  |       managersCount.value = data.managersCount || 0; | ||||||
|  |       subcontractorsCount.value = data.subcontractorsCount || 0; | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('获取人员分类统计数据失败:', error); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 获取人员总览数据 - 通过allAttendanceCount接口 | ||||||
|  |  */ | ||||||
|  | const getProjectAttendanceCount = async () => { | ||||||
|  |   try { | ||||||
|  |     const res = await allAttendanceCount(); | ||||||
|  |     const { data, code } = res; | ||||||
|  |     if (code === 0 && data) { | ||||||
|  |       // 直接使用接口返回的数据 | ||||||
|  |       attendanceCount.value = data.attendanceCount || 0; | ||||||
|  |       attendanceRate.value = data.attendanceRate || 0; | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('获取人员总览数据失败:', error); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 滚动到指定项目 | ||||||
|  |  */ | ||||||
|  | const scrollToProject = (index: number) => { | ||||||
|  |   if (!attendanceChart || projectAttendanceData.value.length === 0) return; | ||||||
|  |  | ||||||
|  |   // 计算实际索引 | ||||||
|  |   const realIndex = index % projectAttendanceData.value.length; | ||||||
|  |   currentScrollIndex.value = realIndex; | ||||||
|  |  | ||||||
|  |   // 获取项目名称 | ||||||
|  |   const projectName = projectAttendanceData.value[realIndex].name; | ||||||
|  |  | ||||||
|  |   // 触发tooltip显示 | ||||||
|  |   attendanceChart.dispatchAction({ | ||||||
|  |     type: 'showTip', | ||||||
|  |     seriesIndex: 0, | ||||||
|  |     dataIndex: realIndex | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   // 高亮当前项目的柱子 | ||||||
|  |   attendanceChart.dispatchAction({ | ||||||
|  |     type: 'highlight', | ||||||
|  |     seriesIndex: 0, | ||||||
|  |     dataIndex: realIndex | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   // 取消高亮其他项目的柱子 | ||||||
|  |   attendanceChart.dispatchAction({ | ||||||
|  |     type: 'downplay', | ||||||
|  |     seriesIndex: 0, | ||||||
|  |     dataIndex: Array.from({ length: projectAttendanceData.value.length }, (_, i) => i).filter((i) => i !== realIndex) | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   // 更新滚动条位置,确保滚动时滚动条同步移动 | ||||||
|  |   // 无论项目数量多少,都应该同步滚动条 | ||||||
|  |   if (projectAttendanceData.value.length > 0) { | ||||||
|  |     // 计算滚动条应该移动到的位置 | ||||||
|  |     // 确保当前项目居中显示 | ||||||
|  |     const totalProjects = projectAttendanceData.value.length; | ||||||
|  |     const visiblePercentage = 15; // 与dataZoom的end值保持一致 | ||||||
|  |     const itemPercentage = 100 / totalProjects; // 每个项目所占总宽度的百分比 | ||||||
|  |  | ||||||
|  |     // 计算新的start值,使当前项目尽量居中显示 | ||||||
|  |     let newStart = realIndex * itemPercentage - visiblePercentage / 2 + itemPercentage / 2; | ||||||
|  |  | ||||||
|  |     // 确保start值在有效范围内 | ||||||
|  |     newStart = Math.max(0, Math.min(100 - visiblePercentage, newStart)); | ||||||
|  |  | ||||||
|  |     // 更新dataZoom组件的位置 | ||||||
|  |     attendanceChart.dispatchAction({ | ||||||
|  |       type: 'dataZoom', | ||||||
|  |       start: newStart, | ||||||
|  |       end: newStart + visiblePercentage | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 开始自动滚动 | ||||||
|  |  */ | ||||||
|  | const startScroll = () => { | ||||||
|  |   if (scrollInterval.value) return; | ||||||
|  |  | ||||||
|  |   scrollInterval.value = window.setInterval(() => { | ||||||
|  |     currentScrollIndex.value++; | ||||||
|  |     scrollToProject(currentScrollIndex.value); | ||||||
|  |   }, scrollSpeed); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 停止自动滚动 | ||||||
|  |  */ | ||||||
|  | const stopScroll = () => { | ||||||
|  |   if (scrollInterval.value) { | ||||||
|  |     clearInterval(scrollInterval.value); | ||||||
|  |     scrollInterval.value = null; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 获取项目出勤率统计数据 - 保持项目出勤率图表功能 | ||||||
|  |  */ | ||||||
|  | const getProjectAttendanceStats = async () => { | ||||||
|  |   try { | ||||||
|  |     const res = await projectAttendanceCount(); | ||||||
|  |     const { data, code } = res; | ||||||
|  |  | ||||||
|  |     // 添加日志信息以便调试 | ||||||
|  |     console.log('projectAttendanceCount接口返回数据:', res); | ||||||
|  |  | ||||||
|  |     if (code === 200 && data && data.length > 0) { | ||||||
|  |       console.log('有效数据:', data); | ||||||
|  |  | ||||||
|  |       // 更新项目出勤率图表数据 | ||||||
|  |       projectAttendanceData.value = data.map((project) => ({ | ||||||
|  |         name: project.projectName, | ||||||
|  |         value: project.attendanceRate | ||||||
|  |       })); | ||||||
|  |  | ||||||
|  |       console.log('处理后的数据:', projectAttendanceData.value); | ||||||
|  |  | ||||||
|  |       // 如果图表已初始化,重新设置数据 | ||||||
|  |       if (attendanceChart) { | ||||||
|  |         console.log('更新图表数据'); | ||||||
|  |         attendanceChart.setOption({ | ||||||
|  |           xAxis: { | ||||||
|  |             data: projectAttendanceData.value.map((item) => item.name) | ||||||
|  |           }, | ||||||
|  |           series: [ | ||||||
|  |             { | ||||||
|  |               data: projectAttendanceData.value.map((item) => item.value) | ||||||
|  |             } | ||||||
|  |           ] | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       console.warn('数据不符合预期:', { code, data }); | ||||||
|  |       // 使用默认数据确保图表有内容显示 | ||||||
|  |       if (!projectAttendanceData.value.length) { | ||||||
|  |         projectAttendanceData.value = [ | ||||||
|  |           { name: 'A项目', value: 62 }, | ||||||
|  |           { name: 'B项目', value: 56 }, | ||||||
|  |           { name: 'C项目', value: 95 }, | ||||||
|  |           { name: 'D项目', value: 64 }, | ||||||
|  |           { name: 'E项目', value: 97.5 } | ||||||
|  |         ]; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('获取项目出勤率数据失败:', error); | ||||||
|  |     // 发生错误时使用默认数据 | ||||||
|  |     if (!projectAttendanceData.value.length) { | ||||||
|  |       projectAttendanceData.value = [ | ||||||
|  |         { name: 'A项目', value: 62 }, | ||||||
|  |         { name: 'B项目', value: 56 }, | ||||||
|  |         { name: 'C项目', value: 95 }, | ||||||
|  |         { name: 'D项目', value: 64 }, | ||||||
|  |         { name: 'E项目', value: 97.5 } | ||||||
|  |       ]; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 获取企业关键指标数据 |  * 获取企业关键指标数据 | ||||||
|  */ |  */ | ||||||
| @ -163,8 +379,23 @@ const getKeyIndexData = async () => { | |||||||
|  */ |  */ | ||||||
| const initAttendanceChart = () => { | const initAttendanceChart = () => { | ||||||
|   if (!attendanceChartRef.value) { |   if (!attendanceChartRef.value) { | ||||||
|  |     console.warn('attendanceChartRef不存在'); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // 确保有数据可显示 | ||||||
|  |   if (!projectAttendanceData.value || !projectAttendanceData.value.length) { | ||||||
|  |     console.log('使用默认数据初始化图表'); | ||||||
|  |     projectAttendanceData.value = [ | ||||||
|  |       { name: 'A项目', value: 62 }, | ||||||
|  |       { name: 'B项目', value: 56 }, | ||||||
|  |       { name: 'C项目', value: 95 }, | ||||||
|  |       { name: 'D项目', value: 64 }, | ||||||
|  |       { name: 'E项目', value: 97.5 } | ||||||
|  |     ]; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   console.log('开始初始化图表,当前数据:', projectAttendanceData.value); | ||||||
|   attendanceChart = echarts.init(attendanceChartRef.value); |   attendanceChart = echarts.init(attendanceChartRef.value); | ||||||
|  |  | ||||||
|   const option = { |   const option = { | ||||||
| @ -191,13 +422,45 @@ const initAttendanceChart = () => { | |||||||
|     grid: { |     grid: { | ||||||
|       top: 40, |       top: 40, | ||||||
|       right: 30, |       right: 30, | ||||||
|       bottom: 40, |       bottom: 60, // 增加底部空间以确保标签完全显示 | ||||||
|       left: 30, |       left: 30, | ||||||
|       containLabel: true, |       containLabel: true, | ||||||
|       backgroundColor: 'rgba(10, 24, 45, 0.1)', |       backgroundColor: 'rgba(10, 24, 45, 0.1)', | ||||||
|       borderColor: 'rgba(29, 214, 255, 0.1)', |       borderColor: 'rgba(29, 214, 255, 0.1)', | ||||||
|       borderWidth: 1 |       borderWidth: 1 | ||||||
|     }, |     }, | ||||||
|  |     // 添加dataZoom组件实现水平滚动 | ||||||
|  |     dataZoom: [ | ||||||
|  |       { | ||||||
|  |         type: 'slider', | ||||||
|  |         show: true, | ||||||
|  |         xAxisIndex: [0], | ||||||
|  |         start: 0, | ||||||
|  |         end: 20, // 固定为20%,与visiblePercentage保持一致 | ||||||
|  |         height: 20, | ||||||
|  |         bottom: 10, | ||||||
|  |         backgroundColor: 'rgba(10, 24, 45, 0.2)', | ||||||
|  |         fillerColor: 'rgba(29, 214, 255, 0.2)', | ||||||
|  |         borderColor: 'rgba(29, 214, 255, 0.3)', | ||||||
|  |         textStyle: { | ||||||
|  |           color: '#e6f7ff' | ||||||
|  |         }, | ||||||
|  |         handleStyle: { | ||||||
|  |           color: 'rgba(29, 214, 255, 0.6)', | ||||||
|  |           borderColor: 'rgba(255, 255, 255, 0.3)' | ||||||
|  |         }, | ||||||
|  |         moveHandleStyle: { | ||||||
|  |           color: 'rgba(29, 214, 255, 0.8)' | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       // 支持鼠标滚轮缩放 | ||||||
|  |       { | ||||||
|  |         type: 'inside', | ||||||
|  |         xAxisIndex: [0], | ||||||
|  |         start: 0, | ||||||
|  |         end: 20 // 固定为20%,与visiblePercentage保持一致 | ||||||
|  |       } | ||||||
|  |     ], | ||||||
|     xAxis: { |     xAxis: { | ||||||
|       type: 'category', |       type: 'category', | ||||||
|       data: projectAttendanceData.value.map((item) => item.name), |       data: projectAttendanceData.value.map((item) => item.name), | ||||||
| @ -209,9 +472,18 @@ const initAttendanceChart = () => { | |||||||
|       axisLabel: { |       axisLabel: { | ||||||
|         color: '#e6f7ff', |         color: '#e6f7ff', | ||||||
|         fontSize: 14, |         fontSize: 14, | ||||||
|         interval: 0, |         interval: 0, // 强制显示所有标签 | ||||||
|         fontWeight: 'bold', |         fontWeight: 'bold', | ||||||
|         padding: [10, 0, 0, 0] |         padding: [10, 0, 0, 0], | ||||||
|  |         // 防止标签重叠,旋转角度调整 | ||||||
|  |         rotate: 0, | ||||||
|  |         // 添加formatter函数,当名称超过6个汉字时显示省略号 | ||||||
|  |         formatter: function (value) { | ||||||
|  |           if (value.length > 6) { | ||||||
|  |             return value.substring(0, 6) + '...'; | ||||||
|  |           } | ||||||
|  |           return value; | ||||||
|  |         } | ||||||
|       }, |       }, | ||||||
|       axisTick: { |       axisTick: { | ||||||
|         show: true, |         show: true, | ||||||
| @ -321,6 +593,19 @@ const initAttendanceChart = () => { | |||||||
|  |  | ||||||
|   attendanceChart.setOption(option); |   attendanceChart.setOption(option); | ||||||
|  |  | ||||||
|  |   // 添加鼠标悬浮事件监听 | ||||||
|  |   if (attendanceChartRef.value) { | ||||||
|  |     // 鼠标进入图表区域时停止滚动 | ||||||
|  |     attendanceChartRef.value.addEventListener('mouseenter', () => { | ||||||
|  |       stopScroll(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // 鼠标离开图表区域时重新开始滚动 | ||||||
|  |     attendanceChartRef.value.addEventListener('mouseleave', () => { | ||||||
|  |       startScroll(); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // 添加窗口大小变化时的图表更新 |   // 添加窗口大小变化时的图表更新 | ||||||
|   const handleResize = () => { |   const handleResize = () => { | ||||||
|     if (attendanceChart) { |     if (attendanceChart) { | ||||||
| @ -333,6 +618,12 @@ const initAttendanceChart = () => { | |||||||
|   // 清理函数 |   // 清理函数 | ||||||
|   onUnmounted(() => { |   onUnmounted(() => { | ||||||
|     window.removeEventListener('resize', handleResize); |     window.removeEventListener('resize', handleResize); | ||||||
|  |  | ||||||
|  |     // 移除鼠标事件监听 | ||||||
|  |     if (attendanceChartRef.value) { | ||||||
|  |       attendanceChartRef.value.removeEventListener('mouseenter', stopScroll); | ||||||
|  |       attendanceChartRef.value.removeEventListener('mouseleave', startScroll); | ||||||
|  |     } | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @ -347,17 +638,23 @@ const initMapChart = () => { | |||||||
|   mapChart.setOption(mapOption); |   mapChart.setOption(mapOption); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| onMounted(() => { | onMounted(async () => { | ||||||
|   // nextTick(() => { |   // nextTick(() => { | ||||||
|   //   initMapChart(); |   //   initMapChart(); | ||||||
|   // }); |   // }); | ||||||
|   getPeopleData(); |   getPeopleData(); | ||||||
|   getKeyIndexData(); |   getKeyIndexData(); | ||||||
|  |   getProjectAttendanceCount(); // 获取人员总览数据 | ||||||
|  |   getPeopleCategoryData(); | ||||||
|  |  | ||||||
|   // 初始化项目出勤率柱状图 |   // 先等待获取项目出勤率数据 | ||||||
|   setTimeout(() => { |   await getProjectAttendanceStats(); // 获取项目出勤率图表数据 | ||||||
|  |  | ||||||
|  |   // 再初始化图表 | ||||||
|   initAttendanceChart(); |   initAttendanceChart(); | ||||||
|   }, 100); |  | ||||||
|  |   // 图表初始化后自动开始滚动 | ||||||
|  |   startScroll(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| onUnmounted(() => { | onUnmounted(() => { | ||||||
| @ -370,6 +667,9 @@ onUnmounted(() => { | |||||||
|     attendanceChart.dispose(); |     attendanceChart.dispose(); | ||||||
|     attendanceChart = null; |     attendanceChart = null; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // 清理滚动计时器 | ||||||
|  |   stopScroll(); | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| @ -383,37 +683,59 @@ onUnmounted(() => { | |||||||
|   .endPage { |   .endPage { | ||||||
|     display: flex; |     display: flex; | ||||||
|     flex-direction: column; |     flex-direction: column; | ||||||
|     align-items: center; |  | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     padding: 15px 0; |     height: 250; | ||||||
|     border: 1px solid rgba(29, 214, 255, 0.1); |     border: 1px solid rgba(29, 214, 255, 0.1); | ||||||
|     box-sizing: border-box; |     box-sizing: border-box; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   .endPage { |   .endPage { | ||||||
|  |     height: auto; | ||||||
|     flex: 1; |     flex: 1; | ||||||
|     margin-top: 23px; |     min-height: 550px; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   .project_attendance_chart { |   .project_attendance_chart { | ||||||
|     display: flex; |     display: flex; | ||||||
|     flex-direction: column; |     flex-direction: column; | ||||||
|     align-items: center; |  | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     padding: 15px 0; |     margin-top: 15px; | ||||||
|     border: 1px solid rgba(29, 214, 255, 0.1); |     padding-bottom: 10px; | ||||||
|     box-sizing: border-box; |  | ||||||
|  |  | ||||||
|     .chart_title { |     .chart_title { | ||||||
|       font-size: 16px; |       font-size: 16px; | ||||||
|       color: #e6f7ff; |       color: #e6f7ff; | ||||||
|       margin-bottom: 15px; |       margin-bottom: 5px; | ||||||
|       text-align: left; |       text-align: left; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     .scroll_controls { | ||||||
|  |       display: flex; | ||||||
|  |       justify-content: flex-end; | ||||||
|  |       gap: 10px; | ||||||
|  |       margin-bottom: 10px; | ||||||
|  |       padding-right: 10px; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .control_btn { | ||||||
|  |       padding: 5px 15px; | ||||||
|  |       background: rgba(10, 24, 45, 0.8); | ||||||
|  |       border: 1px solid rgba(29, 214, 255, 0.3); | ||||||
|  |       color: #e6f7ff; | ||||||
|  |       font-size: 12px; | ||||||
|  |       cursor: pointer; | ||||||
|  |       border-radius: 4px; | ||||||
|  |       transition: all 0.3s ease; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .control_btn:hover { | ||||||
|  |       background: rgba(29, 214, 255, 0.2); | ||||||
|  |       border-color: rgba(29, 214, 255, 0.6); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     .chart_content { |     .chart_content { | ||||||
|       width: 100%; |       width: 100%; | ||||||
|       height: 200px; |       height: 320px; /* 增加高度以容纳滚动条 */ | ||||||
|       position: relative; |       position: relative; | ||||||
|  |       overflow: hidden; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @ -422,20 +744,20 @@ onUnmounted(() => { | |||||||
|   width: 100%; |   width: 100%; | ||||||
|   display: grid; |   display: grid; | ||||||
|   grid-template-columns: repeat(2, 1fr); |   grid-template-columns: repeat(2, 1fr); | ||||||
|   gap: 15px; |   gap: 10px; | ||||||
|   margin-top: 15px; |   margin-bottom: 8px; | ||||||
|   padding: 0 15px; |   padding: 0 10px; | ||||||
|   box-sizing: border-box; |   box-sizing: border-box; | ||||||
| } | } | ||||||
|  |  | ||||||
| .indicator-card { | .indicator-card { | ||||||
|   position: relative; |   position: relative; | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   height: 100px; |   height: 80px; | ||||||
|   background: rgba(10, 24, 45, 0.7); |   background: rgba(10, 24, 45, 0.7); | ||||||
|   border: 1px solid rgba(29, 214, 255, 0.2); |   border: 1px solid rgba(29, 214, 255, 0.2); | ||||||
|   border-radius: 4px; |   border-radius: 4px; | ||||||
|   padding: 15px; |   padding: 10px; | ||||||
|   box-sizing: border-box; |   box-sizing: border-box; | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
| @ -488,53 +810,151 @@ onUnmounted(() => { | |||||||
|   object-fit: contain; |   object-fit: contain; | ||||||
| } | } | ||||||
|  |  | ||||||
| .map { | /* 人员总览样式 */ | ||||||
|   margin-top: 15px; | .people_overview { | ||||||
|  |   width: 100%; | ||||||
|  |   margin-top: 5px; | ||||||
|  |   box-sizing: border-box; | ||||||
|  |   border: 1px solid rgba(29, 214, 255, 0.1); | ||||||
|  |   background: rgba(10, 24, 45, 0.3); | ||||||
| } | } | ||||||
|  |  | ||||||
| .attendance_tag { | .people_overview_title { | ||||||
|   width: 100%; |   font-size: 16px; | ||||||
|   display: flex; |   color: #e6f7ff; | ||||||
|   justify-content: space-between; |   margin-bottom: 5px; | ||||||
|   padding: 0 30px; |   border-bottom: 1px solid rgba(29, 214, 255, 0.1); | ||||||
|   margin-top: 15px; | } | ||||||
|  |  | ||||||
|   .tag_item { | .people_overview_content { | ||||||
|     width: 28%; |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .people_image { | ||||||
|  |   width: 60px; | ||||||
|  |   height: 60px; | ||||||
|  |   background: linear-gradient(135deg, rgba(29, 214, 255, 0.1) 0%, rgba(29, 214, 255, 0.05) 100%); | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: center; | ||||||
|  |   flex-shrink: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .placeholder_image { | ||||||
|  |   width: 100%; | ||||||
|  |   height: 100%; | ||||||
|  |   object-fit: contain; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .people_stats { | ||||||
|  |   flex: 1; | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-around; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .stat_item { | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|   align-items: center; |   align-items: center; | ||||||
|     gap: 10px; |  | ||||||
|     border: 1px dashed rgba(29, 214, 255, 0.3); |  | ||||||
|     padding: 10px; |  | ||||||
|  |  | ||||||
|     .tag_info { |  | ||||||
|       font-size: 20px; |  | ||||||
|       font-weight: 700; |  | ||||||
|       color: rgba(230, 247, 255, 1); |  | ||||||
|       text-shadow: 0px 1.24px 6.21px rgba(0, 190, 247, 1); |  | ||||||
| } | } | ||||||
|  |  | ||||||
|     .tag_title { | .stat_label { | ||||||
|   font-size: 14px; |   font-size: 14px; | ||||||
|       font-weight: 400; |   color: #8ab2ff; | ||||||
|       color: rgba(230, 247, 255, 1); |   margin-bottom: 5px; | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| .attendance_list { | .stat_value { | ||||||
|   padding: 0px 30px; |   font-size: 28px; | ||||||
|   font-size: 14px; |   font-weight: bold; | ||||||
|  |   color: #e6f7ff; | ||||||
|   .attendance_item { |   text-shadow: 0px 1.24px 6.21px rgba(0, 190, 247, 0.5); | ||||||
|     display: grid; |  | ||||||
|     grid-template-columns: 3fr 2fr 2fr 3fr; |  | ||||||
|     margin-top: 20px; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| .subfont { | .stat_rate { | ||||||
|   color: rgba(138, 149, 165, 1); |   font-size: 28px; | ||||||
|  |   font-weight: bold; | ||||||
|  |   color: #00c853; | ||||||
|  |   text-shadow: 0px 1.24px 6.21px rgba(0, 200, 83, 0.5); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* 点阵地图样式 */ | ||||||
|  | .people_map { | ||||||
|  |   width: 100%; | ||||||
|  |   height: 120px; | ||||||
|  |   margin-top: 8px; | ||||||
|  |   background: rgba(10, 24, 45, 0.5); | ||||||
|  |   border: 1px solid rgba(29, 214, 255, 0.1); | ||||||
|  |   border-radius: 4px; | ||||||
|  |   overflow: hidden; | ||||||
|  |   position: relative; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .map_background { | ||||||
|  |   width: 100%; | ||||||
|  |   height: 100%; | ||||||
|  |   background-image: radial-gradient(circle, rgba(29, 214, 255, 0.3) 1px, transparent 1px), | ||||||
|  |     radial-gradient(circle, rgba(29, 214, 255, 0.3) 1px, transparent 1px); | ||||||
|  |   background-size: 40px 40px; | ||||||
|  |   background-position: | ||||||
|  |     0, | ||||||
|  |     0, | ||||||
|  |     20px 20px; | ||||||
|  |   position: relative; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* 人员分类统计样式 */ | ||||||
|  | .people_categories { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-around; | ||||||
|  |   margin-top: 5px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .category_item { | ||||||
|  |   display: flex; | ||||||
|  |   flex: 1; | ||||||
|  |   margin: 10px, 30px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .category_icon { | ||||||
|  |   margin-left: 15px; | ||||||
|  |   margin-top: 10px; | ||||||
|  |   width: 30px; | ||||||
|  |   height: 30px; | ||||||
|  |   background: linear-gradient(135deg, rgba(29, 214, 255, 0.1) 0%, rgba(10, 120, 200, 0.1) 100%); | ||||||
|  |   border: 1px solid rgba(29, 214, 255, 0.2); | ||||||
|  |   border-radius: 5px; | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: center; | ||||||
|  |   flex-shrink: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .category_image { | ||||||
|  |   width: 45px; | ||||||
|  |   height: 45px; | ||||||
|  |   object-fit: contain; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .category_info { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   margin-left: 8px; | ||||||
|  |   margin-top: 8px; | ||||||
|  |   flex: 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .category_label { | ||||||
|  |   font-size: 12px; | ||||||
|  |   color: #8ab2ff; | ||||||
|  |   margin-bottom: 1px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .category_count { | ||||||
|  |   font-size: 16px; | ||||||
|  |   font-weight: bold; | ||||||
|  |   color: #e6f7ff; | ||||||
|  |   text-shadow: 0px 1.24px 6.21px rgba(0, 190, 247, 0.3); | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user