This commit is contained in:
dhr
2025-09-19 10:24:20 +08:00
127 changed files with 7983 additions and 15 deletions

View File

@ -1,11 +1,11 @@
# 页面标题
VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统
VITE_APP_TITLE = 新能源场站智慧运维平台
# 开发环境配置
VITE_APP_ENV = 'development'
# 开发环境
VITE_APP_BASE_API = 'http://192.168.110.149:18899'
VITE_APP_BASE_API = 'http://192.168.110.180:18899'
# 应用访问路径 例如使用前缀 /admin/
VITE_APP_CONTEXT_PATH = '/'

View File

@ -1,5 +1,5 @@
# 页面标题
VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统
VITE_APP_TITLE = 新能源场站智慧运维平台
# 生产环境配置
VITE_APP_ENV = 'production'

View File

@ -6,7 +6,7 @@
<meta name="renderer" content="webkit" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<link rel="icon" href="/favicon.ico" />
<title>RuoYi-Vue-Plus多租户管理系统</title>
<title>煤科运维平台</title>
<!--[if lt IE 11
]><script>
window.location.href = '/html/ie.html';

View File

@ -1,8 +1,8 @@
{
"$schema": "https://json.schemastore.org/package",
"name": "ruoyi-vue-plus",
"name": "新能源场站智慧运维平台",
"version": "5.4.1-2.4.1",
"description": "RuoYi-Vue-Plus多租户管理系统",
"description": "新能源场站智慧运维平台",
"author": "LionLi",
"license": "MIT",
"type": "module",
@ -29,6 +29,7 @@
"axios": "1.8.4",
"crypto-js": "4.2.0",
"echarts": "5.6.0",
"echarts-liquidfill": "^3.1.0",
"element-plus": "2.9.8",
"file-saver": "2.0.5",
"highlight.js": "11.9.0",
@ -93,4 +94,4 @@
"Safari >= 14",
"Firefox >= 78"
]
}
}

BIN
public/assets/Sunny.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
public/assets/Weather.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
public/assets/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
public/assets/back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
public/assets/back2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

BIN
public/assets/back3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

BIN
public/assets/back4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

BIN
public/assets/beUnder.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
public/assets/bigRain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
public/assets/bigSnow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
public/assets/cloudy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
public/assets/contract.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
public/assets/czzl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
public/assets/dayImg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
public/assets/dayImg1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
public/assets/fengshu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
public/assets/fog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/assets/glsc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/assets/haze.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
public/assets/manyCloud.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
public/assets/qiangdu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/assets/rain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1014 B

BIN
public/assets/rain1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
public/assets/rainSnow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
public/assets/rain_show.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 993 B

BIN
public/assets/riluo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
public/assets/sandstorm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
public/assets/sb1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
public/assets/sb2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
public/assets/sb3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
public/assets/sb4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
public/assets/sbi1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
public/assets/sbi2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
public/assets/shidu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/assets/sjjk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
public/assets/smallRain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
public/assets/smallSnow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
public/assets/sunnyBig.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/assets/sunny_s.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
public/assets/wcl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
public/assets/ycl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
public/assets/yin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
public/assets/zgjxx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
public/assets/zzcl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 34 KiB

BIN
src/assets/demo/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
src/assets/demo/back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
src/assets/demo/baojing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

BIN
src/assets/demo/gaojing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/assets/demo/huojian.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

BIN
src/assets/demo/more.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

BIN
src/assets/demo/rain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

BIN
src/assets/demo/upload.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

BIN
src/assets/demo/use.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

BIN
src/assets/demo/wcl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/assets/demo/wendu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 B

BIN
src/assets/demo/yichuli.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
src/assets/demo/zzcl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

165
src/assets/fonts/fonts.scss Normal file
View File

@ -0,0 +1,165 @@
@font-face {
font-family: 'Rang_men_zheng'; //庞门正道字体
src: url('./PangmenTi/PangMenZhengDaoBiaoTiTi-1.ttf');
font-style: normal;
}
@font-face {
font-family: 'Rang_men_zheng_title'; //庞门正道标题体
src: url('./PangMenZhengDaoBiaoTiTi/PangMenZhengDaoBiaoTiTi-1.ttf');
font-weight: normal;
font-weight: normal;
font-style: normal;
}
// 思源字体
// @font-face {
// font-family: 'SourceHanSansCN-Bold';
// src: url('./ReflectTi/SourceHanSansCN-Bold_0.otf'); //暂时没用
// font-weight: normal;
// font-style: normal;
// }
// @font-face {
// font-family: 'SourceHanSansCN-ExtraLight';
// src: url('./ReflectTi/SourceHanSansCN-ExtraLight.otf');//暂时没用
// font-weight: normal;
// font-style: normal;
// }
// @font-face {
// font-family: 'SourceHanSansCN-Heavy';
// src: url('./ReflectTi/SourceHanSansCN-Heavy.otf');//暂时没用
// font-weight: normal;
// font-style: normal;
// }
// @font-face {
// font-family: 'SourceHanSansCN-Light';
// src: url('./ReflectTi/SourceHanSansCN-Light.otf');
// font-weight: normal;
// font-style: normal;
// }
// @font-face {
// font-family: 'SourceHanSansCN-Medium';
// src: url('./ReflectTi/SourceHanSansCN-Medium_0.otf');
// font-weight: normal;
// font-style: normal;
// }
// @font-face {
// font-family: 'SourceHanSansCN-Normal';
// src: url('./ReflectTi/SourceHanSansCN-Normal.otf');//暂时没用
// font-weight: normal;
// font-style: normal;
// }
@font-face {
font-family: 'SourceHanSansCN-Regular';
src: url('./ReflectTi/SourceHanSansCN-Regular.otf');
font-weight: normal;
font-style: normal;
}
// @font-face {
// font-family: 'SourceHanSansCN-Bold';
// src: url('./ReflectTi/SourceHanSerifCN-Bold.otf');//暂时没用
// font-weight: normal;
// font-style: normal;
// }
// @font-face {
// font-family: 'SourceHanSansCN-ExtraLight';
// src: url('./ReflectTi/SourceHanSerifCN-ExtraLight.otf');//暂时没用
// font-weight: normal;
// font-style: normal;
// }
// @font-face {
// font-family: 'SourceHanSerifCN-Heavy';
// src: url('./ReflectTi/SourceHanSerifCN-Heavy.otf');//暂时没用
// font-weight: normal;
// font-style: normal;
// }
// @font-face {
// font-family: 'SourceHanSerifCN-Light';
// src: url('./ReflectTi/SourceHanSerifCN-Light.otf');//暂时没用
// font-weight: normal;
// font-style: normal;
// }
// @font-face {
// font-family: 'SourceHanSerifCN-Medium';
// src: url('./ReflectTi/SourceHanSerifCN-Medium.otf');//暂时没用
// font-weight: normal;
// font-style: normal;
// }
// @font-face {
// font-family: 'SourceHanSerifCN-Regular';
// src: url('./ReflectTi/SourceHanSerifCN-Regular.otf');//暂时没用
// font-weight: normal;
// font-style: normal;
// }
// @font-face {
// font-family: 'SourceHanSerifCN-SemiBold';
// src: url('./ReflectTi/SourceHanSerifCN-SemiBold.otf');//暂时没用
// font-weight: normal;
// font-style: normal;
// }
// 阿里巴巴普惠体
@font-face {
font-family: 'Alibaba-PuHuiTi-Bold';
src: url('./Alibaba/Alibaba-PuHuiTi-Bold.otf');
font-weight: normal;
font-style: normal;
}
//阿里黑体
@font-face {
font-family: 'AlimamaShuHeiTi-Bold';
src: url('./Alibaba/AlimamaShuHeiTi-Bold.otf');
font-weight: normal;
font-style: normal;
}
// @font-face {
// font-family: 'Alibaba-PuHuiTi-Heavy';
// src: url('./Alibaba/Alibaba-PuHuiTi-Heavy.otf');//暂时没用
// font-weight: normal;
// font-style: normal;
// }
// @font-face {
// font-family: 'Alibaba-PuHuiTi-Light';
// src: url('./Alibaba/Alibaba-PuHuiTi-Light.otf');//暂时没用
// font-weight: normal;
// font-style: normal;
// }
@font-face {
font-family: 'Alibaba-PuHuiTi-Medium';
src: url('./Alibaba/Alibaba-PuHuiTi-Medium.otf');
font-weight: normal;
font-style: normal;
}
// @font-face {
// font-family: 'Alibaba-PuHuiTi-Regular';
// src: url('./Alibaba/Alibaba-PuHuiTi-Regular.otf');//暂时没用
// font-weight: normal;
// font-style: normal;
// }
@font-face {
font-family: 'DOUYUFont'; //斗鱼追光体
src: url('./DouYu//斗鱼追光体2.0.ttf');
font-weight: normal;
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'D-Din';
src: url('./D-Din//D-DIN.ttf');
font-weight: normal;
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Roboto-Regular'; //Roboto
src: url('./Roboto//Roboto-Regular.ttf');
font-weight: normal;
font-weight: normal;
font-style: normal;
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,30 @@
<template>
<el-row>
<el-col>
<div style="color: rgba(0, 30, 59, 1);font-family: 'Alibaba-PuHuiTi-Bold';margin: 10px 0 0 0;"
:style="{ fontSize: fontLevelMap[props.fontLevel] }">
{{ props.title }}
</div>
</el-col>
<el-col>
<p style="color: rgba(154, 154, 154, 1);font-size: 14px;">
{{ props.subtitle }}
</p>
</el-col>
</el-row>
</template>
<script setup>
const props = defineProps({
title: String,
subtitle: String,
fontLevel: {
type: Number,
default: 1
}
})
const fontLevelMap = {
1: "24px",
2: "18px"
}
</script>

View File

@ -1,19 +1,18 @@
<template>
<div
class="sidebar-logo-container"
:class="{ collapse: collapse }"
:style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"
>
<div class="sidebar-logo-container" :class="{ collapse: collapse }"
:style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
<transition :enter-active-class="proxy?.animate.logoAnimate.enter" mode="out-in">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
<h1 v-else class="sidebar-title"
:style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
{{ title }}
</h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
<h1 class="sidebar-title"
:style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
{{ title }}
</h1>
</router-link>
@ -34,7 +33,7 @@ defineProps({
}
});
const title = ref('RuoYi-Vue-Plus');
const title = ref('新能源场站智慧运维平台');
const settingsStore = useSettingsStore();
const sideTheme = computed(() => settingsStore.sideTheme);
</script>

View File

@ -34,6 +34,8 @@ import i18n from '@/lang/index';
// vxeTable
import VXETable from 'vxe-table';
import 'vxe-table/lib/style.css';
import '@/assets/fonts/fonts.scss';
VXETable.setConfig({
zIndex: 999999
});

View File

@ -0,0 +1,497 @@
<template>
<div class="data-dashboard">
<!-- 标题区域 -->
<el-row class="dashboard-header">
<el-col :span="12">
<TitleComponent title="实时数据" :fontLevel="2" />
</el-col>
<el-col :span="12" class="text-right">
<span class="update-time">截止至2025/06/30 12:00</span>
</el-col>
</el-row>
<!-- 关键指标卡片区域 -->
<el-row class="metrics-container" :gutter="0">
<el-col :span="6">
<div class="metric-card">
<div class="metric-value">{{ props.dashboardData.todayAlarmTotal }}</div>
<div class="metric-label">今日报警总数</div>
<div class="metric-change">较上周 <span :class="props.dashboardData.updates.todayAlarmTotal.type">
{{ props.dashboardData.updates.todayAlarmTotal.type === 'up' ? '↑' : '↓' }}{{ props.dashboardData.updates.todayAlarmTotal.value }}
</span></div>
</div>
</el-col>
<el-col :span="6">
<div class="metric-card">
<div class="metric-value">{{ props.dashboardData.unhandledAlarms }}</div>
<div class="metric-label">未处理报警</div>
<div class="metric-change">较上周 <span :class="props.dashboardData.updates.unhandledAlarms.type">
{{ props.dashboardData.updates.unhandledAlarms.type === 'up' ? '↑' : '↓' }}{{ props.dashboardData.updates.unhandledAlarms.value }}
</span></div>
</div>
</el-col>
<el-col :span="6">
<div class="metric-card">
<div class="metric-value">{{ props.dashboardData.handledAlarms }}</div>
<div class="metric-label">已处理报警</div>
<div class="metric-change">较上周 <span :class="props.dashboardData.updates.handledAlarms.type">
{{ props.dashboardData.updates.handledAlarms.type === 'up' ? '↑' : '↓' }}{{ props.dashboardData.updates.handledAlarms.value }}
</span></div>
</div>
</el-col>
<el-col :span="6">
<div class="metric-card">
<div class="metric-value">{{ props.dashboardData.avgProcessTime }}</div>
<div class="metric-label">平均处理时长</div>
<div class="metric-change">较上周 <span :class="props.dashboardData.updates.avgProcessTime.type">
{{ props.dashboardData.updates.avgProcessTime.type === 'up' ? '↑' : '↓' }}{{ props.dashboardData.updates.avgProcessTime.value }}
</span></div>
</div>
</el-col>
</el-row>
<!-- 报警趋势图表区域 -->
<el-row class="trend-container">
<!-- 使用弹性布局实现标题在左下拉框在右 -->
<div class="trend-header-flex">
<TitleComponent title="报警趋势" :fontLevel="2" />
<el-select v-model="timeRange" placeholder="近7天" size="small">
<el-option label="近7天" value="7days" />
<el-option label="近30天" value="30days" />
<el-option label="近90天" value="90days" />
</el-select>
</div>
<el-col :span="24">
<div class="trend-section">
<el-row :gutter="20">
<!-- 报警数量图表 -->
<el-col :span="12">
<div class="chart-container">
<div class="chart-left-right-layout">
<div class="chart-info-container">
<div class="chart-title">报警数量() </div>
<div class="chart-total">{{ props.chartData.totals.alarmCount }}</div>
<div class="chart-value">较昨日 <span :class="props.chartData.dailyChanges.alarmCount.type">
{{ props.chartData.dailyChanges.alarmCount.type === 'up' ? '↑' : '↓' }}{{ props.chartData.dailyChanges.alarmCount.value }}
</span></div>
</div>
<div ref="alarmCountRef" class="chart-content"></div>
</div>
</div>
</el-col>
<!-- 报警处理效率图表 -->
<el-col :span="12">
<div class="chart-container">
<div class="chart-left-right-layout">
<div class="chart-info-container">
<div class="chart-title">报警处理效率(%)</div>
<div class="chart-total">{{ props.chartData.totals.processEfficiency }}</div>
<div class="chart-value">较昨日 <span :class="props.chartData.dailyChanges.processEfficiency.type">
{{ props.chartData.dailyChanges.processEfficiency.type === 'up' ? '↑' : '↓' }}{{ props.chartData.dailyChanges.processEfficiency.value }}
</span></div>
</div>
<div ref="processEfficiencyRef" class="chart-content"></div>
</div>
</div>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import * as echarts from 'echarts';
import TitleComponent from '@/components/TitleComponent/index.vue';
// 定义props
const props = defineProps({
dashboardData: {
type: Object,
default: () => ({
todayAlarmTotal: 25,
unhandledAlarms: 8,
handledAlarms: 16,
avgProcessTime: '42分钟',
updates: {
todayAlarmTotal: { value: '4.2%', type: 'down' },
unhandledAlarms: { value: '5%', type: 'up' },
handledAlarms: { value: '8%', type: 'down' },
avgProcessTime: { value: '10%', type: 'down' }
}
})
},
chartData: {
type: Object,
default: () => ({
alarmCount: [150, 230, 224, 218, 135, 147, 260],
processEfficiency: [85, 88, 90, 87, 89, 91, 89],
dates: ['04/21', '04/22', '04/23', '04/24', '04/25', '04/26', '04/27'],
totals: {
alarmCount: 56,
processEfficiency: '89%'
},
dailyChanges: {
alarmCount: { value: '0.9%', type: 'down' },
processEfficiency: { value: '0.9%', type: 'down' }
}
})
}
});
const timeRange = ref('7days');
const alarmCountRef = ref(null);
const processEfficiencyRef = ref(null);
let alarmCountChart = null;
let processEfficiencyChart = null;
// 初始化图表
const initCharts = () => {
// 报警数量图表
if (alarmCountRef.value) {
alarmCountChart = echarts.init(alarmCountRef.value);
}
// 报警处理效率图表
if (processEfficiencyRef.value) {
processEfficiencyChart = echarts.init(processEfficiencyRef.value);
}
// 设置报警数量图表配置
const alarmCountOption = {
tooltip: {
trigger: 'axis',
},
grid: {
left: '-45px',
right: '0%',
bottom: '0%',
top: '0%',
containLabel: true,
},
xAxis: {
type: 'category',
//文本颜色
axisLabel: {
textStyle: {
color: '#999999'
},
interval: 2 // 每隔三天显示一个标签索引从0开始间隔为2表示每3个数据点显示一个
},
// x轴线样式
axisLine: {
show: false,
lineStyle: {
color: "#427394"
}
},
axisTick: {
show: false
},
data: props.chartData.dates,
},
yAxis: {
type: 'value',
show: false,
axisLabel: {
textStyle: {
color: '#C5D6E6'
}
},
splitLine: {
show: false
}
},
series: [
{
data: props.chartData.alarmCount,
type: 'line',
smooth: true,
symbol: 'none',
lineStyle: {
color: '#3692FF',
width: 2
},
// 悬停时颜色
itemStyle: {
color: '#3692FF'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(54, 146, 255, 0.3)'
},
{
offset: 1,
color: 'rgba(54, 146, 255, 0.05)'
}
])
}
}
]
};
// 设置报警处理效率图表配置
const processEfficiencyOption = {
tooltip: {
trigger: 'axis',
},
grid: {
left: '-45px',
right: '0%',
bottom: '0%',
top: '0%',
containLabel: true,
},
xAxis: {
type: 'category',
//文本颜色
axisLabel: {
textStyle: {
color: '#999999'
},
interval: 2 // 每隔三天显示一个标签索引从0开始间隔为2表示每3个数据点显示一个
},
// x轴线样式
axisLine: {
show: false,
lineStyle: {
color: "#427394"
}
},
axisTick: {
show: false
},
data: props.chartData.dates,
},
yAxis: {
type: 'value',
show: false,
axisLabel: {
textStyle: {
color: '#C5D6E6'
}
},
splitLine: {
show: false
}
},
series: [
{
data: props.chartData.processEfficiency,
type: 'line',
smooth: true,
symbol: 'none',
lineStyle: {
color: '#FF9900',
width: 2
},
// 悬停时颜色
itemStyle: {
color: '#FF9900'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(255, 140, 0, 0.3)'
},
{
offset: 1,
color: 'rgba(255, 140, 0, 0.05)'
}
])
}
}
]
};
alarmCountChart.setOption(alarmCountOption);
processEfficiencyChart.setOption(processEfficiencyOption);
};
// 处理窗口大小变化
const handleResize = () => {
if (alarmCountChart) {
alarmCountChart.resize();
}
if (processEfficiencyChart) {
processEfficiencyChart.resize();
}
};
onMounted(() => {
initCharts();
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
if (alarmCountChart) {
alarmCountChart.dispose();
}
if (processEfficiencyChart) {
processEfficiencyChart.dispose();
}
});
</script>
<style scoped lang="scss">
.data-dashboard {
width: 100%;
height: 100%;
background: #fff;
padding:0 20px;
box-sizing: border-box;
}
.dashboard-header {
align-items: center;
}
.dashboard-header h2 {
margin: 0;
font-size: 18px;
font-weight: 500;
color: #333;
}
.update-time {
font-size: 12px;
color: #999;
}
.metrics-container {
border-radius: 4px;
overflow: hidden;
background-color: rgba(242, 248, 252, 1);
background-color: rgba(242, 248, 252, 1);
}
.metric-card {
padding: 20px;
text-align: center;
position: relative;
height: 100%;
}
/* 移除最后一个卡片的分隔线 */
.metrics-container .el-col:last-child .metric-card::after {
display: none;
}
.metric-value {
font-size: 24px;
font-weight: bold;
color: #333;
margin-bottom: 8px;
}
.metric-label {
font-size: 14px;
color: #666;
margin-bottom: 4px;
}
.metric-change {
font-size: 12px;
color: #999;
}
.up {
color: #ff4d4f;
}
.down {
color: #52c41a;
}
.trend-container {
width: 100%;
}
.trend-header-flex {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.trend-section {
background: #fff;
border-radius: 4px;
padding: 20px;
}
.trend-header {
align-items: center;
}
.trend-header h3 {
margin: 0;
font-size: 16px;
font-weight: 500;
color: #333;
}
.chart-container {
background: #fff;
border-radius: 4px;
display: flex;
align-items: center;
height: 150px;
}
.chart-left-right-layout {
display: flex;
align-items: center;
height: 100%;
width: 100%;
}
.chart-info-container {
width: 110px;
}
.chart-title {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.chart-total {
font-size: 32px;
font-weight: bold;
color: #333;
margin-bottom: 8px;
}
.chart-value {
font-size: 12px;
color: #999;
margin-bottom: 0;
}
.chart-content {
flex: 1;
height: 110px;
}
// Element Plus 样式覆盖
:deep(.el-select) {
width: 80px;
}
</style>

View File

@ -0,0 +1,145 @@
<template>
<div class="pie-chart-container">
<div class="chart-header">
<TitleComponent title="报警类型分布" :fontLevel="2" />
<el-select v-model="selectedTimeRange" placeholder="选择时间范围" size="small">
<el-option label="今日" value="today" />
</el-select>
</div>
<div ref="pieChartRef" class="chart-content"></div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import * as echarts from 'echarts';
import TitleComponent from '@/components/TitleComponent/index.vue';
// 定义props
const props = defineProps({
alarmTypeData: {
type: Array,
default: () => []
}
});
const selectedTimeRange = ref('today');
const pieChartRef = ref(null);
let chartInstance = null;
onMounted(() => {
initChart();
window.addEventListener('resize', handleResize);
});
const handleResize = () => {
if (chartInstance) {
chartInstance.resize();
}
};
const initChart = () => {
if (pieChartRef.value) {
chartInstance = echarts.init(pieChartRef.value);
} else {
console.warn('Chart container not found');
return;
}
const option = {
backgroundColor: '#fff',
tooltip: {
trigger: 'item',
formatter: '{b}: {d}%'
},
legend: {
bottom: '5%',
left: 'center',
formatter: (name) => {
// 计算每个数据项的百分比
const data = option.series[0].data;
const total = data.reduce((sum, item) => sum + item.value, 0);
const item = data.find(item => item.name === name);
const percentage = item ? Math.round((item.value / total) * 100) : 0;
return `${name}(${percentage}%)`;
}
},
// 添加图形组件,用于在半圆中心显示图片
graphic: [
{
type: 'image',
id: 'alarmImage',
// 图片位置与半圆中心一致
left: 'center',
top: '50%',
// 图片大小
width: 60,
height: 60,
// 图片路径
style: {
image: '/src/assets/demo/baojing.png',
// 保持图片原始比例
width: 60,
height: 60
},
// 图片中心点调整
origin: [30, 30]
}
],
series: [
{
name: '报警类型分布',
type: 'pie',
radius: [100, 200],
center: ['50%', '70%'],
// adjust the start and end angle
startAngle: 180,
endAngle: 360,
data: props.alarmTypeData,
label: {
show: true,
position: 'inner',
formatter: '{d}%',
color: '#fff',
fontSize: 14,
}
}
]
};
chartInstance.setOption(option);
};
</script>
<style scoped lang="scss">
.pie-chart-container {
width: 100%;
height: 400px;
background: #fff;
padding: 20px;
box-sizing: border-box;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.chart-header h3 {
margin: 0;
font-size: 16px;
font-weight: 500;
color: #333;
}
.chart-content {
width: 100%;
height: calc(100% - 60px);
}
:deep(.el-select) {
width: 80px;
}
</style>

View File

@ -0,0 +1,226 @@
<template>
<el-form :inline="true" :model="formInline" style="display: flex; width: 100%;">
<div style="display: flex; gap: 16px;">
<el-form-item label="状态:">
<el-select v-model="formInline.status" placeholder="状态" clearable style="width: 120px;">
<el-option v-for="(item, key) in statusMap" :key="key" :label="item.label" :value="key" />
</el-select>
</el-form-item>
<el-form-item label="级别:">
<el-select v-model="formInline.alarmLevel" placeholder="级别" clearable style="width: 120px;">
<el-option v-for="(item, key) in alarmLevelMap" :key="key" :label="item.label" :value="key" />
</el-select>
</el-form-item>
<el-form-item label="类别:">
<el-select v-model="formInline.alarmType" placeholder="类别" clearable style="width: 120px;">
<el-option v-for="item in alarmTypeList" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="default" @click="clearFilters">清除筛选</el-button>
</el-form-item>
</div>
<div style="margin-left: auto;">
<el-form-item label="排序:">
<el-select v-model="formInline.region" placeholder="排序" clearable style="width: 120px;">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
</div>
</el-form>
<el-table
v-loading="loading"
:data="listData"
border
style="width: 100%"
>
<el-table-column label="报警时间" align="center" prop="time" />
<el-table-column label="设备名称" align="center" prop="toolname" />
<el-table-column label="报警类型" align="center" prop="alarmType" />
<el-table-column label="报警级别" align="center" prop="alarmLevel">
<template #default="scope">
<el-tag :type="alarmLevelMap[scope.row.alarmLevel].type">{{
alarmLevelMap[scope.row.alarmLevel].label }}</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<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" prop="manager" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
<template #default="scope">
<div style="display: flex;align-items: center;">
<el-button link type="primary" :icon="View">查看</el-button>
<div v-if="scope.row.status == 1"
style="color: #43CF7C;margin-left: 5px; display: flex; align-items: center;">
<el-icon style="margin-right: 4px; font-size: 14px;">
<CircleCheckFilled />
</el-icon>
<span style="font-size: 13px;">已处理</span>
</div>
<el-button link type="success" v-else :icon="Opportunity">处理</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper">
<div class="pagination-info">
显示第 {{ (currentPage - 1) * pageSize + 1 }} 到第
{{ Math.min(currentPage * pageSize, total) }} {{ total }} 条记录
</div>
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[5, 10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
background
/>
</div>
</template>
<script setup>
import { View, CircleCheckFilled, Opportunity } from '@element-plus/icons-vue'
import { ref, computed, reactive } from 'vue'
import { ElMessage } from 'element-plus'
// 定义props
const props = defineProps({
alarmRecords: {
type: Array,
default: () => []
}
});
// 分页数据 - 设置为第1页一页显示5条
const currentPage = ref(1);
const pageSize = ref(5);
const total = ref(props.alarmRecords.length); // 使用传入的数据长度
const formInline = reactive({})
const loading = ref(false);
// 使用props中的数据而不是本地虚拟数据
const fullData = props.alarmRecords;
// 状态映射
const statusMap = {
1: {
label: "已处理",
type: "success"
},
2: {
label: "未处理",
type: "danger"
},
3: {
label: "处理中",
type: "primary"
}
}
// 报警级别映射
const alarmLevelMap = {
1: {
label: "紧急",
type: "danger"
},
2: {
label: "重要",
type: "warning"
},
3: {
label: "一般",
type: "info"
}
}
// 报警类型列表
const alarmTypeList = computed(() => {
const types = [...new Set(fullData.map(item => item.alarmType))];
return types;
});
// 计算当前页显示的数据(包含筛选功能)
const listData = computed(() => {
// 先进行筛选
let filteredData = fullData.filter(item => {
// 状态筛选
if (formInline.status !== undefined && formInline.status !== null && formInline.status !== '') {
if (item.status !== parseInt(formInline.status)) {
return false;
}
}
// 级别筛选
if (formInline.alarmLevel !== undefined && formInline.alarmLevel !== null && formInline.alarmLevel !== '') {
if (item.alarmLevel !== parseInt(formInline.alarmLevel)) {
return false;
}
}
// 类别筛选
if (formInline.alarmType !== undefined && formInline.alarmType !== null && formInline.alarmType !== '') {
if (item.alarmType !== formInline.alarmType) {
return false;
}
}
return true;
});
// 更新总条数
total.value = filteredData.length;
// 然后进行分页
const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
return filteredData.slice(startIndex, endIndex);
});
// 清除筛选条件
const clearFilters = () => {
// 清除所有筛选条件
Object.keys(formInline).forEach(key => {
if (key !== 'region') { // 保留排序功能
delete formInline[key];
}
});
// 重置到第一页
currentPage.value = 1;
// 显示提示信息
ElMessage.success('筛选条件已清除');
};
// 分页处理函数
const handleSizeChange = (size) => {
pageSize.value = size;
currentPage.value = 1; // 切换每页显示条数时重置到第一页
// 模拟数据加载效果
loading.value = true;
setTimeout(() => {
loading.value = false;
}, 300);
};
const handleCurrentChange = (current) => {
currentPage.value = current;
// 模拟数据加载效果
loading.value = true;
setTimeout(() => {
loading.value = false;
}, 300);
};
</script>
<style scoped lang="scss">
.pagination-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 10px;
border-top: 1px solid #f5f5f5;
}
.pagination-info {
color: #8c8c8c;
font-size: 14px;
}
</style>

View File

@ -0,0 +1,161 @@
<template>
<div class="model">
<!-- 标题栏 -->
<el-row :gutter="24">
<el-col :span="12">
<TitleComponent title="报警分析与管理" subtitle="实时监控光伏报警信息,高效处理运维任务" />
</el-col>
<!-- 外层col控制整体宽度并右对齐同时作为flex容器 -->
<el-col :span="12" style="display: flex; justify-content: flex-end; align-items: center;">
<el-col :span="7">
<el-input placeholder="搜索报警信息">
<template #prefix>
<el-icon>
<Search />
</el-icon>
</template>
</el-input>
</el-col>
<el-col :span="4">
<el-button type="primary">
导出数据
<el-icon class="el-icon--right">
<UploadFilled />
</el-icon>
</el-button>
</el-col>
</el-col>
</el-row>
<!-- 数据展示-->
<el-row :gutter="24">
<el-col :span="16">
<el-card style="height: 100%;">
<div style="height: 100%;">
<Data :dashboardData="dashboardData" :chartData="chartData" />
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card style="height: 100%;">
<div style="height: 100%;">
<Pie :alarmTypeData="alarmTypeData" />
</div>
</el-card>
</el-col>
</el-row>
<!-- 状态表 -->
<el-row style="margin-top: 20px;">
<el-col :span="24">
<el-card>
<TitleComponent title="报警记录" :fontLevel="2" />
<status style="width: 100%;" :alarmRecords="alarmRecords" />
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import TitleComponent from '@/components/TitleComponent/index.vue';
import Status from '@/views/pvSystem/alarmAnalysis/components/status.vue'
import Pie from '@/views/pvSystem/alarmAnalysis/components/pie.vue';
import Data from '@/views/pvSystem/alarmAnalysis/components/data.vue';
// Mock数据
const dashboardData = ref({
todayAlarmTotal: 25,
unhandledAlarms: 8,
handledAlarms: 16,
avgProcessTime: '42分钟',
updates: {
todayAlarmTotal: { value: '4.2%', type: 'down' },
unhandledAlarms: { value: '5%', type: 'up' },
handledAlarms: { value: '8%', type: 'down' },
avgProcessTime: { value: '10%', type: 'down' }
}
});
const chartData = ref({
alarmCount: [150, 230, 224, 218, 135, 147, 260],
processEfficiency: [85, 88, 90, 87, 89, 91, 89],
dates: ['04/21', '04/22', '04/23', '04/24', '04/25', '04/26', '04/27'],
totals: {
alarmCount: 56,
processEfficiency: '89%'
},
dailyChanges: {
alarmCount: { value: '0.9%', type: 'down' },
processEfficiency: { value: '0.9%', type: 'down' }
}
});
const alarmTypeData = ref([
{ value: 25, name: '逆变器故障', itemStyle: { color: '#F94144' } },
{ value: 20, name: '组串异常', itemStyle: { color: '#F3722C' } },
{ value: 15, name: '通讯中断', itemStyle: { color: '#00BAAD' } },
{ value: 15, name: '汇流箱报警', itemStyle: { color: '#186DF5' } }
]);
const alarmRecords = ref([
{ time: "2025-10-15 08:30:22", toolname: '逆变器 INV-001', alarmType: "逆变器过温保护", alarmLevel: 1, status: 1, manager: "张工程师" },
{ time: "2025-10-15 09:15:45", toolname: '逆变器 INV-001', alarmType: "逆变器过温保护", alarmLevel: 2, status: 1, manager: "李工程师" },
{ time: "2025-10-15 10:42:18", toolname: '逆变器 INV-001', alarmType: "逆变器过温保护", alarmLevel: 3, status: 2, manager: "王工程师" },
{ time: "2025-10-15 11:20:33", toolname: '逆变器 INV-001', alarmType: "逆变器过温保护", alarmLevel: 1, status: 2, manager: "赵工程师" },
{ time: "2025-10-15 13:55:09", toolname: '逆变器 INV-001', alarmType: "逆变器过温保护", alarmLevel: 2, status: 3, manager: "孙工程师" },
{ time: "2025-10-15 14:30:56", toolname: '逆变器 INV-002', alarmType: "通讯中断", alarmLevel: 1, status: 3, manager: "周工程师" },
{ time: "2025-10-15 15:20:12", toolname: '逆变器 INV-002', alarmType: "通讯中断", alarmLevel: 2, status: 3, manager: "吴工程师" },
{ time: "2025-10-15 16:05:47", toolname: '逆变器 INV-002', alarmType: "通讯中断", alarmLevel: 1, status: 3, manager: "郑工程师" },
{ time: "2025-10-15 17:30:29", toolname: '逆变器 INV-002', alarmType: "通讯中断", alarmLevel: 2, status: 3, manager: "钱工程师" },
{ time: "2025-10-15 18:15:54", toolname: '逆变器 INV-002', alarmType: "通讯中断", alarmLevel: 1, status: 3, manager: "冯工程师" },
{ time: "2025-10-16 08:30:22", toolname: '汇流箱 HLB-001', alarmType: "组串异常", alarmLevel: 1, status: 1, manager: "陈工程师" },
{ time: "2025-10-16 09:15:45", toolname: '汇流箱 HLB-001', alarmType: "组串异常", alarmLevel: 2, status: 1, manager: "卫工程师" },
{ time: "2025-10-16 10:42:18", toolname: '汇流箱 HLB-001', alarmType: "组串异常", alarmLevel: 3, status: 2, manager: "蒋工程师" },
{ time: "2025-10-16 11:20:33", toolname: '汇流箱 HLB-001', alarmType: "组串异常", alarmLevel: 1, status: 2, manager: "沈工程师" },
{ time: "2025-10-16 13:55:09", toolname: '汇流箱 HLB-001', alarmType: "组串异常", alarmLevel: 2, status: 3, manager: "韩工程师" },
{ time: "2025-10-16 14:30:56", toolname: '汇流箱 HLB-002', alarmType: "过压保护", alarmLevel: 1, status: 3, manager: "杨工程师" },
{ time: "2025-10-16 15:20:12", toolname: '汇流箱 HLB-002', alarmType: "过压保护", alarmLevel: 2, status: 3, manager: "朱工程师" },
{ time: "2025-10-16 16:05:47", toolname: '汇流箱 HLB-002', alarmType: "过压保护", alarmLevel: 1, status: 3, manager: "秦工程师" },
{ time: "2025-10-16 17:30:29", toolname: '汇流箱 HLB-002', alarmType: "过压保护", alarmLevel: 2, status: 3, manager: "尤工程师" },
{ time: "2025-10-16 18:15:54", toolname: '汇流箱 HLB-002', alarmType: "过压保护", alarmLevel: 1, status: 3, manager: "许工程师" },
{ time: "2025-10-17 08:30:22", toolname: '配电柜 PD-001', alarmType: "电流异常", alarmLevel: 1, status: 1, manager: "何工程师" },
{ time: "2025-10-17 09:15:45", toolname: '配电柜 PD-001', alarmType: "电流异常", alarmLevel: 2, status: 1, manager: "吕工程师" },
{ time: "2025-10-17 10:42:18", toolname: '配电柜 PD-001', alarmType: "电流异常", alarmLevel: 3, status: 2, manager: "施工程师" },
{ time: "2025-10-17 11:20:33", toolname: '配电柜 PD-001', alarmType: "电流异常", alarmLevel: 1, status: 2, manager: "张工程师" },
{ time: "2025-10-17 13:55:09", toolname: '配电柜 PD-001', alarmType: "电流异常", alarmLevel: 2, status: 3, manager: "孔工程师" },
{ time: "2025-10-17 14:30:56", toolname: '配电柜 PD-002', alarmType: "电压异常", alarmLevel: 1, status: 3, manager: "曹工程师" },
{ time: "2025-10-17 15:20:12", toolname: '配电柜 PD-002', alarmType: "电压异常", alarmLevel: 2, status: 3, manager: "严工程师" },
{ time: "2025-10-17 16:05:47", toolname: '配电柜 PD-002', alarmType: "电压异常", alarmLevel: 1, status: 3, manager: "华工程师" },
{ time: "2025-10-17 17:30:29", toolname: '配电柜 PD-002', alarmType: "电压异常", alarmLevel: 2, status: 3, manager: "金工程师" },
{ time: "2025-10-17 18:15:54", toolname: '配电柜 PD-002', alarmType: "电压异常", alarmLevel: 1, status: 3, manager: "魏工程师" },
{ time: "2025-10-18 08:30:22", toolname: '环境监测仪 EM-001', alarmType: "温度过高", alarmLevel: 1, status: 1, manager: "陶工程师" },
{ time: "2025-10-18 09:15:45", toolname: '环境监测仪 EM-001', alarmType: "温度过高", alarmLevel: 2, status: 1, manager: "姜工程师" },
{ time: "2025-10-18 10:42:18", toolname: '环境监测仪 EM-001', alarmType: "温度过高", alarmLevel: 3, status: 2, manager: "戚工程师" },
{ time: "2025-10-18 11:20:33", toolname: '环境监测仪 EM-001', alarmType: "温度过高", alarmLevel: 1, status: 2, manager: "谢工程师" },
{ time: "2025-10-18 13:55:09", toolname: '环境监测仪 EM-001', alarmType: "温度过高", alarmLevel: 2, status: 3, manager: "邹工程师" },
{ time: "2025-10-18 14:30:56", toolname: '环境监测仪 EM-002', alarmType: "湿度异常", alarmLevel: 1, status: 3, manager: "喻工程师" },
{ time: "2025-10-18 15:20:12", toolname: '环境监测仪 EM-002', alarmType: "湿度异常", alarmLevel: 2, status: 3, manager: "柏工程师" },
{ time: "2025-10-18 16:05:47", toolname: '环境监测仪 EM-002', alarmType: "湿度异常", alarmLevel: 1, status: 3, manager: "水工程师" }
]);
// 模拟接口调用
const fetchAlarmData = async () => {
try {
// 模拟网络请求延迟
await new Promise(resolve => setTimeout(resolve, 800));
// 这里可以添加数据处理逻辑
console.log('模拟API调用成功数据已加载');
} catch (error) {
console.error('模拟API调用失败:', error);
}
};
onMounted(() => {
fetchAlarmData();
});
</script>
<style scoped lang="scss">
.model {
padding: 0px 15px;
background-color: rgba(242, 248, 252, 1);
}
</style>

View File

@ -0,0 +1,38 @@
<template>
<div style="display: flex;align-items: center;">
<div class="icon">
<img :src="`/assets/${icon}.png`" alt="图片" />
</div>
<div class="subtitle">
{{ subtitle }}
</div>
</div>
</template>
<script setup>
const { subtitle, icon } = defineProps({
subtitle: {
type: String,
default: ''
},
icon: {
type: String,
default: ''
}
})
</script>
<style scoped>
.subtitle {
font-family: Alibaba-PuHuiTi-Bold;
font-size: 18px;
font-weight: 400;
letter-spacing: 0px;
line-height: 21.6px;
color: rgba(0, 30, 59, 1);
text-align: left;
vertical-align: top;
}
img {
width: 30px;
}
</style>

View File

@ -0,0 +1,226 @@
<template>
<div class="operation-log-container">
<!-- 页面标题 -->
<div class="page-title">
<el-icon class="title-icon">
<DocumentChecked />
</el-icon>
<h1>操作指令记录</h1>
</div>
<!-- 搜索和操作区域 -->
<div class="search-bar">
<el-select v-model="selectedCommandType" placeholder="输入操作指令..." class="search-select" clearable>
<el-option v-for="type in commandTypes" :key="type.value" :label="type.label" :value="type.value" />
</el-select>
<el-button type="primary" class="send-btn">
<el-icon>
<Paperclip />
</el-icon>
发送
</el-button>
<el-button type="primary" :plain="true" class="export-btn">
<el-icon>
<Download />
</el-icon>
导出
</el-button>
</div>
<!-- 表格区域 -->
<el-table :data="tableData" border stripe class="log-table">
<el-table-column type="selection" width="50" />
<el-table-column prop="commandType" label="指令类型" width="120" />
<el-table-column prop="operationContent" label="操作内容" min-width="200" />
<el-table-column prop="device" label="设备" width="100" />
<el-table-column prop="status" label="状态" width="120" align="center">
<template #default="scope">
<el-tag :type="scope.row.status === 'SUCCESS' ? 'success' : 'danger'" effect="light"
class="status-tag">
<el-icon :class="scope.row.status === 'SUCCESS' ? 'success-icon' : 'error-icon'">
<Check v-if="scope.row.status === 'SUCCESS'" />
<Close v-else />
</el-icon>
{{ scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="time" label="时间" width="140" />
</el-table>
<!-- 分页区域 -->
<div class="pagination-container">
<div class="record-info">
显示第1到7条共54条记录
</div>
<el-pagination v-model:current-page="currentPage" :page-size="pageSize" :page-count="totalPages"
layout="prev, pager, next" @current-change="handlePageChange" />
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import {
DocumentChecked,
Paperclip,
Download,
Check,
Close
} from '@element-plus/icons-vue';
// 指令类型选项
const commandTypes = [
{ label: '系统自检', value: 'system_check' },
{ label: '闭合馈线开关', value: 'close_feeder_switch' },
{ label: '闭合保护开关', value: 'close_protection_switch' },
{ label: '断开馈线开关', value: 'open_feeder_switch' },
];
// 选中的指令类型
const selectedCommandType = ref('');
// 表格数据
const tableData = ref([
{
commandType: '系统自检',
operationContent: '系统进入正常供电模式.',
device: '主系统',
status: 'SUCCESS',
time: '09:51:24'
},
{
commandType: '闭合馈线开关',
operationContent: '馈线开关A已闭合负载A.',
device: '主系统',
status: 'SUCCESS',
time: '09:41:20'
},
{
commandType: '闭合保护开关',
operationContent: '闭合母线开关',
device: '主系统',
status: 'ERROR',
time: '09:21:24'
},
{
commandType: '系统自检',
operationContent: '母线电压稳定在110KV',
device: '主系统',
status: 'SUCCESS',
time: '09:11:24'
},
{
commandType: '断开馈线开关',
operationContent: '闭合馈线开关',
device: '主系统',
status: 'SUCCESS',
time: '09:02:24'
},
{
commandType: '断开馈线开关',
operationContent: '闭合馈线开关',
device: '主系统',
status: 'SUCCESS',
time: '09:02:24'
},
{
commandType: '断开馈线开关',
operationContent: '闭合馈线开关',
device: '主系统',
status: 'SUCCESS',
time: '09:02:24'
}
]);
// 分页相关
const currentPage = ref(3);
const pageSize = ref(7);
const totalPages = ref(20);
// 处理页码变化
const handlePageChange = (page) => {
currentPage.value = page;
// 实际应用中这里会根据页码加载对应的数据
console.log(`切换到第${page}`);
};
</script>
<style scoped>
.operation-log-container {
max-width: 1200px;
margin: 20px auto;
padding: 0 20px;
}
.page-title {
display: flex;
align-items: center;
margin-bottom: 20px;
color: #333;
}
.title-icon {
margin-right: 8px;
color: #409eff;
}
.search-bar {
display: flex;
gap: 10px;
margin-bottom: 20px;
align-items: center;
}
.search-select {
flex: 0 1 300px;
}
.send-btn,
.export-btn {
white-space: nowrap;
}
.log-table {
width: 100%;
margin-bottom: 20px;
}
.status-tag {
display: flex;
align-items: center;
gap: 4px;
}
.success-icon {
color: #67c23a;
}
.error-icon {
color: #f56c6c;
}
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
color: #606266;
font-size: 14px;
}
.record-info {
color: #606266;
}
.el-pagination {
--el-pagination-active-color: #409eff;
}
</style>

Some files were not shown because too many files have changed in this diff Show More