<template>
|
<div class="center-top-container">
|
<!-- 顶部统计卡片 -->
|
<div class="stats-cards">
|
<!-- <div class="stat-card">
|
<img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
|
<div class="card-content">
|
<span class="card-label">员工总数</span>
|
<span class="card-value">{{ totalStaff }}</span>
|
<div class="card-compare" :class="compareClass(staffYoY)">
|
<span>同比</span>
|
<span class="compare-value">{{ formatPercent(staffYoY) }}</span>
|
<span class="compare-icon">{{ staffYoY >= 0 ? '↑' : '↓' }}</span>
|
</div>
|
</div>
|
</div> -->
|
<div class="stat-card">
|
<img src="@/assets/BI/icon@2x.png"
|
alt="图标"
|
class="card-icon" />
|
<div class="card-content">
|
<span class="card-label">客户总数</span>
|
<span class="card-value">{{ totalCustomers }}</span>
|
<div class="card-compare"
|
:class="compareClass(customersYoY)">
|
<span>同比</span>
|
<span class="compare-value">{{ formatPercent(customersYoY) }}</span>
|
<span class="compare-icon">{{ customersYoY >= 0 ? '↑' : '↓' }}</span>
|
</div>
|
</div>
|
</div>
|
<div class="stat-card">
|
<img src="@/assets/BI/icon@2x.png"
|
alt="图标"
|
class="card-icon" />
|
<div class="card-content">
|
<span class="card-label">供应商总数</span>
|
<span class="card-value">{{ totalSuppliers }}</span>
|
<div class="card-compare"
|
:class="compareClass(suppliersYoY)">
|
<span>同比</span>
|
<span class="compare-value">{{ formatPercent(suppliersYoY) }}</span>
|
<span class="compare-icon">{{ suppliersYoY >= 0 ? '↑' : '↓' }}</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
<!-- 设备统计 -->
|
<div class="equipment-stats">
|
<div class="equipment-header">
|
<img src="@/assets/BI/shujutongjiicon@2x.png"
|
alt="图标"
|
class="equipment-icon" />
|
<span class="equipment-title">设备统计</span>
|
</div>
|
<div class="equipment-items">
|
<div class="equipment-item">
|
<span class="equipment-value">{{ equipmentNum }}</span>
|
<span class="equipment-label">设备总数</span>
|
</div>
|
<div class="equipment-item">
|
<span class="equipment-value">{{ equipmentRepair }}</span>
|
<span class="equipment-label">待维修设备</span>
|
</div>
|
<div class="equipment-item">
|
<span class="equipment-value">{{ equipmentMaintain }}</span>
|
<span class="equipment-label">待保养设备</span>
|
</div>
|
<div class="equipment-item">
|
<span class="equipment-value">{{ totalMeasuring }}</span>
|
<span class="equipment-label">计量器具总数</span>
|
</div>
|
</div>
|
</div>
|
<!-- 事件名称 -->
|
<div class="event-info">
|
<div class="event-header">
|
<img src="@/assets/BI/shijianmingxiicon@2x.png"
|
alt="图标"
|
class="event-icon" />
|
<span class="event-title">事件名称</span>
|
</div>
|
<div class="event-content">
|
<ul class="todo-list"
|
v-if="todoList.length > 0"
|
ref="refTodoList">
|
<li v-for="item in todoList"
|
:key="item.id">
|
<div style="
|
display: flex;
|
flex-direction: column;
|
justify-content: space-between;
|
width: 100%;
|
gap: 20px;
|
">
|
<div class="todo-division">待办事由:{{ item.approveReason }}</div>
|
<div style="display: flex;justify-content: space-between;align-items: center;">
|
<div class="todo-title">申请类型:{{ item.approveTypeName }}</div>
|
<div class="todo-division">申请部门:{{ item.approveDeptName }}</div>
|
<div class="todo-time">{{ item.approveTime }}</div>
|
</div>
|
</div>
|
</li>
|
</ul>
|
<div v-else
|
style="text-align: center;color:#fff">暂无数据</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, onMounted, onBeforeUnmount, nextTick } from "vue";
|
import { homeTodos, summaryStatistics } from "@/api/viewIndex.js";
|
import { getLedgerPage } from "@/api/equipmentManagement/ledger.js";
|
import { getRepairPage } from "@/api/equipmentManagement/repair.js";
|
import { getUpkeepPage } from "@/api/equipmentManagement/upkeep.js";
|
import { measuringInstrumentListPage } from "@/api/equipmentManagement/measurementEquipment.js";
|
|
// 统计数据
|
const totalStaff = ref(0);
|
const totalCustomers = ref(0);
|
const totalSuppliers = ref(0);
|
// 同比
|
const staffYoY = ref(0);
|
const customersYoY = ref(0);
|
const suppliersYoY = ref(0);
|
const equipmentNum = ref(0);
|
const equipmentRepair = ref(0);
|
const equipmentMaintain = ref(0);
|
const totalMeasuring = ref(0);
|
|
// 待办事项
|
const todoList = ref([]);
|
const refTodoList = ref(null);
|
|
const formatPercent = val => {
|
const num = Number(val) || 0;
|
return `${Math.abs(num).toFixed(2)}%`;
|
};
|
|
const compareClass = val => (val >= 0 ? "compare-up" : "compare-down");
|
|
// 获取员工、客户、供应商数量
|
const getNum = () => {
|
summaryStatistics()
|
.then(res => {
|
totalStaff.value = res.data.totalStaff;
|
staffYoY.value = res.data.staffGrowthRate;
|
totalCustomers.value = res.data.totalCustomer;
|
customersYoY.value = res.data.customerGrowthRate;
|
totalSuppliers.value = res.data.totalSupplier;
|
suppliersYoY.value = res.data.supplierGrowthRate;
|
})
|
.catch(err => {
|
console.error("获取基础统计数据失败:", err);
|
});
|
};
|
|
// 获取设备相关数量
|
const getLedgerNum = () => {
|
const params = {
|
pageNum: -1,
|
pageSize: -1,
|
};
|
getLedgerPage(params).then(res => {
|
equipmentNum.value = res.data.total;
|
});
|
getRepairPage({ ...params, status: 0 }).then(res => {
|
equipmentRepair.value = res.data.total;
|
});
|
getUpkeepPage({ ...params, status: 0 }).then(res => {
|
equipmentMaintain.value = res.data.total;
|
});
|
measuringInstrumentListPage(params).then(res => {
|
totalMeasuring.value = res.data.total;
|
});
|
};
|
|
// 初始化待办事项列表滚动功能
|
const initTodoListScroll = () => {
|
const todoListEl = refTodoList.value;
|
// 强制启用滚动,不检查任何条件
|
if (todoListEl) {
|
// 创建一个克隆项,用于实现无缝滚动
|
const scrollItems = Array.from(todoListEl.querySelectorAll("li"));
|
if (scrollItems.length > 0) {
|
// 确保有足够的项目用于滚动
|
// 如果项目太少,多复制几次以确保滚动效果
|
if (scrollItems.length < 4) {
|
const originalItems = [...scrollItems];
|
for (let i = 0; i < 4; i++) {
|
originalItems.forEach(item => {
|
const clone = item.cloneNode(true);
|
todoListEl.appendChild(clone);
|
});
|
}
|
// 重新获取所有项目
|
scrollItems.push(
|
...Array.from(todoListEl.querySelectorAll("li")).slice(
|
scrollItems.length
|
)
|
);
|
}
|
const itemHeight = scrollItems[0]?.offsetHeight || 0;
|
const containerHeight = todoListEl.clientHeight;
|
const cloneCount = Math.ceil(containerHeight / itemHeight) + 2;
|
|
// 克隆前几个项目并添加到列表末尾,实现无缝滚动
|
for (let i = 0; i < cloneCount; i++) {
|
const clone = scrollItems[i % scrollItems.length].cloneNode(true);
|
todoListEl.appendChild(clone);
|
}
|
|
let scrollPosition = 0;
|
const scrollSpeed = 1.5; // 增加滚动速度,使滚动更加明显
|
const pauseTime = 3000; // 滚动暂停时间
|
let isPaused = false;
|
let lastTimestamp = 0;
|
|
// 连续滚动动画函数
|
function scrollAnimation(timestamp) {
|
if (!lastTimestamp) lastTimestamp = timestamp;
|
const deltaTime = timestamp - lastTimestamp;
|
lastTimestamp = timestamp;
|
|
if (!isPaused) {
|
scrollPosition += scrollSpeed * (deltaTime / 16); // 标准化为60fps的速度
|
|
// 当滚动超过原始内容长度时,重置位置实现无缝滚动
|
const maxScroll = Math.max(
|
todoListEl.scrollHeight - containerHeight - cloneCount * itemHeight,
|
itemHeight * scrollItems.length
|
);
|
if (scrollPosition >= maxScroll) {
|
scrollPosition = 0;
|
todoListEl.scrollTop = 0;
|
} else {
|
todoListEl.scrollTop = scrollPosition;
|
}
|
}
|
|
todoListEl._animationFrame = requestAnimationFrame(scrollAnimation);
|
}
|
|
// 启动滚动动画
|
todoListEl._animationFrame = requestAnimationFrame(scrollAnimation);
|
|
// 设置滚动-暂停-滚动的循环效果
|
const pauseTimer = setInterval(() => {
|
isPaused = !isPaused;
|
}, pauseTime);
|
|
// 清理定时器
|
todoListEl._pauseTimer = pauseTimer;
|
}
|
}
|
};
|
|
// 待办事项
|
const todoInfoS = () => {
|
homeTodos().then(res => {
|
todoList.value = res.data;
|
// 在获取到待办事项数据后,初始化滚动功能
|
nextTick(() => {
|
initTodoListScroll();
|
});
|
});
|
};
|
|
onMounted(() => {
|
getNum();
|
getLedgerNum();
|
todoInfoS();
|
});
|
|
onBeforeUnmount(() => {
|
// 清理待办事项列表的动画和定时器
|
const todoListEl = refTodoList.value;
|
if (todoListEl) {
|
if (todoListEl._animationFrame) {
|
cancelAnimationFrame(todoListEl._animationFrame);
|
todoListEl._animationFrame = null;
|
}
|
if (todoListEl._pauseTimer) {
|
clearInterval(todoListEl._pauseTimer);
|
todoListEl._pauseTimer = null;
|
}
|
}
|
});
|
</script>
|
|
<style scoped>
|
.center-top-container {
|
display: flex;
|
flex-direction: column;
|
height: 100%;
|
}
|
|
.stats-cards {
|
display: flex;
|
gap: 30px;
|
flex-shrink: 0;
|
}
|
|
.stat-card {
|
flex: 1;
|
display: flex;
|
align-items: center;
|
background-image: url("@/assets/BI/border@2x.png");
|
background-size: 100% 100%;
|
background-position: center;
|
background-repeat: no-repeat;
|
height: 142px;
|
}
|
|
.card-icon {
|
width: 100px;
|
height: 100px;
|
margin: 20px 20px 0 10px;
|
}
|
|
.card-content {
|
display: flex;
|
flex-direction: column;
|
gap: 10px;
|
}
|
|
.card-value {
|
font-weight: 500;
|
font-size: 40px;
|
background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%);
|
-webkit-background-clip: text;
|
-webkit-text-fill-color: transparent;
|
background-clip: text;
|
}
|
|
.card-label {
|
font-weight: 400;
|
font-size: 19px;
|
color: rgba(208, 231, 255, 0.7);
|
}
|
|
.card-compare {
|
display: flex;
|
align-items: center;
|
gap: 6px;
|
font-size: 15px;
|
color: #d0e7ff;
|
}
|
|
.card-compare > span:first-child {
|
font-size: 13px;
|
opacity: 0.8;
|
}
|
|
.compare-value {
|
font-weight: 600;
|
}
|
|
.compare-icon {
|
font-size: 14px;
|
position: relative;
|
top: -1px; /* 轻微上移,让箭头与文字垂直居中对齐 */
|
}
|
|
.compare-up .compare-value,
|
.compare-up .compare-icon {
|
color: #00c853;
|
}
|
|
.compare-down .compare-value,
|
.compare-down .compare-icon {
|
color: #ff5252;
|
}
|
|
.equipment-stats {
|
border: 1px solid #1a58b0;
|
padding: 18px;
|
padding-top: 0px;
|
flex-shrink: 0;
|
}
|
|
.equipment-header {
|
font-weight: 500;
|
font-size: 21px;
|
display: flex;
|
border-bottom: 1px solid;
|
border-image: linear-gradient(
|
270deg,
|
rgba(0, 126, 255, 0) 0%,
|
rgba(0, 126, 255, 0.4549) 35%,
|
#007eff 78%,
|
#007eff 100%
|
)
|
1;
|
padding-bottom: 2px;
|
}
|
|
.equipment-title {
|
font-weight: 500;
|
font-size: 18px;
|
background: linear-gradient(360deg, #056dff 0%, #43e8fc 100%);
|
-webkit-background-clip: text;
|
-webkit-text-fill-color: transparent;
|
background-clip: text;
|
line-height: 50px;
|
}
|
|
.equipment-icon {
|
width: 50px;
|
height: 50px;
|
}
|
|
.equipment-items {
|
display: flex;
|
justify-content: space-around;
|
gap: 30px;
|
}
|
|
.equipment-item {
|
text-align: center;
|
}
|
|
.equipment-value {
|
display: block;
|
font-weight: 500;
|
font-size: 40px;
|
color: #ffffff;
|
width: 120px;
|
height: 110px;
|
line-height: 110px;
|
background-image: url("@/assets/BI/shujutongji@2x.png");
|
background-size: 100% 100%;
|
background-position: center;
|
background-repeat: no-repeat;
|
margin-bottom: 8px;
|
}
|
|
.equipment-label {
|
font-weight: 500;
|
font-size: 16px;
|
color: #fffffe;
|
}
|
|
.event-info {
|
background-image: url("@/assets/BI/shijianmingchengbeijing@2x.png");
|
background-size: 100% 100%;
|
background-position: center;
|
background-repeat: no-repeat;
|
padding: 20px;
|
padding-top: 10px;
|
flex: 1;
|
min-height: 0;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.event-header {
|
display: flex;
|
align-items: center;
|
height: 80px;
|
}
|
|
.event-icon {
|
width: 40px;
|
height: 40px;
|
}
|
|
.event-title {
|
font-weight: 500;
|
font-size: 18px;
|
color: #fffffe;
|
line-height: 30px;
|
}
|
.event-content {
|
flex: 1;
|
}
|
|
.todo-list {
|
list-style: none;
|
padding: 0;
|
margin: 0;
|
height: 100%; /* 按用户要求调整高度 */
|
overflow: hidden;
|
font-size: 15px;
|
}
|
|
.todo-list li {
|
border-radius: 8px;
|
margin-bottom: 12px;
|
padding: 12px 40px;
|
height: 74px;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.todo-title {
|
font-weight: 400;
|
font-size: 16px;
|
color: #fffffe;
|
position: relative;
|
}
|
|
.todo-division {
|
font-weight: 400;
|
font-size: 16px;
|
color: #fffffe;
|
position: relative;
|
}
|
|
.todo-division::before {
|
content: "";
|
position: absolute;
|
left: -20px;
|
top: 50%;
|
transform: translateY(-50%);
|
width: 6px;
|
height: 6px;
|
background: #498ceb;
|
border-radius: 50%;
|
}
|
|
.todo-time {
|
font-weight: 400;
|
font-size: 16px;
|
color: #fffffe;
|
background: rgba(24, 93, 190, 0.4);
|
border-radius: 5px 5px 5px 5px;
|
padding: 5px 10px;
|
}
|
</style>
|