Commit 51fd1539 authored by Cai Wei's avatar Cai Wei

feat(*): 首页开发

parent 7a56a91f
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"axios": "^1.9.0", "axios": "^1.9.0",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"echarts": "^5.6.0",
"element-plus": "^2.9.10", "element-plus": "^2.9.10",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"path": "^0.12.7", "path": "^0.12.7",
...@@ -1593,6 +1594,15 @@ ...@@ -1593,6 +1594,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/echarts": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
"dependencies": {
"tslib": "2.3.0",
"zrender": "5.6.1"
}
},
"node_modules/element-plus": { "node_modules/element-plus": {
"version": "2.9.10", "version": "2.9.10",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.10.tgz", "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.10.tgz",
...@@ -2504,6 +2514,11 @@ ...@@ -2504,6 +2514,11 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
},
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "6.21.0", "version": "6.21.0",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz",
...@@ -2636,6 +2651,14 @@ ...@@ -2636,6 +2651,14 @@
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT" "license": "MIT"
},
"node_modules/zrender": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz",
"integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
"dependencies": {
"tslib": "2.3.0"
}
} }
} }
} }
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"axios": "^1.9.0", "axios": "^1.9.0",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"echarts": "^5.6.0",
"element-plus": "^2.9.10", "element-plus": "^2.9.10",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"path": "^0.12.7", "path": "^0.12.7",
......
This diff is collapsed.
...@@ -143,7 +143,7 @@ export default { ...@@ -143,7 +143,7 @@ export default {
height: calc(100vh - 4.0625rem); height: calc(100vh - 4.0625rem);
box-sizing: border-box; box-sizing: border-box;
overflow: hidden; overflow: hidden;
padding: 1.25rem 1.25rem 3.125rem; padding: 1.25rem 1.25rem 2rem;
background: #e7eef5; background: #e7eef5;
} }
} }
......
import { color } from "echarts";
export const getLineOption = (xData = [], seriesData = []) => ({
tooltip: {
trigger: 'axis',
},
grid: {
left: '3%',
right: '4%',
bottom: '15%',
top: '5%',
containLabel: true,
},
legend: {
data: ['健康度指数(%)'],
bottom: '0%',
icon:"circle",
itemWidth: 10,
itemHeight: 10,
itemGap: 10,
},
xAxis: {
type: 'category',
axisTick: {
show: false,
},
axisLine: {
lineStyle: {
color: '#E9ECF2',
},
},
axisLabel: {
color: 'rgba(0,0,0,0.6)',
},
data: xData,
},
yAxis: {
type: 'value',
axisLabel: {
color: 'rgba(0,0,0,0.6)',
},
splitLine: {
lineStyle: {
color: '#E9ECF2',
type: 'dashed',
},
},
},
series: [
{
name: '健康度指数(%)',
type: 'line',
color: '#399DFA',
data: seriesData,
smooth: true,
},
],
});
\ No newline at end of file
<template>
<div ref="chartRef" class="chart-line"></div>
</template>
<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue';
import * as echarts from 'echarts';
import { getLineOption } from '@/utils/chart';
const chartRef = ref(null);
let chartInstance = null;
const initChart = () => {
if (chartRef.value) {
chartInstance = echarts.init(chartRef.value);
const xData = ['01', '02', '03', '04', '05', '06', '07'];
const seriesData = [120, 200, 150, 80, 60, 110, 120];
const option = getLineOption(xData, seriesData);
chartInstance.setOption(option);
}
};
onMounted(() => {
initChart();
window.addEventListener('resize', () => {
chartInstance?.resize();
});
});
onBeforeUnmount(() => {
chartInstance?.dispose();
});
</script>
<style scoped>
.chart-line {
width: 100%;
height: calc(100% - 22px);
}
</style>
\ No newline at end of file
<template>
<div class="message-list" @mouseenter="pauseScroll" @mouseleave="resumeScroll">
<div class="message-wrapper" ref="messageWrapper">
<div
class="message-item"
v-for="(message, index) in extendedList"
:key="index"
>
<span class="time">{{ message.time }}</span>
<div class="content"><span class="title">{{ message.title }}</span>{{ message.content }}</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
const props = defineProps({
msgList: {
type: Array,
default: () => []
}
});
const extendedList = ref([]);
const messageWrapper = ref(null);
const itemHeight = 34; // 一条消息高度
const scrollInterval = 3000; // 每 2 秒滚动一次
let timer = null;
let isPaused = false;
// 滚动逻辑:每 2 秒滚动一条(50px)
const startScroll = () => {
stopScroll();
const wrapper = messageWrapper.value;
if (!wrapper) return;
timer = setInterval(() => {
if (isPaused) return;
const currentTop = parseInt(wrapper.style.transform.replace('translateY(', '').replace('px)', '')) || 0;
const nextTop = currentTop - itemHeight;
wrapper.style.transition = 'transform 0.5s';
wrapper.style.transform = `translateY(${nextTop}px)`;
const listHeight = (extendedList.value.length / 2) * itemHeight;
if (Math.abs(nextTop) >= listHeight) {
// 到底了,重置
setTimeout(() => {
wrapper.style.transition = 'none';
wrapper.style.transform = 'translateY(0)';
}, 500); // 等待动画完成后重置
}
}, scrollInterval);
};
const stopScroll = () => {
if (timer) {
clearInterval(timer);
timer = null;
}
};
const pauseScroll = () => {
isPaused = true;
};
const resumeScroll = () => {
isPaused = false;
};
// 数据更新时刷新滚动状态
const updateScroll = () => {
stopScroll();
if (props.msgList.length < 5) {
extendedList.value = [...props.msgList];
nextTick(() => {
if (messageWrapper.value) {
messageWrapper.value.style.transform = 'translateY(0)';
messageWrapper.value.style.transition = 'none';
}
});
} else {
extendedList.value = [...props.msgList, ...props.msgList]; // 实现无缝滚动
nextTick(() => {
if (messageWrapper.value) {
messageWrapper.value.style.transform = 'translateY(0)';
messageWrapper.value.style.transition = 'none';
}
startScroll();
});
}
};
// 生命周期钩子
onMounted(() => {
updateScroll();
});
onBeforeUnmount(() => {
stopScroll();
});
watch(() => props.msgList, () => {
updateScroll();
}, { deep: true });
</script>
<style scoped>
.message-list {
position: relative;
overflow: hidden;
max-height: 160px; /* 7条 * 50px */
width: 100%;
height: calc(100% - 22px);
}
.message-wrapper {
width: 100%;
}
.message-item {
display: flex;
align-items: center;
font-size: 14px;
height: 32px;
line-height: 32px;
padding: 1px 0;
max-width: 100%;
}
.message-item .time {
color: #58616b;
margin-right: 5px;
min-width: 80px;
flex-shrink: 0;
}
.message-item .title {
margin-right: 5px;
color: #0c0b0b;
font-weight: bold;
}
.message-item .content {
flex: 1;
color: #58616b;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
</style>
\ No newline at end of file
<template> <template>
<div class="page-container"> <div class="dashboard-container">
dashborad <div class="header">
<div class="msg-box">
<div class="title">AI智能监测</div>
<msg-item :msgList="msgList"></msg-item>
</div>
<div class="indicators-box">
<div class="title">综合健康度</div>
<div class="indicators-num">98%</div>
<div>
<div class="indicators-item">布袋健康度</div>
<el-progress :percentage="percentageO" :color="customColorMethod" />
</div>
<div>
<div class="indicators-item">脉冲阀健康度</div>
<el-progress :percentage="percentageT" :color="customColorMethod" />
</div>
<div>
<div class="indicators-item">提升阀健康度</div>
<el-progress :percentage="percentageTh" :color="customColorMethod" />
</div>
</div>
<div class="line-box">
<div class="title">健康度指数</div>
<chart-line></chart-line>
</div>
</div> </div>
</template>
\ No newline at end of file <div class="map-box">
<img src="@/assets/map.png" alt="" />
<div class="spot-box"></div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, computed, nextTick } from "vue";
import { useRoute, useRouter } from "vue-router";
import msgItem from "./components/msg-item.vue";
import chartLine from "./components/chart-line.vue";
const percentageO = ref(20);
const percentageT = ref(62);
const percentageTh = ref(90);
const customColors = [
{ color: "#ff6a6a", percentage: 60 },
{ color: "#f5c701", percentage: 80 },
{ color: "#08c733", percentage: 100 },
];
const route = useRoute();
const router = useRouter();
const msgList = ref([
{
title: "3#除尘器",
content: "三仓室存在轻微泄漏",
time: "05-18 12:00",
},
{
title: "2#除尘器",
content:
"除尘器脉冲阀故障或者提升阀故障",
time: "05-18 08:00",
},
{
title: "3#除尘器",
content: "三仓室存在轻微泄漏",
time: "05-18 07:00",
},
{
title: "2#除尘器",
content: "除尘器脉冲阀故障或者提升阀故障",
time: "05-18 05:00",
},
{
title: "3#除尘器",
content: "三仓室存在轻微泄漏",
time: "05-18 03:00",
},
{
title: "2#除尘器",
content: "除尘器脉冲阀故障或者提升阀故障",
time: "05-18 02:00",
},
{
title: "2#除尘器",
content: "除尘器脉冲阀故障或者提升阀故障",
time: "05-18 01:00",
},
{
title: "3#除尘器",
content: "三仓室存在轻微泄漏",
time: "05-17 11:00",
},
{
title: "2#除尘器",
content: "除尘器脉冲阀故障或者提升阀故障",
time: "05-16 02:00",
},
]);
const customColorMethod = (percentage) => {
if (percentage < 60) {
return customColors[0].color;
}
if (percentage < 90) {
return customColors[1].color;
}
return customColors[2].color;
};
onMounted(async () => {});
onBeforeUnmount(() => {});
</script>
<style lang="scss" scoped>
.dashboard-container {
width: 100%;
height: calc(100% - 14px);
// background: #ffffff;
// border-radius: 6px;
// box-shadow: 0px 3px 6px 0px rgba(13,15,18,0.10);
// padding: 1rem;
box-sizing: border-box;
.header {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
height: 15rem;
& > div {
background: #ffffff;
border-radius: 6px;
box-shadow: 0px 3px 6px 0px rgba(13, 15, 18, 0.1);
padding: 16px;
box-sizing: border-box;
}
.title {
font-size: 18px;
font-weight: normal;
color: #83868b;
line-height: 22px;
margin-bottom: 10px;
}
.msg-box {
width: calc(38% - 16px);
height: 100%;
}
.indicators-box {
width: 24%;
height: 100%;
.indicators-num {
font-size: 37px;
font-weight: bold;
color: #08c733;
line-height: 44px;
margin-bottom: 10px;
}
.indicators-item {
height: 22px;
font-size: 15px;
font-weight: normal;
color: #919399;
line-height: 22px;
}
}
.line-box {
width: calc(38% - 16px);
height: 100%;
}
}
.map-box {
position: relative;
width: 100%;
height: calc(100% - 16rem);
margin-top: 1rem;
background: #f5f5f5;
border-radius: 6px;
box-shadow: 0px 3px 6px 0px rgba(13, 15, 18, 0.1);
padding: 16px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
img {
display: block;
width: auto;
height: 100%;
}
}
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment