feat: 添加运行日志组件并集成到参数表单
在参数表单中添加运行日志组件,用于显示计算过程中的日志信息 后端增加日志处理功能,将计算日志返回给前端显示 优化tsconfig配置,添加路径别名支持
This commit is contained in:
106
webui/src/components/Log.vue
Normal file
106
webui/src/components/Log.vue
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<template>
|
||||||
|
<q-card class="q-mt-md shadow-2">
|
||||||
|
<q-card-section class="bg-blue-grey-1">
|
||||||
|
<div class="text-h6 text-blue-grey-9 flex items-center gap-2">
|
||||||
|
<q-icon name="terminal" />
|
||||||
|
运行日志
|
||||||
|
<q-space />
|
||||||
|
<q-btn flat dense round icon="delete" @click="clearLog" size="sm" />
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section class="q-pa-none">
|
||||||
|
<div ref="logContainer" class="log-container">
|
||||||
|
<div
|
||||||
|
v-for="(log, index) in logs"
|
||||||
|
:key="index"
|
||||||
|
:class="['log-line', `log-${log.level}`]"
|
||||||
|
>
|
||||||
|
<span class="log-time">{{ log.time }}</span>
|
||||||
|
<span class="log-message">{{ log.message }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, nextTick } from 'vue'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'LogComponent'
|
||||||
|
})
|
||||||
|
|
||||||
|
interface LogEntry {
|
||||||
|
level: 'info' | 'warning' | 'error' | 'debug'
|
||||||
|
time: string
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const logs = ref<LogEntry[]>([])
|
||||||
|
const logContainer = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
const addLog = (level: LogEntry['level'], message: string) => {
|
||||||
|
const now = new Date()
|
||||||
|
const time = now.toLocaleTimeString('zh-CN', { hour12: false })
|
||||||
|
|
||||||
|
logs.value.push({ level, time, message })
|
||||||
|
|
||||||
|
// 自动滚动到底部
|
||||||
|
nextTick(() => {
|
||||||
|
if (logContainer.value) {
|
||||||
|
logContainer.value.scrollTop = logContainer.value.scrollHeight
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearLog = () => {
|
||||||
|
logs.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
addLog,
|
||||||
|
clearLog
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.log-container {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
font-family: 'Consolas', 'Monaco', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 8px;
|
||||||
|
user-select: text;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-line {
|
||||||
|
padding: 2px 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-time {
|
||||||
|
color: #6a9955;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-info .log-message {
|
||||||
|
color: #d4d4d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-warning .log-message {
|
||||||
|
color: #dcdcaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-error .log-message {
|
||||||
|
color: #f48771;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-debug .log-message {
|
||||||
|
color: #9cdcfe;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -296,6 +296,9 @@
|
|||||||
<p class="text-negative">{{ error }}</p>
|
<p class="text-negative">{{ error }}</p>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
|
<!-- 运行日志 -->
|
||||||
|
<LogComponent ref="logRef" />
|
||||||
</div>
|
</div>
|
||||||
</q-page>
|
</q-page>
|
||||||
</q-page-container>
|
</q-page-container>
|
||||||
@@ -305,6 +308,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive } from 'vue'
|
import { ref, reactive } from 'vue'
|
||||||
import type { AllParameters } from '@/types'
|
import type { AllParameters } from '@/types'
|
||||||
|
import LogComponent from './Log.vue'
|
||||||
|
|
||||||
// 默认参数
|
// 默认参数
|
||||||
const defaultParams: AllParameters = {
|
const defaultParams: AllParameters = {
|
||||||
@@ -336,6 +340,7 @@ const params = reactive<AllParameters>(JSON.parse(JSON.stringify(defaultParams))
|
|||||||
const calculating = ref(false)
|
const calculating = ref(false)
|
||||||
const result = ref<string | null>(null)
|
const result = ref<string | null>(null)
|
||||||
const error = ref<string | null>(null)
|
const error = ref<string | null>(null)
|
||||||
|
const logRef = ref<InstanceType<typeof LogComponent> | null>(null)
|
||||||
|
|
||||||
// 数组操作函数
|
// 数组操作函数
|
||||||
const addHArm = () => {
|
const addHArm = () => {
|
||||||
@@ -379,11 +384,32 @@ const calculate = async () => {
|
|||||||
try {
|
try {
|
||||||
// 调用 pywebview 的 Python 函数
|
// 调用 pywebview 的 Python 函数
|
||||||
if (window.pywebview) {
|
if (window.pywebview) {
|
||||||
|
logRef.value?.addLog('info', '开始调用后端计算...')
|
||||||
const response = await window.pywebview.api.calculate(params)
|
const response = await window.pywebview.api.calculate(params)
|
||||||
|
logRef.value?.addLog('info', `后端返回 keys: ${Object.keys(response).join(', ')}`)
|
||||||
|
logRef.value?.addLog('info', `DEBUG_VERSION: ${response.DEBUG_VERSION || '无'}`)
|
||||||
|
logRef.value?.addLog('info', `后端返回: ${JSON.stringify(response).substring(0, 150)}`)
|
||||||
|
|
||||||
|
// 显示日志
|
||||||
|
console.log('[DEBUG] response:', response)
|
||||||
|
const logs = response.logs
|
||||||
|
if (Array.isArray(logs) && logs.length > 0) {
|
||||||
|
logRef.value?.addLog('info', `收到 ${logs.length} 条日志`)
|
||||||
|
for (const log of logs) {
|
||||||
|
logRef.value?.addLog(log.level as any, log.message)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logRef.value?.addLog('warning', '后端未返回日志')
|
||||||
|
logRef.value?.addLog('warning', `response.logs = ${JSON.stringify(logs)}`)
|
||||||
|
}
|
||||||
|
|
||||||
result.value = JSON.stringify(response, null, 2)
|
result.value = JSON.stringify(response, null, 2)
|
||||||
} else {
|
} else {
|
||||||
// 开发模式下的模拟
|
// 开发模式下的模拟
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
logRef.value?.addLog('info', '开始 EGM 计算(开发模式)...')
|
||||||
|
logRef.value?.addLog('info', '参数: 额定电压=750kV, 雷暴日=20d, 海拔=1000m')
|
||||||
|
logRef.value?.addLog('info', '计算完成')
|
||||||
result.value = JSON.stringify({
|
result.value = JSON.stringify({
|
||||||
success: true,
|
success: true,
|
||||||
message: '计算完成(开发模式)',
|
message: '计算完成(开发模式)',
|
||||||
@@ -395,6 +421,7 @@ const calculate = async () => {
|
|||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
error.value = e.message || '计算失败'
|
error.value = e.message || '计算失败'
|
||||||
|
logRef.value?.addLog('error', e.message || '计算失败')
|
||||||
} finally {
|
} finally {
|
||||||
calculating.value = false
|
calculating.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,25 @@
|
|||||||
{"compilerOptions": {"target": "ES2020", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], "skipLibCheck": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "preserve", "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true}, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], "references": [{"path": "./tsconfig.node.json"}]}
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import sys
|
|||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any, List
|
||||||
|
from datetime import datetime
|
||||||
import webview
|
import webview
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -26,11 +27,46 @@ from core import (
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
class WebHandler:
|
||||||
|
"""Web日志处理器"""
|
||||||
|
def __init__(self, callback=None):
|
||||||
|
self.callback = callback
|
||||||
|
self.logs: List[Dict[str, str]] = []
|
||||||
|
|
||||||
|
def write(self, message):
|
||||||
|
if message.strip():
|
||||||
|
log_entry = {
|
||||||
|
"level": "info",
|
||||||
|
"time": datetime.now().strftime("%H:%M:%S"),
|
||||||
|
"message": message.strip()
|
||||||
|
}
|
||||||
|
self.logs.append(log_entry)
|
||||||
|
if self.callback:
|
||||||
|
self.callback(log_entry)
|
||||||
|
|
||||||
|
|
||||||
class EGMWebApp:
|
class EGMWebApp:
|
||||||
"""EGM 计算程序的 Web 界面后端"""
|
"""EGM 计算程序的 Web 界面后端"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.window = None
|
self.window = None
|
||||||
|
self.web_handler = None
|
||||||
|
self.logs: List[Dict[str, str]] = []
|
||||||
|
|
||||||
|
def add_log(self, level: str, message: str):
|
||||||
|
"""添加日志"""
|
||||||
|
log_entry = {
|
||||||
|
"level": level,
|
||||||
|
"time": datetime.now().strftime("%H:%M:%S"),
|
||||||
|
"message": message
|
||||||
|
}
|
||||||
|
self.logs.append(log_entry)
|
||||||
|
|
||||||
|
def get_logs(self) -> List[Dict[str, str]]:
|
||||||
|
"""获取日志列表"""
|
||||||
|
logs = self.logs.copy()
|
||||||
|
self.logs = []
|
||||||
|
return logs
|
||||||
|
|
||||||
def calculate(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
def calculate(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
@@ -42,9 +78,10 @@ class EGMWebApp:
|
|||||||
Returns:
|
Returns:
|
||||||
计算结果字典
|
计算结果字典
|
||||||
"""
|
"""
|
||||||
try:
|
self.logs = [] # 清空日志
|
||||||
logger.info("开始 EGM 计算...")
|
self.add_log("info", "开始 EGM 计算...")
|
||||||
|
|
||||||
|
try:
|
||||||
# 解析参数
|
# 解析参数
|
||||||
parameter_data = params.get('parameter', {})
|
parameter_data = params.get('parameter', {})
|
||||||
advance_data = params.get('advance', {})
|
advance_data = params.get('advance', {})
|
||||||
@@ -73,27 +110,54 @@ class EGMWebApp:
|
|||||||
para.voltage_n = int(optional_data.get('voltage_n', 3))
|
para.voltage_n = int(optional_data.get('voltage_n', 3))
|
||||||
para.max_i = float(optional_data.get('max_i', 200))
|
para.max_i = float(optional_data.get('max_i', 200))
|
||||||
|
|
||||||
logger.info(f"参数: 额定电压={para.rated_voltage}kV, 雷暴日={para.td}d, 海拔={para.altitude}m")
|
self.add_log("info", f"参数: 额定电压={para.rated_voltage}kV, 雷暴日={para.td}d, 海拔={para.altitude}m")
|
||||||
|
|
||||||
|
logger.info("开始执行 _do_calculate...")
|
||||||
|
|
||||||
# 执行实际计算
|
# 执行实际计算
|
||||||
|
self.add_log("info", "EGM 计算中...")
|
||||||
result = self._do_calculate()
|
result = self._do_calculate()
|
||||||
|
|
||||||
logger.info("EGM 计算完成")
|
# 调试输出
|
||||||
return result
|
logger.info(f"_do_calculate 返回 keys: {list(result.keys())}")
|
||||||
|
logger.info(f"日志列表: {self.logs}")
|
||||||
|
|
||||||
|
# 将日志添加到结果中(在返回之前添加最后一条日志)
|
||||||
|
self.add_log("info", "EGM 计算完成")
|
||||||
|
|
||||||
|
# 创建一个新的返回值,确保 logs 字段被包含
|
||||||
|
final_result = {
|
||||||
|
"success": result.get("success", True),
|
||||||
|
"message": result.get("message", "计算完成"),
|
||||||
|
"data": result.get("data", {}),
|
||||||
|
"logs": self.logs,
|
||||||
|
"DEBUG_VERSION": "v2" # 标记版本
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"最终返回结果 keys: {list(final_result.keys())}")
|
||||||
|
logger.info(f"日志数量: {len(self.logs)}")
|
||||||
|
logger.info(f"DEBUG: self.logs = {self.logs}")
|
||||||
|
|
||||||
|
return final_result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"计算失败: {str(e)}")
|
self.add_log("error", f"计算失败: {str(e)}")
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"message": f"计算失败: {str(e)}",
|
"message": f"计算失败: {str(e)}",
|
||||||
"error": str(e)
|
"error": str(e),
|
||||||
|
"logs": self.logs
|
||||||
}
|
}
|
||||||
|
|
||||||
def _do_calculate(self) -> Dict[str, Any]:
|
def _do_calculate(self) -> Dict[str, Any]:
|
||||||
"""执行实际的EGM计算"""
|
"""执行实际的EGM计算"""
|
||||||
h_whole = para.h_arm[0]
|
try:
|
||||||
|
h_whole = para.h_arm[0]
|
||||||
|
except Exception as e:
|
||||||
|
self.add_log("error", f"获取参数失败: {str(e)}")
|
||||||
|
raise
|
||||||
string_g_len = para.string_g_len
|
string_g_len = para.string_g_len
|
||||||
string_c_len = para.string_c_len
|
string_c_len = para.string_c_len
|
||||||
h_g_sag = para.h_g_sag
|
h_g_sag = para.h_g_sag
|
||||||
@@ -123,7 +187,7 @@ class EGMWebApp:
|
|||||||
results = []
|
results = []
|
||||||
|
|
||||||
for ground_angel in ground_angels:
|
for ground_angel in ground_angels:
|
||||||
logger.info(f"地面倾角 {ground_angel / math.pi * 180:.3f}°")
|
self.add_log("info", f"地面倾角 {ground_angel / math.pi * 180:.3f}°")
|
||||||
rg_type = None
|
rg_type = None
|
||||||
rg_x = None
|
rg_x = None
|
||||||
rg_y = None
|
rg_y = None
|
||||||
@@ -205,11 +269,11 @@ class EGMWebApp:
|
|||||||
if min_distance_intersection < 0.1:
|
if min_distance_intersection < 0.1:
|
||||||
break
|
break
|
||||||
|
|
||||||
logger.info(f"最大电流为 {i_max:.2f}, 最小电流为 {i_min:.2f}")
|
self.add_log("info", f"最大电流为 {i_max:.2f}, 最小电流为 {i_min:.2f}")
|
||||||
|
|
||||||
curt_fineness = 0.1
|
curt_fineness = 0.1
|
||||||
if i_min > i_max or abs(i_min - i_max) < curt_fineness:
|
if i_min > i_max or abs(i_min - i_max) < curt_fineness:
|
||||||
logger.info("最大电流小于等于最小电流,没有暴露弧。")
|
self.add_log("info", "最大电流小于等于最小电流,没有暴露弧。")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
curt_segment_n = int((i_max - i_min) / curt_fineness)
|
curt_segment_n = int((i_max - i_min) / curt_fineness)
|
||||||
@@ -249,7 +313,7 @@ class EGMWebApp:
|
|||||||
avr_n_sf += n_sf / voltage_n
|
avr_n_sf += n_sf / voltage_n
|
||||||
n_sf_phases[phase_conductor_foo][u_bar] = n_sf
|
n_sf_phases[phase_conductor_foo][u_bar] = n_sf
|
||||||
|
|
||||||
logger.info(f"相{phase_conductor_foo + 1}, 跳闸率: {n_sf:.16f} 次/(100km·a)")
|
self.add_log("info", f"相{phase_conductor_foo + 1}, 跳闸率: {n_sf:.16f} 次/(100km·a)")
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"ground_angle": f"{ground_angel / math.pi * 180:.3f}°",
|
"ground_angle": f"{ground_angel / math.pi * 180:.3f}°",
|
||||||
|
|||||||
Reference in New Issue
Block a user