You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2020 lines
101 KiB
HTML

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>tool</title>
<script type="text/javascript" src="./three/three.min.js"></script>
<script type="text/javascript" src="./three/OrbitControls.js"></script>
<script type="text/javascript" src="./three/Lut.js"></script>
<script src="./js/vue.global.min.js"></script>
<script src="./js/antd.min.js"></script>
<link rel="stylesheet" href="./css/antd.min.css">
<link rel="stylesheet" href="./css/element-plus.css">
<script src="./js/element-plus.js"></script>
<!-- 引入 Element Plus 中文语言包 -->
<script src="./js/zh-cn.min.js"></script>
<script src="./js/echarts.min.js"></script>
<script src="./js/echarts-gl.min.js"></script>
<link rel="stylesheet" href="./css/common.css">
<script type="text/javascript" src="./js/axios.min.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
padding: 10px;
background-color: #f5f5f5;
}
</style>
</head>
<body>
<div id="app">
<div class="content">
<!-- 树菜单 -->
<div ref="treeMenu" v-if="treeMenuInfo.menuVisible" class="context-menu"
:style="{ left: `${treeMenuInfo.menuX}px`, top: `${treeMenuInfo.menuY}px` }">
<div v-if="treeMenuInfo.data.isRoot" @click="addMonitor(treeMenuInfo.data)">新增监测点</div>
<div @click="editNodeItem(treeMenuInfo.data)">编辑</div>
<a-popconfirm title="确定要删除吗?" ok-text="确定" cancel-text="取消"
@confirm="deleteNodeItem(treeMenuInfo.data)">
<div>删除</div>
</a-popconfirm>
</div>
<div class="tree-content">
<div class="input-box">
<a-input placeholder="名称" v-model:value="searchValue" @change="search">
</a-input>
</div>
<div class="tree-box">
<el-tree ref="treeRef" node-key="key" check-strictly :check-on-click-node="false" :data="treeData"
:props="defaultProps" :load="onLoadData" lazy v-model:checked-keys="checkedKeys1"
:current-node-key="selectedKey" :show-checkbox="typeOfCheckable.includes(activeKey)"
:default-expanded-keys="defaultExpendKeys" @check="checkTree" @node-click="handleNodeClick"
:filter-node-method="filterNode" @node-contextmenu="handleRightClick">
<template #default="{ node, data }">
<div :class="[selectedKey===data.key?'hight-light-node':'','node-content']"
:title="node.label" @click.stop="handleNodeContentClick(data)">
{{ node.label }}
</div>
</template>
</el-tree>
</div>
<div class="bottom-btns">
<a-button type="primary" size="small" @click="isImportMonitorModalShow=true"> 导入监测点 </a-button>
<a-button style="margin-left: 4px;" type="primary" size="small" @click="addSite"> 新增站点 </a-button>
</div>
</div>
<div class="right-content">
<!-- tab切换 -->
<div class="top-box">
<a-tabs v-model:active-key="activeKey" @change="(key)=>activeKey=key">
<a-tab-pane v-for="item in tabsArr" :key="item.key" :tab="item.tab"></a-tab-pane>
</a-tabs>
<div class="top-timer" v-if="timeSelectArr.includes(activeKey)">
<el-date-picker v-model="dateRange" style="width: 260px" class="custom-date-picker"
value-format="YYYY-MM-DD HH:mm:ss" type="datetimerange" range-separator="至"
start-placeholder="开始日期" end-placeholder="结束日期" />
</div>
</div>
<div class="panel-views">
<div v-if="activeKey === 'alarmConfig'" key="alarmConfig" class="sub-content alarm-config">
<!-- 告警配置 -->
<div class="search-bar">
<div class="search-item">
<span>放电类型:</span>
<a-select v-model:value="alarmFilters.pdTypes" :options="pdTypeOps" mode="tags"
placeholder="请选择" style="width: 300px">
</div>
<div class="search-item">
<span>时间:</span>
<el-date-picker v-model="alarmFilters.times" style="width: 360px"
class="custom-date-picker" value-format="YYYY-MM-DD HH:mm:ss" type="datetimerange"
range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" />
</div>
<div class="break"></div>
<div class="search-item">
<span>是否复归:</span>
<a-radio-group :options="cancelOps" v-model:value="alarmFilters.isCancel" />
</div>
<div class="search-item">
<a-button type="primary" size="small" @click="fetchAlarmGridData">查询</a-button>
</div>
<div class="search-item">
<a-button size="small" @click="initAlarmGridData">重置</a-button>
</div>
</div>
<div class="grid-content">
<a-table :columns="columns" :data-source="dataSource" :pagination="pagination"
:loading="configLoading" @change="handleTableChange" :scroll="{ y: 460 }"
:custom-row="alarmRowClick">
<template #name1="{ text }">
{{pdTypeOps.find(item=>item.value==text).label}}
</template>
<template #name2="{ text }">
{{ cancelOps.find(item=>item.value==text).label}}
</template>
</a-table>
</div>
</div>
<div v-if="activeKey === 'historyTrend'" key="historyTrend" class="sub-content history-trend">
<div class="trend-graph-container">
<history-trend-graph v-for="(item,index) in trendGraphData" :key="item.key"
:ref="el => { if (el) trendGraphRefs[index] = el }" :data="item"
@jump-to-prpd="jumpToPRPD" />
</div>
</div>
<div v-if="activeKey === 'prpdAndPrps'" key="prpdAndPrps" class="sub-content total-prpd-panel">
<div>
<history-trend-graph ref="trendRef" :data='trendGraphInfo' used-tab="prpdAndPrps"
@get-event-list="getEventList" />
</div>
<div class="prpd-box">
<!-- 累计PRPD组件 -->
<div class="totle-prpds">
<prpd-and-prps-comp class="total-prpd-item" v-for="(item,index) in totalPrpds"
:key="index" :data="item" :time="prpdAndPrpsTimes"
:is-count-prpd="isTypeOfCountPrpd" />
</div>
<div class="time-sider">
<div class="title">
<span>
事件:
</span>
<el-popover placement="right" :width="320" trigger="click"
:visible="everntFilterVisible">
<template #reference>
<el-button @click="everntFilterVisible=!everntFilterVisible">筛选</el-button>
</template>
<el-form :inline="true" :model="eventFilterForm" label-width="auto"
label-suffix=":" class="demo-form-inline">
<el-form-item label="脉冲数">
<el-input v-model="eventFilterForm.plusCount" placeholder="请输入" />
</el-form-item>
<el-form-item label="幅值">
<el-input v-model="eventFilterForm.maxValue" placeholder="请输入" />
</el-form-item>
<el-form-item label="放电类型">
<el-select v-model="eventFilterForm.pdTypes" multiple collapse-tags
collapse-tags-tooltip style="width: 240px" placeholder="请选择">
<el-option v-for="item in allPdTypes" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onFilterEventList">确定</el-button>
<el-button @click="onResetEventList">重置</el-button>
</el-form-item>
</el-form>
</el-popover>
</div>
<div class="list" v-loading="eventListLoading">
<div :class="['event-item',activeEventTime==item.time?'active-event-item':'']"
v-for="(item,index) in eventList" :key="item.time"
@click="handleEventClick(item)">
<div class="time">
<span class="label">时间:</span>
{{item.time}}
</div>
<div class="pd-type">
<span class="label">放电类型:</span>
{{allPdTypes.find(({value})=>value==item.pdType)?.label}}
</div>
<div class="num">
<span style="margin-right: 12px;">
<span class="label">最大值:</span>
{{item.maxValue}}</span>
<span>
<span class="label">脉冲数:</span>
{{item.plusCount}}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-if="activeKey === 'eventCount'" key="eventCount" class="sub-content">
<!-- 事件统计 -->
<event-count :time="dateRange" :selected-key="selectedKey" :tree-data="fullTreeData" />
</div>
<div v-if="activeKey === 'config'" key="config" class="sub-content config-page">
<!-- 配置页面 -->
<div class="station-config">
<div class="title">站点告警配置</div>
<div class="station">
<span>站点:</span>
<a-select v-model:value="stationName1" size="small" style="width: 200px">
<a-select-option v-for="item in treeData" :key="item.key" :value="item.stationName">
{{ item.stationName }}
</a-select-option>
</a-select>
</div>
<div class="delete-bar">
<span class="delete-time">
<span class="label">清除告警时间段:</span>
<el-date-picker v-model="deleteAlarmTimes" style="width: 360px"
class="custom-date-picker" value-format="YYYY-MM-DD HH:mm:ss"
type="datetimerange" range-separator="至" start-placeholder="开始日期"
end-placeholder="结束日期" />
</span>
<a-popconfirm title="确定删除?" ok-text="是" cancel-text="否" @confirm="deleteAlarmConfirm">
<a href="#">删除</a>
</a-popconfirm>
</div>
<el-upload action="#" :http-request="alarmCustomUpload" :show-file-list="false" :limit="1"
accept=".csv,.xlsx,.xls" :before-upload="alarmBeforeUpload">
<a-button type="primary" size="small">导入告警</el-button>
</el-upload>
</div>
<div class="path-config">
<div class="title">路径配置</div>
<div class="path-item" v-for="item in allPathCongfigs" :key="item.key">
<div class="name">{{item.label}} :</div>
<div class="row">
<el-radio-group v-if="item.key==='ALARM_TYPE'" v-model="item.value">
<el-radio value="3">国网</el-radio>
<el-radio value="2">南网</el-radio>
</el-radio-group>
<el-input v-else v-model="item.value" style="width: 560px" placeholder="请输入路径" />
</div>
<a-button size="small" type="primary" style="margin-right: 4px;"
@click="savePathConfig(item)">保存</a-button>
<a-button v-if="item.key==='MERGIN_ROOT_PATH'" size="small" type="primary"
style="margin-right: 4px;" :loading="isCurrentInit"
@click="initCatalogue">初始化</a-button>
<a-button size="small" @click="resetPathConfig(item.key)">重置</a-button>
<p v-if="item.key==='MERGIN_ROOT_PATH'&&isCurrentInit" class="init-warning-tips">
当前初始化进度:{{initTips}}
</p>
</div>
</div>
</div>
</div>
</div>
<!-- 导入监测点弹框 -->
<a-modal width="550px" :destroyOnClose="true" :visible="isImportMonitorModalShow" title="导入监测点"
@ok="onImportMonitor" @cancel="isImportMonitorModalShow=false"
:confirmLoading="importMonitorModalloading"></a-modal>
<!-- 站点弹框 -->
<a-modal width="550px" :destroyOnClose="true" :visible="isSiteModalShow"
:title="isAddTypeOfSiteModal?'新增站点':'编辑站点'" @cancel="isSiteModalShow=false">
<a-form layout=" vertical" ref="siteModalRef" :model="siteModalForm">
<a-form-item label="站点名称" name="stationName" :rules="[
{
required: true,
message: '请输入站点名称',
},
{
max: 20,
message: '最多输入20个字符',
},
]">
<a-input v-model:value="siteModalForm.stationName" placeholder="请输入" />
</a-form-item>
<a-form-item label="背景图路径" name="img" :rules="[
{
required: true,
message: '请输入背景图路径',
},
]">
<a-input v-model:value="siteModalForm.img" placeholder="请输入" />
</a-form-item>
</a-form>
<template #footer>
<a-button @click="isSiteModalShow=false">关闭</a-button>
<a-button type="primary" @click="onSiteModalSubmit" :loading="siteModalloading">确定</a-button>
</template>
</a-modal>
<!-- 监测点弹框 -->
<a-modal width="550px" :destroyOnClose="true" :visible="isMonitorModalShow"
:title="isAddTypeOfMonitorModal?'新增监测点':'编辑监测点'" @cancel="isMonitorModalShow=false">
<a-form layout=" vertical" ref="monitorModalRef" :model="monitorModalForm">
<a-form-item label="站点名称" name="stationName" :rules="[
{
required: true,
message: '请输入监测点名称',
},
]" disabled>
<a-input v-model:value="monitorModalForm.stationName" disabled />
</a-form-item>
<a-form-item label="监测点Key" name="monitorKey" :rules="[
{
required: true,
message: '请输入监测点Key',
},
{
max: 20,
message: '最多输入20个字符',
},
]">
<a-input v-model:value="monitorModalForm.monitorKey" placeholder="请输入"
:disabled="!isAddTypeOfMonitorModal" />
</a-form-item>
<a-form-item label="监测点名称" name="name" :rules="[
{
required: true,
message: '请输入监测点名称',
},
{
max: 20,
message: '最多输入20个字符',
},
]">
<a-input v-model:value="monitorModalForm.name" placeholder="请输入" />
</a-form-item>
</a-form>
<template #footer>
<a-button @click="isMonitorModalShow=false">关闭</a-button>
<a-button type="primary" @click="onMonitorModalSubmit" :loading="monitorModalLoading">确定</a-button>
</template>
</a-modal>
</div>
<script>
const { createApp, ref, onMounted, unref, watch, reactive, computed, nextTick, onUnmounted, components, provide, inject } = Vue;
const { message } = antd;
axios.defaults.baseURL = 'http://192.168.1.198:9501'; // 临时服务地址
axios.defaults.timeout = 10000;
const HISTORY_TREND_LEGEND = [
{ id: 'avg', name: '平均值' },
{ id: 'maxValue', name: '最大值' },
{ id: 'plusAvg', name: '脉冲平均值' },
{ id: 'plusCount', name: '脉冲次数' },
]
const findParentByKey = (tree, targetKey) => {
for (const parent of tree) {
// 检查子节点
if (parent?.children?.length) {
for (const child of parent.children) {
if (child.key === targetKey) {
return parent.stationName; // 返回父节点
}
}
}
}
return null;
}
const formatTreeData = (node) => {
return {
...node.data,
children: node.childNodes?.map(child => formatTreeData(child)) || []
};
};
// 生成正弦波形
const generateSineWaveData = () => {
const dataPoints = [];
// 遍历0到360度每1度取一个点
for (let angle = 0; angle <= 360; angle++) {
// 将角度转换为弧度
const radian = angle * Math.PI / 180;
// 计算增益值
const gain = 40 * Math.sin(radian) - 40;
// 将 [相位, 增益] 对添加到数组中
// 使用 toFixed(2) 来保留两位小数,避免浮点数精度问题,并将其转换为数字类型
dataPoints.push([angle, parseFloat(gain.toFixed(2))]);
}
return dataPoints;
}
// 处理数据,生成 ECharts 所需的三维坐标格式
const deal = (arr, dbm) => {
for (let i = 0; i < 6400; i++) {
let j = parseInt(i / 128) + 1;
arr[i] = [j, parseInt((i - (j - 1) * 128 + 1) * 2.8125), parseInt(dbm[i]) + 80];
}
return arr;
}
const findMonitorNameByKey = (arr, key) => {
let result = [];
arr.forEach(item => {
if (item?.children?.length) {
result = [...result, ...item.children]
}
});
return result.find(item => item.key === key)?.name
}
// 获取24h前时间
const getPrevious24HourMark = (timeStr) => {
const date = new Date(timeStr);
date.setHours(date.getHours() - 24);
// 注意:分钟、秒、毫秒会保留原始值
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 历史趋势图组件
const trendGraphRefs = ref([]);
watch(trendGraphRefs, (arr) => {
// 此处定时器解决异步
setTimeout(() => {
arr.forEach((el) => {
el.getChart().group = 'trend-group';
});
}, 300);
}, {
immediate: true,
deep: true,
},);
// 历史趋势图谱
const historyTrendGraph = {
template: `<div class='graph-container'>
<div class="title">
监测点:{{ data?.label||'' }}
</div>
<div ref='graphRef' class="line-graph" @dblclick='onDblclickGraph'></div>
</div>`,
data() {
return {
resizeObserver: null
}
},
props: {
data: {
type: Object,
default: () => { }
},
// 使用的场景
usedTab: {
type: String,
default: 'historyTrend'
}
},
// 定义常量不定义在data减少性能开销
created() {
this.chart = null
},
watch: {
data: {
async handler(val, oldVal) {
if (
val.key === oldVal?.key &&
val.label === oldVal?.label &&
val.startTime === oldVal?.startTime &&
val.endTime === oldVal?.endTime
) {
return
}
const { startTime, endTime, key } = val
if (!key || !startTime) return
this.chart?.showLoading()
const params = {
current: 1,
startTime,
endTime,
monitorKey: key,
pageSize: 99999
}
const { data: { result: { result: { result } } } } = await axios.get(`/ldpdtools/trendData/listByParam`, {
params,
}).finally(() => {
this.chart?.hideLoading()
})
this.initTrendGraph(result || [])
},
deep: true,
immediate: true
}
},
async mounted() {
this.resizeObserver = new ResizeObserver(() => {
this.chart?.resize();
});
this.resizeObserver.observe(this.$refs.graphRef);
},
methods: {
initTrendGraph(data) {
// 图数据
const series = HISTORY_TREND_LEGEND.map((item, index) => ({
name: item.name,
type: 'line',
yAxisIndex: index,
data: data.map(el => el[item.id]),
smooth: true,
showSymbol: false,
lineStyle: {
width: 2,
},
}));
const legend = {
data: HISTORY_TREND_LEGEND.map(s => s.name), // 图例数据
top: 0,
};
const xAxis = {
type: 'category',
data: data.map(el => el.time.replace(' ', '\n')),
axisLine: {
onZero: false, // 禁止轴线对齐到0刻度强制固定在底部
},
name: '时间',
axisLabel: {
fontSize: 10,
},
};
const yAxis = HISTORY_TREND_LEGEND.map((item, index) => {
return {
type: 'value',
name: item.name,
nameTextStyle: {
color: '#666',
fontSize: 10,
padding: [0, 0, 0, 30], // 上、右、下、左左间距30px
},
triggerEvent: true, // 关键配置
axisLabel: {
formatter: '{value}',
fontSize: 10,
},
position: 'right',
offset: (index + 1) * 48, // 控制 Y 轴横向间距
splitLine: {
show: false, // 仅第一个 Y 轴显示网格线
},
splitNumber: 3, // Y轴仅显示3个刻度线
};
});
this.chart = echarts.init(this.$refs.graphRef);
const option = {
// 设置响应式
grid: {
top: 30,
right: 230,
bottom: 30,
left: 30
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
legend,
xAxis,
yAxis,
series,
dataZoom: [
{
type: 'slider', // 滑动条型数据区域缩放
xAxisIndex: [0],
start: 0,
end: 100,
top: 20, // 距离底部的距离
height: 10, // 滑动条高度
throttle: 500,
showDetail: false // 不显示详细的起止数值,根据需求设置
}
]
}
// 3. 设置配置项并渲染图表
this.chart.setOption(option);
this.addEchartsEvents();
if (this.usedTab === 'prpdAndPrps') {
this.handleDataZoom({ start: 0, end: 100 })
}
},
addEchartsEvents() {
if (!this.chart || this.usedTab !== 'prpdAndPrps') return;
this.chart.on('datazoom', (params) => {
this.handleDataZoom(params)
});
},
// 外界触发筛选逻辑
emitFilterEvent() {
const { start, end } = this.chart.getOption()?.dataZoom?.[0] || {}
this.handleDataZoom({ start, end })
},
// 获取时间段
handleDataZoom(params) {
const startPercent = params.start;
const endPercent = params.end;
if (startPercent === undefined || endPercent === undefined) {
return;
}
// 获取当前的 ECharts option特别是 xAxis 的 data
const currentOption = this.chart.getOption();
const xAxisData = currentOption.xAxis[0].data; // 假设你的X轴是第一个 (index 0)
if (!xAxisData || xAxisData.length === 0) {
this.$emit('getEventList', { startTime: null, endTime: null })
return message.warning(`当前监测点历史趋势为空,请重新选择监测点!`);
}
const dataLength = xAxisData.length;
const startIndex = Math.floor(dataLength * (startPercent / 100));
const endIndex = Math.min(Math.ceil(dataLength * (endPercent / 100)) - 1, dataLength - 1); // -1 是因为索引从0开始
const startCategory = xAxisData[startIndex];
const endCategory = xAxisData[endIndex];
const startTime = startCategory ? String(startCategory).replace(/\n/g, ' ') : '';
const endTime = endCategory ? String(endCategory).replace(/\n/g, ' ') : '';
if (!startTime || !endTime) {
return
}
this.$emit('getEventList', { startTime, endTime })
},
// 双击跳转
onDblclickGraph() {
if (this.usedTab !== 'historyTrend') return
// 是否空数据
const isEmptyData = this.chart.getOption().series?.[0]?.data?.length === 0
if (isEmptyData) {
return message.error(`当前数据为空!`);
}
this.$emit('jumpToPrpd', this.data.key)
},
getChart() {
return this.chart;
}
},
}
// prpd/prps组件
const prpdAndPrpsComp = {
template: `<div>
<div class="total-prpd-title" :style="{color:!!data.monitorName?'red':''}">
{{title}}</div>
<div v-show='currentKey==="PRPD"' ref="prpdBoxRef" class="prpd"></div>
<div v-show='currentKey==="PRPS"' ref="prpsBoxRef" class="prps"></div>
<div v-if='!isCountPrpd' class='switcher'>
<span v-for='item in switchBox' :key='item' @click='currentKey=item'
:class='[currentKey===item?"active-span":""]'>
<span v-if='item==="PRPS"'>/</span>
{{item}}</span>
</div>
</div>`,
props: ['data', 'time', 'isCountPrpd'],
data() {
return {
currentKey: 'PRPD',
switchBox: ['PRPD', 'PRPS',],
timer: 0,
len: 0,
}
},
computed: {
// prpd所需参数
mixPRPDParams() {
return {
...this.data,
isCountPrpd: this.isCountPrpd,
time: this.time
}
},
title() {
return this.data.monitorName ? this.data.monitorName + (this.isCountPrpd ? '(累计PRPD)' : '') : '暂无监测点'
}
},
watch: {
mixPRPDParams: {
handler(newVal) {
if (newVal.monitorKey && newVal.time[0]) {
// 新增操作
this.$nextTick(() => {
if (newVal.isCountPrpd) {
// 加载累计prpd
this.currentKey = 'PRPD'
this.fetchPRPDData(newVal)
} else {
// 加载prpd / prps
this.fetchPRPDAndPrpsData(newVal)
}
})
}
if (!newVal.monitorKey || !newVal.time[0]) {
// 删除操作
this.myChart.setOption({
series: [{
name: 'PRPD数据',
data: []
}]
}, { notMerge: false });
}
},
deep: true
},
isCountPrpd: {
handler(bool) {
if (bool) {
this.currentKey = 'PRPD'
}
},
immediate: true,
}
},
created() {
this.myChart = null
this.resizeObserver = null
this.myPRPSChart = null // prps容器
this.myPRPSOps = null
this.resize3dObserver = null
},
mounted() {
this.initPrpd()
this.initPrps()
},
methods: {
initPrpd() {
const chartDom = this.$refs.prpdBoxRef;
this.myChart = echarts.init(chartDom);
const option = {
backgroundColor: '#ffffff', // 设置背景色为深色,与图片相似
tooltip: {
show: false
},
grid: {
left: '8%',
right: '10%',
top: '8%',
bottom: '10%',
containLabel: true
},
xAxis: {
type: 'value',
name: '相位',
nameTextStyle: {
color: '#000000' // <-- 将轴名称“相位”的颜色设置为黑色
},
nameLocation: 'middle',
nameGap: 30, // 调整名称与轴线的距离
min: 0,
max: 360,
interval: 90, // 固定刻度为0, 90, 180, 270, 360
axisLabel: {
color: '#000' // X轴刻度文字颜色
},
axisLine: {
lineStyle: {
color: '#ccc' // X轴线颜色
}
},
splitLine: {
show: true,
lineStyle: {
color: '#ccc', // X轴网格线颜色
type: 'solid'
}
}
},
yAxis: {
type: 'value',
name: '幅值',
nameLocation: 'middle',
nameTextStyle: {
color: '#000000' // <-- 将轴名称“相位”的颜色设置为黑色
},
nameRotate: 90, // Y轴名称旋转
nameGap: 30, // 调整名称与轴线的距离
min: -80,
max: 0,
interval: 20, // 固定刻度为 -80, -60, -40, -20, 0
axisLabel: {
color: '#000', // Y轴刻度文字颜色
},
axisLine: {
lineStyle: {
color: '#ccc' // Y轴线颜色
}
},
splitLine: {
show: true,
lineStyle: {
color: '#ccc', // Y轴网格线颜色
type: 'solid'
}
}
},
// 颜色区分
visualMap: {
dimension: 2,
show: false,
inRange: {
color: ['#007ACC', '#FF4500'] // 例如:从浅蓝色到深蓝色
},
},
series: [
{
name: '正弦波形', // 系列名称
type: 'line', // 图表类型为折线图
smooth: true, // 开启平滑曲线
showSymbol: false, // 不显示数据点符号
lineStyle: {
width: 2 // 调整线条宽度
},
data: generateSineWaveData() // 调用函数生成数据
},
{
name: 'PRPD数据',
type: 'scatter', // 使用折线图来模拟曲线
symbolSize: 1,
data: [],
},
]
};
this.myChart.setOption(option);
this.resizeObserver = new ResizeObserver(() => {
this.myChart?.resize();
});
this.resizeObserver.observe(chartDom);
},
// 请求PRPD数据
async fetchPRPDData(data) {
this.myChart.showLoading()
const params = {
startTime: data.time[0],
endTime: data.time[1],
monitorKey: data.monitorKey,
}
const { data: { result: res } } = await axios.get(`/ldpdtools/eventData/getPrpdByParam`, {
params,
}).catch(err => {
this.myChart.setOption({
series: [{
name: 'PRPD数据',
data: [],
symbolSize: 1,
}],
}, { notMerge: false });
}).finally(() => {
this.myChart.hideLoading()
})
const result = res || []
const countArr = result.map(item => item[2])
const minCount = Math.min(...countArr)
const maxCount = Math.max(...countArr)
this.myChart.setOption({
series: [{
name: 'PRPD数据',
data: result,
symbolSize: 1,
}],
visualMap: {
min: minCount,
max: maxCount
}
}, { notMerge: false });
},
// 请求prpd/prps的float数据
async fetchPRPDAndPrpsData({ monitorKey, time }) {
this.myChart.showLoading()
const params = {
monitorKey,
timeStr: time[0],
}
const { data: { result: res } } = await axios.get(`/ldpdtools/eventData/getInfoByTime`, {
params,
}).finally(() => {
this.myChart.hideLoading()
})
this.draw2dChart(res)
this.draw3dChart(res)
},
// prpd数据处理
draw2dChart(params) {
const res = params || []
let dbm = res.slice(6400, 12800);
if (dbm.length == 0) {
dbm = res.slice(0, 6400)
} else if (dbm.length < 6400) {
dbm = res.slice(6400, 6400 + dbm.length).concat(res.slice(0, 6400 - dbm.length))
}
let arr = [];
deal(arr, dbm)
const data = this.datting(arr)
const result = data.map(item => [item[1], item[2] - 80, item[0]])
const countArr = result.map(item => item[2])
const minCount = Math.min(...countArr)
const maxCount = Math.max(...countArr)
this.myChart.setOption({
series: [{
name: 'PRPD数据',
data: result,
symbolSize: 2.5,
}],
visualMap: {
min: minCount,
max: maxCount
}
}, { notMerge: false });
},
datting(arr) {
let dataArr = [];
let newArr = [];
for (let i = 1; i <= 128; i++) {
let obj = []
for (let j = 0; j < 50; j++) {
obj.push(arr[i + j * 128 - 1])
}
newArr.push(obj);
};
let lay1 = []
for (let i = 0; i < newArr.length; i++) {
let lay2 = [];
for (let k = 0; k < 80; k++) {
let lay3 = [];
for (let j = 0; j < newArr[i].length; j++) {
if (newArr[i][j][2] == k) {
lay3.push(newArr[i][j])
}
}
if (lay3 != '') {
lay2.push(lay3)
}
}
lay1.push(lay2)
// console.log(lay1)
}
for (let i = 0; i < lay1.length; i++) {
for (let j = 0; j < lay1[i].length; j++) {
for (let k = 0; k < lay1[i][j].length; k++) {
for (let m = 0; m < lay1[i][j][k].length; m++) {
lay1[i][j][k][0] = lay1[i][j].length;
}
}
dataArr.push(lay1[i][j][0]);
}
}
// console.log(dataArr)
return dataArr;
},
// 初始化prps
initPrps() {
const dom3d = this.$refs.prpsBoxRef
this.myPRPSChart = echarts.init(dom3d);
// 初始化一次不变的 ECharts 配置
this.myPRPSOps = {
title: {
text: 'PRPS',
left: 'center',
top: 10,
textStyle: {
fontSize: 14,
color: '#111'
}
},
animation: false,
tooltip: {
show: false,
},
toolbox: {
show: false,
},
visualMap: {
min: -20,
max: 80,
show: false,
itemWidth: 5,
orient: "vertical",
inRange: {
color: ['transparent', '#00ee00', '#eeee00', '#ee0000', '#4e0211']
},
formatter: (value) => {
return parseInt(value - 80);
}
},
xAxis3D: {
type: 'value',
min: 0,
max: 50,
splitNumber: 5,
name: '周期',
nameGap: 24,
axisLine: {
lineStyle: {
color: '#000'
}
},
axisLabel: {
color: '#000',
fontSize: 14,
formatter: (value, index) => {
value = 50 - value
if (value >= 0) {
return value;
}
}
},
},
yAxis3D: {
type: 'value',
min: 0,
max: 360,
splitNumber: 4,
interval: 90,
name: '相位',
nameGap: 24,
splitArea: {
interval: 4,
},
axisLine: {
lineStyle: {
color: '#000'
}
},
axisLabel: {
color: '#000',
fontSize: 14,
},
},
zAxis3D: {
type: 'value',
min: 0,
max: 80,
scale: true,
splitNumber: 4,
name: '幅值',
nameGap: 24,
axisLine: {
lineStyle: {
color: '#000'
}
},
axisLabel: {
color: '#000',
fontSize: 14,
formatter: (value, index) => {
if (value >= 0) {
return value - 80;
}
}
},
},
grid3D: {
boxHeight: 100,
boxWidth: 120,
boxDepth: 100,
axisLine: {
lineStyle: {
color: '#fff',
opacity: 0.1
}
},
axisPointer: {
show: false,
lineStyle: {
color: '#fff'
},
},
viewControl: {
distance: 260,
minDistance: 40,
maxDistance: 400,
rotateSensitivity: [1, 1],
zoomSensitivity: 0,
panSensitivity: 0,
panMouseButton: "middle",
rotateMouseButton: "left",
orthographicSize: 150,
maxOrthographicSize: 400,
minOrthographicSize: 20,
center: [0, -10, 0],
minBeta: 30,
maxBeta: 180,
minAlpha: -90,
maxAlpha: 90,
projection: "perspective",
autoRotateDirection: "cw",
autoRotateAfterStill: 3,
damping: 0.8
},
light: {
main: { intensity: 1.2 },
ambient: { intensity: 0.3 }
}
},
roam: false,
series: [{
type: 'bar3D',
data: [],
shading: 'color',
label: {
show: false,
},
itemStyle: {
opacity: 0.8,
},
silent: true,
emphasis: {
label: { show: false },
}
}]
};
this.myPRPSChart.setOption(this.myPRPSOps);
this.resize3dObserver = new ResizeObserver(() => {
this.myPRPSChart?.resize();
});
this.resize3dObserver.observe(dom3d);
},
// prps数据处理
draw3dChart(data) {
let databox = []
for (let i = 0; i < 6400; i++) {
databox[i] = -128;
}
const fullData = databox.concat(data); // 直接使用导入的 JSON 数
this.dispose([...fullData]); // 启动动画
},
// 动画循环逻辑
dispose(data) {
clearTimeout(this.timer); // 清除上一个定时器
this.drawEcharts3d(data); // 绘制图表
let em = [];
em = data.splice(0, 1280); // 移除前1000个元素
this.len++; // 计数器加一
if (this.len > 5) { // 如果超过5次循环将移除的元素重新加回末尾
data = data.concat(em);
}
// 设置下一个定时器
this.timer = setTimeout(() => {
this.dispose(data); // 递归调用自身
}, 200);
},
drawEcharts3d(obj) {
if (!this.myPRPSChart) return; // 确保图表实例已存在
let arr = [];
deal(arr, obj); // 处理数据
// 只更新 series 的 data 部分
this.myPRPSChart.setOption({
series: [{
data: arr.map(function (item) {
return {
value: [item[0], item[1], item[2]]
}
})
}]
});
},
},
}
// 事件统计组件
const eventCount = {
components: {
historyTrendGraph,
},
template: `<div class='event-count-box'>
<history-trend-graph :data='graphInfo' used-tab="eventCount"/>
<div class="pd-type-grid">
<el-table :data="pdTypeData" border>
<el-table-column prop="typeCn" label="局放类型"/>
<el-table-column prop="max" label="最大值" />
<el-table-column prop="avg" label="平均值" />
<el-table-column prop="count" label="事件数" />
<el-table-column prop="plusCount" label="脉冲数" />
</el-table>
</div>
</div>`,
data() {
return {
pdTypeData: []
}
},
props: ['time', 'selectedKey', 'treeData'],
inject: ['provideAllPdTypes'],
computed: {
graphInfo() {
return {
key: this.selectedKey,
label: findMonitorNameByKey(this.treeData, this.selectedKey),
startTime: this.time[0],
endTime: this.time[1],
};
}
},
watch: {
graphInfo: {
handler(value) {
const { startTime, endTime, key } = value
if (!key || !startTime) {
return
}
this.feachGrid()
},
deep: true,
immediate: true
}
},
methods: {
// 获取表格数据
async feachGrid() {
this.loading = true
const params = {
startTime: this.time[0],
endTime: this.time[1],
monitorKey: this.selectedKey,
}
const { data: { result } } = await axios.get(`/ldpdtools/eventData/getEventStatistics`, {
params,
})
const res = result || {}
const data = [] // 展示的数据
Object.keys(res).forEach(key => {
const type = unref(this.provideAllPdTypes).find(el => el.value == key)
data.push({
typeCn: type.label,
typeEn: type.value,
...res[key]
})
})
this.pdTypeData = data || []
},
}
}
createApp({
setup() {
const typeOfCheckable = ['historyTrend', 'prpdAndPrps']
const timeSelectArr = ['historyTrend', 'prpdAndPrps', 'eventCount'] // 含有时间选择的组件
const treeData = ref([])
const fullTreeData = ref([]) // 懒加载后的完整菜单数据
const checkedKeys1 = ref([]); // 勾选
const selectedKey = ref('') // 选中
const defaultExpendKeys = ref([]); // 默认展开的节点
const treeMenuInfo = reactive({
menuVisible: false,
data: null,
menuX: 0,
menuY: 0
})
const treeMenu = ref()
const handleClickOutside = (e) => {
if (treeMenuInfo.menuVisible && !treeMenu.value?.contains(e.target)) {
treeMenuInfo.menuVisible = false;
}
};
const searchValue = ref('');
const treeRef = ref()
const activeKey = ref('alarmConfig')
const tabsArr = [
{ key: 'alarmConfig', tab: '告警列表' },
{ key: 'historyTrend', tab: '历史趋势' },
{ key: 'prpdAndPrps', tab: 'PRPD/PRPS' },
{ key: 'eventCount', tab: '事件统计' },
{ key: 'config', tab: '配置' }
]
const isImportMonitorModalShow = ref(false)
const importMonitorModalloading = ref(false)
const siteModalloading = ref(false)
const isSiteModalShow = ref(false)
const siteModalRef = ref()
const siteHistoryData = ref() // 站点历史数据
// 站点信息
const siteModalForm = reactive({
stationName: '',
img: ''
})
const isAddTypeOfSiteModal = ref(true) // true:新增站点 false:修改站点
const isMonitorModalShow = ref(false) // 监测点弹框
const isAddTypeOfMonitorModal = ref(true) // true:新增监测点 false:修改监测点
const monitorModalLoading = ref(false)
const monitorModalRef = ref()
const monitorModalForm = reactive({
stationName: '', // 站点名称
name: '',// 监测点名称
monitorKey: '' // 监测点key
})
// 查询树
const search = () => {
treeRef.value.filter(unref(searchValue)); // 触发过滤
};
const filterNode = (value, data) => {
if (!value) return true;
return data.name.includes(value); // 根据label匹配
};
const handleRightClick = (event, data) => {
event.preventDefault();
treeMenuInfo.menuVisible = true
treeMenuInfo.menuX = event.clientX - 8
treeMenuInfo.menuY = event.clientY - 12
treeMenuInfo.data = data
}
const checkTree = (checkedNode, { checkedKeys: currentKeys }) => {
if (currentKeys.length > 6) {
message.warning('最多只能勾选6个节点');
// 阻止勾选:回退到前一次的状态
checkedKeys1.value = checkedKeys1.value.slice(0, 6);
treeRef.value.setCheckedKeys(checkedKeys1.value);
} else {
if (unref(activeKey) === 'prpdAndPrps') {
// 处理累计prpd逻辑
handleCountPRPDSelect(checkedNode)
}
checkedKeys1.value = currentKeys
}
}
const handleNodeContentClick = (node) => {
if (node.isRoot) {
// 单击父节点
return
}
selectedKey.value = node.key
}
const defaultProps = {
children: 'children',
label: 'name',
isLeaf: 'leaf',
class: (node) => node.isRoot ? 'is-root' : ''
}
const onLoadData = (node, resolve) => {
if (node.level !== 1) {
resolve([]);
return
}
const stationName = node?.label ?? ''
axios.get(`/ldpdtools/monitor/monitorList?stationName=${stationName}`).then(({ data: { code, result } }) => {
const data = (result || []).map(item => {
return {
title: item.name,
key: item.monitorKey,
isRoot: false,
disabled: false,
leaf: true,
...item
}
})
resolve(data)
fullTreeData.value = treeRef.value?.store?.root.childNodes.map(node => formatTreeData(node));
})
}
// 初始化树
const initTreeData = async () => {
const { data: { code, result } } = await axios.get('/ldpdtools/monitor/getStations');
if (code !== 200) {
return
}
treeData.value = result.map((item, index) => {
return {
key: index,
title: item.stationName,
isRoot: true,
disabled: true,
leaf: false,
...item
}
})
// 初始化默认展开第一个父节点
if (unref(treeData)?.length) {
const firstParentNodeData = unref(treeData)[0].key;
defaultExpendKeys.value = [firstParentNodeData]
}
fullTreeData.value = treeRef.value?.store?.root.childNodes.map(node => formatTreeData(node));
}
// 编辑节点
const editNodeItem = (data) => {
const { isRoot, stationName, img, monitorKey, name } = data
if (isRoot) {
// 编辑站点
isSiteModalShow.value = true
isAddTypeOfSiteModal.value = false
siteModalForm.stationName = stationName
siteModalForm.img = img
siteHistoryData.value = JSON.parse(JSON.stringify({ stationName, img }))
} else {
// 编辑监测点
isMonitorModalShow.value = true
isAddTypeOfMonitorModal.value = false
monitorModalForm.stationName = stationName
monitorModalForm.name = name
monitorModalForm.monitorKey = monitorKey
}
}
// 删除节点
const deleteNodeItem = async (data) => {
const { isRoot, stationName, monitorKey } = data
let resultCode = 200
if (isRoot) {
// 删除站点
const { data: { code } } = await axios.delete(`/ldpdtools/monitor/deleteStation?stationName=${stationName}`);
resultCode = code
} else {
// 删除检测点
const { data: { code } } = await axios.delete(`/ldpdtools/monitor/deleteMonitor?stationName=${stationName}&monitorKey=${monitorKey}`);
resultCode = code
}
if (resultCode === 200) {
message.success(`删除${isRoot ? '站点' : '监测点'}成功`);
initTreeData()
} else {
message.error(`删除${isRoot ? '站点' : '监测点'}失败`);
}
}
// 新增监测点
const addMonitor = (data) => {
const { stationName } = data
monitorModalForm.stationName = stationName
isMonitorModalShow.value = true
}
// 导入监测点
const onImportMonitor = () => {
}
const addSite = () => {
isSiteModalShow.value = true
}
// 新增、编辑站点
const onSiteModalSubmit = async () => {
await siteModalRef.value.validate()
siteModalloading.value = true
const { img, stationName } = unref(siteModalForm)
let resultCode = 200
if (unref(isAddTypeOfSiteModal)) {
// 新增
const { data: { code } } = await axios.put(`/ldpdtools/monitor/saveStateion?img=${img}&stationName=${stationName}`);
resultCode = code
} else {
// 编辑
const { data: { code } } = await axios.put(`/ldpdtools/monitor/editStateion?img=${img}&newStationName=${stationName}&stationName=${unref(siteHistoryData).stationName}`);
resultCode = code
}
siteModalloading.value = false
if (resultCode === 200) {
message.success(`${unref(isAddTypeOfSiteModal) ? '新增' : '编辑'}站点成功`);
isSiteModalShow.value = false
initTreeData()
} else {
message.error(`${unref(isAddTypeOfSiteModal) ? '新增' : '编辑'}站点失败`);
}
}
// 新增、编辑监测点
const onMonitorModalSubmit = async () => {
await monitorModalRef.value.validate()
monitorModalLoading.value = true
const { stationName, name, monitorKey } = unref(monitorModalForm)
let resultCode = 200
let resultMsg = ''
if (unref(isAddTypeOfMonitorModal)) {
// 新增
const { data: { code, message } } = await axios.put(`/ldpdtools/monitor/saveMonitor?stationName=${stationName}&name=${name}&monitorKey=${monitorKey}`);
resultCode = code
resultMsg = message
} else {
// 编辑
const { data: { code, message } } = await axios.put(`/ldpdtools/monitor/editMonitor?stationName=${stationName}&name=${name}&monitorKey=${monitorKey}`);
resultCode = code
resultMsg = message
}
monitorModalLoading.value = false
if (resultCode === 200) {
message.success(`${unref(isAddTypeOfMonitorModal) ? '新增' : '编辑'}监测点成功`);
isMonitorModalShow.value = false
initTreeData()
} else {
message.error(resultMsg);
}
}
// 监测站点弹框关闭
watch(isSiteModalShow, (bool) => {
if (bool) {
return
}
siteModalRef.value.resetFields()
isAddTypeOfSiteModal.value = true
siteModalForm.stationName = ''
siteModalForm.img = ''
siteHistoryData.value = {}
})
// 监测监测点弹框关闭
watch(isMonitorModalShow, (bool) => {
if (bool) {
return
}
monitorModalRef.value.resetFields()
isAddTypeOfMonitorModal.value = true
monitorModalForm.stationName = ''
monitorModalForm.name = ''
monitorModalForm.monitorKey = ''
})
// 历史趋势部分逻辑
const trendGraphData = ref([])
const dateRange = ref([]);
// tab在历史趋势监听勾选节点
watch([checkedKeys1, dateRange], async ([keys, time]) => {
const [startTime, endTime] = time || [];
if (unref(activeKey) !== 'historyTrend' || !startTime || !endTime) return;
echarts.connect('trend-group'); // 通过分组连接
trendGraphData.value = keys.map((key) => {
return {
key,
label: findMonitorNameByKey(unref(fullTreeData), key),
startTime,
endTime,
};
});
});
// 跳转到prpd/prps
const jumpToPRPD = (key) => {
activeKey.value = 'prpdAndPrps'
selectedKey.value = key
const isExist = unref(totalPrpds).some(item => item.monitorKey === key)
if (isExist) return
const addItem = unref(totalPrpds).find(item => !item.monitorKey)
addItem.monitorKey = key
addItem.monitorName = findMonitorNameByKey(unref(fullTreeData), key)
}
// 告警信息
const configLoading = ref(false)
const columns = [
{
title: '监测点',
dataIndex: 'name',
align: 'center',
ellipsis: true,
width: 120
},
{
title: '告警信息',
dataIndex: 'detailsInfo',
align: 'center',
ellipsis: true,
},
{
title: '放电类型',
dataIndex: 'pdType',
align: 'center',
ellipsis: true,
slots: { customRender: 'name1' },
width: 120
},
{
title: '是否复归',
dataIndex: 'isCancel',
align: 'center',
ellipsis: true,
slots: { customRender: 'name2' },
width: 120
},
{
title: '告警时间',
dataIndex: 'time',
align: 'center',
ellipsis: true,
width: 180
},
]
const dataSource = ref([])
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
pageSizeOptions: ['5', '10', '20'],
showTotal: (total) => `${total} 条数据`,
});
const cancelOps = [
{ label: '是', value: '0' },
{ label: '否', value: '1' },
]
const pdTypeOps = [
{
value: '1',
label: '尖刺放电',
},
{
value: '2',
label: '悬浮放电',
},
{
value: '3',
label: '沿面放电',
},
{
value: '4',
label: '内部放电',
},
{
value: '5',
label: '颗粒放电',
}
]
const alarmFilters = reactive({
times: [],
isCancel: '',
pdTypes: []
})
// 请求告警信息表格数据
const fetchAlarmGridData = async () => {
if (!unref(selectedKey)) {
message.warning(`请先选择左侧监测点`);
return
}
configLoading.value = true;
const params = {
current: pagination.current,
pageSize: pagination.pageSize,
startTime: alarmFilters.times?.[0] || '',
endTime: alarmFilters.times?.[1] || '',
isCancel: alarmFilters.isCancel,
pdType: alarmFilters.pdTypes,
monitorKey: unref(selectedKey),
stationName: findParentByKey(unref(fullTreeData), unref(selectedKey))
}
const { data: { result } } = await axios.post(`/ldpdtools/alarm/queryAlarmList`, params)
configLoading.value = false
dataSource.value = result?.result || []
pagination.total = result?.total || 0
}
// 重置表格
const initAlarmGridData = () => {
Object.assign(alarmFilters, {
times: [],
isCancel: '',
pdTypes: []
});
fetchAlarmGridData();
}
const handleTableChange = (pag) => {
Object.assign(pagination, pag);
fetchAlarmGridData();
};
// 单点击行
const alarmRowClick = (record, index) => {
return {
onDblclick: () => {
// 跳转到历史趋势
const pre24hTime = getPrevious24HourMark(record.time)
activeKey.value = 'historyTrend'
checkedKeys1.value = [record.monitorKey]
treeRef.value.setCheckedKeys(checkedKeys1.value);
dateRange.value = [pre24hTime, record.time]
},
};
}
// 告警管理监听
watch(selectedKey, (key) => {
if (!key || unref(activeKey) !== 'alarmConfig') return
// 初始化筛选数据
Object.assign(alarmFilters, {
times: [],
isCancel: '',
pdTypes: []
});
// 请求数据
fetchAlarmGridData();
});
// prpd/prps
const totalPrpds = ref([
{
monitorKey: '', // 监控点key
monitorName: '' // 监控点名称
},
{
monitorKey: '',
monitorName: ''
},
{
monitorKey: '',
monitorName: ''
},
{
monitorKey: '',
monitorName: ''
},
{
monitorKey: '',
monitorName: ''
},
{
monitorKey: '',
monitorName: ''
},
])
const prpdAndPrpsTimes = ref([]) // prpd/prps筛选时间
const eventListLoading = ref(false)
const eventList = ref([]) //事件列表
const isTypeOfCountPrpd = ref(false) //是否是累计prpd
const activeEventTime = ref('') // 当前选中的事件时间
const everntFilterVisible = ref(false)
const trendRef = ref()
// 事件筛选
const eventFilterForm = ref({
plusCount: '',
maxValue: '',
pdTypes: []
})
// 历史趋势所需数据
const trendGraphInfo = computed(() => {
return {
key: unref(selectedKey),
label: findMonitorNameByKey(unref(fullTreeData), unref(selectedKey)),
startTime: unref(dateRange)[0],
endTime: unref(dateRange)[1],
}
})
// 获取事件列表
const getEventList = async ({ startTime, endTime }) => {
if (startTime === null && endTime === null) {
// 历史趋势没数据
isTypeOfCountPrpd.value = true
eventList.value = []
activeEventTime.value = ''
prpdAndPrpsTimes.value = [] // 子组件判断空清除
return
}
isTypeOfCountPrpd.value = true
prpdAndPrpsTimes.value = [startTime, endTime]
eventListLoading.value = true
const params = {
current: 1,
pageSize: 999999999,
startTime,
endTime,
monitorKey: unref(selectedKey),
...unref(eventFilterForm),
pdTypes: unref(eventFilterForm).pdTypes.join(','),
};
const { data: { code, result: { result: res } } } = await axios.get('/ldpdtools/eventData/listByParam', { params }).finally(() => {
eventListLoading.value = false
})
if (code !== 200) {
return
}
activeEventTime.value = ''
eventList.value = res?.map(item => {
return {
pdType: item.pdType,
time: item.time,
maxValue: item.maxFloat,
plusCount: item.plusCount,
}
}) || []
}
// 筛选
const onFilterEventList = () => {
trendRef.value.emitFilterEvent()
everntFilterVisible.value = false
}
// 重置筛选框
const onResetEventList = () => {
eventFilterForm.value = {
plusCount: '',
maxValue: '',
pdTypes: []
}
onFilterEventList()
}
// 点击事件
const handleEventClick = (data) => {
activeEventTime.value = data.time
isTypeOfCountPrpd.value = false
prpdAndPrpsTimes.value = [data.time, '']
}
const handleCountPRPDSelect = ({ key: currentKey }) => {
const index = unref(checkedKeys1).indexOf(currentKey);
if (index > -1) {
// 取消选中
const deleteItem = unref(totalPrpds).find(item => item.monitorKey === currentKey)
deleteItem.monitorKey = ''
deleteItem.monitorName = ''
} else {
// 选中
const addItem = unref(totalPrpds).find(item => !item.monitorKey)
addItem.monitorKey = currentKey
addItem.monitorName = findMonitorNameByKey(unref(fullTreeData), currentKey)
}
}
// 配置页面
const stationName1 = ref('')
const deleteAlarmTimes = ref([]) // 删除告警时间段
const deleteAlarmConfirm = async () => {
if (!unref(stationName1)) {
return message.warning(`请选择站点!`);
}
if (!unref(deleteAlarmTimes).length) {
return message.warning(`请选择需要清除告警时间段!`);
}
const res = await axios.get(`/ldpdtools/alarm/deleteAlarmByTime`, {
params: {
startTime: unref(deleteAlarmTimes)?.[0] || '',
endTime: unref(deleteAlarmTimes)?.[1] || '',
stationName: unref(stationName1)
},
})
}
// 上传前校验
const alarmBeforeUpload = (file) => {
if (!unref(stationName1)) {
message.warning(`请选择站点!`);
return false
}
const allowedTypes = ['text/csv', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']
const isAllowedType = allowedTypes.includes(file.type) ||
['.csv', '.xlsx', '.xls'].some(ext => file.name.toLowerCase().endsWith(ext))
if (!isAllowedType) {
message.error('只能上传 CSV 或 Excel 文件!')
return false
}
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
message.error('文件大小不能超过 10MB!')
return false
}
return true
}
// 自定义上传方法
const alarmCustomUpload = async (options) => {
const { file } = options
try {
const formData = new FormData()
formData.append('file', file)
const { data: { code, message: message1 } } = await axios.post(`/ldpdtools/alarm/importAlarm`, formData, {
params: {
stationName: unref(stationName1)
}
})
if (code === 200) {
message.success(message1)
} else {
message.error('导入失败')
}
} catch (error) {
console.error('上传错误:', error)
}
}
const allPathCongfigs = ref([]) // 所有路径配置
const isCurrentInit = ref(false) // 是否正在初始化
const initTips = ref('')
const allPdTypes = ref([])
provide('provideAllPdTypes', allPdTypes)
// 获取放电类型
const getPdTypes = async () => {
const types = []
const { data: { result } } = await axios.get('/ldpdtools/config/getPdTypes');
Object.keys(result).forEach(key => {
types.push({
value: key,
label: result[key]
})
})
allPdTypes.value = types
}
// 获取所有配置项
const getAllConfigs = async () => {
getPdTypes()
allPathCongfigs.value = []
const { data: { result } } = await axios.post('/ldpdtools/config/getConfigKey')
Object.keys(result).forEach(key => {
allPathCongfigs.value = [...unref(allPathCongfigs), {
key,
label: result[key],
value: ''
}]
})
const { data: { result: paths } } = await axios.get('/ldpdtools/config/getConfig')
// 初始化路径赋值
allPathCongfigs.value.forEach(item => {
item.value = paths[item.key] || ''
})
// 排序
const itemIdToMoves = ["ALARM_TYPE", "MERGIN_ROOT_PATH"];
const startOfItemToMove = unref(allPathCongfigs).find(item => item.key === itemIdToMoves[0]);
const endOfItemToMove = unref(allPathCongfigs).find(item => item.key === itemIdToMoves[1]);
const remainingItems = unref(allPathCongfigs).filter(item => !itemIdToMoves.includes(item.key));
if (startOfItemToMove && endOfItemToMove) {
allPathCongfigs.value = [startOfItemToMove, ...remainingItems, endOfItemToMove];
}
}
// 保存路径
const savePathConfig = async (data) => {
if (!data.value) {
return message.error(`请输入${data.label}!`);
}
const { label, ...params } = data
const { data: { code } } = await axios.get('/ldpdtools/config/editConfig', { params })
if (code !== 200) return message.error(`保存${data.label}失败`)
message.success('保存成功!')
getAllConfigs()
}
// 重置路径
const resetPathConfig = async (key) => {
const { data: { code } } = await axios.get('/ldpdtools/config/resetConfig', { params: { key } })
if (code !== 200) return message.error(`重置${data.label}失败`)
message.success('重置成功!')
getAllConfigs()
}
// 获取初始化进度
const getInitPrgress = async () => {
const { data: { message } } = await axios.get('/ldpdtools/config/getInitProgress')
isCurrentInit.value = message.includes('/')
initTips.value = message
}
// 初始化目录
const initCatalogue = async () => {
const { data: { code } } = await axios.get('/ldpdtools/config/initConfig')
if (code === 200) {
setTimeout(() => {
getInitPrgress()
}, 1000) // 后台有一定延迟
}
}
onMounted(() => {
initTreeData()
getAllConfigs()
getInitPrgress()
document.addEventListener('click', handleClickOutside);
})
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside);
});
return {
treeRef,
treeData,
fullTreeData,
treeMenuInfo,
treeMenu,
filterNode,
handleRightClick,
checkedKeys1,
searchValue,
search,
activeKey,
tabsArr,
onLoadData,
editNodeItem,
deleteNodeItem,
isImportMonitorModalShow,
onImportMonitor,
importMonitorModalloading,
siteModalloading,
addSite,
isSiteModalShow,
siteModalRef,
siteModalForm,
onSiteModalSubmit,
isAddTypeOfSiteModal,
isMonitorModalShow,
isAddTypeOfMonitorModal,
monitorModalLoading,
addMonitor,
monitorModalRef,
monitorModalForm,
onMonitorModalSubmit,
dateRange,
checkTree,
trendGraphData,
typeOfCheckable,
timeSelectArr,
columns,
dataSource,
pagination,
configLoading,
handleTableChange,
selectedKey,
cancelOps,
alarmFilters,
pdTypeOps,
deleteAlarmTimes,
deleteAlarmConfirm,
fetchAlarmGridData,
initAlarmGridData,
stationName1,
alarmCustomUpload,
alarmBeforeUpload,
defaultProps,
totalPrpds,
allPathCongfigs,
savePathConfig,
resetPathConfig,
initCatalogue,
isCurrentInit,
initTips,
trendGraphRefs,
allPdTypes,
trendGraphInfo,
prpdAndPrpsTimes,
eventListLoading,
getEventList,
eventList,
isTypeOfCountPrpd,
handleEventClick,
activeEventTime,
alarmRowClick,
defaultExpendKeys,
jumpToPRPD,
everntFilterVisible,
eventFilterForm,
onFilterEventList,
onResetEventList,
trendRef,
handleNodeContentClick
};
},
components: {
prpdAndPrpsComp,
eventCount,
historyTrendGraph
}
})
.use(antd)
.use(ElementPlus, {
locale: ElementPlusLocaleZhCn // 使用中文语言包
})
.mount('#app');
</script>
</body>
</html>