Files
egm/webui/src/components/ParameterForm.vue
dmy 6ebfcf848d feat: 添加基于pywebview的图形界面支持
新增图形界面模块webui,使用Vue 3 + Quasar + TypeScript + Tailwind CSS开发
扩展README文档说明图形界面使用方法
更新.gitignore忽略前端相关文件
添加Python版本配置文件
2026-03-02 19:39:28 +08:00

432 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<q-layout view="lHh lpr lFf">
<q-header elevated class="bg-indigo-600 text-white">
<q-toolbar>
<q-toolbar-title>
<div class="flex items-center gap-2">
<q-icon name="flash_on" size="md" />
<span class="text-xl font-bold">EGM 输电线路绕击跳闸率计算</span>
</div>
</q-toolbar-title>
</q-toolbar>
</q-header>
<q-page-container>
<q-page class="q-pa-md">
<div class="max-w-4xl mx-auto">
<!-- 基本参数 -->
<q-card class="q-mb-md shadow-2">
<q-card-section class="bg-indigo-50">
<div class="text-h6 text-indigo-900 flex items-center gap-2">
<q-icon name="settings" />
基本参数
</div>
</q-card-section>
<q-card-section>
<div class="row q-col-gutter-md">
<div class="col-12 col-md-6">
<q-input
v-model="params.parameter.rated_voltage"
type="number"
label="额定电压等级 (kV)"
hint="输电线路的额定电压"
:rules="[val => val > 0 || '必须大于0']"
/>
</div>
<div class="col-12 col-md-6">
<q-input
v-model="params.parameter.h_c_sag"
type="number"
step="0.01"
label="导线弧垂 (m)"
hint="导线的平均弧垂"
/>
</div>
<div class="col-12 col-md-6">
<q-input
v-model="params.parameter.h_g_sag"
type="number"
step="0.01"
label="地线弧垂 (m)"
hint="地线的平均弧垂"
/>
</div>
<div class="col-12 col-md-6">
<q-input
v-model="params.parameter.insulator_c_len"
type="number"
step="0.01"
label="导线串子绝缘长度 (m)"
hint="绝缘子的有效绝缘距离"
/>
</div>
<div class="col-12 col-md-6">
<q-input
v-model="params.parameter.string_c_len"
type="number"
step="0.1"
label="导线串长 (m)"
hint="导线串的总长度"
/>
</div>
<div class="col-12 col-md-6">
<q-input
v-model="params.parameter.string_g_len"
type="number"
step="0.1"
label="地线串长 (m)"
hint="地线串的总长度"
/>
</div>
<div class="col-12 col-md-6">
<q-input
v-model="params.parameter.altitude"
type="number"
label="海拔高度 (m)"
hint="线路所在地的海拔"
/>
</div>
<div class="col-12 col-md-6">
<q-input
v-model="params.parameter.td"
type="number"
label="雷暴日 (d)"
hint="年雷暴日数"
/>
</div>
</div>
<!-- 地线挂点高度 -->
<div class="q-mt-md">
<div class="text-subtitle2 q-mb-sm">
地线挂点垂直距离 (m) - 第一个值为地线挂点高度
</div>
<div class="row q-col-gutter-sm">
<div class="col" v-for="(h, index) in params.parameter.h_arm" :key="index">
<q-input
v-model="params.parameter.h_arm[index]"
type="number"
step="0.1"
:label="index === 0 ? '地线' : `导线 ${index}`"
dense
/>
</div>
<div class="col-auto">
<q-btn flat round color="primary" icon="add" @click="addHArm" />
</div>
<div class="col-auto">
<q-btn flat round color="negative" icon="remove" @click="removeHArm" :disable="params.parameter.h_arm.length <= 1" />
</div>
</div>
</div>
<!-- 地线水平坐标 -->
<div class="q-mt-md">
<div class="text-subtitle2 q-mb-sm">
地线水平坐标 (m)
</div>
<div class="row q-col-gutter-sm">
<div class="col" v-for="(x, index) in params.parameter.gc_x" :key="index">
<q-input
v-model="params.parameter.gc_x[index]"
type="number"
step="0.1"
:label="index === 0 ? '地线' : `导线 ${index}`"
dense
/>
</div>
<div class="col-auto">
<q-btn flat round color="primary" icon="add" @click="addGcX" />
</div>
<div class="col-auto">
<q-btn flat round color="negative" icon="remove" @click="removeGcX" :disable="params.parameter.gc_x.length <= 1" />
</div>
</div>
</div>
<!-- 地面倾角 -->
<div class="q-mt-md">
<div class="text-subtitle2 q-mb-sm">
地面倾角 (°) - 向下为正
</div>
<div class="row q-col-gutter-sm">
<div class="col" v-for="(angle, index) in params.parameter.ground_angels" :key="index">
<q-input
v-model="params.parameter.ground_angels[index]"
type="number"
step="1"
:label="`角度 ${index + 1}`"
dense
/>
</div>
<div class="col-auto">
<q-btn flat round color="primary" icon="add" @click="addGroundAngel" />
</div>
<div class="col-auto">
<q-btn flat round color="negative" icon="remove" @click="removeGroundAngel" :disable="params.parameter.ground_angels.length <= 1" />
</div>
</div>
</div>
</q-card-section>
</q-card>
<!-- 高级参数 -->
<q-card class="q-mb-md shadow-2">
<q-card-section class="bg-indigo-50">
<div class="text-h6 text-indigo-900 flex items-center gap-2">
<q-icon name="tune" />
高级参数
</div>
</q-card-section>
<q-card-section>
<div class="row q-col-gutter-md">
<div class="col-12 col-md-6">
<q-input
v-model="params.advance.ng"
type="number"
step="0.01"
label="地闪密度 (次/(km²·a))"
hint="大于0时使用此值否则通过雷暴日计算"
/>
</div>
<div class="col-12 col-md-6">
<q-input
v-model="params.advance.Ip_a"
type="number"
step="0.01"
label="雷电流概率密度曲线系数 a"
hint="大于0时使用此值"
/>
</div>
<div class="col-12 col-md-6">
<q-input
v-model="params.advance.Ip_b"
type="number"
step="0.01"
label="雷电流概率密度曲线系数 b"
hint="大于0时使用此值"
/>
</div>
</div>
</q-card-section>
</q-card>
<!-- 可选参数 -->
<q-card class="q-mb-md shadow-2">
<q-card-section class="bg-indigo-50">
<div class="text-h6 text-indigo-900 flex items-center gap-2">
<q-icon name="more_horiz" />
可选参数
</div>
</q-card-section>
<q-card-section>
<div class="row q-col-gutter-md">
<div class="col-12 col-md-6">
<q-input
v-model="params.optional.voltage_n"
type="number"
label="计算时电压分成多少份"
hint="考虑电压波动影响"
/>
</div>
<div class="col-12 col-md-6">
<q-input
v-model="params.optional.max_i"
type="number"
label="最大尝试雷电流 (kA)"
hint="迭代计算的最大电流"
/>
</div>
</div>
</q-card-section>
</q-card>
<!-- 操作按钮 -->
<div class="row q-gutter-md justify-center q-mt-lg">
<q-btn
color="primary"
size="lg"
label="开始计算"
icon="calculate"
@click="calculate"
:loading="calculating"
class="px-8"
/>
<q-btn
color="grey-7"
size="lg"
label="重置参数"
icon="refresh"
@click="resetParams"
class="px-8"
/>
<q-btn
color="positive"
size="lg"
label="导出配置"
icon="download"
@click="exportConfig"
class="px-8"
/>
</div>
<!-- 计算结果 -->
<q-card v-if="result" class="q-mt-md shadow-2 bg-green-50">
<q-card-section class="bg-green-100">
<div class="text-h6 text-green-900 flex items-center gap-2">
<q-icon name="check_circle" />
计算结果
</div>
</q-card-section>
<q-card-section>
<pre class="text-caption bg-white q-pa-md rounded">{{ result }}</pre>
</q-card-section>
</q-card>
<!-- 错误信息 -->
<q-card v-if="error" class="q-mt-md shadow-2 bg-red-50">
<q-card-section>
<div class="text-negative q-mb-sm flex items-center gap-2">
<q-icon name="error" />
错误信息
</div>
<p class="text-negative">{{ error }}</p>
</q-card-section>
</q-card>
</div>
</q-page>
</q-page-container>
</q-layout>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { AllParameters } from '@/types'
// 默认参数
const defaultParams: AllParameters = {
parameter: {
rated_voltage: 750,
h_c_sag: 14.43,
h_g_sag: 11.67,
insulator_c_len: 7.02,
string_c_len: 9.2,
string_g_len: 0.5,
h_arm: [150, 130],
gc_x: [17.9, 17],
ground_angels: [0],
altitude: 1000,
td: 20
},
advance: {
ng: -1,
Ip_a: -1,
Ip_b: -1
},
optional: {
voltage_n: 3,
max_i: 200
}
}
const params = reactive<AllParameters>(JSON.parse(JSON.stringify(defaultParams)))
const calculating = ref(false)
const result = ref<string | null>(null)
const error = ref<string | null>(null)
// 数组操作函数
const addHArm = () => {
const last = params.parameter.h_arm[params.parameter.h_arm.length - 1] || 100
params.parameter.h_arm.push(last - 20)
}
const removeHArm = () => {
if (params.parameter.h_arm.length > 1) {
params.parameter.h_arm.pop()
}
}
const addGcX = () => {
const last = params.parameter.gc_x[params.parameter.gc_x.length - 1] || 10
params.parameter.gc_x.push(last)
}
const removeGcX = () => {
if (params.parameter.gc_x.length > 1) {
params.parameter.gc_x.pop()
}
}
const addGroundAngel = () => {
params.parameter.ground_angels.push(0)
}
const removeGroundAngel = () => {
if (params.parameter.ground_angels.length > 1) {
params.parameter.ground_angels.pop()
}
}
// 计算函数
const calculate = async () => {
calculating.value = true
result.value = null
error.value = null
try {
// 调用 pywebview 的 Python 函数
if (window.pywebview) {
const response = await window.pywebview.api.calculate(params)
result.value = JSON.stringify(response, null, 2)
} else {
// 开发模式下的模拟
await new Promise(resolve => setTimeout(resolve, 1000))
result.value = JSON.stringify({
success: true,
message: '计算完成(开发模式)',
data: {
tripping_rate: '0.0581 次/(100km·a)',
parameters: params
}
}, null, 2)
}
} catch (e: any) {
error.value = e.message || '计算失败'
} finally {
calculating.value = false
}
}
// 重置参数
const resetParams = () => {
Object.assign(params, JSON.parse(JSON.stringify(defaultParams)))
result.value = null
error.value = null
}
// 导出配置
const exportConfig = () => {
const config = JSON.stringify(params, null, 2)
const blob = new Blob([config], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'egm_config.json'
a.click()
URL.revokeObjectURL(url)
}
// 声明 pywebview API 类型
declare global {
interface Window {
pywebview?: {
api: {
calculate: (params: AllParameters) => Promise<any>
}
}
}
}
</script>