This commit is contained in:
dhr
2025-09-09 22:34:40 +08:00
parent 78de3276d3
commit 65ff68673c
4 changed files with 550 additions and 99 deletions

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
src/assets/images/map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

View File

@ -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>