From 8622f25048814317a2237aee41a7772d7258e48f Mon Sep 17 00:00:00 2001 From: yexb <553699424@qq.com> Date: Thu, 11 Jun 2026 10:53:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(steady):=20=E5=AE=9E=E7=8E=B0=E7=A8=B3?= =?UTF-8?q?=E6=80=81=E6=A0=A1=E9=AA=8C=E4=BB=BB=E5=8A=A1=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加influxdb配置支持和资源文件打包 - 实现校验任务表格组件和相关工具函数 - 重构校验工作台为任务创建对话框模式 - 实现校验详情面板支持多种异常类型展示 - 更新校验概览表格显示任务基本信息 - 优化校验查询参数和API接口定义 - 实现搜索表单组件化和过滤功能增强 --- .../influxdb-1.7.0/start-influxdb.bat | 5 +- .../java/application.yml.template | 7 + cmd/builder.json | 5 + electron/preload/lifecycle.js | 133 ++++-- .../src/api/steady/steadyDataView/index.ts | 20 +- .../steady/steadyDataView/interface/index.ts | 101 ++++- frontend/src/components/Grid/index.vue | 6 +- .../SearchForm/components/SearchFormItem.vue | 10 +- frontend/src/components/SearchForm/index.vue | 8 +- .../components/ChecksquareDetailPanel.vue | 209 ++++++++- .../components/ChecksquareSummaryTable.vue | 14 + .../components/ChecksquareTaskTable.vue | 312 +++++++++++++ .../components/ChecksquareWorkbench.vue | 65 +-- .../check-checksquare-feature-contract.mjs | 382 ++++++++-------- .../src/views/steady/checksquare/index.vue | 229 +++++----- .../checksquare/utils/checksquarePayload.ts | 15 +- .../checksquare/utils/checksquareTable.ts | 8 +- .../checksquare/utils/checksquareTaskTable.ts | 46 ++ scripts/config-generator.js | 30 +- .../check-influxdb-startup-contract.mjs | 94 ++++ scripts/influxdb-process-manager.js | 410 ++++++++++++++++++ scripts/java-runner.js | 6 +- scripts/mysql-process-manager.js | 7 +- scripts/path-utils.js | 25 ++ scripts/startup-manager.js | 14 +- 25 files changed, 1675 insertions(+), 486 deletions(-) create mode 100644 frontend/src/views/steady/checksquare/components/ChecksquareTaskTable.vue create mode 100644 frontend/src/views/steady/checksquare/utils/checksquareTaskTable.ts create mode 100644 scripts/contracts/check-influxdb-startup-contract.mjs create mode 100644 scripts/influxdb-process-manager.js diff --git a/build/extraResources/influxdb-1.7.0/start-influxdb.bat b/build/extraResources/influxdb-1.7.0/start-influxdb.bat index dfedd5d..a6e1acd 100644 --- a/build/extraResources/influxdb-1.7.0/start-influxdb.bat +++ b/build/extraResources/influxdb-1.7.0/start-influxdb.bat @@ -1,4 +1,5 @@ @echo off cd /d "%~dp0" -influxd.exe -config "%~dp0influxdb.conf" -pause +set "CONFIG_FILE=%~1" +if "%CONFIG_FILE%"=="" set "CONFIG_FILE=%~dp0influxdb.conf" +influxd.exe -config "%CONFIG_FILE%" diff --git a/build/extraResources/java/application.yml.template b/build/extraResources/java/application.yml.template index bf65a50..7fec411 100644 --- a/build/extraResources/java/application.yml.template +++ b/build/extraResources/java/application.yml.template @@ -95,6 +95,13 @@ qr: db: type: mysql +steady: + influxdb: + url: http://127.0.0.1:{{INFLUXDB_PORT}} + database: pqsbase + username: + password: + # 比对录波需要的配置,晚点再做优化 # 系统配置 diff --git a/cmd/builder.json b/cmd/builder.json index 80a0c73..d5388f4 100644 --- a/cmd/builder.json +++ b/cmd/builder.json @@ -48,6 +48,11 @@ "to": "mysql", "filter": ["**/*"] }, + { + "from": "build/extraResources/influxdb-1.7.0", + "to": "influxdb-1.7.0", + "filter": ["**/*"] + }, { "from": "build/extraResources/jre", "to": "jre", diff --git a/electron/preload/lifecycle.js b/electron/preload/lifecycle.js index 21efbc9..9046a50 100644 --- a/electron/preload/lifecycle.js +++ b/electron/preload/lifecycle.js @@ -11,45 +11,40 @@ const { app } = require('electron'); function getScriptsPath(scriptName) { const fs = require('fs'); - // 判断是否是打包后的环境 - // 只要 process.resourcesPath 存在,就是打包后的环境(无论在哪个目录) - const isProd = !!process.resourcesPath; - - if (isProd) { - // 生产环境(打包后):从 resources 目录 + if (process.resourcesPath) { const prodPath = path.join(process.resourcesPath, 'scripts', scriptName); + const prodFile = `${prodPath}.js`; + if (fs.existsSync(prodFile)) { console.log(`[getScriptsPath] Production mode, using: ${prodPath}`); return prodPath; - } else { - // 开发环境:从项目根目录 - // __dirname 是 electron/preload 或 public/electron/preload - // 需要找到项目根目录 - let currentDir = __dirname; - let scriptsPath = null; - - // 向上查找,直到找到 scripts 目录 - for (let i = 0; i < 5; i++) { - currentDir = path.join(currentDir, '..'); - const testPath = path.join(currentDir, 'scripts', scriptName + '.js'); - if (fs.existsSync(testPath)) { - scriptsPath = path.join(currentDir, 'scripts', scriptName); - console.log(`[getScriptsPath] Development mode, found at: ${scriptsPath}`); - return scriptsPath; - } } - - // 如果找不到,返回一个默认路径 - console.warn(`[getScriptsPath] Cannot find ${scriptName}, returning default path`); - return path.join(__dirname, '../../../scripts', scriptName); } + + // 开发环境:从项目根目录向上查找 scripts 目录,避免误用 Electron 安装目录 resources。 + let currentDir = __dirname; + let scriptsPath = null; + + for (let i = 0; i < 5; i++) { + currentDir = path.join(currentDir, '..'); + const testPath = path.join(currentDir, 'scripts', scriptName + '.js'); + if (fs.existsSync(testPath)) { + scriptsPath = path.join(currentDir, 'scripts', scriptName); + console.log(`[getScriptsPath] Development mode, found at: ${scriptsPath}`); + return scriptsPath; + } + } + + console.warn(`[getScriptsPath] Cannot find ${scriptName}, returning default path`); + return path.join(__dirname, '../../../scripts', scriptName); } // 延迟加载 scripts -let MySQLProcessManager, JavaRunner, ConfigGenerator, PortChecker, StartupManager, LogWindowManager; +let MySQLProcessManager, InfluxDBProcessManager, JavaRunner, ConfigGenerator, PortChecker, StartupManager, LogWindowManager; function loadScripts() { if (!MySQLProcessManager) { MySQLProcessManager = require(getScriptsPath('mysql-process-manager')); + InfluxDBProcessManager = require(getScriptsPath('influxdb-process-manager')); JavaRunner = require(getScriptsPath('java-runner')); ConfigGenerator = require(getScriptsPath('config-generator')); PortChecker = require(getScriptsPath('port-checker')); @@ -61,10 +56,12 @@ function loadScripts() { class Lifecycle { constructor() { this.mysqlProcessManager = null; + this.influxdbProcessManager = null; this.javaRunner = null; this.startupManager = null; this.logWindowManager = null; this.mysqlPort = null; + this.influxdbPort = null; this.javaPort = null; this.autoRefreshTimer = null; } @@ -124,9 +121,38 @@ class Lifecycle { } } + // InfluxDB 用于稳态趋势、补数等时序数据能力,必须早于后端服务启动。 + this.logWindowManager.addLog('system', '▶ 步骤7: 启动 InfluxDB 进程管理器...'); + this.startupManager.updateProgress('check-influxdb-port', { mysqlPort: this.mysqlPort }); + + this.influxdbProcessManager = new InfluxDBProcessManager(this.logWindowManager); + this.logWindowManager.addLog('system', '正在检查 InfluxDB 进程状态...'); + + try { + this.influxdbPort = await this.influxdbProcessManager.ensureServiceRunning( + PortChecker.findAvailablePort.bind(PortChecker), + PortChecker.waitForPort.bind(PortChecker) + ); + + logger.info(`[lifecycle] InfluxDB process running on port: ${this.influxdbPort}`); + this.logWindowManager.addLog('success', `✅ InfluxDB 服务已就绪,端口: ${this.influxdbPort}`); + this.startupManager.updateProgress('wait-influxdb', { + mysqlPort: this.mysqlPort, + influxdbPort: this.influxdbPort + }); + await this.sleep(500); + } catch (error) { + logger.error('[lifecycle] InfluxDB error:', error); + this.logWindowManager.addLog('error', `InfluxDB 错误: ${error.message}`); + throw error; + } + // 步骤5: 检测 Java 端口 - this.logWindowManager.addLog('system', '▶ 步骤7: 检测可用的 Java 端口(从18093开始)...'); - this.startupManager.updateProgress('check-java-port', { mysqlPort: this.mysqlPort }); + this.logWindowManager.addLog('system', '▶ 步骤8: 检测可用的 Java 端口(从18093开始)...'); + this.startupManager.updateProgress('check-java-port', { + mysqlPort: this.mysqlPort, + influxdbPort: this.influxdbPort + }); this.javaPort = await PortChecker.findAvailablePort(18093, 100); @@ -136,7 +162,7 @@ class Lifecycle { } // 步骤5.5: 检测 WebSocket 端口 - this.logWindowManager.addLog('system', '▶ 步骤8: 检测可用的 WebSocket 端口(从7778开始)...'); + this.logWindowManager.addLog('system', '▶ 步骤9: 检测可用的 WebSocket 端口(从7778开始)...'); this.websocketPort = await PortChecker.findAvailablePort(7778, 100); @@ -161,14 +187,16 @@ class Lifecycle { logger.info(`[lifecycle] WebSocket will use port: ${this.websocketPort}`); this.startupManager.updateProgress('check-java-port', { mysqlPort: this.mysqlPort, + influxdbPort: this.influxdbPort, javaPort: this.javaPort }); await this.sleep(500); // 步骤6: 生成配置文件 - this.logWindowManager.addLog('system', '▶ 步骤9: 生成 Spring Boot 配置文件...'); + this.logWindowManager.addLog('system', '▶ 步骤10: 生成 Spring Boot 配置文件...'); this.startupManager.updateProgress('generate-config', { mysqlPort: this.mysqlPort, + influxdbPort: this.influxdbPort, javaPort: this.javaPort, websocketPort: this.websocketPort }); @@ -176,6 +204,7 @@ class Lifecycle { const configGenerator = new ConfigGenerator(); const { configPath, dataPath } = await configGenerator.generateConfig({ mysqlPort: this.mysqlPort, + influxdbPort: this.influxdbPort, javaPort: this.javaPort, websocketPort: this.websocketPort, mysqlPassword: 'njcnpqs' @@ -194,9 +223,10 @@ class Lifecycle { await this.sleep(500); // 步骤7: 启动 Spring Boot - this.logWindowManager.addLog('system', '▶ 步骤10: 启动 Spring Boot 应用...'); + this.logWindowManager.addLog('system', '▶ 步骤11: 启动 Spring Boot 应用...'); this.startupManager.updateProgress('start-java', { mysqlPort: this.mysqlPort, + influxdbPort: this.influxdbPort, javaPort: this.javaPort, dataPath: dataPath }); @@ -204,9 +234,10 @@ class Lifecycle { await this.startSpringBoot(configPath, dataPath); // 步骤8: 等待 Spring Boot 就绪 - this.logWindowManager.addLog('system', '▶ 步骤11: 等待 Spring Boot 就绪(最多60秒)...'); + this.logWindowManager.addLog('system', '▶ 步骤12: 等待 Spring Boot 就绪(最多60秒)...'); this.startupManager.updateProgress('wait-java', { mysqlPort: this.mysqlPort, + influxdbPort: this.influxdbPort, javaPort: this.javaPort, dataPath: dataPath }); @@ -224,9 +255,10 @@ class Lifecycle { await this.sleep(1000); // 步骤9: 完成 - this.logWindowManager.addLog('system', '▶ 步骤12: 启动完成,准备显示主窗口...'); + this.logWindowManager.addLog('system', '▶ 步骤13: 启动完成,准备显示主窗口...'); this.startupManager.updateProgress('done', { mysqlPort: this.mysqlPort, + influxdbPort: this.influxdbPort, javaPort: this.javaPort, dataPath: dataPath }); @@ -235,6 +267,7 @@ class Lifecycle { this.logWindowManager.addLog('system', '='.repeat(60)); this.logWindowManager.addLog('success', '✓ 电能质量运维工具 启动完成!所有服务正常运行'); this.logWindowManager.addLog('system', `✓ MySQL 端口: ${this.mysqlPort}`); + this.logWindowManager.addLog('system', `✓ InfluxDB 端口: ${this.influxdbPort}`); this.logWindowManager.addLog('system', `✓ Java 端口: ${this.javaPort}`); this.logWindowManager.addLog('system', `✓ WebSocket 端口: ${this.websocketPort}`); this.logWindowManager.addLog('system', `✓ 数据目录: ${dataPath}`); @@ -375,7 +408,26 @@ class Lifecycle { } } - // 停止 MySQL 进程(进程模式) + // 停止数据库进程(进程模式) + // InfluxDB 只清理当前应用记录的 PID,避免影响用户本机其他 InfluxDB 实例。 + if (this.influxdbProcessManager) { + try { + logger.info('[lifecycle] Stopping InfluxDB process...'); + if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) { + this.logWindowManager.addLog('system', '正在停止 InfluxDB...'); + } + + await this.influxdbProcessManager.stopInfluxDBProcess(); + + logger.info('[lifecycle] InfluxDB process stopped'); + if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) { + this.logWindowManager.addLog('success', 'InfluxDB 已停止'); + } + } catch (error) { + logger.error('[lifecycle] Failed to stop InfluxDB:', error); + } + } + if (this.mysqlProcessManager) { try { logger.info('[lifecycle] Stopping MySQL process...'); @@ -427,12 +479,11 @@ class Lifecycle { // 启动Java应用 this.javaRunner = new JavaRunner(); - // 开发环境:build/extraResources/java/entrance.jar - // 打包后:resources/extraResources/java/entrance.jar - const isDev = !process.resourcesPath; - const jarPath = isDev - ? path.join(__dirname, '..', 'build', 'extraResources', 'java', 'entrance.jar') - : path.join(process.resourcesPath, 'extraResources', 'java', 'entrance.jar'); + const { resolvePackagedRuntime } = require(getScriptsPath('path-utils')); + const runtime = resolvePackagedRuntime(); + const jarPath = runtime.isPackaged + ? path.join(runtime.resourcesPath, 'extraResources', 'java', 'entrance.jar') + : path.join(runtime.baseDir, 'build', 'extraResources', 'java', 'entrance.jar'); const logPath = path.join(dataPath, 'logs'); diff --git a/frontend/src/api/steady/steadyDataView/index.ts b/frontend/src/api/steady/steadyDataView/index.ts index 096e937..8990c71 100644 --- a/frontend/src/api/steady/steadyDataView/index.ts +++ b/frontend/src/api/steady/steadyDataView/index.ts @@ -17,8 +17,24 @@ export const querySteadyTrendDay = (params: SteadyDataView.SteadyTrendQueryParam return http.post('/steady/data-view/trend/day', params, { loading: false }) } -export const querySteadyChecksquare = (params: SteadyDataView.SteadyChecksquareQueryParams) => { - return http.post('/steady/data-view/checksquare/query', params, { +export const querySteadyChecksquareTasks = (params: SteadyDataView.SteadyChecksquareTaskQueryParams) => { + return http.post>('/steady/data-view/checksquare/query', params, { + loading: false + }) +} + +export const createSteadyChecksquareTask = (params: SteadyDataView.SteadyChecksquareCreateParams) => { + return http.post('/steady/data-view/checksquare/create', params, { + loading: false + }) +} + +export const getSteadyChecksquareDetail = (taskId: string) => { + return http.get('/steady/data-view/checksquare/detail', { taskId }, { loading: false }) +} + +export const getSteadyChecksquareItemDetail = (params: SteadyDataView.SteadyChecksquareItemDetailParams) => { + return http.get('/steady/data-view/checksquare/item-detail', params, { loading: false }) } diff --git a/frontend/src/api/steady/steadyDataView/interface/index.ts b/frontend/src/api/steady/steadyDataView/interface/index.ts index b80e896..2042fe2 100644 --- a/frontend/src/api/steady/steadyDataView/interface/index.ts +++ b/frontend/src/api/steady/steadyDataView/interface/index.ts @@ -1,4 +1,12 @@ export namespace SteadyDataView { + export interface PageResult { + records: T[] + current: number + size: number + total: number + pages?: number + } + export interface SteadyLedgerNode { id: string parentId?: string @@ -77,18 +85,56 @@ export namespace SteadyDataView { series: SteadyTrendSeries[] } - export interface SteadyChecksquareQueryParams { + export interface SteadyChecksquareTaskQueryParams { + pageNum?: number + pageSize?: number + lineId?: string + lineName?: string + indicatorCode?: string + timeStart?: string + timeEnd?: string + hasAbnormal?: boolean + } + + export interface SteadyChecksquareCreateParams { lineId: string indicatorCodes: string[] timeStart: string timeEnd: string - harmonicOrders?: number[] + } + + export interface SteadyChecksquareTask { + taskId: string + taskNo?: string + lineId?: string + lineName?: string + timeStart?: string + timeEnd?: string + intervalMinutes?: number + taskStatus?: 'SUCCESS' | string + itemCount?: number + abnormalItemCount?: number + maxMissingRate?: number | null + createTime?: string + } + + export interface SteadyChecksquareCreateResult { + taskId: string + taskNo?: string + lineId?: string + lineName?: string + timeStart?: string + timeEnd?: string + intervalMinutes?: number + itemCount?: number + abnormalItemCount?: number } export interface SteadyChecksquareSegment { startTime: string endTime: string status: 'NORMAL' | 'MISSING' | string + harmonicOrder?: number | null missingPointCount?: number durationMinutes?: number } @@ -112,6 +158,7 @@ export namespace SteadyDataView { } export interface SteadyChecksquareItem { + itemId?: string itemKey: string indicatorCode: string indicatorName?: string @@ -123,12 +170,18 @@ export namespace SteadyDataView { missingRate?: number | null missingRateText?: string | null maxContinuousMissingMinutes?: number + abnormal?: boolean + abnormalPointCount?: number + harmonicParityAbnormal?: boolean + harmonicParityAbnormalPointCount?: number statSummaries: SteadyChecksquareStatSummary[] statDetails: SteadyChecksquareStatDetail[] children?: SteadyChecksquareItem[] } export interface SteadyChecksquareQueryResult { + taskId?: string + taskNo?: string lineId: string lineName?: string timeStart: string @@ -136,4 +189,48 @@ export namespace SteadyDataView { intervalMinutes?: number items: SteadyChecksquareItem[] } + + export type SteadyChecksquareDetailType = 'SEGMENT' | 'VALUE_ORDER' | 'HARMONIC_PARITY' + + export interface SteadyChecksquareItemDetailParams { + itemId: string + detailType: SteadyChecksquareDetailType + statType?: SteadyTrendStatType + pageNum?: number + pageSize?: number + } + + export interface SteadyChecksquareValueOrderDetail { + time: string + phase?: string + harmonicOrder?: number | null + maxValue?: number | null + minValue?: number | null + avgValue?: number | null + cp95Value?: number | null + } + + export interface SteadyChecksquareHarmonicParityDetail { + time: string + phase?: string + statType?: SteadyTrendStatType + evenHarmonicOrder?: number + evenValue?: number | null + oddHarmonicOrders?: number[] + oddValues?: number[] + oddMedianValue?: number | null + thresholdMultiplier?: number | null + } + + export interface SteadyChecksquareItemDetail { + itemId: string + detailType: SteadyChecksquareDetailType + statType?: SteadyTrendStatType | null + pageNum?: number | null + pageSize?: number | null + total?: number | null + segments: SteadyChecksquareSegment[] + valueOrderDetails: SteadyChecksquareValueOrderDetail[] + harmonicParityDetails: SteadyChecksquareHarmonicParityDetail[] + } } diff --git a/frontend/src/components/Grid/index.vue b/frontend/src/components/Grid/index.vue index 2b17ad9..b1c654c 100644 --- a/frontend/src/components/Grid/index.vue +++ b/frontend/src/components/Grid/index.vue @@ -5,6 +5,7 @@ diff --git a/frontend/src/views/steady/checksquare/components/ChecksquareWorkbench.vue b/frontend/src/views/steady/checksquare/components/ChecksquareWorkbench.vue index 5ccc0aa..4d37b40 100644 --- a/frontend/src/views/steady/checksquare/components/ChecksquareWorkbench.vue +++ b/frontend/src/views/steady/checksquare/components/ChecksquareWorkbench.vue @@ -63,42 +63,25 @@
- - 查询 + + 新增校验任务 重置
- -
- -
- - - -