1 Commits

Author SHA1 Message Date
guanj
4f907a80c4 优化项目 2026-06-04 19:06:36 +08:00
53 changed files with 987 additions and 3499 deletions

View File

@@ -154,7 +154,7 @@ const tableStore: any = new TableStore({
series: [
{
type: 'bar',
name: '越限占比',
name: '指标越限严重度',
data: tableStore.table.data.map((item: any) => Math.floor(item.extent * 100) / 100),
barMaxWidth: 30
}

View File

@@ -52,9 +52,10 @@
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h, computed, nextTick } from 'vue'
import { ref, onMounted, onUnmounted, provide, reactive, watch, h, computed, nextTick } from 'vue'
import TableStore from '@/utils/tableStore'
import { exportExcel } from '@/views/govern/reportForms/export.js'
import { destroyLuckysheet, renderLuckysheetReport } from '@/utils/luckysheetHelper'
import TableHeader from '@/components/table/header/index.vue'
import { querySysExcel } from '@/api/harmonic-boot/luckyexcel'
import { getListByIds } from '@/api/harmonic-boot/cockpit/cockpit'
@@ -119,6 +120,9 @@ const downloadExcel = () => {
onMounted(() => {
initListByIds()
})
onUnmounted(() => {
destroyLuckysheet()
})
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
if (datePickerValue && datePickerValue.timeValue) {
@@ -159,16 +163,7 @@ const tableStore: any = new TableStore({
// }
},
loadCallback: () => {
luckysheet.create({
container: 'luckysheet',
title: '', // 表 头名
lang: 'zh', // 中文
showtoolbar: false, // 是否显示工具栏
showinfobar: false, // 是否显示顶部信息栏
showsheetbar: true, // 是否显示底部sheet按钮
allowEdit: false, // 禁止所有编辑操作(必填)
data: tableStore.table.data
})
renderLuckysheetReport('luckysheet', tableStore.table.data, { allowEdit: false })
}
})

View File

@@ -220,7 +220,7 @@ const tableStore: any = new TableStore({
icon: 'el-icon-DataLine',
render: 'basicButton',
disabled: row => {
return !!row.wavePath
return row.wavePath
}
}
]

View File

@@ -214,7 +214,7 @@ const tableStore: any = new TableStore({
icon: 'el-icon-DataLine',
render: 'basicButton',
disabled: row => {
return !!row.wavePath
return row.wavePath
}
}
]

View File

@@ -236,7 +236,7 @@ const fliteWaveData = (wp, step, iphasicValue, isOpen) => {
// 监听消息
self.onmessage = function (e) {
const { wp, isOpen, value, boxoList } = JSON.parse(e.data)
const { wp, isOpen, value, boxoList, requestId } = e.data
try {
const iphasicValue = wp.iphasic || 1
@@ -303,6 +303,7 @@ self.onmessage = function (e) {
}
// 发送处理结果回主线程
self.postMessage({
requestId,
titles: titles,
success: true,
waveDatas,
@@ -313,6 +314,7 @@ self.onmessage = function (e) {
})
} catch (error) {
self.postMessage({
requestId,
success: false,
error: error.message
})

View File

@@ -14,9 +14,15 @@ import html2canvas from 'html2canvas'
import $ from 'jquery'
import * as echarts from 'echarts'
import { mainHeight } from '@/utils/layout'
import { calcRmsYAxisRange, formatAxisLabel } from '@/utils/chartAxisHelper'
import url from '@/assets/img/point.png'
import url2 from '@/assets/img/dw.png'
const worker = ref<Worker | null>(null)
import { buildWaveCacheKey, getWaveCache, setWaveCache } from '@/utils/waveCache'
import { getRmsWorker, buildWorkerPayload } from '@/utils/waveWorkerPool'
let waveRequestId = 0
const pendingCacheKeys = new Map<number, string>()
let rmsWorker: Worker | null = null
interface WaveData {
instantF: { max: number; min: number }
instantS: { max: number; min: number }
@@ -125,26 +131,19 @@ const vw = computed(() => '100%')
watch(
() => props.value,
newVal => {
if (newVal == 2) {
initWaves()
} else {
$('#wave1').remove()
initWaves()
}
() => {
query()
}
)
onMounted(() => {
const zoomValue = document.body.style.getPropertyValue('zoom')
zoom.value = 1 / (zoomValue ? parseFloat(zoomValue) : 1)
window.addEventListener('resize', handleResize)
watch(
() => props.wp,
() => {
query()
}
)
// 初始化 Web Worker
worker.value = new Worker(new URL('./rmsWorker.js', import.meta.url))
worker.value.onmessage = e => {
if (e.data.success) {
const data = e.data
const applyWorkerResult = (data: any) => {
titles.value = data.titles
waveDatas.value = data.waveDatas
time.value = data.time
@@ -152,25 +151,41 @@ onMounted(() => {
severity.value = data.severity
iphasic.value = data.iphasic
// 初始化波形图
initWave(waveDatas.value, time.value, type.value, severity.value, isOpen.value)
} else {
console.error('Worker error:', e.data.error)
loading.value = false
}
if (Number(severity.value) < 0) {
severity.value = '/'
type.value = '/'
}
initWave(waveDatas.value, time.value, type.value, severity.value, isOpen.value)
loading.value = false
}
onMounted(() => {
const zoomValue = document.body.style.getPropertyValue('zoom')
zoom.value = 1 / (zoomValue ? parseFloat(zoomValue) : 1)
window.addEventListener('resize', handleResize)
rmsWorker = getRmsWorker(data => {
if (data.requestId !== waveRequestId) return
if (!data.success) {
console.error('Worker error:', data.error)
loading.value = false
return
}
const cacheKey = pendingCacheKeys.get(data.requestId)
if (cacheKey) {
setWaveCache(cacheKey, data)
pendingCacheKeys.delete(data.requestId)
}
applyWorkerResult(data)
})
nextTick(() => {
setTimeout(() => {
query()
}, 500)
})
})
onBeforeUnmount(() => {
if (worker.value) {
worker.value.terminate()
}
backbxlb()
window.removeEventListener('resize', handleResize)
})
@@ -195,23 +210,15 @@ const download = () => {
}
}
const EXTRA_PANEL_CLASS = 'wave-extra-panel'
const resetWaveDom = () => {
backbxlb()
$('#rmsp').nextAll(`.${EXTRA_PANEL_CLASS}`).remove()
}
const query = () => {
loading.value = true
if (props.wp) {
// 使用 Worker 处理数据
if (worker.value) {
worker.value.postMessage(
JSON.stringify({
wp: props.wp,
isOpen: isOpen.value,
value: props.value,
boxoList: props.boxoList
})
)
}
} else {
initWave(null, null, null, null, null)
}
initWaves()
}
const waveData = (
@@ -245,29 +252,31 @@ const waveData = (
}
const initWaves = () => {
if (props.wp) {
iphasic.value = props.wp.iphasic || 1
const picCounts = (props.wp.waveTitle.length - 1) / iphasic.value
waveDatas.value = []
for (let i = 0; i < picCounts; i++) {
const data = fliteWaveData(props.wp, i)
waveDatas.value.push(data)
}
time.value = props.wp.time
type.value = props.wp.waveType
severity.value = props.wp.yzd
if (Number(severity.value) < 0) {
severity.value = '/'
type.value = '/'
}
initWave(waveDatas.value, time.value, type.value, severity.value, isOpen.value)
} else {
if (!props.wp?.listRmsData?.length) {
initWave(null, null, null, null, null)
loading.value = false
return
}
const cacheKey = buildWaveCacheKey('rms', props.wp, props.value, isOpen.value, props.boxoList)
const cached = getWaveCache<any>(cacheKey)
if (cached) {
applyWorkerResult(cached)
return
}
loading.value = true
iphasic.value = props.wp.iphasic || 1
const currentRequestId = ++waveRequestId
pendingCacheKeys.set(currentRequestId, cacheKey)
rmsWorker?.postMessage(
buildWorkerPayload('rms', props.wp, props.boxoList, {
requestId: currentRequestId,
value: props.value,
isOpen: isOpen.value
})
)
}
const fliteWaveData = (wp: any, step: number): WaveData => {
@@ -482,7 +491,7 @@ const initWave = (
severity: string | null,
isOpen: boolean | null
) => {
$('div.bx').remove()
resetWaveDom()
let picHeight = vh.value
const show = !isOpen
@@ -594,7 +603,7 @@ const initWave = (
for (let step = waveDatas.length - 1; step > 0 && step < waveDatas.length; step--) {
const rmsId = 'rms' + step
const newDivRms = $(
`<div style="height:${vh.value};overflow: hidden;min-height: 200px;"><div class='bx' id='${rmsId}'></div></div>`
`<div class="${EXTRA_PANEL_CLASS}" style="height:${vh.value};overflow: hidden;min-height: 200px;"><div class='bx' id='${rmsId}'></div></div>`
)
newDivRms.insertAfter($('#rmsp'))
$(`#${rmsId}`).css('height', picHeight).css('width', vw.value).css('min-height', '200px')
@@ -607,6 +616,12 @@ const initWave = (
const rms = document.getElementById('rmsp')
if (!rms) return
const yRange = calcRmsYAxisRange(rmscu[0]?.[1] ?? 0, rmscm[0]?.[1] ?? 0)
const existingChart = echarts.getInstanceByDom(rms)
if (existingChart) existingChart.dispose()
const myChartes = echarts.init(rms)
const echartsColor = {
WordColor: '#000',
@@ -750,8 +765,8 @@ const initWave = (
},
// max: rmscm[0]?.[1] * 1.06 || 0,
// min: rmscu[0]?.[1] - rmscu[0]?.[1] * 0.2 || 0,
max: Math.floor((rmscm[0]?.[1] * 1.06 || 0) * 1.1 * 10) / 10,
min: Math.floor((rmscu[0]?.[1] - rmscu[0]?.[1] * 0.2 || 0) * 10) / 10,
max: yRange.max,
min: yRange.min,
boundaryGap: [0, '100%'],
showLastLabel: true,
opposite: false,
@@ -770,7 +785,7 @@ const initWave = (
fontSize: '12px',
color: props.DColor ? '#000' : echartsColor.WordColor,
formatter: function (value: number) {
return Math.floor(value * 1000) / 1000
return formatAxisLabel(value)
}
},
splitLine: {
@@ -955,6 +970,10 @@ const drawPics = (
const rmsIds = document.getElementById(rmsId)
if (!rmsIds) return
const subMin = props.value === 1 ? waveDataTemp.RMSF.min : waveDataTemp.RMSS.min
const subMax = props.value === 1 ? waveDataTemp.RMSF.max : waveDataTemp.RMSS.max
const yRange = calcRmsYAxisRange(subMin, subMax)
const myChartes = echarts.init(rmsIds)
const echartsColor = {
WordColor: '#000',
@@ -1078,6 +1097,8 @@ const drawPics = (
},
boundaryGap: [0, '100%'],
showLastLabel: true,
max: yRange.max,
min: yRange.min,
opposite: false,
// max: Math.floor((rmscm[0]?.[1] * 1.06 || 0) * 1.1 * 10) / 10,
// min: Math.floor((rmscu[0]?.[1] - rmscu[0]?.[1] * 0.2 || 0) * 10) / 10,
@@ -1096,8 +1117,7 @@ const drawPics = (
fontSize: '12px',
color: props.DColor ? '#000' : echartsColor.WordColor,
formatter: function (value: number) {
// return (value - 0).toFixed(2)
return Math.floor(value * 1000) / 1000
return formatAxisLabel(value)
}
},
splitLine: {
@@ -1213,12 +1233,7 @@ const backbxlb = () => {
myChartess4.value = null
myChartess5.value = null
// echarts.disconnect(charts.filter(Boolean) as echarts.ECharts[])
charts.filter(Boolean).forEach(chart => {
if (chart && typeof chart.dispose === 'function') {
chart.dispose()
}
})
$('#rmsp').nextAll(`.${EXTRA_PANEL_CLASS}`).remove()
}
const getMax = (temp: number, tempA: number, tempB: number, tempC: number): number => {

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
// waveData.worker.js
self.addEventListener('message', function (e) {
const { wp, value, iphasic, isOpen, boxoList } = JSON.parse(e.data)
const { wp, value, iphasic, isOpen, boxoList, requestId } = e.data
// 处理波形数据的函数
const fliteWaveData = (wp, step) => {
@@ -195,6 +195,8 @@ self.addEventListener('message', function (e) {
// 将处理结果发送回主线程
self.postMessage({
requestId,
success: true,
waveDatas,
time,
type,

View File

@@ -1,8 +1,8 @@
<template>
<div v-loading="loading" class="boxbx" style="position: relative; height: 100%">
<div id="boxsj">
<div id="shushi" :style="`height:${vh};overflow: hidden;min-height: 200px;`">
<div class="bx" id="wave" style="min-height: 200px"></div>
<div id="shushi" :style="containerStyle">
<div class="bx" id="wave" :style="waveStyle"></div>
</div>
</div>
</div>
@@ -14,9 +14,14 @@ import html2canvas from 'html2canvas'
import $ from 'jquery'
import * as echarts from 'echarts'
import { mainHeight } from '@/utils/layout'
import { calcShuYAxisRange, formatAxisLabel } from '@/utils/chartAxisHelper'
import url from '@/assets/img/point.png'
// 创建Worker
let waveDataWorker: Worker | null = null
import { buildWaveCacheKey, getWaveCache, setWaveCache } from '@/utils/waveCache'
import { getShuWorker, buildWorkerPayload } from '@/utils/waveWorkerPool'
let waveRequestId = 0
const pendingCacheKeys = new Map<number, string>()
let shuWorker: Worker | null = null
interface WaveData {
instantF: { max: number; min: number }
instantS: { max: number; min: number }
@@ -110,38 +115,82 @@ const vh = computed(() => {
const vw = computed(() => '100%')
const containerStyle = computed(() => ({
height: vh.value,
overflow: 'hidden',
minHeight: '200px'
}))
const waveStyle = computed(() => ({
width: '100%',
height: '100%',
minHeight: '200px'
}))
const applyChartSize = (el: HTMLElement) => {
el.style.width = '100%'
el.style.height = vh.value
}
const finishChartRender = (chart: echarts.ECharts, endLoading = false) => {
nextTick(() => {
chart.resize()
if (endLoading) {
loading.value = false
}
})
}
watch(
() => props.value,
newVal => {
if (newVal == 2) {
initWaves()
} else {
$('#wave1').remove()
initWaves()
}
() => {
query()
}
)
watch(
() => props.wp,
() => {
query()
}
)
const applyWorkerResult = (data: any) => {
titles.value = data.titles
iphasic.value = data.iphasic
time.value = data.time
type.value = data.type
severity.value = data.severity
initWave(data.waveDatas, data.time, data.type, data.severity, isOpen.value)
loading.value = false
}
onMounted(() => {
const zoomValue = document.body.style.getPropertyValue('zoom')
zoom.value = 1 / (zoomValue ? parseFloat(zoomValue) : 1)
window.addEventListener('resize', handleResize)
shuWorker = getShuWorker(data => {
if (data.requestId !== waveRequestId) return
if (!data.success) {
console.error('Worker error:', data.error)
loading.value = false
return
}
const cacheKey = pendingCacheKeys.get(data.requestId)
if (cacheKey) {
setWaveCache(cacheKey, data)
pendingCacheKeys.delete(data.requestId)
}
applyWorkerResult(data)
})
nextTick(() => {
setTimeout(() => {
query()
}, 500)
})
})
onBeforeUnmount(() => {
console.log('组件卸载')
if (waveDataWorker) {
waveDataWorker.terminate()
waveDataWorker = null
}
backbxlb()
window.removeEventListener('resize', handleResize)
})
@@ -166,6 +215,13 @@ const download = () => {
}
}
const EXTRA_PANEL_CLASS = 'wave-extra-panel'
const resetWaveDom = () => {
backbxlb()
$('#shushi').nextAll(`.${EXTRA_PANEL_CLASS}`).remove()
}
const query = () => {
loading.value = true
initWaves()
@@ -184,48 +240,32 @@ const waveData = (instantF: any, instantS: any, shunshiF: any, shunshiS: any, ti
// 在组件中修改initWaves函数
const initWaves = () => {
if (props.wp) {
if (!props.wp?.listWaveData?.length) {
initWave(null, null, null, null, null)
loading.value = false
return
}
const cacheKey = buildWaveCacheKey('shu', props.wp, props.value, isOpen.value, props.boxoList)
const cached = getWaveCache<any>(cacheKey)
if (cached) {
applyWorkerResult(cached)
return
}
loading.value = true
iphasic.value = props.wp.iphasic || 1
// 使用Web Worker处理数据
if (!waveDataWorker) {
waveDataWorker = new Worker(new URL('./shuWorker.js', import.meta.url))
const currentRequestId = ++waveRequestId
pendingCacheKeys.set(currentRequestId, cacheKey)
waveDataWorker.onmessage = function (e) {
const data = e.data
titles.value = data.titles
iphasic.value = data.iphasic
time.value = data.time
type.value = data.type
severity.value = data.severity
initWave(data.waveDatas, data.time, data.type, data.severity, isOpen.value)
loading.value = false
}
waveDataWorker.onerror = function (error) {
console.error('Worker error:', error)
loading.value = false
// 备用方案:在主线程处理数据
// processDataInMainThread();
}
}
// 发送数据到Worker
waveDataWorker.postMessage(
JSON.stringify({
wp: props.wp,
shuWorker?.postMessage(
buildWorkerPayload('shu', props.wp, props.boxoList, {
requestId: currentRequestId,
value: props.value,
iphasic: iphasic.value,
isOpen: isOpen.value,
boxoList: props.boxoList
iphasic: iphasic.value
})
)
} else {
initWave(null, null, null, null, null)
}
}
const initWave = (
@@ -235,6 +275,7 @@ const initWave = (
severity: string | null,
isOpen: boolean | null
) => {
resetWaveDom()
$('div.bx1').remove()
let picHeight = vh.value
@@ -327,7 +368,7 @@ const initWave = (
for (let step = waveDatas.length - 1; step > 0 && step < waveDatas.length; step--) {
const waveId = 'wave' + step
const newDivShunshi = $(`<div style="height:${vh.value};overflow: hidden;min-height: 200px;">
const newDivShunshi = $(`<div class="${EXTRA_PANEL_CLASS}" style="height:${vh.value};overflow: hidden;min-height: 200px;">
<div class='bx1' id='${waveId}'></div>
</div>`)
newDivShunshi.insertAfter($('#shushi'))
@@ -342,6 +383,13 @@ const initWave = (
const wave = document.getElementById('wave')
if (!wave) return
applyChartSize(wave)
const yRange = calcShuYAxisRange(Number(min), Number(max))
const existingChart = echarts.getInstanceByDom(wave)
if (existingChart) existingChart.dispose()
const myChartes = echarts.init(wave)
const echartsColor = {
WordColor: '#000',
@@ -363,11 +411,6 @@ const initWave = (
]
}
setTimeout(() => {
wave.style.width = '100%'
wave.style.height = vh.value
}, 0)
const option = {
tooltip: {
top: '10px',
@@ -481,10 +524,8 @@ const initWave = (
},
boundaryGap: [0, '100%'],
showLastLabel: true,
// max: max.toFixed(2) * 1.1,
// min: min.toFixed(2) > 0 ? min.toFixed(2) - min.toFixed(2) * 0.1 : min.toFixed(2) * 1.1,
max: Math.floor(max.toFixed(2) * 1.1 * 10) / 10,
min: Math.floor(min.toFixed(2) > 0 ? min.toFixed(2) - min.toFixed(2) * 0.1 : min.toFixed(2) * 1.1 * 10) / 10 ,
max: yRange.max,
min: yRange.min,
opposite: false,
nameTextStyle: {
fontSize: '12px',
@@ -501,8 +542,7 @@ const initWave = (
fontSize: '12px',
color: props.DColor ? '#000' : echartsColor.WordColor,
formatter: function (value: number) {
// return (value - 0).toFixed(2)
return Math.floor(value * 1000) / 1000
return formatAxisLabel(value)
}
},
splitLine: {
@@ -518,7 +558,6 @@ const initWave = (
right: '45px',
bottom: '40px',
top: '60px'
// containLabel: true
},
dataZoom: [
{
@@ -578,11 +617,7 @@ const initWave = (
myChartes.setOption(option)
myChartess.value = myChartes
setTimeout(() => {
myChartes.resize()
loading.value = false
}, 400)
finishChartRender(myChartes, true)
if (waveDatas && waveDatas.length > 1) {
const waveDatasTemp = waveDatas.slice(1)
@@ -679,6 +714,13 @@ const drawPics = (
const waveIds = document.getElementById(waveId)
if (!waveIds) return
applyChartSize(waveIds)
const yRange = calcShuYAxisRange(Number(min), Number(max))
const existingChart = echarts.getInstanceByDom(waveIds)
if (existingChart) existingChart.dispose()
const myChartes = echarts.init(waveIds)
const echartsColor = {
WordColor: '#000',
@@ -791,8 +833,8 @@ const drawPics = (
},
boundaryGap: [0, '100%'],
showLastLabel: true,
max: Math.floor(max.toFixed(2) * 1.1 * 10) / 10,
min: Math.floor(min.toFixed(2) > 0 ? min.toFixed(2) - min.toFixed(2) * 0.1 : min.toFixed(2) * 1.1 * 10) / 10 ,
max: yRange.max,
min: yRange.min,
opposite: false,
nameTextStyle: {
fontSize: '12px',
@@ -809,8 +851,7 @@ const drawPics = (
fontSize: '12px',
color: props.DColor ? '#000' : echartsColor.WordColor,
formatter: function (value: number) {
// return (value - 0).toFixed(2)
return Math.floor(value * 1000) / 1000
return formatAxisLabel(value)
}
},
splitLine: {
@@ -897,10 +938,7 @@ const drawPics = (
break
}
setTimeout(() => {
myChartes.resize()
loading.value = false
}, 400)
finishChartRender(myChartes)
echarts.connect([myChartes1, myChartes])
}
@@ -929,12 +967,8 @@ const backbxlb = () => {
myChartess4.value = null
myChartess5.value = null
// echarts.disconnect(charts.filter(Boolean) as echarts.ECharts[])
charts.filter(Boolean).forEach(chart => {
if (chart && typeof chart.dispose === 'function') {
chart.dispose()
}
})
$('#shushi').nextAll(`.${EXTRA_PANEL_CLASS}`).remove()
$('div.bx1').remove()
}
const getMax = (temp: number, tempA: number, tempB: number, tempC: number): number => {

File diff suppressed because it is too large Load Diff

View File

@@ -10,10 +10,11 @@
@checkbox-all="selectChangeEvent"
@checkbox-change="selectChangeEvent"
:showOverflow="showOverflow"
:sort-config="{ remote: true }"
@sort-change="handleSortChange"
>
>
<!-- :sort-config="{ remote: true }" -->
<!-- @sort-change="handleSortChange" -->
<!-- Column 组件内部是 el-table-column -->
<template v-if="isGroup">
<GroupColumn :column="tableStore.table.column" />

View File

@@ -152,7 +152,7 @@ async function selectInitialNode(
treeInstance?.setCurrentKey(node.id)
emit('init', { level, ...node })
emit('init', { ...node })
return

View File

@@ -64,7 +64,7 @@ async function loadTree() {
await selectTreeNode(treRef.value, node, {
level: 3,
onSelect: selected => {
emit('init', { level: 3, ...selected })
emit('init', { ...selected, level: 3 })
changePointType('4', selected)
}
})

View File

@@ -48,7 +48,7 @@ async function initTree(list: any[]) {
}
await selectTreeNode(treRef.value, node, {
onSelect: selected => emit('init', { level: 2, ...selected })
onSelect: selected => emit('init', { ...selected, level: 2 })
})
}

View File

@@ -1,16 +1,7 @@
<template>
<Tree
ref="treRef"
@check-change="handleCheckChange"
:default-checked-keys="defaultCheckedKeys"
:show-checkbox="props.showCheckbox"
:data="tree"
:height="props.height"
@changeDeviceType="changeDeviceType"
@changeTreeType="loadTree"
:engineering="props.engineering"
leaf-mode="device"
/>
<Tree ref="treRef" @check-change="handleCheckChange" :default-checked-keys="defaultCheckedKeys"
:show-checkbox="props.showCheckbox" :data="tree" :height="props.height" @changeDeviceType="changeDeviceType"
@changeTreeType="loadTree" :engineering="props.engineering" leaf-mode="device" />
</template>
<script lang="ts" setup>
@@ -65,7 +56,7 @@ async function selectInitialNode(type: string | undefined, leaves: LineTreeLeave
if (!node) continue
const treeInstance = await waitForTreeRef(treRef.value, refKey)
treeInstance?.setCurrentKey(node.id)
emit('init', { level, ...node })
emit('init', { ...node })
return
}
emit('init')
@@ -81,13 +72,10 @@ const loadTree = (type?: string) => {
onMounted(() => loadTree(props.engineering ? '2' : '1'))
const handleCheckChange = throttle(
const handleCheckChange =
(data: any, checked: any, indeterminate: any) => {
emit('checkChange', { data, checked, indeterminate })
},
300,
{ leading: true, trailing: false }
)
}
defineExpose({ treRef })
</script>

View File

@@ -17,7 +17,8 @@ export function decorateDeviceTree(
child.children?.forEach((grand: any) => {
applyMeta(grand, {
icon: 'el-icon-Platform',
color: statusColor(grand.comFlag)
color: statusColor(grand.comFlag),
level: 3
})
leaves.engineering.push(grand)
})
@@ -68,6 +69,7 @@ export function decorateDeviceTree(
l3.pName = '监测设备'
applyMeta(l3, {
icon: 'el-icon-Platform',
level: 3,
color: l3.comFlag === 1 ? '#e26257 !important' : primary()
})
leaves.monitor.push(l3)

View File

@@ -39,6 +39,21 @@ export function createLineTreeDecorators(getPrimaryColor: () => string): LineTre
export type TreeRefKey = 'treeRef1' | 'treeRef2' | 'treeRef3' | 'treeRef4'
/** 线路树可选叶子节点元数据 */
export const LINE_LEAF_META = { level: 3, type: 'line' as const }
/** 是否为线路树可选叶子(监测点/线路) */
export function isLineTreeLeaf(node: any): boolean {
if (!node?.id) return false
return node.type === 'line' || node.level === 3
}
/** 是否为报告/导出可选监测点 */
export function isReportMonitorPoint(node: any): boolean {
if (!node?.id) return false
return isLineTreeLeaf(node) || node.level === 3 || (!node.children?.length && !!node.pid)
}
export interface DecorateLineTreeOptions {
/** 是否禁用父级节点(分析树隐藏父节点,测点树不禁用) */
disableParents?: boolean
@@ -69,7 +84,11 @@ export function decorateLineTree(
...parentDisabled
})
grand.children?.forEach((leaf: any) => {
applyMeta(leaf, { icon: 'el-icon-Platform', color: statusColor(leaf.comFlag) })
applyMeta(leaf, {
icon: 'el-icon-Platform',
color: statusColor(leaf.comFlag),
...LINE_LEAF_META
})
leaves.engineering.push(leaf)
})
})
@@ -90,7 +109,11 @@ export function decorateLineTree(
...parentDisabled
})
l3.children?.forEach((l4: any) => {
applyMeta(l4, { icon: 'el-icon-Platform', color: statusColor(l4.comFlag) })
applyMeta(l4, {
icon: 'el-icon-Platform',
color: statusColor(l4.comFlag),
...LINE_LEAF_META
})
leaves.govern.push(l4)
})
})
@@ -100,7 +123,11 @@ export function decorateLineTree(
item.children?.forEach((l1: any) => {
applyMeta(l1, { icon: 'el-icon-Platform', color: statusColor(l1.comFlag) })
l1.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-Platform', color: statusColor(l2.comFlag) })
applyMeta(l2, {
icon: 'el-icon-Platform',
color: statusColor(l2.comFlag),
...LINE_LEAF_META
})
leaves.portable.push(l2)
})
})
@@ -117,7 +144,11 @@ export function decorateLineTree(
...parentDisabled
})
l3.children?.forEach((l4: any) => {
applyMeta(l4, { icon: 'el-icon-Platform', color: statusColor(l4.comFlag) })
applyMeta(l4, {
icon: 'el-icon-Platform',
color: statusColor(l4.comFlag),
...LINE_LEAF_META
})
leaves.monitor.push(l4)
})
})

View File

@@ -64,7 +64,7 @@ async function selectInitialNode(type: string | undefined, leaves: LineTreeLeave
const treeInstance = await waitForTreeRef(treRef.value, refKey)
treeInstance?.setCurrentKey(node.id)
emit('init', { level, ...node })
emit('init', { ...node })
if (type === '2') {
changePointType('4', node)

View File

@@ -59,7 +59,7 @@ interface Props {
const props = withDefaults(defineProps<Props>(), { template: false })
const emit = defineEmits(['init', 'checkChange', 'nodeChange', 'editNode', 'getChart', 'Policy'])
const emit = defineEmits(['init', 'checkChange', 'nodeChange', 'node-click', 'editNode', 'getChart', 'Policy'])
const config = useConfig()
const tree = ref<any[]>([])
@@ -109,6 +109,7 @@ const clickNode = (e: any) => {
planId.value = e?.children ? e.id : e.pid
id.value = e.id
emit('nodeChange', e)
emit('node-click', e)
}
bootstrapWithTemplate(

View File

@@ -52,6 +52,7 @@ import useCurrentInstance from '@/utils/useCurrentInstance'
import { ElMessage, ElTree } from 'element-plus'
import { ref, watch } from 'vue'
import { createTreeFilterNode } from './govern/treeFilterUtils'
import { isLineTreeLeaf } from './govern/lineTreeUtils'
defineOptions({ name: 'govern/select', inheritAttrs: false })
@@ -75,7 +76,8 @@ const filterNode = createTreeFilterNode()
const checkedNodes = ref<any[]>([])
const defaultCheckedKeys = ref<string[]>([])
const MAX_CHECK = 5
const MONITOR_LEVEL = 3
const isMonitorLeaf = (node: any) => isLineTreeLeaf(node)
watch(filterText, val => treeRef.value?.filter(val))
@@ -85,7 +87,7 @@ const onMenuCollapse = () => {
}
const handleCheckChange = (_data: any, checkInfo: any) => {
const monitoringPointNodes = (checkInfo.checkedNodes as any[]).filter(node => node.level === MONITOR_LEVEL)
const monitoringPointNodes = (checkInfo.checkedNodes as any[]).filter(isMonitorLeaf)
if (monitoringPointNodes.length > MAX_CHECK) {
const previousCheckedNodes = checkedNodes.value
@@ -122,7 +124,7 @@ const updateNodeCheckStatus = (currentCount: number) => {
if (!treeRef.value) return
const isMaxSelected = currentCount >= MAX_CHECK
treeRef.value.store._getAllNodes().forEach((node: any) => {
if (node.level === MONITOR_LEVEL) {
if (isMonitorLeaf(node.data)) {
node.data.disabled = isMaxSelected && !node.checked
}
})

View File

@@ -1,6 +1,6 @@
<template>
<div class="layout-logo">
<img v-if="!config.layout.menuCollapse" class="logo-img" :src="getTheme.logoUrl" />
<img v-if="!config.layout.menuCollapse && getTheme.logoUrl" class="logo-img" :src="getTheme.logoUrl" />
<!-- <div-->
<!-- v-if="!config.layout.menuCollapse"-->
<!-- :style="{ color: config.getColorVal('menuActiveColor') }"-->
@@ -8,7 +8,7 @@
<!-- >-->
<!-- 灿能-->
<!-- </div>-->
<!-- <Icon
<Icon
v-if="config.layout.layoutMode != 'Streamline'"
@click="onMenuCollapse"
:name="config.layout.menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
@@ -17,18 +17,18 @@
style="margin: 15px;"
size="18"
class="fold"
/> -->
/>
</div>
</template>
<script setup lang="ts">
import { useConfig } from '@/stores/config'
import { closeShade } from '@/utils/pageShade'
import { Session } from '@/utils/storage'
import { getStoredTheme } from '@/utils/storage'
import { setNavTabsWidth } from '@/utils/layout'
const config = useConfig()
const getTheme = JSON.parse(window.localStorage.getItem('getTheme') as string)
const getTheme = getStoredTheme()
const onMenuCollapse = function () {
if (config.layout.shrink && !config.layout.menuCollapse) {
closeShade()

View File

@@ -4,7 +4,7 @@
<!-- <Icon @click="onMenuCollapse" name="fa fa-indent" :color="config.getColorVal('menuActiveColor')"
size="18" /> -->
</div>
<span class="nav-bar-title">{{ getTheme.name }} <span style="font-size: 14px;" v-if="Version?.versionName">
<span class="nav-bar-title">{{ themeName }} <span style="font-size: 14px;" v-if="Version?.versionName">
({{ Version?.versionName }})
</span></span>
<NavMenus />
@@ -19,8 +19,9 @@ import NavMenus from '../navMenus.vue'
import { showShade } from '@/utils/pageShade'
import { onMounted } from 'vue'
import { getLastData } from '@/api/systerm'
import { getStoredThemeName } from '@/utils/storage'
const config = useConfig()
const getTheme = JSON.parse(window.localStorage.getItem('getTheme') as string)
const themeName = getStoredThemeName()
const Version: any = ref({})
const onMenuCollapse = () => {
showShade('ba-aside-menu-shade', () => {
@@ -33,7 +34,7 @@ onMounted(() => {
Version.value = res.data
})
document.title = getTheme.name
document.title = themeName
})
</script>

View File

@@ -0,0 +1,59 @@
const AXIS_DECIMALS = 2
export function roundAxisValue(val: number, decimals = AXIS_DECIMALS): number {
if (!Number.isFinite(val)) return 0
const factor = 10 ** decimals
return Math.round(val * factor) / factor
}
/** Y 轴刻度:最多 2 位小数,末尾 0 去掉(如 1.00 → 10.10 → 0.1 */
export function formatAxisLabel(value: number): string {
if (!Number.isFinite(value)) return '0'
return String(Number(roundAxisValue(value).toFixed(AXIS_DECIMALS)))
}
/** 瞬间波形 Y 轴范围 */
export function calcShuYAxisRange(dataMin: number, dataMax: number): { min: number; max: number } {
const min = Number(dataMin)
const max = Number(dataMax)
if (!Number.isFinite(min) || !Number.isFinite(max)) {
return { min: 0, max: 1 }
}
let axisMax = max * 1.1
let axisMin = min > 0 ? min - min * 0.1 : min * 1.1
if (axisMax <= axisMin) {
const pad = Math.abs(max) * 0.1 || 0.01
axisMax = max + pad
axisMin = min - pad
}
return {
min: roundAxisValue(axisMin),
max: roundAxisValue(axisMax)
}
}
/** RMS 波形 Y 轴范围 */
export function calcRmsYAxisRange(dataMin: number, dataMax: number): { min: number; max: number } {
const min = Number(dataMin)
const max = Number(dataMax)
if (!Number.isFinite(min) || !Number.isFinite(max)) {
return { min: 0, max: 1 }
}
let axisMax = max * 1.06 * 1.1
let axisMin = min - min * 0.2
if (axisMax <= axisMin) {
const pad = Math.abs(max - min) * 0.1 || Math.abs(max) * 0.1 || 0.01
axisMax = max + pad
axisMin = min - pad
}
return {
min: roundAxisValue(axisMin),
max: roundAxisValue(axisMax)
}
}

View File

@@ -0,0 +1,83 @@
import { ElMessage } from 'element-plus'
import { exportExcel } from '@/views/system/reportForms/export.js'
/** 解析 Luckysheet 接口返回的 sheet 数据 */
export function parseLuckysheetSheets(sheets: any[]) {
sheets.forEach((item: any) => {
if (item.data1) {
try {
item.data = JSON.parse(item.data1)
} catch {
/* ignore invalid json */
}
}
item.celldata?.forEach((cell: any) => {
if (item.data?.[cell.r]?.[cell.c]?.v != null) {
item.data[cell.r][cell.c] = cell.v
}
})
})
}
declare const luckysheet: any
const DEFAULT_REPORT_OPTIONS = {
title: '',
lang: 'zh',
showtoolbar: false,
showinfobar: false,
showsheetbar: true,
}
/** 销毁已有 Luckysheet 实例,避免重复 create 导致 DOM 堆积 */
export function destroyLuckysheet() {
try {
if (typeof luckysheet !== 'undefined' && luckysheet.destroy) {
luckysheet.destroy()
}
} catch {
/* ignore */
}
}
/** 解析 sheet 数据、销毁旧实例并渲染报表 */
export function renderLuckysheetReport(
container: string,
sheets: any[],
options: Record<string, any> = {}
) {
parseLuckysheetSheets(sheets)
destroyLuckysheet()
setTimeout(() => {
luckysheet.create({
container,
...DEFAULT_REPORT_OPTIONS,
...options,
data: sheets,
})
}, 10)
}
/** 安全导出 Luckysheet无数据时提示并返回 false */
export function exportLuckysheetFile(filename: string, hasData = true): boolean {
if (!hasData) {
ElMessage.warning('暂无数据')
return false
}
try {
if (typeof luckysheet === 'undefined' || !luckysheet.getAllSheets) {
ElMessage.warning('暂无数据')
return false
}
const sheets = luckysheet.getAllSheets()
if (!sheets?.length) {
ElMessage.warning('暂无数据')
return false
}
exportExcel(sheets, filename)
return true
} catch {
ElMessage.warning('导出失败,请先加载报表数据')
return false
}
}

View File

@@ -28,6 +28,21 @@ export const Local = {
* @method remove 移除会话缓存
* @method clear 移除全部会话缓存
*/
const DEFAULT_THEME_NAME = '电能质量监测系统'
export function getStoredTheme(): { name?: string; logoUrl?: string; [key: string]: any } {
try {
const raw = window.localStorage.getItem('getTheme')
return raw ? JSON.parse(raw) : {}
} catch {
return {}
}
}
export function getStoredThemeName(): string {
return getStoredTheme().name || DEFAULT_THEME_NAME
}
export const Session = {
set(key: string, val: any) {
window.sessionStorage.setItem(key, JSON.stringify(val))

View File

@@ -19,7 +19,7 @@ interface TableStoreParams {
resetCallback?: () => void // 重置
loadCallback?: () => void // 接口调用后的回调
exportProcessingData?: () => void //导出处理数据
beforeSearchFun?: () => void // 接口调用前的回调
beforeSearchFun?: () => void | boolean // 接口调用前的回调,返回 false 中止请求
}
export default class TableStore {
@@ -75,7 +75,13 @@ export default class TableStore {
}
index() {
this.table.beforeSearchFun && this.table.beforeSearchFun()
if (this.table.beforeSearchFun) {
const canSearch = this.table.beforeSearchFun()
if (canSearch === false) {
this.table.loading = false
return
}
}
this.table.data = []
this.table.loading = true
// 重置用的数据数据

42
src/utils/waveCache.ts Normal file
View File

@@ -0,0 +1,42 @@
const MAX_CACHE_SIZE = 30
const cache = new Map<string, unknown>()
function dataFingerprint(data: unknown[][] | undefined): string {
if (!data?.length) return '0'
const first = data[0]
const last = data[data.length - 1]
return `${data.length}:${first?.[0]}:${last?.[0]}`
}
export function buildWaveCacheKey(
type: 'shu' | 'rms',
wp: Record<string, any> | undefined,
value: number,
isOpen: boolean,
boxoList: Record<string, any>
): string {
if (!wp) return ''
const waveFp =
type === 'shu' ? dataFingerprint(wp.listWaveData) : dataFingerprint(wp.listRmsData)
const boxoFp = boxoList?.startTime ?? boxoList?.lineName ?? boxoList?.equipmentName ?? ''
return `${type}|${wp.time}|${wp.waveType}|${wp.iphasic}|${value}|${isOpen}|${waveFp}|${boxoFp}`
}
export function getWaveCache<T>(key: string): T | null {
if (!key || !cache.has(key)) return null
const value = cache.get(key) as T
cache.delete(key)
cache.set(key, value)
return value
}
export function setWaveCache(key: string, value: unknown): void {
if (!key) return
if (cache.has(key)) cache.delete(key)
cache.set(key, value)
while (cache.size > MAX_CACHE_SIZE) {
const oldest = cache.keys().next().value
if (oldest !== undefined) cache.delete(oldest)
}
}

View File

@@ -0,0 +1,96 @@
import { toRaw } from 'vue'
type WorkerMessageHandler = (data: any) => void
let shuWorker: Worker | null = null
let rmsWorker: Worker | null = null
/** 递归剥离 Vue 响应式代理,得到可 structuredClone 的纯对象 */
export function toPlainDeep<T>(value: T): T {
const raw = toRaw(value as object) as T
if (Array.isArray(raw)) {
return raw.map(item => toPlainDeep(item)) as T
}
if (raw !== null && typeof raw === 'object') {
const out: Record<string, unknown> = {}
for (const [key, val] of Object.entries(raw)) {
out[key] = toPlainDeep(val)
}
return out as T
}
return raw
}
const BOXO_LIST_KEYS = [
'systemType',
'powerStationName',
'measurementPointName',
'startTime',
'featureAmplitude',
'duration',
'engineeringName',
'equipmentName',
'evtParamVVaDepth',
'evtParamTm',
'lineName',
'persistTime',
'subName'
] as const
export function buildWorkerPayload(
type: 'shu' | 'rms',
wp: Record<string, any>,
boxoList: Record<string, any>,
extras: { requestId: number; value: number; isOpen: boolean; iphasic?: number }
) {
const plainWp = toPlainDeep(wp)
const wpPayload: Record<string, unknown> = {
pt: plainWp.pt,
ct: plainWp.ct,
waveTitle: plainWp.waveTitle,
iphasic: plainWp.iphasic,
time: plainWp.time,
waveType: plainWp.waveType,
yzd: plainWp.yzd
}
if (type === 'shu') {
wpPayload.listWaveData = plainWp.listWaveData
} else {
wpPayload.listRmsData = plainWp.listRmsData
}
const plainBoxo: Record<string, unknown> = {}
const rawBoxo = toPlainDeep(boxoList)
for (const key of BOXO_LIST_KEYS) {
if (rawBoxo[key] !== undefined) {
plainBoxo[key] = rawBoxo[key]
}
}
return {
requestId: extras.requestId,
value: extras.value,
isOpen: extras.isOpen,
iphasic: extras.iphasic,
wp: wpPayload,
boxoList: plainBoxo
}
}
export function getShuWorker(onMessage: WorkerMessageHandler): Worker {
if (!shuWorker) {
shuWorker = new Worker(new URL('../components/echarts/shuWorker.js', import.meta.url))
}
shuWorker.onmessage = e => onMessage(e.data)
shuWorker.onerror = error => console.error('Shu worker error:', error)
return shuWorker
}
export function getRmsWorker(onMessage: WorkerMessageHandler): Worker {
if (!rmsWorker) {
rmsWorker = new Worker(new URL('../components/echarts/rmsWorker.js', import.meta.url))
}
rmsWorker.onmessage = e => onMessage(e.data)
rmsWorker.onerror = error => console.error('Rms worker error:', error)
return rmsWorker
}

View File

@@ -92,8 +92,8 @@ const tableStore = new TableStore({
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{ title: '设备名称', field: 'ndid', align: 'center' },
{ title: '异常时间', field: 'evtTime', align: 'center', sortable: true },
{ title: '设备名称', field: 'ndid', align: 'center' },
{
title: '告警代码',
field: 'code',

View File

@@ -99,16 +99,17 @@ const tableStore = new TableStore({
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{ title: '设备名称', field: 'equipmentName', align: 'center', width: 120 },
{ title: '监测点名称', field: 'lineName', align: 'center', width: 140 },
{ title: '工程名称', field: 'engineeringName', align: 'center', width: 120 },
{ title: '项目名称', field: 'projectName', align: 'center', width: 120 },
{ title: '发生时刻', field: 'startTime', align: 'center', width: 180, sortable: true },
{ title: '发生时刻', field: 'startTime', align: 'center', minWidth: 180, sortable: true },
{ title: '工程名称', field: 'engineeringName', align: 'center', minWidth: 120 },
{ title: '项目名称', field: 'projectName', align: 'center', minWidth: 120 },
{ title: '设备名称', field: 'equipmentName', align: 'center', minWidth: 120 },
{ title: '监测点名称', field: 'lineName', align: 'center', minWidth: 120 },
{
title: '模块信息',
field: 'moduleNo',
align: 'center',
width: 100,
minWidth: 100,
formatter: (row: any) => {
return row.cellValue ? row.cellValue : '/'
}
@@ -117,7 +118,7 @@ const tableStore = new TableStore({
title: '告警代码',
field: 'code',
align: 'center',
width: 100,
minWidth: 100,
formatter: (row: any) => {
return row.cellValue ? '\u200B' + row.cellValue : '/'
},
@@ -125,13 +126,13 @@ const tableStore = new TableStore({
},
{
title: '事件描述',
minWidth: 250,
minWidth: 300,
field: 'showName'
},
{
title: '级别',
field: 'level',
width: 100,
width: 110,
render: 'tag',
custom: {
// 1:Ⅰ级 2:Ⅱ级 3:Ⅲ级 4:DEBUG 5:NORMAL 6:WARN 7:ERROR
@@ -202,7 +203,7 @@ tableStore.table.params.deviceTypeId = ''
tableStore.table.params.deviceTypeName = ''
const deviceTreeOptions = ref<any>(props.deviceTree)
deviceTreeOptions.value.map((item: any, index: any) => {
if (item.children.length == 0) {
if (item?.children.length == 0) {
deviceTreeOptions.value.splice(index, 1)
}
})

View File

@@ -78,10 +78,10 @@ const tableStore = new TableStore({
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{ title: '前置服务器名称', field: 'lineId', align: 'center', width: 120 },
{ title: '前置服务器ip', field: 'wavePath', align: 'center', width: 120 },
{ title: '进程号', field: 'clDid', align: 'center', width: 60 },
{ title: '发生时刻', field: 'startTime', align: 'center', width: 180, sortable: true },
{ title: '前置服务器名称', field: 'lineId', align: 'center', width: 150 },
{ title: '前置服务器ip', field: 'wavePath', align: 'center', width: 150 },
{ title: '进程号', field: 'clDid', align: 'center', width: 70 },
{
title: '事件描述',
@@ -102,7 +102,7 @@ const tableStore = new TableStore({
{
title: '级别',
field: 'level',
width: 100,
width: 110,
render: 'tag',
custom: {
// 1:Ⅰ级 2:Ⅱ级 3:Ⅲ级 4:DEBUG 5:NORMAL 6:WARN 7:ERROR
@@ -153,7 +153,7 @@ tableStore.table.params.searchValue = ''
tableStore.table.params.level = ''
const deviceTreeOptions = ref<any>(props.deviceTree)
deviceTreeOptions.value.map((item: any, index: any) => {
if (item.children.length == 0) {
if (item?.children.length == 0) {
deviceTreeOptions.value.splice(index, 1)
}
})

View File

@@ -2,16 +2,9 @@
<TableHeader datePicker ref="refheader" showExport>
<template v-slot:select>
<el-form-item label="数据来源">
<el-cascader
v-model.trim="tableStore.table.params.cascader"
filterable
placeholder="请选择数据来源"
@change="sourceChange"
:options="deviceTreeOptions"
:show-all-levels="false"
:props="{ checkStrictly: true, value: 'id', label: 'name' }"
clearable
></el-cascader>
<el-cascader v-model.trim="tableStore.table.params.cascader" filterable placeholder="请选择数据来源"
@change="sourceChange" :options="deviceTreeOptions" :show-all-levels="false"
:props="{ checkStrictly: true, value: 'id', label: 'name' }" clearable></el-cascader>
<!-- <el-input maxlength="32" show-word-limit v-model.trim="tableStore.table.params.searchValue" placeholder="请输入设备名称" /> -->
</el-form-item>
<!-- <el-form-item label="级别">
@@ -83,12 +76,13 @@ const tableStore = new TableStore({
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{ title: '设备名称', field: 'equipmentName', align: 'center' },
{ title: '工程名称', field: 'engineeringName', align: 'center' },
{ title: '项目名称', field: 'projectName', align: 'center' },
{ title: '发生时刻', field: 'startTime', align: 'center', sortable: true },
{ title: '发生时刻', field: 'startTime', align: 'center', sortable: true, minWidth: 180, },
{ title: '工程名称', field: 'engineeringName', align: 'center', minWidth: 120, },
{ title: '项目名称', field: 'projectName', align: 'center', minWidth: 120, },
{ title: '设备名称', field: 'equipmentName', align: 'center', minWidth: 120, },
{ title: '事件描述', field: 'showName', align: 'center' }
{ title: '事件描述', field: 'showName', align: 'center', minWidth: 250, }
],
beforeSearchFun: () => { }
})
@@ -112,7 +106,7 @@ tableStore.table.params.deviceTypeName = ''
const deviceTreeOptions = ref<any>(props.deviceTree)
deviceTreeOptions.value.map((item: any, index: any) => {
if (item.children.length == 0) {
if (item?.children.length == 0) {
deviceTreeOptions.value.splice(index, 1)
}
})

View File

@@ -3,16 +3,10 @@
<TableHeader datePicker showExport>
<template v-slot:select>
<el-form-item label="数据来源">
<el-cascader
placeholder="请选择数据来源"
@change="sourceChange"
filterable
v-model.trim="tableStore.table.params.cascader"
:options="deviceTreeOptions"
:show-all-levels="false"
:props="{ checkStrictly: true, value: 'id', label: 'name' }"
clearable
></el-cascader>
<el-cascader placeholder="请选择数据来源" @change="sourceChange" filterable
v-model.trim="tableStore.table.params.cascader" :options="deviceTreeOptions"
:show-all-levels="false" :props="{ checkStrictly: true, value: 'id', label: 'name' }"
clearable></el-cascader>
<!-- <el-input maxlength="32" show-word-limit v-model.trim="tableStore.table.params.searchValue" placeholder="请输入设备名称" /> -->
</el-form-item>
<!-- <el-form-item label="级别">
@@ -26,13 +20,8 @@
<Table></Table>
</div>
<waveFormAnalysis
v-loading="loading"
v-if="isWaveCharts"
ref="waveFormAnalysisRef"
@handleHideCharts="isWaveCharts = false"
:wp="wp"
/>
<waveFormAnalysis v-loading="loading" v-if="isWaveCharts" ref="waveFormAnalysisRef"
@handleHideCharts="isWaveCharts = false" :wp="wp" />
<!-- <div style="height: 300px;"> -->
<!-- <div style="padding: 10px" v-if="!view" v-loading="loading">
@@ -129,16 +118,20 @@ const tableStore = new TableStore({
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{ title: '设备名称', field: 'equipmentName', minWidth: 120, align: 'center' },
{ title: '暂降发生时刻', field: 'startTime', align: 'center', minWidth: 180, sortable: true },
{ title: '暂降(骤升)幅值(%)', minWidth: 160, field: 'evtParamVVaDepth', align: 'center', sortable: true },
{ title: '持续时间(s)', field: 'evtParamTm', minWidth: 110, align: 'center', sortable: true },
{ title: '相别', field: 'evtParamPhase', minWidth: 80, align: 'center' },
{ title: '触发类型', field: 'showName', minWidth: 120, align: 'center' },
{ title: '工程名称', field: 'engineeringName', minWidth: 120, align: 'center' },
{ title: '项目名称', field: 'projectName', minWidth: 120, align: 'center' },
{ title: '发生时刻', field: 'startTime', align: 'center', minWidth: 180, sortable: true },
{ title: '设备名称', field: 'equipmentName', minWidth: 120, align: 'center' },
{ title: '监测点名称', field: 'lineName', minWidth: 120, align: 'center' },
{ title: '事件描述', field: 'showName', minWidth: 120, align: 'center' },
{ title: '事件发生位置', field: 'evtParamPosition', minWidth: 150, align: 'center' },
{ title: '相别', field: 'evtParamPhase', minWidth: 80, align: 'center' },
{ title: '持续时间(s)', field: 'evtParamTm', minWidth: 80, align: 'center', sortable: true },
{ title: '暂降(聚升)幅值(%)', minWidth: 100, field: 'evtParamVVaDepth', align: 'center', sortable: true },
{ title: '发生位置', field: 'evtParamPosition', minWidth: 150, align: 'center' },
{
title: '操作',
@@ -219,6 +212,7 @@ const tableStore = new TableStore({
return !row.wavePath
},
click: row => {
ElMessage.info('下载中......')
getFileZip({ eventId: row.id }).then(res => {
let blob = new Blob([res], { type: 'application/zip' }) // console.log(blob) // var href = window.URL.createObjectURL(blob); //创建下载的链接
const url = window.URL.createObjectURL(blob)
@@ -228,6 +222,7 @@ const tableStore = new TableStore({
document.body.appendChild(link)
link.click() //执行下载
document.body.removeChild(link) //释放标签
ElMessage.success('波形下载成功')
})
}
},
@@ -238,7 +233,7 @@ const tableStore = new TableStore({
icon: 'el-icon-DataLine',
render: 'basicButton',
disabled: row => {
return row.showName != '未知'
return row.wavePath
}
},
{
@@ -295,7 +290,7 @@ tableStore.table.params.deviceTypeName = ''
const deviceTreeOptions: any = ref<any>(props.deviceTree)
deviceTreeOptions.value.map((item: any, index: any) => {
if (item.children.length == 0) {
if (item?.children.length == 0) {
deviceTreeOptions.value.splice(index, 1)
}
})

View File

@@ -9,44 +9,23 @@
<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 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 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-option v-for="item in typelist" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
</template>
@@ -149,7 +128,7 @@ const deviceTypeChange = (val: any, obj: any) => {
nodeClick(obj)
}
const nodeClick = async (e: anyObj) => {
if (e.level == 2 && flag.value) {
if ((e.level == 2 || e.level == 3) && flag.value) {
formInline.devId = e.id
loading.value = true
if (zblist.value.length === 0) {
@@ -315,8 +294,7 @@ const setEchart = () => {
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]) : '-'
str += `${marker}${el.seriesName.split('(')[0]}${el.value[1] != null ? el.value[1] + ' ' + (el.value[2] == 'null' ? '' : el.value[2]) : '-'
}<br>`
})
return str

View File

@@ -122,15 +122,16 @@ const tableStore = new TableStore({
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{ title: '事件描述', field: 'showName', minWidth: 150 },
{ title: '暂降发生时刻', field: 'startTime', sortable: true, minWidth: 180 },
{
title: '发生位置', field: 'evtParamPosition', minWidth: 150,
title: '暂降(骤升)幅值(%)',
field: 'evtParamVVaDepth',
minWidth: 160,
formatter: (row: any) => {
const val = row.cellValue
if (val === null || val === undefined || val === '' || val === '-') return '/'
return val
}
let a = row.cellValue.split('%')[0] - 0
return a ? a.toFixed(2) : '/'
},
sortable: true
},
{
title: '持续时间(s)',
@@ -145,17 +146,19 @@ const tableStore = new TableStore({
return Math.floor(num * 10000) / 100
}
},
{ title: '触发类型', field: 'showName', minWidth: 150 },
{
title: '暂降(聚升)幅值(%)',
field: 'evtParamVVaDepth',
minWidth: 150,
title: '发生位置', field: 'evtParamPosition', minWidth: 150,
formatter: (row: any) => {
let a = row.cellValue.split('%')[0] - 0
return a ? a.toFixed(2) : '/'
const val = row.cellValue
if (val === null || val === undefined || val === '' || val === '-') return '/'
return val
}
},
sortable: true
},
{ title: '发生时刻', field: 'startTime', sortable: true, minWidth: 180 },
{
title: '操作',
fixed: 'right',
@@ -229,6 +232,7 @@ const tableStore = new TableStore({
return !row.wavePath
},
click: row => {
ElMessage.info('下载中......')
getFileZip({ eventId: row.id }).then(res => {
let blob = new Blob([res], { type: 'application/zip' }) // console.log(blob) // var href = window.URL.createObjectURL(blob); //创建下载的链接
const url = window.URL.createObjectURL(blob)
@@ -238,6 +242,7 @@ const tableStore = new TableStore({
document.body.appendChild(link)
link.click() //执行下载
document.body.removeChild(link) //释放标签
ElMessage.success('波形下载成功')
})
}
}
@@ -262,7 +267,7 @@ const deviceTypeChange = (val: any, obj: any) => {
}
const nodeClick = async (e: anyObj) => {
// console.log("🚀 ~ nodeClick ~ e:", e)
if (e.level == 2 && flag.value) {
if ((e.level == 2 || e.level == 3) && flag.value) {
loading.value = false
tableStore.table.params.deviceId = e.id
nextTick(() => {

View File

@@ -39,7 +39,7 @@
</TableHeader>
</div>
<el-empty description="暂无数据" v-if="!echartsData" style="flex: 1"></el-empty>
<el-empty description="暂无数据" v-if="!echartsData" style="flex: 1;margin-top: 10%"></el-empty>
<template v-else>
<div :style="echartHeight">
<MyEchart :options="echartsData" />

View File

@@ -42,13 +42,14 @@
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, provide } from 'vue'
import { onMounted, onUnmounted, ref, provide } from 'vue'
import TableStore from '@/utils/tableStore'
import pointTreeWx from '@/components/tree/govern/pointTreeWx.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useDictData } from '@/stores/dictData'
import { mainHeight } from '@/utils/layout'
import { exportExcel } from '@/views/system/reportForms/export.js'
import { destroyLuckysheet, exportLuckysheetFile, renderLuckysheetReport } from '@/utils/luckysheetHelper'
import { isLineTreeLeaf } from '@/components/tree/govern/lineTreeUtils'
import DatePicker from '@/components/form/datePicker/time.vue'
import CloudDeviceEntryTree from '@/components/tree/govern/cloudDeviceEntryTreeZL.vue'
import { getListByIds } from '@/api/harmonic-boot/cockpit/cockpit'
@@ -77,35 +78,20 @@ const tableStore = new TableStore({
// ;(tableStore.table.params.startTime = datePickerRef.value.timeValue[0]),
// (tableStore.table.params.endTime = datePickerRef.value.timeValue[1]),
if (!tableStore.table.params.tempId) {
return ElMessage.warning('请选择模板')
ElMessage.warning('请选择模板')
return false
}
if (!dotList.value?.id) {
ElMessage.warning('请选择监测点')
return false
}
delete tableStore.table.params.searchBeginTime
delete tableStore.table.params.searchEndTime
delete tableStore.table.params.timeFlag
},
loadCallback: () => {
console.log('🚀 ~ tableStore.table:', tableStore.table.data)
name.value = dotList.value.name
// tableStore.table.data.forEach((item: any) => {
// item.data1 ? (item.data = JSON.parse(item.data1)) : ''
// item.celldata.forEach((k: any) => {
// item.data[k.r][k.c].v ? (item.data[k.r][k.c] = k.v) : ''
// })
// })
setTimeout(() => {
luckysheet.create({
container: 'luckysheet',
title: '', // 表 头名
lang: 'zh', // 中文
showtoolbar: false, // 是否显示工具栏
showinfobar: false, // 是否显示顶部信息栏
showsheetbar: true, // 是否显示底部sheet按钮
allowEdit: false, // 禁止所有编辑操作(必填)
data: tableStore.table.data
// tableStore.table.data
})
}, 10)
renderLuckysheetReport('luckysheet', tableStore.table.data, { allowEdit: false })
}
})
provide('tableStore', tableStore)
@@ -115,6 +101,9 @@ const flag = ref(true)
onMounted(() => {
initListByIds()
})
onUnmounted(() => {
destroyLuckysheet()
})
const idList = ref([])
// 监测对象
@@ -131,7 +120,6 @@ const initListByIds = () => {
})
}
const stencil = (val: any) => {
console.log('🚀 ~ stencil ~ val:', val)
templatePolicy.value = val.filter((item: any) => item.excelType == '4')
Template.value = templatePolicy.value[0]
reportForm.value = templatePolicy.value[0]?.excelType
@@ -142,7 +130,7 @@ const changetype = (val: any) => {
}
const handleNodeClick = (data: any, node: any) => {
if (data?.level == 3) {
if (isLineTreeLeaf(data) || data?.level == 3) {
dotList.value = data
setTimeout(() => {
tableStore.index()
@@ -160,7 +148,7 @@ const exportEvent = () => {
// 格式化YYYY - MM - DD补零
const formattedDate = `${year}${String(month).padStart(2, '0')}${String(day).padStart(2, '0')}`
exportExcel(luckysheet.getAllSheets(), name.value + formattedDate)
exportLuckysheetFile(name.value + formattedDate, tableStore.table.data.length > 0)
}
</script>
<style lang="scss">

View File

@@ -484,7 +484,7 @@
<div style="height: calc(100vh - 340px)" v-if="dataSet.indexOf('_trenddata') != -1">
<Trend ref="trendRef" :TrendList="TrendList"></Trend>
</div>
<!-- 电数据 -->
<!-- 电数据 -->
<div style="height: calc(100vh - 340px)" v-if="dataSet.indexOf('_kilowattHour') != -1">
<electroplating ref="electroplatingRef" :TrendList="TrendList"></electroplating>
</div>
@@ -585,7 +585,7 @@ import { ElMessage } from 'element-plus'
import DatePicker from '@/components/form/datePicker/index.vue'
import Trend from './tabs/trend.vue' //趋势数据
import realTime from './tabs/realtime.vue' //实时数据-主界面
import electroplating from './tabs/electroplating.vue' //电数据-主界面
import electroplating from './tabs/electroplating.vue' //电数据-主界面
import realTrend from './tabs/components/realtrend.vue' //实时数据-实时趋势
import operatingTrend from './tabs/operatingTrend.vue' //运行趋势
import harmonicSpectrum from './tabs/components/harmonicSpectrum.vue' //实时数据-谐波频谱子页面
@@ -705,6 +705,25 @@ const activeTrendName: any = ref(0)
const trendTimer: any = ref()
const trendDataTime: any = ref()
const showButton = ref(false)
const decodeMqttPayload = (message: any) => {
try {
return JSON.parse(JSON.stringify(JSON.parse(new TextDecoder().decode(message))))
} catch {
return {}
}
}
/** 谐波频谱 MQTT 消息(命名函数,便于 off 避免重复注册) */
const onMqttTrendMessage = (topic: any, message: any) => {
const obj = decodeMqttPayload(message) || {}
if ((obj.hasOwnProperty('data1') || obj.hasOwnProperty('data2')) && obj.dataTime) {
trendDataTime.value = obj.dataTime
realTrendRef.value?.setRealTrendData(obj)
tableLoading.value = false
}
}
//谐波频谱方法
const handleTrend = async () => {
realTimeFlag.value = false
@@ -728,21 +747,7 @@ const handleTrend = async () => {
// console.log(res, '获取谐波频谱数据')
})
}, 30000)
mqttRef.value.on('message', (topic: any, message: any) => {
let obj = JSON.parse(JSON.stringify(JSON.parse(new TextDecoder().decode(message)))) || {}
if ((obj.hasOwnProperty('data1') || obj.hasOwnProperty('data2')) && obj.dataTime) {
trendDataTime.value = obj.dataTime
realTrendRef.value && realTrendRef.value.setRealTrendData(obj)
tableLoading.value = false
// console.log(
// '谐波频谱---mqtt接收到消息',
// JSON.parse(JSON.stringify(JSON.parse(new TextDecoder().decode(message))))
// )
}
// else {
// trendDataTime.value = obj.dataTime
// }
})
bindMqttMessage(onMqttTrendMessage)
} else {
ElMessage.warning('设备应答失败')
}
@@ -885,8 +890,8 @@ const lineId: any = ref('')
const dataLevel: any = ref('')
const dataSource = ref([])
const engineeringName = ref('')
const nodeClick = async (e: anyObj, node: any) => {
if (e == undefined || e.level == 2) {
const nodeClick = async (e: anyObj, node?: any) => {
if (e == undefined) {
return (loading.value = false)
}
searchValue.value = ''
@@ -900,7 +905,7 @@ const nodeClick = async (e: anyObj, node: any) => {
}
//选中设备名称后,点击标签页也能查询数据,要求点击设备名称后,点击标签页默认查询第一个监测点数据
if (e.level == 3 || e.level == 2) {
if (e.level == 3 ) {
engineeringName.value = node?.parent.parent.data.name
await queryDictType({
@@ -940,7 +945,7 @@ const nodeClick = async (e: anyObj, node: any) => {
if (item.type === 'trenddata') {
item.id = item.id + '_trenddata'
}
//电数据
//电数据
if (item.type === 'kilowattHour') {
item.id = item.id + '_kilowattHour'
}
@@ -995,6 +1000,14 @@ const trendRef: any = ref()
const eventRef: any = ref()
const mqttRef = ref()
const url: any = window.localStorage.getItem('MQTTURL')
/** 同一 handler 先 off 再 on避免重复 message 监听 */
const bindMqttMessage = (handler: (topic: any, message: any) => void) => {
if (!mqttRef.value) return
mqttRef.value.off('message', handler)
mqttRef.value.on('message', handler)
}
const connectMqtt = () => {
if (mqttRef.value) {
if (mqttRef.value.connected) {
@@ -1035,12 +1048,40 @@ const getRealDataMqttMsg = async () => {
// console.log(res, '获取基础实时数据')
})
}, 30000)
mqttRef.value.on('message', (topic: any, message: any) => {
// console.log(
// '实时数据&实时趋势---mqtt接收到消息',
// JSON.parse(JSON.stringify(JSON.parse(new TextDecoder().decode(message))))
// )
let obj = JSON.parse(JSON.stringify(JSON.parse(new TextDecoder().decode(message))))
bindMqttMessage(onMqttRealDataMessage)
//2.建立mqtt通讯
//每隔30s调用一下接口通知后台推送mqtt消息
mqttRef.value.on('error', (error: any) => {
console.log('mqtt连接失败...', error)
mqttRef.value.end()
})
mqttRef.value.on('close', function () {
console.log('mqtt客户端已断开连接.....')
})
setTimeout(() => {
tableLoading.value = false
}, 6000)
} else {
ElMessage.success('设备应答失败')
tableLoading.value = false
}
})
.catch(e => {
setTimeout(() => {
tableLoading.value = false
}, 0)
})
}
//tab点击事件
const realDataTimer: any = ref()
const mqttMessage = ref<any>({})
/** 实时数据 / 实时趋势 MQTT 消息(命名函数,便于 off 避免重复注册) */
const onMqttRealDataMessage = (topic: any, message: any) => {
let obj = decodeMqttPayload(message)
if (lineId.value != obj.lineId || adminInfo.userIndex != obj.userId) return
@@ -1178,36 +1219,8 @@ const getRealDataMqttMsg = async () => {
// sonTab.value == 1 &&
// realTrendRef.value &&
// realTrendRef.value.setRealTrendData(obj)
})
//2.建立mqtt通讯
//每隔30s调用一下接口通知后台推送mqtt消息
mqttRef.value.on('error', (error: any) => {
console.log('mqtt连接失败...', error)
mqttRef.value.end()
})
mqttRef.value.on('close', function () {
console.log('mqtt客户端已断开连接.....')
})
setTimeout(() => {
tableLoading.value = false
}, 6000)
} else {
ElMessage.success('设备应答失败')
tableLoading.value = false
}
})
.catch(e => {
setTimeout(() => {
tableLoading.value = false
}, 0)
})
}
//tab点击事件
const realDataTimer: any = ref()
const mqttMessage = ref<any>({})
const handleClick = async (tab?: any) => {
tableLoading.value = true
showButton.value = false
@@ -1272,7 +1285,7 @@ const handleClick = async (tab?: any) => {
tableLoading.value = false
}, 0)
}
//电数据
//电数据
if (dataSet.value.includes('_kilowattHour')) {
let obj = {
devId: deviceId.value, //e.id
@@ -1475,6 +1488,8 @@ const handleClick = async (tab?: any) => {
window.clearInterval(trendTimer.value)
}
if (mqttRef.value) {
mqttRef.value.off('message', onMqttTrendMessage)
mqttRef.value.off('message', onMqttRealDataMessage)
mqttRef.value.end()
}
}
@@ -1557,6 +1572,8 @@ onBeforeUnmount(() => {
realDataTimer.value = 0
trendTimer.value = 0
if (mqttRef.value) {
mqttRef.value.off('message', onMqttTrendMessage)
mqttRef.value.off('message', onMqttRealDataMessage)
mqttRef.value.end()
}
})

View File

@@ -293,31 +293,31 @@ const setRealTrendData = (val: any) => {
if (selectValue.value == '2') {
if (activeName.value == 2) {
if (numberPart % 2 !== 0 && numberPart < 17) {
tableData.value[key] = val[key]
tableData.value[key] = val[key].toFixed(2)
}
} else {
if (numberPart % 2 === 0) {
tableData.value[key] = val[key]
tableData.value[key] = val[key].toFixed(2)
}
}
} else {
if (activeName.value == 2) {
if (numberPart % 2 === 0 && numberPart < 17) {
tableData.value[key] = val[key]
tableData.value[key] = val[key].toFixed(2)
}
} else {
if (numberPart % 2 !== 0) {
tableData.value[key] = val[key]
tableData.value[key] = val[key].toFixed(2)
}
}
}
} else {
if (activeName.value == 2) {
if (numberPart < 17) {
tableData.value[key] = val[key]
tableData.value[key] = val[key].toFixed(2)
}
} else {
tableData.value[key] = val[key]
tableData.value[key] = val[key].toFixed(2)
}
}
}

View File

@@ -20,25 +20,15 @@
<el-button @click="handleBack" :icon="Back">返回</el-button>
</div>
<!-- v-loading="loading" -->
<el-tabs class="home_body" type="border-card" v-model.trim="activeName1" @tab-click="handleClick">
<!-- -->
<el-tabs class="home_body" v-loading="loading" type="border-card" v-model.trim="activeName1" @tab-click="handleClick">
<el-tab-pane label="瞬时波形" name="ssbx" :style="'height:' + bxecharts + ';overflow-y: auto;'">
<shushiboxi
v-if="isWp && wp && activeName == 'ssbx' && showBoxi"
:value="value"
:boxoList="boxoList"
:parentHeight="parentHeight"
:wp="wp"
></shushiboxi>
<shushiboxi v-if="isWp && wp && activeName == 'ssbx' && showBoxi" :value="value" :boxoList="boxoList"
:parentHeight="parentHeight" :wp="wp"></shushiboxi>
</el-tab-pane>
<el-tab-pane label="RMS波形" name="rmsbx" :style="'height:' + bxecharts + ';overflow-y: auto;'">
<rmsboxi
v-if="isWp && wp && activeName == 'rmsbx' && showBoxi"
:value="value"
:boxoList="boxoList"
:parentHeight="parentHeight"
:wp="wp"
></rmsboxi>
<rmsboxi v-if="isWp && wp && activeName == 'rmsbx' && showBoxi" :value="value" :boxoList="boxoList"
:parentHeight="parentHeight" :wp="wp"></rmsboxi>
</el-tab-pane>
</el-tabs>
</div>
@@ -117,11 +107,10 @@ const getWpData = (val: any, list: any) => {
const changeView = () => {
showBoxi.value = false
loading.value = true
setTimeout(() => {
value.value = theTypeOfValue.value
showBoxi.value = true
}, 500)
setTimeout(() => {
loading.value = false
}, 1000)
}

View File

@@ -1,6 +1,6 @@
<template>
<div>
<!-- 数据数据 -->
<!-- 数据数据 -->
<div>
<TableHeader ref="tableHeaderRef" :showSearch="false" @selectChange="selectChange">
<template v-slot:select>
@@ -310,7 +310,7 @@ const setEchart = () => {
exportCSV(
echartsData.value.options.series.map((item: any) => item.name),
dataList,
'电数据.csv'
'电数据.csv'
)
}
}

View File

@@ -4,13 +4,8 @@
<TableHeader datePicker ref="headerRef" :showReset="false"></TableHeader>
<Table ref="tableRef" />
</div>
<waveFormAnalysis
v-loading="loading"
v-if="isWaveCharts"
ref="waveFormAnalysisRef"
@handleHideCharts="isWaveCharts = false"
:wp="wp"
/>
<waveFormAnalysis v-loading="loading" v-if="isWaveCharts" ref="waveFormAnalysisRef"
@handleHideCharts="isWaveCharts = false" :wp="wp" />
</div>
</template>
<script lang="ts" setup>
@@ -57,16 +52,23 @@ const tableStore: any = new TableStore({
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{ field: 'startTime', title: '发生时刻', minWidth: 170, sortable: true },
{ field: 'showName', title: '事件描述', minWidth: 120 },
{ field: 'startTime', title: '暂降发生时刻', minWidth: 180, sortable: true },
{
field: 'phaseType',
title: '相别',
minWidth: 80,
field: 'featureAmplitude',
title: '暂降(骤升)幅值(%)',
minWidth: 160,
sortable: true,
formatter: (row: any) => {
return row.cellValue || '/'
//row.cellValue = row.cellValue + '' ? row.cellValue.toFixed(2) : '/'
row.cellValue = row.cellValue != null ? Number(row.cellValue).toFixed(2) : '/'
if (String(row.cellValue).split('.')[1] == '00') {
row.cellValue = String(row.cellValue).split('.')[0]
}
return row.cellValue
}
},
{
field: 'persistTime',
title: '持续时间(s)',
@@ -79,18 +81,17 @@ const tableStore: any = new TableStore({
sortable: true
},
{
field: 'featureAmplitude',
title: '暂降(聚升)幅值(%)',
minWidth: 130,
field: 'phaseType',
title: '相别',
minWidth: 80,
formatter: (row: any) => {
//row.cellValue = row.cellValue + '' ? row.cellValue.toFixed(2) : '/'
row.cellValue = row.cellValue != null ? Number(row.cellValue).toFixed(2) : '/'
if (String(row.cellValue).split('.')[1] == '00') {
row.cellValue = String(row.cellValue).split('.')[0]
}
return row.cellValue
return row.cellValue || '/'
}
},
{ field: 'showName', title: '触发类型', minWidth: 120 },
{
title: '操作',
fixed: 'right',
@@ -149,7 +150,7 @@ const tableStore: any = new TableStore({
icon: 'el-icon-DataLine',
render: 'basicButton',
disabled: row => {
return row.showName != '未知'
return row.wavePath
}
},
{
@@ -164,6 +165,7 @@ const tableStore: any = new TableStore({
return !row.wavePath
},
click: row => {
ElMessage.info('下载中......')
getFileZip({ eventId: row.id }).then(res => {
let blob = new Blob([res], { type: 'application/zip' }) // console.log(blob) // var href = window.URL.createObjectURL(blob); //创建下载的链接
const url = window.URL.createObjectURL(blob)
@@ -173,6 +175,7 @@ const tableStore: any = new TableStore({
document.body.appendChild(link)
link.click() //执行下载
document.body.removeChild(link) //释放标签
ElMessage.success('波形下载成功')
})
}
},

View File

@@ -238,7 +238,7 @@ const deviceTypeChange = (val: any, obj: any) => {
nodeClick(obj)
}
const nodeClick = (e: any) => {
if (e && (e.level == 2 || e.type == 'device')) {
if (e && (e.level == 2 || e.level == 3 || e.type == 'device')) {
loading.value = true
nDid.value = e.ndid
devId.value = e.id

View File

@@ -137,12 +137,11 @@ const deviceTypeChange = (val: any, obj: any) => {
}
// 树节点点击
const nodeClick = (e: anyObj) => {
console.log('🚀 ~ nodeClick ~ e:', e)
if (!e) {
loading.value = false
return
}
if (e.level == 2) {
if (e.level == 2 || e.level == 3) {
pName.value = e.pName
nDid.value = e.ndid
loading.value = true

View File

@@ -44,8 +44,28 @@ const tableStore = new TableStore({
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{ field: 'startTime', title: '发生时刻', minWidth: 170, sortable: true },
{ field: 'showName', title: '事件描述', minWidth: 170 },
{ field: 'startTime', title: '暂降发生时刻', minWidth: 170, sortable: true },
{
field: 'featureAmplitude',
title: '暂降(骤升)幅值(%)',
minWidth: 160,
formatter: (row: any) => {
row.cellValue = row.cellValue + '' ? row.cellValue.toFixed(2) : '/'
if (String(row.cellValue).split('.')[1] == '00') {
row.cellValue = String(row.cellValue).split('.')[0]
}
return row.cellValue
}, sortable: true
},
{
field: 'persistTime',
title: '持续时间(s)',
minWidth: 110,
formatter: (row: any) => {
row.cellValue = row.cellValue ? row.cellValue.toFixed(2) : '/'
return row.cellValue
}, sortable: true
},
{
field: 'phaseType',
title: '相别',
@@ -55,27 +75,9 @@ const tableStore = new TableStore({
return row.cellValue
}
},
{
field: 'persistTime',
title: '持续时间(s)',
minWidth: 100,
formatter: (row: any) => {
row.cellValue = row.cellValue ? row.cellValue.toFixed(2) : '/'
return row.cellValue
}, sortable: true
},
{
field: 'featureAmplitude',
title: '暂降(聚升)幅值(%)',
minWidth: 100,
formatter: (row: any) => {
row.cellValue = row.cellValue + '' ? row.cellValue.toFixed(2) : '/'
if (String(row.cellValue).split('.')[1] == '00') {
row.cellValue = String(row.cellValue).split('.')[0]
}
return row.cellValue
}, sortable: true
},
{ field: 'showName', title: '触发类型', minWidth: 170 },
{
title: '操作', fixed: 'right',
width: 180,
@@ -143,6 +145,7 @@ const tableStore = new TableStore({
return !row.wavePath
},
click: row => {
ElMessage.info('下载中......')
getFileZip({ eventId: row.id }).then(res => {
let blob = new Blob([res], { type: 'application/zip' }) // console.log(blob) // var href = window.URL.createObjectURL(blob); //创建下载的链接
const url = window.URL.createObjectURL(blob)
@@ -152,7 +155,7 @@ const tableStore = new TableStore({
document.body.appendChild(link)
link.click() //执行下载
document.body.removeChild(link) //释放标签
ElMessage.success('波形下载成功')
})
}

View File

@@ -1,13 +1,7 @@
<template>
<div class="device-manage" :style="{ height: pageHeight.height }" v-loading="loading">
<DeviceTree
ref="treeRef"
:showCheckbox="true"
:default-checked-keys="defaultCheckedKeys"
@checkChange="checkChange"
:height="35"
:engineering="true"
></DeviceTree>
<DeviceTree ref="treeRef" :showCheckbox="true" :default-checked-keys="defaultCheckedKeys"
@checkChange="checkChange" :height="35" :engineering="true"></DeviceTree>
<div class="device-manage-right" :style="{ height: pageHeight.height }">
<vxe-table v-bind="defaultAttribute" :data="tableData" height="auto" style="width: 100%">
<vxe-column field="enginerName" title="工程名称"></vxe-column>
@@ -36,6 +30,7 @@ const tableData = ref([])
const treeRef = ref(null)
const ignoreCheckChange = ref(false)
const checkChange = (data: any) => {
console.log("🚀 ~ checkChange ~ data:", data)
if (data == undefined) return (loading.value = false)
if (data.data.pName == '便携式设备') {
if (ignoreCheckChange.value) {
@@ -47,7 +42,7 @@ const checkChange = (data: any) => {
return treeRef.value?.treRef?.treeRef2?.setCheckedKeys([])
}
if (data.data.level === 2) {
if (data.data.level === 2 || data.data.level === 3) {
if (data.checked) {
defaultCheckedKeys.value.push(data.data.id)
} else {
@@ -100,6 +95,7 @@ onMounted(() => {
overflow: hidden;
flex: 1;
padding: 10px 10px 10px 0;
.el-descriptions__header {
height: 36px;
margin-bottom: 7px;

View File

@@ -83,7 +83,7 @@
</div>
</el-col>
<el-col :span="12" class="mTop">
<el-checkbox v-model="formd.glfbfz">暂降幅值</el-checkbox>
<el-checkbox v-model="formd.glfbfz">暂降(骤升)幅值</el-checkbox>
<el-checkbox v-model="formd.glfbsj">持续时间</el-checkbox>
</el-col>
</el-row>
@@ -144,6 +144,7 @@ import { genFileId, ElMessage, ElNotification } from 'element-plus'
import type { UploadProps, UploadUserFile } from 'element-plus'
import pointTree from '@/components/tree/govern/pointTree.vue'
import { getLineExport } from '@/api/harmonic-boot/cockpit/cockpit'
import { isReportMonitorPoint } from '@/components/tree/govern/lineTreeUtils'
defineOptions({
name: 'TransientReport/monitoringpointReport'
})
@@ -212,7 +213,7 @@ const choose = (files: any) => {
//生成报告
const exportEvent = () => {
if (dotList.value?.level != 3) {
if (!isReportMonitorPoint(dotList.value)) {
return ElMessage.warning('请选择监测点进行报告生成!')
}
let a = ''

View File

@@ -1,5 +1,5 @@
<template>
<div class="default-main" :style="height" style="display: flex; height: 100%; overflow: hidden">
<div class="default-main" :style="height" style="display: flex; overflow: hidden">
<div style="width: 280px; flex-shrink: 0; height: 100%; overflow: hidden">
<pointTree
ref="TerminalRef"
@@ -73,6 +73,7 @@ import { genFileId, ElMessage, ElNotification } from 'element-plus'
import type { UploadProps, UploadUserFile } from 'element-plus'
import pointTree from '@/components/tree/govern/pointTree.vue'
import { exportModel } from '@/api/cs-harmonic-boot/datatrend'
import { isReportMonitorPoint } from '@/components/tree/govern/lineTreeUtils'
defineOptions({
name: 'harmonic-boot/report/word'
})
@@ -97,8 +98,10 @@ const pointTypeChange = (val: any, obj: any) => {
handleNodeClick(obj)
}
const handleNodeClick = (data: any) => {
if (isReportMonitorPoint(data)) {
dotList.value = data
}
}
// 上传
const choose = (files: any) => {
const isJPG = files.raw.type === 'image/jpg'
@@ -115,7 +118,7 @@ const choose = (files: any) => {
// 生成
const exportEvent = () => {
console.log('🚀 ~ exportEvent ~ dotList.value:', dotList.value)
if (dotList.value?.level != 3) {
if (!isReportMonitorPoint(dotList.value)) {
return ElMessage.warning('请选择监测点进行报告生成!')
}
let form = new FormData()

View File

@@ -29,13 +29,14 @@
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, provide } from 'vue'
import { onMounted, onUnmounted, ref, provide } from 'vue'
import TableStore from '@/utils/tableStore'
import PointTree from '@/components/tree/govern/pointTree.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useDictData } from '@/stores/dictData'
import { mainHeight } from '@/utils/layout'
import { exportExcel } from '@/views/system/reportForms/export.js'
import { destroyLuckysheet, exportLuckysheetFile, renderLuckysheetReport } from '@/utils/luckysheetHelper'
import { isLineTreeLeaf } from '@/components/tree/govern/lineTreeUtils'
import 'splitpanes/dist/splitpanes.css'
import { Splitpanes, Pane } from 'splitpanes'
// import data from './123.json'
@@ -74,25 +75,7 @@ const tableStore = new TableStore({
tableStore.table.params.lineId = dotList.value.id
},
loadCallback: () => {
tableStore.table.data.forEach((item: any) => {
item.data1 ? (item.data = JSON.parse(item.data1)) : ''
item.celldata.forEach((k: any) => {
item.data[k.r][k.c].v ? (item.data[k.r][k.c] = k.v) : ''
})
})
setTimeout(() => {
luckysheet.create({
container: 'luckysheet',
title: '', // 表 头名
lang: 'zh', // 中文
showtoolbar: false, // 是否显示工具栏
showinfobar: false, // 是否显示顶部信息栏
showsheetbar: true, // 是否显示底部sheet按钮
data: tableStore.table.data
// tableStore.table.data
})
}, 10)
renderLuckysheetReport('luckysheet', tableStore.table.data)
}
})
provide('tableStore', tableStore)
@@ -104,6 +87,9 @@ onMounted(() => {
size.value = ((280 / (dom.offsetWidth - 7)) * 100)
}
})
onUnmounted(() => {
destroyLuckysheet()
})
const stencil = (val: any) => {
@@ -118,7 +104,7 @@ const changetype = (val: any) => {
}
const handleNodeClick = (data: any, node: any) => {
if (data?.type == "line") {
if (isLineTreeLeaf(data)) {
dotList.value = data
setTimeout(() => {
tableStore.index()
@@ -127,7 +113,7 @@ const handleNodeClick = (data: any, node: any) => {
}
const exportEvent = () => {
exportExcel(luckysheet.getAllSheets(), '统计报表下载')
exportLuckysheetFile('统计报表下载', tableStore.table.data.length > 0)
}
</script>
<style lang="scss">

View File

@@ -31,14 +31,15 @@
</div>
</template>
<script setup lang="ts">
import { ref, provide } from 'vue'
import { ref, provide, onUnmounted } from 'vue'
import TableStore from '@/utils/tableStore'
import pointTreeWx from '@/components/tree/govern/pointTreeWx.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useDictData } from '@/stores/dictData'
import { mainHeight } from '@/utils/layout'
import { exportExcel } from '@/views/system/reportForms/export.js'
import { destroyLuckysheet, exportLuckysheetFile, renderLuckysheetReport } from '@/utils/luckysheetHelper'
import DatePicker from '@/components/form/datePicker/time.vue'
import { ElMessage } from 'element-plus'
const name = ref('')
// import data from './123.json'
defineOptions({
@@ -61,33 +62,19 @@ const tableStore = new TableStore({
beforeSearchFun: () => {
tableStore.table.params.tempId = Template.value.id
tableStore.table.params.lineId = dotList.value.id
tableStore.table.params.startTime = datePickerRef.value.timeValue[0],
tableStore.table.params.endTime = datePickerRef.value.timeValue[1],
if (!datePickerRef.value?.timeValue?.[0] || !datePickerRef.value?.timeValue?.[1]) {
ElMessage.warning('请选择时间')
return false
}
tableStore.table.params.startTime = datePickerRef.value.timeValue[0]
tableStore.table.params.endTime = datePickerRef.value.timeValue[1]
delete tableStore.table.params.searchBeginTime
delete tableStore.table.params.searchEndTime
delete tableStore.table.params.timeFlag
},
loadCallback: () => {
name.value = dotList.value.name
tableStore.table.data.forEach((item: any) => {
item.data1 ? (item.data = JSON.parse(item.data1)) : ''
item.celldata.forEach((k: any) => {
item.data[k.r][k.c].v ? (item.data[k.r][k.c] = k.v) : ''
})
})
setTimeout(() => {
luckysheet.create({
container: 'luckysheet',
title: '', // 表 头名
lang: 'zh', // 中文
showtoolbar: false, // 是否显示工具栏
showinfobar: false, // 是否显示顶部信息栏
showsheetbar: true, // 是否显示底部sheet按钮
data: tableStore.table.data
// tableStore.table.data
})
}, 10)
renderLuckysheetReport('luckysheet', tableStore.table.data, { allowEdit: false })
}
})
provide('tableStore', tableStore)
@@ -127,8 +114,11 @@ const exportEvent = () => {
// 格式化YYYY - MM - DD补零
const formattedDate = `${year}${String(month).padStart(2, '0')}${String(day).padStart(2, '0')}`
exportExcel(luckysheet.getAllSheets(), name.value + formattedDate)
exportLuckysheetFile(name.value + formattedDate, tableStore.table.data.length > 0)
}
onUnmounted(() => {
destroyLuckysheet()
})
</script>
<style lang="scss">
.report-zl-page {

View File

@@ -55,13 +55,14 @@
</div>
</template>
<script setup lang="ts">
import { ref, provide } from 'vue'
import { ref, provide, onUnmounted } from 'vue'
import TableStore from '@/utils/tableStore'
import pointTreeWx from '@/components/tree/govern/pointTreeWx.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useDictData } from '@/stores/dictData'
import { mainHeight } from '@/utils/layout'
import { exportExcel } from '@/views/system/reportForms/export.js'
import { destroyLuckysheet, exportLuckysheetFile, renderLuckysheetReport } from '@/utils/luckysheetHelper'
import { isLineTreeLeaf } from '@/components/tree/govern/lineTreeUtils'
import DatePicker from '@/components/form/datePicker/time.vue'
import pointTree from '@/components/tree/govern/pointTree.vue'
// import data from './123.json'
@@ -92,36 +93,8 @@ const tableStore = new TableStore({
delete tableStore.table.params.timeFlag
},
loadCallback: () => {
console.log('🚀 ~ tableStore.table:', tableStore.table)
name.value = dotList.value.name
// tableStore.table.data.forEach((item: any) => {
// item.data1 ? (item.data = JSON.parse(item.data1)) : ''
// item.celldata.forEach((k: any) => {
// item.data[k.r][k.c].v ? (item.data[k.r][k.c] = k.v) : ''
// })
// })
tableStore.table.data.forEach((item: any) => {
item.data1 ? (item.data = JSON.parse(item.data1)) : ''
item.celldata.forEach((k: any) => {
item.data[k.r][k.c].v ? (item.data[k.r][k.c] = k.v) : ''
})
})
console.log('🚀 ~ tableStore.table:', tableStore.table)
setTimeout(() => {
luckysheet.create({
container: 'luckysheet',
title: '', // 表 头名
lang: 'zh', // 中文
showtoolbar: false, // 是否显示工具栏
showinfobar: false, // 是否显示顶部信息栏
showsheetbar: true, // 是否显示底部sheet按钮
allowEdit: false, // 禁止所有编辑操作(必填)
data: tableStore.table.data
// tableStore.table.data
})
}, 10)
renderLuckysheetReport('luckysheet', tableStore.table.data, { allowEdit: false })
}
})
provide('tableStore', tableStore)
@@ -143,7 +116,7 @@ const pointTypeChange = (val: any, obj: any) => {
}
const handleNodeClick = (data: any) => {
if (data?.level == 3) {
if (isLineTreeLeaf(data)) {
dotList.value = data
setTimeout(() => {
tableStore.index()
@@ -155,14 +128,15 @@ const handleNodeClick = (data: any) => {
const exportEvent = () => {
const now = new Date()
const year = now.getFullYear() // 4位年份
const month = now.getMonth() + 1 // 月份0-11需+1
const day = now.getDate() // 日期1-31
// 格式化YYYY - MM - DD补零
const year = now.getFullYear()
const month = now.getMonth() + 1
const day = now.getDate()
const formattedDate = `${year}${String(month).padStart(2, '0')}${String(day).padStart(2, '0')}`
exportExcel(luckysheet.getAllSheets(), name.value + formattedDate)
exportLuckysheetFile(name.value + formattedDate, tableStore.table.data.length > 0)
}
onUnmounted(() => {
destroyLuckysheet()
})
</script>
<style lang="scss">
.report-zl-page {

View File

@@ -43,13 +43,14 @@
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, provide } from 'vue'
import { onMounted, onUnmounted, ref, provide } from 'vue'
import TableStore from '@/utils/tableStore'
import pointTreeWx from '@/components/tree/govern/pointTreeWx.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useDictData } from '@/stores/dictData'
import { mainHeight } from '@/utils/layout'
import { exportExcel } from '@/views/system/reportForms/export.js'
import { destroyLuckysheet, exportLuckysheetFile, renderLuckysheetReport } from '@/utils/luckysheetHelper'
import { isLineTreeLeaf } from '@/components/tree/govern/lineTreeUtils'
import DatePicker from '@/components/form/datePicker/time.vue'
import CloudDeviceEntryTree from '@/components/tree/govern/cloudDeviceEntryTreeZL.vue'
import { getListByIds } from '@/api/harmonic-boot/cockpit/cockpit'
@@ -80,35 +81,20 @@ const tableStore = new TableStore({
// ;(tableStore.table.params.startTime = datePickerRef.value.timeValue[0]),
// (tableStore.table.params.endTime = datePickerRef.value.timeValue[1]),
if (!tableStore.table.params.tempId) {
return ElMessage.warning('请选择模板')
ElMessage.warning('请选择模板')
return false
}
if (!dotList.value?.id) {
ElMessage.warning('请选择监测点')
return false
}
delete tableStore.table.params.searchBeginTime
delete tableStore.table.params.searchEndTime
delete tableStore.table.params.timeFlag
},
loadCallback: () => {
console.log('🚀 ~ tableStore.table:', tableStore.table.data)
name.value = dotList.value.name
// tableStore.table.data.forEach((item: any) => {
// item.data1 ? (item.data = JSON.parse(item.data1)) : ''
// item.celldata.forEach((k: any) => {
// item.data[k.r][k.c].v ? (item.data[k.r][k.c] = k.v) : ''
// })
// })
setTimeout(() => {
luckysheet.create({
container: 'luckysheet',
title: '', // 表 头名
lang: 'zh', // 中文
showtoolbar: false, // 是否显示工具栏
showinfobar: false, // 是否显示顶部信息栏
showsheetbar: true, // 是否显示底部sheet按钮
allowEdit: false, // 禁止所有编辑操作(必填)
data: tableStore.table.data
// tableStore.table.data
})
}, 10)
renderLuckysheetReport('luckysheet', tableStore.table.data, { allowEdit: false })
}
})
provide('tableStore', tableStore)
@@ -118,6 +104,9 @@ const flag = ref(true)
onMounted(() => {
initListByIds()
})
onUnmounted(() => {
destroyLuckysheet()
})
const idList = ref([])
// 监测对象
@@ -134,7 +123,6 @@ const initListByIds = () => {
})
}
const stencil = (val: any) => {
console.log('🚀 ~ stencil ~ val:', val)
templatePolicy.value = val.filter((item: any) => item.excelType == '4')
Template.value = templatePolicy.value[0]
reportForm.value = templatePolicy.value[0]?.excelType
@@ -145,7 +133,7 @@ const changetype = (val: any) => {
}
const handleNodeClick = (data: any, node: any) => {
if (data?.level == 3) {
if (isLineTreeLeaf(data) || data?.level == 3) {
dotList.value = data
setTimeout(() => {
tableStore.index()
@@ -163,7 +151,7 @@ const exportEvent = () => {
// 格式化YYYY - MM - DD补零
const formattedDate = `${year}${String(month).padStart(2, '0')}${String(day).padStart(2, '0')}`
exportExcel(luckysheet.getAllSheets(), name.value + formattedDate)
exportLuckysheetFile(name.value + formattedDate, tableStore.table.data.length > 0)
}
</script>
<style lang="scss">

2
types/table.d.ts vendored
View File

@@ -30,7 +30,7 @@ declare global {
}
loadCallback: (() => void) | null
resetCallback: (() => void) | null
beforeSearchFun: (() => void) | null
beforeSearchFun: (() => void | boolean) | null
exportProcessingData: (() => void) | null
height: string
publicHeight: number