# 本程序用于Ni基牌号调料程序 # 较严重的错误会覆盖次严重的错误,错误严重级别由大到小排序: #...
Created on: April 13, 2025
Answered using O3 Mini High by Chat01
Created on: April 13, 2025
Answered using O3 Mini High by Chat01
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.set_option('future.no_silent_downcasting', True)
TOTAL_WEIGHT = 0.0
DF_RANGE_REQUIREMENTS = None
FOLDER_PATH = os.path.dirname(os.path.abspath(file))
if getattr(sys, 'frozen', False):
application_path = os.path.dirname(sys.executable)
else:
application_path = 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] # 这里输入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)
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
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]
textif 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)
# 注意集合之中不允许存在同样的两种符号,所以这里可以判断是哪一种超限情况以及严重超限情况
textif "警报" 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网页,可以在网页中与后端代码交互,在前端实现后端的一些互动操作的功能。
下面给出一个详细的实现思路和示例代码,帮助你将现有的后端 Python 代码转变为一个可以通过网页进行交互的系统。基本思路是利用 Python Web 框架(如 Flask 或 Django)构建一个 web 服务器,由后端接口调用你原有的业务逻辑,然后通过 HTML、JavaScript 与用户交互。
alloy_weight_input
、main_process
、各个计算函数)封装为独立的函数,并在 Flask 中通过路由暴露出来。例如:
下面提供一个基于 Flask 的示例,其中包含:
/
路由下渲染 HTML 页面,在 /process
路由下接收表单数据进行计算。pythonfrom 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
:
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>
用户界面交互
用户通过浏览器访问 /
,看到一个简单的表单,可以选择牌号并输入钢液重量,然后点击“开始调料计算”。
前端 AJAX 提交
表单数据通过 AJAX 提交到 /process
接口。前端 JavaScript(这里使用了 jQuery)捕获表单提交事件,将数据以 POST 请求发送到后端。
后端处理与计算
后端 Flask 路由 /process
接收到参数后,调用 main_process
(这部分整合了你原有代码的部分逻辑),进行文件路径配置、数据读取、数据合并和报警检查等计算。
返回结果
计算结果(例如各种警报信息)以 JSON 格式返回给前端。前端接收到后,将结果动态显示在页面上。
日志与提示
后端依然保留日志记录功能,计算记录、警报等信息写入日志文件中,同时可以根据需要扩展更多业务逻辑(例如二次调料计算、文件下载等)。
calculate_additions_sub
、calculate_additions_plus
等)设计独立接口,或者统一通过一个接口返回详细调料方案。request.files
获取上传的文件,然后传给相关函数进行处理。通过以上步骤,你可以将原有基于 Tkinter 的后端逻辑转为在网页上交互的应用。这个示例代码只是一个基础骨架,根据具体业务逻辑和用户需求,还需要进行进一步的功能完善和测试。
这种本身py文件设计很多页面交互的代码,是不是不适合使用html进行网页界面交互?有什么好的方案推荐吗
在现有的代码中,你已经把很多与界面交互相关的逻辑写在了 Python 代码中(例如使用 Tkinter 来实现提示、输入和输出),这在桌面应用中是可行的。但如果你想迁移到网页应用,则需要考虑以下几点:
问题根源
推荐方案
重新设计交互层:虽然你已经在代码中实现了交互逻辑,但在网页中,你需要重新设计用户输入、提示和反馈的方式。例如,可以设计一个现代的网页表单,动态显示运算进度、计算结果以及警报信息,而不是依赖 Tkinter 的弹窗提示。
重构代码以提高可维护性:
考虑使用现代开发框架:
虽然你目前的 Python 文件中包含了大量的交互代码,但直接映射为 HTML 网页界面并不是最优方案。最佳做法是解耦业务逻辑与用户交互部分,并选择适当的 Web 框架来重构前端和后端。这不仅能够提高代码的可维护性,也更容易扩展和适应不同平台的需求。根据你的项目规模和目标用户群体,可以选择传统的 Flask/Django 架构、或更快速的 Streamlit/Dash 等工具,甚至使用 Eel 这种适合桌面与网页混合应用的方案。
有没有可以不用移植py代码的交互逻辑到html文件上,可以直接在html文件上运行python的程序,展示python的交互,并在html上显示结果
下面介绍几种解决方案,可以让你在 HTML 页面中直接运行 Python 代码,而无需将交互逻辑全部重写为前端 JavaScript 代码。这些方案实际上是在浏览器中“嵌入”一个 Python 解释器,使得你能够复用现有的 Python 代码逻辑,并在 HTML 页面中展示交互结果。
Pyodide 是目前较成熟的方案之一,它将 CPython 编译为 WebAssembly,使得 Python 解释器及大量常用库(例如 NumPy、Pandas、SciPy 等)可以在浏览器中运行。你可以在 HTML 页面中直接引入 Pyodide,通过 JavaScript 调用 Python 代码并获取输出。
优点:
缺点:
示例: 下面是一个简单的 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 页面中,并通过前端交互展示计算结果。
Brython 是另一种在浏览器中运行 Python 代码的方案,它将 Python 代码直接转换并在浏览器中执行。不过 Brython 对标准库的支持和第三方库的兼容性比 Pyodide 略弱,适合简单的交互场景。
优缺点:
JupyterLite 是一个基于 WebAssembly 和 Pyodide 的轻量级 Jupyter 环境,允许你在浏览器中运行交互式笔记本。如果你的应用逻辑可以整理为 Notebook 的格式,这是一个不错的选择。不过这种方案更适合数据分析展示,而不是生产型的交互式网页应用。
如果你希望保持现有 Python 代码的同时又提供基于网页的交互界面,还可以考虑使用 Eel 或 PyWebView 这类工具。它们允许你用 HTML/CSS/JavaScript 构建前端界面,同时通过 Python 后端逻辑处理业务。这种方案适合制作桌面应用,并且在技术上不需要将所有 Python 代码移植成纯前端执行。
选择哪种方案,关键在于你的业务逻辑复杂度、对第三方库的依赖以及预期的用户体验。对于需要大量数据处理和计算的项目,Pyodide 可能会更适合;而如果只是简单的交互演示,Brython 也是一个不错的选择。
可以在html页面上设计一个按钮,点击后,自动链接到某个本地硬盘的路径的应用程序,将其打开吗,帮我设计一段代码,自动打开"D:\Apps\CCleaner\CCleaner.exe"。
在 HTML 页面中直接通过按钮点击打开本地硬盘上的应用程序,受到浏览器安全限制的影响,是不允许的。现代浏览器会阻止网页直接访问用户本地文件系统,出于安全原因,它不允许网页代码执行任何可能会修改用户计算机文件或执行可执行程序的操作。
然而,有几种方式可以间接地实现这个功能:
你可以设置一个自定义协议或本地服务,允许通过特定 URL 打开本地程序。但这种方法相对复杂且不太常见,需要进行一些操作系统级别的配置。
如果你希望网页按钮点击打开本地应用程序,推荐使用像 Electron 或 Eel 这样的框架来构建桌面应用,这些框架允许你将 HTML、JavaScript 和 Python 代码结合在一起并且访问本地系统资源。
如果你有一个特定的需求并且只在某些浏览器或环境下工作,可能可以通过浏览器扩展或用户脚本(如使用 Tampermonkey)来实现。但这种方式适用性较差,只能在特定环境下运行。
假如你选择使用 Electron 这样的桌面应用框架,下面是一个示例代码,演示如何通过按钮点击打开本地应用程序 CCleaner.exe
。
首先,确保你已经安装了 Node.js 和 npm。然后在你的项目目录下执行以下命令来安装 Electron:
bashnpm init -y npm install electron --save-dev
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>
main.js
这是 Electron 后端代码,它会监听 openCCleaner
事件,并使用 Node.js 的 child_process
模块打开 CCleaner.exe
。
javascriptconst { 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 启动成功'); } }); });
preload.js
preload.js
用来将 Electron 后端的功能暴露给前端。它可以通过 window.electronAPI
来调用后端逻辑。
javascriptconst { contextBridge, ipcRenderer } = require('electron'); // 暴露一个 API 给前端页面 contextBridge.exposeInMainWorld('electronAPI', { openCCleaner: () => ipcRenderer.invoke('open-ccleaner') });
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" } }
执行以下命令启动 Electron 应用:
bashnpm start
虽然浏览器本身不允许直接通过 HTML 页面打开本地应用程序,但通过 Electron 这样的桌面框架,你可以结合 HTML、JavaScript 和 Node.js 来实现类似的功能。用户点击按钮后,Electron 后端可以调用本地的可执行程序。
如果你的目标是构建一个可以跨平台运行的桌面应用,并且保留网页前端和本地应用逻辑结合,Electron 是一个非常适合的选择。