调整代码

This commit is contained in:
guanj
2025-11-20 15:12:01 +08:00
parent 0a52d1afae
commit 028fd44490
17 changed files with 3910 additions and 3761 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

BIN
public/favicon3.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -61,11 +61,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, watch, onMounted, onUnmounted, ref, nextTick, getCurrentInstance, toRaw } from 'vue' import { reactive, watch, onMounted, onUnmounted, ref, nextTick, getCurrentInstance, toRaw } from 'vue'
import { getSelectData } from '@/api/common' // import { getSelectData } from '@/api/common'
import { uuid } from '@/utils/random' import { uuid } from '@/utils/random'
import type { ElSelect } from 'element-plus' import type { ElSelect } from 'element-plus'
import { isEmpty } from 'lodash-es' import { isEmpty } from 'lodash-es'
import { getArrayKey } from '@/utils/common' // import { getArrayKey } from '@/utils/common'
const selectRef = ref<InstanceType<typeof ElSelect> | undefined>() const selectRef = ref<InstanceType<typeof ElSelect> | undefined>()
type ElSelectProps = Partial<InstanceType<typeof ElSelect>['$props']> type ElSelectProps = Partial<InstanceType<typeof ElSelect>['$props']>
@@ -140,15 +140,15 @@ const onChangeSelect = (val: valType) => {
let pkArr = props.pk.split('.') let pkArr = props.pk.split('.')
let pk = pkArr[pkArr.length - 1] let pk = pkArr[pkArr.length - 1]
if (typeof val == 'number' || typeof val == 'string') { if (typeof val == 'number' || typeof val == 'string') {
const dataKey = getArrayKey(state.options, pk, val.toString()) // const dataKey = getArrayKey(state.options, pk, val.toString())
emits('row', dataKey ? toRaw(state.options[dataKey]) : {}) // emits('row', dataKey ? toRaw(state.options[dataKey]) : {})
} else { } else {
const valueArr = [] // const valueArr = []
for (const key in val) { // for (const key in val) {
let dataKey = getArrayKey(state.options, pk, val[key].toString()) // let dataKey = getArrayKey(state.options, pk, val[key].toString())
if (dataKey) valueArr.push(toRaw(state.options[dataKey])) // if (dataKey) valueArr.push(toRaw(state.options[dataKey]))
} // }
emits('row', valueArr) // emits('row', valueArr)
} }
} }
} }
@@ -196,35 +196,35 @@ const getData = (initValue: valType = '') => {
state.params.page = state.currentPage state.params.page = state.currentPage
state.params.initKey = props.pk state.params.initKey = props.pk
state.params.initValue = initValue state.params.initValue = initValue
getSelectData(props.remoteUrl, state.keyword, state.params) // getSelectData(props.remoteUrl, state.keyword, state.params)
.then((res) => { // .then((res) => {
let initializeData = true // let initializeData = true
let opts = res.data.options ? res.data.options : res.data.list // let opts = res.data.options ? res.data.options : res.data.list
if (typeof props.labelFormatter == 'function') { // if (typeof props.labelFormatter == 'function') {
for (const key in opts) { // for (const key in opts) {
opts[key][props.field] = props.labelFormatter(opts[key], key) // opts[key][props.field] = props.labelFormatter(opts[key], key)
} // }
} // }
state.options = opts // state.options = opts
state.total = res.data.total ?? 0 // state.total = res.data.total ?? 0
if (initValue) { // if (initValue) {
// 重新渲染组件,确保在赋值前,opts已加载到-兼容 modelValue 更新 // // 重新渲染组件,确保在赋值前,opts已加载到-兼容 modelValue 更新
state.selectKey = uuid() // state.selectKey = uuid()
initializeData = false // initializeData = false
} // }
state.loading = false // state.loading = false
state.initializeData = initializeData // state.initializeData = initializeData
if (state.accidentBlur) { // if (state.accidentBlur) {
nextTick(() => { // nextTick(() => {
const inputEl = selectRef.value?.$el.querySelector('.el-select__tags .el-select__input') // const inputEl = selectRef.value?.$el.querySelector('.el-select__tags .el-select__input')
inputEl && inputEl.focus() // inputEl && inputEl.focus()
state.accidentBlur = false // state.accidentBlur = false
}) // })
} // }
}) // })
.catch(() => { // .catch(() => {
state.loading = false // state.loading = false
}) // })
} }
const onSelectCurrentPageChange = (val: number) => { const onSelectCurrentPageChange = (val: number) => {

View File

@@ -23,7 +23,7 @@
<el-button <el-button
@click="backbxlb" @click="backbxlb"
class="el-icon-refresh-right" class="el-icon-refresh-right"
icon="el-icon-CloseBold" icon="el-icon-Back"
style="float: right" style="float: right"
> >
返回 返回

View File

@@ -1,43 +1,24 @@
<template> <template>
<div :style="{ height:props.height?props.height: tableStore.table.height }"> <div :style="{ height: typeof props.height === 'string' ? props.height : tableStore.table.height }">
<vxe-table <vxe-table ref="tableRef" height="auto" :key="key" :data="tableStore.table.data"
ref="tableRef" v-loading="tableStore.table.loading" v-bind="Object.assign({}, defaultAttribute, $attrs)"
height="auto" @checkbox-all="selectChangeEvent" @checkbox-change="selectChangeEvent" :showOverflow="showOverflow"
:key="key" @sort-change="handleSortChange">
:data="tableStore.table.data"
v-loading="tableStore.table.loading"
v-bind="Object.assign({}, defaultAttribute, $attrs)"
@checkbox-all="selectChangeEvent"
@checkbox-change="selectChangeEvent"
:showOverflow="showOverflow"
@sort-change="handleSortChange"
>
<!-- Column 组件内部是 el-table-column --> <!-- Column 组件内部是 el-table-column -->
<template v-if="isGroup"> <template v-if="isGroup">
<GroupColumn :column="tableStore.table.column" /> <GroupColumn :column="tableStore.table.column" />
</template> </template>
<template v-else> <template v-else>
<Column <Column :attr="item" :key="key + '-column'" v-for="(item, key) in tableStore.table.column"
:attr="item" :tree-node="item.treeNode">
:key="key + '-column'"
v-for="(item, key) in tableStore.table.column"
:tree-node="item.treeNode"
>
<!-- tableStore 预设的列 render 方案 --> <!-- tableStore 预设的列 render 方案 -->
<template v-if="item.render" #default="scope"> <template v-if="item.render" #default="scope">
<FieldRender <FieldRender :field="item" :row="scope.row" :column="scope.column" :index="scope.rowIndex" :key="key +
:field="item" '-' +
:row="scope.row" item.render +
:column="scope.column" '-' +
:index="scope.rowIndex" (item.field ? '-' + item.field + '-' + scope.row[item.field] : '')
:key=" " />
key +
'-' +
item.render +
'-' +
(item.field ? '-' + item.field + '-' + scope.row[item.field] : '')
"
/>
</template> </template>
</Column> </Column>
</template> </template>
@@ -46,16 +27,11 @@
</div> </div>
<div v-if="tableStore.showPage" class="table-pagination"> <div v-if="tableStore.showPage" class="table-pagination">
<el-pagination <el-pagination :currentPage="tableStore.table.params!.pageNum" :page-size="tableStore.table.params!.pageSize"
:currentPage="tableStore.table.params!.pageNum" :page-sizes="pageSizes" background
:page-size="tableStore.table.params!.pageSize"
:page-sizes="pageSizes"
background
:layout="config.layout.shrink ? 'prev, next, jumper' : 'sizes,total, ->, prev, pager, next, jumper'" :layout="config.layout.shrink ? 'prev, next, jumper' : 'sizes,total, ->, prev, pager, next, jumper'"
:total="tableStore.table.total" :total="tableStore.table.total" @size-change="onTableSizeChange"
@size-change="onTableSizeChange" @current-change="onTableCurrentChange"></el-pagination>
@current-change="onTableCurrentChange"
></el-pagination>
</div> </div>
<slot name="footer"></slot> <slot name="footer"></slot>
</template> </template>
@@ -80,13 +56,13 @@ const key = ref(0)
interface Props extends /* @vue-ignore */ Partial<InstanceType<typeof ElTable>> { interface Props extends /* @vue-ignore */ Partial<InstanceType<typeof ElTable>> {
isGroup?: boolean isGroup?: boolean
showOverflow?: boolean showOverflow?: boolean
height?: string | boolean height?: string | number
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
isGroup: false, isGroup: false,
showOverflow: true, showOverflow: true,
height: false height: undefined
}) })
onMounted(() => { onMounted(() => {
tableStore.table.ref = tableRef.value as VxeTableInstance tableStore.table.ref = tableRef.value as VxeTableInstance

View File

@@ -1,5 +1,4 @@
import { useCache, CACHE_KEY } from '@/hooks/web/useCache' import { useCache, CACHE_KEY } from '@/hooks/web/useCache'
import { TokenType } from '@/api/login/types'
import { decrypt, encrypt } from '@/utils/jsencrypt' import { decrypt, encrypt } from '@/utils/jsencrypt'
const { wsCache } = useCache() const { wsCache } = useCache()
@@ -19,7 +18,7 @@ export const getRefreshToken = () => {
} }
// 设置token // 设置token
export const setToken = (token: TokenType) => { export const setToken = (token: any) => {
wsCache.set(RefreshTokenKey, token.refreshToken) wsCache.set(RefreshTokenKey, token.refreshToken)
wsCache.set(AccessTokenKey, token.accessToken) wsCache.set(AccessTokenKey, token.accessToken)
} }

View File

@@ -157,3 +157,146 @@ export const exportCSV = (title: object, data: any, filename: string) => {
// 释放URL对象 // 释放URL对象
URL.revokeObjectURL(link.href) 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
}

View File

@@ -23,7 +23,7 @@ const prop = defineProps({
const dictData = useDictData() const dictData = useDictData()
const fontdveoption = dictData.getBasicData('Dev_Ops') const fontdveoption = dictData.getBasicData('Dev_Ops')
const tableStore = new TableStore({ const tableStore:any = new TableStore({
url: '/device-boot/pqsTerminalLogs/getList', url: '/device-boot/pqsTerminalLogs/getList',
method: 'POST', method: 'POST',
column: [ column: [

View File

@@ -399,7 +399,7 @@ const handleCurrentChange = (val: number) => {
const exportEvent = () => { const exportEvent = () => {
const allFilteredData = filteredData.value const allFilteredData = filteredData.value
tableRef.value.exportData({ tableRef.value.exportData({
filename: '场级评估-污染值报告', filename: '场级评估-污染值报告',
sheetName: 'Sheet1', sheetName: 'Sheet1',
type: 'xlsx', type: 'xlsx',
useStyle: true, useStyle: true,

View File

@@ -652,7 +652,7 @@ const initRadioCharts = () => {
echartsData1.value.options.series[i].center = ['50%', '50%'] echartsData1.value.options.series[i].center = ['50%', '50%']
} }
} }
const initEcharts = (color: string, key: number) => { const initEcharts = (color: string, key: number, name: string) => {
return { return {
options: { options: {
tooltip: {}, tooltip: {},
@@ -731,7 +731,7 @@ const initEcharts = (color: string, key: number) => {
data: [ data: [
{ {
value: 0, value: 0,
name: 'A相', name: name,
itemStyle: { itemStyle: {
color: color color: color
} }
@@ -744,14 +744,14 @@ const initEcharts = (color: string, key: number) => {
} }
//渲染echarts //渲染echarts
const init = () => { const init = () => {
const url = (localStorage.getItem('WebSocketUrl') || 'ws://192.168.1.68:10407/api/pushMessage/') const url = localStorage.getItem('WebSocketUrl') || 'ws://192.168.1.68:10407/api/pushMessage/'
echartsDataV1.value = initEcharts('#DAA520', 0) echartsDataV1.value = initEcharts('#DAA520', 0, 'A相')
echartsDataV2.value = initEcharts('#2E8B57', 0) echartsDataV2.value = initEcharts('#2E8B57', 0, 'B相')
echartsDataV3.value = initEcharts('#A52a2a', 0) echartsDataV3.value = initEcharts('#A52a2a', 0, 'C相')
echartsDataA1.value = initEcharts('#DAA520', 1) echartsDataA1.value = initEcharts('#DAA520', 1, 'A相')
echartsDataA2.value = initEcharts('#2E8B57', 1) echartsDataA2.value = initEcharts('#2E8B57', 1, 'B相')
echartsDataA3.value = initEcharts('#A52a2a', 1) echartsDataA3.value = initEcharts('#A52a2a', 1, 'C相')
if (!dataSocket.socketServe) { if (!dataSocket.socketServe) {
console.error('WebSocket 客户端实例不存在') console.error('WebSocket 客户端实例不存在')
@@ -764,9 +764,7 @@ const url = (localStorage.getItem('WebSocketUrl') || 'ws://192.168.1.68:10407/ap
}) })
} }
let pids = monitoringPoint.state.pid.split(',') let pids = monitoringPoint.state.pid.split(',')
dataSocket.socketServe.connect( dataSocket.socketServe.connect(`${url}${adminInfo.id},${monitoringPoint.state.lineId},${pids[pids.length - 2]}`)
`${url}${adminInfo.id},${monitoringPoint.state.lineId},${pids[pids.length - 2]}`
)
dataSocket.socketServe.registerCallBack('message', (res: any) => { dataSocket.socketServe.registerCallBack('message', (res: any) => {
txtContent.value = res.value txtContent.value = res.value
let data = JSON.parse(res.value) let data = JSON.parse(res.value)

View File

@@ -67,7 +67,13 @@
<!-- <el-form :inline="true"> <!-- <el-form :inline="true">
</el-form> --> </el-form> -->
<div id="canvas" class="mt10" :style="height1" style="overflow-y: auto;overflow-x: hidden" v-loading="loading"> <div
id="canvas"
class="mt10"
:style="height1"
style="overflow-y: auto; overflow-x: hidden"
v-loading="loading"
>
<my-echart <my-echart
:options="item.option" :options="item.option"
v-for="item in list" v-for="item in list"
@@ -98,7 +104,7 @@ import TableStore from '@/utils/tableStore'
import waveForm from '@/components/echarts/waveForm.vue' import waveForm from '@/components/echarts/waveForm.vue'
import html2canvas from 'html2canvas' import html2canvas from 'html2canvas'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { yMethod } from '@/utils/echartMethod' import { yMethod, completeTimeSeries } from '@/utils/echartMethod'
import * as echarts from 'echarts' // 全引入 import * as echarts from 'echarts' // 全引入
import { min } from 'xe-utils' import { min } from 'xe-utils'
const datePickerRef = ref() const datePickerRef = ref()
@@ -1520,22 +1526,42 @@ const getEcharts = () => {
let str = (params[i].value[1] * 1).toString() let str = (params[i].value[1] * 1).toString()
let reg = str.indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g let reg = str.indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g
let str1 = str.replace(reg, '$1,') let str1 = str.replace(reg, '$1,')
tips += params[i].marker + params[i].seriesName.replace('(kV)', '') + ':' + str1 + 'kV<br/>' tips +=
params[i].marker +
params[i].seriesName.replace('(kV)', '') +
':' +
(Number.isNaN(Number(str1)) ? '/' : str1) +
'kV<br/>'
} else if (params[i].seriesName == '零序电压(V)' || params[i].seriesName == '负序电压(V)') { } else if (params[i].seriesName == '零序电压(V)' || params[i].seriesName == '负序电压(V)') {
let str = (params[i].value[1] * 1).toString() let str = (params[i].value[1] * 1).toString()
let reg = str.indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g let reg = str.indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g
let str1 = str.replace(reg, '$1,') let str1 = str.replace(reg, '$1,')
tips += params[i].marker + params[i].seriesName.replace('(V)', '') + ':' + str1 + 'V<br/>' tips +=
params[i].marker +
params[i].seriesName.replace('(V)', '') +
':' +
(Number.isNaN(Number(str1)) ? '/' : str1) +
'V<br/>'
} else if (params[i].seriesName !== '正序电压(kV)') { } else if (params[i].seriesName !== '正序电压(kV)') {
let str = (params[i].value[1] * 1).toString() let str = (params[i].value[1] * 1).toString()
let reg = str.indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g let reg = str.indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g
let str1 = str.replace(reg, '$1,') let str1 = str.replace(reg, '$1,')
tips += params[i].marker + params[i].seriesName + ':' + str1 + '<br/>' tips +=
params[i].marker +
params[i].seriesName +
':' +
(Number.isNaN(Number(str1)) ? '/' : str1) +
'<br/>'
} else { } else {
let str = (params[i].value[2] * 1).toString() let str = (params[i].value[2] * 1).toString()
let reg = str.indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g let reg = str.indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g
let str1 = str.replace(reg, '$1,') let str1 = str.replace(reg, '$1,')
tips += params[i].marker + params[i].seriesName + ':' + str1 + '<br/>' tips +=
params[i].marker +
params[i].seriesName +
':' +
(Number.isNaN(Number(str1)) ? '/' : str1) +
'<br/>'
let str2 = (params[i].value[3] * 1).toString() let str2 = (params[i].value[3] * 1).toString()
let reg2 = str2.indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g let reg2 = str2.indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g
@@ -1543,7 +1569,12 @@ const getEcharts = () => {
// if(params[i].seriesName){ // if(params[i].seriesName){
// } // }
tips += params[i].marker + params[i].seriesName + ':' + str12 + '<br/>' tips +=
params[i].marker +
params[i].seriesName +
':' +
(Number.isNaN(Number(str2)) ? '/' : str2) +
'<br/>'
} }
} }

View File

@@ -113,6 +113,7 @@ const tableStore = new TableStore({
showtoolbar: false, // 是否显示工具栏 showtoolbar: false, // 是否显示工具栏
showinfobar: false, // 是否显示顶部信息栏 showinfobar: false, // 是否显示顶部信息栏
showsheetbar: true, // 是否显示底部sheet按钮 showsheetbar: true, // 是否显示底部sheet按钮
allowEdit: false, // 禁止所有编辑操作(必填)
data: tableStore.table.data data: tableStore.table.data
}) })
}, 10) }, 10)
@@ -166,5 +167,6 @@ const exportEvent = () => {
.box { .box {
padding: 10px; padding: 10px;
} }
</style> </style>