feat: 添加光伏系统主系统图页面及相关组件

新增主系统图页面及多个组件,包括设备情况、实时数据监控、功率输出趋势、操作指令记录等。添加天气图标、字体文件和样式资源,实现系统状态监控和操作功能。优化图表展示和交互体验,完善响应式布局。
This commit is contained in:
tcy
2025-09-17 17:16:37 +08:00
parent 5c5baaab44
commit 47c4b182e1
84 changed files with 2465 additions and 1 deletions

View File

@ -0,0 +1,311 @@
<template>
<div class="chart-container">
<!-- 图表标题和时间范围选择器 -->
<div class="chart-header">
<h2>功率与效率趋势</h2>
<div class="chart-actions">
<button @click="timeRange = 'day'" :class="{ active: timeRange === 'day' }">今日</button>
<button @click="timeRange = 'week'" :class="{ active: timeRange === 'week' }">本周</button>
<button @click="timeRange = 'month'" :class="{ active: timeRange === 'month' }">本月</button>
</div>
</div>
<!-- 图表内容区域 -->
<div ref="chartRef" class="chart-content"></div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import * as echarts from 'echarts';
// 图表DOM引用
const chartRef = ref(null);
// 时间范围状态
const timeRange = ref('day');
// 图表实例
let chartInstance = null;
// 定义颜色常量
const POWER_COLOR = 'rgba(42, 130, 228, 1)';
const EFFICIENCY_COLOR = 'rgba(67, 207, 124, 1)';
// 生成指定范围内的随机数(用于模拟数据)
const getRandomValue = (min, max) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
// 生成指定范围内的随机浮点数(用于效率数据)
const getRandomFloat = (min, max, decimalPlaces = 1) => {
const value = Math.random() * (max - min) + min;
return parseFloat(value.toFixed(decimalPlaces));
};
// 获取当前月份的天数
const getDaysInCurrentMonth = () => {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1; // 月份从0开始所以+1
return new Date(year, month, 0).getDate();
};
// 根据时间范围返回对应的数据
const getChartData = () => {
if (timeRange.value === 'day') {
return {
xAxis: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'],
powerData: [320, 380, 350, 420, 580, 630, 550, 480],
efficiencyData: [85.2, 86.5, 87.1, 88.3, 89.5, 89.2, 88.7, 88.1]
};
} else if (timeRange.value === 'week') {
return {
xAxis: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
powerData: [4200, 4800, 5100, 4900, 5300, 3800, 3200],
efficiencyData: [86.2, 87.5, 88.1, 87.8, 89.0, 88.5, 87.9]
};
} else {
// 本月数据 - 按天显示
const daysInMonth = getDaysInCurrentMonth();
const xAxis = [];
const powerData = [];
const efficiencyData = [];
// 生成每天的数据
for (let i = 1; i <= daysInMonth; i++) {
xAxis.push(`${i}`);
// 生成合理范围内的功率数据10000-25000之间
powerData.push(getRandomValue(10000, 25000));
// 生成合理范围内的效率数据85-90之间保留1位小数
efficiencyData.push(getRandomFloat(85, 90));
}
return {
xAxis,
powerData,
efficiencyData
};
}
};
// 初始化图表
const initChart = () => {
if (chartRef.value && !chartInstance) {
chartInstance = echarts.init(chartRef.value);
}
const data = getChartData();
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'cross' }
},
legend: {
data: ['总功率(kW)', '平均效率(%)'],
top: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: data.xAxis,
// 当月天数较多时优化X轴标签显示
axisLabel: {
interval: timeRange.value === 'month' ? 'auto' : 0,
rotate: timeRange.value === 'month' ? 45 : 0
}
},
yAxis: [
{
type: 'value',
name: '总功率(kW)',
axisLabel: {
formatter: '{value}'
}
},
{
type: 'value',
name: '平均效率(%)',
min: 80,
max: 95,
axisLabel: {
formatter: '{value}%'
}
}
],
series: [
{
name: '总功率(kW)',
type: 'line',
data: data.powerData,
smooth: true,
lineStyle: {
width: 3,
color: POWER_COLOR
},
symbol: 'circle',
symbolSize: 8,
itemStyle: {
color: POWER_COLOR
},
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' }
],
itemStyle: {
color: POWER_COLOR
}
}
},
{
name: '平均效率(%)',
type: 'line',
yAxisIndex: 1,
data: data.efficiencyData,
smooth: true,
lineStyle: {
width: 3,
type: 'dashed',
color: EFFICIENCY_COLOR
},
symbol: 'diamond',
symbolSize: 8,
itemStyle: {
color: EFFICIENCY_COLOR
},
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' }
],
itemStyle: {
color: EFFICIENCY_COLOR
}
}
}
]
};
chartInstance.setOption(option);
};
// 响应窗口大小变化
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
// 生命周期钩子
onMounted(() => {
initChart();
window.addEventListener('resize', handleResize);
// 清理函数
return () => {
window.removeEventListener('resize', handleResize);
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
};
});
// 监听时间范围变化,更新图表
watch(timeRange, () => {
initChart();
});
</script>
<style scoped>
.chart-container {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
overflow: hidden;
height: 400px;
width: 100%;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #f0f0f0;
}
.chart-header h2 {
font-size: 16px;
font-weight: 600;
color: #333;
margin: 0;
}
.chart-actions button {
background: none;
border: 1px solid #e0e0e0;
padding: 5px 12px;
border-radius: 4px;
margin-left: 8px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s ease;
}
.chart-actions button.active {
background-color: #1890ff;
color: white;
border-color: #1890ff;
}
.chart-content {
width: 100%;
height: calc(100% - 54px);
padding: 10px;
}
@media (max-width: 768px) {
.chart-container {
height: 350px;
}
}
@media (max-width: 480px) {
.chart-container {
height: 300px;
}
.chart-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.chart-actions {
width: 100%;
display: flex;
justify-content: space-between;
}
.chart-actions button {
margin: 0;
flex: 1;
margin-right: 5px;
}
.chart-actions button:last-child {
margin-right: 0;
}
}
.model {
padding: 20px;
background-color: rgba(242, 248, 252, 1);
}
</style>

View File

@ -0,0 +1,210 @@
<template>
<el-card shadow="never" style="border-radius: 10px;">
<div style="margin-bottom: 20px;display: flex;align-items: center;justify-content: right;">
<span style="margin-right: 5px;color: rgba(113, 128, 150, 1);font-size: 14px;">
查看全部告警信息
</span>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="8" height="8"
viewBox="0 0 8 8" fill="none">
<path
d="M5.94575 4.15722C5.94975 4.14947 5.95624 4.14298 5.95975 4.13497C6.0285 3.99197 6.00599 3.81697 5.88425 3.70197L3.1245 1.09172C2.97399 0.949466 2.73676 0.956216 2.59425 1.10648C2.45199 1.25698 2.4585 1.49422 2.60901 1.63673L5.08526 3.97922L2.61875 6.35647C2.46975 6.50021 2.46525 6.73746 2.60901 6.88672C2.6825 6.96321 2.78076 7.00148 2.87901 7.00148C2.97276 7.00148 3.06651 6.96648 3.13926 6.89648L5.87401 4.26073C5.87927 4.25547 5.88125 4.24823 5.88651 4.24274C5.89052 4.23899 5.89476 4.23623 5.89875 4.23224C5.92 4.20997 5.93124 4.18272 5.94575 4.15722Z"
fill="#718096">
</path>
</svg>
</div>
<div class="notice">
<div class="item">
<div class="icon">
<img src="/assets/zgjxx.png" alt="">
</div>
<div class="content">
<p>总告警信息</p>
<p style="color: rgba(42, 130, 228, 1);">1,921</p>
</div>
</div>
<div class="item">
<div class="icon">
<img src="/assets/ycl.png" alt="">
</div>
<div class="content">
<p>已处理</p>
<p style="color:rgba(0, 184, 122, 1);">500</p>
</div>
</div>
<div class="item">
<div class="icon">
<img src="/assets/zzcl.png" alt="">
</div>
<div class="content">
<p>正在处理</p>
<p style="color: rgba(255, 141, 26, 1);">200</p>
</div>
</div>
<div class="item">
<div class="icon">
<img src="/assets/wcl.png" alt="">
</div>
<div class="content">
<p>未处理</p>
<p style="color: rgba(227, 39, 39, 1);">1,221</p>
</div>
</div>
</div>
<div class="content">
<div class="list">
<div class="item waring">
<div class="left">
<p class="title">
逆变器温度过高
</p>
<p class="text">INV-2023-003 温度达到52超过阈值48</p>
</div>
<div class="right">
<div class="time">
10分钟前
</div>
<el-text type="warning" size="small">正在处理</el-text>
</div>
</div>
<div class="item danger">
<div class="left">
<p class="title">
通信中断
</p>
<p class="text">INV-2023-003 与监控系统通信中断</p>
</div>
<div class="right">
<div class="time">
20分钟前
</div>
<el-text type="primary" size="small">查看详情</el-text>
</div>
</div>
<div class="item danger">
<div class="left">
<p class="title">
通信中断
</p>
<p class="text">INV-2023-003 与监控系统通信中断</p>
</div>
<div class="right">
<div class="time">
1小时前
</div>
<el-text type="primary" size="small">查看详情</el-text>
</div>
</div>
<div class="item grey">
<div class="left">
<p class="title">
逆变器温度过高
</p>
<p class="text">INV-2023-003 温度达到52超过阈值48</p>
</div>
<div class="right">
<div class="time">
2小时前
</div>
<!-- <el-text type="primary" size="small">查看详情</el-text> -->
<el-button type="info" size="small" disabled>已处理</el-button>
</div>
</div>
</div>
</div>
</el-card>
</template>
<style scoped lang="scss">
.notice {
display: flex;
justify-content: space-between;
border-bottom: 1px solid rgba(32, 32, 32, 0.05);
padding-bottom: 20px;
.item {
display: flex;
}
p {
padding: 0;
margin: 0;
}
.icon {
width: 50px;
height: 50px;
margin-right: 10px;
img {
display: block;
width: 100%;
height: 100%;
}
}
.content p:nth-child(2) {
font-size: 14px;
margin-top: 5px;
}
}
.content {
.item {
border-radius: 10px;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
.text {
color: rgba(125, 133, 146, 1);
font-size: 12px;
}
.title {
font-size: 16px;
}
.right {
font-size: 12px;
color: rgba(125, 133, 146, 1);
.time {
margin-bottom: 5px;
}
}
}
.waring {
background-color: #FFFCE6;
border: 1px solid #FFF0E1;
.title {
color: #FFA408;
}
}
.danger {
background-color: #FFE9E5;
border: 1px solid #FFEFEB;
.title {
color: rgba(255, 87, 51, 1);
}
}
.grey {
background-color: #F3F3F3;
border: 1px solid #FCFCFC;
.right {
text-align: right;
}
.title {
color: rgba(102, 102, 102, 1);
}
}
}
</style>
<script setup></script>

View File

@ -0,0 +1,216 @@
<template>
<div class="cardItem">
<el-card>
<div class="tianqi"
style="display: flex; flex-direction: column; align-items: center; background-color: #fafafa; border-radius: 10px; padding-bottom: 40px">
<div>
<img src="/assets/Sunny.png" style="display: block; width: 100px; height: 100px" alt="" />
</div>
<div style="font-family: 'Alibaba-PuHuiTi-Bold'; font-size: 24px">31</div>
<div>晴朗</div>
<div style="color: rgba(154, 154, 154, 1); font-size: 14px">紫外线强度<span></span></div>
<div class="tianqi2">
<div class="item">
<div>
<img src="/assets/shidu.png" alt="" />
</div>
<div class="text">相对湿度 <span>45%</span></div>
</div>
<div class="item">
<div>
<img src="/assets/qiangdu.png" alt="" />
</div>
<div class="text">光照强度 <span>45%</span></div>
</div>
<div class="item">
<div>
<img src="/assets/fengshu.png" alt="" />
</div>
<div class="text">风速 <span>2.3m/s</span></div>
</div>
<div class="item">
<div>
<img src="/assets/riluo.png" alt="" />
</div>
<div class="text">日落时间 <span>19.45</span></div>
</div>
</div>
</div>
<div class="weather-timeline">
<div class="time-box">
<div class="time-item">
<div class="time">16:00</div>
<div class="temp">30°C</div>
<div class="img-box">
<img src="/assets/sunny_s.png" alt="" />
</div>
</div>
<div class="time-item">
<div class="time">17:00</div>
<div class="temp">29°C</div>
<div class="img-box">
<img src="/assets/sunny_s.png" alt="" />
</div>
</div>
<div class="time-item">
<div class="child">
<div class="time">18:00</div>
<div class="temp">25°C</div>
<div class="img-box">
<img src="/assets/rain.png" alt="" />
</div>
</div>
</div>
<div class="time-item show">
<div class="time">现在</div>
<div class="temp">25°C</div>
<div class="img-box">
<img src="/assets/rain_show.png" alt="" />
</div>
</div>
<div class="time-item">
<div class="time">20:00</div>
<div class="temp">25°C</div>
<div class="img-box">
<img src="/assets/yin.png" alt="" />
</div>
</div>
<div class="time-item">
<div class="time">21:00</div>
<div class="temp">20°C</div>
<div class="img-box">
<img src="/assets/yin.png" alt="" />
</div>
</div>
</div>
</div>
</el-card>
</div>
</template>
<style scoped lang="scss">
.cardItem {
padding: -20px !important;
}
.tianqi2 {
width: 100%;
display: flex;
justify-content: space-around;
margin-top: 50px;
img {
width: 40px;
height: 40x;
display: block;
}
.item {
display: flex;
flex-direction: column;
align-items: center;
font-size: 14px;
.text {
margin-top: 10px;
}
}
}
.weather-timeline {
text-align: center;
color: #fff;
font-size: 14px;
margin: 15px 0;
.time {
margin: 10px 0;
}
.img-box {
width: 40px;
height: 40px;
margin: 10px 0;
}
img {
width: 100%;
height: 100%;
display: block;
}
// img[src*='Sunny'] {
// width: 50px;
// height: 50px;
// }
// img[src*='rain'] {
// width: 60px;
// height: 60px;
// }
padding: 15px;
background: linear-gradient(to right, #d6e2ff, #deebff);
border-radius: 15px;
.time-box {
background: linear-gradient(to right, #447bff, #67a3fd);
display: flex;
justify-content: space-between;
border-radius: 10px;
padding: 10px 20px;
align-items: center;
}
.time-item.show {
color: rgba(24, 109, 245, 1);
position: relative;
// z-index: 888;
background-color: #fff;
padding: 0 5px;
}
.show::after {
// color: rgba(24, 109, 245, 1);
// position: relative;
// z-index: 888;
// background-color: #fff;
content: '';
position: absolute;
width: 100%;
height: 25px;
// background-color: red;
background-color: #fff;
left: 0;
border-radius: 0 0 25px 25px;
}
.show::before {
// color: rgba(24, 109, 245, 1);
// position: relative;
// z-index: 888;
// background-color: #fff;
content: '';
position: absolute;
width: 100%;
height: 25px;
// background-color: red;
background-color: #fff;
left: 0;
top: -25px;
border-radius: 25px 25px 0 0;
}
// .show::after {
// content: '';
// position: absolute;
// height: 155px;
// background-color: #fff;
// z-index: 999;
// width: 100%;
// top: -25px;
// left: 0;
// border-radius: 20px;
// }
}
</style>
<script setup></script>

View File

@ -0,0 +1,107 @@
<template>
<el-card shadow="never" style="border-radius: 10px;">
<el-form :inline="true" :model="formInline" label-width="120" style="display: flex; justify-content: center;">
<el-form-item label="规则编号">
<el-input v-model="formInline.user" placeholder="请输入规则编号" clearable />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="formInline.region" placeholder="请输入状态" clearable>
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="更新时间">
<el-date-picker v-model="formInline.date" type="date" placeholder="请选择时间" clearable />
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="onSubmit">搜索</el-button>
</el-form-item>
<el-form-item>
<el-button icon="refresh" type="default" @click="onSubmit">重置</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="listData" @selection-change="handleSelectionChange">
<!-- <el-table-column type="index" width="50" label="序号" /> -->
<el-table-column label="逆变器编号" align="center" prop="id" />
<!-- <el-table-column label="合同类型" align="center" prop="contractType">
<template #default="scope">
<dict-tag :options="income_contract_type" :value="scope.row.contractType" />
</template>
</el-table-column> -->
<el-table-column label="输出功率" align="center" prop="shuchu" />
<el-table-column label="效率" align="center" prop="xiaolv" />
<el-table-column label="温度" align="center" prop="wendu" />
<el-table-column label="今日发电量" align="center" prop="fadian" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<!-- <el-button link type="primary">详情</el-button>
<el-button link type="danger">处理</el-button>
<el-button link type="warning">维护记录</el-button> -->
<el-tag :type="statusMap[scope.row.status].type">{{ statusMap[scope.row.status].label }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
<template #default="scope">
<el-button link type="primary">详情</el-button>
<el-button link type="danger">处理</el-button>
<el-button link type="warning">维护记录</el-button>
</template>
</el-table-column>
</el-table>
<!-- <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="getList" /> -->
<pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</el-card>
</template>
<script setup>
const formInline = ref({})
const total = ref(0);
const loading = ref(false);
const listData = [
{ id: "INV-2023-001", shuchu: "12.8kw", xiaolv: "98.2%", wendu: "42℃", fadian: "158.5kWh", status: 1 },
{ id: "INV-2023-001", shuchu: "12.8kw", xiaolv: "98.2%", wendu: "42℃", fadian: "158.5kWh", status: 1 },
{ id: "INV-2023-001", shuchu: "12.8kw", xiaolv: "98.2%", wendu: "42℃", fadian: "158.5kWh", status: 2 },
{ id: "INV-2023-001", shuchu: "12.8kw", xiaolv: "98.2%", wendu: "42℃", fadian: "158.5kWh", status: 2 },
{ id: "INV-2023-001", shuchu: "12.8kw", xiaolv: "98.2%", wendu: "42℃", fadian: "158.5kWh", status: 3 },
{ id: "INV-2023-001", shuchu: "12.8kw", xiaolv: "98.2%", wendu: "42℃", fadian: "158.5kWh", status: 3 }
]
const statusMap = {
1: {
label: "正常运行",
type: "success"
},
2: {
label: "异常",
type: "danger"
},
3: {
label: "维护中",
type: "warning"
}
}
const initFormData = {
};
const data = reactive({
form: { ...initFormData },
queryParams: {
},
});
const { queryParams, form, rules } = toRefs(data);
/** 多选框选中数据 */
const handleSelectionChange = (selection) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
const getList = async () => {
// loading.value = true;
// const res = await listIncomeContract(queryParams.value);
// incomeContractList.value = res.rows;
// total.value = res.total;
// loading.value = false;
};
</script>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long