耐受图调整、屏蔽非必要的菜单、登录后跳转到变频器页面
This commit is contained in:
@@ -130,6 +130,7 @@ const initChart = () => {
|
|||||||
chart.resize()
|
chart.resize()
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
|
const getChartInstance = () => chart
|
||||||
const handlerBar = (options: any) => {
|
const handlerBar = (options: any) => {
|
||||||
if (Array.isArray(options.series)) {
|
if (Array.isArray(options.series)) {
|
||||||
options.series.forEach((item: any) => {
|
options.series.forEach((item: any) => {
|
||||||
@@ -253,7 +254,7 @@ onMounted(() => {
|
|||||||
initChart()
|
initChart()
|
||||||
resizeObserver.observe(chartRef.value!)
|
resizeObserver.observe(chartRef.value!)
|
||||||
})
|
})
|
||||||
defineExpose({ initChart })
|
defineExpose({ initChart, getChartInstance })
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
resizeObserver.unobserve(chartRef.value!)
|
resizeObserver.unobserve(chartRef.value!)
|
||||||
chart?.dispose()
|
chart?.dispose()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// ? 全局默认配置项
|
// ? 全局默认配置项
|
||||||
|
|
||||||
// 首页地址(默认)
|
// 首页地址(默认)
|
||||||
export const HOME_URL: string = "/home/index";
|
export const HOME_URL: string = "/machine/freqConverter";
|
||||||
|
|
||||||
// export const HOME_URL: string = "/machine/controlSource";
|
// export const HOME_URL: string = "/machine/controlSource";
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import { HOME_URL } from '@/config'
|
||||||
import { useAuthStore } from '@/stores/modules/auth'
|
import { useAuthStore } from '@/stores/modules/auth'
|
||||||
import { useModeStore } from '@/stores/modules/mode' // 引入模式 store
|
import { useModeStore } from '@/stores/modules/mode' // 引入模式 store
|
||||||
import { useTabsStore } from '@/stores/modules/tabs'
|
import { useTabsStore } from '@/stores/modules/tabs'
|
||||||
@@ -74,8 +75,8 @@ const handelOpen = async (item: string, key: string) => {
|
|||||||
await initDynamicRouter()
|
await initDynamicRouter()
|
||||||
|
|
||||||
// 只有当目标路径与当前路径不同时才跳转
|
// 只有当目标路径与当前路径不同时才跳转
|
||||||
if (router.currentRoute.value.path !== '/home/index') {
|
if (router.currentRoute.value.path !== HOME_URL) {
|
||||||
await router.push({ path: '/home/index' })
|
await router.push({ path: HOME_URL })
|
||||||
} else {
|
} else {
|
||||||
// 如果已在目标页面,手动触发组件更新
|
// 如果已在目标页面,手动触发组件更新
|
||||||
window.location.reload() // 或者采用其他方式刷新数据
|
window.location.reload() // 或者采用其他方式刷新数据
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { LOGIN_URL } from '@/config'
|
import { HOME_URL, LOGIN_URL } from '@/config'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useUserStore } from '@/stores/modules/user'
|
import { useUserStore } from '@/stores/modules/user'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
@@ -132,7 +132,7 @@ const changeScene = async (value: string) => {
|
|||||||
//模式切换
|
//模式切换
|
||||||
const changeMode = async () => {
|
const changeMode = async () => {
|
||||||
authStore.changeModel()
|
authStore.changeModel()
|
||||||
await router.push('/home/index')
|
await router.push(HOME_URL)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { useRoute, useRouter } from 'vue-router'
|
|||||||
import { useGlobalStore } from '@/stores/modules/global'
|
import { useGlobalStore } from '@/stores/modules/global'
|
||||||
import { useTabsStore } from '@/stores/modules/tabs'
|
import { useTabsStore } from '@/stores/modules/tabs'
|
||||||
import { useAuthStore } from '@/stores/modules/auth'
|
import { useAuthStore } from '@/stores/modules/auth'
|
||||||
|
import { HOME_URL } from '@/config'
|
||||||
import { TabPaneName, TabsPaneContext } from 'element-plus'
|
import { TabPaneName, TabsPaneContext } from 'element-plus'
|
||||||
import MoreButton from './components/MoreButton.vue'
|
import MoreButton from './components/MoreButton.vue'
|
||||||
|
|
||||||
@@ -73,13 +74,13 @@ watch(
|
|||||||
// 初始化需要固定的 tabs
|
// 初始化需要固定的 tabs
|
||||||
const initTabs = () => {
|
const initTabs = () => {
|
||||||
authStore.flatMenuListGet.forEach(item => {
|
authStore.flatMenuListGet.forEach(item => {
|
||||||
if (item.meta.isAffix && !item.meta.isHide && !item.meta.isFull) {
|
if (item.path === HOME_URL && !item.meta.isHide && !item.meta.isFull) {
|
||||||
const tabsParams = {
|
const tabsParams = {
|
||||||
icon: item.meta.icon,
|
icon: item.meta.icon,
|
||||||
title: item.meta.title,
|
title: item.meta.title,
|
||||||
path: item.path,
|
path: item.path,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
close: !item.meta.isAffix,
|
close: false,
|
||||||
isKeepAlive: item.meta.isKeepAlive,
|
isKeepAlive: item.meta.isKeepAlive,
|
||||||
unshift: true
|
unshift: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { type AuthState } from '@/stores/interface'
|
|||||||
import { getAuthButtonListApi, getAuthMenuListApi } from '@/api/user/login'
|
import { getAuthButtonListApi, getAuthMenuListApi } from '@/api/user/login'
|
||||||
import { getAllBreadcrumbList, getFlatMenuList, getShowMenuList } from '@/utils'
|
import { getAllBreadcrumbList, getFlatMenuList, getShowMenuList } from '@/utils'
|
||||||
import { AUTH_STORE_KEY } from '@/stores/constant'
|
import { AUTH_STORE_KEY } from '@/stores/constant'
|
||||||
|
import { HOME_URL } from '@/config'
|
||||||
import { useModeStore } from '@/stores/modules/mode'
|
import { useModeStore } from '@/stores/modules/mode'
|
||||||
import { getLicense } from '@/api/activate'
|
import { getLicense } from '@/api/activate'
|
||||||
import type { Activate } from '@/api/activate/interface'
|
import type { Activate } from '@/api/activate/interface'
|
||||||
@@ -52,7 +53,7 @@ export const useAuthStore = defineStore(AUTH_STORE_KEY, {
|
|||||||
? filterMenuByExcludedNames(menuData, ['testSource', 'testScript', 'controlSource'])
|
? filterMenuByExcludedNames(menuData, ['testSource', 'testScript', 'controlSource'])
|
||||||
: filterMenuByExcludedNames(menuData, ['standardDevice'])
|
: filterMenuByExcludedNames(menuData, ['standardDevice'])
|
||||||
|
|
||||||
this.authMenuList = filteredMenu
|
this.authMenuList = normalizeHomeAffix(filteredMenu)
|
||||||
},
|
},
|
||||||
// Set RouteName
|
// Set RouteName
|
||||||
async setRouteName(name: string) {
|
async setRouteName(name: string) {
|
||||||
@@ -112,3 +113,22 @@ function filterMenuByExcludedNames(menuList: any[], excludedNames: string[]): an
|
|||||||
return !excludedNames.includes(menu.name)
|
return !excludedNames.includes(menu.name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeHomeAffix(menuList: any[]): any[] {
|
||||||
|
return menuList.map(menu => {
|
||||||
|
const nextMenu = { ...menu }
|
||||||
|
|
||||||
|
if (nextMenu.meta) {
|
||||||
|
nextMenu.meta = {
|
||||||
|
...nextMenu.meta,
|
||||||
|
isAffix: nextMenu.path === HOME_URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(nextMenu.children) && nextMenu.children.length > 0) {
|
||||||
|
nextMenu.children = normalizeHomeAffix(nextMenu.children)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextMenu
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ const login = (formEl: FormInstance | undefined) => {
|
|||||||
await tabsStore.setTabs([])
|
await tabsStore.setTabs([])
|
||||||
await keepAliveStore.setKeepAliveName([])
|
await keepAliveStore.setKeepAliveName([])
|
||||||
// 登录默认不显示菜单和导航栏
|
// 登录默认不显示菜单和导航栏
|
||||||
await authStore.resetAuthStore()
|
await authStore.setShowMenu()
|
||||||
// 跳转到首页
|
// 跳转到首页
|
||||||
await router.push(HOME_URL)
|
await router.push(HOME_URL)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-card class="dip-chart-card" shadow="never">
|
<el-card class="dip-chart-card" shadow="never">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@@ -9,50 +9,59 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-button
|
<div class="header-actions">
|
||||||
|
<el-button type="primary" plain :icon="Download" :disabled="!hasChartData" @click="downloadChartImage">
|
||||||
|
下载图片
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" plain :icon="Document" :disabled="!hasChartData" @click="exportChartData">
|
||||||
|
导出数据
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
:loading="curveLoading"
|
:loading="curveLoading"
|
||||||
class="draw-curve-button"
|
class="draw-curve-button"
|
||||||
@click="drawCharacteristicCurve"
|
@click="drawCharacteristicCurve"
|
||||||
>
|
>
|
||||||
绘制特性曲线
|
绘制特性曲线
|
||||||
</el-button>
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="chart-wrapper">
|
<div class="chart-wrapper">
|
||||||
<MyEchart :options="chartOptions"/>
|
<MyEchart ref="chartRef" :options="chartOptions" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</el-card>
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {computed, ref, watch} from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import {ElMessage} from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { Document, Download } from '@element-plus/icons-vue'
|
||||||
|
import * as XLSX from 'xlsx'
|
||||||
import MyEchart from '@/components/echarts/line/index.vue'
|
import MyEchart from '@/components/echarts/line/index.vue'
|
||||||
import {getFreqConverterSCurve} from '@/api/device/freqConverter'
|
import { getFreqConverterSCurve } from '@/api/device/freqConverter'
|
||||||
|
|
||||||
type ChartPointStatus = 'pass' | 'fail'
|
type ChartPointStatus = 'pass' | 'fail'
|
||||||
|
|
||||||
interface ChartPoint {
|
interface ChartPoint {
|
||||||
duration: number;
|
duration: number
|
||||||
residualVoltage: number;
|
residualVoltage: number
|
||||||
status: ChartPointStatus;
|
status: ChartPointStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NormalizedTolerantPoint {
|
interface NormalizedTolerantPoint {
|
||||||
duration: number;
|
duration: number
|
||||||
residualVoltage: number;
|
residualVoltage: number
|
||||||
tolerant: number | null;
|
tolerant: number | null
|
||||||
status: ChartPointStatus;
|
status: ChartPointStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
selectedMapping?: Record<string, any> | null;
|
selectedMapping?: Record<string, any> | null
|
||||||
webMsgSend?: any;
|
webMsgSend?: any
|
||||||
resultData?: any;
|
resultData?: any
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const STATUS_COLOR_MAP: Record<ChartPointStatus, string> = {
|
const STATUS_COLOR_MAP: Record<ChartPointStatus, string> = {
|
||||||
@@ -63,6 +72,7 @@ const STATUS_COLOR_MAP: Record<ChartPointStatus, string> = {
|
|||||||
const chartPoints = ref<ChartPoint[]>([])
|
const chartPoints = ref<ChartPoint[]>([])
|
||||||
const characteristicCurveData = ref<Array<[number, number]>>([])
|
const characteristicCurveData = ref<Array<[number, number]>>([])
|
||||||
const curveLoading = ref(false)
|
const curveLoading = ref(false)
|
||||||
|
const chartRef = ref<any>(null)
|
||||||
|
|
||||||
const selectedMappingText = computed(() => {
|
const selectedMappingText = computed(() => {
|
||||||
if (!props.selectedMapping) {
|
if (!props.selectedMapping) {
|
||||||
@@ -72,6 +82,91 @@ const selectedMappingText = computed(() => {
|
|||||||
return `变频器:${props.selectedMapping.freqConverterName || '-'}`
|
return `变频器:${props.selectedMapping.freqConverterName || '-'}`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const positiveDurations = computed(() => {
|
||||||
|
return [
|
||||||
|
...chartPoints.value.map(item => item.duration),
|
||||||
|
...characteristicCurveData.value.map(item => item[0])
|
||||||
|
].filter(item => Number.isFinite(item) && item > 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
const xAxisMin = computed(() => {
|
||||||
|
if (!positiveDurations.value.length) {
|
||||||
|
return 0.001
|
||||||
|
}
|
||||||
|
|
||||||
|
const minValue = Math.min(...positiveDurations.value)
|
||||||
|
return Math.min(0.001, Number(minValue.toFixed(3)))
|
||||||
|
})
|
||||||
|
|
||||||
|
const xAxisMax = computed(() => {
|
||||||
|
if (!positiveDurations.value.length) {
|
||||||
|
return 60
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxValue = Math.max(...positiveDurations.value)
|
||||||
|
return Math.max(Number((maxValue * 1.05).toFixed(3)), 60)
|
||||||
|
})
|
||||||
|
|
||||||
|
const sortedChartPoints = computed(() => {
|
||||||
|
return [...chartPoints.value].sort((a, b) => {
|
||||||
|
if (a.duration !== b.duration) {
|
||||||
|
return a.duration - b.duration
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.residualVoltage - b.residualVoltage
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const sortedCharacteristicCurveData = computed(() => {
|
||||||
|
return [...characteristicCurveData.value].sort((a, b) => {
|
||||||
|
if (a[0] !== b[0]) {
|
||||||
|
return a[0] - b[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return a[1] - b[1]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const hasChartData = computed(() => {
|
||||||
|
return sortedChartPoints.value.length > 0 || sortedCharacteristicCurveData.value.length > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const formatLogDurationLabel = (value: number) => {
|
||||||
|
if (!Number.isFinite(value)) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value >= 1) {
|
||||||
|
return `${Number(value.toFixed(2))}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${Number(value.toFixed(3))}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitizeFileName = (value: string) => {
|
||||||
|
return value.replace(/[\\/:*?"<>|]/g, '_').trim() || '未命名变频器'
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildFileName = (prefix: string, suffix: string) => {
|
||||||
|
const freqConverterName = sanitizeFileName(props.selectedMapping?.freqConverterName || '未命名变频器')
|
||||||
|
return `${prefix}_${freqConverterName}.${suffix}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const triggerDownload = (url: string, fileName: string) => {
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.style.display = 'none'
|
||||||
|
link.href = url
|
||||||
|
link.download = fileName
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
}
|
||||||
|
|
||||||
|
const toNumber = (value: unknown) => {
|
||||||
|
const result = Number(value)
|
||||||
|
return Number.isFinite(result) ? result : null
|
||||||
|
}
|
||||||
|
|
||||||
const normalizeTolerantValue = (value: unknown) => {
|
const normalizeTolerantValue = (value: unknown) => {
|
||||||
if (value === undefined || value === null || value === '') {
|
if (value === undefined || value === null || value === '') {
|
||||||
return null
|
return null
|
||||||
@@ -89,246 +184,12 @@ const normalizeDuration = (source: Record<string, any>) => {
|
|||||||
return toNumber(
|
return toNumber(
|
||||||
source.durationMs !== undefined && source.durationMs !== null
|
source.durationMs !== undefined && source.durationMs !== null
|
||||||
? Number(source.durationMs) / 1000
|
? Number(source.durationMs) / 1000
|
||||||
: source.duration ??
|
: source.duration ?? source.x ?? source.dipDuration ?? source.retainTime ?? source.durationValue
|
||||||
source.x ??
|
|
||||||
source.dipDuration ??
|
|
||||||
source.retainTime ??
|
|
||||||
source.durationValue
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizeResidualVoltageValue = (source: Record<string, any>) => {
|
const normalizeResidualVoltageValue = (source: Record<string, any>) => {
|
||||||
return toNumber(
|
return toNumber(source.residualVoltage ?? source.y ?? source.residual ?? source.voltage ?? source.residual_value)
|
||||||
source.residualVoltage ??
|
|
||||||
source.y ??
|
|
||||||
source.residual ??
|
|
||||||
source.voltage ??
|
|
||||||
source.residual_value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizeCurveData = (source: unknown) => {
|
|
||||||
if (!Array.isArray(source)) {
|
|
||||||
return [] as Array<[number, number]>
|
|
||||||
}
|
|
||||||
|
|
||||||
return source
|
|
||||||
.map(item => {
|
|
||||||
if (Array.isArray(item) && item.length >= 2) {
|
|
||||||
const duration = Number(item[0])
|
|
||||||
const residualVoltage = Number(item[1])
|
|
||||||
if (Number.isFinite(duration) && Number.isFinite(residualVoltage)) {
|
|
||||||
return [duration, residualVoltage] as [number, number]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item && typeof item === 'object') {
|
|
||||||
const record = item as Record<string, any>
|
|
||||||
const tolerant = normalizeTolerantValue(record.tolerant)
|
|
||||||
if (tolerant !== null && tolerant !== 2) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const duration = normalizeDuration(record)
|
|
||||||
const residualVoltage = normalizeResidualVoltageValue(record)
|
|
||||||
if (duration !== null && residualVoltage !== null) {
|
|
||||||
return [duration, residualVoltage] as [number, number]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
.filter((item): item is [number, number] => !!item)
|
|
||||||
}
|
|
||||||
|
|
||||||
const extractCurveData = (payload: any) => {
|
|
||||||
if (!payload) {
|
|
||||||
return [] as Array<[number, number]>
|
|
||||||
}
|
|
||||||
|
|
||||||
const candidates = [
|
|
||||||
payload,
|
|
||||||
payload?.data,
|
|
||||||
payload?.data?.records,
|
|
||||||
payload?.data?.points,
|
|
||||||
payload?.points,
|
|
||||||
payload?.records,
|
|
||||||
payload?.list
|
|
||||||
]
|
|
||||||
|
|
||||||
for (const candidate of candidates) {
|
|
||||||
const normalized = normalizeCurveData(candidate)
|
|
||||||
if (normalized.length) {
|
|
||||||
return normalized
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [] as Array<[number, number]>
|
|
||||||
}
|
|
||||||
|
|
||||||
const drawCharacteristicCurve = async () => {
|
|
||||||
const freqConverterId = props.selectedMapping?.freqConverterId
|
|
||||||
if (!freqConverterId) {
|
|
||||||
ElMessage.warning('未获取到变频器ID')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
curveLoading.value = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await getFreqConverterSCurve({
|
|
||||||
converterId: freqConverterId
|
|
||||||
})
|
|
||||||
|
|
||||||
const normalizedCurveData = extractCurveData(result)
|
|
||||||
|
|
||||||
if (!normalizedCurveData.length) {
|
|
||||||
characteristicCurveData.value = []
|
|
||||||
ElMessage.warning('未获取到特性曲线数据')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
characteristicCurveData.value = normalizedCurveData
|
|
||||||
} catch (error) {
|
|
||||||
console.error('绘制特性曲线失败:', error)
|
|
||||||
characteristicCurveData.value = []
|
|
||||||
} finally {
|
|
||||||
curveLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const chartOptions = computed(() => {
|
|
||||||
const maxDuration = 2
|
|
||||||
// const maxDuration = Math.max(
|
|
||||||
// 2,
|
|
||||||
// ...chartPoints.value.map(item => item.duration).filter(item => Number.isFinite(item)),
|
|
||||||
// ...characteristicCurveData.value.map(item => item[0]).filter(item => Number.isFinite(item))
|
|
||||||
// )
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: {
|
|
||||||
text: ''
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
top: 30,
|
|
||||||
left: 48,
|
|
||||||
right: 22,
|
|
||||||
bottom: 52
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'item',
|
|
||||||
formatter(params: any) {
|
|
||||||
if (params.seriesType === 'line') {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const [duration, residualVoltage, statusText] = params.value
|
|
||||||
return [
|
|
||||||
`持续时间: ${duration} s`,
|
|
||||||
`残余电压: ${residualVoltage} %`,
|
|
||||||
`状态: ${statusText}`
|
|
||||||
].join('<br/>')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
top: 0,
|
|
||||||
right: 0,
|
|
||||||
data: ['特性测试曲线']
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'log',
|
|
||||||
name: '持续时间(s)',
|
|
||||||
nameLocation: 'middle',
|
|
||||||
nameGap: 34,
|
|
||||||
min: 0.01,
|
|
||||||
max: 60,
|
|
||||||
logBase: 10,
|
|
||||||
minorTick: {
|
|
||||||
show: true,
|
|
||||||
splitNumber: 10
|
|
||||||
},
|
|
||||||
minorSplitLine: {
|
|
||||||
show: true,
|
|
||||||
lineStyle: {
|
|
||||||
color: '#e8edf6'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
lineStyle: {
|
|
||||||
color: '#cfd8e6'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
formatter(value: number) {
|
|
||||||
return value.toFixed(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value',
|
|
||||||
name: '暂降幅值',
|
|
||||||
min: 0,
|
|
||||||
max: 100,
|
|
||||||
interval: 10,
|
|
||||||
minorTick: {
|
|
||||||
show: true,
|
|
||||||
splitNumber: 2
|
|
||||||
},
|
|
||||||
minorSplitLine: {
|
|
||||||
show: true,
|
|
||||||
lineStyle: {
|
|
||||||
color: '#e8edf6'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
lineStyle: {
|
|
||||||
color: '#cfd8e6'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
formatter(value: number) {
|
|
||||||
return `${value}%`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dataZoom: [],
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '特性测试曲线',
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
showSymbol: true,
|
|
||||||
symbolSize: 7,
|
|
||||||
lineStyle: {
|
|
||||||
color: '#ff2a2a',
|
|
||||||
width: 3
|
|
||||||
},
|
|
||||||
itemStyle: {
|
|
||||||
color: '#ff2a2a'
|
|
||||||
},
|
|
||||||
data: characteristicCurveData.value
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '暂降点',
|
|
||||||
type: 'scatter',
|
|
||||||
symbolSize: 10,
|
|
||||||
data: chartPoints.value.map(item => ({
|
|
||||||
value: [item.duration, item.residualVoltage, getStatusText(item.status)],
|
|
||||||
itemStyle: {
|
|
||||||
color: STATUS_COLOR_MAP[item.status]
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
],
|
|
||||||
options: {
|
|
||||||
animation: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const toNumber = (value: unknown) => {
|
|
||||||
const result = Number(value)
|
|
||||||
return Number.isFinite(result) ? result : null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizeStatus = (value: unknown): ChartPointStatus => {
|
const normalizeStatus = (value: unknown): ChartPointStatus => {
|
||||||
@@ -362,14 +223,14 @@ const normalizeTolerantPoint = (source: Record<string, any>): NormalizedTolerant
|
|||||||
|
|
||||||
const tolerant = normalizeTolerantValue(
|
const tolerant = normalizeTolerantValue(
|
||||||
source.tolerant ??
|
source.tolerant ??
|
||||||
source.endure ??
|
source.endure ??
|
||||||
source.isEndure ??
|
source.isEndure ??
|
||||||
source.tolerable ??
|
source.tolerable ??
|
||||||
source.isTolerable ??
|
source.isTolerable ??
|
||||||
source.status ??
|
source.status ??
|
||||||
source.pointStatus ??
|
source.pointStatus ??
|
||||||
source.result ??
|
source.result ??
|
||||||
source.state
|
source.state
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -382,25 +243,21 @@ const normalizeTolerantPoint = (source: Record<string, any>): NormalizedTolerant
|
|||||||
: tolerant === 1
|
: tolerant === 1
|
||||||
? 'pass'
|
? 'pass'
|
||||||
: normalizeStatus(
|
: normalizeStatus(
|
||||||
source.tolerant ??
|
source.tolerant ??
|
||||||
source.endure ??
|
source.endure ??
|
||||||
source.isEndure ??
|
source.isEndure ??
|
||||||
source.tolerable ??
|
source.tolerable ??
|
||||||
source.isTolerable ??
|
source.isTolerable ??
|
||||||
source.status ??
|
source.status ??
|
||||||
source.pointStatus ??
|
source.pointStatus ??
|
||||||
source.result ??
|
source.result ??
|
||||||
source.state
|
source.state
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStatusText = (status: ChartPointStatus) => {
|
const getStatusText = (status: ChartPointStatus) => {
|
||||||
if (status === 'fail') {
|
return status === 'fail' ? '不耐受' : '耐受'
|
||||||
return '不耐受'
|
|
||||||
}
|
|
||||||
|
|
||||||
return '耐受'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizePoint = (source: Record<string, any>): ChartPoint | null => {
|
const normalizePoint = (source: Record<string, any>): ChartPoint | null => {
|
||||||
@@ -416,6 +273,65 @@ const normalizePoint = (source: Record<string, any>): ChartPoint | null => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizeCurveData = (source: unknown) => {
|
||||||
|
if (!Array.isArray(source)) {
|
||||||
|
return [] as Array<[number, number]>
|
||||||
|
}
|
||||||
|
|
||||||
|
return source
|
||||||
|
.map(item => {
|
||||||
|
if (Array.isArray(item) && item.length >= 2) {
|
||||||
|
const duration = Number(item[0])
|
||||||
|
const residualVoltage = Number(item[1])
|
||||||
|
if (Number.isFinite(duration) && Number.isFinite(residualVoltage) && duration > 0) {
|
||||||
|
return [duration, residualVoltage] as [number, number]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item && typeof item === 'object') {
|
||||||
|
const record = item as Record<string, any>
|
||||||
|
const tolerant = normalizeTolerantValue(record.tolerant)
|
||||||
|
if (tolerant !== null && tolerant !== 2) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const duration = normalizeDuration(record)
|
||||||
|
const residualVoltage = normalizeResidualVoltageValue(record)
|
||||||
|
if (duration !== null && residualVoltage !== null && duration > 0) {
|
||||||
|
return [duration, residualVoltage] as [number, number]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
.filter((item): item is [number, number] => !!item)
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractCurveData = (payload: any) => {
|
||||||
|
if (!payload) {
|
||||||
|
return [] as Array<[number, number]>
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidates = [
|
||||||
|
payload,
|
||||||
|
payload?.data,
|
||||||
|
payload?.data?.records,
|
||||||
|
payload?.data?.points,
|
||||||
|
payload?.points,
|
||||||
|
payload?.records,
|
||||||
|
payload?.list
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
const normalized = normalizeCurveData(candidate)
|
||||||
|
if (normalized.length) {
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [] as Array<[number, number]>
|
||||||
|
}
|
||||||
|
|
||||||
const extractCharacteristicCurvePoints = (payload: any) => {
|
const extractCharacteristicCurvePoints = (payload: any) => {
|
||||||
const result: Array<[number, number]> = []
|
const result: Array<[number, number]> = []
|
||||||
const seen = new Set<string>()
|
const seen = new Set<string>()
|
||||||
@@ -494,63 +410,288 @@ const extractPoints = (payload: any) => {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
const drawCharacteristicCurve = async () => {
|
||||||
() => props.webMsgSend,
|
const freqConverterId = props.selectedMapping?.freqConverterId
|
||||||
newValue => {
|
if (!freqConverterId) {
|
||||||
if (!newValue) {
|
ElMessage.warning('未获取到变频器ID')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextPoints = extractPoints(newValue)
|
curveLoading.value = true
|
||||||
if (!nextPoints.length) {
|
|
||||||
const nextCurvePoints = extractCharacteristicCurvePoints(newValue)
|
|
||||||
if (nextCurvePoints.length) {
|
|
||||||
characteristicCurveData.value = nextCurvePoints
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingPointMap = new Map(
|
try {
|
||||||
chartPoints.value.map(item => [`${item.duration}|${item.residualVoltage}`, item] as const)
|
const result = await getFreqConverterSCurve({
|
||||||
)
|
converterId: freqConverterId
|
||||||
|
})
|
||||||
|
|
||||||
nextPoints.forEach(item => {
|
const normalizedCurveData = extractCurveData(result)
|
||||||
const key = `${item.duration}|${item.residualVoltage}`
|
|
||||||
existingPointMap.set(key, item)
|
|
||||||
})
|
|
||||||
|
|
||||||
chartPoints.value = Array.from(existingPointMap.values())
|
if (!normalizedCurveData.length) {
|
||||||
|
|
||||||
const nextCurvePoints = extractCharacteristicCurvePoints(newValue)
|
|
||||||
if (nextCurvePoints.length) {
|
|
||||||
characteristicCurveData.value = nextCurvePoints
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{deep: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.resultData,
|
|
||||||
newValue => {
|
|
||||||
if (!newValue) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
chartPoints.value = extractPoints(newValue)
|
|
||||||
const nextCurvePoints = extractCharacteristicCurvePoints(newValue)
|
|
||||||
if (nextCurvePoints.length) {
|
|
||||||
characteristicCurveData.value = nextCurvePoints
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{deep: true, immediate: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.selectedMapping,
|
|
||||||
() => {
|
|
||||||
chartPoints.value = []
|
|
||||||
characteristicCurveData.value = []
|
characteristicCurveData.value = []
|
||||||
|
ElMessage.warning('未获取到特性曲线数据')
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
characteristicCurveData.value = normalizedCurveData
|
||||||
|
} catch (error) {
|
||||||
|
console.error('draw characteristic curve failed:', error)
|
||||||
|
characteristicCurveData.value = []
|
||||||
|
} finally {
|
||||||
|
curveLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadChartImage = async () => {
|
||||||
|
if (!hasChartData.value) {
|
||||||
|
ElMessage.warning('暂无可下载的图表数据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
const chartInstance = chartRef.value?.getChartInstance?.()
|
||||||
|
if (!chartInstance) {
|
||||||
|
ElMessage.warning('图表尚未渲染完成')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageUrl = chartInstance.getDataURL({
|
||||||
|
type: 'png',
|
||||||
|
pixelRatio: 2,
|
||||||
|
backgroundColor: '#ffffff'
|
||||||
|
})
|
||||||
|
|
||||||
|
triggerDownload(imageUrl, buildFileName('变频器耐受图', 'png'))
|
||||||
|
ElMessage.success('图表图片导出成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportChartData = () => {
|
||||||
|
if (!hasChartData.value) {
|
||||||
|
ElMessage.warning('暂无可导出的点位数据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const workbook = XLSX.utils.book_new()
|
||||||
|
|
||||||
|
if (sortedChartPoints.value.length) {
|
||||||
|
const pointSheet = XLSX.utils.json_to_sheet(
|
||||||
|
sortedChartPoints.value.map((item, index) => ({
|
||||||
|
序号: index + 1,
|
||||||
|
持续时间_s: item.duration,
|
||||||
|
暂降幅值_pct: item.residualVoltage,
|
||||||
|
状态: getStatusText(item.status)
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
XLSX.utils.book_append_sheet(workbook, pointSheet, '暂降点')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortedCharacteristicCurveData.value.length) {
|
||||||
|
const curveSheet = XLSX.utils.json_to_sheet(
|
||||||
|
sortedCharacteristicCurveData.value.map((item, index) => ({
|
||||||
|
序号: index + 1,
|
||||||
|
持续时间_s: item[0],
|
||||||
|
暂降幅值_pct: item[1]
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
XLSX.utils.book_append_sheet(workbook, curveSheet, '特性曲线')
|
||||||
|
}
|
||||||
|
|
||||||
|
const workbookBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' })
|
||||||
|
const blob = new Blob([workbookBuffer], {
|
||||||
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||||
|
})
|
||||||
|
const blobUrl = window.URL.createObjectURL(blob)
|
||||||
|
|
||||||
|
try {
|
||||||
|
triggerDownload(blobUrl, buildFileName('变频器耐受图数据', 'xlsx'))
|
||||||
|
ElMessage.success('点位数据导出成功')
|
||||||
|
} finally {
|
||||||
|
window.URL.revokeObjectURL(blobUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartOptions = computed(() => {
|
||||||
|
return {
|
||||||
|
title: {
|
||||||
|
text: ''
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: 30,
|
||||||
|
left: 48,
|
||||||
|
right: 22,
|
||||||
|
bottom: 52
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter(params: any) {
|
||||||
|
const rawValue = Array.isArray(params.value) ? params.value : params.value?.value
|
||||||
|
if (!Array.isArray(rawValue)) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const [duration, residualVoltage, statusText] = rawValue
|
||||||
|
return [
|
||||||
|
`类型: ${params.seriesName}`,
|
||||||
|
`持续时间: ${duration} s`,
|
||||||
|
`残余电压: ${residualVoltage} %`,
|
||||||
|
...(statusText ? [`状态: ${statusText}`] : [])
|
||||||
|
].join('<br/>')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
data: ['特性测试曲线']
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'log',
|
||||||
|
name: '持续时间(s)',
|
||||||
|
nameLocation: 'middle',
|
||||||
|
nameGap: 34,
|
||||||
|
min: xAxisMin.value,
|
||||||
|
max: xAxisMax.value,
|
||||||
|
logBase: 10,
|
||||||
|
minorTick: {
|
||||||
|
show: true,
|
||||||
|
splitNumber: 10
|
||||||
|
},
|
||||||
|
minorSplitLine: {
|
||||||
|
show: false,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#e8edf6'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#cfd8e6'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
formatter(value: number) {
|
||||||
|
return formatLogDurationLabel(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
name: '暂降幅值',
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
interval: 10,
|
||||||
|
minorTick: {
|
||||||
|
show: true,
|
||||||
|
splitNumber: 2
|
||||||
|
},
|
||||||
|
minorSplitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#e8edf6'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#cfd8e6'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
formatter(value: number) {
|
||||||
|
return `${value}%`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataZoom: [],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '特性测试曲线',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
showSymbol: true,
|
||||||
|
symbolSize: 7,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#ff2a2a',
|
||||||
|
width: 3
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: '#ff2a2a'
|
||||||
|
},
|
||||||
|
data: characteristicCurveData.value.map(item => [item[0], item[1], '特性曲线'])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '暂降点',
|
||||||
|
type: 'scatter',
|
||||||
|
symbolSize: 10,
|
||||||
|
data: chartPoints.value.map(item => ({
|
||||||
|
value: [item.duration, item.residualVoltage, getStatusText(item.status)],
|
||||||
|
itemStyle: {
|
||||||
|
color: STATUS_COLOR_MAP[item.status]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
animation: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.webMsgSend,
|
||||||
|
newValue => {
|
||||||
|
if (!newValue) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextPoints = extractPoints(newValue)
|
||||||
|
if (!nextPoints.length) {
|
||||||
|
const nextCurvePoints = extractCharacteristicCurvePoints(newValue)
|
||||||
|
if (nextCurvePoints.length) {
|
||||||
|
characteristicCurveData.value = nextCurvePoints
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingPointMap = new Map(
|
||||||
|
chartPoints.value.map(item => [`${item.duration}|${item.residualVoltage}`, item] as const)
|
||||||
|
)
|
||||||
|
|
||||||
|
nextPoints.forEach(item => {
|
||||||
|
const key = `${item.duration}|${item.residualVoltage}`
|
||||||
|
existingPointMap.set(key, item)
|
||||||
|
})
|
||||||
|
|
||||||
|
chartPoints.value = Array.from(existingPointMap.values())
|
||||||
|
|
||||||
|
const nextCurvePoints = extractCharacteristicCurvePoints(newValue)
|
||||||
|
if (nextCurvePoints.length) {
|
||||||
|
characteristicCurveData.value = nextCurvePoints
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.resultData,
|
||||||
|
newValue => {
|
||||||
|
if (!newValue) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chartPoints.value = extractPoints(newValue)
|
||||||
|
const nextCurvePoints = extractCharacteristicCurvePoints(newValue)
|
||||||
|
if (nextCurvePoints.length) {
|
||||||
|
characteristicCurveData.value = nextCurvePoints
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.selectedMapping,
|
||||||
|
() => {
|
||||||
|
chartPoints.value = []
|
||||||
|
characteristicCurveData.value = []
|
||||||
|
}
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -574,6 +715,14 @@ watch(
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.card-header-main {
|
.card-header-main {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user