25 Commits

Author SHA1 Message Date
caozehui
72838462ad 微调 2026-04-22 10:02:21 +08:00
caozehui
327addf625 微调 2026-04-22 09:58:03 +08:00
caozehui
7fd3b6fdff 归档 2026-04-13 16:31:45 +08:00
4bfab6518e 微调 2026-04-08 20:33:01 +08:00
caozehui
4655259153 归档 2026-04-07 13:28:04 +08:00
caozehui
cdb23726f8 归档 2026-04-07 13:22:57 +08:00
caozehui
68a1c9d28d 归档 2026-04-07 11:25:33 +08:00
caozehui
30e815c027 归档 2026-04-07 11:14:34 +08:00
caozehui
ce10f91b5b 归档 2026-04-07 11:07:18 +08:00
65ac4b1dde 修复C端打包后的程序图标不显示logo的问题 2026-04-02 21:00:12 +08:00
926b85bf8d C端打包修复不能在中文路径下启动的问题 2026-04-02 20:51:19 +08:00
caozehui
ad02fac4ff 比对模式根据配置文件动态展示原始数据、历史趋势图 2026-02-02 10:14:41 +08:00
caozehui
e4de4fe43c 微调 2026-01-27 11:06:31 +08:00
caozehui
bc7d6b24b7 微调 2026-01-23 11:27:42 +08:00
caozehui
3de7461960 微调 2026-01-23 11:24:47 +08:00
caozehui
bff94f4521 微调 2026-01-23 11:20:03 +08:00
caozehui
e63a3dfa92 数据库变更 2026-01-22 15:42:02 +08:00
caozehui
7efb738d54 微调 2026-01-22 15:25:30 +08:00
caozehui
84763eb414 微调 2026-01-22 11:26:42 +08:00
caozehui
820a6bb233 系数调整 2026-01-21 13:59:57 +08:00
caozehui
31e44cfc69 比对系数下发全局配置 2026-01-19 14:22:24 +08:00
caozehui
fb9460e307 Merge remote-tracking branch 'origin/master' 2026-01-16 10:46:11 +08:00
caozehui
99a8c97b17 比对系数下发全局配置 2026-01-16 10:46:00 +08:00
caozehui
c30569f5e1 比对系数下发全局配置 2026-01-16 10:44:41 +08:00
caozehui
cdc19e1db9 更新后端jar包 2026-01-06 10:29:29 +08:00
106 changed files with 1389 additions and 1182 deletions

View File

@@ -1,18 +0,0 @@
@echo off
chcp 65001 >nul
REM 获取当前批处理文件所在目录
cd /d "%~dp0"
REM 检查是否有管理员权限
net session >nul 2>&1
if %errorlevel% == 0 (
REM 已有管理员权限,直接启动
start "" "NPQS9100.exe"
exit
) else (
REM 没有管理员权限,使用 PowerShell 以管理员身份启动(隐藏窗口)
powershell -WindowStyle Hidden -Command "Start-Process '%~dp0NPQS9100.exe' -Verb RunAs"
exit
)

View File

@@ -4,27 +4,34 @@ NPQS-9100 升级与回滚说明
一、升级步骤(超简单!) 一、升级步骤(超简单!)
---------------------------------------- ----------------------------------------
1. 双击 upgrade.bat首次会自动创建 upgrade 文件夹) 1. 先使用 Navicat 或其他工具手动导出数据库 SQL 备份
2. 将升级文件放入 upgrade/ 目录: 2. 双击 upgrade.bat首次会自动创建 upgrade 文件夹)
- app.asar (前端升级包 - 文件) 3. 将升级文件放入 upgrade/ 目录:
- app.asar.unpacked\ (前端升级包 - 文件夹 - app.asar + app.asar.unpacked\ (前端升级包,必须成套放入
- entrance.jar (后端升级包) - entrance.jar (后端升级包)
3. 再次双击 upgrade.bat 开始升级 4. 再次双击 upgrade.bat 开始升级
4. 等待完成后重启应用 5. 等待完成后重启应用
补充说明:
- 可以只升级后端
- 可以只升级前端,但前端升级时必须同时提供:
app.asar
app.asar.unpacked\
- 数据库不由 upgrade.bat 自动备份,请务必提前手动导出 SQL
二、回滚步骤 二、回滚步骤
---------------------------------------- ----------------------------------------
如果升级后出现问题: 如果升级后出现问题:
1. 双击 rollback.bat 1. 双击 rollback.bat(仅回滚前后端程序文件)
2. 选择是否回滚数据库(谨慎!) 2. 等待完成后重启应用
3. 等待完成后重启应用 3. 如需恢复数据库,请手动执行升级前导出的 SQL
三、重要提示 三、重要提示
---------------------------------------- ----------------------------------------
✓ 升级前会自动备份到 backup/ 目录 ✓ 升级前会自动备份前后端程序文件到 backup/ 目录
✓ 数据库会自动备份到 mysql/data_backup/
✓ 升级日志保存在 logs/upgrade.log ✓ 升级日志保存在 logs/upgrade.log
✓ 多次升级时backup/ 保存最后一次升级前的版本 ✓ 多次升级时backup/ 保存最后一次升级前的版本
✓ 数据库备份与恢复由人工处理,不再由脚本自动执行
四、紧急情况 四、紧急情况
---------------------------------------- ----------------------------------------
@@ -32,15 +39,15 @@ NPQS-9100 升级与回滚说明
【恢复前端】 【恢复前端】
copy /Y backup\app.asar resources\app.asar copy /Y backup\app.asar resources\app.asar
rmdir /s /q resources\app.asar.unpacked
xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y
【恢复后端】 【恢复后端】
copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar
【恢复数据库】(慎用!) 【恢复数据库】
xcopy mysql\data_backup mysql\data\ /E /I /Y 请使用 Navicat 或其他工具执行升级前导出的 SQL 备份
======================================== ========================================
详细文档请参考doc/绿色包升级指南.md 如需完整技术文档,请联系交付方提供开发文档。
======================================== ========================================

View File

@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="*"
name="NPQS9100"
type="win32"
/>
<description>NPQS-9100自动检测平台</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
</assembly>

View File

@@ -52,7 +52,8 @@ echo.
echo ======================================== echo ========================================
echo 打包完成! echo 打包完成!
echo 输出目录: out\win-unpacked\ echo 最终交付目录: out\NPQS-9100\
echo 原始输出目录: out\win-unpacked\ (已自动重命名)
echo ======================================== echo ========================================
echo. echo.
pause pause

View File

@@ -63,7 +63,8 @@ echo.
echo ======================================== echo ========================================
echo ✓ 打包完成! echo ✓ 打包完成!
echo 输出目录: out\win-unpacked\ echo 最终交付目录: out\NPQS-9100\
echo 原始输出目录: out\win-unpacked\ (已自动重命名)
echo ======================================== echo ========================================
echo. echo.
pause pause

View File

@@ -53,13 +53,13 @@ if exist "%LOGDIR%" (
) )
echo. echo.
echo [4] 检查 MySQL 服务... echo [4] 检查当前运行模式...
sc query mysql9100 >nul 2>&1 echo 当前版本为绿色包进程模式,不使用 MySQL Windows 服务
if %errorlevel% equ 0 ( if exist "mysql\my.ini" (
echo MySQL 服务存在 echo 检测到 MySQL 配置文件:
sc query mysql9100 | findstr "STATE" findstr /i "^port=" "mysql\my.ini"
) else ( ) else (
echo MySQL 服务不存在 echo 尚未检测到 mysql\my.ini可能应用还未完整启动
) )
echo. echo.

View File

@@ -115,3 +115,4 @@ activate:
private-key: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcUyYhVqczGxblL+o/xZzF/8nf+LjrfUE/dS1aRHM7uMDD0cgCArhjtfneFePrMxt+Z7W8yNBzSarub8qsfhaVNikV7Es7oaeTygfjQXTi2n4AFkir3fM07J08RpWhl5M8f8uWTCuvFUYAw00gq55typqmnbkmJa2VIUy/iQf+cMCP7abz4/jNhUzUR3qA7TV4oMRgTdIEDUp63YF8dOC+JH8XxYrCVeHXV6fLCwmesdMzl0lB2VTEKMfLbXhOmF5g7P9y/16VCcN8UBuZlbyYfn+GAxJOSbeHi5HshOKfoSuD7Jz+3WQZpNavOWjIFExKIU38/CvnJCOP7XBCqpSTAgMBAAECggEAYeWokWRE3TpvwiOZnUpR/aVMdVi75a3ROL5XIpqPV61B+t/bU3cEpl0GF9C5pUeiRi0IoStZb3mI9D1KPW/REKyUWkhabQO1gFYbTnRlkNOn6MILzKX4cwJjDaZeeo4EBPU7N+qHyOOXrU6hdH5FfxhMdV983ajm5eeuupxER1C2kAcIklTeVpTX6EKOgZb5LBp5ssOVm2P42pOauvcRozRcvZmqnErXmukv0H4l3EVNt4rHpTn9riHUC63e8JfiYzVaF6zuNUxv6nHEft0/SRMw11XSTnNfDzcKqgjz6ksFBS/6eQQYKESk+ONC53HUuYHFAknkwsPupDCT2W8FIQKBgQDLHT/xCU3nxGr4vFKBDNaO2D5oK20ECbBO4oDvLWWmQG7f+6TsMy8PgVdMnoL4RfqGlwFAKEpS6KVFHnBVqnNEhcdy9uCI7x7Xx8UnyUtxj1EDTm76uta9Ki9OrlqB6tImDM9+Ya3vGktW37ht4WOx2OsJRhG1dbf6RLwFlH7DWwKBgQDFBxvi5I1BR6hg6Tj7xd2SqOT2Y+BED3xuSYENhWbmMhLJDResaB7mjztbxlYaY2mOE0holWm2uDmVFFhMh4jYXik4hYH8nmDzq9mDpZCZ9pyjYqnAP8THoAa8EbgrUWB8A6BPH4iL3KbMnBfBKY0pIr2xrvnjQjNBAgta7KDRKQKBgCe6oe4wxrdF2TKsC2tIqpMoQxS3Icy/ZGgZr+SYuaBKTCWtoDW/UT40K3JGMxIDBhzbXphBCUCsVt9tM8Xd4EwP6tJW7dZ7B0pnve2pVwNwaAVAiz6p2yUHIle+jN+Koe5lZRSwYIg7WW81tWpwwsJfzqFyvjYDP6hJV4mz4ROvAoGAaRcdnKvjXApomShMqJ4lTPChD3q+SA8qg3jZSOj6tZXHx00gb2kp8jg7pPvpOTIFPy6x1Ha9aCRjMk0ju84fA6lVuzwa1S907wOehUVuF3Eeo1cgy9Y3k3KbpPyeixxgpkUY4JslLdSHc2NemD0dee951qhJyRmqVOZOQDUuoeECgYEAqBw2cAFk3vM97WY06TSldGA8ajVHx3BYRjj+zl62NTQthy8fw3tqxb3c5e8toOmZWKjZvDhg2TRLhsDDQWEYg3LZG87REqVIjgEPcpjNLidjygGX8n3JF2o0O5I/EMvl0s/+LVQONfduOBvhwDqr8QNisbLsyneiAq7umewMolo=" private-key: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcUyYhVqczGxblL+o/xZzF/8nf+LjrfUE/dS1aRHM7uMDD0cgCArhjtfneFePrMxt+Z7W8yNBzSarub8qsfhaVNikV7Es7oaeTygfjQXTi2n4AFkir3fM07J08RpWhl5M8f8uWTCuvFUYAw00gq55typqmnbkmJa2VIUy/iQf+cMCP7abz4/jNhUzUR3qA7TV4oMRgTdIEDUp63YF8dOC+JH8XxYrCVeHXV6fLCwmesdMzl0lB2VTEKMfLbXhOmF5g7P9y/16VCcN8UBuZlbyYfn+GAxJOSbeHi5HshOKfoSuD7Jz+3WQZpNavOWjIFExKIU38/CvnJCOP7XBCqpSTAgMBAAECggEAYeWokWRE3TpvwiOZnUpR/aVMdVi75a3ROL5XIpqPV61B+t/bU3cEpl0GF9C5pUeiRi0IoStZb3mI9D1KPW/REKyUWkhabQO1gFYbTnRlkNOn6MILzKX4cwJjDaZeeo4EBPU7N+qHyOOXrU6hdH5FfxhMdV983ajm5eeuupxER1C2kAcIklTeVpTX6EKOgZb5LBp5ssOVm2P42pOauvcRozRcvZmqnErXmukv0H4l3EVNt4rHpTn9riHUC63e8JfiYzVaF6zuNUxv6nHEft0/SRMw11XSTnNfDzcKqgjz6ksFBS/6eQQYKESk+ONC53HUuYHFAknkwsPupDCT2W8FIQKBgQDLHT/xCU3nxGr4vFKBDNaO2D5oK20ECbBO4oDvLWWmQG7f+6TsMy8PgVdMnoL4RfqGlwFAKEpS6KVFHnBVqnNEhcdy9uCI7x7Xx8UnyUtxj1EDTm76uta9Ki9OrlqB6tImDM9+Ya3vGktW37ht4WOx2OsJRhG1dbf6RLwFlH7DWwKBgQDFBxvi5I1BR6hg6Tj7xd2SqOT2Y+BED3xuSYENhWbmMhLJDResaB7mjztbxlYaY2mOE0holWm2uDmVFFhMh4jYXik4hYH8nmDzq9mDpZCZ9pyjYqnAP8THoAa8EbgrUWB8A6BPH4iL3KbMnBfBKY0pIr2xrvnjQjNBAgta7KDRKQKBgCe6oe4wxrdF2TKsC2tIqpMoQxS3Icy/ZGgZr+SYuaBKTCWtoDW/UT40K3JGMxIDBhzbXphBCUCsVt9tM8Xd4EwP6tJW7dZ7B0pnve2pVwNwaAVAiz6p2yUHIle+jN+Koe5lZRSwYIg7WW81tWpwwsJfzqFyvjYDP6hJV4mz4ROvAoGAaRcdnKvjXApomShMqJ4lTPChD3q+SA8qg3jZSOj6tZXHx00gb2kp8jg7pPvpOTIFPy6x1Ha9aCRjMk0ju84fA6lVuzwa1S907wOehUVuF3Eeo1cgy9Y3k3KbpPyeixxgpkUY4JslLdSHc2NemD0dee951qhJyRmqVOZOQDUuoeECgYEAqBw2cAFk3vM97WY06TSldGA8ajVHx3BYRjj+zl62NTQthy8fw3tqxb3c5e8toOmZWKjZvDhg2TRLhsDDQWEYg3LZG87REqVIjgEPcpjNLidjygGX8n3JF2o0O5I/EMvl0s/+LVQONfduOBvhwDqr8QNisbLsyneiAq7umewMolo="
public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnFMmIVanMxsW5S/qP8Wcxf/J3/i4631BP3UtWkRzO7jAw9HIAgK4Y7X53hXj6zMbfme1vMjQc0mq7m/KrH4WlTYpFexLO6Gnk8oH40F04tp+ABZIq93zNOydPEaVoZeTPH/LlkwrrxVGAMNNIKuebcqapp25JiWtlSFMv4kH/nDAj+2m8+P4zYVM1Ed6gO01eKDEYE3SBA1Ket2BfHTgviR/F8WKwlXh11enywsJnrHTM5dJQdlUxCjHy214TpheYOz/cv9elQnDfFAbmZW8mH5/hgMSTkm3h4uR7ITin6Erg+yc/t1kGaTWrzloyBRMSiFN/Pwr5yQjj+1wQqqUkwIDAQAB" public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnFMmIVanMxsW5S/qP8Wcxf/J3/i4631BP3UtWkRzO7jAw9HIAgK4Y7X53hXj6zMbfme1vMjQc0mq7m/KrH4WlTYpFexLO6Gnk8oH40F04tp+ABZIq93zNOydPEaVoZeTPH/LlkwrrxVGAMNNIKuebcqapp25JiWtlSFMv4kH/nDAj+2m8+P4zYVM1Ed6gO01eKDEYE3SBA1Ket2BfHTgviR/F8WKwlXh11enywsJnrHTM5dJQdlUxCjHy214TpheYOz/cv9elQnDfFAbmZW8mH5/hgMSTkm3h4uR7ITin6Erg+yc/t1kGaTWrzloyBRMSiFN/Pwr5yQjj+1wQqqUkwIDAQAB"

View File

@@ -1 +1 @@
34820 53820

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -6,3 +6,9 @@
.\binlog.000028 .\binlog.000028
.\binlog.000029 .\binlog.000029
.\binlog.000030 .\binlog.000030
.\binlog.000031
.\binlog.000032
.\binlog.000033
.\binlog.000034
.\binlog.000035
.\binlog.000036

View File

@@ -1,17 +1,19 @@
@echo off @echo off
chcp 65001 >nul chcp 65001 >nul
setlocal
echo ======================================== echo ========================================
echo NPQS9100 回滚工具 echo NPQS9100 回滚工具
echo ======================================== echo ========================================
echo. echo.
echo 【重要提示】 echo 【重要提示】
echo 本工具用于回滚上次升级的内容 echo 本工具仅回滚前后端程序文件
echo 回滚后将恢复到升级前的状态 echo 数据库不会由本脚本自动恢复
echo 如需恢复数据库,请手动执行之前导出的 SQL 备份
echo. echo.
pause pause
echo. echo.
echo [1/5] 停止 NPQS9100 进程... echo [1/4] 停止 NPQS9100 进程...
taskkill /F /IM NPQS9100.exe 2>nul taskkill /F /IM NPQS9100.exe 2>nul
if %errorlevel% equ 0 ( if %errorlevel% equ 0 (
echo NPQS9100 已停止 echo NPQS9100 已停止
@@ -21,7 +23,7 @@ if %errorlevel% equ 0 (
) )
echo. echo.
echo [2/5] 检查备份文件... echo [2/4] 检查备份文件...
set hasBackup=0 set hasBackup=0
if exist backup\app.asar ( if exist backup\app.asar (
@@ -39,23 +41,17 @@ if exist backup\entrance.jar (
set hasBackup=1 set hasBackup=1
) )
if exist mysql\data_backup (
echo 发现数据库备份
set hasBackup=1
)
if %hasBackup%==0 ( if %hasBackup%==0 (
echo 未发现任何备份文件! echo 未发现任何前后端备份文件!
echo 无法执行回滚操作 echo 无法执行回滚操作
pause pause
exit /b 1 exit /b 1
) )
echo. echo.
echo [3/5] 回滚前端... echo [3/4] 回滚前端...
set frontendRollback=0 set frontendRollback=0
REM 回滚 app.asar
if exist backup\app.asar ( if exist backup\app.asar (
echo 正在恢复 app.asar... echo 正在恢复 app.asar...
copy /Y backup\app.asar resources\app.asar >nul 2>&1 copy /Y backup\app.asar resources\app.asar >nul 2>&1
@@ -69,7 +65,6 @@ if exist backup\app.asar (
) )
) )
REM 回滚 app.asar.unpacked
if exist backup\app.asar.unpacked ( if exist backup\app.asar.unpacked (
echo 正在恢复 app.asar.unpacked... echo 正在恢复 app.asar.unpacked...
if exist resources\app.asar.unpacked ( if exist resources\app.asar.unpacked (
@@ -91,7 +86,7 @@ if %frontendRollback%==0 (
) )
echo. echo.
echo [4/5] 回滚后端... echo [4/4] 回滚后端...
if exist backup\entrance.jar ( if exist backup\entrance.jar (
echo 正在恢复 JAR 文件... echo 正在恢复 JAR 文件...
copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1 copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1
@@ -107,37 +102,11 @@ if exist backup\entrance.jar (
) )
echo. echo.
echo [5/5] 回滚数据库...
if exist mysql\data_backup (
echo 是否回滚数据库?(数据库回滚会丢失升级后的数据!)
echo [Y] 是 [N] 否
choice /C YN /N /M "请选择:"
if errorlevel 2 (
echo 已跳过数据库回滚
) else (
echo 正在回滚数据库...
if exist mysql\data (
rmdir /s /q mysql\data 2>nul
)
xcopy mysql\data_backup mysql\data\ /E /I /Y /Q >nul 2>&1
if %errorlevel% equ 0 (
echo 数据库已回滚
) else (
echo 数据库回滚失败
pause
exit /b 1
)
)
) else (
echo 无数据库备份,跳过
)
echo.
echo ======================================== echo ========================================
echo 回滚完成! echo 回滚完成!
echo ======================================== echo ========================================
echo. echo.
echo 如需恢复数据库,请手动执行之前导出的 SQL 备份。
echo 您现在可以启动 NPQS9100 了 echo 您现在可以启动 NPQS9100 了
echo. echo.
pause pause

View File

@@ -1,5 +1,6 @@
@echo off @echo off
chcp 65001 >nul chcp 65001 >nul
setlocal
echo ======================================== echo ========================================
echo NPQS9100 升级工具 echo NPQS9100 升级工具
echo ======================================== echo ========================================
@@ -11,9 +12,8 @@ if not exist upgrade (
echo 【首次使用】已自动创建 upgrade 目录 echo 【首次使用】已自动创建 upgrade 目录
echo. echo.
echo 请将升级文件放入 upgrade 目录: echo 请将升级文件放入 upgrade 目录:
echo - app.asar (前端升级包 - 文件 echo - app.asar + app.asar.unpacked\ (前端升级包,必须成套放入
echo - app.asar.unpacked\ (前端升级包 - 文件夹 echo - entrance.jar (后端升级包
echo - entrance.jar (后端升级包)
echo. echo.
echo 放置完成后,重新运行本脚本即可升级 echo 放置完成后,重新运行本脚本即可升级
echo. echo.
@@ -21,19 +21,51 @@ if not exist upgrade (
exit /b 0 exit /b 0
) )
REM 检查是否有升级文件 REM 检查升级文件状态
set hasUpgrade=0 set "hasFrontendAsar=0"
if exist upgrade\app.asar set hasUpgrade=1 set "hasFrontendUnpacked=0"
if exist upgrade\app.asar.unpacked set hasUpgrade=1 set "upgradeFrontend=0"
if exist upgrade\entrance.jar set hasUpgrade=1 set "upgradeBackend=0"
set "frontendUpgraded=0"
if %hasUpgrade%==0 ( if exist upgrade\app.asar set "hasFrontendAsar=1"
if exist upgrade\app.asar.unpacked set "hasFrontendUnpacked=1"
if exist upgrade\entrance.jar set "upgradeBackend=1"
if %hasFrontendAsar%==1 if %hasFrontendUnpacked%==1 (
set "upgradeFrontend=1"
)
if %hasFrontendAsar%==1 if %hasFrontendUnpacked%==0 (
echo 【错误】前端升级包不完整!
echo.
echo 当前仅检测到upgrade\app.asar
echo 前端升级必须同时提供以下两个内容:
echo - upgrade\app.asar
echo - upgrade\app.asar.unpacked\
echo.
pause
exit /b 1
)
if %hasFrontendAsar%==0 if %hasFrontendUnpacked%==1 (
echo 【错误】前端升级包不完整!
echo.
echo 当前仅检测到upgrade\app.asar.unpacked\
echo 前端升级必须同时提供以下两个内容:
echo - upgrade\app.asar
echo - upgrade\app.asar.unpacked\
echo.
pause
exit /b 1
)
if %upgradeFrontend%==0 if %upgradeBackend%==0 (
echo 【提示】upgrade 目录为空! echo 【提示】upgrade 目录为空!
echo. echo.
echo 请将升级文件放入 upgrade 目录: echo 请将升级文件放入 upgrade 目录:
echo - app.asar (前端升级包 - 文件 echo - app.asar + app.asar.unpacked\ (前端升级包,必须成套放入
echo - app.asar.unpacked\ (前端升级包 - 文件夹 echo - entrance.jar (后端升级包
echo - entrance.jar (后端升级包)
echo. echo.
echo 放置完成后,重新运行本脚本即可升级 echo 放置完成后,重新运行本脚本即可升级
echo. echo.
@@ -42,19 +74,22 @@ if %hasUpgrade%==0 (
) )
echo 【检测到升级文件】 echo 【检测到升级文件】
if exist upgrade\app.asar echo - 前端升级asar文件 upgrade\app.asar if %upgradeFrontend%==1 (
if exist upgrade\app.asar.unpacked echo - 前端升级unpacked文件夹 upgrade\app.asar.unpacked\ echo - 前端升级: upgrade\app.asar + upgrade\app.asar.unpacked\
if exist upgrade\entrance.jar echo - 后端升级: upgrade\entrance.jar )
if %upgradeBackend%==1 (
echo - 后端升级: upgrade\entrance.jar
)
echo. echo.
echo 【重要提示】 echo 【重要提示】
echo 1. 升级前会自动备份当前版本 echo 1. 升级前会自动备份当前前后端程序文件
echo 2. 如升级失败可运行 rollback.bat 回滚 echo 2. 如升级失败可运行 rollback.bat 回滚前后端程序文件
echo 3. 数据库会自动备份到 mysql\data_backup\ echo 3. 数据库请先使用 Navicat 或其他工具手动导出 SQL 备份
echo. echo.
pause pause
echo. echo.
echo [1/6] 停止 NPQS9100 进程... echo [1/5] 停止 NPQS9100 进程...
taskkill /F /IM NPQS9100.exe 2>nul taskkill /F /IM NPQS9100.exe 2>nul
if %errorlevel% equ 0 ( if %errorlevel% equ 0 (
echo NPQS9100 已停止 echo NPQS9100 已停止
@@ -64,63 +99,54 @@ if %errorlevel% equ 0 (
) )
echo. echo.
echo [2/6] 备份当前版本(用于回滚)... echo [2/5] 备份当前版本(用于回滚)...
if not exist backup mkdir backup if not exist backup mkdir backup
REM 备份前端(app.asar 和 app.asar.unpacked REM 备份前端(仅在执行前端升级时
if exist resources\app.asar ( if %upgradeFrontend%==1 (
echo 正在备份前端 app.asar... if exist resources\app.asar (
if not exist backup mkdir backup echo 正在备份前端 app.asar...
copy /Y resources\app.asar backup\app.asar >nul 2>&1 copy /Y resources\app.asar backup\app.asar >nul 2>&1
if %errorlevel% equ 0 ( if %errorlevel% equ 0 (
echo app.asar 备份完成 echo app.asar 备份完成
) else ( ) else (
echo app.asar 备份失败 echo app.asar 备份失败
pause
exit /b 1
)
)
if exist resources\app.asar.unpacked (
echo 正在备份前端 app.asar.unpacked...
if exist backup\app.asar.unpacked (
rmdir /s /q backup\app.asar.unpacked 2>nul
)
xcopy resources\app.asar.unpacked backup\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
if %errorlevel% equ 0 (
echo app.asar.unpacked 备份完成
) else (
echo app.asar.unpacked 备份失败
pause
exit /b 1
)
) )
) )
if exist resources\app.asar.unpacked ( REM 备份后端(仅在执行后端升级时)
echo 正在备份前端 app.asar.unpacked... if %upgradeBackend%==1 if exist resources\extraResources\java\entrance.jar (
if exist backup\app.asar.unpacked (
rmdir /s /q backup\app.asar.unpacked 2>nul
)
xcopy resources\app.asar.unpacked backup\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
if %errorlevel% equ 0 (
echo app.asar.unpacked 备份完成
) else (
echo app.asar.unpacked 备份失败
)
)
REM 备份后端
if exist resources\extraResources\java\entrance.jar (
echo 正在备份后端... echo 正在备份后端...
copy /Y resources\extraResources\java\entrance.jar backup\entrance.jar >nul 2>&1 copy /Y resources\extraResources\java\entrance.jar backup\entrance.jar >nul 2>&1
if %errorlevel% equ 0 ( if %errorlevel% equ 0 (
echo 后端备份完成 echo 后端备份完成
) else ( ) else (
echo 后端备份失败 echo 后端备份失败
)
)
REM 备份数据库
if exist mysql\data (
echo 正在备份数据库...
if exist mysql\data_backup (
rmdir /s /q mysql\data_backup 2>nul
)
xcopy mysql\data mysql\data_backup\ /E /I /Y /Q >nul 2>&1
if %errorlevel% equ 0 (
echo 数据库备份完成
) else (
echo 数据库备份失败,请手动备份后继续
pause pause
exit /b 1 exit /b 1
) )
) )
echo. echo.
echo [3/6] 记录版本信息... echo [3/5] 记录版本信息...
if not exist backup\version.txt ( if not exist backup\version.txt (
echo 备份时间: %date% %time% > backup\version.txt echo 备份时间: %date% %time% > backup\version.txt
echo 升级前版本备份 >> backup\version.txt echo 升级前版本备份 >> backup\version.txt
@@ -130,29 +156,22 @@ if not exist backup\version.txt (
echo 版本信息已记录 echo 版本信息已记录
echo. echo.
echo [4/6] 升级前端... echo [4/5] 升级前端...
set frontendUpgraded=0 if %upgradeFrontend%==1 (
REM 升级 app.asar
if exist upgrade\app.asar (
echo 正在替换 app.asar... echo 正在替换 app.asar...
copy /Y upgrade\app.asar resources\app.asar >nul 2>&1 copy /Y upgrade\app.asar resources\app.asar >nul 2>&1
if %errorlevel% equ 0 ( if %errorlevel% equ 0 (
echo app.asar 升级完成 echo app.asar 升级完成
set frontendUpgraded=1
) else ( ) else (
echo app.asar 升级失败,正在回滚... echo app.asar 升级失败,正在回滚...
if exist backup\app.asar ( if exist backup\app.asar (
copy /Y backup\app.asar resources\app.asar >nul 2>&1 copy /Y backup\app.asar resources\app.asar >nul 2>&1
echo 已回滚到升级前版本
) )
echo 已回滚到升级前版本
pause pause
exit /b 1 exit /b 1
) )
)
REM 升级 app.asar.unpacked
if exist upgrade\app.asar.unpacked (
echo 正在替换 app.asar.unpacked... echo 正在替换 app.asar.unpacked...
if exist resources\app.asar.unpacked ( if exist resources\app.asar.unpacked (
rmdir /s /q resources\app.asar.unpacked 2>nul rmdir /s /q resources\app.asar.unpacked 2>nul
@@ -160,26 +179,27 @@ if exist upgrade\app.asar.unpacked (
xcopy upgrade\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1 xcopy upgrade\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
if %errorlevel% equ 0 ( if %errorlevel% equ 0 (
echo app.asar.unpacked 升级完成 echo app.asar.unpacked 升级完成
set frontendUpgraded=1 set "frontendUpgraded=1"
) else ( ) else (
echo app.asar.unpacked 升级失败,正在回滚... echo app.asar.unpacked 升级失败,正在回滚整个前端...
if exist backup\app.asar (
copy /Y backup\app.asar resources\app.asar >nul 2>&1
)
if exist backup\app.asar.unpacked ( if exist backup\app.asar.unpacked (
rmdir /s /q resources\app.asar.unpacked 2>nul rmdir /s /q resources\app.asar.unpacked 2>nul
xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1 xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
echo 已回滚到升级前版本
) )
echo 已回滚到升级前版本
pause pause
exit /b 1 exit /b 1
) )
) ) else (
if %frontendUpgraded%==0 (
echo 无前端升级包,跳过 echo 无前端升级包,跳过
) )
echo. echo.
echo [5/6] 升级后端... echo [5/5] 升级后端...
if exist upgrade\entrance.jar ( if %upgradeBackend%==1 (
echo 正在替换 JAR 文件... echo 正在替换 JAR 文件...
copy /Y upgrade\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1 copy /Y upgrade\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1
if %errorlevel% equ 0 ( if %errorlevel% equ 0 (
@@ -188,8 +208,17 @@ if exist upgrade\entrance.jar (
echo 后端升级失败,正在回滚... echo 后端升级失败,正在回滚...
if exist backup\entrance.jar ( if exist backup\entrance.jar (
copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1 copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1
echo 已回滚到升级前版本
) )
if %frontendUpgraded%==1 (
if exist backup\app.asar (
copy /Y backup\app.asar resources\app.asar >nul 2>&1
)
if exist backup\app.asar.unpacked (
rmdir /s /q resources\app.asar.unpacked 2>nul
xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
)
)
echo 已回滚到升级前版本
pause pause
exit /b 1 exit /b 1
) )
@@ -198,19 +227,17 @@ if exist upgrade\entrance.jar (
) )
echo. echo.
echo [6/6] 记录升级日志...
if not exist logs mkdir logs if not exist logs mkdir logs
echo ========================================== >> logs\upgrade.log echo ========================================== >> logs\upgrade.log
echo 升级时间: %date% %time% >> logs\upgrade.log echo 升级时间: %date% %time% >> logs\upgrade.log
if exist upgrade\dist ( if %upgradeFrontend%==1 (
echo 升级内容: 前端 >> logs\upgrade.log echo 升级内容: 前端 >> logs\upgrade.log
) )
if exist upgrade\entrance.jar ( if %upgradeBackend%==1 (
echo 升级内容: 后端 >> logs\upgrade.log echo 升级内容: 后端 >> logs\upgrade.log
) )
echo 数据库处理: 请手动导出/导入 SQL >> logs\upgrade.log
echo ========================================== >> logs\upgrade.log echo ========================================== >> logs\upgrade.log
echo 升级日志已记录
echo.
echo ======================================== echo ========================================
echo 升级完成! echo 升级完成!
@@ -218,10 +245,9 @@ echo ========================================
echo. echo.
echo 【提示】 echo 【提示】
echo 1. 如需回滚,请运行 rollback.bat echo 1. 如需回滚,请运行 rollback.bat
echo 2. 升级文件已使用,可删除 upgrade 目录 echo 2. 数据库如需恢复,请手动执行之前导出的 SQL
echo 3. 升级文件已使用,可删除 upgrade 目录
echo. echo.
echo 您现在可以启动 NPQS9100 应用。 echo 您现在可以启动 NPQS9100 应用。
echo. echo.
pause pause

View File

@@ -3,18 +3,21 @@
## 使用方法 ## 使用方法
1. 将升级文件放入此目录: 1. 将升级文件放入此目录:
- app.asar (前端升级包 - 文件) - app.asar + app.asar.unpacked/ (前端升级包,必须成套放入)
- app.asar.unpacked/ (前端升级包 - 文件夹)
- entrance.jar (后端升级包) - entrance.jar (后端升级包)
2. 双击运行根目录的 upgrade.bat 脚本 2. 升级前先手动导出数据库 SQL 备份
3. 升级完成后重启应用 3. 双击运行根目录的 upgrade.bat 脚本
4. 升级完成后重启应用
## 注意事项 ## 注意事项
- 可以只放前端或只放后端,支持单独升级 - 可以只放后端,或放完整前端包,支持单独升级
- 如果升级前端,必须同时放入 app.asar 和 app.asar.unpacked/
- 升级前会自动备份到 backup/ 目录 - 升级前会自动备份到 backup/ 目录
- 如果升级失败,运行 rollback.bat 可回滚 - 数据库不由脚本自动备份,请手动导出/导入 SQL
- 如果升级失败,运行 rollback.bat 可回滚前后端程序文件
详细说明请参考README-升级回滚.txt 详细说明请参考根目录 README-升级回滚.txt

View File

@@ -72,9 +72,9 @@
} }
], ],
"win": { "win": {
"icon": "build/icons/icon.ico", "icon": "public/images/icon.ico",
"requestedExecutionLevel": "requireAdministrator", "requestedExecutionLevel": "asInvoker",
"signAndEditExecutable": false, "signAndEditExecutable": true,
"verifyUpdateCodeSignature": false, "verifyUpdateCodeSignature": false,
"artifactName": "${productName}-${os}-${version}-${arch}.${ext}", "artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
"target": [ "target": [
@@ -88,9 +88,9 @@
"oneClick": false, "oneClick": false,
"allowElevation": true, "allowElevation": true,
"allowToChangeInstallationDirectory": true, "allowToChangeInstallationDirectory": true,
"installerIcon": "build/icons/icon.ico", "installerIcon": "public/images/icon.ico",
"uninstallerIcon": "build/icons/icon.ico", "uninstallerIcon": "public/images/icon.ico",
"installerHeaderIcon": "build/icons/icon.ico", "installerHeaderIcon": "public/images/icon.ico",
"createDesktopShortcut": true, "createDesktopShortcut": true,
"createStartMenuShortcut": true, "createStartMenuShortcut": true,
"shortcutName": "NPQS9100" "shortcutName": "NPQS9100"

View File

@@ -40,8 +40,8 @@
- 自动更新配置文件 - 自动更新配置文件
#### **2. 盘符自动识别** #### **2. 盘符自动识别**
- 应用在 C 盘 → 数据目录:`C:\NPQS9100_Data\` - 程序目录为纯英文/ASCII路径时数据目录位于应用目录内的 `NPQS9100_Data\`
- 应用在 D 盘 → 数据目录:`D:\NPQS9100_Data\` - 程序目录包含中文或其他非 ASCII 字符时:自动切换到当前盘符根目录下的 `NPQS9100_Runtime\`
- 自动创建所有必要的子目录 - 自动创建所有必要的子目录
#### **3. Loading 界面** #### **3. Loading 界面**
@@ -65,18 +65,21 @@
### 📁 目录结构 ### 📁 目录结构
交付口径说明:
- `electron-builder` 原始输出目录:`out/win-unpacked`
- `npm run build-w` 脚本最终交付目录:`out/NPQS-9100`
``` ```
win-unpacked/ # 绿色版目录(约 650-800 MB NPQS-9100/ # 绿色版目录(约 650-800 MB
├── NPQS9100.exe # 主程序(双击运行)⭐ ├── NPQS9100.exe # 主程序(双击运行)⭐
├── NPQS9100-启动器.bat # 管理员启动器(备用)
├── upgrade.bat # 升级工具 ⭐ ├── upgrade.bat # 升级工具 ⭐
├── rollback.bat # 回滚工具 ⭐ ├── rollback.bat # 回滚工具 ⭐
├── uninstall-mysql-service.bat # MySQL 服务卸载工具
├── 使用说明.txt # 使用手册 ├── 使用说明.txt # 使用手册
├── README-升级回滚.txt # 升级说明 ├── README-升级回滚.txt # 升级说明
├── upgrade/ # 升级包目录(首次使用自动创建) ├── upgrade/ # 升级包目录(首次使用自动创建)
├── backup/ # 自动备份目录 ├── backup/ # 自动备份目录
│ ├── dist/ # 前端备份 │ ├── app.asar # 前端主包备份
│ ├── app.asar.unpacked/ # 前端展开目录备份
│ ├── entrance.jar # 后端备份 │ ├── entrance.jar # 后端备份
│ └── version.txt # 版本记录 │ └── version.txt # 版本记录
├── resources/ ├── resources/
@@ -93,13 +96,12 @@ win-unpacked/ # 绿色版目录(约 650-800 MB
├── scripts/ # 启动管理脚本(⚠️ 不要修改) ├── scripts/ # 启动管理脚本(⚠️ 不要修改)
│ ├── config-generator.js │ ├── config-generator.js
│ ├── java-runner.js │ ├── java-runner.js
│ ├── mysql-service-manager.js │ ├── mysql-process-manager.js
│ ├── port-checker.js │ ├── port-checker.js
│ └── startup-manager.js │ └── startup-manager.js
├── mysql/ # MySQL 数据库 ├── mysql/ # MySQL 数据库
│ ├── bin/ │ ├── bin/
│ ├── data/ # 数据库数据 ⚠️ 不要动 │ ├── data/ # 数据库数据 ⚠️ 不要动
│ ├── data_backup/ # 自动备份
│ ├── my.ini # 运行时生成 │ ├── my.ini # 运行时生成
│ └── README.txt │ └── README.txt
├── jre/ # Java 运行环境(⚠️ 不要修改) ├── jre/ # Java 运行环境(⚠️ 不要修改)
@@ -110,7 +112,12 @@ win-unpacked/ # 绿色版目录(约 650-800 MB
└── upgrade.log └── upgrade.log
用户数据目录(首次运行自动创建): 用户数据目录(首次运行自动创建):
X:\NPQS9100_Data\ (X 是应用所在盘符) - 程序目录为纯英文/ASCII路径时
`应用目录\NPQS9100_Data\`
- 程序目录包含中文或其他非 ASCII 字符时:
`X:\NPQS9100_Runtime\data\` (X 是应用所在盘符)
用户数据目录内包含:
├── logs/ # Spring Boot 日志 ├── logs/ # Spring Boot 日志
├── template/ # 报告模板 ├── template/ # 报告模板
├── report/ # 生成的报告 ├── report/ # 生成的报告
@@ -126,10 +133,8 @@ X:\NPQS9100_Data\ (X 是应用所在盘符)
| 文件 | 功能 | 使用场景 | | 文件 | 功能 | 使用场景 |
|------|------|---------| |------|------|---------|
| **NPQS9100.exe** | 主程序 | 日常启动 | | **NPQS9100.exe** | 主程序 | 日常启动 |
| **NPQS9100-启动器.bat** | 管理员启动器 | 首次运行或权限问题时 |
| **upgrade.bat** | 升级工具 | 收到升级包时使用 | | **upgrade.bat** | 升级工具 | 收到升级包时使用 |
| **rollback.bat** | 回滚工具 | 升级后出问题时使用 | | **rollback.bat** | 回滚工具 | 升级后出问题时使用 |
| **uninstall-mysql-service.bat** | MySQL 服务卸载 | 完全卸载应用时使用 |
| **使用说明.txt** | 使用手册 | 遇到问题时参考 | | **使用说明.txt** | 使用手册 | 遇到问题时参考 |
| **README-升级回滚.txt** | 升级说明 | 升级前阅读 | | **README-升级回滚.txt** | 升级说明 | 升级前阅读 |
@@ -151,9 +156,9 @@ X:\NPQS9100_Data\ (X 是应用所在盘符)
| 路径 | 说明 | 备份建议 | | 路径 | 说明 | 备份建议 |
|------|------|---------| |------|------|---------|
| **mysql/data/** | 数据库数据 | ⚠️ 定期备份! | | **应用目录\\mysql\\data/****X:\\NPQS9100_Runtime\\mysql\\data/** | 数据库数据 | ⚠️ 定期备份! |
| **backup/** | 升级备份 | 自动管理,无需手动操作 | | **backup/** | 升级备份 | 自动管理,无需手动操作 |
| **X:\NPQS9100_Data/** | 用户数据目录 | 包含日志、模板、报告 | | **应用目录\\NPQS9100_Data/****X:\\NPQS9100_Runtime\\data/** | 用户数据目录 | 包含日志、模板、报告 |
--- ---
@@ -174,7 +179,7 @@ clean-and-build.bat
3. 构建前端代码Vue 3 3. 构建前端代码Vue 3
4. 编译 Electron 主进程代码 4. 编译 Electron 主进程代码
5. 复制 MySQL、JRE、Java 资源 5. 复制 MySQL、JRE、Java 资源
6. 生成 win-unpacked 绿色版目录 6. 生成 `out/win-unpacked`,再自动重命名为最终交付目录 `out/NPQS-9100`
**等待时间**:约 5-15 分钟 **等待时间**:约 5-15 分钟
@@ -183,7 +188,7 @@ clean-and-build.bat
#### **步骤 2测试运行** #### **步骤 2测试运行**
```bash ```bash
cd out\win-unpacked cd out\NPQS-9100
NPQS9100.exe NPQS9100.exe
``` ```
@@ -200,7 +205,7 @@ NPQS9100.exe
```powershell ```powershell
cd out cd out
Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.zip" -Force Compress-Archive -Path NPQS-9100 -DestinationPath "NPQS9100-v1.0.0-绿色版.zip" -Force
``` ```
**压缩后大小**:约 350-450 MB **压缩后大小**:约 350-450 MB
@@ -237,7 +242,8 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
├─ MySQL 连接localhost:3307 ├─ MySQL 连接localhost:3307
├─ MySQL 密码njcnpqs ⭐ 自动写入 ├─ MySQL 密码njcnpqs ⭐ 自动写入
├─ Java 端口18093 ├─ Java 端口18093
├─ 数据路径:C:\NPQS9100_Data\ ├─ 数据路径:纯英文路径时为 `应用目录\NPQS9100_Data\`
├─ 数据路径:中文路径时为 `X:\NPQS9100_Runtime\data\`
├─ 创建所有必要目录logs、template、report、data ├─ 创建所有必要目录logs、template、report、data
[80%] 启动 Spring Boot 后端 [80%] 启动 Spring Boot 后端
@@ -274,7 +280,7 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
停止 Spring Boot 进程通过PID和端口精确清理 停止 Spring Boot 进程通过PID和端口精确清理
停止 MySQL 服务(如果是服务模式 停止 MySQL 进程(随应用启动的 mysqld.exe
清理资源 清理资源
@@ -289,17 +295,19 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
#### **升级流程** #### **升级流程**
``` ```
1. 双击 upgrade.bat首次自动创建 upgrade/ 目录) 1. 先手动导出数据库 SQL 备份
2. 将升级包放入 upgrade/ 目录 2. 双击 upgrade.bat首次自动创建 upgrade/ 目录
3. 再次运行 upgrade.bat自动备份 + 升级) 3. 将升级包放入 upgrade/ 目录
4. 重启应用测试 4. 再次运行 upgrade.bat自动备份前后端程序文件 + 升级)
5. 重启应用测试
``` ```
#### **回滚流程** #### **回滚流程**
``` ```
1. 双击 rollback.bat 1. 双击 rollback.bat
2. 自动从 backup/ 恢复旧版本 2. 自动从 backup/ 恢复前后端程序文件
3. 重启应用 3. 如需恢复数据库,手动执行之前导出的 SQL
4. 重启应用
``` ```
--- ---
@@ -310,23 +318,27 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
**第 1 步:准备升级包** **第 1 步:准备升级包**
1. 双击 `upgrade.bat` 1. 先使用 Navicat 或其他工具手动导出数据库 SQL 备份
2. 脚本自动创建 `upgrade/` 文件夹并提示 2. 双击 `upgrade.bat`
3. 将升级文件放入 `upgrade/` 目录: 3. 脚本自动创建 `upgrade/` 文件夹并提示
- `dist/` - 前端升级包(可选) 4. 将升级文件放入 `upgrade/` 目录:
- `app.asar` + `app.asar.unpacked/` - 前端升级包(可选,必须成套)
- `entrance.jar` - 后端升级包(可选) - `entrance.jar` - 后端升级包(可选)
4. 再次双击 `upgrade.bat` 开始升级 5. 再次双击 `upgrade.bat` 开始升级
**第 2 步:自动升级** **第 2 步:自动升级**
脚本会自动: 脚本会自动:
- ✅ 停止 NPQS9100 进程 - ✅ 停止 NPQS9100 进程
-**自动备份当前版本到 `backup/` 目录** -**自动备份当前前后端程序文件到 `backup/` 目录**
- ✅ 备份数据库到 `mysql/data_backup/`
- ✅ 替换前端文件(如果有) - ✅ 替换前端文件(如果有)
- ✅ 替换后端 JAR如果有 - ✅ 替换后端 JAR如果有
- ✅ 记录升级日志到 `logs/upgrade.log` - ✅ 记录升级日志到 `logs/upgrade.log`
数据库说明:
- ⚠️ 数据库不再由 `upgrade.bat` 自动备份
- ⚠️ 升级前请务必手动导出 SQL 备份
**第 3 步:重启应用** **第 3 步:重启应用**
升级完成后,双击 `NPQS9100.exe` 启动应用。 升级完成后,双击 `NPQS9100.exe` 启动应用。
@@ -340,8 +352,9 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
**升级前端** **升级前端**
```batch ```batch
taskkill /F /IM NPQS9100.exe taskkill /F /IM NPQS9100.exe
rmdir /s /q resources\app.asar.unpacked\public\dist copy /Y upgrade\app.asar resources\app.asar
xcopy upgrade\dist resources\app.asar.unpacked\public\dist\ /E /I /Y rmdir /s /q resources\app.asar.unpacked
xcopy upgrade\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y
``` ```
**升级后端** **升级后端**
@@ -360,23 +373,23 @@ copy /Y upgrade\entrance.jar resources\extraResources\java\entrance.jar
脚本会自动: 脚本会自动:
- ✅ 停止 NPQS9100 - ✅ 停止 NPQS9100
- ✅ 恢复前端(从 `backup/dist/` - ✅ 恢复前端(从 `backup/app.asar``backup/app.asar.unpacked/`
- ✅ 恢复后端(从 `backup/entrance.jar` - ✅ 恢复后端(从 `backup/entrance.jar`
-询问是否恢复数据库(⚠️ 会丢失升级后的数据) -数据库由人工恢复,不再由脚本自动处理
#### **方法 2手动回滚** #### **方法 2手动回滚**
```batch ```batch
# 恢复前端 # 恢复前端
rmdir /s /q resources\app.asar.unpacked\public\dist copy /Y backup\app.asar resources\app.asar
xcopy backup\dist resources\app.asar.unpacked\public\dist\ /E /I /Y rmdir /s /q resources\app.asar.unpacked
xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y
# 恢复后端 # 恢复后端
copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar
# 恢复数据库(⚠️ 谨慎!) # 恢复数据库
rmdir /s /q mysql\data # 使用 Navicat 或其他工具执行升级前导出的 SQL 备份
xcopy mysql\data_backup mysql\data\ /E /I /Y
``` ```
--- ---
@@ -385,9 +398,9 @@ xcopy mysql\data_backup mysql\data\ /E /I /Y
| 升级类型 | 升级包来源 | 客户放置位置 | 最终替换位置 | | 升级类型 | 升级包来源 | 客户放置位置 | 最终替换位置 |
|---------|-----------|-------------|-------------| |---------|-----------|-------------|-------------|
| **前端** | `out/win-unpacked/resources/app.asar` + `app.asar.unpacked/` | `upgrade/app.asar` + `upgrade/app.asar.unpacked/` | `resources/app.asar` + `resources/app.asar.unpacked/` | | **前端** | `out/NPQS-9100/resources/app.asar` + `app.asar.unpacked/` | `upgrade/app.asar` + `upgrade/app.asar.unpacked/` | `resources/app.asar` + `resources/app.asar.unpacked/` |
| **后端** | `out/win-unpacked/resources/extraResources/java/entrance.jar` | `upgrade/entrance.jar` | `resources/extraResources/java/entrance.jar` | | **后端** | `out/NPQS-9100/resources/extraResources/java/entrance.jar` | `upgrade/entrance.jar` | `resources/extraResources/java/entrance.jar` |
| **数据库** | ⚠️ **不升级,自动保留** | - | `mysql/data/` | | **数据库** | 手动导出的 SQL 备份 | 不放入升级包 | 需要时人工导入 |
--- ---
@@ -412,10 +425,11 @@ npm run build-frontend
# 4⃣ 打包应用 # 4⃣ 打包应用
npm run build-w npm run build-w
# 输出: out/win-unpacked/ # 最终交付目录: out/NPQS-9100/
# 原始输出目录: out/win-unpacked/(脚本会自动重命名)
# 5⃣ 准备升级包 # 5⃣ 准备升级包
# 从 out/win-unpacked/resources/ 复制以下文件: # 从 out/NPQS-9100/resources/ 复制以下文件:
# - app.asar (文件) # - app.asar (文件)
# - app.asar.unpacked/ (整个文件夹) # - app.asar.unpacked/ (整个文件夹)
# #
@@ -435,7 +449,7 @@ npm run build-w
# - app.asar.unpacked/ # - app.asar.unpacked/
# 2⃣ 复制到升级目录 # 2⃣ 复制到升级目录
# 将两个文件都放到:win-unpacked/upgrade/ # 将两个文件都放到:NPQS-9100/upgrade/
# - upgrade/app.asar # - upgrade/app.asar
# - upgrade/app.asar.unpacked/ # - upgrade/app.asar.unpacked/
@@ -478,7 +492,7 @@ npm run build-w
# 2⃣ 复制到升级目录 # 2⃣ 复制到升级目录
# 将 entrance.jar 复制到: # 将 entrance.jar 复制到:
# win-unpacked/upgrade/entrance.jar # NPQS-9100/upgrade/entrance.jar
# 3⃣ 运行升级脚本 # 3⃣ 运行升级脚本
# 双击: upgrade.bat # 双击: upgrade.bat
@@ -497,7 +511,7 @@ NPQS9100-升级-v1.1.0.zip
└── entrance.jar # 后端升级包 └── entrance.jar # 后端升级包
# 客户侧:复制到 # 客户侧:复制到
win-unpacked/upgrade/ NPQS-9100/upgrade/
├── app.asar ├── app.asar
├── app.asar.unpacked/ ├── app.asar.unpacked/
└── entrance.jar └── entrance.jar
@@ -511,13 +525,11 @@ win-unpacked/upgrade/
### ✅ 打包验证 ### ✅ 打包验证
- [ ] `out/win-unpacked` 目录存在 - [ ] `out/NPQS-9100` 目录存在
- [ ] 目录大小约 650-800 MB - [ ] 目录大小约 650-800 MB
- [ ] NPQS9100.exe 存在 - [ ] NPQS9100.exe 存在
- [ ] NPQS9100-启动器.bat 存在
- [ ] upgrade.bat 存在 - [ ] upgrade.bat 存在
- [ ] rollback.bat 存在 - [ ] rollback.bat 存在
- [ ] uninstall-mysql-service.bat 存在
- [ ] 使用说明.txt 存在 - [ ] 使用说明.txt 存在
- [ ] README-升级回滚.txt 存在 - [ ] README-升级回滚.txt 存在
- [ ] mysql/ 目录完整 - [ ] mysql/ 目录完整
@@ -602,10 +614,12 @@ win-unpacked/upgrade/
- [ ] upgrade.bat 首次运行自动创建 upgrade/ 目录 - [ ] upgrade.bat 首次运行自动创建 upgrade/ 目录
- [ ] upgrade.bat 检测到升级文件后正常升级 - [ ] upgrade.bat 检测到升级文件后正常升级
- [ ] 升级前自动备份到 backup/ 目录 - [ ] 升级前自动备份到 backup/ 目录
- [ ] 升级前已手动导出数据库 SQL
- [ ] 升级后应用正常运行 - [ ] 升级后应用正常运行
- [ ] 升级后数据库数据保留 - [ ] 升级后数据库数据保留
- [ ] rollback.bat 能正确回滚前端 - [ ] rollback.bat 能正确回滚前端
- [ ] rollback.bat 能正确回滚后端 - [ ] rollback.bat 能正确回滚后端
- [ ] 如需恢复数据库,能手动导入 SQL
- [ ] 回滚后应用恢复正常 - [ ] 回滚后应用恢复正常
--- ---
@@ -710,7 +724,7 @@ C:\Users\[用户名]\AppData\Roaming\NQPS9100\logs\
### Q8: 升级后数据丢失? ### Q8: 升级后数据丢失?
**A**: 检查 `mysql/data/` 目录是否完整有备份 `mysql/data_backup/` 恢复 **A**: 检查升级前是否已手动导出数据库 SQL 备份需恢复数据库请使用 Navicat 或其他工具执行该 SQL 备份
--- ---
@@ -728,7 +742,8 @@ C:\Users\[用户名]\AppData\Roaming\NQPS9100\logs\
**A** **A**
1. 检查 `backup/` 目录是否有备份文件 1. 检查 `backup/` 目录是否有备份文件
2. 查看 `backup/version.txt` 确认备份版本 2. 查看 `backup/version.txt` 确认备份版本
3. 手动执行回滚步骤参考本文档 3. 手动执行前后端回滚步骤参考本文档
4. 如问题与数据库有关手动导入升级前导出的 SQL
--- ---
@@ -767,27 +782,32 @@ VITE_API_URL=http://192.168.1.100:18092
--- ---
### Q14: MySQL 服务如何卸载 ### Q14: 现在还需要安装或卸载 MySQL 服务
**A**: **A**:
**方法 1使用卸载脚本推荐**
```batch
双击: uninstall-mysql-service.bat
```
**方法 2手动卸载** **当前版本不需要。**
```batch
# 停止服务
net stop mysql9100
# 删除服务 当前版本已经放弃首次使用时把 MySQL 安装成 Windows 服务并设置开机自启的方案改为**绿色包 + 进程模式**
sc delete mysql9100
```
**注意** - 双击 `NPQS9100.exe` 应用会自动启动 `mysqld.exe`
- 卸载服务不会删除数据库数据 - 退出应用时MySQL 进程会自动停止
- 数据保存在 `mysql/data/` 目录 - **无需管理员权限**
- 下次启动应用会重新安装服务 - **不会注册 Windows 服务**
- **不会依赖开机自启**
**为什么改成这样?**
- 旧方案需要管理员权限
- 用户可能直接双击使用导致服务安装失败后应用启动失败
- 也可能出现授权失败赋权不完整等问题
- C 端用户来说不稳定因此改为随应用启停的绿色包进程模式
**数据说明**
- 程序目录为纯英文/ASCII路径时MySQL 数据位于 `应用目录\mysql\data\`
- 程序目录包含中文或其他非 ASCII 字符时MySQL 数据位于 `X:\NPQS9100_Runtime\mysql\data\`
- 当前版本不依赖任何 MySQL Windows 服务
--- ---
@@ -800,8 +820,7 @@ sc delete mysql9100
**开发者文档**doc/ 目录 **开发者文档**doc/ 目录
- `doc/NPQS-9100绿色包完整指南.md` - 本文档完整指南 - `doc/NPQS-9100绿色包完整指南.md` - 本文档完整指南
- `doc/打包前检查清单.md` - 逐项检查 - `doc/打包前检查清单.md` - 逐项检查
- `doc/管理员权限说明.md` - 权限问题处理 - `doc/管理员权限说明.md` - 历史服务模式与当前进程模式说明
- `doc/MySQL服务化方案说明.md` - MySQL 服务管理
--- ---
@@ -812,7 +831,7 @@ sc delete mysql9100
- `scripts/port-checker.js` - 端口检测工具 - `scripts/port-checker.js` - 端口检测工具
- `scripts/startup-manager.js` - 启动状态管理 - `scripts/startup-manager.js` - 启动状态管理
- `scripts/config-generator.js` - 配置文件生成 - `scripts/config-generator.js` - 配置文件生成
- `scripts/mysql-service-manager.js` - MySQL 服务管理器 - `scripts/mysql-process-manager.js` - MySQL 进程管理器
- `scripts/java-runner.js` - Java 运行器 - `scripts/java-runner.js` - Java 运行器
- `scripts/log-window-manager.js` - 日志窗口管理 - `scripts/log-window-manager.js` - 日志窗口管理
- `electron/preload/lifecycle.js` - 生命周期管理 - `electron/preload/lifecycle.js` - 生命周期管理
@@ -827,7 +846,7 @@ StartupManager (显示 Loading)
↓ 调用 ↓ 调用
PortChecker (检测端口) PortChecker (检测端口)
↓ 调用 ↓ 调用
MySQLServiceManager (管理 MySQL 服务) MySQLProcessManager (管理 MySQL 进程)
↓ 调用 ↓ 调用
ConfigGenerator (生成配置) ConfigGenerator (生成配置)
↓ 调用 ↓ 调用
@@ -840,14 +859,14 @@ JavaRunner (启动 Spring Boot)
### 用户体验提升 ### 用户体验提升
- **任务栏只显示1个图标** - Loading 窗口不在任务栏显示 - **任务栏只显示1个图标** - Loading 窗口不在任务栏显示
- **纯绿色版** - 只生成 win-unpacked 目录压缩成 zip 即可发布 - **纯绿色版** - 最终交付目录统一为 `out/NPQS-9100`压缩成 zip 即可发布
- **一键解压即用** - 用户解压后双击即可运行 - **一键解压即用** - 用户解压后双击即可运行
- **热更新机制** - 前后端可独立升级支持一键回滚 - **热更新机制** - 前后端可独立升级支持一键回滚
### 技术改进 ### 技术改进
- **MySQL 密码自动配置** - 配置生成器自动写入密码 - **MySQL 密码自动配置** - 配置生成器自动写入密码
- **权限自动授权** - 支持 localhost 127.0.0.1 访问 - **权限自动授权** - 支持 localhost 127.0.0.1 访问
- **MySQL 服务化** - 使用 Windows 服务管理开机自启 - **MySQL 进程模式** - 随应用启动和退出自动管理无需管理员权限
- **窗口管理优化** - 使用 destroy() 确保窗口完全释放 - **窗口管理优化** - 使用 destroy() 确保窗口完全释放
- **精确进程清理** - 不会误杀其他 Java 进程 - **精确进程清理** - 不会误杀其他 Java 进程
- **自动备份机制** - 升级前自动备份支持回滚 - **自动备份机制** - 升级前自动备份支持回滚

View File

@@ -96,7 +96,7 @@ gen.generateConfig({ mysqlPort: 3307 }).then(console.log);
--- ---
#### `scripts/mysql-service-manager.js` - MySQL 进程管理器(绿色包 - 进程模式) #### `scripts/mysql-process-manager.js` - MySQL 进程管理器(绿色包 - 进程模式)
**作用**:以进程模式管理 MySQL**无需管理员权限**,随应用启动/关闭 **作用**:以进程模式管理 MySQL**无需管理员权限**,随应用启动/关闭
**核心方法** **核心方法**
```javascript ```javascript
@@ -141,8 +141,8 @@ this.mysqlProcess = spawn(mysqldPath, [
**调试方法** **调试方法**
```javascript ```javascript
// 单独测试 // 单独测试
const MySQLServiceManager = require('./scripts/mysql-service-manager'); const MySQLProcessManager = require('./scripts/mysql-process-manager');
const mysql = new MySQLServiceManager(); const mysql = new MySQLProcessManager();
mysql.ensureServiceRunning( mysql.ensureServiceRunning(
(startPort, maxAttempts) => startPort, // Mock port checker (startPort, maxAttempts) => startPort, // Mock port checker
(port, timeout) => Promise.resolve(true) (port, timeout) => Promise.resolve(true)
@@ -363,7 +363,7 @@ await this.checkEnvironment(); // 你的自定义方法
### 场景4修改MySQL进程配置绿色包 - 进程模式) ### 场景4修改MySQL进程配置绿色包 - 进程模式)
**文件**`scripts/mysql-service-manager.js` **文件**`scripts/mysql-process-manager.js`
**修改 my.ini 配置**(推荐方式): **修改 my.ini 配置**(推荐方式):
```javascript ```javascript
@@ -502,7 +502,7 @@ electron/preload/lifecycle.js (启动入口)
├── scripts/port-checker.js (检测端口) ├── scripts/port-checker.js (检测端口)
├── scripts/mysql-service-manager.js (MySQL服务管理) ├── scripts/mysql-process-manager.js (MySQL进程管理)
├── scripts/config-generator.js (生成配置) ├── scripts/config-generator.js (生成配置)
│ ↓ 读取 │ ↓ 读取
@@ -555,7 +555,7 @@ electron/preload/lifecycle.js (启动入口)
|------|------|---------| |------|------|---------|
| 端口检测 | `scripts/port-checker.js` | 全文 | | 端口检测 | `scripts/port-checker.js` | 全文 |
| 启动管理 | `scripts/startup-manager.js` | 全文 | | 启动管理 | `scripts/startup-manager.js` | 全文 |
| MySQL服务管理 | `scripts/mysql-service-manager.js` | 核心方法 ensureServiceRunning | | MySQL进程管理 | `scripts/mysql-process-manager.js` | 核心方法 ensureServiceRunning |
| Java启动 | `scripts/java-runner.js` | 152-198 | | Java启动 | `scripts/java-runner.js` | 152-198 |
| 配置生成 | `scripts/config-generator.js` | 38-83 | | 配置生成 | `scripts/config-generator.js` | 38-83 |
| 启动流程 | `electron/preload/lifecycle.js` | 27-159 | | 启动流程 | `electron/preload/lifecycle.js` | 27-159 |

View File

@@ -21,7 +21,7 @@
- [ ] `scripts/port-checker.js` 存在 - [ ] `scripts/port-checker.js` 存在
- [ ] `scripts/startup-manager.js` 存在 - [ ] `scripts/startup-manager.js` 存在
- [ ] `scripts/config-generator.js` 存在 - [ ] `scripts/config-generator.js` 存在
- [ ] `scripts/mysql-service-manager.js` 存在 - [ ] `scripts/mysql-process-manager.js` 存在
- [ ] `scripts/log-window-manager.js` 存在 - [ ] `scripts/log-window-manager.js` 存在
- [ ] `scripts/java-runner.js` 存在 - [ ] `scripts/java-runner.js` 存在
@@ -45,7 +45,7 @@
### MySQL 进程(绿色包 - 进程模式) ### MySQL 进程(绿色包 - 进程模式)
- [ ] MySQL 进程能正常启动(通过 spawn mysqld.exe - [ ] MySQL 进程能正常启动(通过 spawn mysqld.exe
- [ ] ~~MySQL 服务配置为开机自启~~(已废弃,使用进程模式) - [ ] 确认当前版本为进程模式,不依赖 MySQL Windows 服务
- [ ] MySQL 数据库首次启动时自动初始化 - [ ] MySQL 数据库首次启动时自动初始化
- [ ] MySQL 连接密码正确njcnpqs - [ ] MySQL 连接密码正确njcnpqs
- [ ] MySQL 进程随应用退出而自动关闭 - [ ] MySQL 进程随应用退出而自动关闭
@@ -98,9 +98,9 @@ npm run build-w # 打包 Windows 便携版
### 基础测试(绿色包 - 进程模式) ### 基础测试(绿色包 - 进程模式)
- [ ] **无需管理员权限**,普通用户双击 exe 能正常启动 ✅ - [ ] **无需管理员权限**,普通用户双击 exe 能正常启动 ✅
- [ ] Loading 界面正常显示,显示启动步骤 - [ ] Loading 界面正常显示,显示启动步骤
- [ ] ~~MySQL 服务自动安装并启动~~(已废弃) - [ ] 不会尝试安装 MySQL Windows 服务
- [ ] **MySQL 进程自动启动**(任务管理器可见 mysqld.exe - [ ] **MySQL 进程自动启动**(任务管理器可见 mysqld.exe
- [ ] ~~MySQL 服务配置为开机自启~~(已废弃,进程模式随应用启动) - [ ] MySQL 随应用启动,退出后自动停止,不依赖开机自启
- [ ] Spring Boot 自动启动(任务管理器可见 java.exe - [ ] Spring Boot 自动启动(任务管理器可见 java.exe
- [ ] 主界面正常显示 - [ ] 主界面正常显示
- [ ] 应用退出后MySQL 和 Java 进程自动关闭 ✅ - [ ] 应用退出后MySQL 和 Java 进程自动关闭 ✅
@@ -147,8 +147,8 @@ C:\Users\[用户名]\AppData\Roaming\NQPS9100\logs\
### 检查内容 ### 检查内容
- [ ] 端口检测日志正确 - [ ] 端口检测日志正确
- [ ] MySQL 服务安装/启动日志正常 - [ ] MySQL 进程启动日志正常
- [ ] 日志中显示"服务已配置为开机自启" - [ ] 日志中显示 mysqld.exe 启动与退出清理信息
- [ ] Spring Boot 启动日志正常 - [ ] Spring Boot 启动日志正常
- [ ] 无严重错误 - [ ] 无严重错误
@@ -185,7 +185,6 @@ C:\Users\[用户名]\AppData\Roaming\NQPS9100\logs\
- 检查端口是否被占用应自动切换到其他端口 - 检查端口是否被占用应自动切换到其他端口
- 查看应用日志`%APPDATA%/NQPS9100/logs/9100.log` - 查看应用日志`%APPDATA%/NQPS9100/logs/9100.log`
- 查看 MySQL 错误日志`mysql/data/*.err` - 查看 MySQL 错误日志`mysql/data/*.err`
- ~~使用 sc query mysql9100 检查服务状态~~已废弃不再使用服务
- 检查是否有残留的 mysqld.exe 进程任务管理器 - 检查是否有残留的 mysqld.exe 进程任务管理器
### Spring Boot 启动失败 ### Spring Boot 启动失败

View File

@@ -1,58 +1,27 @@
# 管理员权限说明 # 管理员权限说明
> ⚠️ **文档已过期** - 本文档描述的是旧版服务模式,自 2025-12-01 起已改用**进程模式(绿色包)**。 当前版本采用**绿色包 + MySQL 进程模式****不需要管理员权限**。
>
> 🎉 **新版本不再需要管理员权限!**
>
> 参考最新文档:`MySQL进程模式改造方案.md` 和 `NPQS-9100绿色包完整指南.md`
--- ## 当前结论
## ~~🔐 为什么需要管理员权限?~~(已废弃) 1. 双击 `NPQS9100.exe` 即可启动
2. 无需管理员权限
3. 无 UAC 弹窗
4. 解压即用,完全绿色
5. 仅在应用运行期间启动 MySQL
~~NPQS9100 需要管理 MySQL Windows 服务,执行以下操作:~~ ## 说明
- ~~✅ 安装 MySQL 服务 (`mysql9100`)~~
- ~~✅ 启动/停止服务~~
- ~~✅ 删除服务(重新安装时)~~
~~这些操作都需要 **Windows 管理员权限**。~~ - MySQL 和 Spring Boot 会在应用启动时自动启动
- 退出应用时自动清理
- 不注册 Windows 服务
- 不依赖开机自启
**现状**:已改用进程模式,直接启动 `mysqld.exe` 进程,无需注册 Windows 服务,**完全不需要管理员权限**。 ## 为什么这样设计
## 🎉 新版启动方式(进程模式) - 需要管理员权限的启动方式对 C 端用户不稳定
- 用户直接双击使用时,提权、授权或服务安装失败会导致启动失败
**当前版本启动非常简单** - 因此当前版本统一采用无需授权的绿色包进程模式
1. ✅ 双击 `NPQS9100.exe` 即可启动
2. ✅ 无需管理员权限
3. ✅ 无 UAC 弹窗
4. ✅ 解压即用,完全绿色
**MySQL 和 Spring Boot 会在应用启动时自动启动,退出时自动清理。**
---
## ~~以下内容已废弃(保留作为历史参考)~~
<details>
<summary>点击展开查看旧版(服务模式)说明</summary>
### ~~两种启动方式(已废弃)~~
#### ~~方式 1使用启动器推荐~~
~~打包后的目录中有一个 `NPQS9100-启动器.bat` 文件。~~
#### ~~方式 2手动以管理员身份运行~~
~~右键点击 `NPQS9100.exe` 选择"以管理员身份运行"~~
### ~~常见问题(已废弃)~~
~~Q: MySQL 服务安装后还需要管理员权限吗?~~
A: **新版本不使用 Windows 服务,因此完全不需要管理员权限。**
</details>
--- ---

View File

@@ -45,11 +45,11 @@ function getScriptsPath(scriptName) {
} }
// 延迟加载 scripts // 延迟加载 scripts
let MySQLServiceManager, JavaRunner, ConfigGenerator, PortChecker, StartupManager, LogWindowManager; let MySQLProcessManager, JavaRunner, ConfigGenerator, PortChecker, StartupManager, LogWindowManager;
function loadScripts() { function loadScripts() {
if (!MySQLServiceManager) { if (!MySQLProcessManager) {
MySQLServiceManager = require(getScriptsPath('mysql-service-manager')); MySQLProcessManager = require(getScriptsPath('mysql-process-manager'));
JavaRunner = require(getScriptsPath('java-runner')); JavaRunner = require(getScriptsPath('java-runner'));
ConfigGenerator = require(getScriptsPath('config-generator')); ConfigGenerator = require(getScriptsPath('config-generator'));
PortChecker = require(getScriptsPath('port-checker')); PortChecker = require(getScriptsPath('port-checker'));
@@ -60,7 +60,7 @@ function loadScripts() {
class Lifecycle { class Lifecycle {
constructor() { constructor() {
this.mysqlServiceManager = null; this.mysqlProcessManager = null;
this.javaRunner = null; this.javaRunner = null;
this.startupManager = null; this.startupManager = null;
this.logWindowManager = null; this.logWindowManager = null;
@@ -94,26 +94,26 @@ class Lifecycle {
this.startupManager.updateProgress('init'); this.startupManager.updateProgress('init');
await this.sleep(500); await this.sleep(500);
// 步骤2-4: 确保 MySQL 服务运行 // 步骤2-4: 确保 MySQL 进程运行
this.logWindowManager.addLog('system', '▶ 步骤4: 检查 MySQL 配置...'); this.logWindowManager.addLog('system', '▶ 步骤4: 检查 MySQL 配置...');
logger.info('[lifecycle] MySQL config check - enable:', config.mysql?.enable, 'autoStart:', config.mysql?.autoStart); logger.info('[lifecycle] MySQL config check - enable:', config.mysql?.enable, 'autoStart:', config.mysql?.autoStart);
if (config.mysql && config.mysql.enable && config.mysql.autoStart) { if (config.mysql && config.mysql.enable && config.mysql.autoStart) {
this.startupManager.updateProgress('check-mysql-port'); this.startupManager.updateProgress('check-mysql-port');
this.logWindowManager.addLog('system', '▶ 步骤5: 启动 MySQL 服务管理器...'); this.logWindowManager.addLog('system', '▶ 步骤5: 启动 MySQL 进程管理器...');
this.mysqlServiceManager = new MySQLServiceManager(this.logWindowManager); this.mysqlProcessManager = new MySQLProcessManager(this.logWindowManager);
this.logWindowManager.addLog('system', '正在检查 MySQL 服务状态...'); this.logWindowManager.addLog('system', '正在检查 MySQL 进程状态...');
try { try {
// 使用服务管理器确保MySQL服务运行 // 使用进程管理器确保 MySQL 进程运行
this.logWindowManager.addLog('system', '▶ 步骤6: 确保 MySQL 服务运行中...'); this.logWindowManager.addLog('system', '▶ 步骤6: 确保 MySQL 进程运行中...');
this.mysqlPort = await this.mysqlServiceManager.ensureServiceRunning( this.mysqlPort = await this.mysqlProcessManager.ensureServiceRunning(
PortChecker.findAvailablePort.bind(PortChecker), PortChecker.findAvailablePort.bind(PortChecker),
PortChecker.waitForPort.bind(PortChecker) PortChecker.waitForPort.bind(PortChecker)
); );
logger.info(`[lifecycle] MySQL service running on port: ${this.mysqlPort}`); logger.info(`[lifecycle] MySQL process running on port: ${this.mysqlPort}`);
this.logWindowManager.addLog('success', `✓ MySQL 服务已就绪,端口: ${this.mysqlPort}`); this.logWindowManager.addLog('success', `✓ MySQL 服务已就绪,端口: ${this.mysqlPort}`);
this.startupManager.updateProgress('wait-mysql', { mysqlPort: this.mysqlPort }); this.startupManager.updateProgress('wait-mysql', { mysqlPort: this.mysqlPort });
await this.sleep(500); await this.sleep(500);
@@ -201,7 +201,7 @@ class Lifecycle {
dataPath: dataPath dataPath: dataPath
}); });
await this.startSpringBoot(configPath); await this.startSpringBoot(configPath, dataPath);
// 步骤8: 等待 Spring Boot 就绪 // 步骤8: 等待 Spring Boot 就绪
this.logWindowManager.addLog('system', '▶ 步骤11: 等待 Spring Boot 就绪最多60秒...'); this.logWindowManager.addLog('system', '▶ 步骤11: 等待 Spring Boot 就绪最多60秒...');
@@ -376,14 +376,14 @@ class Lifecycle {
} }
// 停止 MySQL 进程(进程模式) // 停止 MySQL 进程(进程模式)
if (this.mysqlServiceManager) { if (this.mysqlProcessManager) {
try { try {
logger.info('[lifecycle] Stopping MySQL process...'); logger.info('[lifecycle] Stopping MySQL process...');
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) { if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
this.logWindowManager.addLog('system', '正在停止 MySQL...'); this.logWindowManager.addLog('system', '正在停止 MySQL...');
} }
await this.mysqlServiceManager.stopMySQLProcess(); await this.mysqlProcessManager.stopMySQLProcess();
logger.info('[lifecycle] MySQL process stopped'); logger.info('[lifecycle] MySQL process stopped');
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) { if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
@@ -419,7 +419,7 @@ class Lifecycle {
/** /**
* 启动 Spring Boot 应用 * 启动 Spring Boot 应用
*/ */
async startSpringBoot(configPath) { async startSpringBoot(configPath, dataPath) {
try { try {
logger.info('[lifecycle] Starting Spring Boot application...'); logger.info('[lifecycle] Starting Spring Boot application...');
this.logWindowManager.addLog('java', '正在启动 Spring Boot 应用...'); this.logWindowManager.addLog('java', '正在启动 Spring Boot 应用...');
@@ -434,12 +434,6 @@ class Lifecycle {
? path.join(__dirname, '..', 'build', 'extraResources', 'java', 'entrance.jar') ? path.join(__dirname, '..', 'build', 'extraResources', 'java', 'entrance.jar')
: path.join(process.resourcesPath, 'extraResources', 'java', 'entrance.jar'); : path.join(process.resourcesPath, 'extraResources', 'java', 'entrance.jar');
// 获取日志路径(与 config-generator.js 中的 dataPath 保持一致)
const isDev2 = !process.resourcesPath;
const baseDir = isDev2
? path.join(__dirname, '..', '..')
: path.dirname(process.resourcesPath);
const dataPath = path.join(baseDir, 'NPQS9100_Data');
const logPath = path.join(dataPath, 'logs'); const logPath = path.join(dataPath, 'logs');
const javaProcess = this.javaRunner.runSpringBoot(jarPath, configPath, { const javaProcess = this.javaRunner.runSpringBoot(jarPath, configPath, {

View File

@@ -19,9 +19,9 @@ VITE_API_URL=/api
# 开发环境跨域代理,支持配置多个 # 开发环境跨域代理,支持配置多个
#VITE_PROXY=[["/api","http://127.0.0.1:18092/"]] VITE_PROXY=[["/api","http://127.0.0.1:18093/"]]
#VITE_PROXY=[["/api","http://192.168.1.124:18092/"]] #VITE_PROXY=[["/api","http://192.168.1.124:18092/"]]
VITE_PROXY=[["/api","http://192.168.2.125:18092/"]] #VITE_PROXY=[["/api","http://192.168.2.125:18092/"]]
# VITE_PROXY=[["/api","http://192.168.1.138:8080/"]]张文 # VITE_PROXY=[["/api","http://192.168.1.138:8080/"]]张文
# 开启激活验证 # 开启激活验证
VITE_ACTIVATE_OPEN=false VITE_ACTIVATE_OPEN=true

View File

@@ -1,6 +1,6 @@
{ {
"name": "frontend", "name": "frontend",
"version": "0.0.1", "version": "2.0.1",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@@ -14,7 +14,8 @@ export const getScriptList = (params: {
devId:string, devId:string,
chnNum:number, chnNum:number,
num:number, num:number,
planId:string planId:string,
code:string
}) => { }) => {
return http.post('/result/getCheckItem', params, {loading: false}) return http.post('/result/getCheckItem', params, {loading: false})
} }

View File

@@ -35,9 +35,25 @@ export namespace StandardDevice {
updateBy?: string | null; //更新用户 updateBy?: string | null; //更新用户
updateTime?: string | null; //更新时间 updateTime?: string | null; //更新时间
disabled?: boolean; disabled?: boolean;
gainList?:Gain[]
} }
interface Gain{
stdDevMonitorId:string;
uaGain:number;
ubGain:number;
ucGain:number;
u0Gain:number;
iaGain:number;
ibGain:number;
icGain:number;
i0Gain:number;
uabGain:number;
ubcGain:number;
ucaGain:number;
}
/** /**
* 标准设备表格查询分页返回的对象; * 标准设备表格查询分页返回的对象;
*/ */

View File

@@ -14,6 +14,7 @@ export namespace Base {
createTime?: string| null; //创建时间 createTime?: string| null; //创建时间
updateBy?: string| null; //更新用户 updateBy?: string| null; //更新用户
updateTime?: string| null; //更新时间 updateTime?: string| null; //更新时间
coefficient:number //比对监测后,当电压、电流不符合时,是否对标准设备进行系数校准
} }
} }

View File

@@ -57,3 +57,14 @@ export const getPublicKey = (username: string) => {
export const getAutoGenerate = () => { export const getAutoGenerate = () => {
return http.get('/sysTestConfig/getAutoGenerate', {}, {loading: false}) return http.get('/sysTestConfig/getAutoGenerate', {}, {loading: false})
} }
/**
* 获取比对模式下是否能够进行系数校准
*/
export const getCanCoefficient=()=>{
return http.get('/prepare/canCoefficient', {}, {loading: false})
}
export const startCoefficient=()=>{
return http.get('/prepare/startCoefficient', {}, {loading: false})
}

View File

@@ -190,7 +190,7 @@ export default class SocketService {
* WebSocket连接配置 * WebSocket连接配置
*/ */
private config: SocketConfig = { private config: SocketConfig = {
url: 'ws://127.0.0.1:7777/hello', url: 'ws://127.0.0.1:7778/hello',
//url: 'ws://192.168.1.124:7777/hello', //url: 'ws://192.168.1.124:7777/hello',
heartbeatInterval: 9000, // 9秒心跳间隔 heartbeatInterval: 9000, // 9秒心跳间隔
reconnectDelay: 5000, // 5秒重连延迟 reconnectDelay: 5000, // 5秒重连延迟

View File

@@ -128,7 +128,7 @@ import {CheckData} from '@/api/check/interface'
import {useCheckStore} from '@/stores/modules/check' import {useCheckStore} from '@/stores/modules/check'
import {ElMessage, ElMessageBox} from 'element-plus' import {ElMessage, ElMessageBox} from 'element-plus'
import {getBigTestItem} from '@/api/check/test' import {getBigTestItem} from '@/api/check/test'
import {getAutoGenerate} from '@/api/user/login' import {getAutoGenerate, getCanCoefficient, startCoefficient} from '@/api/user/login'
import { generateDevReport } from '@/api/plan/plan' import { generateDevReport } from '@/api/plan/plan'
import {useModeStore} from '@/stores/modules/mode' // 引入模式 store import {useModeStore} from '@/stores/modules/mode' // 引入模式 store
import {useDictStore} from '@/stores/modules/dict' import {useDictStore} from '@/stores/modules/dict'
@@ -157,11 +157,6 @@ const props = defineProps({
const emit = defineEmits([ const emit = defineEmits([
'update:testStatus', 'update:testStatus',
'update:webMsgSend',
'sendPause',
'sendResume',
'sendReCheck',
'closeWebSocket'
]) ])
// 用来保存测试项进度抽屉是否打开 // 用来保存测试项进度抽屉是否打开
@@ -684,16 +679,37 @@ const updatePercentage = async () => {
}) })
} }
stopTimeCount(1) stopTimeCount(1)
ElMessageBox.alert(
'检测全部结束,你可以停留在此页面查看检测结果,或返回首页进行复检、报告生成和归档等操作', let { data: canCoefficient } = await getCanCoefficient()
'检测完成', if(canCoefficient) {
{ ElMessageBox.confirm('存在系数偏差,是否进行系数校准?', '系数校准', {
confirmButtonText: '确定' confirmButtonText: '确定',
} cancelButtonText: '取消',
) type: 'warning',
// 关闭WebSocket连接 }).then(async ()=>{
emit('closeWebSocket') // 调用系数校准接口
//clear(); await startCoefficient()
ElMessage.success({ message: '系数下发中,请稍等...', duration: 15000 ,onClose() {
ElMessage.success('系数下发成功!')
},})
}).catch(()=>{
ElMessageBox.alert(
'检测全部结束,你可以停留在此页面查看检测结果,或返回首页进行复检、报告生成和归档等操作',
'检测完成',
{
confirmButtonText: '确定'
}
)
})
}else{
ElMessageBox.alert(
'检测全部结束,你可以停留在此页面查看检测结果,或返回首页进行复检、报告生成和归档等操作',
'检测完成',
{
confirmButtonText: '确定'
}
)
}
} }
} }
// ========== 时间计数器管理函数 ========== // ========== 时间计数器管理函数 ==========
@@ -862,7 +878,6 @@ const handleClick = (item: any, chnNum: number, scriptType: string) => {
} }
const handlePause = () => { const handlePause = () => {
//emit('sendPause')
testLogList.push({ testLogList.push({
type: 'error', type: 'error',
log: `${new Date().toLocaleString()}:当前测试小项正在执行中,将在该小项执行结束后暂停...` log: `${new Date().toLocaleString()}:当前测试小项正在执行中,将在该小项执行结束后暂停...`

View File

@@ -198,8 +198,8 @@
</template> </template>
<script setup lang="tsx" name="useProTable"> <script setup lang="tsx" name="useProTable">
import { onBeforeMount, onMounted, type PropType, reactive, ref, watch } from 'vue' import {onBeforeMount, onMounted, type PropType, reactive, ref, watch} from 'vue'
import { type Action, ElMessage, ElMessageBox } from 'element-plus' import {type Action, ElMessage, ElMessageBox} from 'element-plus'
import TestPopup from './testPopup.vue' import TestPopup from './testPopup.vue'
import dataCheckPopup from './dataCheckSingleChannelSingleTestPopup.vue' import dataCheckPopup from './dataCheckSingleChannelSingleTestPopup.vue'
import CompareDataCheckSingleChannelSingleTestPopup from '@/views/home/components/compareDataCheckSingleChannelSingleTestPopup.vue' import CompareDataCheckSingleChannelSingleTestPopup from '@/views/home/components/compareDataCheckSingleChannelSingleTestPopup.vue'
@@ -207,20 +207,20 @@ import ProTable from '@/components/ProTable/index.vue'
import SelectTestItemPopup from '@/views/home/components/selectTestItemPopup.vue' import SelectTestItemPopup from '@/views/home/components/selectTestItemPopup.vue'
import WriteTHPopup from '@/views/home/components/writeTHPopup.vue' import WriteTHPopup from '@/views/home/components/writeTHPopup.vue'
import DeviceConnectionPopup from '@/views/home/components/deviceConnectionPopup.vue' import DeviceConnectionPopup from '@/views/home/components/deviceConnectionPopup.vue'
import { type Device } from '@/api/device/interface/device' import {type Device} from '@/api/device/interface/device'
import { type ColumnProps, type ProTableInstance } from '@/components/ProTable/interface' import {type ColumnProps, type ProTableInstance} from '@/components/ProTable/interface'
import { type Plan } from '@/api/plan/interface' import {type Plan} from '@/api/plan/interface'
import { type StandardDevice } from '@/api/device/interface/standardDevice' import {type StandardDevice} from '@/api/device/interface/standardDevice'
import { downloadDevData, generateDevReport, getBoundPqDevList } from '@/api/plan/plan' import {downloadDevData, generateDevReport, getBoundPqDevList} from '@/api/plan/plan'
import { getPqDev } from '@/api/device/device' import {getPqDev} from '@/api/device/device'
import { useAppSceneStore, useModeStore } from '@/stores/modules/mode' // 引入模式 store import {useAppSceneStore, useModeStore} from '@/stores/modules/mode' // 引入模式 store
import { useCheckStore } from '@/stores/modules/check' import {useCheckStore} from '@/stores/modules/check'
import { CheckData } from '@/api/check/interface' import {CheckData} from '@/api/check/interface'
import { useAuthStore } from '@/stores/modules/auth' import {useAuthStore} from '@/stores/modules/auth'
import { useDownload } from '@/hooks/useDownload' import {useDownload} from '@/hooks/useDownload'
import { documentedPqDev } from '@/api/device/report' import {documentedPqDev} from '@/api/device/report'
import { ResultEnum } from '@/enums/httpEnum' import {ResultEnum} from '@/enums/httpEnum'
import { getPqMonList } from '@/api/device/monitor/index' import {getPqMonList} from '@/api/device/monitor/index'
import ReportResultPopup from '@/views/home/components/reportResultPopup.vue' import ReportResultPopup from '@/views/home/components/reportResultPopup.vue'
const checkStore = useCheckStore() const checkStore = useCheckStore()
@@ -539,6 +539,7 @@ const columns = reactive<ColumnProps<Device.ResPqDev>[]>([
{ prop: 'operation', label: '操作', fixed: 'right', minWidth :200,isShow: operationShow } { prop: 'operation', label: '操作', fixed: 'right', minWidth :200,isShow: operationShow }
]) ])
let testType = 'test' // 检测类型:'test'-检测 'reTest'-复检 let testType = 'test' // 检测类型:'test'-检测 'reTest'-复检
let qualifiedCount = 0 //合格数量
//比对单个报告生成 //比对单个报告生成
@@ -568,12 +569,14 @@ const handleSelectionChange = (selection: any[]) => {
} }
// 统计已完成检测的设备数量 // 统计已完成检测的设备数量
const result = selection.filter(item => item.checkResult != 0) let result = selection.filter(item => item.checkState == 0)
if (result.length > 0) { if (result.length > 0) {
testType = 'test' testType = 'test'
} else { } else {
testType = 'reTest' testType = 'reTest'
} }
qualifiedCount=selection.filter(item => item.checkResult == 1).length
let devices: CheckData.Device[] = selection.map((item: any) => { let devices: CheckData.Device[] = selection.map((item: any) => {
return { return {
deviceId: item.id, deviceId: item.id,
@@ -925,6 +928,7 @@ const handleTest = async (val: string) => {
distinguishCancelAndClose: true, distinguishCancelAndClose: true,
confirmButtonText: '不合格项复检', confirmButtonText: '不合格项复检',
cancelButtonText: '全部复检', cancelButtonText: '全部复检',
showConfirmButton:qualifiedCount<=0,
type: 'warning' type: 'warning'
}) })
.then(() => { .then(() => {

View File

@@ -261,7 +261,7 @@ const dictStore = useDictStore()
const activeName = ref('') const activeName = ref('')
const childActiveName = ref('') const childActiveName = ref('')
const childActiveIndex = ref(0) const childActiveIndex = ref(0)
const firstName = 'first' const firstName = ref('first')
const viewRowRef = ref() const viewRowRef = ref()
const communicationList = ref<[]>([]) const communicationList = ref<[]>([])
const testProjectPopupRef = ref() const testProjectPopupRef = ref()

View File

@@ -1,100 +1,138 @@
<template> <template>
<el-dialog :title="dialogTitle" v-model='dialogVisible' @close="close" v-bind="dialogBig" align-center> <el-dialog :title="dialogTitle" v-model='dialogVisible' @close="close" v-bind="dialogBig" align-center>
<el-tabs type="border-card"> <el-tabs type="border-card" v-model="activeName">
<el-tab-pane label="设备台账信息"> <el-tab-pane label="设备台账信息" name="devInfo">
<div > <div>
<el-form :model='formContent' ref='dialogFormRef' :rules='rules' :disabled="false" label-width="auto" class="form-three"> <el-form :model='formContent' ref='dialogFormRef' :rules='rules' :disabled="false" label-width="auto" class="form-three">
<el-divider >设备信息</el-divider> <el-divider>设备信息</el-divider>
<el-form-item label="设备名称" prop="name" > <el-form-item label="设备名称" prop="name">
<el-input v-model='formContent.name' placeholder="请输入设备名称" maxlength="32" show-word-limit/> <el-input v-model='formContent.name' placeholder="请输入设备名称" maxlength="32" show-word-limit/>
</el-form-item> </el-form-item>
<el-form-item label='设备类型' prop='devType' > <el-form-item label='设备类型' prop='devType'>
<el-select v-model="formContent.devType" filterable clearable placeholder="请选择设备类型" @change="handleDevTypeChange"> <el-select v-model="formContent.devType" filterable clearable placeholder="请选择设备类型" @change="handleDevTypeChange">
<el-option <el-option
v-for="item in devTypeOptions" v-for="item in devTypeOptions"
:key="item.id" :key="item.id"
:label="item.name" :label="item.name"
:value="item.id" :value="item.id"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label='设备厂家' prop='manufacturer'> <el-form-item label='设备厂家' prop='manufacturer'>
<el-select v-model="formContent.manufacturer" clearable placeholder="请选择设备厂家"> <el-select v-model="formContent.manufacturer" clearable placeholder="请选择设备厂家">
<el-option <el-option
v-for="item in dictStore.getDictData('Dev_Manufacturers')" v-for="item in dictStore.getDictData('Dev_Manufacturers')"
:key="item.id" :key="item.id"
:label="item.name" :label="item.name"
:value="item.id" :value="item.id"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-divider >参数信息</el-divider> <el-divider>参数信息</el-divider>
<el-form-item label='通讯协议' prop='protocol'> <el-form-item label='通讯协议' prop='protocol'>
<el-select v-model="formContent.protocol" clearable placeholder="请选择通讯协议"> <el-select v-model="formContent.protocol" clearable placeholder="请选择通讯协议">
<el-option <el-option
v-for="item in dictStore.getDictData('Protocol')" v-for="item in dictStore.getDictData('Protocol')"
:key="item.id" :key="item.id"
:label="item.name" :label="item.name"
:value="item.id" :value="item.id"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="IP地址" prop="ip" placeholder="请输入IP地址"> <el-form-item label="IP地址" prop="ip" placeholder="请输入IP地址">
<el-input v-model="formContent.ip"/> <el-input v-model="formContent.ip"/>
</el-form-item> </el-form-item>
<el-form-item label="端口号" prop="port" placeholder="请输入端口号" > <el-form-item label="端口号" prop="port" placeholder="请输入端口号">
<el-input v-model="formContent.port" /> <el-input v-model="formContent.port"/>
</el-form-item> </el-form-item>
<el-form-item label='可检通道数' prop='inspectChannel' > <el-form-item label='可检通道数' prop='inspectChannel'>
<el-select v-model="formContent.inspectChannel" multiple collapse-tags :max-collapse-tags="4" placeholder="请选择可检通道数" clearable> <el-select v-model="formContent.inspectChannel" multiple collapse-tags :max-collapse-tags="4" placeholder="请选择可检通道数" clearable>
<el-option <el-option
v-for="(option, index) in pqChannelArray" v-for="(option, index) in pqChannelArray"
:key="index" :key="index"
:label="option.label" :label="option.label"
:value="option.value" :value="option.value"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label='是否加密' prop='encryptionFlag' > <el-form-item label='是否加密' prop='encryptionFlag'>
<el-select v-model="formContent.encryptionFlag" clearable placeholder="请选择是否加密"> <el-select v-model="formContent.encryptionFlag" clearable placeholder="请选择是否加密">
<el-option label="是" :value="1"></el-option> <el-option label="是" :value="1"></el-option>
<el-option label="否" :value="0"></el-option> <el-option label="否" :value="0"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label='识别码' prop='series' clearable v-if="formContent.encryptionFlag"> <el-form-item label='识别码' prop='series' clearable v-if="formContent.encryptionFlag">
<el-input v-model='formContent.series' placeholder="请输入识别码" show-password/> <el-input v-model='formContent.series' placeholder="请输入识别码" show-password/>
</el-form-item> </el-form-item>
<el-form-item label='密钥' prop='devKey' clearable v-if="formContent.encryptionFlag"> <el-form-item label='密钥' prop='devKey' clearable v-if="formContent.encryptionFlag">
<el-input v-model='formContent.devKey' placeholder="请输入密钥" show-password/> <el-input v-model='formContent.devKey' placeholder="请输入密钥" show-password/>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="通道系数" name="gainInfo">
<el-tabs type="border-card" style="margin: 15px 0px;">
<el-tab-pane v-for="(item, index) in formContent.gainList" :label="'通道'+(index+1)">
<el-form :model='item' ref='gainFormRef' :disabled="true" class="form-four">
<el-form-item label-width="30" label-position="left" label="ua" prop="uaGain">
<el-input v-model="item.uaGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="ub" prop="ubGain">
<el-input v-model="item.ubGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="uc" prop="ucGain">
<el-input v-model="item.ucGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="u0" prop="u0Gain">
<el-input v-model="item.u0Gain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="ia" prop="iaGain">
<el-input v-model="item.iaGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="ib" prop="ibGain">
<el-input v-model="item.ibGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="ic" prop="icGain">
<el-input v-model="item.icGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="i0" prop="i0Gain">
<el-input v-model="item.i0Gain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="uab" prop="uabGain">
<el-input v-model="item.uabGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="ubc" prop="uabGain">
<el-input v-model="item.ubcGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="uca" prop="ucaGain">
<el-input v-model="item.ucaGain"/>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs> </el-tabs>
<template #footer> </el-tab-pane>
<div > </el-tabs>
<el-button @click='close()'> </el-button> <template #footer>
<el-button type="primary" @click='save()'>保存</el-button> <div>
</div> <el-button @click='close()'> </el-button>
</template> <el-button type="primary" @click='save()'>保存</el-button>
</el-dialog> </div>
</template>
</el-dialog>
</template> </template>
<script setup lang='ts'> <script setup lang='ts'>
import IPAddress from '@/components/IpAddress/index.vue' import {dialogBig} from '@/utils/elementBind'
import { dialogBig } from '@/utils/elementBind'
import {type StandardDevice} from '@/api/device/interface/standardDevice.ts' import {type StandardDevice} from '@/api/device/interface/standardDevice.ts'
import { ElMessage, type FormItemRule } from 'element-plus' import {ElMessage, type FormItemRule} from 'element-plus'
import { addPqStandardDev, updatePqStandardDev} from '@/api/device/standardDevice/index.ts' import {addPqStandardDev, updatePqStandardDev} from '@/api/device/standardDevice/index.ts'
import { computed, reactive, type Ref, ref } from 'vue' import {computed, reactive, type Ref, ref} from 'vue'
import { useDictStore } from '@/stores/modules/dict' import {useDictStore} from '@/stores/modules/dict'
import { CirclePlus, Delete, EditPen } from '@element-plus/icons-vue'
import {type Device} from '@/api/device/interface/device.ts' import {type Device} from '@/api/device/interface/device.ts'
// 使用 dayjs 库格式化 // 使用 dayjs 库格式化
import dayjs from 'dayjs'
// 存储设备类型选项 // 存储设备类型选项
const devTypeOptions = ref<Device.ResDev[]>([]) const devTypeOptions = ref<Device.ResDev[]>([])
@@ -120,112 +158,114 @@ const pqChannelArray = ref([
label: '4', label: '4',
}, },
]) ])
const activeName=ref('devInfo')
function useMetaInfo() {
const dialogVisible = ref(false) function useMetaInfo() {
const titleType = ref('add') const dialogVisible = ref(false)
const formContent = reactive<StandardDevice.ResPqStandardDevice>({ const titleType = ref('add')
id: '', const formContent = reactive<StandardDevice.ResPqStandardDevice>({
name: '', id: '',
devType:'', name: '',
manufacturer:'', devType: '',
protocol: 'MMS', manufacturer: '',
ip: '', protocol: 'MMS',
port: 102, ip: '',
inspectChannel:'', port: 102,
encryptionFlag: 0, inspectChannel: '',
state: 1, encryptionFlag: 0,
}) state: 1,
return { dialogVisible, titleType, formContent } })
} return {dialogVisible, titleType, formContent}
}
const { dialogVisible, titleType, formContent } = useMetaInfo()
// 清空formContent const {dialogVisible, titleType, formContent} = useMetaInfo()
const resetFormContent = () => { // 清空formContent
Object.assign( const resetFormContent = () => {
formContent,{ Object.assign(
formContent, {
id: '', id: '',
name: '', name: '',
devType:'', devType: '',
manufacturer:'', manufacturer: '',
protocol: 'MMS', protocol: 'MMS',
ip: '', ip: '',
port: 102, port: 102,
inspectChannel:'', inspectChannel: '',
encryptionFlag: 0, encryptionFlag: 0,
state: 1, state: 1,
}
)
}
let dialogTitle = computed(() => {
return titleType.value === 'add' ? '新增标准设备' : '编辑标准设备'
})
//定义校验规则
const rules: Ref<Record<string, Array<FormItemRule>>> = ref({
name: [{required: true, message: '设备名称必填!', trigger: 'blur'}],
devType: [{required: true, message: '设备类型必选!', trigger: 'change'}],
manufacturer: [{required: true, message: '生产厂家必选!', trigger: 'change'}],
ip: [
{required: true, message: 'IP地址必填', trigger: 'blur'},
{pattern: /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, message: 'IP地址格式错误', trigger: 'blur'}
],
port: [
{required: true, message: '端口号必填!', trigger: 'blur'},
{pattern: /^(6553[0-5]|655[0-2][0-9]|64[0-9]{3}|[1-5]?[0-9]{1,4})$/, message: '端口号范围0到65535的整数', trigger: 'blur'}
],
inspectChannel: [{required: true, message: '可检通道数必选', trigger: 'change'}],
encryptionFlag: [{required: true, message: '是否加密必选!', trigger: 'change'}],
series: [{required: true, message: '请输入识别码', trigger: 'blur'}],
devKey: [{required: true, message: '请输入密钥', trigger: 'blur'}],
protocol: [{required: true, message: '通讯协议必选!', trigger: 'change'}],
})
// 关闭弹窗
const close = () => {
dialogVisible.value = false
// 清空dialogForm中的值
resetFormContent()
// 重置表单
dialogFormRef.value?.resetFields()
}
// 保存数据
const save = () => {
try {
dialogFormRef.value?.validate(async (valid: boolean) => {
if (formContent.encryptionFlag === 0) {
formContent.series = ''
formContent.devKey = ''
}
if (valid) {
//保存时判是否加密,把识别码密钥字段清空
if (formContent.encryptionFlag === 0) {
formContent.series = null
formContent.devKey = null
} }
)
}
let dialogTitle = computed(() => {
return titleType.value === 'add' ? '新增标准设备' : '编辑标准设备'
})
//定义校验规则
const rules: Ref<Record<string, Array<FormItemRule>>> = ref({
name : [{ required: true, message: '设备名称必填!', trigger: 'blur' }],
devType: [{ required: true, message: '设备类型必选!', trigger: 'change' }],
manufacturer:[{ required: true, message: '生产厂家必选!', trigger: 'change' }],
ip: [
{ required: true, message: 'IP地址必填', trigger: 'blur' },
{ pattern: /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, message: 'IP地址格式错误', trigger: 'blur' }
],
port: [
{ required: true, message: '端口号必填!', trigger: 'blur' },
{ pattern: /^(6553[0-5]|655[0-2][0-9]|64[0-9]{3}|[1-5]?[0-9]{1,4})$/, message: '端口号范围0到65535的整数', trigger: 'blur' }
],
inspectChannel:[ { required: true, message: '可检通道数必选', trigger: 'change' }],
encryptionFlag: [{ required: true, message: '是否加密必选!', trigger: 'change' }],
series: [{ required: true, message: '请输入识别码', trigger: 'blur' }],
devKey: [{ required: true, message: '请输入密钥', trigger: 'blur' }],
protocol: [{required: true, message: '通讯协议必选!', trigger: 'change'}],
})
// 关闭弹窗
const close = () => {
dialogVisible.value = false
// 清空dialogForm中的值
resetFormContent()
// 重置表单
dialogFormRef.value?.resetFields()
}
// 保存数据
const save = () => {
try {
dialogFormRef.value?.validate(async (valid: boolean) => {
if (formContent.encryptionFlag === 0) {
formContent.series = ''
formContent.devKey = ''
}
if (valid) {
//保存时判是否加密,把识别码密钥字段清空
if(formContent.encryptionFlag === 0){
formContent.series = null
formContent.devKey = null
}
// 可检通道转为字符串逗号分隔(保存前临时转换) // 可检通道转为字符串逗号分隔(保存前临时转换)
let originalInspectChannel = formContent.inspectChannel; // 保存原始值 let originalInspectChannel = formContent.inspectChannel; // 保存原始值
//可检通道转为字符串逗号分隔 //可检通道转为字符串逗号分隔
// 确保 inspectChannel 是数组再执行 join // 确保 inspectChannel 是数组再执行 join
if (Array.isArray(formContent.inspectChannel)) { if (Array.isArray(formContent.inspectChannel)) {
formContent.inspectChannel = formContent.inspectChannel formContent.inspectChannel = formContent.inspectChannel
.map(Number) // 将值转为数字以保证正确排序 .map(Number) // 将值转为数字以保证正确排序
.sort((a, b) => a - b) // 数字升序排序 .sort((a, b) => a - b) // 数字升序排序
.map(String) // 恢复为字符串用于保存 .map(String) // 恢复为字符串用于保存
.join(','); .join(',');
} }
try { try {
if (formContent.id) { if (formContent.id) {
await updatePqStandardDev(formContent); await updatePqStandardDev(formContent);
ElMessage.success({ message: `${dialogTitle.value}成功!` }) ElMessage.success({message: `${dialogTitle.value}成功!`})
} else { } else {
// 新增需要把通讯协议转成字典ID // 新增需要把通讯协议转成字典ID
const protocolItem = dictStore.getDictData('Protocol').find(item => item.name === formContent.protocol); const protocolItem = dictStore.getDictData('Protocol').find(item => item.name === formContent.protocol);
if (protocolItem) { if (protocolItem) {
@@ -233,7 +273,7 @@ const pqChannelArray = ref([
} }
await addPqStandardDev(formContent); await addPqStandardDev(formContent);
ElMessage.success({ message: `${dialogTitle.value}成功!` }) ElMessage.success({message: `${dialogTitle.value}成功!`})
} }
close() close()
// 刷新表格 // 刷新表格
@@ -249,7 +289,7 @@ const pqChannelArray = ref([
formContent.inspectChannel = formContent.inspectChannel.split(',').filter(Boolean); formContent.inspectChannel = formContent.inspectChannel.split(',').filter(Boolean);
} }
} }
}) })
} catch (err) { } catch (err) {
console.error('验证过程中出现错误', err) console.error('验证过程中出现错误', err)
@@ -258,20 +298,22 @@ const pqChannelArray = ref([
// 打开弹窗,可能是新增,也可能是编辑 // 打开弹窗,可能是新增,也可能是编辑
const open = async (sign: string, data: StandardDevice.ResPqStandardDevice,devType:Device.ResDev[]) => { const open = async (sign: string, data: StandardDevice.ResPqStandardDevice, devType: Device.ResDev[]) => {
// 重置表单 activeName.value = 'devInfo'
dialogFormRef.value?.resetFields() // 重置表单
devTypeOptions.value = devType dialogFormRef.value?.resetFields()
titleType.value = sign devTypeOptions.value = devType
if (data.id) { titleType.value = sign
Object.assign(formContent,{ ...data }) if (data.id) {
if (typeof formContent.inspectChannel === 'string') { Object.assign(formContent, {...data})
formContent.inspectChannel = formContent.inspectChannel.split(',').filter(Boolean) console.log(formContent)
} if (typeof formContent.inspectChannel === 'string') {
//handleDevTypeChange(data.devType) formContent.inspectChannel = formContent.inspectChannel.split(',').filter(Boolean)
} else {
resetFormContent()
} }
//handleDevTypeChange(data.devType)
} else {
resetFormContent()
}
dialogVisible.value = true dialogVisible.value = true
@@ -281,38 +323,38 @@ const open = async (sign: string, data: StandardDevice.ResPqStandardDevice,devTy
const handleDevTypeChange = (value: string) => { const handleDevTypeChange = (value: string) => {
// 在这里处理选中事件的逻辑 // 在这里处理选中事件的逻辑
const dev = devTypeOptions.value.find(t =>t.id === value) const dev = devTypeOptions.value.find(t => t.id === value)
if (dev) { if (dev) {
const maxChannel = dev.devChns const maxChannel = dev.devChns
// 动态设置 pqChannelArray 从 1 到 dev.devChns.length // 动态设置 pqChannelArray 从 1 到 dev.devChns.length
pqChannelArray.value = Array.from({ length: dev.devChns }, (_, i) => ({ pqChannelArray.value = Array.from({length: dev.devChns}, (_, i) => ({
value: String(i + 1), value: String(i + 1),
label: String(i + 1), label: String(i + 1),
})) }))
//if(titleType.value == 'add') // 默认全选所有通道 //if(titleType.value == 'add') // 默认全选所有通道
formContent.inspectChannel = pqChannelArray.value.map(channel => channel.value) formContent.inspectChannel = pqChannelArray.value.map(channel => channel.value)
// 过滤掉超出新通道数范围的选项 // 过滤掉超出新通道数范围的选项
if (Array.isArray(formContent.inspectChannel)) { if (Array.isArray(formContent.inspectChannel)) {
formContent.inspectChannel = formContent.inspectChannel.filter( formContent.inspectChannel = formContent.inspectChannel.filter(
(channel) => parseInt(channel, 10) <= maxChannel (channel) => parseInt(channel, 10) <= maxChannel
) )
} }
} else { } else {
// 可选:恢复默认值 // 可选:恢复默认值
pqChannelArray.value = [ pqChannelArray.value = [
{ value: '1', label: '1' }, {value: '1', label: '1'},
{ value: '2', label: '2' }, {value: '2', label: '2'},
{ value: '3', label: '3' }, {value: '3', label: '3'},
{ value: '4', label: '4' }, {value: '4', label: '4'},
] ]
} }
} }
// 对外映射 // 对外映射
defineExpose({ open }) defineExpose({open})
const props = defineProps<{ const props = defineProps<{
refreshTable: (() => Promise<void>) | undefined; refreshTable: (() => Promise<void>) | undefined;
}>() }>()

View File

@@ -260,7 +260,7 @@ const viewDialog = ref(false)
const dictStore = useDictStore() const dictStore = useDictStore()
const activeName = ref('') const activeName = ref('')
const childActiveName = ref('') const childActiveName = ref('')
const firstName = 'first' const firstName = ref('first')
const viewRowRef = ref() const viewRowRef = ref()
const communicationList = ref([]) const communicationList = ref([])
const testProjectPopupRef = ref() const testProjectPopupRef = ref()

View File

@@ -99,13 +99,11 @@ const handleInputRetainTime = value => {
ElMessage.warning("持续时间不能小于0周波") ElMessage.warning("持续时间不能小于0周波")
props.childForm[0].dipData.retainTime = 0 props.childForm[0].dipData.retainTime = 0
emit('setRetainTime', 0 ) emit('setRetainTime', 0 )
}else if (value > 300) { }else if (value > 3000) {
ElMessage.warning("持续时间不能大于300周波") ElMessage.warning("持续时间不能大于3000周波")
props.childForm[0].dipData.retainTime = 300 props.childForm[0].dipData.retainTime = 3000
emit('setRetainTime', 300 ) emit('setRetainTime', 3000 )
}else{ }else{
emit('setRetainTime', value ) emit('setRetainTime', value )
} }
} }

Some files were not shown because too many files have changed in this diff Show More