feat(steady): 完善稳态数据视图功能
- 更新纵坐标刻度算法,优化小数趋势图范围显示 - 添加稳态趋势图全屏模式和共享工具组件 - 实现多图联动的鼠标悬停竖线同步功能 - 调整主线线宽分档策略,降低最大线宽限制 - 重构稳态趋势工具栏,优化谐波次数选择逻辑 - 添加周时间周期搜索支持和自定义时间范围选择 - 完善稳态数据表格和指示器浮动面板功能 - 优化稳态趋势图性能,添加LTB采样和动画控制 - 修复数据表格打开前的趋势数据验证问题 - 统一时间轴标签格式化和网格对齐处理
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
/* eslint-env node */
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const currentDir = path.dirname(fileURLToPath(import.meta.url))
|
||||
const rootDir = path.resolve(currentDir, '../../../..')
|
||||
|
||||
const files = {
|
||||
api: path.resolve(rootDir, 'api/steady/steadyDataView/index.ts'),
|
||||
apiTypes: path.resolve(rootDir, 'api/steady/steadyDataView/interface/index.ts'),
|
||||
page: path.resolve(rootDir, 'views/steady/checksquare/index.vue'),
|
||||
workbench: path.resolve(rootDir, 'views/steady/checksquare/components/ChecksquareWorkbench.vue'),
|
||||
summaryTable: path.resolve(rootDir, 'views/steady/checksquare/components/ChecksquareSummaryTable.vue'),
|
||||
detailPanel: path.resolve(rootDir, 'views/steady/checksquare/components/ChecksquareDetailPanel.vue'),
|
||||
payload: path.resolve(rootDir, 'views/steady/checksquare/utils/checksquarePayload.ts'),
|
||||
table: path.resolve(rootDir, 'views/steady/checksquare/utils/checksquareTable.ts')
|
||||
}
|
||||
|
||||
const read = file => (exists(file) ? fs.readFileSync(file, 'utf8') : '')
|
||||
const exists = file => fs.existsSync(file)
|
||||
|
||||
const checks = [
|
||||
['checksquare query api exists', () => /querySteadyChecksquare/.test(read(files.api))],
|
||||
[
|
||||
'checksquare api posts to expected endpoint',
|
||||
() => /\/steady\/data-view\/checksquare\/query/.test(read(files.api))
|
||||
],
|
||||
[
|
||||
'checksquare request type uses single lineId',
|
||||
() => /interface SteadyChecksquareQueryParams[\s\S]*lineId: string/.test(read(files.apiTypes))
|
||||
],
|
||||
[
|
||||
'checksquare request type supports per-order harmonic query only',
|
||||
() => {
|
||||
const typeBlock =
|
||||
read(files.apiTypes).match(/interface SteadyChecksquareQueryParams\s*\{[\s\S]*?\n {4}\}/)?.[0] || ''
|
||||
return /harmonicOrders\?: number\[\]/.test(typeBlock) && !/qualityFlag|statTypes|phases|lineIds/.test(typeBlock)
|
||||
}
|
||||
],
|
||||
['workbench component exists', () => exists(files.workbench)],
|
||||
['summary table component exists', () => exists(files.summaryTable)],
|
||||
['detail panel component exists', () => exists(files.detailPanel)],
|
||||
['payload utility exists', () => exists(files.payload)],
|
||||
['table utility exists', () => exists(files.table)],
|
||||
['page reuses steady ledger tree', () => /SteadyLedgerTree/.test(read(files.workbench))],
|
||||
['page reuses shared time period search', () => /TimePeriodSearch/.test(read(files.workbench))],
|
||||
['payload keeps shared time period unit state', () => /timeUnit:\s*TimePeriodUnit/.test(read(files.payload))],
|
||||
[
|
||||
'checksquare time search exposes day week month year custom units',
|
||||
() =>
|
||||
/CHECKSQUARE_TIME_PERIOD_UNITS\s*:\s*TimePeriodUnit\[\]\s*=\s*\['day',\s*'week',\s*'month',\s*'year',\s*'custom'\]/.test(
|
||||
read(files.workbench)
|
||||
) && /:visible-units="CHECKSQUARE_TIME_PERIOD_UNITS"/.test(read(files.workbench))
|
||||
],
|
||||
[
|
||||
'checksquare defaults to day range',
|
||||
() =>
|
||||
/timeRange:\s*buildTimePeriodRange\('day',\s*baseDate\)/.test(read(files.payload)) &&
|
||||
/timeUnit:\s*'day'/.test(read(files.payload))
|
||||
],
|
||||
['page no longer tracks floating indicator panel state', () => !/indicatorPanelCollapsed|indicator-panel-collapsed/.test(read(files.page))],
|
||||
[
|
||||
'query form uses tree select for steady indicators',
|
||||
() =>
|
||||
/<el-tree-select/.test(read(files.workbench)) &&
|
||||
/v-model="selectedIndicatorKeys"/.test(read(files.workbench)) &&
|
||||
/multiple/.test(read(files.workbench)) &&
|
||||
/show-checkbox/.test(read(files.workbench))
|
||||
],
|
||||
[
|
||||
'query form keeps steady indicator immediately after time selector',
|
||||
() => /class="toolbar-field toolbar-field--time"[\s\S]*class="toolbar-field indicator-form-item"/.test(read(files.workbench))
|
||||
],
|
||||
[
|
||||
'query form supports selecting all steady indicators',
|
||||
() =>
|
||||
/@click="handleSelectAllIndicators"/.test(read(files.workbench)) &&
|
||||
/collectAllIndicatorKeys/.test(read(files.workbench))
|
||||
],
|
||||
[
|
||||
'checksquare no longer renders floating indicator panel',
|
||||
() => !/SteadyIndicatorFloatingPanel|indicatorPanelCollapsedProxy|is-indicator-expanded/.test(read(files.workbench))
|
||||
],
|
||||
['summary table renders unsupported stats as dash', () => /formatStatMissingRate[\s\S]*'-'/.test(read(files.table))],
|
||||
[
|
||||
'summary table has localized AVG MAX MIN CP95 columns',
|
||||
() => /平均值缺失率[\s\S]*最大值缺失率[\s\S]*最小值缺失率[\s\S]*CP95缺失率/.test(read(files.summaryTable))
|
||||
],
|
||||
[
|
||||
'table utility localizes checksquare stat type names',
|
||||
() => /AVG:\s*'平均值'[\s\S]*MAX:\s*'最大值'[\s\S]*MIN:\s*'最小值'/.test(read(files.table))
|
||||
],
|
||||
['detail panel renders missing segments', () => /segments/.test(read(files.detailPanel))]
|
||||
,
|
||||
[
|
||||
'summary table title changed to check result',
|
||||
() => /指标校验结果/.test(read(files.summaryTable)) && !/指标校验总览/.test(read(files.summaryTable))
|
||||
],
|
||||
[
|
||||
'summary table shows monitor fallback and keeps meta 15px from title',
|
||||
() => {
|
||||
const summaryTable = read(files.summaryTable)
|
||||
return (
|
||||
/class="summary-meta"/.test(summaryTable) &&
|
||||
/result\.lineName\s*\|\|\s*result\.lineId\s*\|\|\s*'未返回监测点'/.test(summaryTable) &&
|
||||
/\.summary-meta\s*\{[\s\S]*margin-left:\s*15px/.test(summaryTable)
|
||||
)
|
||||
}
|
||||
],
|
||||
[
|
||||
'summary table uses tree rows for harmonic results',
|
||||
() =>
|
||||
/row-key="itemKey"/.test(read(files.summaryTable)) &&
|
||||
/tree-props/.test(read(files.summaryTable)) &&
|
||||
/children/.test(read(files.summaryTable))
|
||||
],
|
||||
[
|
||||
'summary table keeps harmonic tree rows collapsed by default',
|
||||
() => !/default-expand-all/.test(read(files.summaryTable))
|
||||
],
|
||||
[
|
||||
'summary table removes harmonic order column',
|
||||
() => !/<el-table-column[^>]*prop="harmonicOrder"/.test(read(files.summaryTable))
|
||||
],
|
||||
[
|
||||
'summary table uses balanced column widths for check result',
|
||||
() => {
|
||||
const summaryTable = read(files.summaryTable)
|
||||
const indicatorColumn = summaryTable.match(/<el-table-column[^>]*prop="indicatorName"[^>]*>/)?.[0] || ''
|
||||
const hasDataColumn = summaryTable.match(/<el-table-column[^>]*prop="hasData"[^>]*>/)?.[0] || ''
|
||||
const missingRateColumn = summaryTable.match(/<el-table-column[^>]*prop="missingRate"[^>]*>/)?.[0] || ''
|
||||
const avgColumn = summaryTable.match(/<el-table-column[^>]*label="平均值缺失率"[^>]*>/)?.[0] || ''
|
||||
const maxColumn = summaryTable.match(/<el-table-column[^>]*label="最大值缺失率"[^>]*>/)?.[0] || ''
|
||||
const minColumn = summaryTable.match(/<el-table-column[^>]*label="最小值缺失率"[^>]*>/)?.[0] || ''
|
||||
const cp95Column = summaryTable.match(/<el-table-column[^>]*label="CP95缺失率"[^>]*>/)?.[0] || ''
|
||||
const maxMissingColumn =
|
||||
summaryTable.match(/<el-table-column[^>]*prop="maxContinuousMissingMinutes"[^>]*>/)?.[0] || ''
|
||||
const operationColumn = summaryTable.match(/<el-table-column[^>]*label="操作"[^>]*>/)?.[0] || ''
|
||||
const stretchColumns = [
|
||||
hasDataColumn,
|
||||
missingRateColumn,
|
||||
avgColumn,
|
||||
maxColumn,
|
||||
minColumn,
|
||||
cp95Column,
|
||||
maxMissingColumn
|
||||
]
|
||||
|
||||
return (
|
||||
/min-width="208"/.test(indicatorColumn) &&
|
||||
/min-width="120"/.test(hasDataColumn) &&
|
||||
/min-width="130"/.test(missingRateColumn) &&
|
||||
/min-width="130"/.test(avgColumn) &&
|
||||
/min-width="130"/.test(maxColumn) &&
|
||||
/min-width="130"/.test(minColumn) &&
|
||||
/min-width="140"/.test(cp95Column) &&
|
||||
/min-width="150"/.test(maxMissingColumn) &&
|
||||
/width="96"/.test(operationColumn) &&
|
||||
stretchColumns.every(column => /min-width=/.test(column) && !/\swidth=/.test(column)) &&
|
||||
stretchColumns.every(column => /align="center"/.test(column)) &&
|
||||
/align="center"/.test(operationColumn) &&
|
||||
!/align=/.test(indicatorColumn)
|
||||
)
|
||||
}
|
||||
],
|
||||
[
|
||||
'workbench query card follows steady data view toolbar sizing',
|
||||
() => {
|
||||
const workbench = read(files.workbench)
|
||||
return (
|
||||
/\.query-card\s*\{[\s\S]*display:\s*grid[\s\S]*grid-template-columns:\s*minmax\(430px,\s*1\.35fr\)\s+minmax\(0,\s*1fr\)\s+auto[\s\S]*gap:\s*10px[\s\S]*align-items:\s*center[\s\S]*padding:\s*12px/.test(
|
||||
workbench
|
||||
) &&
|
||||
/\.checksquare-time\s*\{[\s\S]*flex:\s*1\s+1\s+0[\s\S]*min-width:\s*0/.test(workbench) &&
|
||||
/\.checksquare-time\s*:deep\(\.time-period-search__unit\)\s*\{[\s\S]*width:\s*88px[\s\S]*flex:\s*0\s+0\s+88px/.test(
|
||||
workbench
|
||||
) &&
|
||||
/\.checksquare-time\s*:deep\(\.time-period-search__picker\)\s*\{[\s\S]*width:\s*136px[\s\S]*flex:\s*0\s+0\s+136px/.test(
|
||||
workbench
|
||||
) &&
|
||||
/\.query-actions\s*\{[\s\S]*display:\s*flex[\s\S]*justify-content:\s*flex-end[\s\S]*gap:\s*8px/.test(workbench)
|
||||
)
|
||||
}
|
||||
],
|
||||
[
|
||||
'summary table exposes detail action',
|
||||
() => /详情/.test(read(files.summaryTable)) && /emit\('detail'/.test(read(files.summaryTable))
|
||||
],
|
||||
[
|
||||
'workbench shows detail in dialog instead of inline panel',
|
||||
() =>
|
||||
/<el-dialog/.test(read(files.workbench)) &&
|
||||
/ChecksquareDetailPanel/.test(read(files.workbench)) &&
|
||||
!/class="content-detail"/.test(read(files.workbench))
|
||||
],
|
||||
[
|
||||
'page builds pending rows from selected indicators',
|
||||
() => /buildPendingChecksquareResult/.test(read(files.page)) && /refreshPendingResult/.test(read(files.page))
|
||||
],
|
||||
[
|
||||
'page queries indicators sequentially',
|
||||
() => /for \(const indicator of queryIndicators\)/.test(read(files.page)) && /mergeChecksquareIndicatorResult/.test(read(files.page))
|
||||
],
|
||||
[
|
||||
'page queries harmonic orders with controlled concurrency',
|
||||
() =>
|
||||
/CHECKSQUARE_HARMONIC_QUERY_CONCURRENCY\s*=\s*6/.test(read(files.page)) &&
|
||||
/runChecksquareHarmonicQuery/.test(read(files.page)) &&
|
||||
/workers = Array\.from\(\{[\s\S]*length: Math\.min\(CHECKSQUARE_HARMONIC_QUERY_CONCURRENCY/.test(read(files.page)) &&
|
||||
/await Promise\.all\(workers\)/.test(read(files.page)) &&
|
||||
/const harmonicOrders = \[\.\.\.CHECKSQUARE_HARMONIC_ORDERS\]/.test(read(files.page)) &&
|
||||
/if \(orderIndex >= harmonicOrders\.length\) return/.test(read(files.page))
|
||||
],
|
||||
[
|
||||
'table pre-creates harmonic rows from second to fiftieth order',
|
||||
() =>
|
||||
/CHECKSQUARE_HARMONIC_ORDER_MIN\s*=\s*2/.test(read(files.table)) &&
|
||||
/CHECKSQUARE_HARMONIC_ORDER_MAX\s*=\s*50/.test(read(files.table)) &&
|
||||
/CHECKSQUARE_HARMONIC_ORDER_MAX - CHECKSQUARE_HARMONIC_ORDER_MIN \+ 1/.test(read(files.table)) &&
|
||||
/children: isChecksquareHarmonicIndicator\(indicator\)\s*\?\s*buildPendingChecksquareHarmonicItems/.test(
|
||||
read(files.table)
|
||||
)
|
||||
],
|
||||
[
|
||||
'table only merges indicators whose harmonic order range intersects second to fiftieth order',
|
||||
() => {
|
||||
const table = read(files.table)
|
||||
return (
|
||||
/CHECKSQUARE_HARMONIC_ORDER_MIN/.test(table) &&
|
||||
/CHECKSQUARE_HARMONIC_ORDER_MAX/.test(table) &&
|
||||
/hasChecksquareHarmonicOrderRange/.test(table) &&
|
||||
/isChecksquareHarmonicIndicator[\s\S]*hasChecksquareHarmonicOrderRange\(indicator\)/.test(table) &&
|
||||
/const shouldMergeHarmonicItems\s*=\s*isChecksquareHarmonicIndicator\(indicator\)/.test(table) &&
|
||||
/const normalItems\s*=\s*shouldMergeHarmonicItems[\s\S]*resultItems/.test(table) &&
|
||||
/const harmonicItems\s*=\s*shouldMergeHarmonicItems[\s\S]*\[\]/.test(table)
|
||||
)
|
||||
}
|
||||
],
|
||||
[
|
||||
'table summarizes harmonic parent after all orders finish',
|
||||
() =>
|
||||
/buildHarmonicParentSummary/.test(read(files.table)) &&
|
||||
/every\(item => isResolvedChecksquareItem\(item\)\)/.test(read(files.table)) &&
|
||||
/missingPointCount \/ expectedPointCount/.test(read(files.table))
|
||||
],
|
||||
[
|
||||
'table marks harmonic parent valid when every order child has data',
|
||||
() =>
|
||||
/hasData:\s*children\.every\(item => item\.hasData === true\),/.test(read(files.table)) &&
|
||||
!/children\.every\(item => \(item\.missingPointCount \|\| 0\) === 0\)/.test(read(files.table))
|
||||
],
|
||||
[
|
||||
'table keeps harmonic row keys stable while merging returned order results',
|
||||
() =>
|
||||
/normalizeChecksquareResultItemKey/.test(read(files.table)) &&
|
||||
/normalizeChecksquareResultItemKey\([\s\S]*child\.itemKey/.test(read(files.table)) &&
|
||||
/resolveChecksquareHarmonicOrder/.test(read(files.table)) &&
|
||||
/resolveChecksquareHarmonicOrder\(item\) === child\.harmonicOrder/.test(read(files.table))
|
||||
],
|
||||
[
|
||||
'table formats harmonic parent progress before final summary is ready',
|
||||
() =>
|
||||
/resolveChecksquareRowName[\s\S]*getHarmonicProgressText/.test(read(files.table)) &&
|
||||
/已完成 \$\{resolvedCount\}\/\$\{totalCount\}/.test(read(files.table))
|
||||
],
|
||||
[
|
||||
'page keeps selected checksquare detail synced after async row replacement',
|
||||
() =>
|
||||
/syncSelectedItemWithLatestResult/.test(read(files.page)) &&
|
||||
/selectedItem\.value\.itemKey/.test(read(files.page)) &&
|
||||
/mergeChecksquareIndicatorResult\(queryResult\.value/.test(read(files.page))
|
||||
]
|
||||
]
|
||||
|
||||
const failures = checks.filter(([, check]) => !check()).map(([name]) => name)
|
||||
|
||||
if (failures.length) {
|
||||
console.error('checksquare feature contract failed:')
|
||||
for (const failure of failures) {
|
||||
console.error(`- ${failure}`)
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('checksquare feature contract passed')
|
||||
Reference in New Issue
Block a user