2026-03-02 19:39:28 +08:00
|
|
|
|
<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">
|
2026-03-02 21:17:34 +08:00
|
|
|
|
<q-select
|
2026-03-02 19:39:28 +08:00
|
|
|
|
v-model="params.parameter.rated_voltage"
|
2026-03-02 21:17:34 +08:00
|
|
|
|
:options="voltageOptions"
|
2026-03-02 19:39:28 +08:00
|
|
|
|
label="额定电压等级 (kV)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2026-03-02 22:09:46 +08:00
|
|
|
|
<div class="col-12 col-md-6">
|
|
|
|
|
|
<q-input
|
|
|
|
|
|
:model-value="currentType"
|
|
|
|
|
|
label="电流类型"
|
|
|
|
|
|
readonly
|
|
|
|
|
|
>
|
|
|
|
|
|
<q-tooltip>交流(AC)或直流(DC),由电压等级自动判断</q-tooltip>
|
|
|
|
|
|
</q-input>
|
|
|
|
|
|
</div>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
<div class="col-12 col-md-6">
|
|
|
|
|
|
<q-input
|
|
|
|
|
|
v-model="params.parameter.h_c_sag"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
step="0.01"
|
|
|
|
|
|
label="导线弧垂 (m)"
|
2026-03-02 21:52:20 +08:00
|
|
|
|
>
|
|
|
|
|
|
<q-tooltip>导线在最大弧垂状态下相对于挂点的垂直距离</q-tooltip>
|
|
|
|
|
|
</q-input>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-12 col-md-6">
|
|
|
|
|
|
<q-input
|
|
|
|
|
|
v-model="params.parameter.h_g_sag"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
step="0.01"
|
|
|
|
|
|
label="地线弧垂 (m)"
|
2026-03-02 21:52:20 +08:00
|
|
|
|
>
|
|
|
|
|
|
<q-tooltip>地线在最大弧垂状态下相对于挂点的垂直距离</q-tooltip>
|
|
|
|
|
|
</q-input>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-12 col-md-6">
|
|
|
|
|
|
<q-input
|
|
|
|
|
|
v-model="params.parameter.insulator_c_len"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
step="0.01"
|
|
|
|
|
|
label="导线串子绝缘长度 (m)"
|
2026-03-02 21:52:20 +08:00
|
|
|
|
>
|
|
|
|
|
|
<q-tooltip>绝缘子串的总长度</q-tooltip>
|
|
|
|
|
|
</q-input>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-12 col-md-6">
|
|
|
|
|
|
<q-input
|
|
|
|
|
|
v-model="params.parameter.string_c_len"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
step="0.1"
|
|
|
|
|
|
label="导线串长 (m)"
|
2026-03-02 21:52:20 +08:00
|
|
|
|
>
|
|
|
|
|
|
<q-tooltip>导线绝缘子串的总长度</q-tooltip>
|
|
|
|
|
|
</q-input>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-12 col-md-6">
|
|
|
|
|
|
<q-input
|
|
|
|
|
|
v-model="params.parameter.string_g_len"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
step="0.1"
|
|
|
|
|
|
label="地线串长 (m)"
|
2026-03-02 21:52:20 +08:00
|
|
|
|
>
|
|
|
|
|
|
<q-tooltip>地线绝缘子串的总长度</q-tooltip>
|
|
|
|
|
|
</q-input>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-12 col-md-6">
|
|
|
|
|
|
<q-input
|
|
|
|
|
|
v-model="params.parameter.altitude"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
label="海拔高度 (m)"
|
2026-03-02 21:52:20 +08:00
|
|
|
|
>
|
|
|
|
|
|
<q-tooltip>用于修正绝缘子串的闪络电压</q-tooltip>
|
|
|
|
|
|
</q-input>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-12 col-md-6">
|
|
|
|
|
|
<q-input
|
|
|
|
|
|
v-model="params.parameter.td"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
label="雷暴日 (d)"
|
2026-03-02 23:04:16 +08:00
|
|
|
|
:disable="isTdDisabled"
|
2026-03-02 21:52:20 +08:00
|
|
|
|
>
|
|
|
|
|
|
<q-tooltip>一年中雷暴天数,用于计算地闪密度</q-tooltip>
|
|
|
|
|
|
</q-input>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 导、地线挂点高度 -->
|
|
|
|
|
|
<div class="q-mt-md">
|
|
|
|
|
|
<div class="text-subtitle2 q-mb-sm">
|
2026-03-02 21:58:09 +08:00
|
|
|
|
导、地线挂点垂直坐标 (m)
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</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">
|
2026-03-02 21:58:09 +08:00
|
|
|
|
<q-btn flat round color="primary" icon="add" @click="addHArm" :disable="params.parameter.h_arm.length >= 4" />
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-auto">
|
2026-03-02 21:58:09 +08:00
|
|
|
|
<q-btn flat round color="negative" icon="remove" @click="removeHArm" :disable="params.parameter.h_arm.length <= 2" />
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 导、地线水平坐标 -->
|
|
|
|
|
|
<div class="q-mt-md">
|
|
|
|
|
|
<div class="text-subtitle2 q-mb-sm">
|
2026-03-02 21:58:09 +08:00
|
|
|
|
导、地线挂点水平坐标 (m)
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</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">
|
2026-03-02 21:58:09 +08:00
|
|
|
|
<q-btn flat round color="primary" icon="add" @click="addGcX" :disable="params.parameter.gc_x.length >= 4" />
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-auto">
|
2026-03-02 21:58:09 +08:00
|
|
|
|
<q-btn flat round color="negative" icon="remove" @click="removeGcX" :disable="params.parameter.gc_x.length <= 2" />
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 地面倾角 -->
|
|
|
|
|
|
<div class="q-mt-md">
|
|
|
|
|
|
<div class="row q-col-gutter-sm">
|
2026-03-02 22:01:04 +08:00
|
|
|
|
<div class="col-12 col-md-6">
|
2026-03-02 19:39:28 +08:00
|
|
|
|
<q-input
|
2026-03-02 22:01:04 +08:00
|
|
|
|
v-model="params.parameter.ground_angels[0]"
|
2026-03-02 19:39:28 +08:00
|
|
|
|
type="number"
|
|
|
|
|
|
step="1"
|
2026-03-02 22:01:04 +08:00
|
|
|
|
label="地面倾角 (°) - 向下为正"
|
|
|
|
|
|
>
|
|
|
|
|
|
<q-tooltip>地面倾斜角度,向下为正值</q-tooltip>
|
|
|
|
|
|
</q-input>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</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">
|
2026-03-02 23:04:16 +08:00
|
|
|
|
<div class="col-12">
|
2026-03-02 19:39:28 +08:00
|
|
|
|
<q-input
|
|
|
|
|
|
v-model="params.advance.ng"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
step="0.01"
|
|
|
|
|
|
label="地闪密度 (次/(km²·a))"
|
2026-03-02 21:52:20 +08:00
|
|
|
|
>
|
|
|
|
|
|
<q-tooltip>每平方公里每年的地闪次数,默认-1表示自动计算</q-tooltip>
|
|
|
|
|
|
</q-input>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</div>
|
2026-03-02 23:04:16 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="row q-col-gutter-md q-mt-sm">
|
|
|
|
|
|
<div class="col-6">
|
2026-03-02 19:39:28 +08:00
|
|
|
|
<q-input
|
|
|
|
|
|
v-model="params.advance.Ip_a"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
step="0.01"
|
|
|
|
|
|
label="雷电流概率密度曲线系数 a"
|
2026-03-02 23:04:16 +08:00
|
|
|
|
dense
|
2026-03-02 21:52:20 +08:00
|
|
|
|
>
|
|
|
|
|
|
<q-tooltip>雷电流幅值概率密度函数参数,默认-1表示使用标准参数</q-tooltip>
|
|
|
|
|
|
</q-input>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</div>
|
2026-03-02 23:04:16 +08:00
|
|
|
|
<div class="col-6">
|
2026-03-02 19:39:28 +08:00
|
|
|
|
<q-input
|
|
|
|
|
|
v-model="params.advance.Ip_b"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
step="0.01"
|
|
|
|
|
|
label="雷电流概率密度曲线系数 b"
|
2026-03-02 23:04:16 +08:00
|
|
|
|
dense
|
2026-03-02 21:52:20 +08:00
|
|
|
|
>
|
|
|
|
|
|
<q-tooltip>雷电流幅值概率密度函数参数,默认-1表示使用标准参数</q-tooltip>
|
|
|
|
|
|
</q-input>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</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="计算时电压分成多少份"
|
2026-03-02 21:52:20 +08:00
|
|
|
|
>
|
|
|
|
|
|
<q-tooltip>将电压波形离散化的份数,影响计算精度</q-tooltip>
|
|
|
|
|
|
</q-input>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-12 col-md-6">
|
|
|
|
|
|
<q-input
|
|
|
|
|
|
v-model="params.optional.max_i"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
label="最大尝试雷电流 (kA)"
|
2026-03-02 21:52:20 +08:00
|
|
|
|
>
|
|
|
|
|
|
<q-tooltip>计算时尝试的最大雷电流幅值</q-tooltip>
|
|
|
|
|
|
</q-input>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</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
|
2026-03-02 22:57:13 +08:00
|
|
|
|
color="orange"
|
2026-03-02 19:39:28 +08:00
|
|
|
|
size="lg"
|
2026-03-02 22:57:13 +08:00
|
|
|
|
label="导入配置"
|
|
|
|
|
|
icon="upload"
|
|
|
|
|
|
@click="importConfig"
|
2026-03-02 19:39:28 +08:00
|
|
|
|
class="px-8"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<q-btn
|
|
|
|
|
|
color="positive"
|
|
|
|
|
|
size="lg"
|
|
|
|
|
|
label="导出配置"
|
|
|
|
|
|
icon="download"
|
|
|
|
|
|
@click="exportConfig"
|
|
|
|
|
|
class="px-8"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-02 22:57:13 +08:00
|
|
|
|
<!-- 隐藏的文件输入 -->
|
|
|
|
|
|
<input
|
|
|
|
|
|
ref="fileInput"
|
|
|
|
|
|
type="file"
|
|
|
|
|
|
accept=".toml"
|
|
|
|
|
|
style="display: none"
|
|
|
|
|
|
@change="handleFileSelect"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
2026-03-02 22:51:00 +08:00
|
|
|
|
|
2026-03-02 19:39:28 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 错误信息 -->
|
|
|
|
|
|
<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>
|
2026-03-02 21:02:58 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 运行日志 -->
|
|
|
|
|
|
<LogComponent ref="logRef" />
|
2026-03-02 22:51:00 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 计算结果 -->
|
|
|
|
|
|
<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>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</q-page>
|
|
|
|
|
|
</q-page-container>
|
|
|
|
|
|
</q-layout>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2026-03-02 23:04:16 +08:00
|
|
|
|
import { ref, reactive, computed, onMounted, onUnmounted, watch } from 'vue'
|
2026-03-02 19:39:28 +08:00
|
|
|
|
import type { AllParameters } from '@/types'
|
2026-03-02 21:02:58 +08:00
|
|
|
|
import LogComponent from './Log.vue'
|
2026-03-02 19:39:28 +08:00
|
|
|
|
|
|
|
|
|
|
// 默认参数
|
|
|
|
|
|
const defaultParams: AllParameters = {
|
|
|
|
|
|
parameter: {
|
2026-03-02 21:17:34 +08:00
|
|
|
|
rated_voltage: '500kV',
|
2026-03-02 22:09:46 +08:00
|
|
|
|
ac_or_dc: 'AC',
|
2026-03-02 19:39:28 +08:00
|
|
|
|
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)
|
2026-03-02 21:02:58 +08:00
|
|
|
|
const logRef = ref<InstanceType<typeof LogComponent> | null>(null)
|
2026-03-02 22:57:13 +08:00
|
|
|
|
const fileInput = ref<HTMLInputElement | null>(null)
|
2026-03-02 19:39:28 +08:00
|
|
|
|
|
2026-03-02 21:17:34 +08:00
|
|
|
|
const voltageOptions = [
|
|
|
|
|
|
'110kV', '220kV', '330kV', '500kV', '750kV','1000kV',
|
|
|
|
|
|
'±500kV', '±660kV', '±800kV', '±1100kV'
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-03-02 22:09:46 +08:00
|
|
|
|
// 根据电压等级自动判断交流/直流
|
|
|
|
|
|
const currentType = computed(() => {
|
|
|
|
|
|
return params.parameter.rated_voltage.includes('±') ? 'DC' : 'AC'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-03-02 23:04:16 +08:00
|
|
|
|
// 当地闪密度大于 0 时,自动将雷暴日设为 -1,且不可编辑
|
|
|
|
|
|
// 当地闪密度小于 0 时,自动将雷暴日设为 20,且可以编辑
|
|
|
|
|
|
const isTdDisabled = computed(() => Number(params.advance.ng) > 0)
|
|
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => params.advance.ng,
|
|
|
|
|
|
(newNg) => {
|
|
|
|
|
|
if (Number(newNg) > 0) {
|
|
|
|
|
|
params.parameter.td = -1
|
|
|
|
|
|
} else {
|
|
|
|
|
|
params.parameter.td = 20
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-02 21:58:09 +08:00
|
|
|
|
// 数组操作函数(最多3条导线,即数组最多4个元素:1地线+3导线)
|
2026-03-02 22:03:01 +08:00
|
|
|
|
// 两个数组同步增减
|
2026-03-02 19:39:28 +08:00
|
|
|
|
const addHArm = () => {
|
2026-03-02 21:58:09 +08:00
|
|
|
|
if (params.parameter.h_arm.length < 4) {
|
|
|
|
|
|
const last = params.parameter.h_arm[params.parameter.h_arm.length - 1] || 100
|
|
|
|
|
|
params.parameter.h_arm.push(last - 20)
|
2026-03-02 22:03:01 +08:00
|
|
|
|
// 同步增加 gc_x
|
|
|
|
|
|
const lastX = params.parameter.gc_x[params.parameter.gc_x.length - 1] || 10
|
|
|
|
|
|
params.parameter.gc_x.push(lastX)
|
2026-03-02 21:58:09 +08:00
|
|
|
|
}
|
2026-03-02 19:39:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const removeHArm = () => {
|
2026-03-02 21:58:09 +08:00
|
|
|
|
if (params.parameter.h_arm.length > 2) {
|
2026-03-02 19:39:28 +08:00
|
|
|
|
params.parameter.h_arm.pop()
|
2026-03-02 22:03:01 +08:00
|
|
|
|
// 同步删除 gc_x
|
|
|
|
|
|
params.parameter.gc_x.pop()
|
2026-03-02 19:39:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const addGcX = () => {
|
2026-03-02 21:58:09 +08:00
|
|
|
|
if (params.parameter.gc_x.length < 4) {
|
2026-03-02 22:03:01 +08:00
|
|
|
|
const lastX = params.parameter.gc_x[params.parameter.gc_x.length - 1] || 10
|
|
|
|
|
|
params.parameter.gc_x.push(lastX)
|
|
|
|
|
|
// 同步增加 h_arm
|
|
|
|
|
|
const last = params.parameter.h_arm[params.parameter.h_arm.length - 1] || 100
|
|
|
|
|
|
params.parameter.h_arm.push(last - 20)
|
2026-03-02 21:58:09 +08:00
|
|
|
|
}
|
2026-03-02 19:39:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const removeGcX = () => {
|
2026-03-02 21:58:09 +08:00
|
|
|
|
if (params.parameter.gc_x.length > 2) {
|
2026-03-02 19:39:28 +08:00
|
|
|
|
params.parameter.gc_x.pop()
|
2026-03-02 22:03:01 +08:00
|
|
|
|
// 同步删除 h_arm
|
|
|
|
|
|
params.parameter.h_arm.pop()
|
2026-03-02 19:39:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算函数
|
|
|
|
|
|
const calculate = async () => {
|
|
|
|
|
|
calculating.value = true
|
|
|
|
|
|
result.value = null
|
|
|
|
|
|
error.value = null
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 调用 pywebview 的 Python 函数
|
|
|
|
|
|
if (window.pywebview) {
|
2026-03-02 22:49:38 +08:00
|
|
|
|
// 后台线程启动计算,实时日志通过 addLogFromBackend 推送
|
|
|
|
|
|
// 结果通过 receiveResult 回调接收
|
|
|
|
|
|
await window.pywebview.api.calculate(params)
|
|
|
|
|
|
// 不在这里设置 calculating = false,等待 receiveResult 回调
|
2026-03-02 19:39:28 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 开发模式下的模拟
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
2026-03-02 21:02:58 +08:00
|
|
|
|
logRef.value?.addLog('info', '开始 EGM 计算(开发模式)...')
|
|
|
|
|
|
logRef.value?.addLog('info', '参数: 额定电压=750kV, 雷暴日=20d, 海拔=1000m')
|
|
|
|
|
|
logRef.value?.addLog('info', '计算完成')
|
2026-03-02 19:39:28 +08:00
|
|
|
|
result.value = JSON.stringify({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '计算完成(开发模式)',
|
|
|
|
|
|
data: {
|
|
|
|
|
|
tripping_rate: '0.0581 次/(100km·a)',
|
|
|
|
|
|
parameters: params
|
|
|
|
|
|
}
|
|
|
|
|
|
}, null, 2)
|
2026-03-02 22:49:38 +08:00
|
|
|
|
calculating.value = false
|
2026-03-02 19:39:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
|
error.value = e.message || '计算失败'
|
2026-03-02 21:02:58 +08:00
|
|
|
|
logRef.value?.addLog('error', e.message || '计算失败')
|
2026-03-02 19:39:28 +08:00
|
|
|
|
calculating.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 22:18:32 +08:00
|
|
|
|
// 将参数转换为 TOML 格式
|
|
|
|
|
|
const tomlStringify = (obj: any, indent: string = ''): string => {
|
|
|
|
|
|
let result = ''
|
|
|
|
|
|
|
|
|
|
|
|
for (const [key, value] of Object.entries(obj)) {
|
|
|
|
|
|
if (value === null || value === undefined) continue
|
|
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
|
result += `${indent}${key} = [${value.join(', ')}]\n`
|
|
|
|
|
|
} else if (typeof value === 'object') {
|
|
|
|
|
|
result += `\n${indent}[${key}]\n`
|
|
|
|
|
|
result += tomlStringify(value, indent)
|
|
|
|
|
|
} else if (typeof value === 'string') {
|
|
|
|
|
|
result += `${indent}${key} = "${value}"\n`
|
|
|
|
|
|
} else if (typeof value === 'boolean') {
|
|
|
|
|
|
result += `${indent}${key} = ${value}\n`
|
|
|
|
|
|
} else {
|
|
|
|
|
|
result += `${indent}${key} = ${value}\n`
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 22:57:13 +08:00
|
|
|
|
// 解析 TOML 格式字符串
|
|
|
|
|
|
const parseToml = (tomlStr: string): any => {
|
|
|
|
|
|
const result: any = {}
|
|
|
|
|
|
let currentSection: any = result
|
|
|
|
|
|
let currentSectionName = ''
|
|
|
|
|
|
|
|
|
|
|
|
const lines = tomlStr.split('\n')
|
|
|
|
|
|
|
|
|
|
|
|
for (let line of lines) {
|
|
|
|
|
|
line = line.trim()
|
|
|
|
|
|
|
|
|
|
|
|
// 跳过空行和注释
|
|
|
|
|
|
if (!line || line.startsWith('#')) continue
|
|
|
|
|
|
|
|
|
|
|
|
// 匹配 section [xxx]
|
|
|
|
|
|
const sectionMatch = line.match(/^\[([^\]]+)\]$/)
|
|
|
|
|
|
if (sectionMatch) {
|
|
|
|
|
|
currentSectionName = sectionMatch[1]
|
|
|
|
|
|
currentSection = {}
|
|
|
|
|
|
result[currentSectionName] = currentSection
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 匹配 key = value
|
|
|
|
|
|
const kvMatch = line.match(/^([^=]+)=(.*)$/)
|
|
|
|
|
|
if (kvMatch) {
|
|
|
|
|
|
const key = kvMatch[1].trim()
|
|
|
|
|
|
let value: any = kvMatch[2].trim()
|
|
|
|
|
|
|
|
|
|
|
|
// 解析数组 [1, 2, 3]
|
|
|
|
|
|
if (value.startsWith('[') && value.endsWith(']')) {
|
|
|
|
|
|
const arrStr = value.slice(1, -1).trim()
|
|
|
|
|
|
if (arrStr) {
|
|
|
|
|
|
value = arrStr.split(',').map((s: string) => {
|
|
|
|
|
|
s = s.trim()
|
|
|
|
|
|
if (s.startsWith('"') && s.endsWith('"')) {
|
|
|
|
|
|
return s.slice(1, -1)
|
|
|
|
|
|
}
|
|
|
|
|
|
return isNaN(Number(s)) ? s : Number(s)
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
value = []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 解析字符串 "xxx"
|
|
|
|
|
|
else if (value.startsWith('"') && value.endsWith('"')) {
|
|
|
|
|
|
value = value.slice(1, -1)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 解析布尔值
|
|
|
|
|
|
else if (value === 'true') {
|
|
|
|
|
|
value = true
|
|
|
|
|
|
} else if (value === 'false') {
|
|
|
|
|
|
value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
// 解析数字
|
|
|
|
|
|
else if (!isNaN(Number(value))) {
|
|
|
|
|
|
value = Number(value)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
currentSection[key] = value
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 19:39:28 +08:00
|
|
|
|
// 导出配置
|
2026-03-02 22:18:32 +08:00
|
|
|
|
const exportConfig = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (window.pywebview) {
|
|
|
|
|
|
const response = await window.pywebview.api.export_config(params)
|
|
|
|
|
|
if (response.success) {
|
|
|
|
|
|
logRef.value?.addLog('info', response.message)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
logRef.value?.addLog('warning', response.message)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 开发模式下的模拟
|
|
|
|
|
|
logRef.value?.addLog('info', '导出配置(开发模式,直接下载)')
|
|
|
|
|
|
const config = tomlStringify(params)
|
|
|
|
|
|
const blob = new Blob([config], { type: 'text/plain' })
|
|
|
|
|
|
const url = URL.createObjectURL(blob)
|
|
|
|
|
|
const a = document.createElement('a')
|
|
|
|
|
|
a.href = url
|
|
|
|
|
|
const timestamp = new Date().toISOString().slice(0, 19).replace(/[:-]/g, '')
|
|
|
|
|
|
a.download = `egm_config_${timestamp}.toml`
|
|
|
|
|
|
a.click()
|
|
|
|
|
|
URL.revokeObjectURL(url)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
|
error.value = e.message || '导出失败'
|
|
|
|
|
|
logRef.value?.addLog('error', e.message || '导出失败')
|
|
|
|
|
|
}
|
2026-03-02 19:39:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 22:57:13 +08:00
|
|
|
|
// 导入配置 - 触发文件选择
|
|
|
|
|
|
const importConfig = () => {
|
|
|
|
|
|
fileInput.value?.click()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理文件选择
|
|
|
|
|
|
const handleFileSelect = async (event: Event) => {
|
|
|
|
|
|
const input = event.target as HTMLInputElement
|
|
|
|
|
|
const file = input.files?.[0]
|
|
|
|
|
|
if (!file) return
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const content = await file.text()
|
|
|
|
|
|
const importedParams = parseToml(content)
|
|
|
|
|
|
|
|
|
|
|
|
// 合并导入的参数到当前参数
|
|
|
|
|
|
if (importedParams.parameter) {
|
|
|
|
|
|
Object.assign(params.parameter, importedParams.parameter)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (importedParams.advance) {
|
|
|
|
|
|
Object.assign(params.advance, importedParams.advance)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (importedParams.optional) {
|
|
|
|
|
|
Object.assign(params.optional, importedParams.optional)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logRef.value?.addLog('info', `成功导入配置: ${file.name}`)
|
|
|
|
|
|
result.value = null
|
|
|
|
|
|
error.value = null
|
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
|
error.value = e.message || '导入失败'
|
|
|
|
|
|
logRef.value?.addLog('error', e.message || '导入失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清空 input 以便可以重复选择同一个文件
|
|
|
|
|
|
input.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 19:39:28 +08:00
|
|
|
|
// 声明 pywebview API 类型
|
|
|
|
|
|
declare global {
|
|
|
|
|
|
interface Window {
|
|
|
|
|
|
pywebview?: {
|
|
|
|
|
|
api: {
|
|
|
|
|
|
calculate: (params: AllParameters) => Promise<any>
|
2026-03-02 22:18:32 +08:00
|
|
|
|
export_config: (params: AllParameters) => Promise<any>
|
2026-03-02 19:39:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-02 22:49:38 +08:00
|
|
|
|
addLogFromBackend?: (log: { level: string; time: string; message: string }) => void
|
|
|
|
|
|
receiveResult?: (result: { success: boolean; message: string; data?: any; error?: string }) => void
|
2026-03-02 19:39:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-02 22:49:38 +08:00
|
|
|
|
|
|
|
|
|
|
// 注册全局日志接收函数,供后端实时调用
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
// 实时日志推送
|
|
|
|
|
|
window.addLogFromBackend = (log: { level: string; time: string; message: string }) => {
|
|
|
|
|
|
logRef.value?.addLog(log.level as any, log.message)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 接收计算结果
|
|
|
|
|
|
window.receiveResult = (res: { success: boolean; message: string; data?: any; error?: string }) => {
|
|
|
|
|
|
calculating.value = false
|
|
|
|
|
|
if (res.success) {
|
|
|
|
|
|
result.value = JSON.stringify(res, null, 2)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
error.value = res.error || res.message
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
window.addLogFromBackend = undefined
|
|
|
|
|
|
window.receiveResult = undefined
|
|
|
|
|
|
})
|
2026-03-02 21:49:35 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
</style>
|