961 lines
24 KiB
Vue
961 lines
24 KiB
Vue
<template>
|
||
<div class="leftPage">
|
||
<div class="topPage">
|
||
<Title style="font-size: 22px" title="企业关键指标" />
|
||
<div class="indicators">
|
||
<div class="indicator-card" v-for="indicator in indicators" :key="indicator.id">
|
||
<div style="display: flex; align-items: baseline; gap: 4px; margin-bottom: 5px">
|
||
<div class="indicator-value">{{ indicator.value }}</div>
|
||
<div class="indicator-unit">{{ indicator.unit }}</div>
|
||
</div>
|
||
<div class="indicator-name">{{ indicator.name }}</div>
|
||
<div class="indicator-icon">
|
||
<img :src="indicator.iconPath" :alt="indicator.name" v-if="indicator.iconPath" style="width: 50px; height: 50px" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="endPage">
|
||
<Title style="font-size: 22px" title="人员情况" />
|
||
<!-- 人员总览区域 -->
|
||
<div class="people_overview">
|
||
<div class="people_overview_content">
|
||
<div class="people_image">
|
||
<!-- 本地图片占位,后续可替换为实际图片路径 -->
|
||
<img src="@/assets/images/man.png" alt="人员总览" class="placeholder_image" />
|
||
</div>
|
||
<div class="people_stats">
|
||
<div class="stat_item">
|
||
<div class="stat_label">出勤人数</div>
|
||
<div class="stat_value">{{ attendanceCount }}</div>
|
||
</div>
|
||
<div class="stat_item">
|
||
<div class="stat_label">出勤率</div>
|
||
<div class="stat_rate">{{ attendanceRate }}%</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 点阵地图 -->
|
||
<div class="people_map">
|
||
<div class="map_container">
|
||
<img src="@/assets/images/map.png" />
|
||
</div>
|
||
</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 class="project_attendance_chart">
|
||
<Title style="font-size: 22px" title="项目出勤率统计" />
|
||
|
||
<div class="chart_content" ref="attendanceChartRef"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref } from 'vue';
|
||
import Title from './title.vue';
|
||
import { getScreenNews, getScreenPeople } from '@/api/projectScreen';
|
||
import { keyIndex, projectAttendanceCount, peopleCount, allAttendanceCount } from '@/api/enterpriseLarge/index';
|
||
import { mapOption } from './optionList';
|
||
import * as echarts from 'echarts';
|
||
|
||
const props = defineProps({
|
||
projectId: {
|
||
type: String,
|
||
default: ''
|
||
}
|
||
});
|
||
|
||
let mapChart = null;
|
||
const mapChartRef = ref<HTMLDivElement | null>(null);
|
||
const indicators = ref([
|
||
{
|
||
id: '1',
|
||
name: '在建项目',
|
||
value: '28',
|
||
unit: '个',
|
||
iconPath: '/src/assets/images/beUnder.png'
|
||
},
|
||
{
|
||
id: '2',
|
||
name: '合同总额',
|
||
value: '288.88',
|
||
unit: '亿元',
|
||
iconPath: '/src/assets/images/contract.png'
|
||
},
|
||
{
|
||
id: '3',
|
||
name: '总容量',
|
||
value: '158.88',
|
||
unit: '个',
|
||
iconPath: '/src/assets/images/totalCapacity.png'
|
||
},
|
||
{
|
||
id: '4',
|
||
name: '今日施工',
|
||
value: '18',
|
||
unit: '个',
|
||
iconPath: '/src/assets/images/todayConstruction.png'
|
||
}
|
||
]);
|
||
|
||
const news = ref([]);
|
||
const newDetail = ref({
|
||
title: '',
|
||
content: ''
|
||
});
|
||
const newId = ref('');
|
||
const attendanceCount = ref(0);
|
||
const attendanceRate = ref(0);
|
||
const totalPeopleCount = ref(0);
|
||
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([
|
||
{ name: 'A项目', value: 62 },
|
||
{ name: 'B项目', value: 56 },
|
||
{ name: 'C项目', value: 95 },
|
||
{ name: 'D项目', value: 64 },
|
||
{ name: 'E项目', value: 97.5 }
|
||
]);
|
||
|
||
let attendanceChart = null;
|
||
const attendanceChartRef = ref<HTMLDivElement | null>(null);
|
||
|
||
// 滚动相关状态
|
||
const scrollInterval = ref<number | null>(null);
|
||
const currentScrollIndex = ref(0);
|
||
const scrollSpeed = 1000; // 滚动间隔时间(ms)
|
||
|
||
/**
|
||
* 获取项目人员出勤数据
|
||
*/
|
||
const getPeopleData = async () => {
|
||
const res = await getScreenPeople(props.projectId);
|
||
const { data, code } = res;
|
||
if (code === 200) {
|
||
totalPeopleCount.value = data.peopleCount;
|
||
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 }
|
||
];
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 获取企业关键指标数据
|
||
*/
|
||
const getKeyIndexData = async () => {
|
||
const res = await keyIndex();
|
||
const { data, code } = res;
|
||
if (code === 200) {
|
||
// 更新指标数据,使用接口返回的指定字段
|
||
indicators.value[0].value = data.ongoingProject || 0;
|
||
indicators.value[1].value = data.totalContractAmount || 0;
|
||
indicators.value[2].value = data.totalCapacity || 0;
|
||
indicators.value[3].value = data.todayProject || 0;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 初始化项目出勤率柱状图(美化版)
|
||
*/
|
||
const initAttendanceChart = () => {
|
||
if (!attendanceChartRef.value) {
|
||
console.warn('attendanceChartRef不存在');
|
||
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);
|
||
|
||
const option = {
|
||
backgroundColor: 'transparent',
|
||
tooltip: {
|
||
trigger: 'axis',
|
||
axisPointer: {
|
||
type: 'shadow',
|
||
shadowStyle: {
|
||
color: 'rgba(29, 214, 255, 0.1)'
|
||
}
|
||
},
|
||
backgroundColor: 'rgba(10, 24, 45, 0.8)',
|
||
borderColor: 'rgba(29, 214, 255, 0.3)',
|
||
borderWidth: 1,
|
||
textStyle: {
|
||
color: '#e6f7ff'
|
||
},
|
||
formatter: (params) => {
|
||
const data = params[0];
|
||
return `${data.name}<br/>出勤率: ${data.value}%`;
|
||
}
|
||
},
|
||
grid: {
|
||
top: 40,
|
||
right: 30,
|
||
bottom: 60, // 增加底部空间以确保标签完全显示
|
||
left: 30,
|
||
containLabel: true,
|
||
backgroundColor: 'rgba(10, 24, 45, 0.1)',
|
||
borderColor: 'rgba(29, 214, 255, 0.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: {
|
||
type: 'category',
|
||
data: projectAttendanceData.value.map((item) => item.name),
|
||
axisLine: {
|
||
lineStyle: {
|
||
color: 'rgba(29, 214, 255, 0.3)'
|
||
}
|
||
},
|
||
axisLabel: {
|
||
color: '#e6f7ff',
|
||
fontSize: 14,
|
||
interval: 0, // 强制显示所有标签
|
||
fontWeight: 'bold',
|
||
padding: [10, 0, 0, 0],
|
||
// 防止标签重叠,旋转角度调整
|
||
rotate: 0,
|
||
// 添加formatter函数,当名称超过6个汉字时显示省略号
|
||
formatter: function (value) {
|
||
if (value.length > 6) {
|
||
return value.substring(0, 6) + '...';
|
||
}
|
||
return value;
|
||
}
|
||
},
|
||
axisTick: {
|
||
show: true,
|
||
length: 5,
|
||
lineStyle: {
|
||
color: 'rgba(29, 214, 255, 0.5)'
|
||
}
|
||
}
|
||
},
|
||
yAxis: {
|
||
type: 'value',
|
||
min: 0,
|
||
max: 100,
|
||
interval: 20,
|
||
axisLine: {
|
||
show: true,
|
||
lineStyle: {
|
||
color: 'rgba(29, 214, 255, 0.3)'
|
||
}
|
||
},
|
||
axisLabel: {
|
||
color: '#e6f7ff',
|
||
fontSize: 12,
|
||
formatter: '{value}%',
|
||
padding: [0, 10, 0, 0]
|
||
},
|
||
splitLine: {
|
||
show: true,
|
||
lineStyle: {
|
||
color: 'rgba(29, 214, 255, 0.15)',
|
||
type: 'dashed'
|
||
}
|
||
},
|
||
axisTick: {
|
||
show: false
|
||
}
|
||
},
|
||
series: [
|
||
{
|
||
data: projectAttendanceData.value.map((item) => item.value),
|
||
type: 'bar',
|
||
itemStyle: {
|
||
color: function (params) {
|
||
// 根据数值动态调整渐变效果
|
||
const value = params.value;
|
||
const baseColor = new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
{ offset: 0, color: 'rgba(29, 214, 255, 1)' },
|
||
{ offset: 1, color: 'rgba(10, 120, 200, 0.8)' }
|
||
]);
|
||
return baseColor;
|
||
},
|
||
borderRadius: [5, 5, 0, 0],
|
||
borderColor: 'rgba(255, 255, 255, 0.3)',
|
||
borderWidth: 1
|
||
},
|
||
barWidth: '45%',
|
||
label: {
|
||
show: true,
|
||
position: 'top',
|
||
color: '#e6f7ff',
|
||
fontSize: 14,
|
||
fontWeight: 'bold',
|
||
formatter: '{c}%',
|
||
offset: [0, -10]
|
||
},
|
||
emphasis: {
|
||
itemStyle: {
|
||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
{ offset: 0, color: 'rgba(29, 214, 255, 1)' },
|
||
{ offset: 1, color: 'rgba(10, 120, 200, 1)' }
|
||
]),
|
||
shadowBlur: 10,
|
||
shadowColor: 'rgba(29, 214, 255, 0.5)',
|
||
borderColor: 'rgba(255, 255, 255, 0.5)'
|
||
},
|
||
label: {
|
||
color: '#ffffff',
|
||
textShadowBlur: 2,
|
||
textShadowColor: 'rgba(0, 0, 0, 0.5)'
|
||
}
|
||
},
|
||
animationDelay: function (idx) {
|
||
// 动画延迟,使柱子依次出现
|
||
return idx * 100;
|
||
}
|
||
}
|
||
],
|
||
// 添加动画效果
|
||
animationEasing: 'elasticOut',
|
||
animationDelayUpdate: function (idx) {
|
||
return idx * 5;
|
||
},
|
||
// 添加单位标签
|
||
graphic: [
|
||
{
|
||
type: 'text',
|
||
left: 10,
|
||
top: 10,
|
||
style: {
|
||
text: '单位: %',
|
||
fill: '#8ab2ff',
|
||
fontSize: 12
|
||
}
|
||
}
|
||
]
|
||
};
|
||
|
||
attendanceChart.setOption(option);
|
||
|
||
// 添加鼠标悬浮事件监听
|
||
if (attendanceChartRef.value) {
|
||
// 鼠标进入图表区域时停止滚动
|
||
attendanceChartRef.value.addEventListener('mouseenter', () => {
|
||
stopScroll();
|
||
});
|
||
|
||
// 鼠标离开图表区域时重新开始滚动
|
||
attendanceChartRef.value.addEventListener('mouseleave', () => {
|
||
startScroll();
|
||
});
|
||
}
|
||
|
||
// 添加窗口大小变化时的图表更新
|
||
const handleResize = () => {
|
||
if (attendanceChart) {
|
||
attendanceChart.resize();
|
||
}
|
||
};
|
||
|
||
window.addEventListener('resize', handleResize);
|
||
|
||
// 清理函数
|
||
onUnmounted(() => {
|
||
window.removeEventListener('resize', handleResize);
|
||
|
||
// 移除鼠标事件监听
|
||
if (attendanceChartRef.value) {
|
||
attendanceChartRef.value.removeEventListener('mouseenter', stopScroll);
|
||
attendanceChartRef.value.removeEventListener('mouseleave', startScroll);
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 初始化地图
|
||
*/
|
||
const initMapChart = () => {
|
||
if (!mapChartRef.value) {
|
||
return;
|
||
}
|
||
mapChart = echarts.init(mapChartRef.value);
|
||
mapChart.setOption(mapOption);
|
||
};
|
||
|
||
onMounted(async () => {
|
||
// nextTick(() => {
|
||
// initMapChart();
|
||
// });
|
||
getPeopleData();
|
||
getKeyIndexData();
|
||
getProjectAttendanceCount(); // 获取人员总览数据
|
||
getPeopleCategoryData();
|
||
|
||
// 先等待获取项目出勤率数据
|
||
await getProjectAttendanceStats(); // 获取项目出勤率图表数据
|
||
|
||
// 再初始化图表
|
||
initAttendanceChart();
|
||
|
||
// 图表初始化后自动开始滚动
|
||
startScroll();
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
// if (mapChart) {
|
||
// mapChart.dispose();
|
||
// mapChart = null;
|
||
// }
|
||
|
||
if (attendanceChart) {
|
||
attendanceChart.dispose();
|
||
attendanceChart = null;
|
||
}
|
||
|
||
// 清理滚动计时器
|
||
stopScroll();
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.leftPage {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
|
||
.topPage,
|
||
.endPage {
|
||
display: flex;
|
||
flex-direction: column;
|
||
width: 100%;
|
||
height: 250;
|
||
border: 1px solid rgba(29, 214, 255, 0.1);
|
||
box-sizing: border-box;
|
||
}
|
||
.endPage {
|
||
height: auto;
|
||
flex: 1;
|
||
min-height: 550px;
|
||
}
|
||
.project_attendance_chart {
|
||
display: flex;
|
||
flex-direction: column;
|
||
width: 100%;
|
||
margin-top: 15px;
|
||
padding-bottom: 10px;
|
||
|
||
.chart_title {
|
||
font-size: 16px;
|
||
color: #e6f7ff;
|
||
margin-bottom: 5px;
|
||
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 {
|
||
width: 100%;
|
||
height: 320px; /* 增加高度以容纳滚动条 */
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
}
|
||
}
|
||
|
||
.indicators {
|
||
width: 100%;
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 10px;
|
||
margin-bottom: 8px;
|
||
padding: 0 10px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.indicator-card {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 80px;
|
||
background: rgba(10, 24, 45, 0.7);
|
||
border: 1px solid rgba(29, 214, 255, 0.2);
|
||
border-radius: 4px;
|
||
padding: 10px;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.indicator-card::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 2px;
|
||
background: linear-gradient(90deg, rgba(29, 214, 255, 0.2) 0%, rgba(29, 214, 255, 0.6) 50%, rgba(29, 214, 255, 0.2) 100%);
|
||
}
|
||
|
||
.indicator-value {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #e6f7ff;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.indicator-unit {
|
||
font-size: 14px;
|
||
color: #8ab2ff;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.indicator-name {
|
||
font-size: 14px;
|
||
color: #e6f7ff;
|
||
}
|
||
|
||
.indicator-icon {
|
||
position: absolute;
|
||
bottom: 10px;
|
||
right: 10px;
|
||
width: 30px;
|
||
height: 30px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.indicator-icon img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: contain;
|
||
}
|
||
|
||
/* 人员总览样式 */
|
||
.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);
|
||
}
|
||
|
||
.people_overview_title {
|
||
font-size: 16px;
|
||
color: #e6f7ff;
|
||
margin-bottom: 5px;
|
||
border-bottom: 1px solid rgba(29, 214, 255, 0.1);
|
||
}
|
||
|
||
.people_overview_content {
|
||
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;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.stat_label {
|
||
font-size: 14px;
|
||
color: #8ab2ff;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.stat_value {
|
||
font-size: 28px;
|
||
font-weight: bold;
|
||
color: #e6f7ff;
|
||
text-shadow: 0px 1.24px 6.21px rgba(0, 190, 247, 0.5);
|
||
}
|
||
|
||
.stat_rate {
|
||
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>
|