Files
CN_Tool_client/frontend/src/views/steady/checksquare/utils/checksquareTable.ts
yexb ef80aff151 feat(steady): 重构稳态校验功能并优化界面布局
- 更新 API 接口路径从 /steady/data-view/checksquare/* 到 /steady/checksquare/*
- 修改校验任务状态枚举值 FAILED 为 FAIL 并更新相关处理逻辑
- 移除缺失率和最大连续缺失分钟数字段,简化数据完整性计算
- 添加新的创建结果面板组件 ChecksquareCreateResultPanel.vue
- 调整创建对话框布局,采用两行搜索控件设计
- 更新任务表头部按钮文字为"新增"并调整搜索列配置为5列
- 修改详情面板显示开始时间和结束时间字段
- 重构工作台界面布局,使用 flex 布局替代 grid 布局
- 更新设备类型相关 API 接口和数据结构定义
- 添加设备类型字典常量并更新路由配置
- 优化搜索表单展开收起逻辑的计算方式
- 调整创建流程不再轮询获取任务详情,改为直接显示摘要信息
- 更新数据完整性格式化函数参数和调用方式
- 修改创建对话框样式类名和尺寸配置
- 添加设备类型管理相关的接口定义和实现方法
2026-06-15 08:40:44 +08:00

319 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

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

import type { SteadyDataView } from '@/api/steady/steadyDataView/interface'
export const CHECKSQUARE_STAT_TYPES: SteadyDataView.SteadyTrendStatType[] = ['AVG', 'MAX', 'MIN', 'CP95']
export const CHECKSQUARE_HARMONIC_ORDER_MIN = 2
export const CHECKSQUARE_HARMONIC_ORDER_MAX = 50
export const CHECKSQUARE_HARMONIC_ORDERS = Array.from(
{ length: CHECKSQUARE_HARMONIC_ORDER_MAX - CHECKSQUARE_HARMONIC_ORDER_MIN + 1 },
(_item, index) => index + CHECKSQUARE_HARMONIC_ORDER_MIN
)
const CHECKSQUARE_STAT_LABEL_MAP: Record<SteadyDataView.SteadyTrendStatType, string> = {
AVG: '平均值',
MAX: '最大值',
MIN: '最小值',
CP95: 'CP95'
}
export const formatChecksquareStatType = (statType: SteadyDataView.SteadyTrendStatType | string) => {
return CHECKSQUARE_STAT_LABEL_MAP[statType as SteadyDataView.SteadyTrendStatType] || statType
}
export const formatBooleanText = (value?: boolean | null) => {
if (value === null || value === undefined) return '-'
return value ? '是' : '否'
}
export const formatDataIntegrity = (value?: number | null, text?: string | null) => {
if (text) return text
const integrityValue = value === null || value === undefined || !Number.isFinite(Number(value)) ? null : Number(value)
if (integrityValue === null) return '-'
return `${(integrityValue * 100).toFixed(2)}%`
}
export const findStatSummary = (
item: SteadyDataView.SteadyChecksquareItem,
statType: SteadyDataView.SteadyTrendStatType
) => {
return item.statSummaries?.find(summary => summary.statType === statType)
}
export const formatStatMissingRate = (
item: SteadyDataView.SteadyChecksquareItem,
statType: SteadyDataView.SteadyTrendStatType
) => {
const summary = findStatSummary(item, statType)
if (!summary || summary.supported === false) return '-'
return formatDataIntegrity(summary.dataIntegrity, summary.dataIntegrityText)
}
export const resolveChecksquareRowName = (item: SteadyDataView.SteadyChecksquareItem) => {
const progressText = getHarmonicProgressText(item)
if (progressText) return `${item.indicatorName || item.indicatorCode}${progressText}`
if (!item.harmonicOrder) return item.indicatorName || item.indicatorCode
return `${item.harmonicOrder}`
}
export const getHarmonicProgressText = (item: SteadyDataView.SteadyChecksquareItem) => {
const children = item.children || []
if (!children.length || !children.some(child => child.harmonicOrder)) return ''
const totalCount = children.length
const resolvedCount = children.filter(child => isResolvedChecksquareItem(child)).length
if (resolvedCount >= totalCount) return ''
return `已完成 ${resolvedCount}/${totalCount}`
}
export const collectMissingSegments = (item: SteadyDataView.SteadyChecksquareItem | null) => {
if (!item) return []
return (item.statDetails || []).flatMap(detail =>
(detail.segments || [])
.filter(segment => segment.status === 'MISSING')
.map(segment => ({
...segment,
statType: detail.statType
}))
)
}
export const hasChecksquareDetail = (item: SteadyDataView.SteadyChecksquareItem) => {
return (
Boolean(item.itemId) ||
Boolean(item.abnormalPointCount) ||
Boolean(item.harmonicParityAbnormalPointCount) ||
Boolean(item.missingPointCount) ||
(item.statDetails || []).some(detail => (detail.segments || []).length)
)
}
const hasChecksquareHarmonicOrderRange = (indicator: SteadyDataView.SteadyIndicatorNode) => {
const start = Number(indicator.harmonicOrderStart)
const end = Number(indicator.harmonicOrderEnd)
if (!Number.isFinite(start) || !Number.isFinite(end)) return false
const rangeStart = Math.min(start, end)
const rangeEnd = Math.max(start, end)
return rangeStart <= CHECKSQUARE_HARMONIC_ORDER_MAX && rangeEnd >= CHECKSQUARE_HARMONIC_ORDER_MIN
}
export const isChecksquareHarmonicIndicator = (indicator: SteadyDataView.SteadyIndicatorNode) => {
// 只有指标目录明确包含 2-50 次谐波范围时,才预建谐波子行并执行逐次合并。
return hasChecksquareHarmonicOrderRange(indicator)
}
export const buildPendingChecksquareItem = (
indicator: SteadyDataView.SteadyIndicatorNode
): SteadyDataView.SteadyChecksquareItem => {
const indicatorCode = indicator.indicatorCode || indicator.id || indicator.treeKey || indicator.name
return {
itemKey: `pending|${indicatorCode}`,
indicatorCode,
indicatorName: indicator.name || indicatorCode,
children: isChecksquareHarmonicIndicator(indicator) ? buildPendingChecksquareHarmonicItems(indicator) : undefined,
statSummaries: [],
statDetails: []
}
}
export const buildPendingChecksquareHarmonicItems = (
indicator: SteadyDataView.SteadyIndicatorNode
): SteadyDataView.SteadyChecksquareItem[] => {
const indicatorCode = indicator.indicatorCode || indicator.id || indicator.treeKey || indicator.name
return CHECKSQUARE_HARMONIC_ORDERS.map(harmonicOrder => ({
itemKey: `pending|${indicatorCode}|${harmonicOrder}`,
indicatorCode,
indicatorName: indicator.name || indicatorCode,
harmonicOrder,
statSummaries: [],
statDetails: []
}))
}
export const buildPendingChecksquareResult = (
indicators: SteadyDataView.SteadyIndicatorNode[],
formState: { timeRange: string[] }
): SteadyDataView.SteadyChecksquareQueryResult => {
return {
lineId: '',
timeStart: formState.timeRange[0] || '',
timeEnd: formState.timeRange[1] || '',
items: indicators.map(buildPendingChecksquareItem)
}
}
const isResolvedChecksquareItem = (item: SteadyDataView.SteadyChecksquareItem) => {
return item.hasData !== undefined || item.expectedPointCount !== undefined || item.actualPointCount !== undefined
}
const normalizeChecksquareResultItemKey = (
item: SteadyDataView.SteadyChecksquareItem,
itemKey: string
): SteadyDataView.SteadyChecksquareItem => ({
...item,
itemKey
})
const isValidChecksquareHarmonicOrder = (value: number) => {
return Number.isInteger(value) && CHECKSQUARE_HARMONIC_ORDERS.includes(value)
}
const parseChecksquareHarmonicOrder = (value?: string | number | null) => {
const order = Number(value)
return isValidChecksquareHarmonicOrder(order) ? order : null
}
const parseChecksquareHarmonicOrderFromText = (value?: string | null) => {
if (!value) return null
const orderText = value.match(/(?:^|[^\d])([2-9]|[1-4]\d|50)(?:次|[^\d]|$)/)?.[1]
return parseChecksquareHarmonicOrder(orderText)
}
const resolveChecksquareHarmonicOrder = (item: SteadyDataView.SteadyChecksquareItem) => {
return (
parseChecksquareHarmonicOrder(item.harmonicOrder) ||
parseChecksquareHarmonicOrderFromText(item.itemKey) ||
parseChecksquareHarmonicOrderFromText(item.indicatorName) ||
parseChecksquareHarmonicOrderFromText(item.indicatorCode)
)
}
const sumNumber = (
items: SteadyDataView.SteadyChecksquareItem[],
getter: (item: SteadyDataView.SteadyChecksquareItem) => unknown
) => {
return items.reduce((total, item) => {
const value = Number(getter(item))
return Number.isFinite(value) ? total + value : total
}, 0)
}
const summarizeStatType = (
items: SteadyDataView.SteadyChecksquareItem[],
statType: SteadyDataView.SteadyTrendStatType
): SteadyDataView.SteadyChecksquareStatSummary => {
const summaries = items
.map(item => findStatSummary(item, statType))
.filter((summary): summary is SteadyDataView.SteadyChecksquareStatSummary => Boolean(summary))
const supportedSummaries = summaries.filter(summary => summary.supported !== false)
const expectedPointCount = supportedSummaries.reduce((total, summary) => total + (summary.expectedPointCount || 0), 0)
const actualPointCount = supportedSummaries.reduce((total, summary) => total + (summary.actualPointCount || 0), 0)
const missingPointCount = supportedSummaries.reduce((total, summary) => total + (summary.missingPointCount || 0), 0)
return {
statType,
supported: supportedSummaries.length > 0,
hasData: supportedSummaries.length > 0 && supportedSummaries.every(summary => summary.hasData === true),
expectedPointCount,
actualPointCount,
missingPointCount,
dataIntegrity: expectedPointCount ? actualPointCount / expectedPointCount : null,
dataIntegrityText: expectedPointCount ? undefined : '-'
}
}
export const buildHarmonicParentSummary = (
parentItem: SteadyDataView.SteadyChecksquareItem,
children: SteadyDataView.SteadyChecksquareItem[]
): SteadyDataView.SteadyChecksquareItem => {
if (!children.length || !children.every(item => isResolvedChecksquareItem(item))) {
return {
itemKey: parentItem.itemKey,
indicatorCode: parentItem.indicatorCode,
indicatorName: parentItem.indicatorName,
statSummaries: [],
statDetails: [],
children
}
}
const expectedPointCount = sumNumber(children, item => item.expectedPointCount)
const actualPointCount = sumNumber(children, item => item.actualPointCount)
const missingPointCount = sumNumber(children, item => item.missingPointCount)
const statSummaries = CHECKSQUARE_STAT_TYPES.map(statType => summarizeStatType(children, statType))
return {
...parentItem,
hasData: children.every(item => item.hasData === true),
expectedPointCount,
actualPointCount,
missingPointCount,
dataIntegrity: expectedPointCount ? actualPointCount / expectedPointCount : null,
dataIntegrityText: expectedPointCount ? undefined : '-',
statSummaries,
statDetails: [],
children
}
}
export const mergeChecksquareIndicatorResult = (
currentResult: SteadyDataView.SteadyChecksquareQueryResult | null,
indicator: SteadyDataView.SteadyIndicatorNode,
indicatorResult: SteadyDataView.SteadyChecksquareQueryResult
): SteadyDataView.SteadyChecksquareQueryResult => {
const pendingResult = currentResult || {
lineId: indicatorResult.lineId || '',
lineName: indicatorResult.lineName,
timeStart: indicatorResult.timeStart || '',
timeEnd: indicatorResult.timeEnd || '',
intervalMinutes: indicatorResult.intervalMinutes,
items: [buildPendingChecksquareItem(indicator)]
}
const indicatorCode = indicator.indicatorCode
const resultItems = indicatorResult.items || []
const shouldMergeHarmonicItems = isChecksquareHarmonicIndicator(indicator)
const normalItems = shouldMergeHarmonicItems
? resultItems.filter(item => !resolveChecksquareHarmonicOrder(item))
: resultItems
const harmonicItems = shouldMergeHarmonicItems
? resultItems.filter(item => resolveChecksquareHarmonicOrder(item))
: []
const currentItem = pendingResult.items.find(item => item.indicatorCode === indicatorCode)
const mergedItem = normalItems[0] || currentItem || buildPendingChecksquareItem(indicator)
const currentChildren = currentItem?.children || mergedItem.children || []
const mergedChildren = currentChildren.length
? currentChildren.map(child => {
const replacement = harmonicItems.find(item => resolveChecksquareHarmonicOrder(item) === child.harmonicOrder)
return replacement
? normalizeChecksquareResultItemKey(
{
...replacement,
harmonicOrder: child.harmonicOrder
},
child.itemKey
)
: child
})
: harmonicItems
const replacement = {
...mergedItem,
itemKey: currentItem?.itemKey || mergedItem.itemKey,
indicatorName: indicator.name || mergedItem.indicatorName || mergedItem.indicatorCode,
children: mergedChildren.length ? mergedChildren : undefined
} as SteadyDataView.SteadyChecksquareItem
const finalReplacement = replacement.children?.length ? buildHarmonicParentSummary(replacement, replacement.children) : replacement
return {
...pendingResult,
lineId: indicatorResult.lineId || pendingResult.lineId,
lineName: indicatorResult.lineName || pendingResult.lineName,
timeStart: indicatorResult.timeStart || pendingResult.timeStart,
timeEnd: indicatorResult.timeEnd || pendingResult.timeEnd,
intervalMinutes: indicatorResult.intervalMinutes ?? pendingResult.intervalMinutes,
items: pendingResult.items.map(item => (item.indicatorCode === indicatorCode ? finalReplacement : item))
}
}