Compare commits
18 Commits
ed0800359d
...
2025-10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ec3bd11a0 | ||
|
|
d553754847 | ||
|
|
dc44e16d4d | ||
|
|
13c0a28c95 | ||
|
|
443d5ab2bd | ||
|
|
288f4254b0 | ||
|
|
130db82e41 | ||
|
|
55a30a323d | ||
|
|
608be23687 | ||
|
|
8f8f2aad6e | ||
|
|
a9dcb54286 | ||
|
|
dede918f34 | ||
|
|
d113df832d | ||
|
|
f41af33413 | ||
|
|
f2e328826d | ||
|
|
b3154e894a | ||
|
|
f3252f8a15 | ||
|
|
7f5296daee |
10908
package-lock.json
generated
10908
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -26,8 +26,10 @@
|
||||
"element-plus": "^2.7.5",
|
||||
"exceljs": "v4.4.0",
|
||||
"file-saver": "v2.0.5",
|
||||
"grid-layout-plus": "^1.1.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jquery": "^3.7.1",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"jszip": "^3.10.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
@@ -38,20 +40,18 @@
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"qs": "^6.12.0",
|
||||
"screenfull": "^6.0.2",
|
||||
"splitpanes": "3.1.5",
|
||||
"splitpanes": "^3.1.5",
|
||||
"use-element-plus-theme": "^0.0.5",
|
||||
"vue": "^3.3.11",
|
||||
"vue-draggable-resizable": "3.0.0-beta.2",
|
||||
"vue-draggable-resizable": "^3.0.0-beta.2",
|
||||
"vue-i18n": "9.10.2",
|
||||
"vue-router": "4",
|
||||
"vue-types": "^5.1.1",
|
||||
"vxe-table": "^4.5.17",
|
||||
"vxe-table-plugin-export-xlsx": "^4.0.7",
|
||||
"grid-layout-plus": "^1.1.0",
|
||||
"vue-i18n": "9.10.2",
|
||||
"qs": "^6.12.0",
|
||||
"vue-types": "^5.1.1",
|
||||
"web-storage-cache": "^1.1.1",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"xe-utils": "^3.5.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -61,9 +61,9 @@
|
||||
"@types/splitpanes": "2.2.6",
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"unplugin-auto-import": "^0.16.7",
|
||||
"sass": "^1.69.5",
|
||||
"typescript": "^5.2.2",
|
||||
"unplugin-auto-import": "^0.16.7",
|
||||
"vite": "^5.0.8",
|
||||
"vue-tsc": "^1.8.25"
|
||||
}
|
||||
|
||||
@@ -47,11 +47,13 @@ export function restartProcess(data: any) {
|
||||
}
|
||||
|
||||
//更新进程号
|
||||
export function updateProcess(data: any) {
|
||||
export function updateProcess(data:any) {
|
||||
return createAxios({
|
||||
url: '/cs-device-boot/node/updateDevProcessNo',
|
||||
url: '/cs-device-boot/node/updateDevNode',
|
||||
method: 'post',
|
||||
params: data
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -77,3 +77,11 @@ export const queryByPagePath = (params: any) => {
|
||||
params
|
||||
})
|
||||
}
|
||||
// 根据用户id查询用户驾驶舱
|
||||
export const getDashboardPageByUserId = (params: any) => {
|
||||
return createAxios({
|
||||
url: '/system-boot/dashboard/getDashboardPageByUserId',
|
||||
method: 'post',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
BIN
src/assets/img/lightning.png
Normal file
BIN
src/assets/img/lightning.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 KiB |
@@ -8,10 +8,16 @@
|
||||
<el-descriptions-item align="center" label="不可容忍">{{ data.bkrr }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<my-echart
|
||||
ref="chartRef"
|
||||
class="tall"
|
||||
:options="echartList"
|
||||
:style="{ width: prop.width, height: `calc(${prop.height} - 80px)` }"
|
||||
@chart-click="handleChartClick"
|
||||
/>
|
||||
<el-dialog v-model="isWaveCharts" draggable title="瞬时/RMS波形" append-to-body width="70%">
|
||||
<waveFormAnalysis v-loading="loading" v-if="isWaveCharts" ref="waveFormAnalysisRef"
|
||||
@handleHideCharts="isWaveCharts = false" :wp="wp" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
@@ -22,6 +28,7 @@ import MyEchart from '@/components/echarts/MyEchart.vue'
|
||||
import { useDictData } from '@/stores/dictData'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getTimeOfTheMonth } from '@/utils/formatTime'
|
||||
import waveFormAnalysis from '@/views/govern/device/control/tabs/components/waveFormAnalysis.vue';
|
||||
const prop = defineProps({
|
||||
width: { type: String },
|
||||
height: { type: String },
|
||||
@@ -30,6 +37,15 @@ const prop = defineProps({
|
||||
})
|
||||
|
||||
const echartList = ref({})
|
||||
|
||||
const chartRef = ref()
|
||||
// 波形
|
||||
const isWaveCharts = ref(false)
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const wp = ref({})
|
||||
|
||||
const OverLimitDetailsRef = ref()
|
||||
const data = reactive({
|
||||
name: '事件个数',
|
||||
@@ -64,7 +80,11 @@ const tableStore: any = new TableStore({
|
||||
text: `F47曲线`
|
||||
},
|
||||
legend: {
|
||||
data: ['上限', '下限', '可容忍事件', '不可容忍事件']
|
||||
// data: ['上限', '下限', '可容忍事件', '不可容忍事件'],
|
||||
data: ['可容忍事件', '不可容忍事件'],
|
||||
itemWidth: 10,
|
||||
itemHeight: 10,
|
||||
itemGap: 15
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
@@ -141,13 +161,40 @@ const tableStore: any = new TableStore({
|
||||
name: '可容忍事件',
|
||||
type: 'scatter',
|
||||
symbol: 'circle',
|
||||
data: gongData.pointF
|
||||
symbolSize: 8,
|
||||
// data: gongData.pointF,
|
||||
data: [
|
||||
[0.2, 10, '2023-01-01 10:00:00'],
|
||||
[0.4, 50, '2023-01-01 11:00:00']
|
||||
],
|
||||
legendSymbol: 'circle',
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
itemStyle: {
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
shadowBlur: 10,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'item',
|
||||
formatter: function (params: any) {
|
||||
return `<strong>可容忍事件</strong><br/>
|
||||
持续时间: ${params.value[0]}s<br/>
|
||||
特征幅值: ${params.value[1].toFixed(2)}%<br/>
|
||||
发生时间: ${params.value[2] || 'N/A'}`
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '不可容忍事件',
|
||||
type: 'scatter',
|
||||
symbol: 'circle',
|
||||
data: gongData.pointFun
|
||||
symbol: 'rect',
|
||||
symbolSize: 8,
|
||||
data: gongData.pointFun,
|
||||
legendSymbol: 'rect'
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -323,6 +370,32 @@ onMounted(() => {
|
||||
tableStore.index()
|
||||
}, 100)
|
||||
})
|
||||
|
||||
// 点击事件处理函数
|
||||
const handleChartClick = (params: any) => {
|
||||
|
||||
if (params.seriesName === '可容忍事件') {
|
||||
// 处理可容忍事件点击
|
||||
ElMessage.info(`点击了可容忍事件: 持续时间${params.value[0]}s, 幅值${params.value[1].toFixed(2)}%`)
|
||||
handleTolerableEventClick(params)
|
||||
} else if (params.seriesName === '不可容忍事件') {
|
||||
// 处理不可容忍事件点击
|
||||
ElMessage.info(`点击了不可容忍事件: 持续时间${params.value[0]}s, 幅值${params.value[1].toFixed(2)}%`)
|
||||
handleIntolerableEventClick(params)
|
||||
}
|
||||
}
|
||||
|
||||
// 可容忍事件点击处理函数
|
||||
const handleTolerableEventClick = (params: any) => {
|
||||
console.log('可容忍事件详情:', params)
|
||||
isWaveCharts.value = true
|
||||
}
|
||||
|
||||
// 不可容忍事件点击处理函数
|
||||
const handleIntolerableEventClick = (params: any) => {
|
||||
console.log('不可容忍事件详情:', params)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => prop.timeKey,
|
||||
val => {
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<!-- 指标日趋势图 -->
|
||||
<el-dialog draggable title="指标日趋势图" v-model="dialogVisible" append-to-body width="70%">
|
||||
<my-echart
|
||||
class="tall"
|
||||
:options="echartList"
|
||||
style="width: 98%; height: 320px"
|
||||
/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
|
||||
import TableStore from '@/utils/tableStore'
|
||||
import TableHeader from '@/components/table/header/index.vue'
|
||||
import { getTimeOfTheMonth } from '@/utils/formatTime'
|
||||
import MyEchart from '@/components/echarts/MyEchart.vue'
|
||||
import { useConfig } from '@/stores/config'
|
||||
const prop = defineProps({
|
||||
width: { type: String },
|
||||
height: { type: String },
|
||||
timeKey: { type: String },
|
||||
timeValue: { type: Object }
|
||||
})
|
||||
|
||||
const dialogVisible: any = ref(false)
|
||||
|
||||
|
||||
const config = useConfig()
|
||||
|
||||
const echartList = ref({
|
||||
title: {
|
||||
text: '35kV进线谐波含有率',
|
||||
},
|
||||
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
axisLabel: {
|
||||
formatter: {
|
||||
day: '{MM}-{dd}',
|
||||
month: '{MM}',
|
||||
year: '{yyyy}'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
yAxis: [{}, {}],
|
||||
grid: {
|
||||
left: '10px',
|
||||
right: '20px'
|
||||
},
|
||||
options: {
|
||||
series: [
|
||||
{
|
||||
// name: '暂降次数',
|
||||
type: 'bar',
|
||||
name: '有功功率',
|
||||
data: [
|
||||
['2025-10-16 07:00:00', 10],
|
||||
['2025-10-16 07:15:00', 10],
|
||||
['2025-10-16 07:30:00', 10],
|
||||
['2025-10-16 07:45:00', 10],
|
||||
['2025-10-16 08:00:00', 30],
|
||||
['2025-10-16 08:15:00', 50],
|
||||
['2025-10-16 08:30:00', 60],
|
||||
['2025-10-16 08:45:00', 70],
|
||||
['2025-10-16 09:00:00', 100],
|
||||
['2025-10-16 09:15:00', 120],
|
||||
['2025-10-16 09:30:00', 130],
|
||||
['2025-10-16 09:45:00', 140],
|
||||
['2025-10-16 10:00:00', 160],
|
||||
['2025-10-16 10:15:00', 160],
|
||||
['2025-10-16 10:30:00', 130],
|
||||
['2025-10-16 10:45:00', 120],
|
||||
['2025-10-16 11:00:00', 140],
|
||||
['2025-10-16 11:15:00', 80],
|
||||
['2025-10-16 11:30:00', 70],
|
||||
['2025-10-16 11:45:00', 90],
|
||||
['2025-10-16 12:00:00', 60],
|
||||
['2025-10-16 12:15:00', 60],
|
||||
['2025-10-16 12:30:00', 60],
|
||||
['2025-10-16 12:45:00', 60]
|
||||
],
|
||||
itemStyle: {
|
||||
normal: {
|
||||
//这里是颜色
|
||||
color: function (params: any) {
|
||||
if (params.value[1] == 0 || params.value[1] == 3.14159) {
|
||||
return '#ccc'
|
||||
} else {
|
||||
return config.layout.elementUiPrimary[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxisIndex: 0
|
||||
},
|
||||
{
|
||||
name: '谐波总畸变率',
|
||||
type: 'line',
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
data: [
|
||||
['2025-10-16 07:00:00', 0],
|
||||
['2025-10-16 07:15:00', 0],
|
||||
['2025-10-16 07:30:00', 0],
|
||||
['2025-10-16 07:45:00', 0],
|
||||
['2025-10-16 08:00:00', 0],
|
||||
['2025-10-16 08:15:00', 0.1],
|
||||
['2025-10-16 08:30:00', 0.1],
|
||||
['2025-10-16 08:45:00', 0.1],
|
||||
['2025-10-16 09:00:00', 1],
|
||||
['2025-10-16 09:15:00', 1],
|
||||
['2025-10-16 09:30:00', 1],
|
||||
['2025-10-16 09:45:00', 1],
|
||||
['2025-10-16 10:00:00', 0.8],
|
||||
['2025-10-16 10:15:00', 0.8],
|
||||
['2025-10-16 10:30:00', 0.8],
|
||||
['2025-10-16 10:45:00', 0.8],
|
||||
['2025-10-16 11:00:00', 0.8],
|
||||
['2025-10-16 11:15:00', 0.1],
|
||||
['2025-10-16 11:30:00', 0.1],
|
||||
['2025-10-16 11:45:00', 0.1],
|
||||
['2025-10-16 12:00:00', 0],
|
||||
['2025-10-16 12:15:00', 0],
|
||||
['2025-10-16 12:30:00', 0],
|
||||
['2025-10-16 12:45:00', 0]
|
||||
],
|
||||
yAxisIndex: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
})
|
||||
|
||||
|
||||
const open = async (row: any) => {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-select) {
|
||||
min-width: 80px;
|
||||
}
|
||||
</style>
|
||||
@@ -7,8 +7,8 @@
|
||||
:style="{ width: prop.width, height: `calc(${prop.height} / 2 )` }"
|
||||
/>
|
||||
<Table ref="tableRef" @cell-click="cellClickEvent" :height="`calc(${prop.height} / 2 )`" isGroup></Table>
|
||||
<!-- 指标越限详情 -->
|
||||
<OverLimitDetails ref="OverLimitDetailsRef" />
|
||||
<!-- 指标日趋势图 -->
|
||||
<DailyTrendChart ref="dailyTrendChartRef" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
@@ -19,7 +19,7 @@ import MyEchart from '@/components/echarts/MyEchart.vue'
|
||||
import { useDictData } from '@/stores/dictData'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getTimeOfTheMonth } from '@/utils/formatTime'
|
||||
import OverLimitDetails from '@/components/cockpit/listOfMainMonitoringPoints/components/overLimitDetails.vue'
|
||||
import DailyTrendChart from '@/components/cockpit/exceedanceLevel/components/dailyTrendChart.vue'
|
||||
const prop = defineProps({
|
||||
width: { type: String },
|
||||
height: { type: String },
|
||||
@@ -66,7 +66,7 @@ const echartList = ref({
|
||||
]
|
||||
}
|
||||
})
|
||||
const OverLimitDetailsRef = ref()
|
||||
const dailyTrendChartRef = ref()
|
||||
const tableStore: any = new TableStore({
|
||||
url: '/user-boot/dept/deptTree',
|
||||
method: 'POST',
|
||||
@@ -178,7 +178,7 @@ provide('tableStore', tableStore)
|
||||
const cellClickEvent = ({ row, column }: any) => {
|
||||
if (column.field != 'name') {
|
||||
console.log(row)
|
||||
OverLimitDetailsRef.value.open(row)
|
||||
dailyTrendChartRef.value.open(row)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--治理效果报表 -->
|
||||
<TableHeader :showReset="false">
|
||||
<TableHeader :showReset="false" v-if="fullscreen">
|
||||
<template v-slot:select>
|
||||
<el-form-item label="治理对象">
|
||||
<el-select
|
||||
@@ -26,13 +26,16 @@
|
||||
<div style="display: flex">
|
||||
<div
|
||||
id="luckysheet"
|
||||
:style="{ width: `calc(${prop.width} )`, height: `calc(${prop.height} - 57px )` }"
|
||||
:style="{
|
||||
width: `calc(${prop.width} )`,
|
||||
height: `calc(${prop.height} - 57px + ${fullscreen ? 0 : 56}px)`
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
|
||||
import { ref, onMounted, provide, reactive, watch, h, computed } from 'vue'
|
||||
import TableStore from '@/utils/tableStore'
|
||||
import { exportExcel } from '@/views/govern/reportForms/export.js'
|
||||
import TableHeader from '@/components/table/header/index.vue'
|
||||
@@ -44,6 +47,8 @@ import { useConfig } from '@/stores/config'
|
||||
import Json from './index.json'
|
||||
|
||||
const prop = defineProps({
|
||||
w: { type: String },
|
||||
h: { type: String },
|
||||
width: { type: String },
|
||||
height: { type: String },
|
||||
timeKey: { type: String },
|
||||
@@ -98,6 +103,17 @@ onMounted(() => {
|
||||
|
||||
tableStore.index()
|
||||
})
|
||||
// 计算是否全屏展示
|
||||
const fullscreen = computed(() => {
|
||||
const w = Number(prop.w)
|
||||
const h = Number(prop.h)
|
||||
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
|
||||
// 执行相应逻辑
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
watch(
|
||||
() => prop.timeKey,
|
||||
val => {
|
||||
@@ -120,5 +136,4 @@ const addMenu = () => {}
|
||||
:deep(.el-select) {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<!-- 总体指标占比详情谐波含有率 -->
|
||||
<el-dialog draggable title="谐波电压/电流含有率" v-model="dialogVisible" append-to-body width="70%">
|
||||
<TableHeader datePicker showExport :showReset="false" @selectChange="selectChange">
|
||||
<template v-slot:select>
|
||||
<el-form-item label="谐波次数">
|
||||
<el-select
|
||||
v-model="tableStore.table.params.searchValue"
|
||||
placeholder="请选择谐波次数"
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="指标类型">
|
||||
<el-select
|
||||
v-model="tableStore.table.params.searchValue"
|
||||
placeholder="请选择指标类型"
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</TableHeader>
|
||||
<my-echart
|
||||
class="tall"
|
||||
:options="echartList"
|
||||
style="width: 98%; height: 320px"
|
||||
/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
|
||||
import TableStore from '@/utils/tableStore'
|
||||
import TableHeader from '@/components/table/header/index.vue'
|
||||
import { getTimeOfTheMonth } from '@/utils/formatTime'
|
||||
import MyEchart from '@/components/echarts/MyEchart.vue'
|
||||
import { useConfig } from '@/stores/config'
|
||||
const prop = defineProps({
|
||||
width: { type: String },
|
||||
height: { type: String },
|
||||
timeKey: { type: String },
|
||||
timeValue: { type: Object }
|
||||
})
|
||||
|
||||
const dialogVisible: any = ref(false)
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: '35kV进线',
|
||||
label: '35kV进线'
|
||||
}
|
||||
]
|
||||
|
||||
const config = useConfig()
|
||||
const powerList: any = ref([
|
||||
{
|
||||
label: '三相总有功功率',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: '三相总无功功率',
|
||||
value: '2'
|
||||
}
|
||||
])
|
||||
const exceedingTheLimitList: any = ref([
|
||||
{
|
||||
label: '越限',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: '不越限',
|
||||
value: '0'
|
||||
}
|
||||
])
|
||||
const indicatorList: any = ref([
|
||||
{
|
||||
label: '谐波电压总畸变率',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: '各次谐波电压',
|
||||
value: '2'
|
||||
},
|
||||
{
|
||||
label: '各次谐波电压',
|
||||
value: '3'
|
||||
},
|
||||
{
|
||||
label: '三相电压不平衡',
|
||||
value: '4'
|
||||
}
|
||||
])
|
||||
const echartList = ref({
|
||||
title: {
|
||||
text: '35kV进线谐波含有率',
|
||||
},
|
||||
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
axisLabel: {
|
||||
formatter: {
|
||||
day: '{MM}-{dd}',
|
||||
month: '{MM}',
|
||||
year: '{yyyy}'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
yAxis: [{}, {}],
|
||||
grid: {
|
||||
left: '10px',
|
||||
right: '20px'
|
||||
},
|
||||
options: {
|
||||
series: [
|
||||
{
|
||||
// name: '暂降次数',
|
||||
type: 'bar',
|
||||
name: '有功功率',
|
||||
data: [
|
||||
['2025-10-16 07:00:00', 10],
|
||||
['2025-10-16 07:15:00', 10],
|
||||
['2025-10-16 07:30:00', 10],
|
||||
['2025-10-16 07:45:00', 10],
|
||||
['2025-10-16 08:00:00', 30],
|
||||
['2025-10-16 08:15:00', 50],
|
||||
['2025-10-16 08:30:00', 60],
|
||||
['2025-10-16 08:45:00', 70],
|
||||
['2025-10-16 09:00:00', 100],
|
||||
['2025-10-16 09:15:00', 120],
|
||||
['2025-10-16 09:30:00', 130],
|
||||
['2025-10-16 09:45:00', 140],
|
||||
['2025-10-16 10:00:00', 160],
|
||||
['2025-10-16 10:15:00', 160],
|
||||
['2025-10-16 10:30:00', 130],
|
||||
['2025-10-16 10:45:00', 120],
|
||||
['2025-10-16 11:00:00', 140],
|
||||
['2025-10-16 11:15:00', 80],
|
||||
['2025-10-16 11:30:00', 70],
|
||||
['2025-10-16 11:45:00', 90],
|
||||
['2025-10-16 12:00:00', 60],
|
||||
['2025-10-16 12:15:00', 60],
|
||||
['2025-10-16 12:30:00', 60],
|
||||
['2025-10-16 12:45:00', 60]
|
||||
],
|
||||
itemStyle: {
|
||||
normal: {
|
||||
//这里是颜色
|
||||
color: function (params: any) {
|
||||
if (params.value[1] == 0 || params.value[1] == 3.14159) {
|
||||
return '#ccc'
|
||||
} else {
|
||||
return config.layout.elementUiPrimary[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxisIndex: 0
|
||||
},
|
||||
{
|
||||
name: '谐波总畸变率',
|
||||
type: 'line',
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
data: [
|
||||
['2025-10-16 07:00:00', 0],
|
||||
['2025-10-16 07:15:00', 0],
|
||||
['2025-10-16 07:30:00', 0],
|
||||
['2025-10-16 07:45:00', 0],
|
||||
['2025-10-16 08:00:00', 0],
|
||||
['2025-10-16 08:15:00', 0.1],
|
||||
['2025-10-16 08:30:00', 0.1],
|
||||
['2025-10-16 08:45:00', 0.1],
|
||||
['2025-10-16 09:00:00', 1],
|
||||
['2025-10-16 09:15:00', 1],
|
||||
['2025-10-16 09:30:00', 1],
|
||||
['2025-10-16 09:45:00', 1],
|
||||
['2025-10-16 10:00:00', 0.8],
|
||||
['2025-10-16 10:15:00', 0.8],
|
||||
['2025-10-16 10:30:00', 0.8],
|
||||
['2025-10-16 10:45:00', 0.8],
|
||||
['2025-10-16 11:00:00', 0.8],
|
||||
['2025-10-16 11:15:00', 0.1],
|
||||
['2025-10-16 11:30:00', 0.1],
|
||||
['2025-10-16 11:45:00', 0.1],
|
||||
['2025-10-16 12:00:00', 0],
|
||||
['2025-10-16 12:15:00', 0],
|
||||
['2025-10-16 12:30:00', 0],
|
||||
['2025-10-16 12:45:00', 0]
|
||||
],
|
||||
yAxisIndex: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
const headerHeight = ref(57)
|
||||
const selectChange = (showSelect: any, height: any) => {
|
||||
headerHeight.value = height
|
||||
}
|
||||
const tableStore: any = new TableStore({
|
||||
url: '/user-boot/role/selectRoleDetail?id=0',
|
||||
method: 'POST',
|
||||
|
||||
showPage: false,
|
||||
exportName: '主要监测点列表',
|
||||
column: [],
|
||||
beforeSearchFun: () => {},
|
||||
loadCallback: () => {
|
||||
tableStore.table.height = `calc(${prop.height} - 80px)`
|
||||
}
|
||||
})
|
||||
|
||||
const tableRef = ref()
|
||||
provide('tableRef', tableRef)
|
||||
tableStore.table.params.power = '1'
|
||||
tableStore.table.params.indicator = '1'
|
||||
tableStore.table.params.exceedingTheLimit = '1'
|
||||
tableStore.table.params.searchValue = ''
|
||||
provide('tableStore', tableStore)
|
||||
|
||||
onMounted(() => {
|
||||
tableStore.index()
|
||||
})
|
||||
watch(
|
||||
() => prop.timeKey,
|
||||
val => {
|
||||
tableStore.index()
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => prop.timeValue, // 监听的目标(函数形式避免直接传递 props 导致的警告)
|
||||
(newVal, oldVal) => {
|
||||
tableStore.index()
|
||||
},
|
||||
{
|
||||
deep: true // 若 timeValue 是对象/数组,需开启深度监听
|
||||
}
|
||||
)
|
||||
|
||||
const openDialog = async (row: any) => {
|
||||
dialogVisible.value = true
|
||||
tableStore.index()
|
||||
}
|
||||
|
||||
defineExpose({ openDialog })
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-select) {
|
||||
min-width: 80px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,21 +1,30 @@
|
||||
<template>
|
||||
<!-- 综合评估详情 -->
|
||||
<el-dialog draggable title="指标合格率统计" v-model="dialogVisible" append-to-body width="70%">
|
||||
<TableHeader datePicker showExport :showReset="false">
|
||||
<template v-slot:select>
|
||||
<el-form-item label="监测点名称">
|
||||
<el-select
|
||||
v-model="tableStore.table.params.searchValue"
|
||||
placeholder="请选择监测点名称"
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</TableHeader>
|
||||
<Table ref="tableRef" isGroup :height="height"></Table>
|
||||
</el-dialog>
|
||||
<div>
|
||||
<!-- 综合评估详情 -->
|
||||
<el-dialog draggable title="指标合格率统计" v-model="dialogVisible" append-to-body width="70%">
|
||||
<TableHeader datePicker showExport :showReset="false">
|
||||
<template v-slot:select>
|
||||
<el-form-item label="监测点名称">
|
||||
<el-select
|
||||
v-model="tableStore.table.params.searchValue"
|
||||
placeholder="请选择监测点名称"
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</TableHeader>
|
||||
<Table ref="tableRef" @cell-click="cellClickEvent" isGroup :height="height"></Table>
|
||||
</el-dialog>
|
||||
<!-- 谐波电流、谐波电压占有率 -->
|
||||
<HarmonicRatio ref="harmonicRatioRef" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, provide } from 'vue'
|
||||
@@ -23,7 +32,9 @@ import Table from '@/components/table/index.vue'
|
||||
import TableHeader from '@/components/table/header/index.vue'
|
||||
import TableStore from '@/utils/tableStore'
|
||||
import { mainHeight } from '@/utils/layout'
|
||||
import HarmonicRatio from '@/components/cockpit/listOfMainMonitoringPoints/components/harmonicRatio.vue'
|
||||
const dialogVisible: any = ref(false)
|
||||
const harmonicRatioRef: any = ref(null)
|
||||
const options = [
|
||||
{
|
||||
value: '35kV进线',
|
||||
@@ -38,7 +49,11 @@ const loop50 = (key: string) => {
|
||||
title: i + '次',
|
||||
// field: key + i,
|
||||
field: 'flicker',
|
||||
width: '80'
|
||||
width: '80',
|
||||
render: 'customTemplate',
|
||||
customTemplate: (row: any) => {
|
||||
return `<span style='cursor: pointer;text-decoration: underline;'>${row.flicker}</span>`
|
||||
}
|
||||
})
|
||||
}
|
||||
return list
|
||||
@@ -71,7 +86,11 @@ const tableStore: any = new TableStore({
|
||||
{
|
||||
title: '闪变越限(分钟)',
|
||||
field: 'flicker',
|
||||
width: '80'
|
||||
width: '80',
|
||||
render: 'customTemplate',
|
||||
customTemplate: (row: any) => {
|
||||
return `<span style='cursor: pointer;text-decoration: underline;'>${row.flicker}</span>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '谐波电压越限(分钟)',
|
||||
@@ -103,21 +122,18 @@ const tableStore: any = new TableStore({
|
||||
{
|
||||
time: '2024-01-01 00:00:00',
|
||||
name: '35kV进线',
|
||||
flicker: '0',
|
||||
|
||||
flicker: '0'
|
||||
},
|
||||
{
|
||||
time: '2024-01-01 00:00:00',
|
||||
name: '35kV进线',
|
||||
flicker: '0',
|
||||
|
||||
flicker: '0'
|
||||
},
|
||||
{
|
||||
time: '2024-01-01 00:00:00',
|
||||
name: '35kV进线',
|
||||
flicker: '0',
|
||||
|
||||
},
|
||||
flicker: '0'
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
@@ -129,6 +145,14 @@ const open = async (row: any) => {
|
||||
tableStore.index()
|
||||
}
|
||||
|
||||
// 点击行
|
||||
const cellClickEvent = ({ row, column }: any) => {
|
||||
if (column.field != 'name' && column.field != 'time') {
|
||||
harmonicRatioRef.value.openDialog(row)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--指标拟合图 -->
|
||||
<TableHeader :showReset="false" @selectChange="selectChange">
|
||||
<TableHeader :showReset="false" datePicker @selectChange="selectChange" v-if="fullscreen">
|
||||
<template v-slot:select>
|
||||
<el-form-item label="用户功率">
|
||||
<el-select
|
||||
@@ -53,12 +53,15 @@
|
||||
<my-echart
|
||||
class="tall"
|
||||
:options="echartList"
|
||||
:style="{ width: prop.width, height: `calc(${prop.height} - ${headerHeight}px )` }"
|
||||
:style="{
|
||||
width: prop.width,
|
||||
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
|
||||
import { ref, onMounted, provide, reactive, watch, h, computed } from 'vue'
|
||||
import TableStore from '@/utils/tableStore'
|
||||
import Table from '@/components/table/index.vue'
|
||||
import TableHeader from '@/components/table/header/index.vue'
|
||||
@@ -68,6 +71,8 @@ import { getTimeOfTheMonth } from '@/utils/formatTime'
|
||||
import MyEchart from '@/components/echarts/MyEchart.vue'
|
||||
import { useConfig } from '@/stores/config'
|
||||
const prop = defineProps({
|
||||
w: { type: String },
|
||||
h: { type: String },
|
||||
width: { type: String },
|
||||
height: { type: String },
|
||||
timeKey: { type: String },
|
||||
@@ -84,6 +89,17 @@ const powerList: any = ref([
|
||||
value: '2'
|
||||
}
|
||||
])
|
||||
// 计算是否全屏展示
|
||||
const fullscreen = computed(() => {
|
||||
const w = Number(prop.w)
|
||||
const h = Number(prop.h)
|
||||
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
|
||||
// 执行相应逻辑
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
const exceedingTheLimitList: any = ref([
|
||||
{
|
||||
label: '越限',
|
||||
|
||||
@@ -63,8 +63,8 @@ const tableStore: any = new TableStore({
|
||||
minWidth: '80',
|
||||
render: 'customTemplate',
|
||||
customTemplate: (row: any) => {
|
||||
return `<span style='cursor: pointer;text-decoration: underline;'>${row.type2}</span>`
|
||||
}
|
||||
return `<span style='cursor: pointer;text-decoration: underline;' onclick="handleReportClick('${row.name}')">${row.type2}</span>`
|
||||
},
|
||||
},
|
||||
{ title: '监测点名称', field: 'type3', minWidth: '70' },
|
||||
{ title: '监测类型', field: 'type4', minWidth: '60' },
|
||||
@@ -164,6 +164,30 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
const addMenu = () => {}
|
||||
const handleReportClick = (id: string) => {
|
||||
const row = tableStore.table.data.find((item: any) => item.id === id)
|
||||
if (row && row.type2 !== '/') {
|
||||
// 示例:触发下载逻辑(根据你注释掉的代码)
|
||||
// getFileZip({ eventId: id }).then(res => {
|
||||
// let blob = new Blob([res], { type: 'application/zip' })
|
||||
// const url = window.URL.createObjectURL(blob)
|
||||
// const link = document.createElement('a')
|
||||
// link.href = url
|
||||
// link.download = row.wavePath?.split('/')[2] || '报告文件.zip'
|
||||
// link.click()
|
||||
// })
|
||||
console.log('点击了报告:', row.type2)
|
||||
} else {
|
||||
ElMessage.warning('暂无报告可下载')
|
||||
}
|
||||
}
|
||||
|
||||
// 挂载到 window 供 HTML 调用
|
||||
window.handleReportClick = handleReportClick
|
||||
|
||||
// 组件销毁时清理全局方法
|
||||
onBeforeUnmount(() => {
|
||||
delete window.handleReportClick
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 暂态事件列表 -->
|
||||
<el-dialog draggable title="暂态事件列表" v-model="dialogVisible" append-to-body width="70%">
|
||||
<!-- <TableHeader datePicker showExport :showReset="false">
|
||||
<template v-slot:select>
|
||||
<el-form-item label="监测点名称">
|
||||
<el-select
|
||||
v-model="tableStore.table.params.searchValue"
|
||||
placeholder="请选择监测点名称"
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</TableHeader> -->
|
||||
<Table ref="tableRef" isGroup :height="height"></Table>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, provide } from 'vue'
|
||||
import Table from '@/components/table/index.vue'
|
||||
import TableHeader from '@/components/table/header/index.vue'
|
||||
import TableStore from '@/utils/tableStore'
|
||||
import { mainHeight } from '@/utils/layout'
|
||||
const dialogVisible: any = ref(false)
|
||||
const harmonicRatioRef: any = ref(null)
|
||||
const options = [
|
||||
{
|
||||
value: '35kV进线',
|
||||
label: '35kV进线'
|
||||
}
|
||||
]
|
||||
const height = mainHeight(0, 2).height as any
|
||||
const tableStore: any = new TableStore({
|
||||
url: '/user-boot/role/selectRoleDetail?id=0',
|
||||
method: 'POST',
|
||||
publicHeight: 30,
|
||||
showPage: false,
|
||||
exportName: '主要监测点列表',
|
||||
column: [
|
||||
{
|
||||
field: 'index',
|
||||
title: '序号',
|
||||
width: '80',
|
||||
formatter: (row: any) => {
|
||||
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '暂态时间',
|
||||
field: 'time',
|
||||
},
|
||||
{
|
||||
title: '测点名称',
|
||||
field: 'name',
|
||||
width: '150'
|
||||
},
|
||||
{
|
||||
title: '暂态类型',
|
||||
field: 'flicker',
|
||||
width: '100',
|
||||
},
|
||||
{
|
||||
title: '特征幅值(%)',
|
||||
field: 'flicker',
|
||||
width: '100'
|
||||
},
|
||||
{
|
||||
title: '暂降深度(%)',
|
||||
field: 'flicker',
|
||||
width: '100'
|
||||
},
|
||||
{
|
||||
title: '持续时间(S)',
|
||||
field: 'flicker',
|
||||
width: '100'
|
||||
},
|
||||
{
|
||||
title: '严重度',
|
||||
field: 'flicker',
|
||||
width: '80'
|
||||
},
|
||||
],
|
||||
beforeSearchFun: () => {},
|
||||
loadCallback: () => {
|
||||
tableStore.table.data = [
|
||||
{
|
||||
time: '2024-01-01 00:00:00',
|
||||
name: '35kV进线',
|
||||
flicker: '0'
|
||||
},
|
||||
{
|
||||
time: '2024-01-01 00:00:00',
|
||||
name: '35kV进线',
|
||||
flicker: '0'
|
||||
},
|
||||
{
|
||||
time: '2024-01-01 00:00:00',
|
||||
name: '35kV进线',
|
||||
flicker: '0'
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
tableStore.table.params.searchValue = ''
|
||||
provide('tableStore', tableStore)
|
||||
const open = async (row: any) => {
|
||||
dialogVisible.value = true
|
||||
tableStore.index()
|
||||
}
|
||||
|
||||
// 点击行
|
||||
const cellClickEvent = ({ row, column }: any) => {
|
||||
if (column.field != 'name') {
|
||||
harmonicRatioRef.value.openDialog(row)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -23,10 +23,11 @@
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
style="text-decoration: underline"
|
||||
:style="{ height: `calc(${prop.height} / 5 - 47px)`, overflow: 'auto' }"
|
||||
v-for="item in list?.filter(item => item.time == data.day)"
|
||||
>
|
||||
<div>电压暂降:{{ item.type || '' }}</div>
|
||||
<div @click="descentClick">电压暂降:{{ item.type || '' }}</div>
|
||||
<div>电压中断:{{ item.type1 || '' }}</div>
|
||||
<div>电压暂升:{{ item.type2 || '' }}</div>
|
||||
</div>
|
||||
@@ -34,6 +35,8 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-calendar>
|
||||
<!-- 暂态事件列表 -->
|
||||
<TransientList ref="transientListRef" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
@@ -45,6 +48,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getTimeOfTheMonth } from '@/utils/formatTime'
|
||||
import { overflow } from 'html2canvas/dist/types/css/property-descriptors/overflow'
|
||||
import { dayjs } from 'element-plus'
|
||||
import TransientList from './components/transientList.vue'
|
||||
|
||||
dayjs.en.weekStart = 1 //设置日历的周起始日为星期一
|
||||
const value = ref(new Date())
|
||||
@@ -54,6 +58,8 @@ const prop = defineProps({
|
||||
timeKey: { type: String },
|
||||
timeValue: { type: Object }
|
||||
})
|
||||
|
||||
const transientListRef = ref()
|
||||
const list = ref([
|
||||
{
|
||||
time: '2025-10-01',
|
||||
@@ -154,7 +160,12 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
const addMenu = () => {}
|
||||
// 电压暂降点击事件
|
||||
const descentClick = () => {
|
||||
transientListRef.value.open()
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-calendar) {
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 暂态事件详情 -->
|
||||
<el-dialog draggable title="暂态事件详情 " v-model="dialogVisible" append-to-body width="70%">
|
||||
<TableHeader datePicker showExport :showReset="false">
|
||||
<template v-slot:select>
|
||||
<el-form-item label="监测点名称">
|
||||
<el-select
|
||||
v-model="tableStore.table.params.searchValue"
|
||||
placeholder="请选择监测点名称"
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</TableHeader>
|
||||
<Table ref="tableRef" @cell-click="cellClickEvent" isGroup :height="height"></Table>
|
||||
</el-dialog>
|
||||
<!-- 查看波形 -->
|
||||
<HarmonicRatio ref="harmonicRatioRef" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, provide } from 'vue'
|
||||
import Table from '@/components/table/index.vue'
|
||||
import TableHeader from '@/components/table/header/index.vue'
|
||||
import TableStore from '@/utils/tableStore'
|
||||
import { mainHeight } from '@/utils/layout'
|
||||
import HarmonicRatio from '@/components/cockpit/listOfMainMonitoringPoints/components/harmonicRatio.vue'
|
||||
const dialogVisible: any = ref(false)
|
||||
const harmonicRatioRef: any = ref(null)
|
||||
const options = [
|
||||
{
|
||||
value: '35kV进线',
|
||||
label: '35kV进线'
|
||||
}
|
||||
]
|
||||
const height = mainHeight(0, 2).height as any
|
||||
const tableStore: any = new TableStore({
|
||||
url: '/user-boot/role/selectRoleDetail?id=0',
|
||||
method: 'POST',
|
||||
publicHeight: 30,
|
||||
showPage: false,
|
||||
exportName: '主要监测点列表',
|
||||
column: [
|
||||
{
|
||||
field: 'index',
|
||||
title: '序号',
|
||||
width: '80',
|
||||
formatter: (row: any) => {
|
||||
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '暂态时间',
|
||||
field: 'time',
|
||||
},
|
||||
{
|
||||
title: '测点名称',
|
||||
field: 'name',
|
||||
width: '150'
|
||||
},
|
||||
{
|
||||
title: '暂态类型',
|
||||
field: 'flicker',
|
||||
width: '100',
|
||||
},
|
||||
{
|
||||
title: '特征幅值(%)',
|
||||
field: 'flicker',
|
||||
width: '100'
|
||||
},
|
||||
{
|
||||
title: '暂降深度(%)',
|
||||
field: 'flicker',
|
||||
width: '100'
|
||||
},
|
||||
{
|
||||
title: '持续时间(S)',
|
||||
field: 'flicker',
|
||||
width: '100'
|
||||
},
|
||||
{
|
||||
title: '严重度',
|
||||
field: 'flicker',
|
||||
width: '80'
|
||||
},
|
||||
{
|
||||
title: '波形',
|
||||
width: '100',
|
||||
render: 'buttons',
|
||||
buttons: [
|
||||
{
|
||||
name: 'check',
|
||||
title: '查看波形',
|
||||
type: 'primary',
|
||||
icon: 'el-icon-EditPen',
|
||||
render: 'basicButton',
|
||||
click: row => {
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
beforeSearchFun: () => {},
|
||||
loadCallback: () => {
|
||||
tableStore.table.data = [
|
||||
{
|
||||
time: '2024-01-01 00:00:00',
|
||||
name: '35kV进线',
|
||||
flicker: '0'
|
||||
},
|
||||
{
|
||||
time: '2024-01-01 00:00:00',
|
||||
name: '35kV进线',
|
||||
flicker: '0'
|
||||
},
|
||||
{
|
||||
time: '2024-01-01 00:00:00',
|
||||
name: '35kV进线',
|
||||
flicker: '0'
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
tableStore.table.params.searchValue = ''
|
||||
provide('tableStore', tableStore)
|
||||
const open = async (row: any) => {
|
||||
dialogVisible.value = true
|
||||
tableStore.index()
|
||||
}
|
||||
|
||||
// 点击行
|
||||
const cellClickEvent = ({ row, column }: any) => {
|
||||
if (column.field != 'name') {
|
||||
harmonicRatioRef.value.openDialog(row)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -7,6 +7,7 @@
|
||||
:style="{ width: prop.width, height: `calc(${prop.height} / 2 )` }"
|
||||
/>
|
||||
<Table ref="tableRef" @cell-click="cellClickEvent" :height="`calc(${prop.height} / 2 )`" isGroup></Table>
|
||||
<TransientStatisticsDetail ref="transientStatisticsDetailRef"></TransientStatisticsDetail>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
@@ -18,6 +19,7 @@ import { useDictData } from '@/stores/dictData'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getTimeOfTheMonth } from '@/utils/formatTime'
|
||||
import { useConfig } from '@/stores/config'
|
||||
import TransientStatisticsDetail from './components/transientStatisticsDetail.vue'
|
||||
const config = useConfig()
|
||||
const prop = defineProps({
|
||||
width: { type: String },
|
||||
@@ -95,7 +97,7 @@ const echartList = ref({
|
||||
]
|
||||
}
|
||||
})
|
||||
const OverLimitDetailsRef = ref()
|
||||
const transientStatisticsDetailRef = ref()
|
||||
const tableStore: any = new TableStore({
|
||||
url: '/user-boot/dept/deptTree',
|
||||
method: 'POST',
|
||||
@@ -187,8 +189,7 @@ provide('tableStore', tableStore)
|
||||
// 点击行
|
||||
const cellClickEvent = ({ row, column }: any) => {
|
||||
if (column.field != 'name') {
|
||||
console.log(row)
|
||||
OverLimitDetailsRef.value.open(row)
|
||||
transientStatisticsDetailRef.value.open(row)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--趋势对比 -->
|
||||
<TableHeader :showReset="false" @selectChange="selectChange">
|
||||
<TableHeader :showReset="false" @selectChange="selectChange" v-if="fullscreen">
|
||||
<template v-slot:select>
|
||||
<el-form-item label="监测点名称">
|
||||
<el-select
|
||||
@@ -53,12 +53,12 @@
|
||||
<my-echart
|
||||
class="tall"
|
||||
:options="echartList"
|
||||
:style="{ width: prop.width, height: `calc(${prop.height} - ${headerHeight}px )` }"
|
||||
:style="{ width: prop.width, height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)` }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, provide, reactive, watch, h } from 'vue'
|
||||
import { ref, onMounted, provide, reactive, watch, h, computed } from 'vue'
|
||||
import TableStore from '@/utils/tableStore'
|
||||
import Table from '@/components/table/index.vue'
|
||||
import TableHeader from '@/components/table/header/index.vue'
|
||||
@@ -68,6 +68,8 @@ import { getTimeOfTheMonth } from '@/utils/formatTime'
|
||||
import MyEchart from '@/components/echarts/MyEchart.vue'
|
||||
import { useConfig } from '@/stores/config'
|
||||
const prop = defineProps({
|
||||
w: { type: String },
|
||||
h: { type: String },
|
||||
width: { type: String },
|
||||
height: { type: String },
|
||||
timeKey: { type: String },
|
||||
@@ -84,6 +86,17 @@ const powerList: any = ref([
|
||||
value: '2'
|
||||
}
|
||||
])
|
||||
// 计算是否全屏展示
|
||||
const fullscreen = computed(() => {
|
||||
const w = Number(prop.w)
|
||||
const h = Number(prop.h)
|
||||
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
|
||||
// 执行相应逻辑
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
const exceedingTheLimitList: any = ref([
|
||||
{
|
||||
label: '2次',
|
||||
|
||||
@@ -15,6 +15,7 @@ import { color, gradeColor3 } from './color'
|
||||
import { useConfig } from '@/stores/config'
|
||||
// import { nextTick } from 'process'
|
||||
|
||||
const emit = defineEmits(['chartClick'])
|
||||
const config = useConfig()
|
||||
color[0] = config.layout.elementUiPrimary[0]
|
||||
const chartRef = ref<HTMLDivElement>()
|
||||
@@ -89,7 +90,7 @@ const initChart = () => {
|
||||
...(props.options?.legend || null)
|
||||
},
|
||||
grid: {
|
||||
top: '50px',
|
||||
top: '50px',
|
||||
left: '30px',
|
||||
right: '70px',
|
||||
bottom: props.options?.options?.dataZoom === null ? '10px' : '40px',
|
||||
@@ -131,6 +132,10 @@ const initChart = () => {
|
||||
// 处理柱状图
|
||||
chart.setOption(options, true)
|
||||
chart.group = 'group'
|
||||
// 添加点击事件
|
||||
chart.on('click', function (params: any) {
|
||||
emit('chartClick', params)
|
||||
})
|
||||
setTimeout(() => {
|
||||
chart.resize()
|
||||
}, 0)
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<el-tree
|
||||
:style="{ height: bxsDeviceData.length != 0 ? 'calc(100vh - 340px)' : 'calc(100vh - 278px)' }"
|
||||
ref="treeRef1" :props="defaultProps" highlight-current :filter-node-method="filterNode"
|
||||
node-key="id" default-expand-all v-bind="$attrs" :data="zlDevList" style="overflow: auto">
|
||||
node-key="id" :default-expand-all="false" v-bind="$attrs" :data="zlDevList" style="overflow: auto">
|
||||
<template #default="{ node, data }">
|
||||
<span class="custom-tree-node">
|
||||
<Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }"
|
||||
@@ -44,7 +44,7 @@
|
||||
<el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length != 0">
|
||||
<el-tree
|
||||
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 280px)' : 'calc(100vh - 238px)' }"
|
||||
ref="treeRef2" :props="defaultProps" highlight-current default-expand-all
|
||||
ref="treeRef2" :props="defaultProps" highlight-current :default-expand-all="false"
|
||||
:filter-node-method="filterNode" node-key="id" :data="bxsDeviceData" v-bind="$attrs"
|
||||
style="overflow: auto">
|
||||
<template #default="{ node, data }">
|
||||
@@ -59,7 +59,7 @@
|
||||
<el-collapse-item title="在线设备" name="2" v-if="frontDeviceData.length != 0">
|
||||
<el-tree
|
||||
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 280px)' : 'calc(100vh - 238px)' }"
|
||||
ref="treeRef3" :props="defaultProps" highlight-current default-expand-all
|
||||
ref="treeRef3" :props="defaultProps" highlight-current :default-expand-all="false"
|
||||
:filter-node-method="filterNode" node-key="id" :data="frontDeviceData" v-bind="$attrs"
|
||||
style="overflow: auto">
|
||||
<template #default="{ node, data }">
|
||||
@@ -124,6 +124,7 @@ watch(
|
||||
item.children.map((vv: any) => {
|
||||
zlDeviceData.value.push(vv)
|
||||
})
|
||||
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
|
||||
} else if (item.name == '便携式设备') {
|
||||
bxsDeviceData.value = []
|
||||
item.children.map((vv: any) => {
|
||||
@@ -156,8 +157,6 @@ watch(filterText, val => {
|
||||
}
|
||||
})
|
||||
watch(process, val => {
|
||||
|
||||
|
||||
if (val == '') {
|
||||
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
|
||||
} else {
|
||||
@@ -177,10 +176,10 @@ function filterProcess(nodes: any) {
|
||||
const children = node.children ? filterProcess(node.children) : []
|
||||
|
||||
// 如果当前节点的process=4,或者有子节点满足条件,则保留当前节点
|
||||
if (node.process == process.value || children.length > 0) {
|
||||
if ( node.process == process.value || children.length > 0) {
|
||||
return {
|
||||
...node,
|
||||
children: node.children
|
||||
children: children
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ getDeviceTree().then(res => {
|
||||
item2.color = config.getColorVal('elementUiPrimary')
|
||||
item2.children.forEach((item3: any) => {
|
||||
item3.icon = 'el-icon-Platform'
|
||||
item3.level = 2
|
||||
item3.color = config.getColorVal('elementUiPrimary')
|
||||
if (item3.comFlag === 1) {
|
||||
item3.color = '#e26257 !important'
|
||||
|
||||
@@ -46,7 +46,7 @@ const info = () => {
|
||||
item2.color = config.getColorVal('elementUiPrimary')
|
||||
item2.children.forEach((item3: any) => {
|
||||
item3.icon = 'el-icon-Platform'
|
||||
item3.level = 1
|
||||
item3.level = 2
|
||||
item3.color =
|
||||
item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
|
||||
item3.children.forEach((item4: any) => {
|
||||
|
||||
@@ -63,7 +63,7 @@ const info = () => {
|
||||
nextTick(() => {
|
||||
if (arr3.length) {
|
||||
//初始化选中
|
||||
treRef.value.treeRef1.setCurrentKey(arr3[0].id)
|
||||
treRef.value.treeRef.setCurrentKey(arr3[0].id)
|
||||
// 注册父组件事件
|
||||
emit('init', {
|
||||
level: 2,
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
<el-tree
|
||||
:style="{ height: bxsDeviceData.length != 0 ? 'calc(100vh - 340px)' : 'calc(100vh - 278px)' }"
|
||||
ref="treeRef1" :props="defaultProps" highlight-current :filter-node-method="filterNode"
|
||||
node-key="id" default-expand-all v-bind="$attrs" :data="zlDevList" style="overflow: auto">
|
||||
node-key="id" v-bind="$attrs" :data="zlDevList" style="overflow: auto"
|
||||
:default-expand-all="false">
|
||||
<template #default="{ node, data }">
|
||||
<span class="custom-tree-node">
|
||||
<Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }"
|
||||
@@ -41,9 +42,9 @@
|
||||
<el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length != 0">
|
||||
<el-tree
|
||||
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 330px)' : 'calc(100vh - 238px)' }"
|
||||
ref="treeRef2" :props="defaultProps" highlight-current default-expand-all
|
||||
ref="treeRef2" :props="defaultProps" highlight-current :default-expand-all="false"
|
||||
:filter-node-method="filterNode" node-key="id" :data="bxsDeviceData" v-bind="$attrs"
|
||||
style="overflow: auto">
|
||||
style="overflow: auto" >
|
||||
<template #default="{ node, data }">
|
||||
<span class="custom-tree-node">
|
||||
<Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }"
|
||||
@@ -56,7 +57,7 @@
|
||||
<el-collapse-item title="在线设备" name="2" v-if="yqfDeviceData.length != 0">
|
||||
<el-tree
|
||||
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 330px)' : 'calc(100vh - 238px)' }"
|
||||
ref="treeRef3" :props="defaultProps" highlight-current default-expand-all
|
||||
ref="treeRef3" :props="defaultProps" highlight-current :default-expand-all="false"
|
||||
:filter-node-method="filterNode" node-key="id" :data="yqfDeviceData" v-bind="$attrs"
|
||||
style="overflow: auto">
|
||||
<template #default="{ node, data }">
|
||||
@@ -76,7 +77,7 @@
|
||||
<script lang="ts" setup>
|
||||
import useCurrentInstance from '@/utils/useCurrentInstance'
|
||||
import { ElTree } from 'element-plus'
|
||||
import { el } from 'element-plus/es/locale'
|
||||
import { el, fa } from 'element-plus/es/locale'
|
||||
import { ref, watch, defineEmits, onMounted, nextTick, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
defineOptions({
|
||||
@@ -117,13 +118,14 @@ watch(
|
||||
() => props.data,
|
||||
(val, oldVal) => {
|
||||
if (val && val.length != 0) {
|
||||
|
||||
val.map((item: any) => {
|
||||
if (item.name == '治理设备') {
|
||||
zlDeviceData.value = []
|
||||
item.children.map((vv: any) => {
|
||||
zlDeviceData.value.push(vv)
|
||||
})
|
||||
// console.log('🚀 ~ item.children.map ~ zlDeviceData.value:', zlDeviceData.value)
|
||||
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
|
||||
} else if (item.name == '便携式设备') {
|
||||
bxsDeviceData.value = []
|
||||
item.children.map((vv: any) => {
|
||||
@@ -155,18 +157,23 @@ watch(filterText, val => {
|
||||
}
|
||||
})
|
||||
watch(process, val => {
|
||||
|
||||
if (val == '') {
|
||||
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
|
||||
} else {
|
||||
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
|
||||
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
changeDevice(activeName.value)
|
||||
}, 0)
|
||||
})
|
||||
|
||||
|
||||
|
||||
const changeDevice = (val: any) => {
|
||||
console.log('changeDevice', val)
|
||||
|
||||
let arr1: any = []
|
||||
//zlDeviceData
|
||||
zlDevList.value.forEach((item: any) => {
|
||||
@@ -231,6 +238,7 @@ const filterNode = (value: string, data: any, node: any) => {
|
||||
return chooseNode(value, data, node)
|
||||
}
|
||||
}
|
||||
|
||||
function filterProcess(nodes: any) {
|
||||
if (process.value == '') {
|
||||
return nodes
|
||||
@@ -240,20 +248,58 @@ function filterProcess(nodes: any) {
|
||||
// 递归处理子节点
|
||||
const children = node.children ? filterProcess(node.children) : []
|
||||
|
||||
// 如果当前节点的process=4,或者有子节点满足条件,则保留当前节点
|
||||
// 对于装置层级(level=2),只保留 process 值匹配的节点
|
||||
if (node.level === 2) {
|
||||
if (node.process == process.value) {
|
||||
return {
|
||||
...node,
|
||||
children: children
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
if (node.process == process.value || children.length > 0) {
|
||||
// 对于其他节点:
|
||||
// 1. 如果有满足条件的子节点则保留
|
||||
// 2. 如果本身 process 值匹配则保留
|
||||
// 3. 如果是叶子节点也保留(监测点通常没有子节点)
|
||||
if (children.length > 0 || node.process == process.value ||
|
||||
(!node.children || node.children.length === 0)) {
|
||||
return {
|
||||
...node,
|
||||
children: node.children
|
||||
children: children
|
||||
}
|
||||
}
|
||||
|
||||
// 否则过滤掉当前节点
|
||||
return null
|
||||
})
|
||||
.filter(Boolean) // 移除null节点
|
||||
}
|
||||
|
||||
|
||||
// function filterProcess(nodes: any) {
|
||||
// if (process.value == '') {
|
||||
// return nodes
|
||||
// }
|
||||
// return nodes
|
||||
// .map(node => {
|
||||
// // 递归处理子节点
|
||||
// const children = node.children ? filterProcess(node.children) : []
|
||||
|
||||
// // 如果当前节点的process=4,或者有子节点满足条件,则保留当前节点
|
||||
|
||||
// if (node.process == process.value || children.length > 0) {
|
||||
// return {
|
||||
// ...node,
|
||||
// children: node.children
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 否则过滤掉当前节点
|
||||
// return null
|
||||
// })
|
||||
// .filter(Boolean) // 移除null节点
|
||||
// }
|
||||
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配,则返回该节点以及其下的所有子节点;如果参数是子节点,则返回该节点的父节点。name是中文字符,enName是英文字符.
|
||||
const chooseNode = (value: string, data: any, node: any) => {
|
||||
if (data.name.indexOf(value) !== -1) {
|
||||
@@ -288,7 +334,9 @@ const treeRef2 = ref<InstanceType<typeof ElTree>>()
|
||||
const treeRef3 = ref<InstanceType<typeof ElTree>>()
|
||||
defineExpose({ treeRef1, treeRef2 })
|
||||
onMounted(() => {
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
if (zlDeviceData.value.length != 0) {
|
||||
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
|
||||
activeName.value = '0'
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
@node-click="handleNodeClick"
|
||||
:default-checked-keys="defaultCheckedKeys"
|
||||
v-bind='$attrs'
|
||||
|
||||
|
||||
>
|
||||
<template #default='{ node, data }'>
|
||||
<span class='custom-tree-node'>
|
||||
|
||||
@@ -1,104 +1,104 @@
|
||||
<template>
|
||||
<el-main class="layout-main" :style="mainHeight()">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition :name="config.layout.mainAnimation" mode="out-in">
|
||||
<keep-alive :include="state.keepAliveComponentNameList">
|
||||
<component :is="Component" :key="state.componentKey" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</el-main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, watch, onBeforeMount, onUnmounted, nextTick, provide } from 'vue'
|
||||
import { useRoute, type RouteLocationNormalized } from 'vue-router'
|
||||
import { mainHeight } from '@/utils/layout'
|
||||
import useCurrentInstance from '@/utils/useCurrentInstance'
|
||||
import { useConfig } from '@/stores/config'
|
||||
import { useNavTabs } from '@/stores/navTabs'
|
||||
|
||||
defineOptions({
|
||||
name: 'layout/main'
|
||||
})
|
||||
|
||||
const { proxy } = useCurrentInstance()
|
||||
|
||||
const route = useRoute()
|
||||
const config = useConfig()
|
||||
const navTabs = useNavTabs()
|
||||
|
||||
const state: {
|
||||
componentKey: string
|
||||
keepAliveComponentNameList: string[]
|
||||
} = reactive({
|
||||
componentKey: route.path,
|
||||
keepAliveComponentNameList: []
|
||||
})
|
||||
|
||||
const addKeepAliveComponentName = function (keepAliveName: string | undefined) {
|
||||
if (keepAliveName) {
|
||||
let exist = state.keepAliveComponentNameList.find((name: string) => {
|
||||
return name === keepAliveName
|
||||
})
|
||||
if (exist) return
|
||||
state.keepAliveComponentNameList.push(keepAliveName)
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
proxy.eventBus.on('onTabViewRefresh', (menu: RouteLocationNormalized) => {
|
||||
state.keepAliveComponentNameList = state.keepAliveComponentNameList.filter(
|
||||
(name: string) => menu.meta.keepalive !== name
|
||||
)
|
||||
state.componentKey = ''
|
||||
nextTick(() => {
|
||||
state.componentKey = menu.path
|
||||
addKeepAliveComponentName(menu.meta.keepalive as string)
|
||||
})
|
||||
})
|
||||
proxy.eventBus.on('onTabViewClose', (menu: RouteLocationNormalized) => {
|
||||
state.keepAliveComponentNameList = state.keepAliveComponentNameList.filter(
|
||||
(name: string) => menu.meta.keepalive !== name
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
proxy.eventBus.off('onTabViewRefresh')
|
||||
proxy.eventBus.off('onTabViewClose')
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 确保刷新页面时也能正确取得当前路由 keepalive 参数
|
||||
if (typeof navTabs.state.activeRoute?.meta.keepalive == 'string') {
|
||||
addKeepAliveComponentName(navTabs.state.activeRoute?.meta.keepalive)
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
state.componentKey = route.path
|
||||
if (typeof navTabs.state.activeRoute?.meta.keepalive == 'string') {
|
||||
addKeepAliveComponentName(navTabs.state.activeRoute?.meta.keepalive)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-container .layout-main {
|
||||
padding: 0 !important;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.layout-main-scrollbar {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
//overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<el-main class="layout-main" :style="mainHeight()">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition :name="config.layout.mainAnimation" mode="out-in">
|
||||
<keep-alive :include="state.keepAliveComponentNameList">
|
||||
<component :is="Component" :key="state.componentKey" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</el-main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, watch, onBeforeMount, onUnmounted, nextTick, provide } from 'vue'
|
||||
import { useRoute, type RouteLocationNormalized } from 'vue-router'
|
||||
import { mainHeight } from '@/utils/layout'
|
||||
import useCurrentInstance from '@/utils/useCurrentInstance'
|
||||
import { useConfig } from '@/stores/config'
|
||||
import { useNavTabs } from '@/stores/navTabs'
|
||||
|
||||
defineOptions({
|
||||
name: 'layout/main'
|
||||
})
|
||||
|
||||
const { proxy } = useCurrentInstance()
|
||||
|
||||
const route = useRoute()
|
||||
const config = useConfig()
|
||||
const navTabs = useNavTabs()
|
||||
|
||||
const state: {
|
||||
componentKey: string
|
||||
keepAliveComponentNameList: string[]
|
||||
} = reactive({
|
||||
componentKey: route.path,
|
||||
keepAliveComponentNameList: []
|
||||
})
|
||||
|
||||
const addKeepAliveComponentName = function (keepAliveName: string | undefined) {
|
||||
if (keepAliveName) {
|
||||
let exist = state.keepAliveComponentNameList.find((name: string) => {
|
||||
return name === keepAliveName
|
||||
})
|
||||
if (exist) return
|
||||
state.keepAliveComponentNameList.push(keepAliveName)
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
proxy.eventBus.on('onTabViewRefresh', (menu: RouteLocationNormalized) => {
|
||||
state.keepAliveComponentNameList = state.keepAliveComponentNameList.filter(
|
||||
(name: string) => menu.meta.keepalive !== name
|
||||
)
|
||||
state.componentKey = ''
|
||||
nextTick(() => {
|
||||
state.componentKey = menu.path
|
||||
addKeepAliveComponentName(menu.meta.keepalive as string)
|
||||
})
|
||||
})
|
||||
proxy.eventBus.on('onTabViewClose', (menu: RouteLocationNormalized) => {
|
||||
state.keepAliveComponentNameList = state.keepAliveComponentNameList.filter(
|
||||
(name: string) => menu.meta.keepalive !== name
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
proxy.eventBus.off('onTabViewRefresh')
|
||||
proxy.eventBus.off('onTabViewClose')
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 确保刷新页面时也能正确取得当前路由 keepalive 参数
|
||||
if (typeof navTabs.state.activeRoute?.meta.keepalive == 'string') {
|
||||
addKeepAliveComponentName(navTabs.state.activeRoute?.meta.keepalive)
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
state.componentKey = route.path
|
||||
if (typeof navTabs.state.activeRoute?.meta.keepalive == 'string') {
|
||||
addKeepAliveComponentName(navTabs.state.activeRoute?.meta.keepalive)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-container .layout-main {
|
||||
padding: 0 !important;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.layout-main-scrollbar {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
//overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,157 +1,296 @@
|
||||
const dataProcessing = (arr: any[]) => {
|
||||
return arr
|
||||
.filter(item => typeof item === 'number' || (typeof item === 'string' && !isNaN(parseFloat(item))))
|
||||
.map(item => (typeof item === 'number' ? item : parseFloat(item)))
|
||||
}
|
||||
|
||||
const calculateValue = (o:number,value: number, num: number, isMin: boolean) => {
|
||||
if (value === 0) {
|
||||
return 0
|
||||
}else if(value>0&& Math.abs(value)<1 && isMin==true){
|
||||
return 0
|
||||
}else if(value>-1&& value<0 && isMin==false){
|
||||
return 0
|
||||
}
|
||||
let base
|
||||
if (Math.abs(o) >= 100) {
|
||||
base = 100
|
||||
} else if (Math.abs(o) >= 10) {
|
||||
base = 10
|
||||
} else if (Math.abs(o) >= 1) {
|
||||
base = 1
|
||||
} else {
|
||||
base = 0.1
|
||||
}
|
||||
let calculatedValue
|
||||
if (isMin) {
|
||||
if (value < 0) {
|
||||
calculatedValue = value + num * value
|
||||
} else {
|
||||
calculatedValue = value - num * value
|
||||
}
|
||||
} else {
|
||||
if (value < 0) {
|
||||
calculatedValue = value - num * value
|
||||
} else {
|
||||
calculatedValue = value + num * value
|
||||
}
|
||||
}
|
||||
if (base === 0.1) {
|
||||
return parseFloat(calculatedValue.toFixed(1))
|
||||
} else if (isMin) {
|
||||
return Math.floor(calculatedValue / base) * base
|
||||
} else {
|
||||
return Math.ceil(calculatedValue / base) * base
|
||||
}
|
||||
}
|
||||
|
||||
// 处理y轴最大最小值
|
||||
export const yMethod = (arr: any) => {
|
||||
let num = 0.2
|
||||
let numList = dataProcessing(arr)
|
||||
let maxValue = 0
|
||||
let minValue = 0
|
||||
let max = 0
|
||||
let min = 0
|
||||
maxValue = Math.max(...numList)
|
||||
minValue = Math.min(...numList)
|
||||
const o=maxValue-minValue
|
||||
min = calculateValue( o,minValue, num, true)
|
||||
max = calculateValue(o,maxValue, num, false)
|
||||
// if (-100 >= minValue) {
|
||||
// min = Math.floor((minValue + num * minValue) / 100) * 100
|
||||
// } else if (-10 >= minValue && minValue > -100) {
|
||||
// min = Math.floor((minValue + num * minValue) / 10) * 10
|
||||
// } else if (-1 >= minValue && minValue > -10) {
|
||||
// min = Math.floor(minValue + num * minValue)
|
||||
// } else if (0 > minValue && minValue > -1) {
|
||||
// min = parseFloat((minValue + num * minValue).toFixed(1))
|
||||
// } else if (minValue == 0) {
|
||||
// min = 0
|
||||
// } else if (0 < minValue && minValue < 1) {
|
||||
// min = parseFloat((minValue - num * minValue).toFixed(1))
|
||||
// } else if (1 <= minValue && minValue < 10) {
|
||||
// min = Math.floor(minValue - num * minValue)
|
||||
// } else if (10 <= minValue && minValue < 100) {
|
||||
// min = Math.floor((minValue - num * minValue) / 10) * 10
|
||||
// } else if (100 <= minValue) {
|
||||
// min = Math.floor((minValue - num * minValue) / 100) * 100
|
||||
// }
|
||||
|
||||
// if (-100 >= maxValue) {
|
||||
// max = Math.ceil((maxValue - num * maxValue) / 100) * 100
|
||||
// } else if (-10 >= maxValue && maxValue > -100) {
|
||||
// max = Math.ceil((maxValue - num * maxValue) / 10) * 10
|
||||
// } else if (-1 >= maxValue && maxValue > -10) {
|
||||
// max = Math.ceil(maxValue - num * maxValue)
|
||||
// } else if (0 > maxValue && maxValue > -1) {
|
||||
// max = parseFloat((maxValue - num * maxValue).toFixed(1))
|
||||
// } else if (maxValue == 0) {
|
||||
// max = 0
|
||||
// } else if (0 < maxValue && maxValue < 1) {
|
||||
// max = parseFloat((maxValue + num * maxValue).toFixed(1))
|
||||
// } else if (1 <= maxValue && maxValue < 10) {
|
||||
// max = Math.ceil(maxValue + num * maxValue)
|
||||
// } else if (10 <= maxValue && maxValue < 100) {
|
||||
// max = Math.ceil((maxValue + num * maxValue) / 10) * 10
|
||||
// } else if (100 <= maxValue) {
|
||||
// max = Math.ceil((maxValue + num * maxValue) / 100) * 100
|
||||
// }
|
||||
|
||||
// if (maxValue > 1000 || minValue < -1000) {
|
||||
// max = Math.ceil(maxValue / 100) * 100
|
||||
// if (minValue == 0) {
|
||||
// min = 0
|
||||
// } else {
|
||||
// min = Math.floor(minValue / 100) * 100
|
||||
// }
|
||||
// } else if (maxValue < 60 && minValue > 40) {
|
||||
// max = 60
|
||||
// min = 40
|
||||
// } else if (maxValue == minValue && maxValue < 10 && minValue > 0) {
|
||||
// max = Math.ceil(maxValue / 10) * 10
|
||||
// min = Math.floor(minValue / 10) * 10
|
||||
// } else if (maxValue == minValue && maxValue != 0 && minValue != 0) {
|
||||
// max = Math.ceil(maxValue / 10 + 1) * 10
|
||||
// min = Math.floor(minValue / 10 - 1) * 10
|
||||
// } else {
|
||||
// max = Math.ceil(maxValue / 10) * 10
|
||||
// min = Math.floor(minValue / 10) * 10
|
||||
// }
|
||||
|
||||
// if (maxValue > 0 && maxValue < 1) {
|
||||
// max = 1
|
||||
// } else if (max == 0 && minValue > -1 && minValue < 0) {
|
||||
// min = -1
|
||||
// }
|
||||
|
||||
return [min, max]
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* title['A相','B相',]
|
||||
* data[[1,2],[3,4]]
|
||||
*/
|
||||
// 导出csv文件
|
||||
const convertToCSV = (title: object, data: any) => {
|
||||
console.log('🚀 ~ convertToCSV ~ data:', data)
|
||||
let csv = ''
|
||||
// 添加列头
|
||||
csv += ',' + title.join(',') + '\n'
|
||||
// 遍历数据并添加到CSV字符串中
|
||||
data?.map(item => {
|
||||
csv += item.join(',') + '\n'
|
||||
})
|
||||
return csv
|
||||
}
|
||||
export const exportCSV = (title: object, data: any, filename: string) => {
|
||||
const csv = convertToCSV(title, data)
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = filename
|
||||
link.click()
|
||||
// 释放URL对象
|
||||
URL.revokeObjectURL(link.href)
|
||||
}
|
||||
const dataProcessing = (arr: any[]) => {
|
||||
return arr
|
||||
.filter(item => typeof item === 'number' || (typeof item === 'string' && !isNaN(parseFloat(item))))
|
||||
.map(item => (typeof item === 'number' ? item : parseFloat(item)))
|
||||
}
|
||||
|
||||
const calculateValue = (o: number, value: number, num: number, isMin: boolean) => {
|
||||
if (value === 0) {
|
||||
return 0
|
||||
} else if (value > 0 && Math.abs(value) < 1 && isMin == true) {
|
||||
return 0
|
||||
} else if (value > -1 && value < 0 && isMin == false) {
|
||||
return 0
|
||||
}
|
||||
let base
|
||||
if (Math.abs(o) >= 100) {
|
||||
base = 100
|
||||
} else if (Math.abs(o) >= 10) {
|
||||
base = 10
|
||||
} else if (Math.abs(o) >= 1) {
|
||||
base = 1
|
||||
} else {
|
||||
base = 0.1
|
||||
}
|
||||
let calculatedValue
|
||||
if (isMin) {
|
||||
if (value < 0) {
|
||||
calculatedValue = value + num * value
|
||||
} else {
|
||||
calculatedValue = value - num * value
|
||||
}
|
||||
} else {
|
||||
if (value < 0) {
|
||||
calculatedValue = value - num * value
|
||||
} else {
|
||||
calculatedValue = value + num * value
|
||||
}
|
||||
}
|
||||
if (base === 0.1) {
|
||||
return parseFloat(calculatedValue.toFixed(1))
|
||||
} else if (isMin) {
|
||||
return Math.floor(calculatedValue / base) * base
|
||||
} else {
|
||||
return Math.ceil(calculatedValue / base) * base
|
||||
}
|
||||
}
|
||||
|
||||
// 处理y轴最大最小值
|
||||
export const yMethod = (arr: any) => {
|
||||
let num = 0.2
|
||||
let numList = dataProcessing(arr)
|
||||
let maxValue = 0
|
||||
let minValue = 0
|
||||
let max = 0
|
||||
let min = 0
|
||||
maxValue = Math.max(...numList)
|
||||
minValue = Math.min(...numList)
|
||||
const o = maxValue - minValue
|
||||
min = calculateValue(o, minValue, num, true)
|
||||
max = calculateValue(o, maxValue, num, false)
|
||||
// if (-100 >= minValue) {
|
||||
// min = Math.floor((minValue + num * minValue) / 100) * 100
|
||||
// } else if (-10 >= minValue && minValue > -100) {
|
||||
// min = Math.floor((minValue + num * minValue) / 10) * 10
|
||||
// } else if (-1 >= minValue && minValue > -10) {
|
||||
// min = Math.floor(minValue + num * minValue)
|
||||
// } else if (0 > minValue && minValue > -1) {
|
||||
// min = parseFloat((minValue + num * minValue).toFixed(1))
|
||||
// } else if (minValue == 0) {
|
||||
// min = 0
|
||||
// } else if (0 < minValue && minValue < 1) {
|
||||
// min = parseFloat((minValue - num * minValue).toFixed(1))
|
||||
// } else if (1 <= minValue && minValue < 10) {
|
||||
// min = Math.floor(minValue - num * minValue)
|
||||
// } else if (10 <= minValue && minValue < 100) {
|
||||
// min = Math.floor((minValue - num * minValue) / 10) * 10
|
||||
// } else if (100 <= minValue) {
|
||||
// min = Math.floor((minValue - num * minValue) / 100) * 100
|
||||
// }
|
||||
|
||||
// if (-100 >= maxValue) {
|
||||
// max = Math.ceil((maxValue - num * maxValue) / 100) * 100
|
||||
// } else if (-10 >= maxValue && maxValue > -100) {
|
||||
// max = Math.ceil((maxValue - num * maxValue) / 10) * 10
|
||||
// } else if (-1 >= maxValue && maxValue > -10) {
|
||||
// max = Math.ceil(maxValue - num * maxValue)
|
||||
// } else if (0 > maxValue && maxValue > -1) {
|
||||
// max = parseFloat((maxValue - num * maxValue).toFixed(1))
|
||||
// } else if (maxValue == 0) {
|
||||
// max = 0
|
||||
// } else if (0 < maxValue && maxValue < 1) {
|
||||
// max = parseFloat((maxValue + num * maxValue).toFixed(1))
|
||||
// } else if (1 <= maxValue && maxValue < 10) {
|
||||
// max = Math.ceil(maxValue + num * maxValue)
|
||||
// } else if (10 <= maxValue && maxValue < 100) {
|
||||
// max = Math.ceil((maxValue + num * maxValue) / 10) * 10
|
||||
// } else if (100 <= maxValue) {
|
||||
// max = Math.ceil((maxValue + num * maxValue) / 100) * 100
|
||||
// }
|
||||
|
||||
// if (maxValue > 1000 || minValue < -1000) {
|
||||
// max = Math.ceil(maxValue / 100) * 100
|
||||
// if (minValue == 0) {
|
||||
// min = 0
|
||||
// } else {
|
||||
// min = Math.floor(minValue / 100) * 100
|
||||
// }
|
||||
// } else if (maxValue < 60 && minValue > 40) {
|
||||
// max = 60
|
||||
// min = 40
|
||||
// } else if (maxValue == minValue && maxValue < 10 && minValue > 0) {
|
||||
// max = Math.ceil(maxValue / 10) * 10
|
||||
// min = Math.floor(minValue / 10) * 10
|
||||
// } else if (maxValue == minValue && maxValue != 0 && minValue != 0) {
|
||||
// max = Math.ceil(maxValue / 10 + 1) * 10
|
||||
// min = Math.floor(minValue / 10 - 1) * 10
|
||||
// } else {
|
||||
// max = Math.ceil(maxValue / 10) * 10
|
||||
// min = Math.floor(minValue / 10) * 10
|
||||
// }
|
||||
|
||||
// if (maxValue > 0 && maxValue < 1) {
|
||||
// max = 1
|
||||
// } else if (max == 0 && minValue > -1 && minValue < 0) {
|
||||
// min = -1
|
||||
// }
|
||||
|
||||
return [min, max]
|
||||
}
|
||||
|
||||
/**
|
||||
* title['A相','B相',]
|
||||
* data[[1,2],[3,4]]
|
||||
*/
|
||||
// 导出csv文件
|
||||
const convertToCSV = (title: object, data: any) => {
|
||||
let csv = ''
|
||||
// 添加列头
|
||||
csv += ',' + title.join(',') + '\n'
|
||||
// 遍历数据并添加到CSV字符串中
|
||||
data?.map(item => {
|
||||
csv += '\u200B' + item.join(',') + '\n'
|
||||
})
|
||||
return csv
|
||||
}
|
||||
export const exportCSV = (title: object, data: any, filename: string) => {
|
||||
const csv = convertToCSV(title, data)
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = filename
|
||||
link.click()
|
||||
// 释放URL对象
|
||||
URL.revokeObjectURL(link.href)
|
||||
}
|
||||
|
||||
/**
|
||||
* 补全时间序列数据中缺失的条目
|
||||
* @param rawData 原始数据,格式为 [["时间字符串", "数值", "单位", "类型"], ...]
|
||||
* @returns 补全后的数据,缺失条目数值为 null
|
||||
*/
|
||||
export const completeTimeSeries = (rawData: string[][]): (string | null)[][] => {
|
||||
// 步骤1:校验原始数据并解析时间
|
||||
if (rawData.length < 2) {
|
||||
console.warn('数据量不足2条,无法计算时间间隔,直接返回原始数据')
|
||||
return rawData.map(item => [...item])
|
||||
}
|
||||
|
||||
// 解析所有时间为Date对象,过滤无效时间并按时间排序
|
||||
const validData = rawData
|
||||
.map(item => {
|
||||
// 确保至少有时间和数值字段
|
||||
if (!item[0]) {
|
||||
return { time: new Date(0), item, isValid: false }
|
||||
}
|
||||
const time = new Date(item[0])
|
||||
return { time, item, isValid: !isNaN(time.getTime()) }
|
||||
})
|
||||
.filter(data => data.isValid)
|
||||
.sort((a, b) => a.time.getTime() - b.time.getTime()) // 确保数据按时间排序
|
||||
.map(data => data.item)
|
||||
|
||||
if (validData.length < 2) {
|
||||
throw new Error('有效时间数据不足2条,无法继续处理')
|
||||
}
|
||||
|
||||
// 步骤2:计算时间间隔(分析前几条数据确定最可能的间隔)
|
||||
const intervals: number[] = []
|
||||
// 分析前10条数据来确定间隔,避免单一间隔出错
|
||||
const analyzeCount = Math.min(10, validData.length - 1)
|
||||
for (let i = 0; i < analyzeCount; i++) {
|
||||
const currentTime = new Date(validData[i][0]!).getTime()
|
||||
const nextTime = new Date(validData[i + 1][0]!).getTime()
|
||||
const interval = nextTime - currentTime
|
||||
if (interval > 0) {
|
||||
intervals.push(interval)
|
||||
}
|
||||
}
|
||||
|
||||
// 取最常见的间隔作为标准间隔
|
||||
const timeInterval = getMostFrequentValue(intervals)
|
||||
if (timeInterval <= 0) {
|
||||
throw new Error('无法确定有效的时间间隔')
|
||||
}
|
||||
|
||||
// 步骤3:生成完整的时间序列范围(从第一条到最后一条)
|
||||
const startTime = new Date(validData[0][0]!).getTime()
|
||||
const endTime = new Date(validData[validData.length - 1][0]!).getTime()
|
||||
const completeTimes: Date[] = []
|
||||
|
||||
// 生成从 startTime 到 endTime 的所有间隔时间点
|
||||
for (let time = startTime; time <= endTime; time += timeInterval) {
|
||||
completeTimes.push(new Date(time))
|
||||
}
|
||||
|
||||
// 步骤4:将原始数据转为时间映射表,使用精确的时间字符串匹配
|
||||
const timeDataMap = new Map<string, (string | undefined)[]>()
|
||||
validData.forEach(item => {
|
||||
// 使用原始时间字符串作为键,避免格式转换导致的匹配问题
|
||||
if (item[0]) {
|
||||
timeDataMap.set(item[0], item)
|
||||
}
|
||||
})
|
||||
|
||||
// 提取模板数据(从第一条有效数据中提取单位和类型,处理可能的缺失)
|
||||
const template = validData[0]
|
||||
|
||||
// 步骤5:对比补全数据,缺失条目数值为 null
|
||||
const completedData = completeTimes.map(time => {
|
||||
// 保持与原始数据相同的时间格式
|
||||
const timeStr = formatTime(time)
|
||||
const existingItem = timeDataMap.get(timeStr)
|
||||
|
||||
if (existingItem) {
|
||||
// 存在该时间,返回原始数据
|
||||
return [...existingItem]
|
||||
} else {
|
||||
// 缺失该时间,数值设为 null,其他字段沿用第一个有效数据的格式
|
||||
// 处理可能缺失的单位和类型字段
|
||||
const result: (string | null | undefined)[] = [timeStr, '/']
|
||||
// 仅在原始数据有单位字段时才添加
|
||||
if (template.length > 2) {
|
||||
result.push(template[2])
|
||||
}
|
||||
// 仅在原始数据有类型字段时才添加
|
||||
if (template.length > 3) {
|
||||
result.push(template[3])
|
||||
}
|
||||
return result
|
||||
}
|
||||
})
|
||||
|
||||
return completedData
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间为 "YYYY-MM-DD HH:mm:ss" 格式
|
||||
* @param date 日期对象
|
||||
* @returns 格式化后的时间字符串
|
||||
*/
|
||||
function formatTime(date: Date): string {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数组中出现频率最高的值
|
||||
* @param arr 数字数组
|
||||
* @returns 出现频率最高的值
|
||||
*/
|
||||
function getMostFrequentValue(arr: number[]): number {
|
||||
if (arr.length === 0) return 0
|
||||
|
||||
const frequencyMap = new Map<number, number>()
|
||||
arr.forEach(num => {
|
||||
frequencyMap.set(num, (frequencyMap.get(num) || 0) + 1)
|
||||
})
|
||||
|
||||
let maxFrequency = 0
|
||||
let mostFrequent = arr[0]
|
||||
|
||||
frequencyMap.forEach((frequency, num) => {
|
||||
if (frequency > maxFrequency) {
|
||||
maxFrequency = frequency
|
||||
mostFrequent = num
|
||||
}
|
||||
})
|
||||
|
||||
return mostFrequent
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import router from '@/router/index'
|
||||
import { isNavigationFailure, NavigationFailureType } from 'vue-router'
|
||||
import { isNavigationFailure, NavigationFailureType ,useRouter} from 'vue-router'
|
||||
import type { RouteRecordRaw, RouteLocationRaw } from 'vue-router'
|
||||
import { ElNotification } from 'element-plus'
|
||||
import { useConfig } from '@/stores/config'
|
||||
@@ -8,8 +8,9 @@ import { closeShade } from '@/utils/pageShade'
|
||||
import { adminBaseRoute } from '@/router/static'
|
||||
import { compact, isEmpty, reverse } from 'lodash-es'
|
||||
import { isAdminApp } from '@/utils/common'
|
||||
import { log } from 'console'
|
||||
|
||||
import { getRouteMenu, dictDataCache } from '@/api/auth'
|
||||
import { adminBaseRoutePath } from '@/router/static'
|
||||
const route = useRouter()
|
||||
/**
|
||||
* 导航失败有错误消息的路由push
|
||||
* @param to — 导航位置,同 router.push
|
||||
@@ -17,7 +18,7 @@ import { log } from 'console'
|
||||
export const routePush = async (to: RouteLocationRaw) => {
|
||||
try {
|
||||
const failure = await router.push(to)
|
||||
|
||||
|
||||
if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
|
||||
ElNotification({
|
||||
message: 'utils.Navigation failed, navigation guard intercepted!',
|
||||
@@ -279,6 +280,41 @@ export const addRouteItem = (
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新菜单
|
||||
export const getMenu = () => {
|
||||
getRouteMenu().then((res: any) => {
|
||||
const handlerMenu = (data: any) => {
|
||||
data.forEach((item: any) => {
|
||||
item.routePath =
|
||||
item.routePath[0] == '/' ? item.routePath.substring(1, item.routePath.length) : item.routePath
|
||||
item.path = item.routePath
|
||||
item.name = item.routePath
|
||||
item.keepalive = item.routePath
|
||||
item.component = item.routeName
|
||||
? item.routeName.indexOf('/src/views/') > -1
|
||||
? item.routeName
|
||||
: `/src/views/${item.routeName}/index.vue`
|
||||
: ''
|
||||
item.type = item.children && item.children.length > 0 ? 'menu_dir' : 'menu'
|
||||
item.menu_type = item.children && item.children.length > 0 ? null : 'tab'
|
||||
if (item.children) {
|
||||
handlerMenu(item.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
handlerMenu(res.data)
|
||||
handleAdminRoute(res.data)
|
||||
if (route.params.to) {
|
||||
const lastRoute = JSON.parse(route.params.to as string)
|
||||
if (lastRoute.path != adminBaseRoutePath) {
|
||||
let query = !isEmpty(lastRoute.query) ? lastRoute.query : {}
|
||||
routePush({ path: lastRoute.path, query: query })
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据name字符串,获取父级name组合的数组
|
||||
* @param name
|
||||
|
||||
214
src/utils/webSocketClient.ts
Normal file
214
src/utils/webSocketClient.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import { ElMessage, EVENT_CODE } from "element-plus";
|
||||
|
||||
// 定义消息类型,用于类型检查
|
||||
type MessageType = {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export default class SocketService {
|
||||
// 单例模式实例
|
||||
private static instance: SocketService | null = null;
|
||||
// 和服务端连接的socket对象
|
||||
private ws: WebSocket | null = null;
|
||||
// 存储回调函数
|
||||
private callBackMapping: {
|
||||
[key: string]: ((message: MessageType) => void) | null;
|
||||
} = {};
|
||||
// 标识是否连接成功
|
||||
private connected: boolean = false;
|
||||
// 记录重试的次数
|
||||
private sendRetryCount: number = 0;
|
||||
// 重新连接尝试的次数
|
||||
private connectRetryCount: number = 0;
|
||||
// Web Worker 实例
|
||||
private work: Worker | null = null;
|
||||
// 临时的 Blob URL
|
||||
private workerBlobUrl: string | null = null;
|
||||
// 上次活动时间戳
|
||||
private lastActivityTime: number = 0;
|
||||
// 最后一次收到心跳回复时间
|
||||
private lastResponseHeartTime: number = Date.now();
|
||||
// 重新连接延迟,单位毫秒
|
||||
private reconnectDelay: number = 5000;
|
||||
|
||||
// 单例模式获取实例
|
||||
public static get Instance(): SocketService {
|
||||
if (!this.instance) {
|
||||
this.instance = new SocketService();
|
||||
}
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
// 定义连接服务器的方法
|
||||
public async connect(id: any) {
|
||||
if (!window.WebSocket) {
|
||||
console.log("您的浏览器不支持WebSocket");
|
||||
return;
|
||||
}
|
||||
let response = null;
|
||||
if (import.meta.env.VITE_NAME == "beijing") {
|
||||
response = await fetch("/vue/");
|
||||
} else if (import.meta.env.VITE_NAME == "wuxi") {
|
||||
response = await fetch("");
|
||||
}
|
||||
// const response = await fetch("/vue/");
|
||||
|
||||
const mqttUrl = response.headers.get("X-Mqtt-Url");
|
||||
console.log("🚀 ~ SocketService ~ connect ~ mqttUrl:", mqttUrl);
|
||||
setTimeout(() => {
|
||||
//"ws://10.156.193.182:18093/ws/screen" 北京
|
||||
//"ws://192.168.1.130:19001/ws/askRealTimeData/" 无锡
|
||||
const url = (mqttUrl || "ws://192.168.1.127:19001/ws/") + id;
|
||||
// const url = (mqttUrl || "ws://192.168.1.63:18093/ws/screen") + id;
|
||||
console.log("🚀 ~ SocketService ~ connect ~ url:", url);
|
||||
this.ws = new WebSocket(url);
|
||||
|
||||
this.ws.onopen = () => this.handleOpen();
|
||||
this.ws.onclose = () => this.handleClose();
|
||||
this.ws.onerror = () => this.handleError();
|
||||
this.ws.onmessage = (event) => this.handleMessage(event);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// 处理连接成功事件
|
||||
private handleOpen(): void {
|
||||
ElMessage.success("webSocket连接服务端成功了");
|
||||
console.log("连接服务端成功了");
|
||||
this.connected = true;
|
||||
this.connectRetryCount = 0;
|
||||
this.updateLastActivityTime();
|
||||
this.startHeartbeat();
|
||||
}
|
||||
|
||||
// 处理连接关闭事件
|
||||
private handleClose(): void {
|
||||
console.log("连接webSocket服务端关闭");
|
||||
this.connected = false;
|
||||
this.connectRetryCount++;
|
||||
this.clearHeartbeat();
|
||||
// 可根据需要添加重连逻辑
|
||||
// setTimeout(() => this.connect(), 500 * this.connectRetryCount);
|
||||
}
|
||||
|
||||
// 处理连接错误事件
|
||||
private handleError(): void {
|
||||
ElMessage.error("webSocket连接异常!");
|
||||
}
|
||||
|
||||
// 处理服务端发送过来的数据
|
||||
private handleMessage(event: MessageEvent): void {
|
||||
// console.log('🚀 ~ SocketService ~ handleMessage ~ event.data:', event.data)
|
||||
|
||||
if (event.data == "连接成功") {
|
||||
this.sendHeartbeat();
|
||||
} else if (event.data.length > 10) {
|
||||
let message: MessageType;
|
||||
try {
|
||||
// console.log(
|
||||
// 'Received message:', event.data)
|
||||
message = JSON.parse(event.data);
|
||||
this.callBackMapping["message"]!(message);
|
||||
} catch (e) {
|
||||
// console.error("消息解析失败", event.data, e);
|
||||
return;
|
||||
}
|
||||
// console.log("🚀 ~ SocketService ~ handleMessage ~ message:", message)
|
||||
|
||||
// this.callBackMapping["message"]!(message);
|
||||
} else {
|
||||
// ElMessage.error(event.data);
|
||||
}
|
||||
}
|
||||
|
||||
// 启动心跳检测
|
||||
private startHeartbeat(): void {
|
||||
this.lastResponseHeartTime = Date.now();
|
||||
const url = window.URL.createObjectURL(
|
||||
new Blob([
|
||||
"(function(e){setInterval(function(){this.postMessage(null)},30000)})()",
|
||||
])
|
||||
);
|
||||
this.workerBlobUrl = url;
|
||||
this.work = new Worker(url);
|
||||
this.work.onmessage = (e) => this.handleWorkerMessage(e);
|
||||
}
|
||||
|
||||
// 处理 Web Worker 消息
|
||||
private handleWorkerMessage(e: MessageEvent): void {
|
||||
// if (this.lastActivityTime - this.lastResponseHeartTime > 30000) {
|
||||
// // 说明已经三轮心跳没收到回复了,关闭检测,提示用户。
|
||||
// // ElMessage.error('业务主体模块发生未知异常,请尝试重新启动!')
|
||||
// this.clearHeartbeat()
|
||||
// return
|
||||
// }
|
||||
// console.log(123);
|
||||
|
||||
this.sendHeartbeat();
|
||||
}
|
||||
|
||||
// 发送心跳消息
|
||||
private sendHeartbeat(): void {
|
||||
// console.log(new Date() + '进入心跳消息发送。。。。。。。。。。。。。')
|
||||
if (this.ws) {
|
||||
this.ws.send("alive");
|
||||
this.updateLastActivityTime();
|
||||
}
|
||||
}
|
||||
|
||||
// 更新活动时间
|
||||
private updateLastActivityTime(): void {
|
||||
this.lastActivityTime = Date.now();
|
||||
}
|
||||
|
||||
// 清除心跳检测
|
||||
private clearHeartbeat(): void {
|
||||
if (this.work) {
|
||||
this.work.terminate();
|
||||
this.work = null;
|
||||
}
|
||||
if (this.workerBlobUrl) {
|
||||
window.URL.revokeObjectURL(this.workerBlobUrl);
|
||||
this.workerBlobUrl = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 回调函数的注册
|
||||
public registerCallBack(
|
||||
socketType: string,
|
||||
callBack: (message: MessageType) => void
|
||||
): void {
|
||||
this.callBackMapping[socketType] = callBack;
|
||||
}
|
||||
|
||||
// 取消某一个回调函数
|
||||
public unRegisterCallBack(socketType: string): void {
|
||||
this.callBackMapping[socketType] = null;
|
||||
}
|
||||
|
||||
// 发送数据的方法
|
||||
public send(data: any): void {
|
||||
if (this.connected) {
|
||||
this.sendRetryCount = 0;
|
||||
try {
|
||||
if (this.ws) {
|
||||
this.ws.send(JSON.stringify(data));
|
||||
}
|
||||
} catch (e) {
|
||||
if (this.ws) {
|
||||
this.ws.send(data);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.sendRetryCount++;
|
||||
setTimeout(() => this.send(data), this.sendRetryCount * 500);
|
||||
}
|
||||
}
|
||||
|
||||
// 断开方法
|
||||
public closeWs(): void {
|
||||
if (this.connected && this.ws) {
|
||||
this.ws.close();
|
||||
}
|
||||
console.log("执行WS关闭命令..");
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,15 @@ const tableStore = new TableStore({
|
||||
},
|
||||
{ title: '设备名称', field: 'ndid', align: 'center' },
|
||||
{ title: '异常时间', field: 'evtTime', align: 'center', sortable: true },
|
||||
{ title: '告警代码', field: 'code', align: 'center', sortable: true }
|
||||
{
|
||||
title: '告警代码',
|
||||
field: 'code',
|
||||
align: 'center',
|
||||
sortable: true,
|
||||
formatter: (row: any) => {
|
||||
return row.cellValue ? '\u200B' + row.cellValue : '/'
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ const tableStore = new TableStore({
|
||||
{ title: '设备名称', field: 'equipmentName', align: 'center' },
|
||||
{ title: '工程名称', field: 'engineeringName', align: 'center' },
|
||||
{ title: '项目名称', field: 'projectName', align: 'center' },
|
||||
{ title: '发生时刻', field: 'startTime', align: 'center', minWidth: 110, sortable: true },
|
||||
{ title: '发生时刻', field: 'startTime', align: 'center', width: 180, sortable: true },
|
||||
{
|
||||
title: '模块信息',
|
||||
field: 'moduleNo',
|
||||
@@ -99,20 +99,21 @@ const tableStore = new TableStore({
|
||||
title: '告警代码',
|
||||
field: 'code',
|
||||
align: 'center',
|
||||
|
||||
width: 100,
|
||||
formatter: (row: any) => {
|
||||
return row.cellValue ? row.cellValue : '/'
|
||||
return row.cellValue ? '\u200B' + row.cellValue : '/'
|
||||
},
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
title: '事件描述',
|
||||
minWidth: 220,
|
||||
field: 'showName'
|
||||
},
|
||||
{
|
||||
title: '级别',
|
||||
field: 'level',
|
||||
|
||||
width: 100,
|
||||
render: 'tag',
|
||||
custom: {
|
||||
1: 'danger',
|
||||
@@ -133,7 +134,8 @@ const tableStore = new TableStore({
|
||||
// }
|
||||
// }
|
||||
],
|
||||
beforeSearchFun: () => {}
|
||||
beforeSearchFun: () => {},
|
||||
|
||||
})
|
||||
|
||||
provide('tableStore', tableStore)
|
||||
|
||||
@@ -51,7 +51,7 @@ const tableStore = new TableStore({
|
||||
align: 'center',
|
||||
|
||||
formatter: (row: any) => {
|
||||
return row.cellValue ? row.cellValue : '/'
|
||||
return row.cellValue ? '\u200B' + row.cellValue : '/'
|
||||
},
|
||||
sortable: true
|
||||
},
|
||||
|
||||
@@ -75,7 +75,7 @@ import TableHeader from '@/components/table/header/index.vue'
|
||||
import shushiboxi from '@/components/echarts/shushiboxi.vue'
|
||||
import waveFormAnalysis from '@/views/govern/device/control/tabs/components/waveFormAnalysis.vue'
|
||||
import rmsboxi from '@/components/echarts/rmsboxi.vue'
|
||||
import { analyseWave } from '@/api/common'
|
||||
import { analyseWave, getFileByEventId } from '@/api/common'
|
||||
import { mainHeight } from '@/utils/layout'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getFileZip } from '@/api/cs-harmonic-boot/datatrend'
|
||||
@@ -153,7 +153,7 @@ const tableStore = new TableStore({
|
||||
render: 'basicButton',
|
||||
loading: 'loading1',
|
||||
disabled: row => {
|
||||
return !row.wavePath && row.evtParamTm < 20
|
||||
return !row.wavePath
|
||||
},
|
||||
click: async row => {
|
||||
row.loading1 = true
|
||||
@@ -212,7 +212,6 @@ const tableStore = new TableStore({
|
||||
loading: 'loading2',
|
||||
render: 'basicButton',
|
||||
disabled: row => {
|
||||
// && row.evtParamTm < 20
|
||||
return !row.wavePath
|
||||
},
|
||||
click: row => {
|
||||
@@ -235,8 +234,24 @@ const tableStore = new TableStore({
|
||||
icon: 'el-icon-DataLine',
|
||||
render: 'basicButton',
|
||||
disabled: row => {
|
||||
return !(!row.wavePath && row.evtParamTm < 20)
|
||||
return row.showName != '未知';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'edit',
|
||||
title: '波形补召',
|
||||
type: 'primary',
|
||||
icon: 'el-icon-Check',
|
||||
render: 'basicButton',
|
||||
disabled: row => {
|
||||
return row.wavePath || row.showName === '未知';
|
||||
},
|
||||
click: row => {
|
||||
getFileByEventId(row.id).then(res => {
|
||||
ElMessage.success(res.message)
|
||||
tableStore.index()
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,448 +1,469 @@
|
||||
<template>
|
||||
<div class="default-main analyze-apf" :style="{ height: pageHeight.height }" v-loading="loading">
|
||||
<DeviceTree @node-click="nodeClick" @init="nodeClick" @deviceTypeChange="deviceTypeChange"></DeviceTree>
|
||||
<div class="analyze-apf-right" v-if="formInline.devId">
|
||||
<div ref="headerRef">
|
||||
<TableHeader :showSearch="false" ref="tableHeaderRef" @selectChange="selectChange">
|
||||
<template v-slot:select>
|
||||
<el-form-item label="时间:">
|
||||
<DatePicker ref="datePickerRef"></DatePicker>
|
||||
</el-form-item>
|
||||
<el-form-item label="统计指标:">
|
||||
<el-select
|
||||
style="width: 200px"
|
||||
v-model.trim="formInline.statisticalId"
|
||||
filterable
|
||||
@change="frequencyFlag"
|
||||
placeholder="请选择"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in zblist"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="谐波次数:" v-show="frequencyShow">
|
||||
<el-select
|
||||
v-model.trim="formInline.frequency"
|
||||
filterable
|
||||
placeholder="请选择"
|
||||
style="width: 100px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in 49"
|
||||
:key="item + 1"
|
||||
:label="item + 1"
|
||||
:value="item + 1"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="值类型:">
|
||||
<el-select v-model.trim="formInline.valueType" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in typelist"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-slot:operation>
|
||||
<el-button type="primary" @click="search" icon="el-icon-Search">查询</el-button>
|
||||
</template>
|
||||
</TableHeader>
|
||||
</div>
|
||||
|
||||
<el-empty description="暂无数据" v-if="!echartsData" style="flex: 1"></el-empty>
|
||||
<template v-else>
|
||||
<div :style="echartHeight">
|
||||
<MyEchart :options="echartsData" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<el-empty v-else description="请选择设备" class="analyze-apf-right" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { mainHeight } from '@/utils/layout'
|
||||
import DeviceTree from '@/components/tree/govern/deviceTree.vue'
|
||||
import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
|
||||
import { getDevCapacity } from '@/api/cs-device-boot/capacity'
|
||||
import { queryCommonStatisticalByTime } from '@/api/cs-harmonic-boot/stable'
|
||||
import DatePicker from '@/components/form/datePicker/index.vue'
|
||||
import MyEchart from '@/components/echarts/MyEchart.vue'
|
||||
import { yMethod } from '@/utils/echartMethod'
|
||||
import TableHeader from '@/components/table/header/index.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'govern/analyze/APF'
|
||||
})
|
||||
|
||||
const tableHeaderRef = ref()
|
||||
const headerRef = ref()
|
||||
const pageHeight = mainHeight(20)
|
||||
const echartHeight = ref(mainHeight(80))
|
||||
const loading = ref(false)
|
||||
const echartsData = ref<any>(null)
|
||||
const datePickerRef = ref()
|
||||
const formInline = reactive({
|
||||
statisticalId: '',
|
||||
valueType: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
devId: '',
|
||||
frequency: ''
|
||||
})
|
||||
const timeFlag = ref(true)
|
||||
const frequencyShow = ref(false)
|
||||
const devCapacity = ref(0)
|
||||
const flag = ref(false)
|
||||
const typelist = [
|
||||
{
|
||||
label: '平均值',
|
||||
value: 'avg'
|
||||
},
|
||||
{
|
||||
label: '最大值',
|
||||
value: 'max'
|
||||
},
|
||||
{
|
||||
label: '最小值',
|
||||
value: 'min'
|
||||
},
|
||||
{
|
||||
label: 'CP95值',
|
||||
value: 'cp95'
|
||||
}
|
||||
]
|
||||
const zblist = ref<any[]>([])
|
||||
|
||||
const init = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
queryByCode('Web_Apf').then(res => {
|
||||
queryCsDictTree(res.data?.id).then(res => {
|
||||
zblist.value = res.data.map((item: any) => {
|
||||
return {
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
...item
|
||||
}
|
||||
})
|
||||
formInline.statisticalId = zblist.value[0]?.value
|
||||
formInline.valueType = typelist[0].value
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
const deviceTypeChange = (val: any, obj: any) => {
|
||||
flag.value = true
|
||||
nodeClick(obj)
|
||||
}
|
||||
const nodeClick = async (e: anyObj) => {
|
||||
if (e.level == 2 && flag.value) {
|
||||
formInline.devId = e.id
|
||||
loading.value = true
|
||||
if (zblist.value.length === 0) {
|
||||
await init()
|
||||
}
|
||||
getDevCapacity(formInline.devId)
|
||||
.then(res => {
|
||||
devCapacity.value = res.data
|
||||
search()
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
const lineStyle = [
|
||||
{ type: 'solid', width: 3 },
|
||||
{ type: 'dotted', width: 3 },
|
||||
{ type: 'dashed', width: 3 }
|
||||
]
|
||||
const search = () => {
|
||||
if (timeFlag.value) {
|
||||
datePickerRef.value && datePickerRef.value.setInterval(5)
|
||||
timeFlag.value = false
|
||||
}
|
||||
loading.value = true
|
||||
formInline.startTime = datePickerRef.value.timeValue[0]
|
||||
formInline.endTime = datePickerRef.value.timeValue[1]
|
||||
if (!frequencyShow.value) {
|
||||
formInline.frequency = ''
|
||||
}
|
||||
|
||||
queryCommonStatisticalByTime(formInline)
|
||||
.then(({ data }: { data: any[] }) => {
|
||||
if (data.length) {
|
||||
let list = processingOfData(data, 'unit')
|
||||
|
||||
echartsData.value = {}
|
||||
let legend: any[] = []
|
||||
let xAxis: any[] = []
|
||||
let yAxis: any[] = []
|
||||
let series: any[] = []
|
||||
let color: any[] = []
|
||||
let title = ''
|
||||
data.forEach(item => {
|
||||
if (!xAxis.includes(item.time)) {
|
||||
xAxis.push(item.time)
|
||||
}
|
||||
// if (!legend.includes(item.anotherName)) {
|
||||
// legend.push(item.anotherName)
|
||||
// }
|
||||
})
|
||||
let units = Object.keys(list)
|
||||
// console.log('🚀 ~ .then ~ units:', units)
|
||||
for (let unit in list) {
|
||||
console.log('🚀 ~ .then ~ unit:', unit)
|
||||
let [min, max] = yMethod(list[unit].map((item: any) => item.statisticalData))
|
||||
yAxis.push({
|
||||
name: unit == 'null' ? '' : unit,
|
||||
type: 'value',
|
||||
// max: 10,
|
||||
min: min,
|
||||
max: max,
|
||||
// splitNumber: 5,
|
||||
// minInterval: 1,
|
||||
|
||||
axisLine: {
|
||||
show: true,
|
||||
//symbol: ["none", "arrow"],
|
||||
lineStyle: {
|
||||
color: '#333'
|
||||
}
|
||||
}
|
||||
})
|
||||
// processingOfData(list[unit], 'anotherName')
|
||||
let anotherList = processingOfData(list[unit], 'anotherName')
|
||||
for (let k in anotherList) {
|
||||
title = k
|
||||
let lineName = lineStyle[Object.keys(anotherList).indexOf(k)]
|
||||
let phaseList = processingOfData(anotherList[k], 'phase')
|
||||
for (let j in phaseList) {
|
||||
color.push(j == 'A' ? '#DAA520' : j == 'B' ? '#2E8B57' : j == 'C' ? '#A52a2a' : '#0000CC')
|
||||
legend.push(
|
||||
j == 'M' ? k : j == 'A' ? `A相_${k}` : j == 'B' ? `B相_${k}` : j == 'C' ? `C相_${k}` : j
|
||||
)
|
||||
series.push({
|
||||
name:
|
||||
j == 'M'
|
||||
? k
|
||||
: j == 'A'
|
||||
? `A相_${k}`
|
||||
: j == 'B'
|
||||
? `B相_${k}`
|
||||
: j == 'C'
|
||||
? `C相_${k}`
|
||||
: j,
|
||||
symbol: 'none',
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
|
||||
data: phaseList[j].map(item => [
|
||||
item.time,
|
||||
Math.floor(item.statisticalData * 100) / 100,
|
||||
unit,
|
||||
lineName.type
|
||||
]),
|
||||
lineStyle: lineName,
|
||||
yAxisIndex: unit.indexOf(units)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echartsData.value = {
|
||||
title: {
|
||||
text: zblist.value.filter(item => item.id == formInline.statisticalId)[0].name
|
||||
},
|
||||
tooltip: {
|
||||
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,
|
||||
formatter(params: any) {
|
||||
const xname = params[0].value[0]
|
||||
let str = `${xname}<br>`
|
||||
params.forEach((el: any, index: any) => {
|
||||
let marker = ''
|
||||
if (el.value[3] == 'dashed') {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
marker += `<span style="display:inline-block;border: 2px ${el.color} solid;margin-right:5px;width:10px;height:0px;background-color:#ffffff00;"></span>`
|
||||
}
|
||||
} else {
|
||||
marker = `<span style="display:inline-block;border: 2px ${el.color} ${el.value[3]};margin-right:5px;width:40px;height:0px;background-color:#ffffff00;"></span>`
|
||||
}
|
||||
|
||||
str += `${marker}${el.seriesName.split('(')[0]}:${
|
||||
el.value[1] != null
|
||||
? el.value[1] + ' ' + (el.value[2] == 'null' ? '' : el.value[2])
|
||||
: '-'
|
||||
}<br>`
|
||||
})
|
||||
return str
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
itemWidth: 20,
|
||||
itemHeight: 20,
|
||||
itemStyle: { opacity: 0 }, //去圆点
|
||||
type: 'scroll', // 开启滚动分页
|
||||
top: 25
|
||||
// data: legend
|
||||
},
|
||||
grid: {
|
||||
left: '20px',
|
||||
right: '40px',
|
||||
bottom: '50px',
|
||||
top: '80px',
|
||||
containLabel: true
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: {}
|
||||
}
|
||||
},
|
||||
color: color,
|
||||
xAxis: {
|
||||
name: '',
|
||||
type: 'time',
|
||||
axisLabel: {
|
||||
formatter: {
|
||||
day: '{MM}-{dd}',
|
||||
month: '{MM}',
|
||||
year: '{yyyy}'
|
||||
}
|
||||
}
|
||||
|
||||
// boundaryGap: false,
|
||||
// data: xAxis,
|
||||
// axisLabel: {
|
||||
// formatter: function (value: string) {
|
||||
// return value.split(' ').join('\n')
|
||||
// }
|
||||
// },
|
||||
// axisLine: {
|
||||
// show: true,
|
||||
// // symbol: ["none", "arrow"],
|
||||
// lineStyle: {
|
||||
// color: '#333'
|
||||
// }
|
||||
// }
|
||||
},
|
||||
yAxis: yAxis,
|
||||
// [
|
||||
// {
|
||||
// name: '畸变率:(%)',
|
||||
// type: 'value',
|
||||
// // max: 10,
|
||||
// min: min1,
|
||||
// max: max1,
|
||||
// splitNumber: 5,
|
||||
// minInterval: 1,
|
||||
|
||||
// axisLine: {
|
||||
// show: true,
|
||||
// //symbol: ["none", "arrow"],
|
||||
// lineStyle: {
|
||||
// color: '#333'
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// name: '电流:(A)',
|
||||
// type: 'value',
|
||||
// min: min,
|
||||
// max: max,
|
||||
// splitNumber: 5,
|
||||
// minInterval: 1,
|
||||
// splitLine: {
|
||||
// show: false
|
||||
// },
|
||||
// axisLine: {
|
||||
// show: true,
|
||||
// //symbol: ["none", "arrow"],
|
||||
// lineStyle: {
|
||||
// color: '#333'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ],
|
||||
options: {
|
||||
series: series
|
||||
}
|
||||
}
|
||||
} else {
|
||||
echartsData.value = null
|
||||
}
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
const processingOfData = (data: any, type: string) => {
|
||||
let groupedData: any = {}
|
||||
|
||||
data.forEach(item => {
|
||||
if (!groupedData[item[type]]) {
|
||||
groupedData[item[type]] = []
|
||||
}
|
||||
groupedData[item[type]].push(item)
|
||||
})
|
||||
return groupedData
|
||||
}
|
||||
const frequencyFlag = () => {
|
||||
let name = zblist.value.filter(item => item.id == formInline.statisticalId)[0].name
|
||||
if (name.includes('含有率') || name.includes('幅值')) {
|
||||
frequencyShow.value = true
|
||||
formInline.frequency = 2
|
||||
} else {
|
||||
frequencyShow.value = false
|
||||
}
|
||||
tableHeaderRef.value && tableHeaderRef.value?.computedSearchRow()
|
||||
}
|
||||
const selectChange = (flag: boolean) => {
|
||||
setTimeout(() => {
|
||||
echartHeight.value = mainHeight(23 + headerRef.value.offsetHeight)
|
||||
}, 100)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.analyze-apf {
|
||||
display: flex;
|
||||
|
||||
&-right {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
padding: 10px 10px 10px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.el-select {
|
||||
min-width: 100px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="default-main analyze-apf" :style="{ height: pageHeight.height }" v-loading="loading">
|
||||
<DeviceTree @node-click="nodeClick" @init="nodeClick" @deviceTypeChange="deviceTypeChange"></DeviceTree>
|
||||
<div class="analyze-apf-right" v-if="formInline.devId">
|
||||
<div ref="headerRef">
|
||||
<TableHeader :showSearch="false" ref="tableHeaderRef" @selectChange="selectChange">
|
||||
<template v-slot:select>
|
||||
<el-form-item label="时间:">
|
||||
<DatePicker ref="datePickerRef"></DatePicker>
|
||||
</el-form-item>
|
||||
<el-form-item label="统计指标:">
|
||||
<el-select
|
||||
style="width: 200px"
|
||||
v-model.trim="formInline.statisticalId"
|
||||
filterable
|
||||
@change="frequencyFlag"
|
||||
placeholder="请选择"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in zblist"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="谐波次数:" v-show="frequencyShow">
|
||||
<el-select
|
||||
v-model.trim="formInline.frequency"
|
||||
filterable
|
||||
placeholder="请选择"
|
||||
style="width: 100px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in 49"
|
||||
:key="item + 1"
|
||||
:label="item + 1"
|
||||
:value="item + 1"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="值类型:">
|
||||
<el-select v-model.trim="formInline.valueType" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in typelist"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-slot:operation>
|
||||
<el-button type="primary" @click="search" icon="el-icon-Search">查询</el-button>
|
||||
<el-button :type="timeControl ? 'primary' : ''" icon="el-icon-Sort" @click="setTimeControl">
|
||||
缺失数据
|
||||
</el-button>
|
||||
</template>
|
||||
</TableHeader>
|
||||
</div>
|
||||
|
||||
<el-empty description="暂无数据" v-if="!echartsData" style="flex: 1"></el-empty>
|
||||
<template v-else>
|
||||
<div :style="echartHeight">
|
||||
<MyEchart :options="echartsData" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<el-empty v-else description="请选择设备" class="analyze-apf-right" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { mainHeight } from '@/utils/layout'
|
||||
import DeviceTree from '@/components/tree/govern/deviceTree.vue'
|
||||
import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
|
||||
import { getDevCapacity } from '@/api/cs-device-boot/capacity'
|
||||
import { queryCommonStatisticalByTime } from '@/api/cs-harmonic-boot/stable'
|
||||
import DatePicker from '@/components/form/datePicker/index.vue'
|
||||
import MyEchart from '@/components/echarts/MyEchart.vue'
|
||||
import { yMethod, completeTimeSeries } from '@/utils/echartMethod'
|
||||
import TableHeader from '@/components/table/header/index.vue'
|
||||
const timeControl = ref(false)
|
||||
defineOptions({
|
||||
name: 'govern/analyze/APF'
|
||||
})
|
||||
|
||||
const tableHeaderRef = ref()
|
||||
const headerRef = ref()
|
||||
const pageHeight = mainHeight(20)
|
||||
const echartHeight = ref(mainHeight(80))
|
||||
const loading = ref(false)
|
||||
const echartsData = ref<any>(null)
|
||||
const datePickerRef = ref()
|
||||
const formInline = reactive({
|
||||
statisticalId: '',
|
||||
valueType: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
devId: '',
|
||||
frequency: ''
|
||||
})
|
||||
const dataLists = ref<any[]>([])
|
||||
const timeFlag = ref(true)
|
||||
const frequencyShow = ref(false)
|
||||
const devCapacity = ref(0)
|
||||
const flag = ref(false)
|
||||
const typelist = [
|
||||
{
|
||||
label: '平均值',
|
||||
value: 'avg'
|
||||
},
|
||||
{
|
||||
label: '最大值',
|
||||
value: 'max'
|
||||
},
|
||||
{
|
||||
label: '最小值',
|
||||
value: 'min'
|
||||
},
|
||||
{
|
||||
label: 'CP95值',
|
||||
value: 'cp95'
|
||||
}
|
||||
]
|
||||
const zblist = ref<any[]>([])
|
||||
|
||||
const init = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
queryByCode('Web_Apf').then(res => {
|
||||
queryCsDictTree(res.data?.id).then(res => {
|
||||
zblist.value = res.data.map((item: any) => {
|
||||
return {
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
...item
|
||||
}
|
||||
})
|
||||
formInline.statisticalId = zblist.value[0]?.value
|
||||
formInline.valueType = typelist[0].value
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
const deviceTypeChange = (val: any, obj: any) => {
|
||||
flag.value = true
|
||||
nodeClick(obj)
|
||||
}
|
||||
const nodeClick = async (e: anyObj) => {
|
||||
if (e.level == 2 && flag.value) {
|
||||
formInline.devId = e.id
|
||||
loading.value = true
|
||||
if (zblist.value.length === 0) {
|
||||
await init()
|
||||
}
|
||||
getDevCapacity(formInline.devId)
|
||||
.then(res => {
|
||||
devCapacity.value = res.data
|
||||
search()
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
const lineStyle = [
|
||||
{ type: 'solid', width: 3 },
|
||||
{ type: 'dotted', width: 3 },
|
||||
{ type: 'dashed', width: 3 }
|
||||
]
|
||||
const search = () => {
|
||||
if (timeFlag.value) {
|
||||
datePickerRef.value && datePickerRef.value.setInterval(5)
|
||||
timeFlag.value = false
|
||||
}
|
||||
loading.value = true
|
||||
formInline.startTime = datePickerRef.value.timeValue[0]
|
||||
formInline.endTime = datePickerRef.value.timeValue[1]
|
||||
if (!frequencyShow.value) {
|
||||
formInline.frequency = ''
|
||||
}
|
||||
|
||||
queryCommonStatisticalByTime(formInline)
|
||||
.then(({ data }: { data: any[] }) => {
|
||||
dataLists.value = data
|
||||
setEchart()
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
const setEchart = () => {
|
||||
loading.value = true
|
||||
let data = JSON.parse(JSON.stringify(dataLists.value))
|
||||
if (data.length) {
|
||||
let list = processingOfData(data, 'unit')
|
||||
|
||||
echartsData.value = {}
|
||||
let legend: any[] = []
|
||||
let xAxis: any[] = []
|
||||
let yAxis: any[] = []
|
||||
let series: any[] = []
|
||||
let color: any[] = []
|
||||
let title = ''
|
||||
data.forEach(item => {
|
||||
if (!xAxis.includes(item.time)) {
|
||||
xAxis.push(item.time)
|
||||
}
|
||||
// if (!legend.includes(item.anotherName)) {
|
||||
// legend.push(item.anotherName)
|
||||
// }
|
||||
})
|
||||
let units = Object.keys(list)
|
||||
// console.log('🚀 ~ .then ~ units:', units)
|
||||
for (let unit in list) {
|
||||
console.log('🚀 ~ .then ~ unit:', unit)
|
||||
let [min, max] = yMethod(list[unit].map((item: any) => item.statisticalData))
|
||||
yAxis.push({
|
||||
name: unit == 'null' ? '' : unit,
|
||||
type: 'value',
|
||||
// max: 10,
|
||||
min: min,
|
||||
max: max,
|
||||
// splitNumber: 5,
|
||||
// minInterval: 1,
|
||||
|
||||
axisLine: {
|
||||
show: true,
|
||||
//symbol: ["none", "arrow"],
|
||||
lineStyle: {
|
||||
color: '#333'
|
||||
}
|
||||
}
|
||||
})
|
||||
// processingOfData(list[unit], 'anotherName')
|
||||
let anotherList = processingOfData(list[unit], 'anotherName')
|
||||
for (let k in anotherList) {
|
||||
title = k
|
||||
let lineName = lineStyle[Object.keys(anotherList).indexOf(k)]
|
||||
let phaseList = processingOfData(anotherList[k], 'phase')
|
||||
for (let j in phaseList) {
|
||||
color.push(j == 'A' ? '#DAA520' : j == 'B' ? '#2E8B57' : j == 'C' ? '#A52a2a' : '#0000CC')
|
||||
legend.push(
|
||||
j == 'M' ? k : j == 'A' ? `A相_${k}` : j == 'B' ? `B相_${k}` : j == 'C' ? `C相_${k}` : j
|
||||
)
|
||||
series.push({
|
||||
name: j == 'M' ? k : j == 'A' ? `A相_${k}` : j == 'B' ? `B相_${k}` : j == 'C' ? `C相_${k}` : j,
|
||||
symbol: 'none',
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
|
||||
// data: phaseList[j].map(item => [
|
||||
// item.time,
|
||||
// Math.floor(item.statisticalData * 100) / 100,
|
||||
// unit,
|
||||
// lineName.type
|
||||
// ]),
|
||||
data: timeControl.value
|
||||
? completeTimeSeries(
|
||||
phaseList[j].map(item => [
|
||||
item.time,
|
||||
Math.floor(item.statisticalData * 100) / 100,
|
||||
unit,
|
||||
lineName.type
|
||||
])
|
||||
)
|
||||
: phaseList[j].map(item => [
|
||||
item.time,
|
||||
Math.floor(item.statisticalData * 100) / 100,
|
||||
unit,
|
||||
lineName.type
|
||||
]),
|
||||
|
||||
lineStyle: lineName,
|
||||
yAxisIndex: unit.indexOf(units)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echartsData.value = {
|
||||
title: {
|
||||
text: zblist.value.filter(item => item.id == formInline.statisticalId)[0].name
|
||||
},
|
||||
tooltip: {
|
||||
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,
|
||||
formatter(params: any) {
|
||||
const xname = params[0].value[0]
|
||||
let str = `${xname}<br>`
|
||||
params.forEach((el: any, index: any) => {
|
||||
let marker = ''
|
||||
if (el.value[3] == 'dashed') {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
marker += `<span style="display:inline-block;border: 2px ${el.color} solid;margin-right:5px;width:10px;height:0px;background-color:#ffffff00;"></span>`
|
||||
}
|
||||
} else {
|
||||
marker = `<span style="display:inline-block;border: 2px ${el.color} ${el.value[3]};margin-right:5px;width:40px;height:0px;background-color:#ffffff00;"></span>`
|
||||
}
|
||||
|
||||
str += `${marker}${el.seriesName.split('(')[0]}:${
|
||||
el.value[1] != null ? el.value[1] + ' ' + (el.value[2] == 'null' ? '' : el.value[2]) : '-'
|
||||
}<br>`
|
||||
})
|
||||
return str
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
itemWidth: 20,
|
||||
itemHeight: 20,
|
||||
itemStyle: { opacity: 0 }, //去圆点
|
||||
type: 'scroll', // 开启滚动分页
|
||||
top: 25
|
||||
// data: legend
|
||||
},
|
||||
grid: {
|
||||
left: '20px',
|
||||
right: '40px',
|
||||
bottom: '50px',
|
||||
top: '80px',
|
||||
containLabel: true
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: {}
|
||||
}
|
||||
},
|
||||
color: color,
|
||||
xAxis: {
|
||||
name: '',
|
||||
type: 'time',
|
||||
axisLabel: {
|
||||
formatter: {
|
||||
day: '{MM}-{dd}',
|
||||
month: '{MM}',
|
||||
year: '{yyyy}'
|
||||
}
|
||||
}
|
||||
|
||||
// boundaryGap: false,
|
||||
// data: xAxis,
|
||||
// axisLabel: {
|
||||
// formatter: function (value: string) {
|
||||
// return value.split(' ').join('\n')
|
||||
// }
|
||||
// },
|
||||
// axisLine: {
|
||||
// show: true,
|
||||
// // symbol: ["none", "arrow"],
|
||||
// lineStyle: {
|
||||
// color: '#333'
|
||||
// }
|
||||
// }
|
||||
},
|
||||
yAxis: yAxis,
|
||||
// [
|
||||
// {
|
||||
// name: '畸变率:(%)',
|
||||
// type: 'value',
|
||||
// // max: 10,
|
||||
// min: min1,
|
||||
// max: max1,
|
||||
// splitNumber: 5,
|
||||
// minInterval: 1,
|
||||
|
||||
// axisLine: {
|
||||
// show: true,
|
||||
// //symbol: ["none", "arrow"],
|
||||
// lineStyle: {
|
||||
// color: '#333'
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// name: '电流:(A)',
|
||||
// type: 'value',
|
||||
// min: min,
|
||||
// max: max,
|
||||
// splitNumber: 5,
|
||||
// minInterval: 1,
|
||||
// splitLine: {
|
||||
// show: false
|
||||
// },
|
||||
// axisLine: {
|
||||
// show: true,
|
||||
// //symbol: ["none", "arrow"],
|
||||
// lineStyle: {
|
||||
// color: '#333'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ],
|
||||
options: {
|
||||
series: series
|
||||
}
|
||||
}
|
||||
} else {
|
||||
echartsData.value = null
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
const setTimeControl = () => {
|
||||
timeControl.value = !timeControl.value
|
||||
setEchart()
|
||||
}
|
||||
|
||||
const processingOfData = (data: any, type: string) => {
|
||||
let groupedData: any = {}
|
||||
|
||||
data.forEach(item => {
|
||||
if (!groupedData[item[type]]) {
|
||||
groupedData[item[type]] = []
|
||||
}
|
||||
groupedData[item[type]].push(item)
|
||||
})
|
||||
return groupedData
|
||||
}
|
||||
const frequencyFlag = () => {
|
||||
let name = zblist.value.filter(item => item.id == formInline.statisticalId)[0].name
|
||||
if (name.includes('含有率') || name.includes('幅值')) {
|
||||
frequencyShow.value = true
|
||||
formInline.frequency = 2
|
||||
} else {
|
||||
frequencyShow.value = false
|
||||
}
|
||||
tableHeaderRef.value && tableHeaderRef.value?.computedSearchRow()
|
||||
}
|
||||
const selectChange = (flag: boolean) => {
|
||||
setTimeout(() => {
|
||||
echartHeight.value = mainHeight(23 + headerRef.value.offsetHeight)
|
||||
}, 100)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.analyze-apf {
|
||||
display: flex;
|
||||
|
||||
&-right {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
padding: 10px 10px 10px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.el-select {
|
||||
min-width: 100px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -314,7 +314,8 @@
|
||||
label="装置mac地址:"
|
||||
:rules="{ required: true, message: '请输入装置mac地址', trigger: 'blur' }"
|
||||
>
|
||||
<MacAddressInput v-model="busItem.mac" :disabled="!((nodeLevel == 3 && pageStatus == 3) || ((nodeLevel == 2 || (nodeLevel == 1 && pageStatus == 2)) && pageStatus == 2))"/>
|
||||
<MacAddressInput v-model="busItem.mac"
|
||||
:disabled="!(pageStatus == 2 && nodeLevel == 2)"/>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item
|
||||
class="form-item"
|
||||
@@ -346,7 +347,7 @@
|
||||
filterable
|
||||
v-model="busItem.nodeId"
|
||||
placeholder="请选择所属前置机"
|
||||
:disabled="!(pageStatus == 2 && nodeLevel >= 2)"
|
||||
:disabled="!(pageStatus == 2 && nodeLevel == 2)"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in affiliatiedFrontArr"
|
||||
@@ -479,17 +480,21 @@
|
||||
:rules="{ required: true, message: '请输入pt', trigger: 'blur' }"
|
||||
>
|
||||
<div style="width: 100%; display: flex; justify-content: space-between">
|
||||
<el-input
|
||||
<el-input-number
|
||||
:controls="false"
|
||||
:min="1"
|
||||
style="width: 48%"
|
||||
v-model="lineItem.ptRatio"
|
||||
:disabled="!((nodeLevel == 4 && pageStatus == 3) || ((nodeLevel == 3 || (nodeLevel == 2 && pageStatus == 2)) && pageStatus == 2))"
|
||||
></el-input>
|
||||
></el-input-number>
|
||||
<span style="display: flex; align-items: center; justify-content: center;">:</span>
|
||||
<el-input
|
||||
<el-input-number
|
||||
:controls="false"
|
||||
:min="1"
|
||||
style="width: 48%"
|
||||
v-model="lineItem.pt2Ratio"
|
||||
:disabled="!((nodeLevel == 4 && pageStatus == 3) || ((nodeLevel == 3 || (nodeLevel == 2 && pageStatus == 2)) && pageStatus == 2))"
|
||||
></el-input>
|
||||
></el-input-number>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
@@ -498,17 +503,21 @@
|
||||
:rules="{ required: true, message: '请输入ct', trigger: 'blur' }"
|
||||
>
|
||||
<div style="width: 100%; display: flex; justify-content: space-between">
|
||||
<el-input
|
||||
<el-input-number
|
||||
:controls="false"
|
||||
:min="1"
|
||||
style="width: 48%"
|
||||
v-model="lineItem.ctRatio"
|
||||
:disabled="!((nodeLevel == 4 && pageStatus == 3) || ((nodeLevel == 3 || (nodeLevel == 2 && pageStatus == 2)) && pageStatus == 2))"
|
||||
></el-input>
|
||||
></el-input-number>
|
||||
<span style="display: flex; align-items: center; justify-content: center;">:</span>
|
||||
<el-input
|
||||
<el-input-number
|
||||
:controls="false"
|
||||
:min="1"
|
||||
style="width: 48%"
|
||||
v-model="lineItem.ct2Ratio"
|
||||
:disabled="!((nodeLevel == 4 && pageStatus == 3) || ((nodeLevel == 3 || (nodeLevel == 2 && pageStatus == 2)) && pageStatus == 2))"
|
||||
></el-input>
|
||||
></el-input-number>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
@@ -818,6 +827,21 @@ const nodeClick = (e: anyObj, data: any) => {
|
||||
cleanUnnecessaryData()
|
||||
/**不是根节点请求数据 */
|
||||
queryNodeContent()
|
||||
|
||||
// 关键修改:确保tabs展开
|
||||
setTimeout(() => {
|
||||
// 设置默认选中的tab索引
|
||||
if (nodeLevel.value >= 2 && projectInfoList.value.length > 0) {
|
||||
deviceIndex.value = '0'
|
||||
}
|
||||
if (nodeLevel.value >= 3 && deviceInfoList.value.length > 0) {
|
||||
busBarIndex.value = '0'
|
||||
}
|
||||
if (nodeLevel.value >= 4 && lineInfoList.value.length > 0) {
|
||||
lineIndex.value = '0'
|
||||
}
|
||||
}, 100)
|
||||
|
||||
treeClickCount.value = 0
|
||||
|
||||
}
|
||||
@@ -1057,10 +1081,10 @@ const add = () => {
|
||||
lineNo: 1,
|
||||
conType: 0,
|
||||
lineInterval: 1,
|
||||
ptRatio: 0,
|
||||
pt2Ratio: 0,
|
||||
ctRatio: 0,
|
||||
ct2Ratio: 0,
|
||||
ptRatio: 1,
|
||||
pt2Ratio: 1,
|
||||
ctRatio: 1,
|
||||
ct2Ratio: 1,
|
||||
volGrade: '',
|
||||
devMac:'',
|
||||
})
|
||||
@@ -1229,10 +1253,10 @@ const updateLineFunc = (id: any) => {
|
||||
lineNo: currentLine.lineNo || 1,
|
||||
conType: currentLine.conType || 0,
|
||||
lineInterval: currentLine.lineInterval || 1,
|
||||
ptRatio: currentLine.ptRatio || 0,
|
||||
pt2Ratio: currentLine.pt2Ratio || 0,
|
||||
ctRatio: currentLine.ctRatio || 0,
|
||||
ct2Ratio: currentLine.ct2Ratio || 0,
|
||||
ptRatio: currentLine.ptRatio || 1,
|
||||
pt2Ratio: currentLine.pt2Ratio || 1,
|
||||
ctRatio: currentLine.ctRatio || 1,
|
||||
ct2Ratio: currentLine.ct2Ratio || 1,
|
||||
volGrade: volGradeValue || 0,
|
||||
devMac: devMac,
|
||||
devId: devId,
|
||||
@@ -1414,10 +1438,10 @@ const next = async () => {
|
||||
lineNo: 1,
|
||||
conType: 0,
|
||||
lineInterval: 1,
|
||||
ptRatio: 0,
|
||||
pt2Ratio: 0,
|
||||
ctRatio: 0,
|
||||
ct2Ratio: 0,
|
||||
ptRatio: 1,
|
||||
pt2Ratio: 1,
|
||||
ctRatio: 1,
|
||||
ct2Ratio: 1,
|
||||
volGrade: '',
|
||||
devMac: '',
|
||||
})
|
||||
@@ -1822,10 +1846,10 @@ const resetAllForms = () => {
|
||||
line.lineNo = 1
|
||||
line.conType = 0
|
||||
line.lineInterval = 1
|
||||
line.ptRatio = 0
|
||||
line.pt2Ratio = 0
|
||||
line.ctRatio = 0
|
||||
line.ct2Ratio = 0
|
||||
line.ptRatio = 1
|
||||
line.pt2Ratio = 1
|
||||
line.ctRatio = 1
|
||||
line.ct2Ratio = 1
|
||||
line.volGrade = ''
|
||||
})
|
||||
|
||||
@@ -2155,10 +2179,10 @@ const handleLineTabsEdit = (targetName: any, action: any) => {
|
||||
lineNo: 1,
|
||||
conType: 0,
|
||||
lineInterval: 1,
|
||||
ptRatio: 0,
|
||||
pt2Ratio: 0,
|
||||
ctRatio: 0,
|
||||
ct2Ratio: 0,
|
||||
ptRatio: 1,
|
||||
pt2Ratio: 1,
|
||||
ctRatio: 1,
|
||||
ct2Ratio: 1,
|
||||
volGrade: '',
|
||||
devMac: '',
|
||||
})
|
||||
|
||||
@@ -10,6 +10,15 @@
|
||||
<div class="device-control-right" v-if="deviceData">
|
||||
<el-descriptions title="监测点信息" class="mb10" width="180" :column="3" border>
|
||||
<template #extra>
|
||||
<!-- <el-button v-if="deviceType == '1'" type="primary" @click="handleDownLoadTemplate">
|
||||
模版下载
|
||||
</el-button> -->
|
||||
<!-- <el-button v-if="deviceType == '1'" type="primary" icon="el-icon-Connection" @click="handleImport">
|
||||
离线补召
|
||||
</el-button>
|
||||
<el-button v-if="deviceType == '1'" type="primary" icon="el-icon-Monitor" @click="handleaddDevice">
|
||||
在线补召
|
||||
</el-button> -->
|
||||
<el-button
|
||||
v-if="deviceType == '1'"
|
||||
type="primary"
|
||||
@@ -36,6 +45,9 @@
|
||||
}}
|
||||
</el-descriptions-item>
|
||||
|
||||
<!-- <el-descriptions-item label="安装位置" width="160">
|
||||
{{ devData.position || '/' }}
|
||||
</el-descriptions-item> -->
|
||||
|
||||
<el-descriptions-item label="PT变比" width="160">
|
||||
{{ devData.ptRatio || '/' }}
|
||||
@@ -44,9 +56,28 @@
|
||||
{{ devData.ctRatio || '/' }}
|
||||
</el-descriptions-item>
|
||||
|
||||
|
||||
<!-- <el-descriptions-item label="名称">
|
||||
{{ devData.name ? devData.name : '/' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="类型">
|
||||
{{ echoName(devData.devType, devTypeOptions) }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="接入方式">
|
||||
{{ devData.devAccessMethod ? devData.devAccessMethod : '/' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="网络设备ID">
|
||||
{{ devData.ndid ? devData.ndid : '/' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="型号">
|
||||
{{ echoName(devData.devModel, devModelOptions) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="接入日期">
|
||||
{{ devData.time ? devData.time : '/' }}
|
||||
</el-descriptions-item> -->
|
||||
</el-descriptions>
|
||||
<el-tabs v-model.trim="dataSet" type="border-card" class="mb10" @tab-click="handleClick">
|
||||
<el-tabs v-model.trim="dataSet" type="border-card" class="device-control-box-card" @tab-click="handleClick">
|
||||
|
||||
<el-tab-pane
|
||||
lazy
|
||||
:label="item.name"
|
||||
@@ -468,7 +499,7 @@
|
||||
v-if="dataSet.indexOf('_event') != -1"
|
||||
v-loading="tableLoading"
|
||||
>
|
||||
<Event ref="eventRef" :device-type="deviceType"></Event>
|
||||
<Event ref="eventRef" :deviceType="deviceType"></Event>
|
||||
</div>
|
||||
<!-- 测试项记录 -->
|
||||
<div
|
||||
@@ -898,7 +929,6 @@ const nodeClick = async (e: anyObj) => {
|
||||
const deviceType = ref('0')
|
||||
const pointTypeChange = (val: any, obj: any) => {
|
||||
deviceType.value = val
|
||||
console.log('pointTypeChange', val)
|
||||
nodeClick(obj)
|
||||
}
|
||||
const realTimeRef: any = ref()
|
||||
@@ -1390,7 +1420,9 @@ const echoName = (value: any, arr: any[]) => {
|
||||
return value ? arr.find(item => item.value == value)?.label : '/'
|
||||
}
|
||||
|
||||
onMounted(() => {})
|
||||
onMounted(() => {
|
||||
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(realDataTimer.value)
|
||||
clearInterval(trendTimer.value)
|
||||
|
||||
@@ -13,7 +13,7 @@ import TableStore from '@/utils/tableStore'
|
||||
import Table from '@/components/table/index.vue'
|
||||
import TableHeader from '@/components/table/header/index.vue'
|
||||
import waveFormAnalysis from './components/waveFormAnalysis.vue'
|
||||
import { ArrowLeft } from '@element-plus/icons-vue'
|
||||
import { ArrowLeft, Message } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { analyseWave,getFileByEventId } from '@/api/common'
|
||||
import { getFileZip } from '@/api/cs-harmonic-boot/datatrend'
|
||||
@@ -134,7 +134,7 @@ const tableStore: any = new TableStore({
|
||||
icon: 'el-icon-DataLine',
|
||||
render: 'basicButton',
|
||||
disabled: row => {
|
||||
return props.deviceType === '2' || row.wavePath;
|
||||
return row.showName != '未知';
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -169,10 +169,11 @@ const tableStore: any = new TableStore({
|
||||
icon: 'el-icon-Check',
|
||||
render: 'basicButton',
|
||||
disabled: row => {
|
||||
return props.deviceType != '2' || row.wavePath;
|
||||
},
|
||||
return props.deviceType === '2' && row.wavePath || row.showName === '未知';
|
||||
},
|
||||
click: row => {
|
||||
getFileByEventId(row.id).then(res => {
|
||||
ElMessage.success(res.message)
|
||||
tableStore.index()
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="default-main">
|
||||
<TableHeader datePicker ref="refheader" >
|
||||
<TableHeader datePicker ref="refheader">
|
||||
<template v-slot:select>
|
||||
<el-form-item label="关键字筛选">
|
||||
<el-input
|
||||
@@ -96,14 +96,14 @@ const tableStore = new TableStore({
|
||||
fixed: 'right',
|
||||
render: 'tag',
|
||||
custom: {
|
||||
2: 'warning',
|
||||
3: 'warning',
|
||||
4: 'success'
|
||||
功能调试: 'warning',
|
||||
出厂调试: 'warning',
|
||||
正式投运: 'success'
|
||||
},
|
||||
replaceValue: {
|
||||
2: '功能调试',
|
||||
3: '出厂调试',
|
||||
4: '正式投运'
|
||||
功能调试: '功能调试',
|
||||
出厂调试: '出厂调试',
|
||||
正式投运: '正式投运'
|
||||
},
|
||||
minWidth: 80
|
||||
},
|
||||
@@ -138,21 +138,21 @@ const tableStore = new TableStore({
|
||||
}
|
||||
},
|
||||
|
||||
{ title: '在线率(%)', fixed: 'right',width: 100, field: 'onlineRate', sortable: true },
|
||||
{ title: '完整性(%)', fixed: 'right',width: 100, field: 'integrity', sortable: true }
|
||||
{ title: '在线率(%)', fixed: 'right', width: 100, field: 'onlineRate', sortable: true },
|
||||
{ title: '完整性(%)', fixed: 'right', width: 100, field: 'integrity', sortable: true }
|
||||
],
|
||||
beforeSearchFun: () => {},
|
||||
loadCallback: () => {
|
||||
tableStore.table.data.forEach(item => {
|
||||
item.process = item.process == 2 ? '功能调试' : item.process == 3 ? '出厂调试' : '正式投运'
|
||||
})
|
||||
|
||||
let name = tableStore.table.params.name
|
||||
let data = tableStore.table.copyData.filter(item => {
|
||||
// 处理latestTime默认值
|
||||
item.latestTime = item.latestTime || '/'
|
||||
// 需要检查的字段列表
|
||||
const fieldsToCheck = ['projectName', 'engineeringName', 'mac', 'devName', 'lineName']
|
||||
console.log(
|
||||
'🚀 ~ fieldsToCheck.some(field => item[field]?.includes(name)):',
|
||||
fieldsToCheck.some(field => item[field]?.includes(name))
|
||||
)
|
||||
|
||||
// 检查任何一个字段包含搜索名称
|
||||
return fieldsToCheck.some(field => item[field]?.includes(name))
|
||||
|
||||
@@ -41,7 +41,7 @@ const tableStore: any = new TableStore({
|
||||
{
|
||||
field: 'engineeringName',
|
||||
title: '项目名称',
|
||||
minWidth: 170,
|
||||
|
||||
formatter: row => {
|
||||
return row.cellValue ? row.cellValue : '/'
|
||||
}
|
||||
@@ -49,7 +49,7 @@ const tableStore: any = new TableStore({
|
||||
{
|
||||
field: 'projectName',
|
||||
title: '工程名称',
|
||||
minWidth: 170,
|
||||
|
||||
formatter: row => {
|
||||
return row.cellValue ? row.cellValue : '/'
|
||||
}
|
||||
@@ -57,7 +57,7 @@ const tableStore: any = new TableStore({
|
||||
{
|
||||
field: 'deviceName',
|
||||
title: '设备名称',
|
||||
minWidth: 170,
|
||||
|
||||
formatter: row => {
|
||||
return row.cellValue ? row.cellValue : '/'
|
||||
}
|
||||
@@ -65,7 +65,7 @@ const tableStore: any = new TableStore({
|
||||
{
|
||||
field: 'lineName',
|
||||
title: '监测点名称',
|
||||
minWidth: 170,
|
||||
|
||||
formatter: row => {
|
||||
return row.cellValue ? row.cellValue : '/'
|
||||
}
|
||||
@@ -73,15 +73,15 @@ const tableStore: any = new TableStore({
|
||||
{
|
||||
field: 'logTime',
|
||||
title: '补召时间',
|
||||
minWidth: 170,
|
||||
|
||||
formatter: row => {
|
||||
return row.cellValue ? row.cellValue : '/'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'log',
|
||||
title: '日志',
|
||||
minWidth: 170,
|
||||
title: '类型',
|
||||
|
||||
formatter: row => {
|
||||
return row.cellValue ? row.cellValue : '/'
|
||||
}
|
||||
@@ -89,7 +89,15 @@ const tableStore: any = new TableStore({
|
||||
{
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
minWidth: 170,
|
||||
|
||||
formatter: row => {
|
||||
return row.cellValue ? row.cellValue : '/'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'result',
|
||||
title: '结果',
|
||||
|
||||
formatter: row => {
|
||||
return row.cellValue ? row.cellValue : '/'
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
style="position: relative"
|
||||
>
|
||||
<!-- @init="nodeClick" -->
|
||||
<PointTree @node-click="nodeClick" @checkChange="handleCheckedNodesChange"></PointTree>
|
||||
<PointTree @node-click="nodeClick" @init="nodeClick" @checkChange="handleCheckedNodesChange"></PointTree>
|
||||
<div class="device-control-right" >
|
||||
<el-tabs type="border-card" class="mb10" @tab-click="handleClick" v-model="activeTab">
|
||||
<el-tab-pane label="稳态补召" name="deviceInfo1">
|
||||
@@ -42,7 +42,7 @@ const checkedNodes = ref<any[]>([]) // 存储左侧树勾选的节点
|
||||
const currentNode = ref<any>(null) // 存储当前点击的树节点
|
||||
|
||||
defineOptions({
|
||||
name: 'govern/monitorRecall/index'
|
||||
name: '/cs-device-boot/monitorRecall'
|
||||
})
|
||||
|
||||
// 处理子组件传递的勾选节点变化
|
||||
@@ -78,6 +78,7 @@ const triggerEventRecallQuery = () => {
|
||||
if (activeTab.value === 'deviceInfo2' && eventRef.value) {
|
||||
// 将当前点击的节点传递给暂态补召组件
|
||||
if (eventRef.value.handleTreeNodeClick) {
|
||||
|
||||
eventRef.value.handleTreeNodeClick(currentNode.value)
|
||||
}
|
||||
}
|
||||
@@ -85,7 +86,7 @@ const triggerEventRecallQuery = () => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.device-control {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, provide, nextTick, defineEmits, watch } from 'vue'
|
||||
import { getTabsDataByType } from '@/api/cs-device-boot/EquipmentDelivery'
|
||||
import TableStore from '@/utils/tableStore'
|
||||
import Table from '@/components/table/index.vue'
|
||||
import TableHeader from '@/components/table/header/index.vue'
|
||||
|
||||
@@ -108,7 +108,7 @@ const editd = (e: any) => {
|
||||
// 设计
|
||||
const Aclick = (e: any) => {
|
||||
// window.open(window.location.origin + `/zutai/?id=${e.id}&&name=${e.name}&&preview=false&&graphicDisplay=zl`)
|
||||
window.open('http://192.168.1.179:4001' + `/zutai/?id=${e.id}&&name=${e.name}&&preview=false&&graphicDisplay=zl`)
|
||||
window.open(window.location.origin + `/zutai/?id=${e.id}&&name=${e.name}&&preview=false&&graphicDisplay=zl`)
|
||||
|
||||
}
|
||||
// 删除
|
||||
@@ -143,8 +143,8 @@ const deleted = (e: any) => {
|
||||
}
|
||||
|
||||
const imgData = (e: any) => {
|
||||
// window.open(window.location.origin + `/zutai/?id=${e.id}&&name=${e.name}&&preview=true&&graphicDisplay=zl#/preview`)
|
||||
window.open('http://192.168.1.179:4001' + `/zutai/?id=${e.id}&&name=${e.name}&&preview=true&&graphicDisplay=zl#/preview`)
|
||||
// window.open(window.location.origin + `/zutai/?id=${e.id}&&name=${e.name}&&preview=true&&graphicDisplay=zl#/preview_ZL`)
|
||||
window.open(window.location.origin + `/zutai/?id=${e.id}&&name=${e.name}&&preview=true&&graphicDisplay=zl#/preview_ZL`)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -187,21 +187,37 @@
|
||||
|
||||
|
||||
<!-- 绑定进程号 -->
|
||||
<el-dialog draggable title="绑定进程号" v-model="popUps" :close-on-click-modal="false" width="400px">
|
||||
<el-select v-model="processNo" placeholder="请选择进程号" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in dataSource"
|
||||
:key="item.name"
|
||||
:label="item.name"
|
||||
:value="item.name"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<el-dialog draggable title="绑定进程号" v-model="popUps" :close-on-click-modal="false" width="400px">
|
||||
<el-form :model="bindProcessForm" ref="bindProcessFormRef" label-width="80px" :rules="rules2" >
|
||||
<el-form-item label="前置机" prop="nodeId">
|
||||
<el-select v-model="bindProcessForm.nodeId" placeholder="请选择前置机" style="width: 100%" clearable @change="handleNodeChange">
|
||||
<el-option
|
||||
v-for="item in tableStore.table.data"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="进程号" prop="processNo">
|
||||
<el-select v-model="bindProcessForm.processNo" placeholder="请选择进程号" style="width: 100%" clearable>
|
||||
<el-option
|
||||
v-for="item in processOptions"
|
||||
:key="item.name"
|
||||
:label="item.name"
|
||||
:value="item.name"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="popUps = false">取 消</el-button>
|
||||
<el-button type="primary" @click="bindTheProcess">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
@@ -231,13 +247,21 @@ const height = mainHeight(70)
|
||||
const loading = ref(false)
|
||||
const popUps = ref(false)
|
||||
const tableRef = ref()
|
||||
const processNo = ref('')
|
||||
const ruleFormRef = ref()
|
||||
|
||||
const dataSource: any = ref([])
|
||||
const defaultProps = {
|
||||
children: 'deviceInfoList',
|
||||
label: 'name'
|
||||
}
|
||||
|
||||
const bindProcessFormRef = ref()
|
||||
const bindProcessForm = ref({
|
||||
nodeId: '',
|
||||
processNo: ''
|
||||
})
|
||||
const processOptions = ref<Array<{ name: string; processNo: string }>>([])
|
||||
|
||||
const formData: any = ref({
|
||||
name: '',
|
||||
ip: '',
|
||||
@@ -256,6 +280,11 @@ const rules = reactive({
|
||||
sort: [{ required: true, message: '排序不可为空', trigger: 'blur' }],
|
||||
remark: [{ required: true, message: '描述不可为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const rules2 = reactive({
|
||||
nodeId: [{ required: true, message: '请选择前置机', trigger: 'change' }],
|
||||
processNo: [{ required: true, message: '请选择进程号', trigger: 'change' }]
|
||||
})
|
||||
const dialogFormVisible = ref(false)
|
||||
|
||||
const dialogTitle = ref('新增前置机')
|
||||
@@ -417,21 +446,66 @@ const treeRef = ref()
|
||||
const change = (val: any) => {
|
||||
treeRef.value!.filter(filterText.value)
|
||||
}
|
||||
|
||||
|
||||
// 修改 edit 方法
|
||||
const edit = (data: any) => {
|
||||
processNo.value = data.nodeProcess
|
||||
bindProcessForm.value.processNo = data.nodeProcess
|
||||
bindProcessForm.value.nodeId = nodeId.value // 默认选中当前节点
|
||||
processId.value = data.id
|
||||
popUps.value = true
|
||||
|
||||
// 首次加载当前前置机的进程号选项
|
||||
loadProcessOptionsForNode(nodeId.value)
|
||||
}
|
||||
|
||||
const loadProcessOptionsForNode = (nodeId: string) => {
|
||||
// 请求该前置机下的进程列表
|
||||
nodeDeviceTree({ id: nodeId }).then(res => {
|
||||
if (res.data && res.data.processDeviceList) {
|
||||
// 处理进程列表数据
|
||||
processOptions.value = res.data.processDeviceList.map(item => ({
|
||||
...item,
|
||||
name: item.processNo + '' // 保持与原逻辑一致
|
||||
}))
|
||||
} else {
|
||||
processOptions.value = []
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// 前置机切换
|
||||
const handleNodeChange = (nodeId: any) => {
|
||||
if (!nodeId) {
|
||||
processOptions.value = []
|
||||
bindProcessForm.value.processNo = ''
|
||||
return
|
||||
}
|
||||
// 清除之前选中的进程号
|
||||
bindProcessForm.value.processNo = ''
|
||||
// 加载新选中前置机的进程号选项
|
||||
loadProcessOptionsForNode(nodeId)
|
||||
}
|
||||
|
||||
|
||||
// 更新进程号
|
||||
const bindTheProcess = () => {
|
||||
updateProcess({
|
||||
id: processId.value,
|
||||
processNo: processNo.value
|
||||
}).then((res: any) => {
|
||||
ElMessage.success('修改成功!')
|
||||
popUps.value = false
|
||||
currentChangeEvent()
|
||||
bindProcessFormRef.value.validate((valid: any) => {
|
||||
if (valid) {
|
||||
updateProcess({
|
||||
id: processId.value,
|
||||
processNo: bindProcessForm.value.processNo,
|
||||
nodeId: bindProcessForm.value.nodeId
|
||||
}).then((res: any) => {
|
||||
ElMessage.success('修改成功!')
|
||||
popUps.value = false
|
||||
currentChangeEvent()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
const filterNode = (value: string, data: any, node: any) => {
|
||||
if (!value) return true
|
||||
|
||||
161
src/views/pqs/cockpit/homePage/components/routingConfig.vue
Normal file
161
src/views/pqs/cockpit/homePage/components/routingConfig.vue
Normal file
@@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog v-model="dialogVisible" title="设置" width="600">
|
||||
<div style="display: flex; justify-content: end" class="mb10">
|
||||
<el-button icon="el-icon-Plus" type="primary" @click="add">新增</el-button>
|
||||
</div>
|
||||
<div style="height: calc(100vh / 2); max-height: 400px">
|
||||
<vxe-table
|
||||
border
|
||||
ref="tableRef"
|
||||
:data="pageList.filter((item: any) => item.pagePath != 'dashboard/index')"
|
||||
align="center"
|
||||
height="auto"
|
||||
v-bind="defaultAttribute"
|
||||
>
|
||||
<vxe-column field="pageName" title="菜单名称"></vxe-column>
|
||||
|
||||
<vxe-column field="icon" title="图标" width="80">
|
||||
<template #default="{ row }">
|
||||
<Icon class="ba-icon-dark" :name="row.icon || ''" />
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="startTime" title="是否激活">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.state"
|
||||
inline-prompt
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
active-text="已激活"
|
||||
inactive-text="未激活"
|
||||
:before-change="() => beforeChange(row)"
|
||||
/>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="startTime" title="操作">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="edit(row)">编辑</el-button>
|
||||
|
||||
<el-button type="danger" link @click="deletes(row)">删除</el-button>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { defaultAttribute } from '@/components/table/defaultAttribute'
|
||||
import { getDashboardPageByUserId, deleteDashboard, activatePage } from '@/api/system-boot/csstatisticalset'
|
||||
import { useAdminInfo } from '@/stores/adminInfo'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
import { getMenu } from '@/utils/router'
|
||||
|
||||
const { push } = useRouter()
|
||||
const dialogVisible = ref(false)
|
||||
const route = useRouter()
|
||||
const adminInfo = useAdminInfo()
|
||||
const pageList: any = ref([])
|
||||
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
init()
|
||||
}
|
||||
const init = () => {
|
||||
getDashboardPageByUserId({ id: adminInfo.id, state: false }).then(res => {
|
||||
pageList.value = res.data
|
||||
})
|
||||
}
|
||||
// 新增
|
||||
const add = () => {
|
||||
push(`/admin/cockpit/popup?path=${String(getNextPagePath(pageList.value))}`)
|
||||
}
|
||||
// 修改
|
||||
const edit = (row: any) => {
|
||||
push(`/admin/cockpit/popup?id=${row?.id}&&path=${row.pagePath}`)
|
||||
}
|
||||
// 激活
|
||||
const beforeChange = (row: any): Promise<boolean> => {
|
||||
return new Promise(resolve => {
|
||||
// setTimeout(() => {
|
||||
// loading1.value = false
|
||||
// ElMessage.success('Switch success')
|
||||
// return resolve(true)
|
||||
// }, 1000)
|
||||
ElMessageBox.confirm('此操作将激活该页面, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
activatePage({ id: row.id, state: row.state == 0 ? 1 : 0 }).then((res: any) => {
|
||||
if (res.code == 'A0000') {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '操作成功!'
|
||||
})
|
||||
}
|
||||
init()
|
||||
resolve(true)
|
||||
getMenu()
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '已取消删除'
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
// 删除
|
||||
const deletes = (row: any) => {
|
||||
ElMessageBox.confirm('此操作将永久删除该菜单, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
deleteDashboard({ id: row.id }).then((res: any) => {
|
||||
if (res.code == 'A0000') {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除页面成功!'
|
||||
})
|
||||
}
|
||||
init()
|
||||
getMenu()
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '已取消删除'
|
||||
})
|
||||
})
|
||||
}
|
||||
function getNextPagePath(pages: any) {
|
||||
// 提取所有pagePath中的数字部分
|
||||
const numbers = pages.map((page: any) => {
|
||||
const match = page.pagePath.match(/dashboard\/index(\d*)$/)
|
||||
if (match && match[1]) {
|
||||
return parseInt(match[1], 10)
|
||||
}
|
||||
return 0 // 没有数字时视为0
|
||||
})
|
||||
|
||||
// 找到最大数字并加1
|
||||
const maxNum = Math.max(...numbers)
|
||||
const nextNum = maxNum + 1
|
||||
|
||||
// 生成下一个pagePath
|
||||
return `dashboard/index${nextNum}`
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -1,12 +1,17 @@
|
||||
<template>
|
||||
<div class="default-main">
|
||||
<TableHeader :showSearch="false">
|
||||
<TableHeader :showSearch="false" v-if="flag">
|
||||
<template v-slot:select>
|
||||
<el-form-item label="日期">
|
||||
<DatePicker ref="datePickerRef" :nextFlag="false" :theCurrentTime="true"></DatePicker>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-slot:operation>
|
||||
<el-button type="primary" icon="el-icon-Edit" @click="editd">编辑</el-button>
|
||||
<el-button type="primary" icon="el-icon-Tools" @click="settings">设置</el-button>
|
||||
</template>
|
||||
</TableHeader>
|
||||
|
||||
<GridLayout
|
||||
v-model:layout="layout"
|
||||
:row-height="rowHeight"
|
||||
@@ -21,66 +26,74 @@
|
||||
<div class="box">
|
||||
<div class="title">
|
||||
<div style="display: flex; align-items: center">
|
||||
<Icon class="HelpFilled" :name="item.icon" />
|
||||
{{ item.name }}
|
||||
<Icon class="HelpFilled" :name="(item as LayoutItem).icon" />
|
||||
{{ (item as LayoutItem).name }}
|
||||
</div>
|
||||
<!-- <FullScreen class="HelpFilled" style="cursor: pointer" @click="zoom(item)" /> -->
|
||||
<img :src="flag ? img : img1" style="cursor: pointer; height: 16px" @click="zoom(item)" />
|
||||
</div>
|
||||
<div>
|
||||
<component
|
||||
:is="item.component"
|
||||
v-if="item.component"
|
||||
:is="(item as LayoutItem).component"
|
||||
v-if="(item as LayoutItem).component"
|
||||
class="pd10"
|
||||
:key="key"
|
||||
:timeValue="datePickerRef.timeValue"
|
||||
:timeValue="datePickerRef?.timeValue || 3"
|
||||
:height="rowHeight * item.h - seRowHeight(item.h) + 'px'"
|
||||
:width="rowWidth * item.w - 30 + 'px'"
|
||||
:timeKey="item.timeKey"
|
||||
:timeKey="(item as LayoutItem).timeKey"
|
||||
:w="item.w"
|
||||
:h="item.h"
|
||||
/>
|
||||
<div v-else class="pd10">组件加载失败...</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</GridLayout>
|
||||
<!-- 设置 -->
|
||||
<RoutingConfig ref="RoutingConfigRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, markRaw, onUnmounted, defineAsyncComponent, type Component } from 'vue'
|
||||
import { ref, reactive, onMounted, markRaw, onUnmounted, computed, defineAsyncComponent, type Component } from 'vue'
|
||||
import TableHeader from '@/components/table/header/index.vue'
|
||||
import { GridLayout } from 'grid-layout-plus'
|
||||
import DatePicker from '@/components/form/datePicker/index.vue'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import { queryActivatePage, queryByPagePath } from '@/api/system-boot/csstatisticalset'
|
||||
import { HelpFilled, FullScreen } from '@element-plus/icons-vue'
|
||||
import RoutingConfig from '@/views/pqs/cockpit/homePage/components/routingConfig.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
const { push } = useRouter()
|
||||
const datePickerRef = ref()
|
||||
const router = useRouter()
|
||||
|
||||
// defineOptions({
|
||||
// name: 'cockpit/homePage'
|
||||
// })
|
||||
defineOptions({
|
||||
// name: 'dashboard/index'
|
||||
})
|
||||
// 定义类型
|
||||
interface LayoutItem {
|
||||
x: number
|
||||
y: number
|
||||
w: number
|
||||
h: number
|
||||
timeKey: number | string
|
||||
i: string | number
|
||||
name: string
|
||||
path: string
|
||||
icon?: string // 新增 icon 可选字段
|
||||
component?: Component | string
|
||||
loading?: boolean
|
||||
error?: any
|
||||
}
|
||||
const RoutingConfigRef = ref()
|
||||
const key = ref(0)
|
||||
const img = new URL(`@/assets/img/amplify.png`, import.meta.url)
|
||||
const img1 = new URL(`@/assets/img/reduce.png`, import.meta.url)
|
||||
const img = new URL(`@/assets/img/amplify.png`, import.meta.url).href
|
||||
const img1 = new URL(`@/assets/img/reduce.png`, import.meta.url).href
|
||||
// 响应式数据
|
||||
const rowHeight = ref(0)
|
||||
const rowWidth = ref(0)
|
||||
const layout = ref<LayoutItem[]>([
|
||||
const layout: any = ref([
|
||||
// {
|
||||
// x: 4,
|
||||
// y: 0,
|
||||
@@ -91,11 +104,11 @@ const layout = ref<LayoutItem[]>([
|
||||
// path: '/src/views/pqs/runManage/assessment/components/uese/index.vue'
|
||||
// },
|
||||
])
|
||||
const layoutCopy = ref<LayoutItem[]>([])
|
||||
const layoutCopy: any = ref([])
|
||||
const flag = ref(true)
|
||||
// 组件映射
|
||||
const componentMap = reactive(new Map<string, Component | string>())
|
||||
|
||||
const dataList: any = ref({})
|
||||
// 获取主内容区域高度
|
||||
const getMainHeight = () => {
|
||||
const elMain = document.querySelector('.el-main')
|
||||
@@ -109,7 +122,7 @@ const getMainWidth = () => {
|
||||
|
||||
// 初始化行高
|
||||
const initRowHeight = () => {
|
||||
rowHeight.value = Math.max(0, (getMainHeight() - 72) / 6)
|
||||
rowHeight.value = Math.max(0, (getMainHeight() - 77 + (flag.value ? 0 : 56)) / 6)
|
||||
rowWidth.value = Math.max(0, getMainWidth() / 12)
|
||||
}
|
||||
|
||||
@@ -153,13 +166,14 @@ const zoom = (value: any) => {
|
||||
if (flag.value) {
|
||||
layout.value = [{ ...value, x: 0, y: 0, w: 12, h: 6 }]
|
||||
} else {
|
||||
layout.value = layoutCopy.value.map((item, index) => ({
|
||||
layout.value = layoutCopy.value.map((item:any, index: number) => ({
|
||||
...item,
|
||||
i: item.i || index, // 确保有唯一标识
|
||||
component: registerComponent(item.path)
|
||||
}))
|
||||
}
|
||||
flag.value = !flag.value
|
||||
initRowHeight()
|
||||
key.value += 1
|
||||
}
|
||||
// 计算组件高度
|
||||
@@ -177,6 +191,7 @@ const seRowHeight = (value: any) => {
|
||||
const fetchLayoutData = async () => {
|
||||
try {
|
||||
const { data } = await queryByPagePath({ pagePath: router.currentRoute.value.name })
|
||||
dataList.value = data
|
||||
const parsedLayout = JSON.parse(data.containerConfig || '[]') as LayoutItem[]
|
||||
// 处理布局数据
|
||||
layout.value = parsedLayout.map((item, index) => ({
|
||||
@@ -185,6 +200,7 @@ const fetchLayoutData = async () => {
|
||||
component: registerComponent(item.path)
|
||||
}))
|
||||
layoutCopy.value = JSON.parse(JSON.stringify(layout.value))
|
||||
initRowHeight()
|
||||
} catch (error) {
|
||||
console.error('获取布局数据失败:', error)
|
||||
// 可以添加错误提示逻辑
|
||||
@@ -197,9 +213,21 @@ const handleResize = useDebounceFn(() => {
|
||||
key.value += 1
|
||||
}, 200)
|
||||
|
||||
// 修改
|
||||
const editd = (e: any) => {
|
||||
if (dataList.value?.id) {
|
||||
push(`/admin/cockpit/popup?id=${dataList.value?.id}&&path=${String(router.currentRoute.value.name)}`)
|
||||
} else {
|
||||
push(`/admin/cockpit/popup?path=${String(router.currentRoute.value.name)}`)
|
||||
}
|
||||
}
|
||||
// 设置
|
||||
const settings = () => {
|
||||
RoutingConfigRef.value.open()
|
||||
}
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
initRowHeight()
|
||||
// initRowHeight()
|
||||
fetchLayoutData()
|
||||
|
||||
// 添加窗口大小变化监听器
|
||||
|
||||
459
src/views/pqs/cockpit/realTimeData/iframeDia.vue
Normal file
459
src/views/pqs/cockpit/realTimeData/iframeDia.vue
Normal file
@@ -0,0 +1,459 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<!-- 使用 v-for 遍历四个角落 -->
|
||||
<div v-for="corner in corners" v-show="corner.show" :key="corner.id" :class="['corner', corner.className]">
|
||||
<div class="content">
|
||||
<div class="title" style="display: flex">
|
||||
<img src="@/assets/img/lightning.png" class="title_img" />
|
||||
<span>{{ corner.title }}</span>
|
||||
</div>
|
||||
<vxe-table
|
||||
:data="corner.data"
|
||||
size="small"
|
||||
border
|
||||
show-header
|
||||
:header-cell-style="{ textAlign: 'center' }"
|
||||
:cell-style="{ textAlign: 'center' }"
|
||||
height="200"
|
||||
style="padding: 5px;"
|
||||
>
|
||||
<vxe-column field="name" title="名称"></vxe-column>
|
||||
<vxe-column field="a" title="A" width="40"></vxe-column>
|
||||
<vxe-column field="b" title="B" width="40"></vxe-column>
|
||||
<vxe-column field="c" title="C" width="40"></vxe-column>
|
||||
</vxe-table>
|
||||
</div>
|
||||
<span class="close-btn" @click="closeCorner(corner.id)">
|
||||
<Close />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount, nextTick, reactive } from 'vue'
|
||||
// import { clickImage } from "@/api/manage_wx";
|
||||
import { Close } from '@element-plus/icons-vue'
|
||||
import socketClient from '@/utils/webSocketClient'
|
||||
|
||||
//开始创建webSocket客户端
|
||||
const dataSocket = reactive({
|
||||
socketServe: socketClient.Instance
|
||||
})
|
||||
// 定义四个角落的数据
|
||||
const corners = ref([
|
||||
{
|
||||
id: 'topLeft',
|
||||
title: '左上',
|
||||
className: 'top-left',
|
||||
show: false,
|
||||
data: [
|
||||
{ name: '电压', a: 1098, b: 2080, c: 3006 },
|
||||
{ name: '电流', a: 15, b: 25, c: 35 },
|
||||
{ name: '电压畸变率', a: 12, b: 22, c: 32 },
|
||||
{ name: '电流畸变率', a: 18, b: 28, c: 38 },
|
||||
{ name: '电压偏值', a: 12, b: 22, c: 32 },
|
||||
{ name: '电压不平衡', a: 18, b: 28, c: 38 }
|
||||
] as any[],
|
||||
elementId: '', // 记录该角落对应的元素ID
|
||||
raceLists: [] as any[] // 为每个角落添加独立的跑马灯数据存储
|
||||
},
|
||||
{
|
||||
id: 'topRight',
|
||||
title: '右上',
|
||||
className: 'top-right',
|
||||
show: false,
|
||||
data: [] as any[],
|
||||
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: "",
|
||||
// },
|
||||
])
|
||||
|
||||
interface DataTableItem {
|
||||
name: string
|
||||
a: number
|
||||
b: number
|
||||
c: number
|
||||
}
|
||||
|
||||
const tableData = ref<DataTableItem[]>([
|
||||
{ name: '电压', a: 1098, b: 2080, c: 3006 },
|
||||
{ name: '电流', a: 15, b: 25, c: 35 },
|
||||
{ name: '电压畸变率', a: 12, b: 22, c: 32 },
|
||||
{ name: '电流畸变率', a: 18, b: 28, c: 38 },
|
||||
{ name: '电压偏值', a: 12, b: 22, c: 32 },
|
||||
{ name: '电压不平衡', a: 18, b: 28, c: 38 }
|
||||
])
|
||||
|
||||
const steadyStateList = ref([])
|
||||
|
||||
const selectedId = ref('')
|
||||
|
||||
// 内部响应式数据
|
||||
const eventList = ref<any[]>([])
|
||||
|
||||
// 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:any) => item !== null && item !== undefined
|
||||
// );
|
||||
// } else if (res.data && Array.isArray(res.data.records)) {
|
||||
// dataToStore = res.data.records.filter(
|
||||
// (item:any) => 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 = [];
|
||||
// }
|
||||
// };
|
||||
|
||||
// 记录显示顺序,用于循环替换
|
||||
const displayOrder = ref<number[]>([])
|
||||
|
||||
// 更新指定角落数据的函数
|
||||
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
|
||||
}
|
||||
|
||||
// 显示下一个角落的函数
|
||||
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: any) => item.lineId == corner.elementId)
|
||||
.forEach((item: any) => {
|
||||
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>`)
|
||||
: ''
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 监听来自 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 scoped lang="scss">
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.corner {
|
||||
width: 240px;
|
||||
/* height: 135px; */
|
||||
background-color: #fff;
|
||||
position: absolute;
|
||||
color: #000;
|
||||
/* 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;
|
||||
padding: 5px;
|
||||
color: #fff;
|
||||
border-bottom: 1px solid #444;
|
||||
background-color: var(--el-color-primary);
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
.title_img {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
border: 1px solid #000;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -2,12 +2,13 @@
|
||||
<div class="default-main" :style="{ padding: prop.height ? '0px !important' : '10px' }">
|
||||
<!-- 实时数据 -->
|
||||
<!-- 添加加载事件监听 -->
|
||||
<div class="dataBox" :style="{ height: prop.height || pageHeight.height }">
|
||||
<div class="dataBox" :style="{ height: prop.height || pageHeight.height }" >
|
||||
<div
|
||||
class="iframe-container"
|
||||
:style="{
|
||||
boxShadow: `var(--el-box-shadow-light)`
|
||||
}"
|
||||
style="position: relative;"
|
||||
>
|
||||
<iframe
|
||||
:src="iframeSrc"
|
||||
@@ -18,9 +19,12 @@
|
||||
id="iframeLeft"
|
||||
@load="onIframeLoad"
|
||||
></iframe>
|
||||
<IframeDia
|
||||
style="position: absolute; top: 0px; right: 0px; left: 0px"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-card class="bottom-container " style="flex: 1;min-height: 165px;">
|
||||
<el-card class="bottom-container " style="min-height: 230px;">
|
||||
<div class="buttonBox">
|
||||
<el-button type="primary" icon="el-icon-Aim">复位</el-button>
|
||||
</div>
|
||||
@@ -36,6 +40,7 @@ import { ref, watch, onMounted, onUnmounted, provide } from 'vue'
|
||||
import Table from '@/components/table/index.vue'
|
||||
import TableStore from '@/utils/tableStore'
|
||||
import { mainHeight } from '@/utils/layout'
|
||||
import IframeDia from './iframeDia.vue'
|
||||
// import { getActive } from "@/api/manage_wx/index";
|
||||
|
||||
// const props = defineProps<{
|
||||
@@ -146,7 +151,7 @@ const iframeSrc = ref('')
|
||||
onMounted(() => {
|
||||
iframeSrc.value =
|
||||
'http://192.168.1.179:4001' +
|
||||
`/zutai/?id=4b4f7f4260198776594f5f9d93a532e8&&name=stt&&preview=true&&display=true#/preview_YPT`
|
||||
`/zutai/?id=4b4f7f4260198776594f5f9d93a532e8&&name=stt&&preview=true#/preview_YPT`
|
||||
|
||||
tableStore.index()
|
||||
|
||||
@@ -226,7 +231,7 @@ const sendKeysToIframe = (keyList: string[]) => {
|
||||
padding: 10px;
|
||||
height: 100%;
|
||||
.buttonBox {
|
||||
display: flex;
|
||||
display: flex;
|
||||
width: 150px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
<template>
|
||||
<div class="pd10">
|
||||
<el-card>
|
||||
<el-form ref="formRef" inline :rules="rules" :model="form" label-width="120px" class="form-four">
|
||||
<el-form ref="formRef" inline :rules="rules" :model="form" label-width="auto" class="form-four">
|
||||
<el-form-item label="页面名称" prop="pageName">
|
||||
<el-input
|
||||
style="width: 100%"
|
||||
maxlength="32"
|
||||
show-word-limit
|
||||
v-model.trim="form.pageName"
|
||||
placeholder="请输入页面名称"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="页面排序" prop="sort">
|
||||
<el-input
|
||||
maxlength="32"
|
||||
show-word-limit-number
|
||||
v-model.trim.number="form.sort"
|
||||
:min="0"
|
||||
:step="1"
|
||||
step-strictly
|
||||
/>
|
||||
<el-form-item label="图标">
|
||||
<IconSelector v-model.trim="form.icon" style="width: 80%" placeholder="请选择图标" />
|
||||
</el-form-item>
|
||||
<el-form-item label="绑定页面">
|
||||
<el-form-item label="页面排序" prop="sort">
|
||||
<el-input-number style="width: 100%" v-model.trim="form.sort" :min="0" :max="10000" :step="1" />
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="绑定页面">
|
||||
<el-select v-model="form.pagePath" filterable placeholder="请选择绑定页面" style="width: 100%" clearable>
|
||||
<el-option v-for="item in pageList" :key="item.path" :label="item.name" :value="item.path" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form-item> -->
|
||||
|
||||
<el-form-item label="备注" class="top">
|
||||
<el-input
|
||||
@@ -91,6 +87,7 @@
|
||||
<template #item="{ item }">
|
||||
<div class="imgBox">
|
||||
<div class="textName">{{ item.name }}</div>
|
||||
|
||||
<img
|
||||
:src="getImg(item.path)"
|
||||
:style="{
|
||||
@@ -110,6 +107,7 @@
|
||||
'px'
|
||||
}"
|
||||
/>
|
||||
|
||||
<CloseBold class="remove" @click="removeItem(item.i)" />
|
||||
</div>
|
||||
<!-- <span class="text">{{ `${item?.name}` }}</span>
|
||||
@@ -127,6 +125,7 @@ import { useRouter } from 'vue-router'
|
||||
import BackComponent from '@/components/icon/back/index.vue'
|
||||
import { mainHeight } from '@/utils/layout'
|
||||
import type { CollapseIconPositionType } from 'element-plus'
|
||||
import IconSelector from '@/components/baInput/components/iconSelector.vue'
|
||||
import { componentTree } from '@/api/user-boot/user'
|
||||
import { GridLayout, GridItem } from 'grid-layout-plus'
|
||||
import { throttle } from 'lodash-es'
|
||||
@@ -135,7 +134,7 @@ import { Tools, CloseBold } from '@element-plus/icons-vue'
|
||||
import { addDashboard, updateDashboard, queryById } from '@/api/system-boot/csstatisticalset'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { getMenu } from '@/utils/router'
|
||||
// defineOptions({
|
||||
// name: 'cockpit/popup'
|
||||
// })
|
||||
@@ -154,9 +153,11 @@ const form: any = reactive({
|
||||
containerConfig: [],
|
||||
sort: '100',
|
||||
id: '',
|
||||
|
||||
icon: '',
|
||||
pagePath: '',
|
||||
pathName: '',
|
||||
remark: ''
|
||||
remark: '',
|
||||
routeName: '/src/views/pqs/cockpit/homePage/index.vue'
|
||||
})
|
||||
const activeNames = ref([])
|
||||
const activeNames1 = ref([])
|
||||
@@ -204,12 +205,14 @@ const info = () => {
|
||||
queryById({ id: query.id }).then(res => {
|
||||
layout.value = JSON.parse(res.data.containerConfig)
|
||||
form.pageName = res.data.pageName
|
||||
form.pagePath = res.data.pagePath
|
||||
form.pathName = res.data.pathName
|
||||
form.pagePath = query.path || res.data.pagePath
|
||||
form.sort = res.data.sort
|
||||
form.remark = res.data.remark
|
||||
form.id = res.data.id
|
||||
form.icon = res.data.icon
|
||||
})
|
||||
} else {
|
||||
form.pagePath = query.path
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +311,6 @@ const drag = throttle(row => {
|
||||
})
|
||||
|
||||
function dragEnd(row: any) {
|
||||
console.log('🚀 ~ drag ~ row:', row)
|
||||
const parentRect = wrapper.value?.getBoundingClientRect()
|
||||
|
||||
if (!parentRect || !gridLayout.value) return
|
||||
@@ -331,7 +333,7 @@ function dragEnd(row: any) {
|
||||
y: dragItem.y,
|
||||
w: dragItem.w,
|
||||
h: dragItem.h,
|
||||
i: dragItem.i,
|
||||
i: Math.random(), //dragItem.i,
|
||||
name: row.name,
|
||||
path: row.path,
|
||||
icon: row.icon,
|
||||
@@ -349,10 +351,10 @@ const onSubmit = () => {
|
||||
if (layout.value.length == 0) {
|
||||
return ElMessage.warning('页面设计不能为空!')
|
||||
}
|
||||
const maxValue = Math.max(...layout.value.map(item => item.y + item.h))
|
||||
if (maxValue > 6) {
|
||||
return ElMessage.warning('组件不能超出当前容器!')
|
||||
}
|
||||
// const maxValue = Math.max(...layout.value.map(item => item.y + item.h))
|
||||
// if (maxValue > 6) {
|
||||
// return ElMessage.warning('组件不能超出当前容器!')
|
||||
// }
|
||||
|
||||
formRef.value.validate(async (valid: boolean) => {
|
||||
let url = ''
|
||||
@@ -362,7 +364,6 @@ const onSubmit = () => {
|
||||
url = canvas.toDataURL('image/png')
|
||||
})
|
||||
form.pagePath = form.pagePath || ''
|
||||
form.pathName = pageList.value.filter((item: any) => item.path == form.pagePath)?.[0]?.name || ''
|
||||
|
||||
if (valid) {
|
||||
if (form.id == '') {
|
||||
@@ -370,6 +371,7 @@ const onSubmit = () => {
|
||||
(res: any) => {
|
||||
ElMessage.success('新增页面成功!')
|
||||
go(-1)
|
||||
getMenu()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
@@ -377,6 +379,7 @@ const onSubmit = () => {
|
||||
(res: any) => {
|
||||
ElMessage.success('修改页面成功!')
|
||||
go(-1)
|
||||
getMenu()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -386,6 +389,7 @@ const onSubmit = () => {
|
||||
const getImg = throttle((path: string) => {
|
||||
if (path != undefined) return treeComponentsCopy.value.filter(item => item.path == path)[0]?.image
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
info()
|
||||
|
||||
@@ -460,7 +464,9 @@ onBeforeUnmount(() => {
|
||||
.vgl-layout {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
:deep(.el-input-group) {
|
||||
width: 90%;
|
||||
}
|
||||
.remove {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
@@ -486,4 +492,7 @@ onBeforeUnmount(() => {
|
||||
background-color: #fff;
|
||||
border: 1px solid black;
|
||||
}
|
||||
:deep(.el-form--inline .el-form-item) {
|
||||
margin-right: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -31,12 +31,7 @@
|
||||
>
|
||||
{{ item.pageName }}
|
||||
</span>
|
||||
<div style="display: flex; align-items: center; font-weight: 550">
|
||||
<div v-if="item.pathName" :style="{ color: `var(--el-color-primary)` }">
|
||||
绑定页面:{{ item.pathName }}
|
||||
</div>
|
||||
<div v-else>暂未绑定</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="display: flex; justify-content: end">
|
||||
<!-- <el-button
|
||||
|
||||
@@ -32,11 +32,11 @@
|
||||
<el-input v-model="form.sort" placeholder="请输入组件排序"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div style="width: 600px; height: 360px; overflow: hidden">
|
||||
<div style="width: 600px; height: 390px; overflow: hidden">
|
||||
<div class="ml10" style="font-weight: 600">组件展示</div>
|
||||
<component :is="registerComponent(form.path)" v-if="registerComponent(form.path)"
|
||||
class="pd10 GridLayout" :key="form.path" :height="'350px'" :width="'580px'"
|
||||
:timeKey="form.timeKey" />
|
||||
:timeKey="form.timeKey" :w="12" :h="6"/>
|
||||
<!-- <div class="pd10">组件加载失败...</div> -->
|
||||
<el-empty v-else description="未查询到组件" style="height: 350px; width: 533px" />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user