代码提交

This commit is contained in:
name
2025-09-25 13:32:47 +08:00
parent 63022ffecd
commit 66b650750a
132 changed files with 35432 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
<!-- 谐波放大表格详情 -->
<template>
<el-dialog
v-model="visible"
:close-on-click-modal="false"
title="详情"
draggable
width="1000px"
@close="handleCloseDialog"
style="height: 600px"
>
<MyEChart :options="[]" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import MyEChart from "@/components/echarts/MyEchart.vue";
const visible = ref(false);
const options = ref<any>(null);
const open = () => {
visible.value = true;
}
const handleCloseDialog = () => {
visible.value = false;
options.value = null;
};
// const showCharts = (row: any, valueType: number, name: string) => {
// getHistoryLineData({
// lineId: row.lineId,
// number: row.number,
// phaseType: row.phaseType,
// searchTime: row.timeId,
// targetCode: row.targetCode,
// valueType,
// }).then((res) => {
// options.value = {
// title: {
// text:
// row.subName +
// " " +
// row.lineName +
// " " +
// row.targetName +
// " " +
// row.phaseType +
// "相" +
// name,
// },
// legend: {
// show: false,
// },
// xAxis: {
// type: "category",
// name: "时间",
// data: res.data[0]?.value.map((item: any[]) => item[0]),
// },
// yAxis: {
// name: "%",
// type: "value",
// },
// series: [
// {
// name: name,
// data: res.data[0]?.value.map((item: any[]) => item[1]),
// type: "line",
// },
// ],
// options: {
// grid: {
// top: "50px",
// left: "40px",
// right: "60px",
// bottom: "10px",
// containLabel: true,
// },
// dataZoom: null,
// },
// };
// });
// };
defineExpose({
open,
});
</script>

View File

@@ -0,0 +1,68 @@
<template>
<el-dialog
v-model="visible"
title="责任度计算"
draggable
width="1700px"
style="height: 875px"
@close="handleClose"
:close-on-click-modal="false"
>
<div class="currentPosition">
当前位置{{ alias || "" }}
</div>
<el-tabs v-model="activeName" class="demo-tabs">
<el-tab-pane label="系统" name="1">
<system @setTitle="setTitle" v-if="activeName == '1'" />
</el-tab-pane>
<el-tab-pane label="配网" name="2">
<distributionNetwork @setTitle="setTitle" v-if="activeName == '2'" />
</el-tab-pane>
</el-tabs>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive } from "vue";
import distributionNetwork from "./distributionNetwork.vue";
import system from "./system.vue";
const visible = ref(false);
const alias = ref("");
const activeName = ref("1");
const openDialog = () => {
visible.value = true;
};
onMounted(() => {});
const setTitle = (title: string) => {
alias.value = title;
};
const emit = defineEmits(["showCalculation", "close-dialog"]); // 打开弹窗
const handleClose = () => {
visible.value = false;
// 通知父组件显示收集界面
emit("showCalculation", false);
emit("close-dialog"); // 关闭弹窗
};
defineExpose({
openDialog,
});
</script>
<style scoped lang="scss">
@use "@/assets/scss/index.scss";
.currentPosition {
position: absolute;
top: 60px;
right: 10px;
font-size: 14px;
font-weight: 600;
z-index: 10;
}
</style>

View File

@@ -0,0 +1,337 @@
<template>
<el-dialog
v-model="visible"
:close-on-click-modal="false"
title="用采数据管理"
draggable
>
<el-form label-width="auto" :model="form" inline class="formBox">
<el-form-item label="关键字">
<el-input
v-model="form.searchValue"
placeholder="关键字查询"
clearable
style="width: 180px"
size="small"
/>
</el-form-item>
<div class="mt5">
<el-button
type="primary"
:icon="Search"
size="small"
@click="getTableData"
>查询</el-button
>
<el-button type="primary" :icon="Plus" size="small" @click="uploadFile"
>新增</el-button
>
<el-button
type="primary"
:icon="Download"
@click="exportTable"
size="small"
>导出
</el-button>
</div>
</el-form>
<div class="tableBox">
<el-table
:scrollbar-always-on="true"
:data="tableData"
height="400px"
size="small"
:header-cell-style="{ textAlign: 'center' }"
v-loading="loading1"
element-loading-background="#343849c7"
border
>
<el-table-column prop="name" align="center" label="表名" />
<el-table-column
prop="startTime"
align="center"
label="起始时间"
width="150"
/>
<el-table-column
prop="endTime"
align="center"
label="截止时间"
width="150"
/>
<el-table-column prop="updateTime" align="center" label="更新时间" />
<el-table-column fixed="right" align="center" label="操作" width="140">
<template #default="{ row }">
<el-button
link
type="primary"
size="small"
v-if="row.integrity == 1"
@click="handleClick(row)"
>完整性详情</el-button
>
<el-button
link
type="danger"
size="small"
@click.stop="handleDelete(row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
size="small"
style="margin-top: 10px"
:currentPage="form.pageNum"
:page-size="form.pageSize"
:page-sizes="[10, 20, 50, 100, 200]"
background
:layout="'sizes,total, ->, prev, pager, next, jumper'"
:total="total"
@size-change="onTableSizeChange"
@current-change="onTableCurrentChange"
></el-pagination>
</el-dialog>
<!-- 详情 -->
<completenessDetails ref="completenessDetailsRef" @close="close" />
<el-dialog
v-model="dialogVisible"
draggable
title="上传数据"
width="500"
:close-on-click-modal="false"
:before-close="handleClose"
>
<el-upload
ref="upload"
action=""
v-model:file-list="fileList"
accept=".xlsx,.xls"
:auto-upload="false"
:on-change="choose"
:limit="2"
>
<el-button type="primary" :icon="Upload" size="small">上传文件</el-button>
</el-upload>
<template #footer>
<el-button @click="handleClose" size="small">取消</el-button>
<el-button
type="primary"
@click="submitupload"
:loading="loading"
size="small"
>确认</el-button
>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive } from "vue";
import completenessDetails from "./completenessDetails.vue";
import type { UploadInstance } from "element-plus";
import { Search, Plus, Upload, Download } from "@element-plus/icons-vue";
import { useStore } from "vuex";
import * as XLSX from "xlsx";
import {
userDataList,
uploadUserData,
deleteUserDataByIds,
} from "@/api/manage_wx";
const store = useStore();
const visible = ref(false);
const openDialog = () => {
visible.value = true;
};
const form = reactive({
searchValue: "",
pageNum: 1,
pageSize: 20,
});
const total = ref(0); // 假设总条数为100
const tableData = ref([]);
const completenessDetailsRef = ref();
const dialogVisible = ref(false);
const upload = ref<UploadInstance>();
const fileList = ref([]);
const loading = ref(false);
const loading1 = ref(false);
// 关闭上传弹框
const handleClose = () => {
fileList.value = [];
dialogVisible.value = false;
visible.value = true;
getTableData();
};
// 详情关闭弹框
const close = () => {
visible.value = true;
};
// 新增
const uploadFile = () => {
dialogVisible.value = true;
visible.value = false;
};
// 上传
const choose = (e: any) => {
upload.value!.clearFiles();
setTimeout(() => {
if (e.name.includes(".xls")) {
fileList.value = [e];
} else {
ElMessage.warning("请上传Excel文件");
}
}, 0);
};
// 上传
const submitupload = async () => {
if (fileList.value.length == 0) {
ElMessage.warning("请上传文件!");
return;
}
ElMessage.info("上传中,请稍等...");
const formData = new FormData();
formData.append("file", fileList.value[0].raw);
loading.value = true;
uploadUserData(formData)
.then((res) => {
ElMessage.success("上传成功");
loading.value = false;
handleClose();
// tableStore.index();
})
.catch((err) => {
loading.value = false;
});
};
// provide("tableStore", tableStore);
// tableStore.table.params.searchValue = "";
const onTableSizeChange = (size: number) => {
form.pageSize = size;
// 重新加载数据
getTableData();
};
const onTableCurrentChange = (page: number) => {
form.pageNum = page;
// 重新加载数据
getTableData();
};
const getTableData = async () => {
loading1.value = true;
// form.deptId = store.state.deptId;
const res: any = await userDataList(form);
if (res.code == "A0000") {
tableData.value = res.data.records;
total.value = res.data.total;
}
loading1.value = false;
};
// 完整性详情
const handleClick = (row) => {
visible.value = false;
completenessDetailsRef.value.open(row);
};
// 删除
const handleDelete = (row) => {
ElMessageBox.confirm("确定删除?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
deleteUserDataByIds([row.id]).then(() => {
ElMessage.success("删除成功");
getTableData();
});
})
.catch(() => {
ElMessage({
type: "info",
message: "删除取消",
});
});
};
// 导出
const exportTable = async () => {
let columnExpor: any = [
[
"表名",
"起始时间",
"截止时间",
"更新时间",
],
];
let list = [];
await userDataList({
...form,
pageNum: 1,
pageSize: total.value,
}).then((res) => {
let data = res.data.records.map((item) => {
return [
item.name,
item.startTime,
item.endTime,
item.updateTime,
];
});
list = [...columnExpor, ...data];
// 创建工作表
const worksheet = XLSX.utils.aoa_to_sheet(list);
worksheet["!cols"] = list.map((col) => ({ wch: 20 }));
// 创建工作簿
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "用采数据管理");
// 写出文件
XLSX.writeFile(workbook, "用采数据管理" + ".xlsx");
});
};
onMounted(() => {
getTableData();
});
defineExpose({
openDialog,
});
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.tableBox {
padding: 0px !important;
}
.formBox {
display: flex;
justify-content: space-between;
}
:deep(.el-upload-list__item-name) {
color: #fff;
}
:deep(.el-upload-list__item:hover) {
.el-upload-list__item-name {
color: #909399;
}
}
</style>

View File

@@ -0,0 +1,195 @@
<template>
<!--暂降 -->
<el-dialog
:close-on-click-modal="false"
draggable
v-model="machineVisible"
title="完整性不足详情"
:before-close="handleClose"
width="1000px"
>
<div class="formBox">
<el-form label-width="70px" :model="form" inline>
<el-form-item label="关键字">
<el-input
style="width: 150px"
v-model="form.searchValue"
placeholder="关键字查询"
clearable
size="small"
/>
</el-form-item>
</el-form>
<div>
<el-button
type="primary"
:icon="Search"
size="small"
@click="getTableData"
>查询</el-button
>
<el-button
type="primary"
:icon="Download"
@click="exportTable"
size="small"
>导出
</el-button>
</div>
</div>
<div class="tableBox">
<el-table
:scrollbar-always-on="true"
:data="tableData"
height="400px"
size="small"
:header-cell-style="{ textAlign: 'center' }"
v-loading="loading"
element-loading-background="#343849c7"
border
>
<el-table-column prop="userNo" align="center" label="户号" />
<el-table-column prop="userName" align="center" label="用户名" />
<el-table-column
prop="lineNo"
align="center"
label="测量点局号"
width="180"
/>
<el-table-column
prop="updateTime"
align="center"
label="日期"
width="180"
/>
<el-table-column
prop="integrity"
align="center"
label="完整性(%)"
width="130"
>
<template #default="{ row }">
{{ Math.floor(row.integrity * 10000) / 100 }}
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
size="small"
style="margin-top: 10px"
:currentPage="form.pageNum"
:page-size="form.pageSize"
:page-sizes="[10, 20, 50, 100, 200]"
background
:layout="'sizes,total, ->, prev, pager, next, jumper'"
:total="total"
@size-change="onTableSizeChange"
@current-change="onTableCurrentChange"
></el-pagination>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, inject, onMounted } from "vue";
import { Search, Download } from "@element-plus/icons-vue";
import { userDataIntegrityList } from "@/api/manage_wx";
import { useStore } from "vuex";
import * as XLSX from "xlsx";
const emits = defineEmits(["close"]);
const store = useStore();
const machineVisible = ref(false);
const form = reactive({
searchValue: "",
pageNum: 1,
pageSize: 20,
userDataId: "",
});
const loading = ref(false);
const total = ref(0); // 假设总条数为100
const tableData = ref([]);
//form表单校验规则
const open = (row) => {
machineVisible.value = true;
form.searchValue = "";
form.pageNum = 1;
form.pageSize = 20;
form.userDataId = row.id;
getTableData();
};
const onTableSizeChange = (size: number) => {
form.pageSize = size;
getTableData();
};
const onTableCurrentChange = (page: number) => {
form.pageNum = page;
getTableData();
};
const getTableData = async () => {
loading.value = true;
const res: any = await userDataIntegrityList(form);
if (res.code == "A0000") {
tableData.value = res.data.records;
total.value = res.data.total;
}
loading.value = false;
};
const handleClose = () => {
machineVisible.value = false;
emits("close");
};
// 导出
const exportTable = async () => {
let columnExpor: any = [
["户号", "用户名", "测量点局号", "日期", "完整性(%)"],
];
let list = [];
await userDataIntegrityList({
...form,
pageNum: 1,
pageSize: total.value,
}).then((res) => {
let data = res.data.records.map((item) => {
return [
item.userNo,
item.userName,
item.lineNo,
item.updateTime,
Math.floor(item.integrity * 10000) / 100,
];
});
list = [...columnExpor, ...data];
// 创建工作表
const worksheet = XLSX.utils.aoa_to_sheet(list);
worksheet["!cols"] = list.map((col) => ({ wch: 20 }));
// 创建工作簿
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "完整性不足详情");
// 写出文件
XLSX.writeFile(workbook, "完整性不足详情" + ".xlsx");
});
};
onMounted(() => {});
defineExpose({ open });
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.tableBox {
padding: 0px !important;
}
.formBox {
display: flex;
justify-content: space-between;
}
</style>

View File

@@ -0,0 +1,262 @@
<template>
<!--暂降 -->
<el-dialog
:close-on-click-modal="false"
draggable
v-model="machineVisible"
title="暂降事件"
width="1200px"
>
<div class="tableBox">
<div style="display: flex;justify-content: flex-end;">
<el-button
type="primary"
:icon="Download"
@click="exportTable"
size="small"
>导出
</el-button>
</div>
<el-table
:scrollbar-always-on="true"
:data="tableData"
height="443px"
size="small"
v-loading="loading"
element-loading-background="#343849c7"
:header-cell-style="{ textAlign: 'center' }"
border
style="margin-top: 13px;"
>
<!-- <el-table-column type="index" align="center" label="序号" width="70" /> -->
<el-table-column label="序号" align="center" type="index" width="70">
<template #default="scope">
<span>{{
(form.pageNum - 1) * form.pageSize + scope.$index + 1
}}</span>
</template>
</el-table-column>
<el-table-column
prop="startTime"
align="center"
label="发生时间"
show-overflow-tooltip
width="160"
/>
<el-table-column prop="stationName" align="center" label="变电站" />
<el-table-column prop="lineName" align="center" label="监测点" />
<el-table-column
prop="objName"
label="用户"
align="center"
show-overflow-tooltip
>
<template v-slot="scope">
<span>{{ scope.row.objName ? scope.row.objName : "/" }}</span>
</template>
</el-table-column>
<el-table-column
prop="eventType"
align="center"
label="触发类型"
width="80"
/>
<el-table-column
prop="featureAmplitude"
align="center"
label="残余电压(%)"
width="100"
>
<template v-slot="scope">
<span>{{ (scope.row.featureAmplitude * 100).toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column
prop="duration"
align="center"
label="持续时间(S)"
width="100"
/>
<el-table-column fixed="right" label="操作" width="80" align="center">
<template #default="scope">
<el-button
link
type="primary"
size="small"
@click.stop="trendCharts(scope.row)"
>波形</el-button
>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
size="small"
style="margin-top: 10px"
:currentPage="form.pageNum"
:page-size="form.pageSize"
:page-sizes="[10, 20, 50, 100, 200]"
background
:layout="'sizes,total, ->, prev, pager, next, jumper'"
:total="total"
@size-change="onTableSizeChange"
@current-change="onTableCurrentChange"
></el-pagination>
</el-dialog>
<!-- 波形图 -->
<el-dialog
:close-on-click-modal="false"
draggable
v-model="trendVisible"
v-if="trendVisible"
title="波形"
width="70%"
@close="handleCloseTrend"
>
<waveForm ref="waveFormRef" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, inject } from "vue";
import { eventListByLineId } from "@/api/manage_wx";
import waveForm from "@/components/BX/waveForm.vue";
import * as XLSX from "xlsx";
import { Download } from "@element-plus/icons-vue";
const machineVisible = ref(false);
const form = reactive({
lineId: "",
pageNum: 1,
pageSize: 20,
searchBeginTime: "",
searchEndTime: "",
});
const total = ref(0); // 假设总条数为100
const loading = ref(false);
const tableData = ref([]);
import { useStore } from "vuex";
const store = useStore();
const trendVisible = ref(false);
const waveFormRef = ref();
//点击波形图
const trendCharts = (row: any) => {
row.eventdetail_index = row.eventId;
machineVisible.value = false;
trendVisible.value = true;
setTimeout(() => {
waveFormRef.value?.open({
...row,
bdname: row.stationName,
pointname: row.lineName,
timeid: row.startTime,
eventvalue: row.featureAmplitude,
persisttime: row.duration,
});
}, 500);
};
// 关闭波形图
const handleCloseTrend = () => {
trendVisible.value = false;
machineVisible.value = true;
};
const open = async (id: any) => {
form.pageNum = 1;
form.pageSize = 20;
form.lineId = id;
machineVisible.value = true;
await init();
};
const init = async () => {
loading.value = true;
eventListByLineId({
lineId: form.lineId,
searchBeginTime: store.state.timeValue[0],
searchEndTime: store.state.timeValue[1],
}).then((res) => {
tableData.value = res.data.records;
total.value = res.data.total;
loading.value = false;
});
};
const onTableSizeChange = (size: number) => {
form.pageSize = size;
init();
};
const onTableCurrentChange = (page: number) => {
form.pageNum = page;
init();
};
// 暂降溯源导出
const exportTable = async () => {
let columnExpor: any = [
[
"发生时间",
"变电站",
"监测点",
"用户",
"触发类型",
"残余电压(%)",
"持续时间(S)",
],
];
let list = [];
await eventListByLineId({
lineId: form.lineId,
searchBeginTime: store.state.timeValue[0],
searchEndTime: store.state.timeValue[1],
pageNum: 1,
pageSize: total.value,
}).then((res) => {
let data = res.data.records.map((item) => {
return [
item.startTime,
item.stationName,
item.lineName,
item.objName,
item.eventType,
(item.featureAmplitude * 100).toFixed(2),
item.duration,
];
});
list = [...columnExpor, ...data];
// 创建工作表
const worksheet = XLSX.utils.aoa_to_sheet(list);
worksheet["!cols"] = list.map((col) => ({ wch: 20 }));
// 创建工作簿
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "暂降事件");
// 写出文件
XLSX.writeFile(workbook, "暂降事件" + ".xlsx");
});
};
defineExpose({ open });
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.tableBox {
padding: 0px !important;
}
.formBox {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.formLeft {
display: flex;
align-items: center;
color: #fff;
}
</style>

View File

@@ -0,0 +1,558 @@
<template>
<!--暂降 -->
<el-dialog
:close-on-click-modal="false"
draggable
v-model="machineVisible"
title="暂降事件"
width="1000px"
>
<div class="tableBox">
<el-form
:inline="true"
style="display: flex; justify-content: space-between"
>
<el-form-item label="关键字筛选">
<el-input
clearable
style="width: 150px"
v-model="searchValue"
size="small"
placeholder="电站、测点、用户信息"
></el-input>
<el-popover placement="bottom" :width="550" trigger="click">
<template #reference>
<el-button
size="small"
:icon="DArrowRight"
type="primary"
style="margin-left: 10px"
>更多</el-button
>
</template>
<el-form label-width="auto">
<!-- <el-form-item label="关键字筛选">
<el-input
clearable
style="width: 150px"
v-model="searchValue"
size="small"
placeholder="电站、测点、用户信息"
></el-input>
</el-form-item> -->
<el-form-item label="运维单位">
<el-tree-select
v-model="deptsIndex"
:data="deptsList"
:render-after-expand="false"
clearable
size="small"
style="width: 150px"
:props="{
value: 'id',
label: 'name',
children: 'children',
}"
/>
<!-- <el-select
v-model="deptsIndex"
placeholder="请选择运维单位"
clearable
size="small"
:teleported="false"
style="width: 150px"
>
<el-option
v-for="item in deptsList"
:label="item.name"
:value="item.id"
:key="item.id"
></el-option>
</el-select> -->
</el-form-item>
<el-form-item label="触发类型">
<el-select
clearable
size="small"
:teleported="false"
v-model="eventForm.eventType"
placeholder="请选择触发类型"
style="width: 150px"
>
<el-option
v-for="item in eventTypeList"
:label="item.name"
:value="item.id"
:key="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="残余电压百分比">
<el-input-number
v-model="eventForm.eventValueMin"
:min="0"
style="width: 150px"
size="small"
:max="100"
:precision="1"
:step="1"
@change="
(e) => (e == null ? (eventForm.eventValueMin = 1) : null)
"
><template #suffix>
<span>%</span>
</template></el-input-number
>
<span> < 残余电压 < </span>
<el-input-number
v-model="eventForm.eventValueMax"
:min="0"
style="width: 150px"
size="small"
:max="100"
:precision="1"
:step="1"
@change="
(e) => (e == null ? (eventForm.eventValueMax = 1) : null)
"
><template #suffix>
<span>%</span>
</template></el-input-number
>
</el-form-item>
<el-form-item label="暂降持续事时间">
<el-input-number
v-model="eventForm.eventDurationMin"
:min="0"
style="width: 150px"
size="small"
:max="1000000"
:precision="1"
:step="1"
@change="
(e) => (e == null ? (eventForm.eventDurationMin = 1) : null)
"
><template #suffix>
<span>ms</span>
</template></el-input-number
>
<span> < 持续时间 < </span>
<el-input-number
v-model="eventForm.eventDurationMax"
:min="0"
style="width: 150px"
size="small"
:max="1000000"
:precision="1"
:step="1"
@change="
(e) => (e == null ? (eventForm.eventDurationMax = 1) : null)
"
><template #suffix>
<span>ms</span>
</template></el-input-number
>
</el-form-item>
</el-form>
</el-popover>
</el-form-item>
<el-form-item style="margin-right: -10px">
<el-button size="small" :icon="Search" type="primary" @click="init()"
>查询</el-button
>
<el-button size="small" :icon="RefreshLeft" @click="clearInit()"
>重置</el-button
>
<el-button
type="primary"
:icon="Download"
@click="exportTable"
size="small"
style="margin-right: 10px"
>导出
</el-button>
<el-button
style="margin-right: 10px"
v-if="displayValue == 1"
size="small"
:icon="HelpFilled"
type="primary"
@click="handleAggregation"
>溯源</el-button
>
</el-form-item>
</el-form>
<!-- <div style="float: right; margin-bottom: 9px" v-if="displayValue == 1">
<el-button
size="small"
:icon="HelpFilled"
type="primary"
@click="handleAggregation"
>聚合</el-button
>
</div> -->
<el-table
:scrollbar-always-on="true"
:data="tableData"
height="443px"
size="small"
v-loading="loading"
element-loading-background="#343849c7"
:header-cell-style="{ textAlign: 'center' }"
border
>
<!-- <el-table-column type="index" align="center" label="序号" width="70" /> -->
<el-table-column label="序号" align="center" type="index" width="70">
<template #default="scope">
<span>{{
(form.pageNum - 1) * form.pageSize + scope.$index + 1
}}</span>
</template>
</el-table-column>
<el-table-column
prop="startTime"
align="center"
label="发生时间"
show-overflow-tooltip
width="160"
/>
<el-table-column
prop="stationName"
align="center"
label="变电站"
width="120"
/>
<el-table-column
prop="lineName"
align="center"
label="监测点"
width="120"
/>
<el-table-column
prop="objName"
label="用户"
align="center"
show-overflow-tooltip
>
<template v-slot="scope">
<span>{{ scope.row.objName ? scope.row.objName : "/" }}</span>
</template>
</el-table-column>
<el-table-column
prop="eventType"
align="center"
label="触发类型"
width="90"
/>
<el-table-column
prop="featureAmplitude"
align="center"
label="残余电压(%)"
width="90"
>
<template v-slot="scope">
<span>{{ (scope.row.featureAmplitude * 100).toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column
prop="duration"
align="center"
label="持续时间(S)"
width="90"
/>
<el-table-column fixed="right" label="操作" width="80" align="center">
<template #default="scope">
<el-button
link
type="primary"
size="small"
@click.stop="trendCharts(scope.row)"
>波形</el-button
>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
size="small"
style="margin-top: 10px"
:currentPage="form.pageNum"
:page-size="form.pageSize"
:page-sizes="[10, 20, 50, 100, 200]"
background
:layout="'sizes,total, ->, prev, pager, next, jumper'"
:total="total"
@size-change="onTableSizeChange"
@current-change="onTableCurrentChange"
></el-pagination>
</el-dialog>
<!-- 波形图 -->
<el-dialog
:close-on-click-modal="false"
draggable
v-model="trendVisible"
v-if="trendVisible"
title="波形"
width="70%"
@close="handleCloseTrend"
>
<waveForm ref="waveFormRef" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, inject, onMounted, watch } from "vue";
import {
getEventList,
processEvents,
loginDeptTree,
getDicDataByTypeCode,
} from "@/api/manage_wx";
import { HelpFilled } from "@element-plus/icons-vue";
import waveForm from "@/components/BX/waveForm.vue";
import {
DArrowRight,
Search,
RefreshLeft,
Download,
} from "@element-plus/icons-vue";
import * as XLSX from "xlsx";
// 定义 emit
const emit = defineEmits(["aggregation-success"]);
const machineVisible = ref(false);
const form = reactive({
pageNum: 1,
pageSize: 20,
});
const total = ref(0); // 假设总条数为100
const loading = ref(false);
const tableData = ref([]);
import { useStore } from "vuex";
const store = useStore();
const displayValue = ref();
const trendVisible = ref(false);
const waveFormRef = ref();
const open = async (val) => {
displayValue.value = val;
machineVisible.value = true;
await init();
};
// 查询
const searchValue = ref("");
const deptsIndex = ref("");
const deptsList = ref([]); //部门列表
const eventTypeList = ref([]); //触发类型
const eventForm: any = reactive({
eventValueMin: null,
eventValueMax: null,
eventDurationMin: null,
eventDurationMax: null,
eventType: null,
});
const clearInit = async () => {
searchValue.value = "";
eventForm.eventValueMin = null;
eventForm.eventValueMax = null;
eventForm.eventDurationMin = null;
eventForm.eventDurationMax = null;
eventForm.eventType = null;
deptsIndex.value = null;
await init();
};
// 部门
const initDept = () => {
loginDeptTree({
deptIndex: store.state.deptId,
}).then((res: any) => {
deptsList.value = res.data;
});
};
// 触发类型
const dicDataByTypeCode = () => {
getDicDataByTypeCode({
dictTypeCode: "Event_Statis",
}).then((res: any) => {
eventTypeList.value = res.data;
});
};
const init = async () => {
loading.value = true;
getEventList({
deptId: store.state.deptId,
searchBeginTime: store.state.timeValue[0],
searchEndTime: store.state.timeValue[1],
pageNum: form.pageNum,
pageSize: form.pageSize,
searchValue: searchValue.value,
...eventForm,
eventValueMin:
eventForm.eventValueMin == null ? null : eventForm.eventValueMin / 100,
eventValueMax:
eventForm.eventValueMax == null ? null : eventForm.eventValueMax / 100,
}).then((res) => {
tableData.value = res.data.records;
total.value = res.data.total;
loading.value = false;
});
};
const handleAggregation = (row: any) => {
machineVisible.value = false;
ElMessageBox.confirm(
`是否确认对当前用户部门${store.state.timeValue[0]}${store.state.timeValue[1]}之间的暂降事件进行溯源?`,
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
)
.then(() => {
processEvents({
deptId: store.state.deptId,
searchBeginTime: store.state.timeValue[0],
searchEndTime: store.state.timeValue[1],
}).then((res: any) => {
if (res.code == "A0000") {
ElMessage({
type: "success",
message: res.message,
});
// 通知父组件执行 initialAggregation
emit("aggregation-success");
} else {
ElMessage({
type: "warning",
message: res.message,
});
}
});
})
.catch((error) => {});
};
//点击波形图
const trendCharts = (row: any) => {
row.eventdetail_index = row.eventId;
machineVisible.value = false;
trendVisible.value = true;
setTimeout(() => {
waveFormRef.value?.open({
...row,
bdname: row.stationName,
pointname: row.lineName,
timeid: row.startTime,
eventvalue: row.featureAmplitude,
persisttime: row.duration,
});
}, 500);
};
// 关闭波形图
const handleCloseTrend = () => {
trendVisible.value = false;
machineVisible.value = true;
};
const onTableSizeChange = (size: number) => {
form.pageSize = size;
init();
};
const onTableCurrentChange = (page: number) => {
form.pageNum = page;
init();
};
// 暂降溯源导出
const exportTable = async () => {
let columnExpor: any = [
[
"发生时间",
"变电站",
"监测点",
"用户",
"触发类型",
"残余电压(%)",
"持续时间(S)",
],
];
let list = [];
await getEventList({
deptId: store.state.deptId,
searchBeginTime: store.state.timeValue[0],
searchEndTime: store.state.timeValue[1],
searchValue: searchValue.value,
...eventForm,
eventValueMin:
eventForm.eventValueMin == null ? null : eventForm.eventValueMin / 100,
eventValueMax:
eventForm.eventValueMax == null ? null : eventForm.eventValueMax / 100,
pageNum: 1,
pageSize: total.value,
}).then((res) => {
let data = res.data.records.map((item) => {
return [
item.startTime,
item.stationName,
item.lineName,
item.objName,
item.eventType,
(item.featureAmplitude * 100).toFixed(2),
item.duration,
];
});
list = [...columnExpor, ...data];
// 创建工作表
const worksheet = XLSX.utils.aoa_to_sheet(list);
worksheet["!cols"] = list.map((col) => ({ wch: 20 }));
// 创建工作簿
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "暂降事件");
// 写出文件
XLSX.writeFile(workbook, "暂降事件" + ".xlsx");
});
};
onMounted(() => {
initDept();
dicDataByTypeCode();
});
defineExpose({ open });
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.tableBox {
padding: 0px !important;
}
.formBox {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.formLeft {
display: flex;
align-items: center;
color: #fff;
}
</style>

View File

@@ -0,0 +1,701 @@
<template>
<!-- 配网计算 -->
<splitpanes style="height: 100%" class="default-theme" id="navigation-splitpanes">
<pane :size="20">
<PointTree :showSelect="false" @node-click="handleNodeClick" @init="handleNodeClick"></PointTree>
</pane>
<pane style="background-color: rgba(44, 46, 60, 0.1)" :size="100 - size">
<el-form :model="form" inline label-width="auto">
<el-form-item label="谐波类型">
<el-radio-group v-model="form.type">
<el-radio-button label="谐波电压" size="small" value="1" />
<el-radio-button label="谐波电流" size="small" value="0" />
</el-radio-group>
</el-form-item>
<el-form-item label="谐波次数">
<el-select v-model="form.index" filterable multiple :multiple-limit="5" collapse-tags collapse-tags-tooltip
clearable placeholder="请选择次数" style="width: 200px" size="small">
<el-option v-for="item in 49" :key="item" :label="item + 1 + '次'" :value="item + 1"></el-option>
</el-select>
</el-form-item>
<el-form-item label="负荷数据">
<el-select v-model="form.loadDataId" clearable filterable placeholder="请选择负荷数据" style="width: 200px"
size="small">
<el-option v-for="item in loadDataOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<!-- <el-button type="primary" icon="el-icon-Plus" @click="push('/admin/division/aListOfLoadData')">
新增
</el-button> -->
<el-button type="primary" :icon="Select" @click="submit" size="small">确定</el-button>
</el-form-item>
</el-form>
<el-tabs v-model="activeName" v-if="showTabs" style="height: 740px">
<el-tab-pane v-for="(item, index) in tabList" :key="item" :label="item.label" :name="index"
style="height: 100%">
<div>
<div>
<span style="color: #fff">时间范围</span>
<el-date-picker v-model="item.time" class="mr10 ml10" type="daterange" start-placeholder="起始时间"
end-placeholder="结束时间" format="YYYY-MM-DD" date-format="YYYY-MM-DD" time-format="YYYY-MM-DD"
value-format="YYYY-MM-DD" :disabled-date="handleDisabledDate" size="small" />
<el-button type="primary" :icon="CaretRight" @click="execute(item, index)" size="small">
执行
</el-button>
</div>
<div v-if="item.showEcahr == 1" class="harmonicButton">
<el-form :inline="true" v-model="item.form">
<el-form-item label="限值" v-if="item.showDynamic">
<el-input v-model="item.form.limit" placeholder="请选择限值" disabled style="width: 200px" size="small">
<template #append>
<el-button :icon="Edit" :class="[code == 0 ? 'frontBox' : '']" @click="setCode(0)" size="small" />
</template>
</el-input>
</el-form-item>
<el-form-item label="时间点一" v-if="item.showDynamic">
<el-input v-model="item.form.time1" placeholder="请选择时间点一" disabled style="width: 200px" size="small">
<template #append>
<el-button :icon="Edit" :class="[code == 1 ? 'frontBox' : '']" @click="setCode(1)" size="small" />
</template>
</el-input>
</el-form-item>
<el-form-item label="时间点二" v-if="item.showDynamic">
<el-input v-model="item.form.time2" placeholder="请选择时间点二" disabled style="width: 200px" size="small">
<template #append>
<el-button :icon="Edit" :class="[code == 2 ? 'frontBox' : '']" @click="setCode(2)" size="small" />
</template>
</el-input>
</el-form-item>
<el-button type="primary" :icon="Document" @click="generateFn" v-if="!item.showDynamic" size="small">
生成动态谐波责任数据
</el-button>
<el-button type="primary" :icon="Document" v-else @click="generateMetrics" size="small">
生成谐波责任指标
</el-button>
</el-form>
</div>
<div class="box" v-loading="loading" element-loading-background="#343849c7">
<MyEChart :options="item.options" v-if="item.showEcahr == 1" @group="group" />
<el-empty description="时间范围内无谐波数据" v-if="item.showEcahr == 2" />
</div>
<!-- 生成动态谐波责任数据 -->
<div class="box boxTab" v-loading="loading1" element-loading-background="#343849c7">
<MyEChart :options="item.dynamicOptions" style="flex: 1" v-if="item.showDynamic" />
<div style="width: 500px" v-if="item.showDynamic" class="tableBox">
<el-table :scrollbar-always-on="true" ref="tableRef" :data="item.dynamicData" height="280px"
size="small" :header-cell-style="{ textAlign: 'center' }" border>
<el-table-column prop="customerName" align="center" label="用户名(用户号)" />
<el-table-column prop="responsibilityData" align="center" label="责任数据(%)" width="120">
<template #default="scope">
{{
Math.floor(scope.row.responsibilityData * 10000) /
10000
}}
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</el-tab-pane>
</el-tabs>
</pane>
</splitpanes>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive } from "vue";
import "splitpanes/dist/splitpanes.css";
import { Splitpanes, Pane } from "splitpanes";
import PointTree from "@/components/tree/pointTree.vue";
import { Edit, Right } from "@element-plus/icons-vue";
import MyEChart from "@/components/echarts/MyEchart.vue";
import { timeFormat } from "@/utils/common";
import { yMethod } from "@/utils/echartMethod";
import { Select, CaretRight, Document } from "@element-plus/icons-vue";
import {
userDataList,
getHistoryHarmData,
getDynamicData,
getResponsibilityData,
} from "@/api/manage_wx";
const emit = defineEmits(["setTitle"]); // 打开弹窗
const visible = ref(false);
const size = ref(0);
const dotList: any = ref({});
const form: any = reactive({
type: "1",
index: [],
loadDataId: "",
});
const loadDataOptions: any = ref([]);
const code = ref(3);
const xAxisData = ref([]);
const loading = ref(false);
const loading1 = ref(false);
const tabList: any = ref([]);
const activeName = ref(0);
const xValue = ref("");
const showTabs = ref(false);
// 设置时间
const timeFrame = ref(["", ""]);
const openDialog = () => {
visible.value = true;
};
const handleNodeClick = (data: any) => {
if (data.level == 6) {
dotList.value = data;
setTimeout(() => {
emit("setTitle", data.alias);
}, 0)
}
};
// 处理日期禁用逻辑
const handleDisabledDate = (date) => {
// 定义时间边界
const startLimit = new Date(timeFrame.value[0]).getTime() - 86400000; //向前推1天
const endLimit = new Date(timeFrame.value[1]).setHours(23, 59, 59, 999);
// 如果日期不存在(选择今天时可能出现),不禁用
if (!date) return false;
// 禁用 2025-08-01 之前和 2025-08-31 之后的日期
return date.getTime() < startLimit || date.getTime() > endLimit;
};
// 这是按钮变色
const setCode = (num: number) => {
if (code.value == num) {
return (code.value = 3);
}
code.value = num;
};
// 确定
const submit = () => {
if (form.loadDataId == "") {
return ElMessage.warning("请选择负荷数据");
}
if (form.index.length == 0) {
return ElMessage.warning("请选择谐波次数");
}
if (form.index.length == 0) {
showTabs.value = false;
} else {
let timeList = loadDataOptions.value.filter(
(item: any) => item.id == form.loadDataId
)[0];
showTabs.value = true;
let list = JSON.parse(JSON.stringify(form.index)).sort((a, b) => a - b);
tabList.value = [];
list.forEach((item: any) => {
tabList.value.push({
label: item + "次谐波",
key: item,
time: [timeList.startTime, timeList.endTime],
showExecute: false,
form: {
limit: "",
time1: "",
time2: "",
},
showEcahr: 3, //1显示echart 2显示无数据 3什么都没有
options: {},
dynamicOptions: {}, //动态echarts
dynamicList: {}, //动态echarts
showDynamic: false, //动态执行展示
});
timeFrame.value = [timeList.startTime, timeList.endTime];
});
code.value = 3;
activeName.value = 0;
}
};
// 执行
const execute = async (item: any, index: number) => {
tabList.value[activeName.value].showDynamic = false;
loading.value = true;
await getHistoryHarmData({
searchBeginTime: item.time[0],
searchEndTime: item.time[1],
type: form.type,
time: item.key,
lineId: dotList.value.id,
})
.then((res: any) => {
if (res.code == "A0000") {
let [min, max] = yMethod(
res.data.historyData.map((item: any) => item.value + 0.1)
);
xAxisData.value = res.data.historyData.map((item: any) => item.time);
tabList.value[index].options = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
// 刻度线样式
axisTick: {
lineStyle: {
color: "#fff", // 刻度线白色
},
},
// 轴线样式
axisLine: {
lineStyle: {
color: "#fff", // 轴线白色
},
},
},
tooltip: {
formatter(params: any) {
xValue.value = params[0].value[0];
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
right: 50,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
series: [
{
name: item.key + (form.type == 1 ? "次谐波电压" : "次谐波电流"),
data: res.data.historyData.map((item: any) => [
item.time,
Math.floor(item.value * 10000) / 10000,
]),
type: "line",
symbol: "none",
markLine: {
symbol: "none", // 去除箭头
label: {
show: false, // 隐藏标签
},
data: [
{
yAxis: "",
},
{
xAxis: "",
},
{
xAxis: "",
},
],
// 样式配置
lineStyle: {
color: "red",
type: "dashed", // 虚线
},
},
},
],
};
tabList.value[index].showEcahr = 1; //显示echart
} else {
ElMessage.warning(res.message);
tabList.value[index].showEcahr = 2;
}
loading.value = false;
})
.catch(() => {
tabList.value[index].showEcahr = 2;
loading.value = false;
});
};
const resDataId = ref("");
// 生成动态谐波责任数据
const generateFn = async () => {
loading1.value = true;
await getDynamicData({
lineId: dotList.value.id,
searchBeginTime: tabList.value[activeName.value].time[0],
searchEndTime: tabList.value[activeName.value].time[1],
time: tabList.value[activeName.value].key,
type: form.type,
userDataId: form.loadDataId,
})
.then((res: any) => {
if (res.code == "A0000") {
resDataId.value = res.data.responsibilityDataIndex;
tabList.value[activeName.value].dynamicData = res.data.responsibilities;
let [min, max] = yMethod(
res.data.datas.map((item: any) => item.valueDatas).flat()
);
let series: any[] = [];
let time: any[] = res.data.timeDatas.map((item: any) =>
timeFormat(item)
);
res.data.datas.forEach((item: any) => {
series.push({
name: item.customerName,
data: item.valueDatas.map((k: any, i: number) => [
time[i],
Math.floor(k * 10000) / 10000,
]),
type: "line",
symbol: "none",
});
});
tabList.value[activeName.value].dynamicOptions = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
// 刻度线样式
axisTick: {
lineStyle: {
color: "#fff", // 刻度线白色
},
},
// 轴线样式
axisLine: {
lineStyle: {
color: "#fff", // 轴线白色
},
},
},
tooltip: {
formatter(params: any) {
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
options: {
series: series,
},
};
tabList.value[activeName.value].showDynamic = true;
} else {
ElMessage.warning(res.message);
}
loading1.value = false;
})
.catch(() => {
loading1.value = false;
});
loading1.value = false;
};
// 生成指标
const generateMetrics = async () => {
if (tabList.value[activeName.value].form.limit == "")
return ElMessage.warning("请选择限值!");
if (tabList.value[activeName.value].form.time1 == "")
return ElMessage.warning("请选择时间一!");
if (tabList.value[activeName.value].form.time2 == "")
return ElMessage.warning("请选择时间二!");
loading1.value = true;
await getResponsibilityData({
limitEndTime: tabList.value[activeName.value].form.time2,
limitStartTime: tabList.value[activeName.value].form.time1,
limitValue: tabList.value[activeName.value].form.limit,
resDataId: resDataId.value,
time: tabList.value[activeName.value].key,
type: form.type,
})
.then((res: any) => {
tabList.value[activeName.value].dynamicData = res.data.responsibilities;
let [min, max] = yMethod(
res.data.datas.map((item: any) => item.valueDatas).flat()
);
let series: any[] = [];
let time: any[] = res.data.timeDatas.map((item: any) => timeFormat(item));
res.data.datas.forEach((item: any) => {
series.push({
name: item.customerName,
data: item.valueDatas.map((k: any, i: number) => [
time[i],
Math.floor(k * 10000) / 10000,
]),
type: "line",
smooth: true,
symbol: "none",
});
});
tabList.value[activeName.value].dynamicOptions = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
},
tooltip: {
formatter(params: any) {
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
options: {
series: series,
},
};
tabList.value[activeName.value].showDynamic = true;
})
.catch(() => {
loading1.value = false;
});
loading1.value = false;
};
// 监听echart点击
const group = (chart: any, myChartDom: any) => {
myChartDom.addEventListener("click", function (event: any) {
// 获取点击位置相对于图表容器的坐标
const rect = myChartDom.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const pointInPixel = [x, y];
// 转换为逻辑坐标(相对于图表坐标系)
const pointInGrid = chart.convertFromPixel({ gridIndex: 0 }, pointInPixel);
// 计算X轴和Y轴的对应数据
// 处理X轴数据分类轴
// 处理Y轴数据数值轴
let yValue = pointInGrid[1].toFixed(4);
// xValue = timeFormat(pointInGrid[0].toFixed(0) - 0)
if (code.value == 0) {
tabList.value[activeName.value].form.limit = yValue;
tabList.value[activeName.value].options.series[0].markLine.data[0].yAxis =
yValue;
chart.setOption(tabList.value[activeName.value].options);
} else if (code.value == 1) {
tabList.value[activeName.value].form.time1 = xValue.value;
tabList.value[activeName.value].options.series[0].markLine.data[1].xAxis =
xValue.value;
chart.setOption(tabList.value[activeName.value].options);
} else if (code.value == 2) {
tabList.value[activeName.value].form.time2 = xValue.value;
tabList.value[activeName.value].options.series[0].markLine.data[2].xAxis =
xValue.value;
chart.setOption(tabList.value[activeName.value].options);
}
// 控制台输出详细信息
// console.log('点击事件详情:', {
// X轴数据: xValue,
// Y轴数据: yValue
// })
});
};
onMounted(() => {
const dom = document.getElementById("navigation-splitpanes");
if (dom) {
size.value = Math.round((180 / dom.offsetHeight) * 100);
}
userDataList({
pageNum: 1,
pageSize: 10000,
searchValue: "",
}).then((res: any) => {
console.log(res.data);
loadDataOptions.value = res.data.records;
});
if (!dotList.value || !dotList.value.alias) {
dotList.value = { alias: "请选择监测点位" };
}
});
defineExpose({
openDialog,
});
</script>
<style scoped lang="scss">
@use "@/assets/scss/index.scss";
.box {
// height: calc((100vh - 190px) / 2);
height: 300px;
}
.boxTab {
display: flex;
}
.harmonicButton {
height: 42px;
margin-top: 10px;
display: flex;
justify-content: end;
align-items: center;
}
:deep(.el-tabs__content) {
height: calc(100vh - 265px);
}
:deep(.el-input-group__append, .el-input-group__prepend) {
background-color: #ffffff00;
padding: 0 17px;
}
:deep(.frontBox) {
background-color: var(--el-color-primary) !important;
color: #fff !important;
}
::v-deep(.el-tabs__item) {
color: #fff;
}
::v-deep(.el-tabs__item.is-active) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__nav-wrap::after) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__active-bar) {
color: var(--el-color-primary);
}
::v-deep(.splitpanes__splitter) {
background-color: rgba(44, 46, 60, 0.1) !important;
&:before {
background-color: #fff !important;
}
&:after {
background-color: #fffFFF80 !important;
}
}
::v-deep(.el-form-item) {
margin-bottom: 0px;
}
.currentPosition {
position: absolute;
top: -10px;
right: 10px;
font-size: 14px;
font-weight: 600;
}
</style>

View File

@@ -0,0 +1,255 @@
<template>
<!--谐波放大事件 -->
<el-dialog
:close-on-click-modal="false"
draggable
v-model="machineVisible"
:title="title"
width="1200px"
>
<div class="tableBox">
<div style="display: flex; justify-content: flex-end">
<el-button
type="primary"
:icon="Download"
@click="exportTable"
size="small"
>导出
</el-button>
</div>
<el-table
:scrollbar-always-on="true"
:data="tableData"
height="443px"
size="small"
v-loading="loading"
element-loading-background="#343849c7"
:header-cell-style="{ textAlign: 'center' }"
border
style="margin-top: 13px"
>
<el-table-column label="序号" align="center" type="index" width="70">
<template #default="scope">
<span>{{
(form.pageNum - 1) * form.pageSize + scope.$index + 1
}}</span>
</template>
</el-table-column>
<el-table-column
prop="monitorName"
align="center"
label="监测点名称"
width="120"
show-overflow-tooltip
/>
<el-table-column
prop="objName"
align="center"
label="用户"
show-overflow-tooltip
/>
<el-table-column
prop="startTime"
align="center"
label="开始时间"
width="140"
/>
<el-table-column
prop="endTime"
align="center"
label="结束时间"
width="140"
/>
<el-table-column
prop="harmonicCount"
align="center"
label="次数"
width="60"
/>
<el-table-column prop="phase" align="center" label="相别" width="60" />
<el-table-column
prop="duration"
align="center"
label="持续时间(min)"
width="100"
>
<template #default="scope">
{{ scope.row.duration / 60 }}
</template>
</el-table-column>
<el-table-column
prop="vavgValue"
align="center"
label="电压标准值"
width="80"
/>
<el-table-column
prop="iavgValue"
align="center"
label="电流标准值"
width="80"
>
<template #default="scope">
{{ Math.floor(scope.row.iavgValue * 100) / 100 }}
</template>
</el-table-column>
<el-table-column
prop="upScheme"
align="center"
label="处理措施"
show-overflow-tooltip
/>
<el-table-column prop="address" align="center" label="操作" width="60">
<template #default="scope">
<el-button
size="small"
type="primary"
link
@click="arendChart(scope.row)"
>趋势图</el-button
>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
size="small"
style="margin-top: 10px"
:currentPage="form.pageNum"
:page-size="form.pageSize"
:page-sizes="[10, 20, 50, 100, 200]"
background
:layout="'sizes,total, ->, prev, pager, next, jumper'"
:total="total"
@size-change="onTableSizeChange"
@current-change="onTableCurrentChange"
></el-pagination>
</el-dialog>
<TrendChart ref="trendChartRef" @close="machineVisible = true"></TrendChart>
</template>
<script lang="ts" setup>
import { ref, reactive, inject, nextTick } from "vue";
import { getDetail } from "@/api/manage_wx";
import TrendChart from "./trendChart.vue";
import { Download } from "@element-plus/icons-vue";
import * as XLSX from "xlsx";
const machineVisible = ref(false);
const title = ref("谐波放大事件");
const form = reactive({
searchBeginTime: "",
lineId: "",
pageNum: 1,
pageSize: 20,
});
const total = ref(0); // 假设总条数为100
const tableData = ref([]);
const loading = ref(false);
const trendChartRef = ref();
//form表单校验规则
const open = (text: string, time?: any, lineId?: any) => {
machineVisible.value = true;
form.lineId = lineId;
form.searchBeginTime = time;
nextTick(() => {
getTableData();
});
};
const onTableSizeChange = (size: number) => {
form.pageSize = size;
getTableData();
};
const onTableCurrentChange = (page: number) => {
form.pageNum = page;
getTableData();
};
const getTableData = async () => {
loading.value = true;
const res: any = await getDetail(form);
if (res.code == "A0000") {
tableData.value = res.data.records;
total.value = res.data.total;
}
loading.value = false;
};
// 趋势图
const arendChart = (row: any) => {
trendChartRef.value.open(row);
machineVisible.value = false;
};
// 导出
const exportTable = async () => {
let columnExpor: any = [
[
"监测点名称",
"用户",
"开始时间",
"结束时间",
"次数",
"相别",
"持续时间(min)",
"电压标准值",
"电流标准值",
],
];
let list = [];
await getDetail({
...form,
pageNum: 1,
pageSize: total.value,
}).then((res) => {
let data = res.data.records.map((item) => {
return [
item.monitorName,
item.objName,
item.startTime,
item.endTime,
item.harmonicCount,
item.phase,
item.duration / 60,
item.vavgValue,
Math.floor(item.iavgValue * 100) / 100,
];
});
list = [...columnExpor, ...data];
// 创建工作表
const worksheet = XLSX.utils.aoa_to_sheet(list);
worksheet["!cols"] = list.map((col) => ({ wch: 20 }));
// 创建工作簿
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "谐波放大事件");
// 写出文件
XLSX.writeFile(workbook, "谐波放大事件" + ".xlsx");
});
};
defineExpose({ open });
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.tableBox {
padding: 0px !important;
}
.formBox {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.formLeft {
display: flex;
align-items: center;
color: #fff;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,667 @@
<template>
<!-- 配网计算 -->
<splitpanes style="height: 100%" class="default-theme" id="navigation-splitpanes">
<pane :size="20">
<SystemTree :showSelect="false" @node-click="handleNodeClick" @init="handleNodeClick"></SystemTree>
</pane>
<pane style="background-color: rgba(44, 46, 60, 0.1)" :size="100 - size">
<el-form :model="form" inline label-width="auto">
<el-form-item label="谐波类型">
<el-radio-group v-model="form.type">
<el-radio-button label="谐波电压" size="small" value="1" />
<el-radio-button label="谐波电流" size="small" value="0" />
</el-radio-group>
</el-form-item>
<el-form-item label="谐波次数">
<el-select v-model="form.index" filterable multiple :multiple-limit="5" collapse-tags
collapse-tags-tooltip clearable placeholder="请选择次数" style="width: 200px" size="small">
<el-option v-for="item in 49" :key="item" :label="item + 1 + '次'" :value="item + 1"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<!-- <el-button type="primary" icon="el-icon-Plus" @click="push('/admin/division/aListOfLoadData')">
新增
</el-button> -->
<el-button type="primary" :icon="Select" @click="submit" size="small">确定</el-button>
</el-form-item>
</el-form>
<el-tabs v-model="activeName" v-if="showTabs" style="height: 740px">
<el-tab-pane v-for="(item, index) in tabList" :key="item" :label="item.label" :name="index"
style="height: 100%">
<div>
<div>
<span style="color: #fff">时间范围</span>
<el-date-picker v-model="item.time" class="mr10 ml10" type="daterange"
start-placeholder="起始时间" end-placeholder="结束时间" format="YYYY-MM-DD"
date-format="YYYY-MM-DD" time-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
:disabled-date="handleDisabledDate" size="small" />
<el-button type="primary" :icon="CaretRight" @click="execute(item, index)" size="small">
执行
</el-button>
</div>
<div v-if="item.showEcahr == 1" class="harmonicButton">
<el-form :inline="true" v-model="item.form">
<el-form-item label="限值" v-if="item.showDynamic">
<el-input v-model="item.form.limit" placeholder="请选择限值" disabled
style="width: 200px" size="small">
<template #append>
<el-button :icon="Edit" :class="[code == 0 ? 'frontBox' : '']"
@click="setCode(0)" size="small" />
</template>
</el-input>
</el-form-item>
<el-form-item label="时间点一" v-if="item.showDynamic">
<el-input v-model="item.form.time1" placeholder="请选择时间点一" disabled
style="width: 200px" size="small">
<template #append>
<el-button :icon="Edit" :class="[code == 1 ? 'frontBox' : '']"
@click="setCode(1)" size="small" />
</template>
</el-input>
</el-form-item>
<el-form-item label="时间点二" v-if="item.showDynamic">
<el-input v-model="item.form.time2" placeholder="请选择时间点二" disabled
style="width: 200px" size="small">
<template #append>
<el-button :icon="Edit" :class="[code == 2 ? 'frontBox' : '']"
@click="setCode(2)" size="small" />
</template>
</el-input>
</el-form-item>
<el-button type="primary" :icon="Document" @click="generateFn" v-if="!item.showDynamic"
size="small">
生成动态谐波责任数据
</el-button>
<el-button type="primary" :icon="Document" v-else @click="generateMetrics" size="small">
生成谐波责任指标
</el-button>
</el-form>
</div>
<div class="box" v-loading="loading" element-loading-background="#343849c7">
<MyEChart :options="item.options" v-if="item.showEcahr == 1" @group="group" />
<el-empty description="时间范围内无谐波数据" v-if="item.showEcahr == 2" />
</div>
<!-- 生成动态谐波责任数据 -->
<div class="box boxTab" v-loading="loading1" element-loading-background="#343849c7">
<MyEChart :options="item.dynamicOptions" style="flex: 1" v-if="item.showDynamic" />
<div style="width: 500px" v-if="item.showDynamic" class="tableBox">
<el-table :scrollbar-always-on="true" ref="tableRef" :data="item.dynamicData"
height="280px" size="small" :header-cell-style="{ textAlign: 'center' }" border>
<el-table-column prop="customerName" align="center" label="用户名(用户号)" />
<el-table-column prop="responsibilityData" align="center" label="责任数据(%)"
width="120">
<template #default="scope">
{{
Math.floor(scope.row.responsibilityData * 10000) /
10000
}}
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</el-tab-pane>
</el-tabs>
</pane>
</splitpanes>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive } from "vue";
import "splitpanes/dist/splitpanes.css";
import { Splitpanes, Pane } from "splitpanes";
import SystemTree from "@/components/tree/systemTree.vue";
import { Edit, Right } from "@element-plus/icons-vue";
import MyEChart from "@/components/echarts/MyEchart.vue";
import { timeFormat } from "@/utils/common";
import { yMethod } from "@/utils/echartMethod";
import { Select, CaretRight, Document } from "@element-plus/icons-vue";
import {
getHistoryHarmData,
getDynamicData,
getResponsibilityData,
} from "@/api/manage_wx";
const visible = ref(false);
const size = ref(0);
const dotList: any = ref({});
const form: any = reactive({
type: "1",
index: [],
});
const code = ref(3);
const xAxisData = ref([]);
const loading = ref(false);
const loading1 = ref(false);
const tabList: any = ref([]);
const activeName = ref(0);
const xValue = ref("");
const showTabs = ref(false);
// 设置时间
const timeFrame = ref(["", ""]);
const openDialog = () => {
visible.value = true;
};
const handleNodeClick = (data: any) => {
if (data.level == 6) {
dotList.value = data;
}
};
// 处理日期禁用逻辑
const handleDisabledDate = (date) => {
return date.getTime() > Date.now();
};
// 这是按钮变色
const setCode = (num: number) => {
if (code.value == num) {
return (code.value = 3);
}
code.value = num;
};
// 确定
const submit = () => {
if (form.index.length == 0) {
return ElMessage.warning("请选择谐波次数");
}
if (form.index.length == 0) {
showTabs.value = false;
} else {
showTabs.value = true;
let list = JSON.parse(JSON.stringify(form.index)).sort((a, b) => a - b);
tabList.value = [];
list.forEach((item: any) => {
tabList.value.push({
label: item + "次谐波",
key: item,
showExecute: false,
form: {
limit: "",
time1: "",
time2: "",
},
showEcahr: 3, //1显示echart 2显示无数据 3什么都没有
options: {},
dynamicOptions: {}, //动态echarts
dynamicList: {}, //动态echarts
showDynamic: false, //动态执行展示
});
});
code.value = 3;
activeName.value = 0;
}
};
// 执行
const execute = async (item: any, index: number) => {
tabList.value[activeName.value].showDynamic = false;
loading.value = true;
await getHistoryHarmData({
searchBeginTime: item.time[0],
searchEndTime: item.time[1],
type: form.type,
time: item.key,
lineId: dotList.value.id,
})
.then((res: any) => {
if (res.code == "A0000") {
let [min, max] = yMethod(
res.data.historyData.map((item: any) => item.value + 0.1)
);
xAxisData.value = res.data.historyData.map((item: any) => item.time);
tabList.value[index].options = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
// 刻度线样式
axisTick: {
lineStyle: {
color: "#fff", // 刻度线白色
},
},
// 轴线样式
axisLine: {
lineStyle: {
color: "#fff", // 轴线白色
},
},
},
tooltip: {
formatter(params: any) {
xValue.value = params[0].value[0];
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
right: 50,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
series: [
{
name: item.key + (form.type == 1 ? "次谐波电压" : "次谐波电流"),
data: res.data.historyData.map((item: any) => [
item.time,
Math.floor(item.value * 10000) / 10000,
]),
type: "line",
symbol: "none",
markLine: {
symbol: "none", // 去除箭头
label: {
show: false, // 隐藏标签
},
data: [
{
yAxis: "",
},
{
xAxis: "",
},
{
xAxis: "",
},
],
// 样式配置
lineStyle: {
color: "red",
type: "dashed", // 虚线
},
},
},
],
};
tabList.value[index].showEcahr = 1; //显示echart
} else {
ElMessage.warning(res.message);
tabList.value[index].showEcahr = 2;
}
loading.value = false;
})
.catch(() => {
tabList.value[index].showEcahr = 2;
loading.value = false;
});
};
const resDataId = ref("");
// 生成动态谐波责任数据
const generateFn = async () => {
loading1.value = true;
await getDynamicData({
lineId: dotList.value.id,
searchBeginTime: tabList.value[activeName.value].time[0],
searchEndTime: tabList.value[activeName.value].time[1],
time: tabList.value[activeName.value].key,
type: form.type,
})
.then((res: any) => {
if (res.code == "A0000") {
resDataId.value = res.data.responsibilityDataIndex;
tabList.value[activeName.value].dynamicData = res.data.responsibilities;
let [min, max] = yMethod(
res.data.datas.map((item: any) => item.valueDatas).flat()
);
let series: any[] = [];
let time: any[] = res.data.timeDatas.map((item: any) =>
timeFormat(item)
);
res.data.datas.forEach((item: any) => {
series.push({
name: item.customerName,
data: item.valueDatas.map((k: any, i: number) => [
time[i],
Math.floor(k * 10000) / 10000,
]),
type: "line",
smooth: true,
symbol: "none",
});
});
tabList.value[activeName.value].dynamicOptions = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
// 刻度线样式
axisTick: {
lineStyle: {
color: "#fff", // 刻度线白色
},
},
// 轴线样式
axisLine: {
lineStyle: {
color: "#fff", // 轴线白色
},
},
},
tooltip: {
formatter(params: any) {
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
options: {
series: series,
},
};
tabList.value[activeName.value].showDynamic = true;
} else {
ElMessage.warning(res.message);
}
loading1.value = false;
})
.catch(() => {
loading1.value = false;
});
loading1.value = false;
};
// 生成指标
const generateMetrics = async () => {
if (tabList.value[activeName.value].form.limit == "")
return ElMessage.warning("请选择限值!");
if (tabList.value[activeName.value].form.time1 == "")
return ElMessage.warning("请选择时间一!");
if (tabList.value[activeName.value].form.time2 == "")
return ElMessage.warning("请选择时间二!");
loading1.value = true;
await getResponsibilityData({
limitEndTime: tabList.value[activeName.value].form.time2,
limitStartTime: tabList.value[activeName.value].form.time1,
limitValue: tabList.value[activeName.value].form.limit,
resDataId: resDataId.value,
time: tabList.value[activeName.value].key,
type: form.type,
})
.then((res: any) => {
tabList.value[activeName.value].dynamicData = res.data.responsibilities;
let [min, max] = yMethod(
res.data.datas.map((item: any) => item.valueDatas).flat()
);
let series: any[] = [];
let time: any[] = res.data.timeDatas.map((item: any) => timeFormat(item));
res.data.datas.forEach((item: any) => {
series.push({
name: item.customerName,
data: item.valueDatas.map((k: any, i: number) => [
time[i],
Math.floor(k * 10000) / 10000,
]),
type: "line",
symbol: "none",
});
});
tabList.value[activeName.value].dynamicOptions = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
},
tooltip: {
formatter(params: any) {
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
options: {
series: series,
},
};
tabList.value[activeName.value].showDynamic = true;
})
.catch(() => {
loading1.value = false;
});
loading1.value = false;
};
// 监听echart点击
const group = (chart: any, myChartDom: any) => {
myChartDom.addEventListener("click", function (event: any) {
// 获取点击位置相对于图表容器的坐标
const rect = myChartDom.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const pointInPixel = [x, y];
// 转换为逻辑坐标(相对于图表坐标系)
const pointInGrid = chart.convertFromPixel({ gridIndex: 0 }, pointInPixel);
// 计算X轴和Y轴的对应数据
// 处理X轴数据分类轴
// 处理Y轴数据数值轴
let yValue = pointInGrid[1].toFixed(4);
// xValue = timeFormat(pointInGrid[0].toFixed(0) - 0)
if (code.value == 0) {
tabList.value[activeName.value].form.limit = yValue;
tabList.value[activeName.value].options.series[0].markLine.data[0].yAxis =
yValue;
chart.setOption(tabList.value[activeName.value].options);
} else if (code.value == 1) {
tabList.value[activeName.value].form.time1 = xValue.value;
tabList.value[activeName.value].options.series[0].markLine.data[1].xAxis =
xValue.value;
chart.setOption(tabList.value[activeName.value].options);
} else if (code.value == 2) {
tabList.value[activeName.value].form.time2 = xValue.value;
tabList.value[activeName.value].options.series[0].markLine.data[2].xAxis =
xValue.value;
chart.setOption(tabList.value[activeName.value].options);
}
// 控制台输出详细信息
// console.log('点击事件详情:', {
// X轴数据: xValue,
// Y轴数据: yValue
// })
});
};
onMounted(() => {
const dom = document.getElementById("navigation-splitpanes");
if (dom) {
size.value = Math.round((180 / dom.offsetHeight) * 100);
}
if (!dotList.value || !dotList.value.alias) {
dotList.value = { alias: "请选择监测点位" };
}
});
defineExpose({
openDialog,
});
</script>
<style scoped lang="scss">
@use "@/assets/scss/index.scss";
.box {
// height: calc((100vh - 190px) / 2);
height: 300px;
}
.boxTab {
display: flex;
}
.harmonicButton {
height: 42px;
margin-top: 10px;
display: flex;
justify-content: end;
align-items: center;
}
:deep(.el-tabs__content) {
height: calc(100vh - 265px);
}
:deep(.el-input-group__append, .el-input-group__prepend) {
background-color: #ffffff00;
padding: 0 17px;
}
:deep(.frontBox) {
background-color: var(--el-color-primary) !important;
color: #fff !important;
}
::v-deep(.el-tabs__item) {
color: #fff;
}
::v-deep(.el-tabs__item.is-active) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__nav-wrap::after) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__active-bar) {
color: var(--el-color-primary);
}
::v-deep(.splitpanes__splitter) {
background-color: rgba(44, 46, 60, 0.1) !important;
&:before {
background-color: #fff !important;
}
&:after {
background-color: #fffFFF80 !important;
}
}
::v-deep(.el-form-item) {
margin-bottom: 0px;
}
</style>

View File

@@ -0,0 +1,982 @@
<template>
<!-- 配网计算 -->
<splitpanes
style="height: 100%"
class="default-theme"
id="navigation-splitpanes"
>
<pane :size="20">
<PointTree
:showSelect="false"
@node-click="handleNodeClick"
@init="handleNodeClick"
></PointTree>
</pane>
<pane style="background-color: rgba(44, 46, 60, 0.1)" :size="100 - size">
<el-form :model="form" inline label-width="auto">
<el-form-item label="用采用户">
<el-tree-select
v-model="form.userList"
:data="dataTree"
multiple
filterable
show-checkbox
ref="treeRef"
:props="defaultProps"
node-key="id"
size="small"
popper-class="tree-select-popper"
:popper-append-to-body="false"
:default-expanded-keys="expandedKeys"
collapse-tags
collapse-tags-tooltip
clearable
class="wide-tree-select"
/>
</el-form-item>
<el-form-item label="谐波类型">
<el-radio-group v-model="form.type">
<el-radio-button label="谐波电压" size="small" value="1" />
<el-radio-button label="谐波电流" size="small" value="0" />
</el-radio-group>
</el-form-item>
<el-form-item label="谐波次数">
<el-select
v-model="form.index"
filterable
multiple
:multiple-limit="5"
collapse-tags
collapse-tags-tooltip
clearable
placeholder="请选择次数"
style="width: 200px"
size="small"
>
<el-option
v-for="item in 49"
:key="item"
:label="item + 1 + '次'"
:value="item + 1"
></el-option>
</el-select>
</el-form-item>
<!-- <el-form-item label="负荷数据">
<el-select v-model="form.loadDataId" clearable filterable placeholder="请选择负荷数据" style="width: 200px"
size="small">
<el-option v-for="item in loadDataOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item> -->
<el-form-item>
<!-- <el-button type="primary" icon="el-icon-Plus" @click="push('/admin/division/aListOfLoadData')">
新增
</el-button> -->
<el-button type="primary" :icon="Select" @click="submit" size="small"
>确定</el-button
>
</el-form-item>
</el-form>
<el-tabs v-model="activeName" v-if="showTabs" style="height: 740px">
<el-tab-pane
v-for="(item, index) in tabList"
:key="item"
:label="item.label"
:name="index"
style="height: 100%"
>
<div>
<div>
<span style="color: #fff">时间范围</span>
<el-date-picker
v-model="item.time"
class="mr10 ml10"
type="daterange"
start-placeholder="起始时间"
end-placeholder="结束时间"
format="YYYY-MM-DD"
date-format="YYYY-MM-DD"
time-format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
:disabled-date="handleDisabledDate"
size="small"
/>
<el-button
type="primary"
:icon="CaretRight"
@click="execute(item, index)"
size="small"
>
执行
</el-button>
</div>
<div v-if="item.showEcahr == 1" class="harmonicButton">
<el-form :inline="true" v-model="item.form">
<el-form-item label="限值" v-if="item.showDynamic">
<el-input
v-model="item.form.limit"
placeholder="请选择限值"
disabled
style="width: 200px"
size="small"
>
<template #append>
<el-button
:icon="Edit"
:class="[code == 0 ? 'frontBox' : '']"
@click="setCode(0)"
size="small"
/>
</template>
</el-input>
</el-form-item>
<el-form-item label="时间点一" v-if="item.showDynamic">
<el-input
v-model="item.form.time1"
placeholder="请选择时间点一"
disabled
style="width: 200px"
size="small"
>
<template #append>
<el-button
:icon="Edit"
:class="[code == 1 ? 'frontBox' : '']"
@click="setCode(1)"
size="small"
/>
</template>
</el-input>
</el-form-item>
<el-form-item label="时间点二" v-if="item.showDynamic">
<el-input
v-model="item.form.time2"
placeholder="请选择时间点二"
disabled
style="width: 200px"
size="small"
>
<template #append>
<el-button
:icon="Edit"
:class="[code == 2 ? 'frontBox' : '']"
@click="setCode(2)"
size="small"
/>
</template>
</el-input>
</el-form-item>
<el-button
type="primary"
:icon="Document"
@click="generateFn"
v-if="!item.showDynamic"
size="small"
>
生成动态谐波责任数据
</el-button>
<el-button
type="primary"
:icon="Document"
v-else
@click="generateMetrics"
size="small"
>
生成谐波责任指标
</el-button>
</el-form>
</div>
<div
class="box"
v-loading="loading"
element-loading-background="#343849c7"
>
<MyEChart
:options="item.options"
v-if="item.showEcahr == 1"
@group="group"
/>
<el-empty
description="时间范围内无谐波数据"
v-if="item.showEcahr == 2"
/>
</div>
<!-- 生成动态谐波责任数据 -->
<div
class="box boxTab"
v-loading="loading1"
element-loading-background="#343849c7"
>
<MyEChart
:options="item.dynamicOptions"
style="flex: 1"
v-if="item.showDynamic"
/>
<div
style="width: 500px"
v-if="item.showDynamic"
class="tableBox"
>
<el-table
:scrollbar-always-on="true"
ref="tableRef"
:data="item.dynamicData"
height="280px"
size="small"
:header-cell-style="{ textAlign: 'center' }"
border
>
<el-table-column
prop="customerName"
align="center"
label="用户名(用户号)"
/>
<el-table-column
prop="responsibilityData"
align="center"
label="责任数据(%)"
width="120"
>
<template #default="scope">
{{
Math.floor(scope.row.responsibilityData * 10000) / 10000
}}
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</el-tab-pane>
</el-tabs>
</pane>
</splitpanes>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive, watch } from "vue";
import "splitpanes/dist/splitpanes.css";
import { Splitpanes, Pane } from "splitpanes";
import PointTree from "@/components/tree/pointTree.vue";
import { Edit, Right } from "@element-plus/icons-vue";
import MyEChart from "@/components/echarts/MyEchart.vue";
import { timeFormat } from "@/utils/common";
import { yMethod } from "@/utils/echartMethod";
import { Select, CaretRight, Document } from "@element-plus/icons-vue";
import {
userDataList,
getHistoryHarmData,
getDynamicData,
getResponsibilityData,
getTerminalTreeForFive,
} from "@/api/manage_wx";
import { useStore } from "vuex";
const store = useStore();
const emit = defineEmits(["setTitle", "init"]); // 打开弹窗
const visible = ref(false);
const size = ref(0);
const dotList: any = ref({});
const form: any = reactive({
type: "1",
index: [],
userList: [],
});
const loadDataOptions: any = ref([]);
const code = ref(3);
const xAxisData = ref([]);
const loading = ref(false);
const loading1 = ref(false);
const tabList: any = ref([]);
const activeName = ref(0);
const xValue = ref("");
const showTabs = ref(false);
// 设置时间
const timeFrame = ref(["", ""]);
const openDialog = () => {
visible.value = true;
};
// 添加已选择的节点ID列表
const selectedNodeIds = ref<string[]>([]);
const handleNodeClick = (data: any) => {
if (data.level == 6) {
dotList.value = data;
setTimeout(() => {
emit("setTitle", data.alias);
}, 0);
// 保存选择的节点ID
if (data.id) {
selectedNodeIds.value = [data.id];
}
// 如果右侧已经选择了这个节点,则从右侧选择中移除
const selectedUserIdIndex = form.userList.indexOf(data.id);
if (selectedUserIdIndex > -1) {
// 创建新数组以触发视图更新
form.userList = form.userList.filter((id: string) => id !== data.id);
}
}
};
// 添加一个方法来更新树节点的禁用状态
const updateTreeDataDisabledState = () => {
if (!dataTree.value || dataTree.value.length === 0) return;
const disableNodes = (nodes: any[]) => {
if (!nodes || nodes.length === 0) return;
nodes.forEach((node) => {
// 如果节点ID在已选择列表中则禁用
if (selectedNodeIds.value.includes(node.id)) {
node.disabled = true;
} else {
// 如果之前被禁用但现在不在选择列表中,则启用
if (node.hasOwnProperty("disabled")) {
node.disabled = false;
}
}
// 递归处理子节点
if (node.children && node.children.length > 0) {
disableNodes(node.children);
}
});
};
disableNodes(dataTree.value);
};
// 监听 selectedNodeIds 的变化,更新树节点状态
watch(
selectedNodeIds,
() => {
updateTreeDataDisabledState();
},
{ deep: true }
);
// 处理日期禁用逻辑
const handleDisabledDate = (date) => {
// 定义时间边界
const startLimit = new Date(timeFrame.value[0]).getTime() - 86400000; //向前推1天
const endLimit = new Date(timeFrame.value[1]).setHours(23, 59, 59, 999);
// 如果日期不存在(选择今天时可能出现),不禁用
if (!date) return false;
// 禁用 2025-08-01 之前和 2025-08-31 之后的日期
return date.getTime() < startLimit || date.getTime() > endLimit;
};
// 这是按钮变色
const setCode = (num: number) => {
if (code.value == num) {
return (code.value = 3);
}
code.value = num;
};
// 确定
const submit = () => {
if (form.index.length == 0) {
return ElMessage.warning("请选择谐波次数");
}
if (form.index.length == 0) {
showTabs.value = false;
} else {
showTabs.value = true;
let list = JSON.parse(JSON.stringify(form.index)).sort((a, b) => a - b);
tabList.value = [];
list.forEach((item: any) => {
tabList.value.push({
label: item + "次谐波",
key: item,
showExecute: false,
form: {
limit: "",
time1: "",
time2: "",
},
showEcahr: 3, //1显示echart 2显示无数据 3什么都没有
options: {},
dynamicOptions: {}, //动态echarts
dynamicList: {}, //动态echarts
showDynamic: false, //动态执行展示
});
});
code.value = 3;
activeName.value = 0;
}
};
// 执行
const execute = async (item: any, index: number) => {
if (item.time == undefined) {
return ElMessage.warning("请选择时间范围");
}
tabList.value[activeName.value].showDynamic = false;
loading.value = true;
await getHistoryHarmData({
searchBeginTime: item.time[0],
searchEndTime: item.time[1],
type: form.type,
time: item.key,
lineId: dotList.value.id,
})
.then((res: any) => {
if (res.code == "A0000") {
let [min, max] = yMethod(
res.data.historyData.map((item: any) => item.value + 0.1)
);
xAxisData.value = res.data.historyData.map((item: any) => item.time);
tabList.value[index].options = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
// 刻度线样式
axisTick: {
lineStyle: {
color: "#fff", // 刻度线白色
},
},
// 轴线样式
axisLine: {
lineStyle: {
color: "#fff", // 轴线白色
},
},
},
tooltip: {
formatter(params: any) {
xValue.value = params[0].value[0];
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
right: 50,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
series: [
{
name: item.key + (form.type == 1 ? "次谐波电压" : "次谐波电流"),
data: res.data.historyData.map((item: any) => [
item.time,
Math.floor(item.value * 10000) / 10000,
]),
type: "line",
symbol: "none",
markLine: {
symbol: "none", // 去除箭头
label: {
show: false, // 隐藏标签
},
data: [
{
yAxis: "",
},
{
xAxis: "",
},
{
xAxis: "",
},
],
// 样式配置
lineStyle: {
color: "red",
type: "dashed", // 虚线
},
},
},
],
};
tabList.value[index].showEcahr = 1; //显示echart
} else {
ElMessage.warning(res.message);
tabList.value[index].showEcahr = 2;
}
loading.value = false;
})
.catch(() => {
tabList.value[index].showEcahr = 2;
loading.value = false;
});
};
const resDataId = ref("");
// 生成动态谐波责任数据
const generateFn = async () => {
if (form.userList.length == 0) {
return ElMessage.warning("请选择用采用户");
}
loading1.value = true;
await getDynamicData({
userDataId: "123",
lineId: dotList.value.id,
searchBeginTime: tabList.value[activeName.value].time[0],
searchEndTime: tabList.value[activeName.value].time[1],
time: tabList.value[activeName.value].key,
type: form.type,
userList: form.userList,
systemType: 1,
})
.then((res: any) => {
if (res.code == "A0000") {
resDataId.value = res.data.responsibilityDataIndex;
tabList.value[activeName.value].dynamicData = res.data.responsibilities;
let [min, max] = yMethod(
res.data.datas.map((item: any) => item.valueDatas).flat()
);
let series: any[] = [];
let time: any[] = res.data.timeDatas.map((item: any) =>
timeFormat(item)
);
res.data.datas.forEach((item: any) => {
series.push({
name: item.customerName,
data: item.valueDatas.map((k: any, i: number) => [
time[i],
Math.floor(k * 10000) / 10000,
]),
type: "line",
symbol: "none",
});
});
tabList.value[activeName.value].dynamicOptions = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
// 刻度线样式
axisTick: {
lineStyle: {
color: "#fff", // 刻度线白色
},
},
// 轴线样式
axisLine: {
lineStyle: {
color: "#fff", // 轴线白色
},
},
},
tooltip: {
formatter(params: any) {
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
options: {
series: series,
},
};
tabList.value[activeName.value].showDynamic = true;
} else {
ElMessage.warning(res.message);
}
loading1.value = false;
})
.catch(() => {
loading1.value = false;
});
loading1.value = false;
};
// 生成指标
const generateMetrics = async () => {
if (tabList.value[activeName.value].form.limit == "")
return ElMessage.warning("请选择限值!");
if (tabList.value[activeName.value].form.time1 == "")
return ElMessage.warning("请选择时间一!");
if (tabList.value[activeName.value].form.time2 == "")
return ElMessage.warning("请选择时间二!");
loading1.value = true;
await getResponsibilityData({
limitEndTime: tabList.value[activeName.value].form.time2,
limitStartTime: tabList.value[activeName.value].form.time1,
limitValue: tabList.value[activeName.value].form.limit,
resDataId: resDataId.value,
time: tabList.value[activeName.value].key,
type: form.type,
})
.then((res: any) => {
tabList.value[activeName.value].dynamicData = res.data.responsibilities;
let [min, max] = yMethod(
res.data.datas.map((item: any) => item.valueDatas).flat()
);
let series: any[] = [];
let time: any[] = res.data.timeDatas.map((item: any) => timeFormat(item));
res.data.datas.forEach((item: any) => {
series.push({
name: item.customerName,
data: item.valueDatas.map((k: any, i: number) => [
time[i],
Math.floor(k * 10000) / 10000,
]),
type: "line",
smooth: true,
symbol: "none",
});
});
tabList.value[activeName.value].dynamicOptions = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
},
tooltip: {
formatter(params: any) {
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
options: {
series: series,
},
};
tabList.value[activeName.value].showDynamic = true;
})
.catch(() => {
loading1.value = false;
});
loading1.value = false;
};
// 监听echart点击
const group = (chart: any, myChartDom: any) => {
myChartDom.addEventListener("click", function (event: any) {
// 获取点击位置相对于图表容器的坐标
const rect = myChartDom.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const pointInPixel = [x, y];
// 转换为逻辑坐标(相对于图表坐标系)
const pointInGrid = chart.convertFromPixel({ gridIndex: 0 }, pointInPixel);
// 计算X轴和Y轴的对应数据
// 处理X轴数据分类轴
// 处理Y轴数据数值轴
let yValue = pointInGrid[1].toFixed(4);
// xValue = timeFormat(pointInGrid[0].toFixed(0) - 0)
if (code.value == 0) {
tabList.value[activeName.value].form.limit = yValue;
tabList.value[activeName.value].options.series[0].markLine.data[0].yAxis =
yValue;
chart.setOption(tabList.value[activeName.value].options);
} else if (code.value == 1) {
tabList.value[activeName.value].form.time1 = xValue.value;
tabList.value[activeName.value].options.series[0].markLine.data[1].xAxis =
xValue.value;
chart.setOption(tabList.value[activeName.value].options);
} else if (code.value == 2) {
tabList.value[activeName.value].form.time2 = xValue.value;
tabList.value[activeName.value].options.series[0].markLine.data[2].xAxis =
xValue.value;
chart.setOption(tabList.value[activeName.value].options);
}
// 控制台输出详细信息
// console.log('点击事件详情:', {
// X轴数据: xValue,
// Y轴数据: yValue
// })
});
};
onMounted(() => {
const dom = document.getElementById("navigation-splitpanes");
if (dom) {
size.value = Math.round((180 / dom.offsetHeight) * 100);
}
userDataList({
pageNum: 1,
pageSize: 10000,
searchValue: "",
}).then((res: any) => {
console.log(res.data);
loadDataOptions.value = res.data.records;
});
if (!dotList.value || !dotList.value.alias) {
dotList.value = { alias: "请选择监测点位" };
}
});
const formData = ref({
deptIndex: store.state.deptId,
});
const dataTree = ref([]);
const expandedKeys = ref([]);
const defaultProps = {
label: "name",
value: "id",
disabled: "disabled",
};
const treeRef = ref();
const loadData = () => {
let form = JSON.parse(JSON.stringify(formData.value));
getTerminalTreeForFive(form).then((res) => {
console.log(res);
res.data = [
{
name: "电网拓扑",
level: -1,
id: 0,
children: res.data,
},
];
// 查找第一层级的最后一个子节点
const firstLevelChildren = res.data[0].children;
if (firstLevelChildren && firstLevelChildren.length > 0) {
let flag = true;
// 设置节点别名
res.data.forEach((item: any) => {
item.children.forEach((item2: any) => {
item2.children.forEach((item3: any) => {
item3.children.forEach((item4: any) => {
item4.children.forEach((item5: any) => {
if (item5.level == 7) {
item5.children.forEach((item6: any) => {
item6.disabled = false;
item6.alias = `${item.name}>${item2.name}>${item3.name}>${item4.name}>${item5.name}>${item6.name}`;
});
} else {
if (flag) {
expandedKeys.value = [item5.id];
treeRef.value.setCurrentKey(item5.id);
emit("init", item5);
flag = false;
}
item5.disabled = false;
item5.alias = `${item.name}>${item2.name}>${item3.name}>${item4.name}>${item5.name}`;
}
});
});
});
});
});
}
dataTree.value = res.data;
// 更新禁用状态
updateTreeDataDisabledState();
});
};
loadData();
defineExpose({
openDialog,
});
</script>
<style scoped lang="scss">
@use "@/assets/scss/index.scss";
.box {
// height: calc((100vh - 190px) / 2);
height: 300px;
}
.boxTab {
display: flex;
}
.harmonicButton {
height: 42px;
margin-top: 10px;
display: flex;
justify-content: end;
align-items: center;
}
:deep(.el-tabs__content) {
height: calc(100vh - 265px);
}
:deep(.el-input-group__append, .el-input-group__prepend) {
background-color: #ffffff00;
padding: 0 17px;
}
:deep(.frontBox) {
background-color: var(--el-color-primary) !important;
color: #fff !important;
}
::v-deep(.el-tabs__item) {
color: #fff;
}
::v-deep(.el-tabs__item.is-active) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__nav-wrap::after) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__active-bar) {
color: var(--el-color-primary);
}
::v-deep(.splitpanes__splitter) {
background-color: rgba(44, 46, 60, 0.1) !important;
&:before {
background-color: #fff !important;
}
&:after {
background-color: #ffffff80 !important;
}
}
::v-deep(.el-form-item) {
margin-bottom: 0px;
}
.currentPosition {
position: absolute;
top: -10px;
right: 10px;
font-size: 14px;
font-weight: 600;
}
// 树选择器下拉面板样式
::v-deep(.tree-select-popper) {
.el-select-dropdown__wrap {
width: 400px; /* 设置你想要的宽度 */
}
}
::v-deep(.el-tag__content){
max-width: 90px;
}
/* 树选择器本身的宽度控制 */
::v-deep(.wide-tree-select) {
width: 200px;
}
</style>

View File

@@ -0,0 +1,230 @@
<!-- 详情 -->
<template>
<el-dialog v-model="visible" :close-on-click-modal="false" title="详情" draggable width="1600px"
@close="handleCloseDialog" style="height: 800px">
<div class="default-main">
<div class="title_1">
<span class="monitoring-point">
{{ props.detailsQuery.name || "" }}</span>
</div>
<el-tabs v-model="activeName" @tab-change="generateFn">
<el-tab-pane v-for="(item, index) in tabList" :key="index" :label="item.name + '次谐波'" style="height: 100%"
:name="index" v-loading="loading">
<div style="height: 680px; overflow-y: auto">
<div class="box mb10" v-for="(value, i) in item.dynamicOptions" :key="i">
<div class="boxTab">
<MyEChart :options="item.dynamicOptions[i]" style="flex: 1" :style="{
height: `calc((680px) / ${item.list.length == 0
? 1
: item.list.length > 1
? 2
: item.list.length
} - 21px)`,
}" />
<div style="width: 500px" class="tableBox">
<el-table ref="tableRef" :data="item.list[i]" size="small" :height="`calc((600px) / ${item.list.length == 0
? 1
: item.list.length > 1
? 2
: item.list.length
} - 21px)`" :header-cell-style="{ textAlign: 'center' }" border>
<el-table-column prop="customerName" align="center" label="用户名(用户号)" />
<el-table-column prop="responsibilityData" align="center" label="责任数据(%)" width="120">
<template #default="scope">
{{
Math.floor(scope.row.responsibilityData * 10000) / 10000
}}
</template>
</el-table-column>
</el-table>
</div>
</div>
<el-divider />
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, nextTick } from "vue";
import MyEChart from "@/components/echarts/MyEchart.vue";
import { timeFormat } from "@/utils/common";
import { yMethod } from "@/utils/echartMethod";
import { displayHistoryData } from "@/api/manage_wx";
const props = defineProps<{
detailsQuery?: any; // 根据实际类型调整
}>();
// const height = mainHeight(155);
const activeName = ref(0);
const tabList: any = ref([]);
const visible: any = ref(false);
const loading: any = ref(false);
const init = () => {
let data =
(Array.isArray(props.detailsQuery.time)
? props.detailsQuery.time[0]
: props.detailsQuery.time
)?.split(",") ?? [];
tabList.value = [];
data.forEach((item: any) => {
tabList.value.push({
name: item,
dynamicOptions: [],
list: [],
});
});
activeName.value = 0;
generateFn(0);
};
// 生成动态谐波责任数据
const generateFn = async (e: any) => {
if (tabList.value[e].dynamicOptions.length != 0) return;
loading.value = true;
await displayHistoryData({
id: props.detailsQuery.id,
time: tabList.value[e].name,
})
.then((res: any) => {
res.data.forEach((item: any) => {
tabList.value[e].list.push(item.responsibilities);
let [min, max] = yMethod(
item.datas.map((k: any) => k.valueDatas).flat()
);
let series: any[] = [];
let time: any[] = item.timeDatas.map((k: any) => timeFormat(k));
item.datas.forEach((k: any) => {
series.push({
name: k.customerName,
data: k.valueDatas.map((k: any, i: number) => [
time[i],
Math.floor(k * 10000) / 10000,
]),
type: "line",
symbol: "none",
smooth: true,
});
});
tabList.value[e].dynamicOptions.push({
title: {
text: `时间:${item.limitSTime}${item.limitETime} 限值:${item.limitValue}`,
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
// 刻度线样式
axisTick: {
lineStyle: {
color: "#fff", // 刻度线白色
},
},
// 轴线样式
axisLine: {
lineStyle: {
color: "#fff", // 轴线白色
},
},
},
tooltip: {
formatter(params: any) {
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
},
legend: {
show: false,
},
yAxis: {
min: min,
max: max,
},
toolbox: {
show: false,
},
options: {
series: series,
},
});
});
})
.catch(() => {
loading.value = false;
});
loading.value = false;
};
const openDialog = () => {
visible.value = true;
if (props.detailsQuery) {
nextTick(() => {
init();
});
}
};
const handleCloseDialog = () => {
visible.value = false;
};
// onMounted(() => {
// init();
// });
defineExpose({
openDialog,
});
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.title_1 {
position: absolute;
right: 10px;
top: 65px;
font-size: 14px;
font-weight: 600;
color: #fff;
}
.monitoring-point {
font-size: 14px;
font-weight: 600;
}
.boxTab {
display: flex;
}
</style>

View File

@@ -0,0 +1,653 @@
<!-- 谐波放大表格详情 -->
<template>
<el-dialog v-model="visible" :close-on-click-modal="false" title="趋势图" draggable width="70%"
@close="handleCloseDialog">
<el-radio-group v-model="condition" size="small" @change="init">
<el-radio-button label="谐波电压" value="42" />
<el-radio-button label="谐波电流" value="43" />
</el-radio-group>
<MyEChart v-loading="loading" element-loading-background="rgba(122, 122, 122, 0.8)" :options="list[0]?.option"
:style="`height:670px`" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import MyEChart from "@/components/echarts/MyEchart.vue";
import { getHistoryResult } from "@/api/manage_wx/index";
import { yMethod } from "@/utils/echartMethod";
const emit = defineEmits(["close"]);
const visible = ref(false);
const loading = ref(false);
const options = ref<any>(null);
const list = ref<any>([]);
const traceability = ref<any>([]);
const rowData: any = ref({});
const condition: any = ref('42');
const open = async (row: any) => {
rowData.value = row;
condition.value = '42';
visible.value = true;
init()
};
const init = async () => {
list.value = [];
loading.value = true;
await getHistoryResult({
lineId: [rowData.value.monitorId],
searchBeginTime: rowData.value.startTime,
searchEndTime: rowData.value.endTime,
valueType: 4,
harmonic: rowData.value.harmonicCount,
ptType: 0,
condition: [condition.value],
}).then((res) => {
shujuchuli(res);
});
}
const shujuchuli = (res: any) => {
list.value = [];
let shujuData = res.data;
shujuData.forEach((item: any, i: number) => {
//判断是否存在暂降点
if (item.eventDetail == null) {
let [min, max] = yMethod([item.minValue, item.maxValue, condition.value == '42' ? rowData.value.vavgValue : rowData.value.vavgValue.iavgValue]
);
//判断是否有限值(有上下限)
if (item.topLimit !== 0 && item.lowerLimit !== 0) {
item.phaiscType.push("上限");
item.phaiscType.push("下限");
if (item.minValue !== null && item.maxValue !== null) {
//最小值等于下限值
//图列为A,B,C,上限,下限
if (item.phaiscType.length == 5) {
let avalue = [];
let bvalue = [];
let cvalue = [];
let topLimit = [];
let lowerLimit = [];
item.maxValue = item.topLimit;
item.minValue = item.lowerLimit;
//判断数据是否存在
if (item.value !== null) {
for (let j = 0; j < item.value.length; j++) {
//判断存在缺失值a
if (item.value[j][1] != undefined) {
avalue.push([item.value[j][0], item.value[j][1].toFixed(3)]);
} else {
avalue.push([]);
}
//判断存在缺失值b
if (item.value[j][2] != undefined) {
bvalue.push([item.value[j][0], item.value[j][2].toFixed(3)]);
} else {
bvalue.push([]);
}
//判断存在缺失值c
if (item.value[j][3] != undefined) {
cvalue.push([item.value[j][0], item.value[j][3].toFixed(3)]);
} else {
cvalue.push([]);
}
//上下限值
topLimit.push([item.value[j][0], item.topLimit.toFixed(3)]);
lowerLimit.push([item.value[j][0], item.lowerLimit.toFixed(3)]);
}
//数据为空
} else {
avalue = [];
bvalue = [];
cvalue = [];
}
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
maxValue: max,
minValue: min,
avalue: avalue,
bvalue: bvalue,
cvalue: cvalue,
topLimit: topLimit,
lowerLimit: lowerLimit,
};
list.value.push(shuju);
}
//图列为频率等,上限,下限
if (item.phaiscType.length == 3) {
let gvalue = [];
let topLimit = [];
let lowerLimit = [];
item.maxValue = item.topLimit;
item.minValue = item.lowerLimit;
//判断数据是否存在
if (item.value !== null) {
for (let j = 0; j < item.value.length; j++) {
//判断存在缺失值a
if (item.value[j][1] != undefined) {
gvalue.push([item.value[j][0], item.value[j][1].toFixed(3)]);
} else {
gvalue.push([]);
}
//上下限值
topLimit.push([item.value[j][0], item.topLimit.toFixed(3)]);
lowerLimit.push([item.value[j][0], item.lowerLimit.toFixed(3)]);
}
//数据为空
} else {
gvalue = [];
}
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
maxValue: max,
minValue: min,
gvalue: gvalue,
topLimit: topLimit,
lowerLimit: lowerLimit,
chufa: [],
};
list.value.push(shuju);
}
} else {
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
minValue: null,
avalue: [],
bvalue: [],
cvalue: [],
topLimit: [],
lowerLimit: [],
chufa: [],
};
list.value.push(shuju);
}
}
//有上限值
if (item.topLimit !== 0 && item.lowerLimit == 0) {
item.phaiscType.push("上限");
if (item.minValue !== null) {
//最小值等于下限值
//图列为A,B,C,上限
if (item.phaiscType.length == 4) {
let avalue = [];
let bvalue = [];
let cvalue = [];
let topLimit = [];
item.maxValue = item.topLimit;
// item.minValue=item.lowerLimit
//判断数据是否存在
if (item.value !== null) {
for (let j = 0; j < item.value.length; j++) {
//判断存在缺失值a
if (item.value[j][1] != undefined) {
avalue.push([item.value[j][0], item.value[j][1].toFixed(3)]);
} else {
avalue.push([]);
}
//判断存在缺失值b
if (item.value[j][2] != undefined) {
bvalue.push([item.value[j][0], item.value[j][2].toFixed(3)]);
} else {
bvalue.push([]);
}
//判断存在缺失值c
if (item.value[j][3] != undefined) {
cvalue.push([item.value[j][0], item.value[j][3].toFixed(3)]);
} else {
cvalue.push([]);
}
//上限值
topLimit.push([item.value[j][0], item.topLimit.toFixed(3)]);
}
//数据为空
} else {
avalue = [];
bvalue = [];
cvalue = [];
}
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
maxValue: max,
minValue: min,
avalue: avalue,
bvalue: bvalue,
cvalue: cvalue,
topLimit: topLimit,
};
list.value.push(shuju);
}
//图列为频率等,上限
if (item.phaiscType.length == 2) {
let gvalue = [];
let topLimit = [];
item.maxValue = item.topLimit;
// item.minValue=item.lowerLimit
//判断数据是否存在
if (item.value !== null) {
for (let j = 0; j < item.value.length; j++) {
//判断存在缺失值a
if (item.value[j][1] != undefined) {
gvalue.push([item.value[j][0], item.value[j][1].toFixed(3)]);
} else {
gvalue.push([]);
}
//上限值
topLimit.push([item.value[j][0], item.topLimit.toFixed(3)]);
}
//数据为空
} else {
gvalue = [];
}
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
minValue: min,
maxValue: max,
gvalue: gvalue,
topLimit: topLimit,
};
list.value.push(shuju);
}
} else {
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
minValue: null,
avalue: [],
bvalue: [],
cvalue: [],
topLimit: [],
lowerLimit: [],
chufa: [],
};
list.value.push(shuju);
}
}
//无限值
if (item.topLimit == 0 && item.lowerLimit == 0) {
if (item.minValue !== null) {
//最小值等于下限值
//图列为A,B,C
if (item.phaiscType.length == 3) {
let avalue = [];
let bvalue = [];
let cvalue = [];
//判断数据是否存在
if (item.value !== null) {
for (let j = 0; j < item.value.length; j++) {
//判断存在缺失值a
if (item.value[j][1] != undefined) {
avalue.push([item.value[j][0], item.value[j][1].toFixed(3)]);
} else {
avalue.push([]);
}
//判断存在缺失值b
if (item.value[j][2] != undefined) {
bvalue.push([item.value[j][0], item.value[j][2].toFixed(3)]);
} else {
bvalue.push([]);
}
//判断存在缺失值c
if (item.value[j][3] != undefined) {
cvalue.push([item.value[j][0], item.value[j][3].toFixed(3)]);
} else {
cvalue.push([]);
}
}
//数据为空
} else {
avalue = [];
bvalue = [];
cvalue = [];
}
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
minValue: min,
maxValue: max,
avalue: avalue,
bvalue: bvalue,
cvalue: cvalue,
};
list.value.push(shuju);
}
//图列为频率等
if (item.phaiscType.length == 1) {
let gvalue = [];
//判断数据是否存在
if (item.value !== null) {
for (let j = 0; j < item.value.length; j++) {
//判断存在缺失值a
if (item.value[j][1] != undefined) {
gvalue.push([item.value[j][0], item.value[j][1].toFixed(3)]);
} else {
gvalue.push([]);
}
}
//数据为空
} else {
gvalue = [];
}
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
minValue: min,
maxValue: max,
gvalue: gvalue,
};
list.value.push(shuju);
}
} else {
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
minValue: null,
avalue: [],
bvalue: [],
cvalue: [],
topLimit: [],
lowerLimit: [],
chufa: [],
};
list.value.push(shuju);
}
}
}
});
rendering();
};
const rendering = () => {
list.value.forEach((item: any) => {
let opitonserise: any[] = [];
item.legend.forEach((item2: any) => {
if (
item.avalue !== undefined &&
(item2 == "A相" || item2 == "AB相" || item2 == "零序电压")
) {
let data = {
name: item2,
symbol: "none",
symbolSize: 5,
type: "line",
smooth: true,
itemStyle: {
normal: {
color: "#DAA520",
},
},
data: item.avalue,
};
opitonserise.push(data);
} else if (
item.bvalue !== undefined &&
(item2 == "B相" || item2 == "BC相" || item2 == "正序电压")
) {
let data = {
name: item2,
symbol: "none",
symbolSize: 5,
type: "line",
smooth: true,
itemStyle: {
normal: {
color: "#2E8B57",
},
},
data: item.bvalue,
};
opitonserise.push(data);
} else if (
item.cvalue !== undefined &&
(item2 == "C相" || item2 == "CA相" || item2 == "负序电压")
) {
let data = {
name: item2,
symbol: "none",
symbolSize: 5,
type: "line",
smooth: true,
barWidth: 22,
itemStyle: {
normal: {
color: "#A52a2a",
},
},
data: item.cvalue,
};
opitonserise.push(data);
}
});
if (item.valueName == undefined) {
item.valueName = "无";
}
opitonserise[0].markLine = {
itemStyle: {
normal: {
lineStyle: {
type: "dashed", //dotted、solid
color: "#FF33FF",
width: 2,
},
},
},
label: {
normal: {
color: "#fff",
formatter: function (params) {
return `标准值`;
},
},
},
data: [
{
name: "标准值",
yAxis: condition.value == '42' ? rowData.value.vavgValue : rowData.value.iavgValue,
},
],
};
// console.log("🚀 ~ rendering ~ row.value:", rowData.value);
// console.log("🚀 ~ rendering ~ opitonserise:", opitonserise);
item.serise = opitonserise;
});
getEcharts();
};
const getEcharts = () => {
list.value.forEach((item: any, i: number) => {
console.log("🚀 ~ getEcharts ~ item:", item)
item.option = {
backgroundColor: "#fff",
title: {
left: "center",
text: item.title,
},
tooltip: {
top: "10px",
trigger: "axis",
borderColor: "grey",
style: {
color: "#fff",
fontSize: "15px",
padding: 10,
},
formatter: function (params) {
// console.log(params)
let tips = "";
tips += "时刻:" + params[0].data[0] + "</br/>";
for (let i = 0; i < params.length; i++) {
if (params[i].seriesName != "暂降触发点") {
tips +=
params[i].marker +
params[i].seriesName +
":" +
(params[i].value[1] - 0).toFixed(2) +
"<br/>";
}
}
return tips;
},
// axisPointer: {
// type: "cross",
// label: {
// color: "#fff",
// fontSize: 16,
// },
// },
textStyle: {
color: "#fff",
fontStyle: "normal",
opacity: 0.35,
fontSize: 14,
},
backgroundColor: "rgba(0,0,0,0.55)",
borderWidth: 0,
},
legend: {
right: 50,
top: 25,
verticalAlign: "top",
enabled: true,
itemDistance: 5,
textStyle: {
fontSize: "0.6rem",
color: "#fff",
rich: {
a: {
verticalAlign: "middle",
},
},
padding: [0, 0, 0, 0], //[上、右、下、左]
},
},
xAxis: [
{
type: "time",
axisLine: {
show: true,
onZero: false,
lineStyle: {
color: "#fff",
},
},
axisLabel: {
textStyle: {
fontFamily: "dinproRegular",
},
},
},
],
toolbox: {
show: false,
feature: {
dataZoom: {
// bottom: '10px',
yAxisIndex: "none",
},
},
},
yAxis: [
{
type: "value",
min: item.minValue,
max: item.maxValue,
name: item.valueName,
axisLine: {
show: true,
onZero: false, //-----------重点
lineStyle: {
color: "#fff",
},
},
splitLine: {
lineStyle: {
// 使用深浅的间隔色
type: "dashed",
opacity: 0.5,
},
},
},
],
series: item.serise,
};
let aValues = [];
let bValues = [];
let CValues = [];
let ZValues = [];
if (
traceability.value.length > 0 &&
traceability.value[0].value != null &&
traceability.value[0].value.length > 0
) {
for (let i = 0; i < traceability.value[0].value.length; i++) {
let T = traceability.value[0].value[i][0];
let A = traceability.value[0].value[i][1];
let B = traceability.value[0].value[i][2];
let C = traceability.value[0].value[i][3];
let Z = A + B + C;
aValues.push([T, A > 0 ? 1 : A == 0 ? 0 : -1]);
bValues.push([T, B > 0 ? 1 : B == 0 ? 0 : -1]);
CValues.push([T, C > 0 ? 1 : C == 0 ? 0 : -1]);
ZValues.push([T, Z > 0 ? 1 : Z == 0 ? 0 : -1]);
}
}
});
loading.value = false;
};
const handleCloseDialog = () => {
visible.value = false;
options.value = null;
emit("close");
};
defineExpose({
open,
});
</script>

View File

@@ -0,0 +1,583 @@
<template>
<div class="container">
<!-- 使用 v-for 遍历四个角落 -->
<div
v-for="corner in corners"
:key="corner.id"
v-show="corner.show"
:class="['corner', corner.className]"
>
<div class="content">
<div class="title">{{ corner.title }}</div>
<el-descriptions :column="1" size="small" label-width="70px" border>
<el-descriptions-item
v-for="(item, index) in corner.data"
:key="index"
:label="item.label"
>
<!-- {{ item.value }} -->
<div v-html="item.value" v-if="item.label !== '暂降次数'"></div>
<!-- 跑马灯 -->
<div v-else style="display: flex">
<div style="width: 30px">{{ corner.raceLists.length }}</div>
<div class="simple-marquee">
<div class="marquee-content">
<span
style="margin-right: 15px"
v-for="(event, index) in corner.raceLists"
:key="index"
@click="goToRace(event)"
>{{
index +
1 +
"、" +
event.startTime +
"发生" +
event.eventType +
"," +
"残余电压:" +
(event.featureAmplitude * 100).toFixed(2) +
"%" +
"," +
"持续时间:" +
event.duration +
"S"
}}</span
>
</div>
</div>
</div>
</el-descriptions-item>
</el-descriptions>
</div>
<span class="close-btn" @click="closeCorner(corner.id)">
<Close />
</span>
</div>
<DipDetail ref="dipDetail"></DipDetail>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, nextTick, reactive } from "vue";
import { clickImage, realTimeData } from "@/api/manage_wx";
import { Close } from "@element-plus/icons-vue";
import socketClient from "@/utils/webSocketClient";
import DipDetail from "../eventStatistics/dipDetail.vue";
// 定义接收的 props
const props = defineProps<{
eventList?: [];
}>();
//开始创建webSocket客户端
const dataSocket = reactive({
socketServe: socketClient.Instance,
});
// 定义四个角落的数据
const corners = ref([
{
id: "topLeft",
title: "左上",
className: "top-left",
show: false,
data: [] as { label: string; value: string }[],
elementId: "", // 记录该角落对应的元素ID
raceLists: [] as any[], // 为每个角落添加独立的跑马灯数据存储
},
{
id: "topRight",
title: "右上",
className: "top-right",
show: false,
data: [] as { label: string; value: string }[],
elementId: "",
raceLists: [] as any[], // 为每个角落添加独立的跑马灯数据存储
},
// {
// id: "bottomLeft",
// title: "左下",
// className: "bottom-left",
// show: false,
// data: [] as { label: string; value: string }[],
// elementId: "",
// },
// {
// id: "bottomRight",
// title: "右下",
// className: "bottom-right",
// show: false,
// data: [] as { label: string; value: string }[],
// elementId: "",
// },
]);
const steadyStateList = ref([]);
const selectedId = ref("");
// 内部响应式数据
const eventList = ref([]);
// 点击跑马灯展示弹框
const dipDetail = ref(null);
const handleClickImage = async (elementId: string) => {
// 检查 elementId 是否有值,没有值则直接返回空数组
if (!elementId) {
eventList.value = [];
return;
}
try {
// 发送点击图片请求
const res = await clickImage({ lineId: elementId });
// 确保返回的数据是数组格式,并且过滤掉 null/undefined 元素
let dataToStore: any[] = [];
if (Array.isArray(res.data)) {
dataToStore = res.data.filter(
(item) => item !== null && item !== undefined
);
} else if (res.data && Array.isArray(res.data.records)) {
dataToStore = res.data.records.filter(
(item) => item !== null && item !== undefined
);
} else if (res.data) {
// 如果是单个对象且不为 null
if (res.data !== null && res.data !== undefined) {
dataToStore = [res.data];
}
}
eventList.value = dataToStore;
} catch (error) {
console.error("调用 clickImage 接口出错:", error);
// 出错时设置为空数组,避免后续处理出错
eventList.value = [];
}
};
// 监听 props 变化
// watch(
// () => props.eventList,
// (newVal) => {
// if (newVal && Array.isArray(newVal)) {
// eventList.value = [...newVal];
// dataLoaded.value = true;
// } else {
// dataLoaded.value = false;
// }
// },
// { immediate: true, deep: true }
// );
// 记录显示顺序,用于循环替换
const displayOrder = ref<number[]>([]);
// 计算跑马灯动画时长(毫秒)
const calculateMarqueeDuration = (corner) => {
if (!corner.raceLists || corner.raceLists.length === 0) {
return 25000; // 默认25秒
}
// 计算所有事件文本的总长度
let totalTextLength = 0;
corner.raceLists.forEach((event, index) => {
const text = `${index + 1}${event.startTime}发生${
event.eventType
},残余电压:${(event.featureAmplitude * 100).toFixed(2)}%,持续时间:${
event.duration
}S`;
totalTextLength += text.length;
});
// 添加分隔符长度和边距
const separatorLength =
corner.raceLists.length > 1 ? (corner.raceLists.length - 1) * 15 : 0; // margin-right: 15px
totalTextLength += separatorLength;
// 根据文本长度计算时长(减慢速度)
// 将每字符需要的时间从50ms增加到80ms最少8秒最多90秒
const duration = Math.min(Math.max(totalTextLength * 80, 8000), 90000);
return duration;
};
// 更新指定角落数据的函数
const updateCornerData = (
cornerIndex: number,
dataItem: any,
elementId: string
) => {
// 更新标题为 objName
if (dataItem.objName) {
corners.value[cornerIndex].title = dataItem.objName;
} else {
corners.value[cornerIndex].title = dataItem.stationName;
}
// 格式化数据
corners.value[cornerIndex].data = [
{ label: "监测点", value: dataItem.lineName },
// {
// label: "暂降次数",
// value: dataItem.eventIds.length,
// },
{
label: "暂降次数",
value: ``,
},
// { label: "稳态指标", value: "Ua:65.5 Ub:65.02 Uc:65.27 Uac:112.85 Uab:112.67 Ubc:112.85" },
{
label: "稳态指标",
value: ``,
},
];
// 记录该角落对应的元素ID
corners.value[cornerIndex].elementId = elementId;
corners.value[cornerIndex].show = true;
// 设置动画时长
nextTick(() => {
const duration = calculateMarqueeDuration(corners.value[cornerIndex]);
const marqueeElement = document.querySelector(
`.${corners.value[cornerIndex].className} .marquee-content`
);
if (marqueeElement) {
(marqueeElement as HTMLElement).style.setProperty(
"--marquee-duration",
`${duration}ms`
);
}
});
};
// 显示下一个角落的函数
const showNextCorner = (elementId: string) => {
// 检查该元素ID是否已经显示过
const existingCornerIndex = corners.value.findIndex(
(corner) => corner.elementId === elementId && corner.show
);
if (existingCornerIndex !== -1) {
// 如果该元素已经显示过,不更新数据
return;
}
// 确保 eventList.value 是数组并且过滤掉 null/undefined 元素
if (!Array.isArray(eventList.value)) {
console.warn("eventList.value 不是数组格式:", eventList.value);
return;
}
// 过滤掉 null 和 undefined 元素,然后查找匹配项
const validItems = eventList.value.filter(
(item) => item !== null && item !== undefined
);
const dataItem = validItems.find((item) => item.lineId === elementId);
// 如果没有找到匹配的数据项,则不更新数据
if (!dataItem) {
console.warn("未找到匹配的数据项:", elementId);
return;
}
// 查找一个未显示的角落
const availableCornerIndex = corners.value.findIndex(
(corner) => !corner.show
);
if (availableCornerIndex !== -1) {
// 有空闲角落,显示在该角落
updateCornerData(availableCornerIndex, dataItem, elementId);
// 将事件数据存储到该角落
corners.value[availableCornerIndex].raceLists = dataItem.eventList || [];
// 记录显示顺序
displayOrder.value.push(availableCornerIndex);
} else {
// 没有空闲角落,按顺序替换角落
// 获取需要替换的角落索引(循环替换)
const replaceIndex = displayOrder.value.shift() || 0;
updateCornerData(replaceIndex, dataItem, elementId);
// 将事件数据存储到该角落
corners.value[replaceIndex].raceLists = dataItem.eventList || [];
// 将替换的索引重新加入队列末尾
displayOrder.value.push(replaceIndex);
}
};
// 关闭指定角落的函数
const closeCorner = (id: string) => {
const cornerIndex = corners.value.findIndex((c) => c.id === id);
if (cornerIndex !== -1) {
corners.value[cornerIndex].show = false;
corners.value[cornerIndex].elementId = ""; // 清空元素ID记录
// 从显示顺序中移除该角落索引
const orderIndex = displayOrder.value.indexOf(cornerIndex);
if (orderIndex !== -1) {
displayOrder.value.splice(orderIndex, 1);
}
}
send();
};
// 关闭所有角落的函数
const closeAllCorners = () => {
corners.value.forEach((corner) => {
corner.show = false;
corner.elementId = "";
});
displayOrder.value = [];
};
// 组件挂载后初始化监听器
onMounted(() => {
init();
// 初始化时不显示任何内容
});
// 连接webSocket客户端
const init = () => {
if (!dataSocket.socketServe) {
console.error("WebSocket 客户端实例不存在");
return;
}
dataSocket.socketServe.connect(new Date().getTime());
dataSocket.socketServe.registerCallBack("message", (res: any) => {
if (res.type == 1) {
//稳态指标数据
let steadyState = JSON.parse(res.message);
// console.log(steadyState, "8990hhhhh");
if (steadyState == null || steadyState.length == 0) return;
steadyStateList.value = steadyState;
corners.value.forEach((corner, index) => {
let str = ``;
steadyState
.filter((item) => item.lineId == corner.elementId)
.forEach((item) => {
if (item.value == 3.1415926) {
str += `<div>${item.statisticalName}/</div>`;
} else {
str += `<div>${item.statisticalName}${item.value}${item.unit}</div>`;
}
});
corner.data.length > 0
? (corner.data[2].value = `<div style="max-height: 100px;overflow-y: auto;">${str} </div>`)
: "";
// 更新跑马灯动画时长
nextTick(() => {
const duration = calculateMarqueeDuration(corner);
const marqueeElement = document.querySelector(
`.${corner.className} .marquee-content`
);
if (marqueeElement) {
(marqueeElement as HTMLElement).style.setProperty(
"--marquee-duration",
`${duration}ms`
);
}
});
});
}
});
};
const time = ref(null);
// 推送消息
const send = () => {
dataSocket.socketServe.send({
pageId: selectedId.value,
lineIdList: corners.value
.filter((item) => item.show == true)
.map((corner) => corner.elementId),
});
if (time.value) {
clearTimeout(time.value);
}
if (
corners.value
.filter((item) => item.show == true)
.map((corner) => corner.elementId).length == 0
)
return;
time.value = setInterval(() => {
dataSocket.socketServe.send({
pageId: selectedId.value,
lineIdList: corners.value
.filter((item) => item.show == true)
.map((corner) => corner.elementId),
});
}, 1000 * 60);
};
const goToRace = (row) => {
console.log(row.measurementPointId, "1222");
dipDetail.value.open(row.measurementPointId);
};
// 监听来自 iframe 的消息
window.addEventListener("message", async function (event) {
// 安全起见可以验证消息来源origin
// if (event.origin !== 'https://trusted-origin.com') return;
// 处理从 iframe 发送过来的消息
if (event.data.action === "coreClick") {
const clickedElementId = event.data.coreId;
selectedId.value = event.data.selectedId;
// 调用接口获取最新数据
await handleClickImage(clickedElementId);
// 根据接收到的元素LineId显示对应数据
await showNextCorner(clickedElementId);
await send();
}
});
// 页面卸载时清除定时器
onBeforeUnmount(() => {
clearTimeout(time.value);
dataSocket.socketServe?.closeWs();
});
</script>
<style>
/* 走马灯样式 - 支持逐条显示 */
.simple-marquee {
width: 230px;
height: 24px;
overflow: hidden;
position: relative;
white-space: nowrap;
}
.marquee-content {
display: inline-block;
padding-left: 100%;
/* animation: marquee-single 15s linear infinite; */
animation: marquee-single var(--marquee-duration, 25s) linear infinite;
line-height: 24px;
text-decoration: underline;
text-decoration-color: #4877f6;
}
@keyframes marquee-single {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-100%);
}
}
/* 鼠标悬停时暂停动画 */
.simple-marquee:hover .marquee-content {
animation-play-state: paused;
cursor: pointer;
}
</style>
<style scoped lang="scss">
.container {
position: relative;
}
.corner {
width: 340px;
/* height: 135px; */
background-color: #2b2d3a90;
position: absolute;
color: white;
/* font-weight: bold; */
/* 添加弹出动画 */
opacity: 0;
transform: scale(0.3);
transition: all 0.4s ease-out;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
/* 显示状态的样式 */
.corner:not([style*="display: none"]):not([style*="display:none"]) {
opacity: 1;
transform: scale(1);
}
.top-left {
top: 10px;
left: 10px;
}
.top-right {
top: 10px;
right: 10px;
}
.bottom-left {
top: 170px;
left: 10px;
}
.bottom-right {
top: 170px;
right: 10px;
}
.content {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.title {
font-size: 16px;
/* text-align: center; */
/* margin-bottom: 8px; */
padding: 8px;
/* color: #409eff; */
border-bottom: 1px solid #444;
background-color: #21232b;
border-radius: 8px 8px 0 0;
}
.data-item {
display: flex;
margin-bottom: 4px;
font-size: 12px;
}
.label {
/* font-weight: bold; */
width: 55px;
flex-shrink: 0;
}
.value {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 关闭按钮样式 */
.close-btn {
position: absolute;
top: 10px;
right: 10px;
width: 14px;
color: white;
font-size: 14px;
font-weight: bold;
cursor: pointer;
}
.indicator {
display: flex;
}
</style>

View File

@@ -0,0 +1,651 @@
<template>
<!-- 项目管理弹框 -->
<el-dialog v-model="dialogVisible" title="项目管理" width="1600px">
<!-- <el-card class="transparent-card"> -->
<div style="display: flex; justify-content: space-between">
<div>
<span style="width: 80px; color: #fff">项目名称</span>
<el-input
v-model.trim="projectName"
maxlength="32"
placeholder="请输入项目名称"
show-word-limit
style="width: 300px; margin-left: 10px"
size="small"
/>
</div>
<div>
<el-button :icon="Search" size="small" type="primary" @click="onSearch">
查询</el-button
>
<!-- <el-button :icon="RefreshLeft" size="small" @click="onReset"
>重置</el-button
> -->
<el-button :icon="Plus" size="small" type="primary" @click="onSubmitadd"
>新增</el-button
>
</div>
</div>
<!-- </el-card> -->
<div
style="
overflow-x: hidden;
overflow-y: scroll;
padding: 0 10px;
margin-top: 10px;
height: 660px;
"
>
<el-row :gutter="12">
<el-col
:span="6"
v-for="item in projectData"
:key="item.id"
class="mt10"
>
<el-card class="box-card" @click="querdata(item)" shadow="hover">
<div slot="header" class="clearfix">
<span style="display: flex; align-items: center; color: #fff"
>{{ item.name }}
<el-tooltip
class="item"
effect="dark"
content="修改项目"
placement="top"
>
<el-icon
:size="20"
class="color"
style="cursor: pointer; margin-left: 5px"
>
<Edit @click="editd(item)" />
</el-icon>
</el-tooltip>
<!-- <el-tooltip
class="item"
effect="dark"
content="修改项目"
placement="top"
>
<Edit
style="margin-left: 5px; width: 16px"
class="xiaoshou color"
@click="editd(item)"
/> </el-tooltip
> -->
</span>
<div style="display: flex; justify-content: end">
<el-button
v-if="item.active == 0"
class="color"
:icon="Compass"
style="padding: 3px 0"
type="text"
@click="activeItem(item)"
>激活</el-button
>
<el-button
v-else
class="color"
style="padding: 3px 0; color: #82bd51"
type="text"
>已激活</el-button
>
<el-button
class="color"
:icon="Share"
style="padding: 3px 0"
type="text"
@click="Aclick(item)"
>设计</el-button
>
<el-button
:icon="Delete"
style="padding: 3px 0; color: #c93434"
type="text"
@click="deleted(item)"
>删除</el-button
>
</div>
</div>
<img
v-if="item.fileContent"
:src="item.fileContent"
class="image xiaoshou"
@click="imgData(item)"
/>
<el-empty v-else description="暂无设计" style="height: 220px" />
</el-card>
</el-col>
</el-row>
</div>
<!-- <template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">Cancel</el-button>
</div>
</template> -->
<div class="table-pagination">
<el-pagination
small
:currentPage="params.pageNum"
:page-size="params.pageSize"
:page-sizes="[10, 20, 50, 100]"
background
:layout="'sizes,total, ->, prev, pager, next, jumper'"
:total="params.total"
@size-change="onTableSizeChange"
@current-change="onTableCurrentChange"
></el-pagination>
</div>
</el-dialog>
<!-- 新增弹框 -->
<el-dialog v-model="innerVisible" :title="dialogTitle" width="500px">
<el-form
ref="ruleFormRef"
:model="ruleForm"
:rules="rules"
label-width="auto"
class="demo-ruleForm"
:size="formSize"
status-icon
>
<el-form-item label="项目名称" prop="name">
<el-input
v-model="ruleForm.name"
maxlength="32"
show-word-limit
placeholder="请输入项目名称"
/>
</el-form-item>
<!-- <el-form-item label="工程项目" prop="projectIds">
<el-select
v-model="ruleForm.projectIds"
placeholder="请选择"
:popper-append-to-body="false"
popper-class="custom-select-dropdown"
>
<el-option label="Zone one111" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item> -->
<el-form-item label="项目排序" prop="orderBy">
<el-input v-model="ruleForm.orderBy" placeholder="请输入" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="ruleForm.remark" type="textarea" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button size="small" type="primary" @click="submitForm(ruleFormRef)">
确定
</el-button>
<el-button size="small" @click="resetForm">取消</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import {
defineComponent,
ref,
reactive,
getCurrentInstance,
onMounted,
} from "vue";
import {
Search,
RefreshLeft,
Plus,
Share,
Delete,
Edit,
Compass,
} from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus";
import type { FormInstance, FormRules } from "element-plus";
import {
projectList,
add,
edit,
active,
getActive,
} from "@/api/manage_wx/index";
const emit = defineEmits<{
(e: "project-change", project: { id: string; name: string }): void;
}>();
const projectData = ref([]);
// 系统配置弹框
const dialogVisible = ref(false);
const innerVisible = ref(false);
const projectName = ref("");
const dialogTitle = ref("新增项目");
const firstForm = ref({
name: "",
id: "",
});
const params = reactive({
pageNum: 1,
pageSize: 10,
total: 0,
});
interface RuleForm {
name: string;
projectIds: [];
orderBy: string;
remark: string;
}
const formSize = ref("default");
const ruleFormRef = ref<FormInstance>();
const ruleForm = reactive<RuleForm>({
name: "",
projectIds: [],
orderBy: "100",
remark: "",
});
const rules = reactive<FormRules<RuleForm>>({
name: [{ required: true, message: "请输入项目名称", trigger: "blur" }],
projectIds: [
{
required: true,
message: "请选择",
trigger: "change",
},
],
orderBy: [
{
required: true,
message: "请输入项目排序",
trigger: "change",
},
],
});
onMounted(() => {
fetachData();
});
// 创建一个专门发送数据到 iframe 的函数
const sendToIframe = (type: string, data: any) => {
try {
// 确保数据可以被序列化
const serializableData = JSON.parse(JSON.stringify(data));
window.parent.postMessage(
{
type: type,
data: serializableData,
},
"*"
);
} catch (error) {
console.error("发送到 iframe 失败:", error);
}
};
const fetachData = async () => {
const res = await projectList({
pageNum: params.pageNum,
pageSize: params.pageSize,
searchValue: projectName.value,
});
// projectData.splice(0, projectData.length, ...res.records);
projectData.value = res.data.records;
params.total = res.data.total;
if (res.data.records.length > 0) {
firstForm.value.id = res.data.records[0].id;
firstForm.value.name = res.data.records[0].name;
}
};
// 查询
const onSearch = () => {
fetachData();
};
// 重置
const onReset = () => {
projectName.value = "";
fetachData();
};
// 新增
const onSubmitadd = () => {
innerVisible.value = true;
dialogTitle.value = "新增项目";
// Object.assign(ruleForm, {}); 不生效
Object.assign(ruleForm, {
//生效,但是一个个赋值,麻烦
id: "0944fe372e90daeefd040916a105ac8b",
name: "",
orderBy: "100",
projectIds: ["1dd1b076e104f15459ac401fc1b902c4"],
remark: "",
});
//Object.keys(ruleForm).forEach((key) => delete ruleForm[key]); //生效,但是有默认值的,一进去会直接报校验
};
const querdata = (e: any) => {};
// 编辑修改
const editd = (row: any) => {
innerVisible.value = true;
dialogTitle.value = "修改项目";
// Object.assign(ruleForm, row);
Object.assign(ruleForm, {
id: row.id,
name: row.name,
orderBy: row.orderBy,
projectIds: row.projectIds,
remark: row.remark,
});
};
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
if (dialogTitle.value == "新增项目") {
getAdd();
} else {
getEdit();
}
console.log("submit!");
} else {
console.log("error submit!", fields);
}
});
};
const getAdd = async () => {
// const params = { ...ruleForm, id: "0944fe372e90daeefd040916a105ac8b" };
const res: any = await add(ruleForm);
if (res.code == "A0000") {
ElMessage({
type: "success",
message: "新增成功!",
});
innerVisible.value = false;
fetachData();
} else {
ElMessage({
type: "error",
message: res.message,
});
}
};
const getEdit = async () => {
const res: any = await edit(ruleForm);
if (res.code == "A0000") {
ElMessage({
type: "success",
message: "修改成功!",
});
innerVisible.value = false;
fetachData();
}
};
const resetForm = () => {
innerVisible.value = false;
};
const onTableSizeChange = (size: number) => {
params.pageSize = size;
//emit("refreshData"); // 触发父组件方法
fetachData();
};
const onTableCurrentChange = (page: number) => {
params.pageNum = page;
//emit("refreshData"); // 触发父组件方法
fetachData();
};
// 设计
const Aclick = (e: any) => {
// window.open("http://192.168.1.179:4001" + `/zutai/?id=${e.id}&&name=decodeURI(${e.name})&&flag=false`)
// window.open(
// window.location.origin +
// `/zutai/?id=${e.id}&&name=${e.name}&&preview=false&&graphicDisplay=true`
// );
// 无锡项目进去不展示数据绑定图元
window.open(
"http://192.168.1.179:4001" +
`/zutai/?id=${e.id}&&name=${e.name}&&preview=false&&graphicDisplay=wx`
);
};
// 删除
const deleted = (e: any) => {
ElMessageBox.confirm("此操作将永久删除该项目, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
let data = {
id: e.id,
name: e.name,
status: "0",
};
edit(data).then((res: any) => {
if (res.code == "A0000") {
ElMessage({
type: "success",
message: "删除项目成功!",
});
}
fetachData();
});
})
.catch(() => {
ElMessage({
type: "info",
message: "已取消删除",
});
});
};
const imgData = (e: any) => {
// window.open(window.location.origin + `/zutai/?id=${e.id}&&name=decodeURI(${e.name})&&flag=true`)
// window.open(
// window.location.origin +
// `/zutai/?id=${e.id}&&name=${e.name}&&preview=true#/preview`
// );
window.open(
"http://192.168.1.179:4001" +
`/zutai/?id=${e.id}&&name=${e.name}&&preview=true&&graphicDisplay=wx#/preview`
);
};
// 获取激活
const getActiveItem = () => {
getActive({}).then((res: any) => {
if (res.code == "A0000") {
// 通知父组件
emit("project-change", {
id: res.data.id,
name: res.data.name,
});
}
});
};
// 激活
const activeItem = (item: any) => {
ElMessageBox.confirm("是否确认激活该项目?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
let data = {
id: item.id,
};
active(data).then((res: any) => {
if (res.code == "A0000") {
ElMessage({
type: "success",
message: "激活成功!",
});
}
fetachData().then(() => {
getActiveItem();
});
});
})
.catch(() => {
ElMessage({
type: "info",
message: "已取消激活",
});
});
};
const open = () => {
dialogVisible.value = true;
};
defineExpose({
open,
});
</script>
<style lang="scss">
.custom-select-dropdown {
// background-color: transparent !important;
width: 510px;
}
.custom-select-dropdown .el-select-dropdown__list {
// background-color: transparent !important;
width: 500px;
}
</style>
<style lang="scss" scoped>
.item {
margin-bottom: 18px;
}
.image {
display: block;
width: 100%;
height: 220px;
}
.clearfix::before,
.clearfix::after {
display: table;
content: "";
}
.clearfix::after {
clear: both;
}
.box-card {
width: 100%;
// box-shadow: var(--el-box-shadow-light)
background-color: transparent; /* 设置背景颜色为透明 */
border: 1px solid gray; /* 添加边框,可以根据需要调整颜色和宽度 */
box-shadow: 0 2px 8px gray; /* 添加边框阴影 */
}
.transparent-card {
background-color: transparent; /* 设置背景颜色为透明 */
border: 1px solid gray; /* 添加边框,可以根据需要调整颜色和宽度 */
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); /* 添加边框阴影 */
}
.xiaoshou {
cursor: pointer;
}
.setstyle {
min-height: 200px;
padding: 0 !important;
margin: 0;
overflow: auto;
cursor: default !important;
}
.color {
color: var(--el-color-primary);
}
:deep(.el-select-dropdown__wrap) {
max-height: 300px;
}
:deep(.el-tree) {
padding-top: 15px;
padding-left: 10px;
// 不可全选样式
.el-tree-node {
.is-leaf + .el-checkbox .el-checkbox__inner {
display: inline-block;
}
.el-checkbox .el-checkbox__inner {
display: none;
}
}
}
:deep(.el-card__header) {
padding: 13px 10px;
height: 44px;
}
.table-pagination {
box-sizing: border-box;
width: 100%;
max-width: 100%;
background-color: var(--ba-bg-color-overlay);
padding: 10px;
}
:deep(.el-pagination__sizes) {
.el-select {
min-width: 128px;
}
}
:deep(.el-form-item .el-form-item__label) {
color: #fff;
}
// :deep(.el-input__wrapper) {
// background-color: transparent !important;
// }
// :deep(.el-select__wrapper) {
// background-color: transparent !important;
// }
// :deep(.el-textarea__inner) {
// background-color: transparent !important;
// }
// :deep(.el-input .el-input__count .el-input__count-inner) {
// background-color: transparent !important;
// display: inline-block;
// line-height: normal;
// padding-left: 8px;
// }
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,118 @@
<template>
<div class="plan">
<!-- src=" http://192.168.1.128:3001/zutai/?id=44368e72a2e594d14ebaf317f0f6ad00&&name=decodeURI(APP测试项目)&&flag=true&&wxqr=true#/" -->
<!-- <iframe
src="http://192.168.1.62:8088/zutai/?id=0944fe372e90daeefd040916a105ac8b&&name=测试组态编辑器&&preview=true#/preview"
width="100%"
height="100%"
frameborder="0"
></iframe> -->
<!-- 添加加载事件监听 -->
<iframe
:src="iframeSrc"
width="100%"
height="100%"
frameborder="0"
scrolling="no"
id="iframeLeft"
@load="onIframeLoad"
></iframe>
</div>
</template>
<script setup lang="ts">
import { ref, watch, onMounted, onUnmounted } from "vue";
import { getActive } from "@/api/manage_wx/index";
const props = defineProps<{
project: { id: string; name: string } | null;
}>();
const iframeSrc = ref("");
// 监听 props 变化
watch(
() => props.project,
(newVal) => {
if (newVal && newVal.id && newVal.name) {
// window.location.origin
iframeSrc.value =
"http://192.168.1.179:4001" +
`/zutai/?id=${newVal.id}&&name=${encodeURIComponent(
newVal.name
)}&&preview=true&&display=true&&graphicDisplay=wx#/preview`;
// console.log("更新 iframeSrc:", iframeSrc.value);
}
},
{ immediate: true, deep: true }
);
onMounted(() => {
// 监听来自 eventStatistics 组件的消息
window.addEventListener("message", handleMessage);
getActive({}).then((res: any) => {
if (res.code == "A0000") {
// window.location.origin
iframeSrc.value =
"http://192.168.1.179:4001" +
`/zutai/?id=${res.data.id}&&name=${encodeURIComponent(
res.data.name
)}&&preview=true&&display=true&&graphicDisplay=wx#/preview`;
}
});
});
onUnmounted(() => {
// 清理事件监听器
window.removeEventListener("message", handleMessage);
});
// iframe 加载完成回调 添加加载事件监听
const onIframeLoad = () => {
// console.log("iframe 加载完成");
// 通知 securityDetail.vue 组件 iframe 已加载完成
window.postMessage(
{
type: "IFRAME_LOADED",
data: { loaded: true },
},
"*"
);
};
// 处理来自 eventStatistics 组件的消息
const handleMessage = (event: MessageEvent) => {
// 验证消息来源(在生产环境中应该验证 origin
// if (event.origin !== 'trusted-origin') return;
const { type, payload } = event.data;
if (type === "SEND_KEYS_TO_IFRAME") {
// 将数据转发给 iframe
sendKeysToIframe(payload);
}
};
// 向 iframe 发送 keyList 数据
const sendKeysToIframe = (keyList: string[]) => {
const iframe = document.getElementById("iframeLeft") as HTMLIFrameElement;
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{
type: "ANALYSIS_KEYS",
payload: keyList,
},
"*"
); // 在生产环境中应该指定具体的域名而不是 '*'
}
};
</script>
<style lang="scss" scoped>
.plan {
width: 100%;
height: 990px;
padding: 5px;
}
</style>

View File

@@ -0,0 +1,262 @@
<template>
<!--暂降 -->
<el-dialog
:close-on-click-modal="false"
draggable
v-model="machineVisible"
:title="title"
width="1000px"
>
<div class="formBox">
<el-form label-width="100px" :model="form" inline>
<el-form-item label="关键字查询">
<el-input
v-model="form.searchValue"
clearable
size="small"
placeholder="请输入关键字查询"
/>
</el-form-item>
<el-form-item label="运行状态">
<el-select
v-model="form.runFlag"
clearable
size="small"
style="width: 160px"
>
<el-option label="投运" value="0" />
<el-option label="热备用" value="1" />
<el-option label="停运" value="2" />
</el-select>
</el-form-item>
</el-form>
<div class="mt5">
<el-button
type="primary"
:icon="Search"
size="small"
@click="getTableData"
>查询</el-button
>
<el-button
type="primary"
:icon="Download"
@click="exportTable"
size="small"
>导出
</el-button>
</div>
</div>
<div class="tableBox">
<el-table
:scrollbar-always-on="true"
:data="tableData"
height="400px"
size="small"
v-loading="loading"
element-loading-background="#343849c7"
:header-cell-style="{ textAlign: 'center' }"
border
>
<el-table-column label="序号" align="center" type="index" width="70">
<template #default="scope">
<span>{{
(form.pageNum - 1) * form.pageSize + scope.$index + 1
}}</span>
</template>
</el-table-column>
<el-table-column prop="devName" align="center" label="终端名称" />
<el-table-column
prop="stationName"
align="center"
label="所属电站"
show-overflow-tooltip
/>
<el-table-column
prop="gdName"
align="center"
label="供电公司"
width="120"
show-overflow-tooltip
/>
<el-table-column
prop="ip"
align="center"
label="网络参数"
show-overflow-tooltip
/>
<el-table-column
prop="manufacturer"
align="center"
label="所属厂家"
width="100"
/>
<el-table-column
prop="runFlag"
align="center"
label="运行状态"
width="100"
>
<template #default="scope">
<el-tag
v-if="scope.row.runFlag == 0"
size="small"
type="primary"
effect="dark"
>投运</el-tag
>
<el-tag
v-if="scope.row.runFlag == 1"
size="small"
type="info"
effect="dark"
>热备用</el-tag
>
<el-tag
v-if="scope.row.runFlag == 2"
size="small"
type="danger"
effect="dark"
>停运</el-tag
>
</template>
</el-table-column>
<el-table-column
prop="loginTime"
align="center"
label="投运时间"
width="120"
/>
</el-table>
</div>
<el-pagination
size="small"
style="margin-top: 10px"
:currentPage="form.pageNum"
:page-size="form.pageSize"
:page-sizes="[10, 20, 50, 100, 200]"
background
:layout="'sizes,total, ->, prev, pager, next, jumper'"
:total="total"
@size-change="onTableSizeChange"
@current-change="onTableCurrentChange"
></el-pagination>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, inject, onMounted } from "vue";
import { ElMessage } from "element-plus";
import datePicker from "@/components/datePicker/index.vue";
import { Search, Download } from "@element-plus/icons-vue";
import { searchTree } from "xe-utils";
import { devPage } from "@/api/manage_wx";
import { useStore } from "vuex";
import * as XLSX from "xlsx";
const loading = ref(false);
const store = useStore();
const machineVisible = ref(false);
const title = ref("");
const formRef = ref();
const form = reactive({
searchValue: "",
pageNum: 1,
pageSize: 20,
deptId: "",
runFlag: "",
});
const total = ref(0); // 假设总条数为100
const tableData = ref([]);
//form表单校验规则
const openDev = (text: string, data?: any) => {
title.value = text;
machineVisible.value = true;
form.searchValue = "";
form.pageNum = 1;
form.pageSize = 20;
form.runFlag = "";
getTableData();
};
const onTableSizeChange = (size: number) => {
form.pageSize = size;
// 重新加载数据
getTableData();
};
const onTableCurrentChange = (page: number) => {
form.pageNum = page;
// 重新加载数据
getTableData();
};
const getTableData = async () => {
loading.value = true;
form.deptId = store.state.deptId;
const res: any = await devPage(form);
if (res.code == "A0000") {
tableData.value = res.data.records;
total.value = res.data.total;
}
loading.value = false;
};
// 导出
const exportTable = async () => {
// 示例数据
let columnExpor: any = [
["终端名称", "所属电站", "供电公司", "网络参数", "所属厂家", "运行状态","投运时间"],
];
let list = [];
await devPage({
...form,
pageNum: 1,
pageSize: total.value,
}).then((res) => {
let data = res.data.records.map((item) => {
return [
item.devName,
item.stationName,
item.gdName,
item.ip,
item.manufacturer,
item.runFlag == 0 ? "运行" : item.runFlag == 1 ? "热备用" : "停运",
item.loginTime,
];
});
list = [...columnExpor, ...data];
// 创建工作表
const worksheet = XLSX.utils.aoa_to_sheet(list);
worksheet["!cols"] = list.map((col) => ({ wch: 20 }));
// 创建工作簿
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, title.value);
// 写出文件
XLSX.writeFile(workbook, title.value + ".xlsx");
});
};
onMounted(() => {
// getTableData();
});
defineExpose({ openDev });
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.tableBox {
padding: 0px !important;
}
.formBox {
display: flex;
justify-content: space-between;
}
.formLeft {
display: flex;
align-items: center;
color: #fff;
}
</style>

View File

@@ -0,0 +1,310 @@
<template>
<div class="titleBox">监测点规模统计</div>
<div class="Box">
<div class="eachartsBox">
<div
class="totalBox"
@click="clickNum('站点详情')"
style="background-color: #28a74570"
>
<div>
<span>站点总数</span>
<span class="text">{{ statisticsObj.stationAll }}</span>
</div>
<div>
<span>运行站点数 </span>
<span>{{ statisticsObj.stationRun }} </span>
</div>
</div>
<div
class="eacharts1"
ref="eacharts1"
style="width: 100%; height: 100%"
></div>
</div>
<div class="eachartsBox">
<div
class="totalBox"
@click="clickNum('终端详情')"
style="background-color: #07ccca70"
>
<div>
<span>终端总数</span>
<span class="text">{{ statisticsObj.devAll }}</span>
</div>
<div>
<span>运行终端数 </span>
<span>{{ statisticsObj.devRun }} </span>
</div>
</div>
<div
class="eacharts2"
ref="eacharts2"
style="width: 100%; height: 100%"
></div>
</div>
<div class="eachartsBox">
<div
class="totalBox"
@click="clickNum('监测点详情')"
style="background-color: #007bff70"
>
<div>
<span>监测点总数</span>
<span class="text">{{ statisticsObj.lineAll }}</span>
</div>
<div>
<span>在线监测点数</span>
<span>{{ statisticsObj.lineRun }} </span>
</div>
</div>
<div
class="eacharts3"
ref="eacharts3"
style="width: 100%; height: 100%"
></div>
</div>
<Table ref="tableRef" />
<DevTable ref="devTableRef" />
<LineTable ref="lineTableRef" />
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, getCurrentInstance, reactive } from "vue";
import { ledgerScale } from "@/api/manage_wx/index";
const { proxy }: any = getCurrentInstance();
import * as echarts from "echarts";
import Table from "./table.vue";
import DevTable from "./devTable.vue";
import LineTable from "./lineTable.vue";
import "echarts-liquidfill";
import { useStore } from "vuex";
const store = useStore();
const eacharts1 = ref(null);
const eacharts2 = ref(null);
const eacharts3 = ref(null);
const tableRef = ref();
const devTableRef = ref();
const lineTableRef = ref();
const renderChart = (
key: any,
color: string,
point: number,
title: string,
clickHandler: Function
) => {
var myChart = proxy.$echarts.init(key);
myChart.setOption({
title: [
{
text: title,
x: title == "监测点在线率" ? "17%" : "22%",
y: "60%",
textStyle: {
fontSize: 14,
fontWeight: "500",
color: "#fff",
lineHeight: 14,
},
},
],
series: [
{
type: "liquidFill",
radius: "95%",
center: ["50%", "50%"],
color: [
{
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: color + "80", // 0% 处的颜色
},
{
offset: 1,
color: color,
},
],
globalCoord: false,
},
],
data: [point], // data个数代表波浪数
backgroundStyle: {
borderWidth: 1,
color: "RGBA(51, 66, 127, 0.7)",
},
label: {
fontSize: 20,
color: "#fff",
position: ["50%", "45%"],
},
outline: {
show: false,
borderDistance: 10,
itemStyle: {
borderWidth: 2,
borderColor: "#112165",
},
},
},
],
});
// 注册图表点击事件
myChart.on("click", clickHandler);
};
const clickNum = (title: string) => {
if (title == "站点详情") {
tableRef.value.open(title);
}
if (title == "终端详情") {
devTableRef.value.openDev(title);
}
if (title == "监测点详情") {
lineTableRef.value.openLine(title);
}
// tableRef.value.open(title);
// devTableRef.value.openDev(title);
};
// 为每个图表创建专门的点击处理函数
const handleStationChartClick = (params: any) => {
// 打开站点详情
tableRef.value.open("站点详情");
};
const handleTerminalChartClick = (params: any) => {
// 打开终端详情
devTableRef.value.openDev("终端详情");
};
const handleLineChartClick = (params: any) => {
// 打开监测点详情
lineTableRef.value.openLine("监测点详情");
};
// 监测点规模统计
const statisticsObj = reactive({
devAll: "",
devRun: "",
lineAll: "",
lineRun: "",
stationAll: "",
stationRun: "",
});
// 场站在线率
const stationOnlineRate = ref(0);
// 终端在线率
const terminalOnlineRate = ref(0);
// 监测点在线率
const onlineRate = ref(0);
onMounted(() => {
// renderChart(eacharts1.value, "#28a745", stationOnlineRate.value, "场站在线率");
// renderChart(eacharts2.value, "#07CCCA", terminalOnlineRate.value, "终端在线率");
// renderChart(eacharts3.value, "#007bff", onlineRate.value, "监测点在线率");
});
const initialData = () => {
ledgerScale({ deptId: store.state.deptId }).then((res: any) => {
if (res.code == "A0000") {
Object.assign(statisticsObj, res.data);
onlineRate.value = res.data.lineRun / res.data.lineAll;
stationOnlineRate.value = res.data.stationRun / res.data.stationAll;
terminalOnlineRate.value = res.data.devRun / res.data.devAll;
renderChart(
eacharts1.value,
"#28a745",
stationOnlineRate.value,
"场站在线率",
handleStationChartClick
);
renderChart(
eacharts2.value,
"#07CCCA",
terminalOnlineRate.value,
"终端在线率",
handleTerminalChartClick
);
renderChart(
eacharts3.value,
"#007bff",
onlineRate.value,
"监测点在线率",
handleLineChartClick
);
}
});
};
defineExpose({
initialData,
});
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.Box {
display: grid;
gap: 5px;
grid-template-columns: repeat(3, 1fr);
height: 275px;
.eachartsBox {
height: 95%;
padding: 0 5px;
display: grid;
gap: 5px;
place-items: center;
grid-template-rows: 1.1fr 1fr;
.totalBox {
height: 100%;
display: grid;
gap: 5px;
height: 80%;
width: 105%;
// margin: 10% auto;
grid-template-rows: repeat(2, 1fr);
border-radius: 10px;
cursor: pointer;
div {
display: flex;
align-items: center;
// justify-content: center;
&:nth-child(1) {
border-bottom: 1px solid #fff;
}
span {
&:nth-child(1) {
font-size: 14px;
width: 100px;
text-align: end;
}
&:nth-child(2) {
font-weight: 700;
font-size: 22px;
}
}
.text {
cursor: pointer;
// text-decoration: underline;
}
}
}
}
}
</style>
<!-- -->

View File

@@ -0,0 +1,177 @@
<template>
<!--暂降 -->
<el-dialog :close-on-click-modal="false" draggable v-model="machineVisible" :title="title" width="1000px">
<div class="formBox">
<el-form label-width="100px" :model="form" inline>
<el-form-item label="关键字查询">
<el-input v-model="form.searchValue" clearable size="small" placeholder="请输入关键字查询" />
</el-form-item>
<el-form-item label="通讯状态">
<el-select v-model="form.comFlag" clearable size="small" style="width: 160px">
<el-option label="在线" value="1" />
<el-option label="中断" value="0" />
</el-select>
</el-form-item>
</el-form>
<div class="mt5">
<el-button type="primary" :icon="Search" size="small" @click="getTableData">查询</el-button>
<el-button type="primary" :icon="Download" @click="exportTable" size="small">导出
</el-button>
</div>
</div>
<div class="tableBox">
<el-table :scrollbar-always-on="true" :data="tableData" height="400px" size="small" v-loading="loading"
element-loading-background="#343849c7" :header-cell-style="{ textAlign: 'center' }" border>
<el-table-column label="序号" align="center" type="index" width="70">
<template #default="scope">
<span>{{
(form.pageNum - 1) * form.pageSize + scope.$index + 1
}}</span>
</template>
</el-table-column>
<el-table-column prop="lineName" align="center" label="监测点名称" width="140" />
<el-table-column prop="stationName" align="center" label="所属电站" width="140" />
<el-table-column prop="gdName" align="center" label="供电公司" width="120" />
<el-table-column prop="voltageLevel" align="center" label="电压等级" width="100" />
<el-table-column prop="comFlag" align="center" label="通讯状态" width="100">
<template v-slot="scope">
<el-tag v-if="scope.row.comFlag == 1" size="small" type="primary" effect="dark">在线</el-tag>
<el-tag v-if="scope.row.comFlag == 0" size="small" type="warning" effect="dark">中断</el-tag>
</template>
</el-table-column>
<el-table-column prop="objName" align="center" label="用户" show-overflow-tooltip />
</el-table>
</div>
<el-pagination size="small" style="margin-top: 10px" :currentPage="form.pageNum" :page-size="form.pageSize"
:page-sizes="[10, 20, 50, 100, 200]" background :layout="'sizes,total, ->, prev, pager, next, jumper'"
:total="total" @size-change="onTableSizeChange" @current-change="onTableCurrentChange"></el-pagination>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, inject, onMounted } from "vue";
import { ElMessage } from "element-plus";
import { Search, Download } from "@element-plus/icons-vue";
import { linePage } from "@/api/manage_wx";
import { useStore } from "vuex";
import * as XLSX from "xlsx";
const loading = ref(false);
const store = useStore();
const machineVisible = ref(false);
const title = ref("");
const formRef = ref();
const form = reactive({
searchValue: "",
pageNum: 1,
pageSize: 20,
deptId: "",
comFlag: "",
});
const total = ref(0); // 假设总条数为100
const tableData = ref([]);
//form表单校验规则
const openLine = (text: string, data?: any) => {
title.value = text;
machineVisible.value = true;
form.searchValue = "";
form.pageNum = 1;
form.pageSize = 20;
form.comFlag = "";
getTableData();
};
const onTableSizeChange = (size: number) => {
form.pageSize = size;
// 重新加载数据
getTableData();
};
const onTableCurrentChange = (page: number) => {
form.pageNum = page;
// 重新加载数据
getTableData();
};
const getTableData = async () => {
loading.value = true;
form.deptId = store.state.deptId;
const res: any = await linePage(form);
if (res.code == "A0000") {
tableData.value = res.data.records;
total.value = res.data.total;
}
loading.value = false;
};
// 导出
const exportTable = async () => {
// 示例数据
let columnExpor: any = [
[
"监测点名称",
"所属电站",
"供电公司",
"电压等级",
"通讯状态",
"用户",
],
];
let list = [];
await linePage({
...form,
pageNum: 1,
pageSize: total.value,
}).then((res) => {
let data = res.data.records.map((item) => {
return [
item.lineName,
item.stationName,
item.gdName,
item.voltageLevel,
item.comFlag == 1 ? '在线' : '中断',
item.objName,
];
});
list = [...columnExpor, ...data];
// 创建工作表
const worksheet = XLSX.utils.aoa_to_sheet(list);
worksheet["!cols"] = list.map((col) => ({ wch: 20 }));
// 创建工作簿
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, title.value);
// 写出文件
XLSX.writeFile(workbook, title.value + ".xlsx");
});
};
onMounted(() => {
// getTableData();
});
defineExpose({ openLine });
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.tableBox {
padding: 0px !important;
}
.formBox {
display: flex;
justify-content: space-between;
}
.formLeft {
display: flex;
align-items: center;
color: #fff;
}
</style>

View File

@@ -0,0 +1,216 @@
<template>
<!--暂降 -->
<el-dialog
:close-on-click-modal="false"
draggable
v-model="machineVisible"
:title="title"
width="1000px"
>
<div class="formBox">
<el-form label-width="100px" :model="form" inline>
<el-form-item label="关键字查询">
<el-input
v-model="form.searchValue"
clearable
size="small"
placeholder="请输入关键字查询"
/>
</el-form-item>
<el-form-item label="是否监测">
<el-select
v-model="form.runFlag"
clearable
size="small"
style="width: 160px"
>
<el-option label="是" value="0" />
<el-option label="否" value="1" />
</el-select>
</el-form-item>
</el-form>
<div class="mt5">
<el-button
type="primary"
:icon="Search"
size="small"
@click="getTableData"
>查询</el-button
><el-button
type="primary"
:icon="Download"
@click="exportTable"
size="small"
>导出
</el-button>
</div>
</div>
<div class="tableBox">
<el-table
:scrollbar-always-on="true"
:data="tableData"
height="400px"
size="small"
v-loading="loading"
element-loading-background="#343849c7"
:header-cell-style="{ textAlign: 'center' }"
border
>
<el-table-column label="序号" align="center" type="index" width="70">
<template #default="scope">
<span>{{
(form.pageNum - 1) * form.pageSize + scope.$index + 1
}}</span>
</template>
</el-table-column>
<el-table-column prop="stationName" align="center" label="变电站名称" />
<el-table-column prop="gdName" align="center" label="供电公司" />
<el-table-column
prop="stationVoltageLevel"
align="center"
label="电压等级"
width="150"
/>
<el-table-column
prop="runFlag"
align="center"
label="是否监测"
width="130"
>
<template v-slot="scope">
<el-tag
v-if="scope.row.runFlag == 0"
size="small"
type="primary"
effect="dark"
></el-tag
>
<el-tag v-else size="small" type="warning" effect="dark"></el-tag>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
size="small"
style="margin-top: 10px"
:currentPage="form.pageNum"
:page-size="form.pageSize"
:page-sizes="[10, 20, 50, 100, 200]"
background
:layout="'sizes,total, ->, prev, pager, next, jumper'"
:total="total"
@size-change="onTableSizeChange"
@current-change="onTableCurrentChange"
></el-pagination>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, inject, onMounted } from "vue";
import { ElMessage } from "element-plus";
import { Search, Download } from "@element-plus/icons-vue";
import { stationPage } from "@/api/manage_wx";
import { useStore } from "vuex";
import * as XLSX from "xlsx";
const store = useStore();
const machineVisible = ref(false);
const title = ref("");
const formRef = ref();
const loading = ref(false);
const form = reactive({
searchValue: "",
pageNum: 1,
pageSize: 20,
deptId: "",
runFlag: "",
// startTime: "",
// endTime: "",
});
const total = ref(0); // 假设总条数为100
const tableData = ref([]);
//form表单校验规则
const open = (text: string, data?: any) => {
title.value = text;
machineVisible.value = true;
form.searchValue = "";
form.pageNum = 1;
form.pageSize = 20;
form.runFlag = "";
getTableData();
};
const onTableSizeChange = (size: number) => {
form.pageSize = size;
// 重新加载数据
getTableData();
};
const onTableCurrentChange = (page: number) => {
form.pageNum = page;
// 重新加载数据
getTableData();
};
const getTableData = async () => {
loading.value = true;
form.deptId = store.state.deptId;
// form.startTime = store.state.timeValue[0];
// form.endTime = store.state.timeValue[1];
const res: any = await stationPage(form);
if (res.code == "A0000") {
tableData.value = res.data.records;
total.value = res.data.total;
}
loading.value = false;
};
// 导出
const exportTable = async () => {
// 示例数据
let columnExpor: any = [["变电站名称", "供电公司", "电压等级", "是否监测"]];
let list = [];
await stationPage({
...form,
pageNum: 1,
pageSize: total.value,
}).then((res) => {
let data = res.data.records.map((item) => {
return [
item.stationName,
item.gdName,
item.stationVoltageLevel,
item.runFlag == 0 ? "是" : "否",
];
});
list = [...columnExpor, ...data];
// 创建工作表
const worksheet = XLSX.utils.aoa_to_sheet(list);
worksheet["!cols"] = list.map((col) => ({ wch: 20 }));
// 创建工作簿
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, title.value);
// 写出文件
XLSX.writeFile(workbook, title.value + ".xlsx");
});
};
// onMounted(() => {
// getTableData();
// });
defineExpose({ open });
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.tableBox {
padding: 0px !important;
}
.formBox {
display: flex;
justify-content: space-between;
}
</style>

View File

@@ -0,0 +1,129 @@
<template>
<!--右下 暂降事件聚合成功暂降事件聚合成功列表 -->
<div class="plan">
<div class="titleBox">
事件列表
<div class="titles">
<div
class="react-right"
:class="flag == 0 ? 'titleClick' : ''"
@click="flag = 0"
>
<span class="text"> 暂降</span>
</div>
<div
class="react-right"
:class="flag == 1 ? 'titleClick' : ''"
@click="flag = 1"
>
<span class="text"> 谐波</span>
</div>
</div>
</div>
<div class="tableBox" v-if="flag == 0">
<el-table
:scrollbar-always-on="true" :data="tableData"
height="250px"
size="small"
:header-cell-style="{ textAlign: 'center' }"
border
>
<el-table-column prop="date" align="center" label="时间" width="160" />
<el-table-column
prop="name"
align="center"
label="事件关联分析名称"
width="160"
/>
<el-table-column prop="cj" align="center" label="事件关联分析描述" />
<el-table-column prop="address" align="center" label="操作" width="90">
<template #default="scope">
<el-button size="small" type="primary" text> 影响范围 </el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="tableBox" v-if="flag == 1">
<el-table
:scrollbar-always-on="true" :data="tableData"
height="250px"
size="small"
:header-cell-style="{ textAlign: 'center' }"
border
>
<el-table-column prop="date" align="center" label="时间" width="160" />
<el-table-column
prop="center"
align="center"
label="事件内容"
/>
<!-- <el-table-column prop="cj" align="center" label="事件关联分析描述" /> -->
<el-table-column prop="address" align="center" label="操作" width="70">
<template #default="scope">
<el-button size="small" type="primary" text> 编辑 </el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
const flag = ref(0); // 0 for 暂降, 1 for 谐波
const tableData = [
{
date: "2024-01-01 17:49:03",
name: "2024-01-01 17:49:03.475",
cj: "事件关联分析编号2024-01-01 17:49:03.475共包含1个事件",
center:
"监测点ID:xxxxxxxxx;起始时间:2024-01-01 17:49:03;结束时间:2024-01-01 17:49:03.475;触发类型:暂降;",
},
{
date: "2024-01-02 19:28:40",
name: "2024-01-02 19:28:40.090",
cj: "事件关联分析编号2024-01-01 17:49:03.475共包含1个事件",
center:
"监测点ID:xxxxxxxxx;起始时间:2024-01-01 17:49:03;结束时间:2024-01-01 17:49:03.475;触发类型:暂降;",
},
{
date: "2024-01-02 19:29:35",
name: "2024-01-02 19:28:40.090",
cj: "事件关联分析编号2024-01-01 17:49:03.475共包含1个事件",
center:
"监测点ID:xxxxxxxxx;起始时间:2024-01-01 17:49:03;结束时间:2024-01-01 17:49:03.475;触发类型:暂降;",
},
{
date: "2024-01-02 19:29:52",
name: "2024-01-02 19:28:40.090",
cj: "事件关联分析编号2024-01-01 17:49:03.475共包含1个事件",
center:
"监测点ID:xxxxxxxxx;起始时间:2024-01-01 17:49:03;结束时间:2024-01-01 17:49:03.475;触发类型:暂降;",
},
{
date: "2024-01-02 19:29:52",
name: "2024-01-02 19:28:40.090",
cj: "事件关联分析编号2024-01-01 17:49:03.475共包含1个事件",
center:
"监测点ID:xxxxxxxxx;起始时间:2024-01-01 17:49:03;结束时间:2024-01-01 17:49:03.475;触发类型:暂降;",
},
];
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.plan {
width: 100%;
height: 295px;
.titles {
margin-right: 10px;
.react-right {
width: 60px !important;
line-height: 20px !important;
font-size: 14px !important;
}
}
}
</style>