# 本程序用于Ni基牌号调料程序 # 较严重的错误会覆盖次严重的错误,错误严重级别由大到小排序: #...

Created on: April 13, 2025

Answered using O3 Mini High by Chat01

Question

本程序用于Ni基牌号调料程序

较严重的错误会覆盖次严重的错误,错误严重级别由大到小排序:

存在元素超过标准范围>

超过3种元素不在目标范围内>

调料后,存在元素超出标准范围>

调料后,存在元素超出一般范围>

调料后,存在元素超出目标范围>

调料后,存在元素超出炉前补加时参考配入上限>

补加重量过多(超过100kg)

两个安全系数分别为0.8和0.9

import glob
import os
import pandas as pd
import logging
from tkinter import *
from tkinter import simpledialog, messagebox
import datetime
import sys
from scipy.optimize import fsolve
import numpy as np

这里防止pd更新时启用旧功能,导致函数无法运行

pd.set_option('future.no_silent_downcasting', True)

初始化全局变量

TOTAL_WEIGHT = 0.0
DF_RANGE_REQUIREMENTS = None
FOLDER_PATH = os.path.dirname(os.path.abspath(file))

获取当前脚本或者程序所在的根目录(在打包的EXE中运行时也有效)

if getattr(sys, 'frozen', False):
application_path = os.path.dirname(sys.executable)
else:
application_path = os.path.dirname(os.path.abspath(file))

创造Ni基合金中的文件配置

Ni_BASED_ALLOY_CONFIGS = {
"XB-407M2": {
"folder": "XB-407M2调料文件",
"log_file": "XB-407M2调料日志.txt"
},
"HT700R": {
"folder": "HT700R调料文件",
"log_file": "HT700R调料日志.txt"
}
}

这里设置日志文件的保存路径函数,文件夹和文件名为形参,后续需要传递实参,使用str()确保路径为字符

def setup_logging(folder_path, log_file_name):
log_file_path = os.path.join(str(folder_path), str(log_file_name))
logging.basicConfig(filename=log_file_path, level=logging.INFO, format='%(message)s')
with open(log_file_path, 'a') as log_file:
log_file.write('\n')

def setup_paths(alloy_name):
config = Ni_BASED_ALLOY_CONFIGS[alloy_name] # 这里输入alloy_name可以定位到字典的子字典
condiments_folder = os.path.join(application_path, config['folder'])
global DF_RANGE_REQUIREMENTS, FOLDER_PATH
xlsx_files = glob.glob(os.path.join(str(condiments_folder), '*.xlsx'))
if xlsx_files:
DF_RANGE_REQUIREMENTS = xlsx_files[0] # 取第一个找到的 .xlsx 文件,是成分页文件,位于调料文件中
FOLDER_PATH = os.path.join(str(condiments_folder), '测试结果') # 这里找到日志保存路径,并启动日志记录
setup_logging(FOLDER_PATH, config['log_file'])

def alloy_weight_input(alloy_name):
global TOTAL_WEIGHT
alloy_weight_value = simpledialog.askfloat(f"{alloy_name}钢液重量", f"坩埚中的{alloy_name}钢液重量约为(单位:kg):",
initialvalue=True)
TOTAL_WEIGHT = alloy_weight_value
setup_paths(alloy_name)
# 运行setup_paths函数后,已经将DF_RANGE_REQUIREMENTS = xlsx_files[0],即成分页位置得到更新
# 同时,已经将FOLDER_PATH = os.path.join(str(condiments_folder), '测试结果'),即测试页位置得到更新
main_process() # 在main_process函数中可以运行更新后的DF_RANGE_REQUIREMENTS和FOLDER_PATH
messagebox.showinfo("提示", f"{alloy_name}调料程序运行完毕,请在调料日志中查看详情。")

def main_process():
file_paths = find_xlsx_files(FOLDER_PATH) # 首先找到测试结果位置
for file_path in file_paths: # 将测试结果输入到加工函数中,进行加工
df_results = process_test_results(file_path)
df_merge = merge_data(DF_RANGE_REQUIREMENTS, df_results) # 将测试结果和成分页进行合并
alerts = check_element_ranges(df_merge)
handle_alerts(alerts, df_merge)

初始化GUI

root = Tk()
root.title('调料程序')

动态创建按钮

for i, alloy in enumerate(Ni_BASED_ALLOY_CONFIGS.keys()):
button = Button(root, text=f'{alloy}调料计算', command=lambda a=alloy: alloy_weight_input(a), height=1, width=15)
button.grid(row=0, column=i, padx=10, pady=10)

def find_xlsx_files(folder_path): # 定义1个函数,获取测试结果文件地址,这里小写的folder_path只是形参,需要在主函数中用FOLDER_PATH代替
search_path = os.path.join(folder_path, '*.xlsx')
file_paths = glob.glob(search_path)
return file_paths

file_paths虽然是列表形式,但是加入函数for file_path in find_xlsx_files(FOLDER_PATH):

print(file_path),其中的file_path就是列表中的1个元素了,此时1个元素不以列表形式展示

def process_test_results(file_path): # 定义1个函数,可以对测试结果进行提取加工,将其格式和成分页模板对齐
try:
df_test_results = pd.read_excel(file_path)
df_results = df_test_results.iloc[9:25, [0, 3]].replace('<', '', regex=True) # 注意替换函数中的字符时,一定要将regex=True加上
df_results.columns = ['元素', '测试结果']
df_results['测试结果'] = pd.to_numeric(df_results['测试结果'], errors='coerce') # 将测试结果转换为数字形式
df_results.reset_index(drop=True, inplace=True) # 重置索引
return df_results
except Exception as e:
logging.error(f'Error processing file {file_path}: {e}')
return pd.DataFrame()

def merge_data(requirements_file, df_results): # 定义读取数据,合并数据的函数,将成分页模板和提取的测试结果合并输出
df_range_requirements = pd.read_excel(requirements_file)
df_merge = pd.merge(df_range_requirements, df_results, on='元素', how='left')
# on属性用于寻找共同列,left属性表示左连接,返回左侧DataFrame的所有键,以及它们在右侧DataFrame中的匹配(如果存在)。
return df_merge

def check_element_ranges(df_merge): # 定义报警函数,用于监控是否存在元素超限,df_merge是融合了成分页和检测结果的全面数据
alerts = []
standard_range_issues_plus = 0
standard_range_issues_sub = 0
general_range_issues_plus = 0
general_range_issues_sub = 0
target_range_issues_plus = 0
target_range_issues_sub = 0
# 这里需要使用逐行检测的方式,用于确定每种超限的类型,并逐行给出警报
for index, row in df_merge.iterrows():
element = row['元素']
test_result = row['测试结果']
range_row = df_merge[df_merge['元素'] == element].iloc[0]

text
if test_result < range_row['标准范围下限']: # 这里需要注意if和elif并列和if和if并列的情况区别,前者是只能满足1个条件执行,后者可以同时满足同时执行多个条件 alerts.append(f"警报:元素{element}<标准范围。") standard_range_issues_sub += 1 elif test_result > range_row['标准范围上限']: alerts.append(f"警报:元素{element}>标准范围。") standard_range_issues_plus += 1 elif test_result < range_row['一般范围下限']: alerts.append(f"注意:元素{element}<一般范围。") general_range_issues_sub += 1 elif test_result > range_row['一般范围上限']: alerts.append(f"注意:元素{element}>一般范围。") general_range_issues_plus += 1 elif test_result < range_row['目标范围下限']: alerts.append(f"注意:元素{element}<目标范围。") target_range_issues_sub += 1 elif test_result > range_row['目标范围上限']: alerts.append(f"注意:元素{element}>目标范围。") target_range_issues_plus += 1 if standard_range_issues_sub + standard_range_issues_plus > 0: alerts.append("警报:存在元素超过标准范围,请联系牌号负责人进行调料。") elif general_range_issues_sub + general_range_issues_plus + target_range_issues_sub + target_range_issues_plus > 3: alerts.append("警报:超过3种元素不在目标范围内,请联系牌号负责人进行调料。") else: alerts.append("注意:元素检测结果处于正常波动水平,请按照程序调料。") return alerts

这里是验算函数,用于验算加入物料后,实际成分和名义成分是否符合要求

def check_element_ranges_after_addition(df_check): # 这里的df_check文件是融合了问题元素和正常元素以及所有补加情况的文件
file_path = os.path.join(FOLDER_PATH, '调料后各元素变化情况.xlsx') # 无论如何,在检查之前先将check文件保存
df_check.round(4).to_excel(file_path, index=False)
alerts2 = []
standard_range_issues = 0
general_range_issues = 0
target_range_issues = 0
addition_lim_issues = 0
for index, row in df_check.iterrows():
element = row['元素']
future_result = row['补加后元素实际含量']
future_result_name = row['补加后元素名义含量']
range_row = df_check[df_check['元素'] == element].iloc[0]
if future_result < range_row['标准范围下限'] or future_result > range_row['标准范围上限']:
alerts2.append(f"警报:调料后,元素{element}超出标准范围。")
standard_range_issues += 1
elif future_result < range_row['一般范围下限'] or future_result > range_row['一般范围上限']:
alerts2.append(f"警报:调料后,元素{element}超出一般范围。")
general_range_issues += 1
elif future_result < range_row['目标范围下限'] or future_result > range_row['目标范围上限']:
alerts2.append(f"警报:调料后,元素{element}超出目标范围。")
target_range_issues += 1
elif future_result_name > range_row['炉前补加时参考配入上限']:
alerts2.append(f"警报:调料后,元素{element}超出炉前补加时参考配入上限。")
addition_lim_issues += 1
if standard_range_issues > 0:
alerts2.append("警报:调料后,存在元素超过标准范围,请联系牌号负责人进行调料。")
elif general_range_issues > 0:
alerts2.append("警报:调料后,存在元素超过一般范围,请联系牌号负责人进行调料。")
elif target_range_issues > 0:
alerts2.append("警报:调料后,存在元素超过目标范围,请联系牌号负责人进行调料。")
elif addition_lim_issues > 0:
alerts2.append("警报:调料后,存在元素超过炉前补加时参考配入上限,请联系牌号负责人进行调料。")
else:
alerts2.append("注意:调料后,元素均在目标范围,请按照程序调料。")
return alerts2

def calculate_additions_sub(df_merge):
# 补加料存在安全系数,相对条件严格,需要补加的料更多,但是检查函数只是按照目标范围检查,相当于条件更宽松。如果按照安全系数来检查,有可能造成条件过于严格,处于无限迭代之中。
# 此时可以将没有通过检查的调料后各元素变化情况的‘补加后元素实际含量’假设为下次‘测试结果’,重新迭代,最终产生的结果只需要满足检查函数即可,而不是这里的安全范围,相当于条件更宽松了。
# 最后将两次结果的补加情况相加即可。
# 在sub情况中也引入元素安全范围的概念,选择出不安全的元素进行补加,安全系数为0.8
df_element_low = df_merge[(df_merge['目标值'] - df_merge['测试结果']) / df_merge['目标值'] >
(df_merge['目标值'] - df_merge['目标范围下限']) / df_merge['目标值'] * 0.8].copy()
# 从DataFrame中提取测试值、目标值和元素名,通过to_numpy()函数将数值转换为numpy数组,可以提高运行速度
test_values = df_element_low['测试结果'].to_numpy()
target_values = df_element_low['炉前参考补加目标'].to_numpy()

text
# 定义方程组,用来N元1次解方程,方程中的补加重量本质上是未知的变量,所有可以用vars代替N多个补加元素的补加重量 def solve_equations(vars): total = TOTAL_WEIGHT + np.sum(vars) equations = [] # 初始化一个空列表,用来存储方程组 for test_value, target_value, var in zip(test_values, target_values, vars): # 使用 zip 函数将 test_values, target_values, vars 各元素一一对应起来,并迭代生成方程组。每个元素的测试值、目标值和变量(补加量)被同时处理和运算。 equation = (test_value * TOTAL_WEIGHT / 100 + var) / total - target_value / 100 # 这里建立equation方程式,本质上是要求equation=0时的var值,并且将不同的var值都添加在equations空列表中 equations.append(equation) return equations # 初始猜测,设置初始猜测值,假设每种元素的初始补加量为10kg,然后通过迭代方式求解方程组。 initial_guess = np.full(len(test_values), 10) # 假设每种元素的初始补加量为10kg solutions, infodict, ier, mesg = fsolve(solve_equations, initial_guess, full_output=True) # 将解添加到DataFrame中 df_element_low['补加重量'] = solutions # 计算补加后的实际含量,由于求解过程中均是以numpy数组形式储存数字,所以可以用np.sum求和 addition_weight = np.sum(solutions) df_element_low['补加后元素实际含量'] = (df_element_low['测试结果'] * TOTAL_WEIGHT / 100 + df_element_low[ '补加重量']) / (TOTAL_WEIGHT + addition_weight) * 100 df_element_low['补加后元素名义含量'] = (df_element_low['目标值'] * TOTAL_WEIGHT / 100 + df_element_low[ '补加重量']) / (TOTAL_WEIGHT + addition_weight) * 100 df_merge['补加重量'] = 0.0 df_merge['补加后元素实际含量'] = df_merge['测试结果'] * TOTAL_WEIGHT / ( addition_weight + TOTAL_WEIGHT) # 计算补加之后其余元素的实际含量 df_merge['补加后元素名义含量'] = df_merge['目标值'] * TOTAL_WEIGHT / ( addition_weight + TOTAL_WEIGHT) # 计算补加之后其余元素的名义含量 df_merge.update(df_element_low) # 这里不能将此操作赋值给新的变量,不然会报错,直接在df_merge基础上进行覆盖操作 df_check = df_merge df_element_problem = df_element_low return df_check, df_element_problem

def calculate_additions_plus(df_merge): # 这里是3种元素全都超过目标范围上限的情况
df_element_problem = df_merge[df_merge['测试结果'] > df_merge['目标范围上限']].copy() # 先将超过目标范围上限的元素挑出
needed_addition = df_element_problem['测试结果'] * TOTAL_WEIGHT / df_element_problem[
'炉前参考降低目标'] - TOTAL_WEIGHT
addition_weight = needed_addition.iloc[:].max() # 补加重量为降低超限元素时需要的最大重量

text
# addition_weight算出后,基本可以确定merge数据结构中的各元素实际和名义产量 df_merge['补加重量'] = 0.0 df_merge['补加后元素实际含量'] = df_merge['测试结果'] * TOTAL_WEIGHT / ( addition_weight + TOTAL_WEIGHT) # 计算补加之后其余元素的实际含量 df_merge['补加后元素名义含量'] = df_merge['目标值'] * TOTAL_WEIGHT / ( addition_weight + TOTAL_WEIGHT) # 计算补加之后其余元素的名义含量 # 这里的df_merge计算中包括了全部元素,但是由于Ni或者Fe在后续可能需要补加,所以这里的Ni或者Fe算法结果不代表最终结果,需要在最后重新计算 # 后续计算补加元素加入后,元素实际含量是否变低到安全范围外,如果变低到安全范围外,则需要补加 # 这里肯定不包括超限元素,因为补加后,超限元素变为目标降低值,高于目标值 df_element_low = df_merge[(df_merge['目标值'] - df_merge['补加后元素实际含量']) / df_merge['目标值'] > (df_merge['目标值'] - df_merge['目标范围下限']) / df_merge['目标值'] * 0.9].copy() # (df_merge['目标值'] - df_merge['目标范围下限']) / df_merge['目标值'] * 0.9计算出目标值安全波动范围 # 其中0.9代表安全系数,指代补加后元素实际含量处于目标范围的90%内才安全,否则就需要补加 # (df_merge['目标值'] - df_merge['补加后元素实际含量']) / df_merge['目标值']计算出补加后元素实际含量变化范围,如果>80%则超出安全范围就需要补加 # 建立补加元素阵列后,首先初始化df_element_low,补全数据结构 df_element_low['补加重量'] = 0.0 df_element_low['补加后元素实际含量'] = 0 df_element_low['补加后元素名义含量'] = 0 # 之后开始填充数据结构 df_element_low['补加重量'] = (TOTAL_WEIGHT + addition_weight) * df_element_low[ '炉前参考补加目标'] / 100 - TOTAL_WEIGHT * df_element_low['测试结果'] / 100 if addition_weight > df_element_low['补加重量'].sum(): if 'Ni' in df_element_low['元素'].values[0]: df_element_low.loc[df_element_low['元素'] == 'Ni', '补加重量'] += addition_weight - df_element_low[ '补加重量'].sum() elif 'Ni' not in df_element_low['元素'].values[0]: df_merge.loc[df_merge['元素'] == 'Ni', '补加重量'] = addition_weight - df_element_low['补加重量'].sum() # 无论如何,在这种情况下,至少保证了加进去足够多的元素重量,让超限元素降下来了;当补加重量过多,超过了降限元素量后,其本身补加量是按照加上降限补加量来算的, # 所以此时补加量更多后,导致元素含量更少,补充不到炉前参考补加目标了,具体补充到多少,还需要看具体的偏离程度,如果可以通过检查,就不需要再补加 elif addition_weight <= df_element_low['补加重量'].sum(): df_element_low['补加重量'] = df_element_low['补加重量'] df_element_low['补加后元素实际含量'] = (df_element_low['测试结果'] / 100 * TOTAL_WEIGHT + df_element_low[ '补加重量']) / (addition_weight + TOTAL_WEIGHT) * 100 df_element_low['补加后元素名义含量'] = (df_element_low['目标值'] / 100 * TOTAL_WEIGHT + df_element_low[ '补加重量']) / (addition_weight + TOTAL_WEIGHT) * 100 # 必须添加df_merge的实际含量运算和名义含量运算,用来计算当Ni或者Fe不在df_element_low数据中的情况,此时df_merge中的Ni或者Fe元素是正确的算法 # 注意,当Ni或者Fe不在df_element_low中时,元素就必然在df_merge中,这时候就需要重新计算df_merge中的Ni的元素实际含量和元素名义含量 # 用来匹配上述的df_merge.loc[df_merge['元素'] == 'Ni', '补加重量'] = addition_weight - df_element_low['补加重量'].sum()的更新 df_merge['补加后元素实际含量'] = (df_merge['测试结果'] / 100 * TOTAL_WEIGHT + df_merge[ '补加重量']) / (addition_weight + TOTAL_WEIGHT) * 100 df_merge['补加后元素名义含量'] = (df_merge['目标值'] / 100 * TOTAL_WEIGHT + df_merge[ '补加重量']) / (addition_weight + TOTAL_WEIGHT) * 100 df_merge.update(df_element_low) df_check = df_merge df_element_problem = df_element_low return df_check, df_element_problem

def calculate_additions_plus_sub(df_merge):
df_element_problem_sub = df_merge[df_merge['测试结果'] < df_merge['目标范围下限']].copy()
df_element_problem_plus = df_merge[df_merge['测试结果'] > df_merge['目标范围上限']].copy()

text
# 首先计算需要将缺少元素补加到目标的重量 test_values = df_element_problem_sub['测试结果'].to_numpy() target_values = df_element_problem_sub['炉前参考补加目标'].to_numpy() # 定义方程组,用来N元1次解方程,方程中的补加重量本质上是未知的变量,所有可以用vars代替N多个补加元素的补加重量 def solve_equations(vars): total = TOTAL_WEIGHT + np.sum(vars) equations = [] # 初始化一个空列表,用来存储方程组 for test_value, target_value, var in zip(test_values, target_values, vars): # 使用 zip 函数将 test_values, target_values, vars 各元素一一对应起来,并迭代生成方程组。每个元素的测试值、目标值和变量(补加量)被同时处理和运算。 equation = (test_value * TOTAL_WEIGHT / 100 + var) / total - target_value / 100 # 这里建立equation方程式,本质上是要求equation=0时的var值,并且将不同的var值都添加在equations空列表中 equations.append(equation) return equations # 初始猜测,设置初始猜测值,假设每种元素的初始补加量为10kg,然后通过迭代方式求解方程组。 initial_guess = np.full(len(test_values), 10) # 假设每种元素的初始补加量为10kg solutions, infodict, ier, mesg = fsolve(solve_equations, initial_guess, full_output=True) # 将解添加到DataFrame中 df_element_problem_sub['补加重量'] = solutions # 计算补加后的实际含量,由于求解过程中均是以numpy数组形式储存数字,所以可以用np.sum求和 addition_weight_sub = np.sum(solutions) # 其次计算需要将多余元素降低时需要补加的重量 needed_addition = df_element_problem_plus['测试结果'] * TOTAL_WEIGHT / df_element_problem_plus[ '炉前参考降低目标'] - TOTAL_WEIGHT addition_weight_plus = needed_addition.iloc[:].max() # 补加重量为降低超限元素时需要的最大重量 if addition_weight_sub >= addition_weight_plus: return calculate_additions_sub(df_merge) elif addition_weight_sub < addition_weight_plus: return calculate_additions_plus(df_merge)

def log_alert(alert, level='info'): # 定义警报和信息的区分,如果错误水平是错误,则动用警报,否则是通知
if level == 'error':
logging.error(alert)
else:
logging.info(alert)

这里是将合并数据进行检查的过程

def handle_alerts(alerts, df_merge):
alert_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M')
alert_set = set() # 将警报和通知全都放在一个集合当中
for alert in alerts:
if "警报" in alert:
alert_set.add("警报")
log_alert(alert)
elif "<" in alert:
alert_set.add("<")
log_alert(alert)
elif ">" in alert:
alert_set.add(">")
log_alert(alert)
# 注意集合之中不允许存在同样的两种符号,所以这里可以判断是哪一种超限情况以及严重超限情况

text
if "警报" in alert_set: log_alert(alert_time + '调料记录\n' + "警报:检测结果出现异常,请联系牌号负责人进行调料。", 'error') messagebox.showwarning("警报", "警报:检测结果出现异常,请联系牌号负责人进行调料。") if "<" in alert_set and ">" not in alert_set: df_check, df_element_problem = calculate_additions_sub(df_merge) alerts2 = check_element_ranges_after_addition(df_check) for alert2 in alerts2: if "警报" in alert2: log_alert(alert_time + '调料记录\n' + alert2, 'error') text = str(alert2) messagebox.showwarning("警报", text) else: log_alert(alert_time + '调料记录\n' + alert2) zero_weight_indices = df_check[df_check['补加重量'] == 0].index # 删除补加重量为0的所有行 df_check = df_check.iloc[:, [0, -3]].drop(zero_weight_indices) # 删除补加重量为0的所有行 addition_weight = df_check['补加重量'].sum() if addition_weight > 100: log_alert("警报:补加重量过多(超过100kg),请联系牌号负责人。") messagebox.showwarning("警报", "警报:补加重量过多(超过100kg),请联系牌号负责人。") elif addition_weight <= 100: formatted_text = format_element_additions(df_check) log_alert('补加重量如下(单位kg):\n' + formatted_text) if ">" in alert_set and "<" not in alert_set: df_check, df_element_problem = calculate_additions_plus(df_merge) alerts2 = check_element_ranges_after_addition(df_check) for alert2 in alerts2: if "警报" in alert2: log_alert(alert_time + '调料记录\n' + alert2, 'error') text = str(alert2) messagebox.showwarning("警报", text) else: log_alert(alert_time + '调料记录\n' + alert2) zero_weight_indices = df_check[df_check['补加重量'] == 0].index # 删除补加重量为0的所有行 df_check = df_check.iloc[:, [0, -3]].drop(zero_weight_indices) # 删除补加重量为0的所有行 addition_weight = df_check['补加重量'].sum() if addition_weight > 100: log_alert("警报:补加重量过多(超过100kg),请联系牌号负责人。") messagebox.showwarning("警报", "警报:补加重量过多(超过100kg),请联系牌号负责人。") elif addition_weight <= 100: formatted_text = format_element_additions(df_check) log_alert('补加重量如下(单位kg):\n' + formatted_text) if ">" in alert_set and "<" in alert_set: df_check, df_element_problem = calculate_additions_plus_sub(df_merge) alerts2 = check_element_ranges_after_addition(df_check) for alert2 in alerts2: if "警报" in alert2: log_alert(alert_time + '调料记录\n' + alert2, 'error') text = str(alert2) messagebox.showwarning("警报", text) else: log_alert(alert_time + '调料记录\n' + alert2) zero_weight_indices = df_check[df_check['补加重量'] == 0].index # 删除补加重量为0的所有行 df_check = df_check.iloc[:, [0, -3]].drop(zero_weight_indices) # 删除补加重量为0的所有行 addition_weight = df_check['补加重量'].sum() if addition_weight > 100: log_alert("警报:补加重量过多(超过100kg),请联系牌号负责人。") messagebox.showwarning("警报", "警报:补加重量过多(超过100kg),请联系牌号负责人。") elif addition_weight <= 100: formatted_text = format_element_additions(df_check) log_alert('补加重量如下(单位kg):\n' + formatted_text)

def format_element_additions(df_check):
return df_check.map(lambda x: f"{x:.4f}" if isinstance(x, float) else str(x)).apply(' '.join, axis=1).to_string(
index=False)

root.mainloop()
这是我的后端代码,如何在前端设计html网页,可以在网页中与后端代码交互,在前端实现后端的一些互动操作的功能。

Thought

Designing frontend-backend interaction

The user wants to design a frontend HTML webpage that interacts with backend Python code (likely using Tkinter and pandas). To achieve this, we need to consider server-client architecture. The backend Python code could act as an API server, potentially using Flask or FastAPI to handle requests. The frontend would send requests through JavaScript (e.g., Fetch API or AJAX), and the backend would respond with the needed data or actions. I can offer detailed guidance for this. This approach enables real-time interaction with the backend.

Creating frontend-backend connection

To have an HTML frontend interact with your Python backend, you'd use frameworks like Flask or Django to create a web server. I'll show how Flask can serve endpoints that the frontend will interact with via HTTP requests, such as form submissions or AJAX calls. The Tkinter-based backend code needs to be refactored into web endpoints (e.g., through forms or AJAX). I'll also give examples of HTML and Flask code that integrate interactivity and backend functionality.

Reasoned about backend/frontend interaction for 39 seconds

Answer

下面给出一个详细的实现思路和示例代码,帮助你将现有的后端 Python 代码转变为一个可以通过网页进行交互的系统。基本思路是利用 Python Web 框架(如 Flask 或 Django)构建一个 web 服务器,由后端接口调用你原有的业务逻辑,然后通过 HTML、JavaScript 与用户交互。


1. 系统架构说明

后端部分

  • Web 框架:选择 Flask(也可以用 Django,但 Flask 上手简单且灵活)。
  • 接口设计:将原有基于 Tkinter 的输入、提示及调料计算逻辑(例如 alloy_weight_inputmain_process、各个计算函数)封装为独立的函数,并在 Flask 中通过路由暴露出来。例如:
    • 一个接口接收前端的请求(如牌号选择、钢液重量输入),调用相应逻辑函数,返回计算结果(调料建议、补加重量信息、警报提示信息等)。
    • 如果涉及文件读取(如 Excel 文件),需要确保上传的文件路径或使用服务器上的固定文件夹作为数据源。

前端部分

  • HTML 页面:设计一个简单的页面,包括表单输入(选择牌号、输入钢液重量等)、按钮以及用于显示计算结果的区域。
  • JavaScript 与 AJAX:采用 JavaScript(例如使用 jQuery)通过 AJAX 将用户输入提交到 Flask 后端接口,然后在收到 JSON 格式的结果后更新页面显示。

2. 示例实现

下面提供一个基于 Flask 的示例,其中包含:

  • 后端(app.py):使用 Flask 创建基本 API,并在 / 路由下渲染 HTML 页面,在 /process 路由下接收表单数据进行计算。
  • 前端(templates/index.html):基本的 HTML 表单页面,利用 jQuery 发送 AJAX 请求,获取计算结果后显示在页面上。

后端代码示例(app.py)

python
from flask import Flask, request, jsonify, render_template import os import pandas as pd import datetime import logging from scipy.optimize import fsolve import numpy as np app = Flask(__name__) # 全局变量和配置(参考原有代码,可以考虑进一步封装和模块化) TOTAL_WEIGHT = 0.0 DF_RANGE_REQUIREMENTS = None BASE_DIR = os.path.dirname(os.path.abspath(__file__)) Ni_BASED_ALLOY_CONFIGS = { "XB-407M2": { "folder": "XB-407M2调料文件", "log_file": "XB-407M2调料日志.txt" }, "HT700R": { "folder": "HT700R调料文件", "log_file": "HT700R调料日志.txt" } } def setup_logging(folder_path, log_file_name): log_file_path = os.path.join(str(folder_path), str(log_file_name)) logging.basicConfig(filename=log_file_path, level=logging.INFO, format='%(message)s') with open(log_file_path, 'a') as log_file: log_file.write('\n') def setup_paths(alloy_name): config = Ni_BASED_ALLOY_CONFIGS[alloy_name] condiments_folder = os.path.join(BASE_DIR, config['folder']) global DF_RANGE_REQUIREMENTS xlsx_files = [f for f in os.listdir(condiments_folder) if f.endswith('.xlsx')] if xlsx_files: # 取第一个 .xlsx 文件作为成分页模板 DF_RANGE_REQUIREMENTS = os.path.join(condiments_folder, xlsx_files[0]) folder_path = os.path.join(condiments_folder, '测试结果') os.makedirs(folder_path, exist_ok=True) setup_logging(folder_path, config['log_file']) return folder_path def find_xlsx_files(folder_path): search_path = os.path.join(folder_path, '*.xlsx') file_paths = [os.path.join(folder_path, f) for f in os.listdir(folder_path) if f.endswith('.xlsx')] return file_paths def process_test_results(file_path): try: df_test_results = pd.read_excel(file_path) df_results = df_test_results.iloc[9:25, [0, 3]].replace('<', '', regex=True) df_results.columns = ['元素', '测试结果'] df_results['测试结果'] = pd.to_numeric(df_results['测试结果'], errors='coerce') df_results.reset_index(drop=True, inplace=True) return df_results except Exception as e: logging.error(f'Error processing file {file_path}: {e}') return pd.DataFrame() def merge_data(requirements_file, df_results): df_range_requirements = pd.read_excel(requirements_file) df_merge = pd.merge(df_range_requirements, df_results, on='元素', how='left') return df_merge def check_element_ranges(df_merge): alerts = [] standard_range_issues_plus = 0 standard_range_issues_sub = 0 general_range_issues_plus = 0 general_range_issues_sub = 0 target_range_issues_plus = 0 target_range_issues_sub = 0 for index, row in df_merge.iterrows(): element = row['元素'] test_result = row['测试结果'] range_row = df_merge[df_merge['元素'] == element].iloc[0] if test_result < range_row['标准范围下限']: alerts.append(f"警报:元素{element}<标准范围。") standard_range_issues_sub += 1 elif test_result > range_row['标准范围上限']: alerts.append(f"警报:元素{element}>标准范围。") standard_range_issues_plus += 1 elif test_result < range_row['一般范围下限']: alerts.append(f"注意:元素{element}<一般范围。") general_range_issues_sub += 1 elif test_result > range_row['一般范围上限']: alerts.append(f"注意:元素{element}>一般范围。") general_range_issues_plus += 1 elif test_result < range_row['目标范围下限']: alerts.append(f"注意:元素{element}<目标范围。") target_range_issues_sub += 1 elif test_result > range_row['目标范围上限']: alerts.append(f"注意:元素{element}>目标范围。") target_range_issues_plus += 1 if standard_range_issues_sub + standard_range_issues_plus > 0: alerts.append("警报:存在元素超过标准范围,请联系牌号负责人进行调料。") elif general_range_issues_sub + general_range_issues_plus + target_range_issues_sub + target_range_issues_plus > 3: alerts.append("警报:超过3种元素不在目标范围内,请联系牌号负责人进行调料。") else: alerts.append("注意:元素检测结果处于正常波动水平,请按照程序调料。") return alerts def main_process(alloy_name, alloy_weight): global TOTAL_WEIGHT TOTAL_WEIGHT = alloy_weight folder_path = setup_paths(alloy_name) file_paths = find_xlsx_files(folder_path) if not file_paths: return {"status": "error", "message": "未找到测试结果文件,请检查文件目录。"} # 针对每个测试文件执行处理(这里仅取第一个作为示例) df_results = process_test_results(file_paths[0]) df_merge = merge_data(DF_RANGE_REQUIREMENTS, df_results) alerts = check_element_ranges(df_merge) # 这里只返回警报信息,实际应用中可以增加计算补料、验算等逻辑 return {"status": "success", "alerts": alerts} @app.route('/') def index(): return render_template('index.html') @app.route('/process', methods=['POST']) def process_route(): # 从前端提交的数据中接收牌号和钢液重量 alloy_name = request.form.get('alloy_name') weight = request.form.get('weight') try: weight = float(weight) except Exception as e: return jsonify({"status": "error", "message": "钢液重量输入格式错误。"}) result = main_process(alloy_name, weight) return jsonify(result) if __name__ == '__main__': # 调试模式下运行,正式部署时推荐使用 gunicorn 或其他 WSGI 服务器 app.run(debug=True)

前端代码示例(templates/index.html)

在项目目录下创建 templates 文件夹,在里面放置 index.html

html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Ni基牌号调料程序</title> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> </head> <body> <h1>Ni基牌号调料程序</h1> <form id="process-form"> <label for="alloy_name">请选择牌号:</label> <select name="alloy_name" id="alloy_name"> <option value="XB-407M2">XB-407M2</option> <option value="HT700R">HT700R</option> </select> <br><br> <label for="weight">钢液重量(kg):</label> <input type="text" id="weight" name="weight" required> <br><br> <input type="submit" value="开始调料计算"> </form> <div id="result" style="margin-top:20px;"> <!-- 显示结果和警报信息 --> </div> <script> $(document).ready(function(){ $('#process-form').on('submit', function(e){ e.preventDefault(); $.ajax({ url: '/process', type: 'POST', data: $(this).serialize(), success: function(data) { let resultDiv = $('#result'); if (data.status === "success") { let alerts = data.alerts; let html = '<h3>计算结果:</h3>'; alerts.forEach(function(alertMsg){ html += '<p>' + alertMsg + '</p>'; }); resultDiv.html(html); } else { resultDiv.html('<p style="color:red;">' + data.message + '</p>'); } }, error: function(err) { $('#result').html('<p style="color:red;">发生错误,请重试。</p>'); } }); }); }); </script> </body> </html>

3. 工作原理说明

  1. 用户界面交互
    用户通过浏览器访问 /,看到一个简单的表单,可以选择牌号并输入钢液重量,然后点击“开始调料计算”。

  2. 前端 AJAX 提交
    表单数据通过 AJAX 提交到 /process 接口。前端 JavaScript(这里使用了 jQuery)捕获表单提交事件,将数据以 POST 请求发送到后端。

  3. 后端处理与计算
    后端 Flask 路由 /process 接收到参数后,调用 main_process(这部分整合了你原有代码的部分逻辑),进行文件路径配置、数据读取、数据合并和报警检查等计算。

  4. 返回结果
    计算结果(例如各种警报信息)以 JSON 格式返回给前端。前端接收到后,将结果动态显示在页面上。

  5. 日志与提示
    后端依然保留日志记录功能,计算记录、警报等信息写入日志文件中,同时可以根据需要扩展更多业务逻辑(例如二次调料计算、文件下载等)。


4. 后续扩展提示

  • 功能扩展
    可以为各个具体的计算函数(如 calculate_additions_subcalculate_additions_plus 等)设计独立接口,或者统一通过一个接口返回详细调料方案。
  • 错误处理
    适当增加异常捕获和错误提示,确保前端能友好展示错误信息。
  • 文件交互
    如果需要用户上传测试结果文件或调整成分页模板,可以加入文件上传组件;后端使用 Flask 的 request.files 获取上传的文件,然后传给相关函数进行处理。
  • 部署上线
    开发完成后,请使用诸如 Gunicorn、uWSGI 这类服务器搭配 Nginx 或 Apache 部署到生产环境。

通过以上步骤,你可以将原有基于 Tkinter 的后端逻辑转为在网页上交互的应用。这个示例代码只是一个基础骨架,根据具体业务逻辑和用户需求,还需要进行进一步的功能完善和测试。

Question

这种本身py文件设计很多页面交互的代码,是不是不适合使用html进行网页界面交互?有什么好的方案推荐吗

Thought

Considering UI design options

The user's asking about using HTML for web interface interaction in a Python file that's already designed with Tkinter for local UI. HTML is generally better suited for web applications, whereas Tkinter is for desktop apps. For web interfaces, Flask, Django, or Streamlit are great solutions to connect your Python backend to web-based UIs. These frameworks allow you to leverage HTML, CSS, and JavaScript for the front end while keeping your Python logic.

Recommending integration options

The user is asking about moving from Tkinter to a web interface. Since Tkinter is a desktop UI library, switching to HTML would usually require a code re-architecture. However, there are solutions to integrate Python backend logic with web UIs without rewriting everything, like using Flask, Django, or hybrid frameworks like PyWebIO and Streamlit. Another option is converting the application using frameworks like Eel, or packing it into a desktop app with PyWebview or PyQt’s web engine. Best practice is to decouple business logic from UI logic.

Reasoned about suitable interface solutions for 16 seconds

Answer

在现有的代码中,你已经把很多与界面交互相关的逻辑写在了 Python 代码中(例如使用 Tkinter 来实现提示、输入和输出),这在桌面应用中是可行的。但如果你想迁移到网页应用,则需要考虑以下几点:


1. 架构与职责分离

问题根源

  • 耦合性高:目前的代码中,业务逻辑与用户交互(UI)紧密耦合在一起。一旦转换到网页平台,整个交互方式和用户体验都会发生变化。
  • 界面技术栈不同:桌面应用(Tkinter、PyQt等)使用的是本地窗口组件,而网页则依赖 HTML、CSS 和 JavaScript,这就要求你必须重写或重新封装交互部分的代码。

推荐方案

  • 分离业务逻辑与前端展示:将已有的业务计算逻辑(文件处理、数据计算、报警判断等)提取成纯粹的 Python 模块,提供清晰的函数接口或 API。
  • 后端 API 设计:利用 Web 框架(例如 Flask、Django)封装这些接口,通过 HTTP 请求调用业务逻辑。
  • 前端构建:独立构建网页前端,使用 HTML、CSS、JavaScript(或现代前端框架如 React/Vue/Angular)来搭建用户界面,再通过 AJAX 请求与后端 API 进行数据交互。

2. 可行的 Web 应用方案

a. 传统 Web 框架(Flask / Django)

  • 优点
    • 框架成熟,社区支持丰富。
    • 清晰分层,可以把纯业务逻辑封装在后端,再用模板引擎或 REST API 与前端交互。
  • 实现方式
    • 把现有业务逻辑抽离成模块(例如计算、数据转换函数)。
    • 使用 Flask 创建 API 路由,接收前端的参数(如牌号、钢液重量等),返回 JSON 格式的计算结果和警报信息。
    • 前端用 jQuery、Vue.js 等构建交互界面,通过 AJAX 与后端接口通信。

b. 快速开发框架(Streamlit / Dash / PyWebIO)

  • 优点
    • 允许你直接用 Python 编写交互页面,降低了前端开发门槛。
    • 适合快速原型开发和数据展示,但可能在交互复杂性上不如完整的前后端分离解决方案。
  • 缺点
    • 如果你的应用逻辑已经比较复杂,可能需要额外的工作来适应框架的开发模式。
  • 推荐场景
    • 应用规模较小,交互逻辑不会太复杂,或者你想快速验证思路。

c. 桌面和网页双用方案(Eel、Pywebview)

  • Eel
    • 能够让你用 HTML/JavaScript 构建前端界面,同时调用 Python 后端逻辑。
    • 不用像 Flask 那样完全分离前后端,适合希望保留一些原有 Python 代码结构的项目。
  • Pywebview
    • 用于创建基于网页技术的桌面应用,在一个窗口中嵌入 HTML/CSS/JS,后端依然使用 Python。
  • 适用场景
    • 如果你希望最终产品既可以作为桌面应用,也能通过 Web 浏览器进行访问,这些工具可以作为过渡方案。

3. 具体建议

  • 重新设计交互层:虽然你已经在代码中实现了交互逻辑,但在网页中,你需要重新设计用户输入、提示和反馈的方式。例如,可以设计一个现代的网页表单,动态显示运算进度、计算结果以及警报信息,而不是依赖 Tkinter 的弹窗提示。

  • 重构代码以提高可维护性

    • 把数据处理和计算函数抽离成一个独立的模块;
    • 为每个核心计算功能编写单元测试,以便在迁移过程中保证业务逻辑不出问题;
    • 提供一个干净、统一的 API 接口,让前端只需要关心如何展示数据,而不关心底层的实现逻辑。
  • 考虑使用现代开发框架

    • 对于网页版应用,如果你已有后端 Python 逻辑,推荐使用 FlaskDjango REST framework 来构建 RESTful API,然后用一个轻量级的前端框架(例如 Vue.js 或 React)构建网页界面。
    • 如果追求快速迭代和简单部署,可以考虑 StreamlitDash,这些工具能让你用 Python 一站式解决前后端问题。

结论

虽然你目前的 Python 文件中包含了大量的交互代码,但直接映射为 HTML 网页界面并不是最优方案。最佳做法是解耦业务逻辑与用户交互部分,并选择适当的 Web 框架来重构前端和后端。这不仅能够提高代码的可维护性,也更容易扩展和适应不同平台的需求。根据你的项目规模和目标用户群体,可以选择传统的 Flask/Django 架构、或更快速的 Streamlit/Dash 等工具,甚至使用 Eel 这种适合桌面与网页混合应用的方案。

Question

有没有可以不用移植py代码的交互逻辑到html文件上,可以直接在html文件上运行python的程序,展示python的交互,并在html上显示结果

Thought

Exploring Python in HTML

The user is asking if it's possible to run Python code directly in an HTML file without needing to transfer the logic to the front-end. Solutions like Pyodide and Brython allow running Python in the browser, as Pyodide uses WebAssembly for CPython. Jupyter Notebooks (or Jupyter Lite) integrate well with Python interactions. For wrapping apps, frameworks like Eel or pywebview can let Python interact with a web interface without reworking the code to JavaScript.

Reviewing Python solutions for the web

There are several tools to run Python in the browser without converting the logic to HTML. For instance:

  • Pyodide runs CPython in the browser with many libraries, making it a strong option.
  • Brython is a Python 3 interpreter in JavaScript, but it doesn’t support every library.
  • Anvil is a platform for building full-stack apps with Python on the client side.
  • Transcrypt compiles Python to JavaScript.

However, note that some libraries (like Tkinter, matplotlib, or backend libraries) may not work well in these environments.

Reasoned for 21 seconds

Answer

下面介绍几种解决方案,可以让你在 HTML 页面中直接运行 Python 代码,而无需将交互逻辑全部重写为前端 JavaScript 代码。这些方案实际上是在浏览器中“嵌入”一个 Python 解释器,使得你能够复用现有的 Python 代码逻辑,并在 HTML 页面中展示交互结果。


1. Pyodide

Pyodide 是目前较成熟的方案之一,它将 CPython 编译为 WebAssembly,使得 Python 解释器及大量常用库(例如 NumPy、Pandas、SciPy 等)可以在浏览器中运行。你可以在 HTML 页面中直接引入 Pyodide,通过 JavaScript 调用 Python 代码并获取输出。

优点:

  • 支持大部分常用 Python 库。
  • 直接运行 Python 代码,无需将代码转换成 JavaScript。
  • 易于集成,并且通过 AJAX、按钮等方式触发后端计算,然后在网页上展示结果。

缺点:

  • 初次加载时需要下载 WebAssembly 模块,体积较大,加载时间可能较长。
  • 对于有图形界面(如 Tkinter)的代码不适用,需要做一定改造(例如使用文本或 HTML 表单替代)。
  • 有些包可能因为运行环境限制而无法完整支持。

示例: 下面是一个简单的 HTML 示例,使用 Pyodide 运行 Python 代码,并将结果显示在页面上:

html
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>在浏览器中运行 Python(Pyodide 示例)</title> <!-- 引入 Pyodide 最新版 --> <script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script> </head> <body> <h1>在网页中运行 Python</h1> <textarea id="pycode" rows="10" cols="50"> import sys print("Hello from Python!") print("Python 版本:", sys.version) </textarea> <br> <button onclick="runPython()">运行 Python 代码</button> <pre id="output"></pre> <script> async function loadPyodideAndPackages() { window.pyodide = await loadPyodide(); // 如果需要其他包,例如 pandas、numpy,可以调用 pyodide.loadPackage(...) } loadPyodideAndPackages(); async function runPython() { const code = document.getElementById("pycode").value; const outputElement = document.getElementById("output"); try { // 运行 Python 代码(异步方式) let result = await pyodide.runPythonAsync(code); outputElement.textContent = result || "代码已执行,输出查看终端(print 语句的输出会自动出现在浏览器控制台或绑定的输出中)。"; } catch (err) { outputElement.textContent = "运行错误: " + err; } } </script> </body> </html>

在这个示例中,我们通过 <script> 标签加载 Pyodide,然后使用 pyodide.runPythonAsync 运行用户提交的 Python 代码。这样你可以很方便地把原来的 Python 逻辑嵌入到 HTML 页面中,并通过前端交互展示计算结果。


2. Brython

Brython 是另一种在浏览器中运行 Python 代码的方案,它将 Python 代码直接转换并在浏览器中执行。不过 Brython 对标准库的支持和第三方库的兼容性比 Pyodide 略弱,适合简单的交互场景。

优缺点:

  • 优点: 部署简单,适用于较轻量级的应用与简单交互。
  • 缺点: 对复杂依赖(如 Numpy、Pandas)支持有限,不适合需要大量科学计算或数据处理的场景。

3. 其他方案

a. JupyterLite

JupyterLite 是一个基于 WebAssembly 和 Pyodide 的轻量级 Jupyter 环境,允许你在浏览器中运行交互式笔记本。如果你的应用逻辑可以整理为 Notebook 的格式,这是一个不错的选择。不过这种方案更适合数据分析展示,而不是生产型的交互式网页应用。

b. 桌面/网页混合方案(Eel、PyWebView)

如果你希望保持现有 Python 代码的同时又提供基于网页的交互界面,还可以考虑使用 EelPyWebView 这类工具。它们允许你用 HTML/CSS/JavaScript 构建前端界面,同时通过 Python 后端逻辑处理业务。这种方案适合制作桌面应用,并且在技术上不需要将所有 Python 代码移植成纯前端执行。


4. 总结

  • 如果你希望真正实现“在 HTML 页面上直接运行 Python 代码”,Pyodide 是目前较推荐的方案,能够支持大部分你已有的业务逻辑和常用的计算库。
  • Brython 更适合简单交互,如果不依赖过多科学计算库,也可以考虑。
  • 如果你不希望重写交互层,同时又希望拥有类似网页的用户体验,EelPyWebView 也能让你在桌面应用中集成 HTML 界面与 Python 后端。

选择哪种方案,关键在于你的业务逻辑复杂度、对第三方库的依赖以及预期的用户体验。对于需要大量数据处理和计算的项目,Pyodide 可能会更适合;而如果只是简单的交互演示,Brython 也是一个不错的选择。

Question

可以在html页面上设计一个按钮,点击后,自动链接到某个本地硬盘的路径的应用程序,将其打开吗,帮我设计一段代码,自动打开"D:\Apps\CCleaner\CCleaner.exe"。

Thought

Laying out the options

I’m working through the user’s request, which involves identifying and addressing errors based on their severity levels, covering errors like elements outside standard ranges or more than three elements outside the target range. Progressing through these points is the next step.

Evaluating error severity

I’m assessing the user's request, focusing on error handling based on severity, addressing elements outside standard ranges or more than three elements outside the target range, and determining the next steps for progression.

Evaluating deficiencies

I'm working through elements beyond standard ranges and those covering over three elements outside the target range, determining the next steps and potential strategies for addressing these issues effectively.

Mapping error responses

I’m devising strategies to address errors by their severity, focusing on elements beyond standard ranges or more than three elements outside the target range. Next steps involve crafting a comprehensive action plan.

Strategies for addressing errors

I'm focusing on crafting comprehensive action plans to efficiently manage errors by addressing elements outside standard ranges or more than three elements outside the target range.

Planning a comprehensive response

I’m mapping out a strategic plan to manage errors efficiently, focusing on elements outside standard ranges or more than three elements beyond the target range. This ensures a thorough solution.

Reasoned for 7 seconds

Answer

在 HTML 页面中直接通过按钮点击打开本地硬盘上的应用程序,受到浏览器安全限制的影响,是不允许的。现代浏览器会阻止网页直接访问用户本地文件系统,出于安全原因,它不允许网页代码执行任何可能会修改用户计算机文件或执行可执行程序的操作。

然而,有几种方式可以间接地实现这个功能:

1. 通过自定义协议(如 Windows 注册表)

你可以设置一个自定义协议或本地服务,允许通过特定 URL 打开本地程序。但这种方法相对复杂且不太常见,需要进行一些操作系统级别的配置。

2. 使用桌面应用程序(如 Electron、Eel)

如果你希望网页按钮点击打开本地应用程序,推荐使用像 ElectronEel 这样的框架来构建桌面应用,这些框架允许你将 HTML、JavaScript 和 Python 代码结合在一起并且访问本地系统资源。

3. 通过浏览器扩展或用户脚本

如果你有一个特定的需求并且只在某些浏览器或环境下工作,可能可以通过浏览器扩展或用户脚本(如使用 Tampermonkey)来实现。但这种方式适用性较差,只能在特定环境下运行。


在本地应用中运行的方法(Electron 框架示例)

假如你选择使用 Electron 这样的桌面应用框架,下面是一个示例代码,演示如何通过按钮点击打开本地应用程序 CCleaner.exe

步骤 1:安装 Electron

首先,确保你已经安装了 Node.js 和 npm。然后在你的项目目录下执行以下命令来安装 Electron:

bash
npm init -y npm install electron --save-dev

步骤 2:创建 index.html

这是你的前端 HTML 页面,包含一个按钮,点击后会调用 Electron 后端打开本地应用。

html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>打开本地应用</title> </head> <body> <h1>点击按钮打开 CCleaner</h1> <button id="openApp">打开 CCleaner</button> <script> document.getElementById("openApp").addEventListener("click", function() { // 发送事件给 Electron 后端,执行打开应用的操作 window.electronAPI.openCCleaner(); }); </script> </body> </html>

步骤 3:创建 main.js

这是 Electron 后端代码,它会监听 openCCleaner 事件,并使用 Node.js 的 child_process 模块打开 CCleaner.exe

javascript
const { app, BrowserWindow, ipcMain } = require('electron'); const path = require('path'); const { exec } = require('child_process'); // 创建浏览器窗口 function createWindow() { const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js') // 引入 preload 文件 } }); win.loadFile('index.html'); // 加载 HTML 页面 } app.whenReady().then(() => { createWindow(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); }); // 退出应用时关闭 app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); // 监听来自前端的打开 CCleaner 请求 ipcMain.handle('open-ccleaner', () => { exec('"D:\\Apps\\CCleaner\\CCleaner.exe"', (err, stdout, stderr) => { if (err) { console.error('打开 CCleaner 错误:', err); } else { console.log('CCleaner 启动成功'); } }); });

步骤 4:创建 preload.js

preload.js 用来将 Electron 后端的功能暴露给前端。它可以通过 window.electronAPI 来调用后端逻辑。

javascript
const { contextBridge, ipcRenderer } = require('electron'); // 暴露一个 API 给前端页面 contextBridge.exposeInMainWorld('electronAPI', { openCCleaner: () => ipcRenderer.invoke('open-ccleaner') });

步骤 5:更新 package.json

确保 package.json 配置了 Electron 的启动入口:

json
{ "name": "open-local-app", "version": "1.0.0", "main": "main.js", "scripts": { "start": "electron ." }, "devDependencies": { "electron": "^15.0.0" } }

步骤 6:运行应用

执行以下命令启动 Electron 应用:

bash
npm start

总结

虽然浏览器本身不允许直接通过 HTML 页面打开本地应用程序,但通过 Electron 这样的桌面框架,你可以结合 HTML、JavaScript 和 Node.js 来实现类似的功能。用户点击按钮后,Electron 后端可以调用本地的可执行程序。

如果你的目标是构建一个可以跨平台运行的桌面应用,并且保留网页前端和本地应用逻辑结合,Electron 是一个非常适合的选择。

Share this Q&A