Files
bigscreenWeb/src/views/SagTraceResult_WX/components/eventStatistics/system.vue
2025-10-11 09:54:24 +08:00

990 lines
27 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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) => {
if (res.code == "A0000") {
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;
} else {
ElMessage.warning(res.message);
loading1.value = false;
}
})
.catch((error) => {
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>