623. Add One Row to Tree

本文介绍了一个LeetCode上的算法题,任务是在二叉树的指定深度处插入带有特定值的新节点。文章提供了详细的实现思路及代码示例,特别注意了当指定深度为1时的特殊情况。

题目描述【Leetcode

Given the root of a binary tree, then value v and depth d, you need to add a row of nodes with value v at the given depth d. The root node is at depth 1.

The adding rule is: given a positive integer depth d, for each NOT null tree nodes N in depth d-1, create two tree nodes with value v as N’s left subtree root and right subtree root. And N’s original left subtree should be the left subtree of the new left subtree root, its original right subtree should be the right subtree of the new right subtree root. If depth d is 1 that means there is no depth d-1 at all, then create a tree node with value v as the new root of the whole original tree, and the original tree is the new root’s left subtree.

Example 1:
这里写图片描述

Example 2:
这里写图片描述
Note:
The given d is in range [1, maximum depth of the given tree + 1].
The given binary tree has at least one tree node.

这道题是在某一个深度插入所给的节点,并不难,但是需要考虑d为1的情况:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
void f(TreeNode* root,int h, int v, int d){
    if(!root) return;
    if(h == d-1){
         TreeNode * lt = new TreeNode(v);
         TreeNode* rt = new TreeNode(v);
         lt->left = root->left;
         rt->right = root->right;
         root->left = lt;
         root->right = rt;
         return ;
    }
        f(root->left,h+1,v,d);
        f(root->right,h+1,v,d);

}

class Solution {
public:
    TreeNode* addOneRow(TreeNode* root, int v, int d) {
        if(d == 1){
            TreeNode * tr = new TreeNode(v);
            tr->left = root;
            return tr;
        }
        f(root,1,v,d);
        return root;
    }
};
import tkinter as tk from tkinter import ttk, messagebox, scrolledtext, filedialog import serial import serial.tools.list_ports import threading import time import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import openpyxl from openpyxl.chart import LineChart, Reference from openpyxl.chart.label import DataLabelList from openpyxl.styles import Font, Alignment from datetime import datetime import queue import json import winsound # Windows提示音(非Windows可注释) # --- 优化字体配置 --- plt.rcParams["font.family"] = ["Microsoft YaHei", "SimHei", "DejaVu Sans", "Arial Unicode MS"] plt.rcParams['axes.unicode_minus'] = False class USBWeightMonitor: def __init__(self, root): self.root = root self.root.title("USB称重变送器 + 机械臂控制系统") self.root.geometry("1600x900") self.root.resizable(True, True) # ========== 称重核心变量 ========== self.ser = None self.is_connected = False self.raw_data = [] # 存储所有原始采样点 [(time_str, weight_g)] self.peak_valley_log = [] # 存储所有识别出的峰值/谷值 [(time_str, weight_g, 'peak'/'valley')] self.sample_interval = 500 # 默认采样间隔 (ms) self.unit = "g" self.recording = False self.data_queue = queue.Queue() self.lock = threading.Lock() self.max_data_points = 1000 # 用于绘图的缓冲区大小 self.max_weight = None self.min_weight = None self.max_time = None self.min_time = None # 用于峰值检测的变量 self.last_weight = None self.peak_detection_buffer = [] # 临时缓冲区,用于峰值检测 self.peak_detection_window = 5 # 用于峰值检测的滑动窗口大小 # 峰值检测的辅助变量 self.last_peak_time = None self.last_valley_time = None self.last_peak_weight = None self.last_valley_weight = None # 用于GUI更新的临时列表 self.new_peak_valley_events = [] # 存储新检测到的峰值/谷值事件 [(time_str, weight_g, 'peak'/'valley')] # 峰值记录开关 self.record_peaks_var = tk.BooleanVar(value=True) # 默认开启 # ========== 机械臂相关变量 ========== self.arm_ser = None self.arm_is_connected = False self.move_step = tk.DoubleVar(value=1.0) self.gcode_content = "" self.running_gcode = False self.loop_count = tk.IntVar(value=1) self.current_loop = 0 # ========== 创建UI ========== self.create_widgets() # 初始化 self.refresh_com_ports() self.refresh_arm_ports() self.update_plot_periodic() self.process_data_queue() self.update_peak_log_table_periodically() # 启动GUI更新循环 def create_widgets(self): # 主容器,使用 Grid main_container = ttk.Frame(self.root) main_container.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) main_container.grid_columnconfigure(0, weight=1) main_container.grid_columnconfigure(1, weight=1) main_container.grid_rowconfigure(1, weight=1) # 曲线图区域占用最大空间 main_container.grid_rowconfigure(2, weight=0) # 日志区域占用较小空间 # 1. 顶部:称重 & 机械臂 串口配置(一行两列) top_frame = ttk.Frame(main_container) top_frame.grid(row=0, column=0, columnspan=2, sticky=tk.EW, padx=5, pady=5) top_frame.grid_columnconfigure(0, weight=1) top_frame.grid_columnconfigure(1, weight=1) # 称重设备配置 self.top_weight_frame = ttk.LabelFrame(top_frame, text="称重设备串口配置") self.top_weight_frame.grid(row=0, column=0, sticky=tk.EW, padx=(0, 5), pady=5) self.top_weight_frame.grid_columnconfigure(4, weight=1) ttk.Label(self.top_weight_frame, text="COM口:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) self.port_combo = ttk.Combobox(self.top_weight_frame, state="readonly") self.port_combo.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W) ttk.Button(self.top_weight_frame, text="刷新端口", command=self.refresh_com_ports).grid(row=0, column=2, padx=5, pady=5) ttk.Label(self.top_weight_frame, text="波特率:").grid(row=0, column=3, padx=5, pady=5, sticky=tk.W) self.baud_combo = ttk.Combobox(self.top_weight_frame, values=["38400", "115200"], state="readonly") self.baud_combo.current(0) self.baud_combo.grid(row=0, column=4, padx=5, pady=5, sticky=tk.W) self.connect_btn = ttk.Button(self.top_weight_frame, text="连接设备", command=self.toggle_connection) self.connect_btn.grid(row=0, column=5, padx=10, pady=5) self.unit_btn = ttk.Button(self.top_weight_frame, text="切换到N", command=self.toggle_unit) self.unit_btn.grid(row=0, column=6, padx=10, pady=5) # 机械臂配置 self.top_arm_frame = ttk.LabelFrame(top_frame, text="机械臂串口配置") self.top_arm_frame.grid(row=0, column=1, sticky=tk.EW, padx=(5, 0), pady=5) self.top_arm_frame.grid_columnconfigure(4, weight=1) ttk.Label(self.top_arm_frame, text="COM口:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) self.arm_port_combo = ttk.Combobox(self.top_arm_frame, state="readonly", width=10) self.arm_port_combo.grid(row=0, column=1, padx=5, pady=5) ttk.Button(self.top_arm_frame, text="刷新", command=self.refresh_arm_ports).grid(row=0, column=2, padx=5, pady=5) ttk.Label(self.top_arm_frame, text="波特率:").grid(row=0, column=3, padx=5, pady=5, sticky=tk.W) self.arm_baud_combo = ttk.Combobox(self.top_arm_frame, values=["9600", "19200", "38400", "115200"], state="readonly", width=10) self.arm_baud_combo.set("115200") self.arm_baud_combo.grid(row=0, column=4, padx=5, pady=5) self.arm_connect_btn = ttk.Button(self.top_arm_frame, text="连接机械臂", command=self.toggle_arm_connection) self.arm_connect_btn.grid(row=0, column=5, padx=10, pady=5) # 2. 中部:重量显示 + 曲线图 (左侧) & 机械臂控制 + 峰值日志 (右侧) mid_container = ttk.Frame(main_container) mid_container.grid(row=1, column=0, columnspan=2, sticky=tk.NSEW, padx=5, pady=5) mid_container.grid_columnconfigure(0, weight=2) mid_container.grid_columnconfigure(1, weight=1) mid_container.grid_rowconfigure(0, weight=1) # 左侧:重量显示 + 曲线图 left_panel = ttk.Frame(mid_container) left_panel.grid(row=0, column=0, sticky=tk.NSEW, padx=(0, 5)) # 重量显示 self.weight_display_frame = ttk.LabelFrame(left_panel, text="重量监控") self.weight_display_frame.pack(fill=tk.X, padx=5, pady=5) self.weight_label = ttk.Label(self.weight_display_frame, text="0.00 g", font=("Arial", 48, "bold")) self.weight_label.pack(pady=10) self.stats_frame = ttk.Frame(self.weight_display_frame) self.stats_frame.pack(pady=5) self.max_label = ttk.Label(self.stats_frame, text="最大值: -- g", font=("Arial", 12), foreground="red") self.max_label.grid(row=0, column=0, padx=20) self.min_label = ttk.Label(self.stats_frame, text="最小值: -- g", font=("Arial", 12), foreground="blue") self.min_label.grid(row=0, column=1, padx=20) # 曲线图 self.plot_frame = ttk.LabelFrame(left_panel, text="实时重力变化曲线") self.plot_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self.fig, self.ax = plt.subplots(figsize=(10, 4)) self.ax.set_xlabel("时间") self.ax.set_ylabel("重量 (g)") self.ax.set_title("实时重力变化曲线", fontsize=14, fontweight='bold') self.line, = self.ax.plot([], [], color="red", linewidth=1, label='Raw Data') self.peak_line, = self.ax.plot([], [], 'o', color="blue", markersize=4, label='Peaks') self.valley_line, = self.ax.plot([], [], 'v', color="green", markersize=4, label='Valleys') self.ax.legend() self.canvas = FigureCanvasTkAgg(self.fig, master=self.plot_frame) self.canvas.draw() self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 右侧:机械臂控制 + 峰值日志 right_panel = ttk.Frame(mid_container) right_panel.grid(row=0, column=1, sticky=tk.NSEW, padx=(5, 0)) # 机械臂控制 self.move_frame = ttk.LabelFrame(right_panel, text="手动移动 (X轴)") self.move_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Label(self.move_frame, text="步长 (mm):").grid(row=0, column=0, padx=5, pady=5) ttk.Entry(self.move_frame, textvariable=self.move_step, width=8).grid(row=0, column=1, padx=5, pady=5) self.x_left_btn = ttk.Button(self.move_frame, text="↓ X-1", command=lambda: self.move_arm("X", -1), state=tk.DISABLED) self.x_left_btn.grid(row=0, column=2, padx=5, pady=5) self.x_right_btn = ttk.Button(self.move_frame, text="X+1 ↑", command=lambda: self.move_arm("X", 1), state=tk.DISABLED) self.x_right_btn.grid(row=0, column=3, padx=5, pady=5) # G-code 控制 self.gcode_frame = ttk.LabelFrame(right_panel, text="G-code 控制") self.gcode_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Label(self.gcode_frame, text="G-code:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.NW) self.gcode_text = tk.Text(self.gcode_frame, height=4, width=40) self.gcode_text.grid(row=0, column=1, padx=5, pady=5, columnspan=3) self.gcode_text.insert(tk.END, "G91\nG0 X-1 F500\nG0 X1 F500\n") ttk.Label(self.gcode_frame, text="循环次数:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) ttk.Spinbox(self.gcode_frame, from_=1, to=100, textvariable=self.loop_count, width=8).grid(row=1, column=1, padx=5, pady=5, sticky=tk.W) btn_frame = ttk.Frame(self.gcode_frame) btn_frame.grid(row=1, column=2, columnspan=2, sticky=tk.E) self.send_gcode_btn = ttk.Button(btn_frame, text="发送G-code", command=self.send_gcode, state=tk.DISABLED) self.send_gcode_btn.pack(side=tk.LEFT, padx=5) self.stop_gcode_btn = ttk.Button(btn_frame, text="停止", command=self.stop_gcode, state=tk.DISABLED) self.stop_gcode_btn.pack(side=tk.LEFT, padx=5) # 峰值日志表格 self.peak_log_frame = ttk.LabelFrame(right_panel, text="峰值/谷值日志") self.peak_log_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self.peak_log_tree = ttk.Treeview(self.peak_log_frame, columns=("Time", "Weight", "Type"), show="headings", height=10) self.peak_log_tree.heading("Time", text="时间") self.peak_log_tree.heading("Weight", text="重量 (g)") self.peak_log_tree.heading("Type", text="类型") self.peak_log_tree.column("Time", width=120, anchor="center") self.peak_log_tree.column("Weight", width=80, anchor="center") self.peak_log_tree.column("Type", width=60, anchor="center") tree_scrollbar = ttk.Scrollbar(self.peak_log_frame, orient=tk.VERTICAL, command=self.peak_log_tree.yview) self.peak_log_tree.configure(yscrollcommand=tree_scrollbar.set) self.peak_log_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) tree_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 3. 底部:操作控制 self.bottom_frame = ttk.LabelFrame(main_container, text="操作控制") self.bottom_frame.grid(row=2, column=0, columnspan=2, sticky=tk.EW, padx=5, pady=5) ttk.Label(self.bottom_frame, text="采样间隔(ms):").grid(row=0, column=0, padx=5, pady=10, sticky=tk.W) self.interval_spin = ttk.Spinbox(self.bottom_frame, from_=50, to=5000, increment=50, value=500) self.interval_spin.grid(row=0, column=1, padx=5, pady=10, sticky=tk.W) ttk.Button(self.bottom_frame, text="应用间隔", command=self.set_sample_interval).grid(row=0, column=2, padx=5, pady=10) # 新增峰值记录复选框 self.record_peaks_check = ttk.Checkbutton(self.bottom_frame, text="记录峰值/谷值", variable=self.record_peaks_var) self.record_peaks_check.grid(row=0, column=3, padx=10, pady=10) ttk.Button(self.bottom_frame, text="零点标定", command=self.calibrate_zero).grid(row=0, column=4, padx=10, pady=10) ttk.Button(self.bottom_frame, text="砝码标定", command=self.calibrate_weight).grid(row=0, column=5, padx=10, pady=10) ttk.Button(self.bottom_frame, text="清零最值", command=self.reset_extremes).grid(row=0, column=6, padx=10, pady=10) ttk.Button(self.bottom_frame, text="导出峰值日志", command=self.export_peak_valley_log_to_excel).grid(row=0, column=7, padx=10, pady=10) # 4. 日志区(放在底部,不占用主空间) self.log_frame = ttk.LabelFrame(main_container, text="运行日志") self.log_frame.grid(row=3, column=0, columnspan=2, sticky=tk.EW, padx=5, pady=5) self.log_text = scrolledtext.ScrolledText(self.log_frame, height=6, state=tk.DISABLED) self.log_text.pack(fill=tk.X, padx=5, pady=5) # ========== 称重功能(核心逻辑修改)========== def add_log(self, content): self.log_text.config(state=tk.NORMAL) current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.log_text.insert(tk.END, f"[{current_time}] {content}\n") self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) def refresh_com_ports(self): ports = [p.device for p in serial.tools.list_ports.comports()] self.port_combo["values"] = ports if ports: self.port_combo.current(0) else: self.add_log("未检测到可用称重设备COM口") def toggle_connection(self): if not self.is_connected: port = self.port_combo.get() baud = int(self.baud_combo.get()) if not port: messagebox.showerror("错误", "请选择COM口") return try: self.ser = serial.Serial(port, baud, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=1) self.is_connected = True self.connect_btn.config(text="断开设备") self.recording = True self.reset_extremes() threading.Thread(target=self.read_weight_data, daemon=True).start() self.add_log(f"称重设备已连接:{port} @ {baud}") except Exception as e: messagebox.showerror("连接失败", str(e)) self.add_log(f"称重连接失败:{e}") else: if self.ser and self.ser.is_open: self.ser.close() self.is_connected = False self.recording = False self.connect_btn.config(text="连接设备") self.add_log("称重设备已断开") def read_weight_data(self): while self.recording and self.ser and self.ser.is_open: try: read_cmd = bytes.fromhex("010300000002C40B") self.ser.write(read_cmd) time.sleep(0.1) # 发送命令后等待响应 response = self.ser.read(9) if len(response) == 9 and response[0] == 0x01 and response[1] == 0x03: raw_data = response[3:7] high_word = (raw_data[2] << 8) | raw_data[3] low_word = (raw_data[0] << 8) | raw_data[1] raw_value = (high_word << 16) | low_word if raw_value > 0x7FFFFFFF: raw_value -= 0x100000000 actual_weight_g = raw_value current_time = datetime.now().strftime("%H:%M:%S.%f")[:-3] with self.lock: # 将当前点加入检测缓冲区 self.peak_detection_buffer.append((current_time, actual_weight_g)) # 如果缓冲区满了,进行一次峰值检测 if len(self.peak_detection_buffer) >= self.peak_detection_window: self.detect_peaks_in_buffer() # 添加到原始数据队列 self.data_queue.put((current_time, actual_weight_g)) self.last_weight = actual_weight_g else: self.add_log(f"称重响应异常:{response.hex()}") except Exception as e: self.add_log(f"称重读取错误:{e}") # 固定采样间隔 time.sleep(self.sample_interval / 1000) def detect_peaks_in_buffer(self): """在缓冲区中检测峰值和谷值""" # 检查峰值记录开关 if not self.record_peaks_var.get(): # 如果开关关闭,清空缓冲区并返回,不进行任何峰值检测 self.peak_detection_buffer = [] return if len(self.peak_detection_buffer) < 3: return # 获取当前缓冲区的值 times = [t for t, w in self.peak_detection_buffer] weights = [w for t, w in self.peak_detection_buffer] # 寻找峰值和谷值 # 峰值:中间点大于两边 for i in range(1, len(weights) - 1): if weights[i] > weights[i-1] and weights[i] > weights[i+1]: # 检查是否比当前已知的最近峰值还大 if self.last_peak_weight is None or weights[i] > self.last_peak_weight: time_str = times[i] weight_g = weights[i] # 检查是否与上一个记录的峰值时间不同,避免重复 if self.last_peak_time != time_str: with self.lock: self.peak_valley_log.append((time_str, weight_g, 'peak')) self.new_peak_valley_events.append((time_str, weight_g, 'peak')) self.last_peak_time = time_str self.last_peak_weight = weight_g self.add_log(f"检测到波峰: {weight_g:.2f}g @ {time_str}") # 找到一个峰值就退出本次检测 break # 谷值:中间点小于两边 for i in range(1, len(weights) - 1): if weights[i] < weights[i-1] and weights[i] < weights[i+1]: # 检查是否比当前已知的最近谷值还小 if self.last_valley_weight is None or weights[i] < self.last_valley_weight: time_str = times[i] weight_g = weights[i] # 检查是否与上一个记录的谷值时间不同,避免重复 if self.last_valley_time != time_str: with self.lock: self.peak_valley_log.append((time_str, weight_g, 'valley')) self.new_peak_valley_events.append((time_str, weight_g, 'valley')) self.last_valley_time = time_str self.last_valley_weight = weight_g self.add_log(f"检测到波谷: {weight_g:.2f}g @ {time_str}") # 找到一个谷值就退出本次检测 break # 保留缓冲区最后的几个点,用于下一次检测的连续性 self.peak_detection_buffer = self.peak_detection_buffer[-2:] def process_data_queue(self): try: new_data = [] while True: try: data = self.data_queue.get_nowait() new_data.append(data) except queue.Empty: break if new_data: with self.lock: for time_str, weight_g in new_data: # 添加到原始数据,用于绘图 self.raw_data.append((time_str, weight_g)) if len(self.raw_data) > self.max_data_points: self.raw_data.pop(0) # 更新统计信息(使用原始数据) if self.max_weight is None or weight_g > self.max_weight: self.max_weight = weight_g self.max_time = time_str if self.min_weight is None or weight_g < self.min_weight: self.min_weight = weight_g self.min_time = time_str self.update_weight_display() except Exception as e: self.add_log(f"处理数据队列时出错:{e}") self.root.after(100, self.process_data_queue) def update_peak_log_table_periodically(self): """定期更新峰值日志表格,避免频繁更新导致卡顿""" # 检查峰值记录开关 if not self.record_peaks_var.get(): # 如果开关关闭,清空待处理列表并返回 with self.lock: self.new_peak_valley_events.clear() return with self.lock: events_to_process = self.new_peak_valley_events[:] self.new_peak_valley_events.clear() # 清空临时列表 for time_str, weight_g, pv_type in events_to_process: # 确保单位转换 display_weight = weight_g if self.unit == "g" else weight_g * 0.0098 unit_str = "g" if self.unit == "g" else "N" self.peak_log_tree.insert("", "end", values=(time_str, f"{display_weight:.4f}", pv_type)) # 滚动到最后 self.peak_log_tree.see(self.peak_log_tree.get_children()[-1]) # 优化:如果事件队列很长,可以增加更新频率,反之降低 if len(events_to_process) > 10: self.root.after(50, self.update_peak_log_table_periodically) # 高频更新 else: self.root.after(200, self.update_peak_log_table_periodically) # 低频更新 def update_weight_display(self): if self.raw_data: last_time, last_weight = self.raw_data[-1] if self.unit == "g": self.weight_label.config(text=f"{last_weight:.2f} g") self.max_label.config(text=f"最大值: {self.max_weight:.2f} g") self.min_label.config(text=f"最小值: {self.min_weight:.2f} g") else: display_N = last_weight * 0.0098 self.weight_label.config(text=f"{display_N:.4f} N") self.max_label.config(text=f"最大值: {self.max_weight*0.0098:.4f} N") self.min_label.config(text=f"最小值: {self.min_weight*0.0098:.4f} N") else: if self.unit == "g": self.weight_label.config(text="0.00 g") self.max_label.config(text="最大值: -- g") self.min_label.config(text="最小值: -- g") else: self.weight_label.config(text="0.00 N") self.max_label.config(text="最大值: -- N") self.min_label.config(text="最小值: -- N") def update_plot_periodic(self): with self.lock: self.ax.set_ylabel(f"重量 ({self.unit})") self.ax.set_title(f"实时重力变化曲线 ({self.unit})", fontsize=14, fontweight='bold') # 绘制原始数据 if self.raw_data: plot_raw_weight = [w if self.unit == "g" else w * 0.0098 for _, w in self.raw_data] time_labels = [t for t, _ in self.raw_data] if len(plot_raw_weight) > 100: plot_raw_weight = plot_raw_weight[-100:] time_labels = time_labels[-100:] x_data = range(len(plot_raw_weight)) # 修复 xlim 警告 if len(plot_raw_weight) == 1: self.ax.set_xlim(-0.5, 0.5) else: self.ax.set_xlim(0, max(len(plot_raw_weight)-1, 0)) self.line.set_data(x_data, plot_raw_weight) if plot_raw_weight: y_min, y_max = min(plot_raw_weight), max(plot_raw_weight) y_margin = (y_max - y_min) * 0.1 if len(plot_raw_weight) > 1 else 0.5 self.ax.set_ylim(y_min - y_margin, y_max + y_margin) step = max(1, len(time_labels) // 10) x_ticks = range(0, len(time_labels), step) x_labels = [time_labels[i] for i in x_ticks] self.ax.set_xticks(x_ticks) self.ax.set_xticklabels(x_labels, rotation=45, fontsize=8) else: self.ax.set_ylim(0, 1) self.ax.set_xlim(0, 1) else: self.line.set_data([], []) self.ax.set_xlim(0, 1) self.ax.set_ylim(0, 1) # 绘制峰值和谷值 peak_times = [] peak_weights = [] valley_times = [] valley_weights = [] if self.peak_valley_log and self.raw_data: # 创建一个时间到索引的映射,提高查找效率 time_to_idx_map = {t: i for i, (t, w) in enumerate(self.raw_data)} for pt, pw, ppv in self.peak_valley_log: idx = time_to_idx_map.get(pt, -1) if idx != -1: if ppv == 'peak': peak_times.append(idx) peak_weights.append(pw if self.unit == "g" else pw * 0.0098) elif ppv == 'valley': valley_times.append(idx) valley_weights.append(pw if self.unit == "g" else pw * 0.0098) self.peak_line.set_data(peak_times, peak_weights) self.valley_line.set_data(valley_times, valley_weights) self.fig.tight_layout() self.canvas.draw() self.root.after(200, self.update_plot_periodic) def toggle_unit(self): with self.lock: if self.unit == "g": self.unit = "N" self.unit_btn.config(text="切换到g") # 更新表格单位 for item in self.peak_log_tree.get_children(): values = self.peak_log_tree.item(item, "values") if values[2] in ['peak', 'valley']: new_weight = float(values[1]) * 0.0098 self.peak_log_tree.item(item, values=(values[0], f"{new_weight:.4f}", values[2])) else: self.unit = "g" self.unit_btn.config(text="切换到N") # 更新表格单位 for item in self.peak_log_tree.get_children(): values = self.peak_log_tree.item(item, "values") if values[2] in ['peak', 'valley']: new_weight = float(values[1]) / 0.0098 self.peak_log_tree.item(item, values=(values[0], f"{new_weight:.2f}", values[2])) self.update_weight_display() self.add_log(f"单位已切换为:{self.unit}") def set_sample_interval(self): try: interval = int(self.interval_spin.get()) if 50 <= interval <= 5000: self.sample_interval = interval self.add_log(f"采样间隔已设置为:{interval}ms") else: messagebox.showerror("错误", "采样间隔需在50-5000ms之间") except ValueError: messagebox.showerror("错误", "请输入有效数字") def calibrate_zero(self): if not self.is_connected: messagebox.showerror("错误", "未连接称重设备") return try: zero_cmd = bytes.fromhex("010600120001E80F") self.ser.write(zero_cmd) time.sleep(0.5) response = self.ser.read(8) if len(response) == 8 and response[0] == 0x01 and response[1] == 0x06: self.add_log("零点标定成功") messagebox.showinfo("成功", "零点标定已完成") else: messagebox.showerror("失败", "零点标定响应异常") except Exception as e: messagebox.showerror("失败", f"零点标定错误:{e}") def calibrate_weight(self): if not self.is_connected: messagebox.showerror("错误", "未连接称重设备") return weight_dialog = tk.Toplevel(self.root) weight_dialog.title("砝码标定") weight_dialog.geometry("300x150") weight_dialog.transient(self.root) ttk.Label(weight_dialog, text="请输入砝码重量(g):").pack(pady=10) weight_entry = ttk.Entry(weight_dialog) weight_entry.pack(pady=5) weight_entry.focus() def confirm_calib(): try: weight_g = float(weight_entry.get()) if weight_g <= 0: raise ValueError("重量需大于0") cal_value = int(weight_g) low_word = cal_value & 0xFFFF high_word = (cal_value >> 16) & 0xFFFF cal_cmd = bytes([0x01, 0x10, 0x00, 0x08, 0x00, 0x02, 0x04, low_word >> 8, low_word & 0xFF, high_word >> 8, high_word & 0xFF]) crc = self.calculate_crc(cal_cmd[:-2]) cal_cmd += crc.to_bytes(2, byteorder="little") self.ser.write(cal_cmd) time.sleep(0.5) response = self.ser.read(8) if len(response) == 8 and response[0] == 0x01 and response[1] == 0x10: trigger_cmd = bytes.fromhex("010600120002E80A") self.ser.write(trigger_cmd) time.sleep(0.5) self.add_log(f"砝码标定成功(砝码重量:{weight_g}g)") messagebox.showinfo("成功", f"砝码标定已完成(砝码:{weight_g}g)") else: messagebox.showerror("失败", "砝码值写入异常") weight_dialog.destroy() except Exception as e: messagebox.showerror("错误", str(e)) weight_dialog.destroy() ttk.Button(weight_dialog, text="确认", command=confirm_calib).pack(pady=10) def calculate_crc(self, data): crc = 0xFFFF for byte in data: crc ^= byte for _ in range(8): if crc & 0x0001: crc >>= 1 crc ^= 0xA001 else: crc >>= 1 return crc def reset_extremes(self): # 在主线程中调用,需要获取锁 with self.lock: self.max_weight = None self.min_weight = None self.max_time = None self.min_time = None # 保留原始数据的清空逻辑,但确保在加锁时进行 self.raw_data = [] self.peak_valley_log = [] self.new_peak_valley_events = [] # 清空表格 for item in self.peak_log_tree.get_children(): self.peak_log_tree.delete(item) # 重置峰值检测状态 self.peak_detection_buffer = [] # 重置辅助变量 self.last_peak_time = None self.last_valley_time = None self.last_peak_weight = None self.last_valley_weight = None # 重置最后权重 self.last_weight = None # 在锁释放后更新显示 self.update_weight_display() self.add_log("最值记录、数据缓冲区、峰值检测状态已清零") def export_peak_valley_log_to_excel(self): with self.lock: if not self.peak_valley_log: messagebox.showerror("错误", "无峰值/谷值日志数据可导出") return save_path = filedialog.asksaveasfilename( defaultextension=".xlsx", filetypes=[("Excel文件", "*.xlsx")], title="选择Excel保存路径" ) if not save_path: return # 在新线程中执行耗时的Excel导出操作,避免阻塞GUI threading.Thread(target=self._export_peak_valley_log_to_excel_threaded, args=(save_path,), daemon=True).start() def _export_peak_valley_log_to_excel_threaded(self, save_path): try: wb = openpyxl.Workbook() ws = wb.active ws.title = "峰值谷值日志" headers = ["序号", "时间", "重量(g)", "重量(N)", "类型 (Peak/Valley)"] for col, header in enumerate(headers, 1): cell = ws.cell(row=1, column=col, value=header) cell.font = Font(bold=True) cell.alignment = Alignment(horizontal="center") for row, (time_str, weight_g, pv_type) in enumerate(self.peak_valley_log, 2): ws.cell(row=row, column=1, value=row-1) ws.cell(row=row, column=2, value=time_str) ws.cell(row=row, column=3, value=round(weight_g, 2)) ws.cell(row=row, column=4, value=round(weight_g * 0.0098, 4)) ws.cell(row=row, column=5, value=pv_type) # 统计信息 peaks = [w for _, w, t in self.peak_valley_log if t == 'peak'] valleys = [w for _, w, t in self.peak_valley_log if t == 'valley'] start_row = len(self.peak_valley_log) + 3 if peaks: ws.cell(row=start_row, column=1, value="峰值统计") ws.cell(row=start_row + 1, column=1, value="最大峰值") ws.cell(row=start_row + 1, column=3, value=round(max(peaks), 2)) if valleys: ws.cell(row=start_row + 2, column=1, value="谷值统计") ws.cell(row=start_row + 3, column=1, value="最小谷值") ws.cell(row=start_row + 3, column=3, value=round(min(valleys), 2)) # 图表 chart = LineChart() chart.title = "峰值谷值变化曲线" chart.x_axis.title = "序号" chart.y_axis.title = "重量 (g)" x_data = Reference(ws, min_col=1, min_row=2, max_row=len(self.peak_valley_log)+1) y_data = Reference(ws, min_col=3, min_row=1, max_row=len(self.peak_valley_log)+1) chart.add_data(y_data, titles_from_data=True) chart.set_categories(x_data) for series in chart.series: series.dataLabels = DataLabelList() series.dataLabels.showVal = True ws.add_chart(chart, "G2") wb.save(save_path) # 在主线程中更新日志和弹窗 self.root.after(0, lambda: self.add_log(f"峰值/谷值日志数据已成功导出至:{save_path}")) self.root.after(0, lambda: messagebox.showinfo("成功", f"峰值/谷值日志数据导出完成!\n路径:{save_path}")) except Exception as e: # 在主线程中处理错误 self.root.after(0, lambda: messagebox.showerror("失败", f"Excel导出错误:{e}")) self.root.after(0, lambda: self.add_log(f"Excel导出失败:{e}")) # ========== 机械臂功能(保持不变)========== def refresh_arm_ports(self): ports = [p.device for p in serial.tools.list_ports.comports()] self.arm_port_combo["values"] = ports if ports: self.arm_port_combo.current(0) def toggle_arm_connection(self): if not self.arm_is_connected: port = self.arm_port_combo.get() baud = int(self.arm_baud_combo.get()) if not port: messagebox.showerror("错误", "请选择机械臂COM口") return try: self.arm_ser = serial.Serial(port, baud, timeout=1) self.arm_is_connected = True self.arm_connect_btn.config(text="断开机械臂") self.send_gcode_btn.config(state=tk.NORMAL) self.x_left_btn.config(state=tk.NORMAL) self.x_right_btn.config(state=tk.NORMAL) self.add_log(f"机械臂已连接:{port} @ {baud}") except Exception as e: messagebox.showerror("连接失败", str(e)) self.add_log(f"机械臂连接失败:{e}") else: if self.arm_ser and self.arm_ser.is_open: self.arm_ser.close() self.arm_is_connected = False self.arm_connect_btn.config(text="连接机械臂") self.send_gcode_btn.config(state=tk.DISABLED) self.x_left_btn.config(state=tk.DISABLED) self.x_right_btn.config(state=tk.DISABLED) self.add_log("机械臂已断开") def move_arm(self, axis, direction): if not self.arm_is_connected: return step = self.move_step.get() cmd = f"G91\nG0 {axis}{direction * step:.3f}\n" try: self.arm_ser.write(cmd.encode()) self.add_log(f"手动移动: {cmd.strip()}") except Exception as e: self.add_log(f"移动失败: {e}") def send_gcode(self): if not self.arm_is_connected or self.running_gcode: return self.gcode_content = self.gcode_text.get("1.0", tk.END).strip() if not self.gcode_content: messagebox.showwarning("警告", "G-code 为空!") return self.running_gcode = True self.send_gcode_btn.config(state=tk.DISABLED) self.stop_gcode_btn.config(state=tk.NORMAL) self.current_loop = 0 threading.Thread(target=self._send_gcode_lines, args=([line.strip() for line in self.gcode_content.splitlines() if line.strip() and not line.startswith(';')],), daemon=True).start() def _send_gcode_lines(self, lines): for line in lines: if not self.running_gcode: break try: self.arm_ser.write((line + '\n').encode()) time.sleep(0.1) except Exception as e: self.add_log(f"G-code 发送错误: {e}") self.running_gcode = False break self.root.after(500, self.run_gcode_loop) def run_gcode_loop(self): if not self.running_gcode or self.current_loop >= self.loop_count.get(): self.finish_gcode_run() return self.current_loop += 1 self.add_log(f"▶ 开始第 {self.current_loop} 次循环...") threading.Thread(target=self._send_gcode_lines, args=([line.strip() for line in self.gcode_content.splitlines() if line.strip() and not line.startswith(';')],), daemon=True).start() def stop_gcode(self): self.running_gcode = False self.add_log("⏹ G-code 运行已停止") def finish_gcode_run(self): self.running_gcode = False self.send_gcode_btn.config(state=tk.NORMAL) self.stop_gcode_btn.config(state=tk.DISABLED) try: winsound.Beep(1000, 300) except: pass # 非Windows忽略 self.add_log("✅ G-code 循环运行完成!") def export_gcode_config(self): config = { "gcode": self.gcode_text.get("1.0", tk.END).strip(), "loop_count": self.loop_count.get(), "move_step": self.move_step.get() } path = filedialog.asksaveasfilename( defaultextension=".json", filetypes=[("JSON 文件", "*.json")], title="导出G-code配置" ) if path: with open(path, 'w', encoding='utf-8') as f: json.dump(config, f, ensure_ascii=False, indent=2) self.add_log(f"G-code 配置已导出:{path}") def import_gcode_config(self): path = filedialog.askopenfilename( filetypes=[("JSON 文件", "*.json")], title="导入G-code配置" ) if path: try: with open(path, 'r', encoding='utf-8') as f: config = json.load(f) self.gcode_text.delete("1.0", tk.END) self.gcode_text.insert("1.0", config.get("gcode", "")) self.loop_count.set(config.get("loop_count", 1)) self.move_step.set(config.get("move_step", 1.0)) self.add_log(f"G-code 配置已导入:{path}") except Exception as e: messagebox.showerror("导入失败", str(e)) self.add_log(f"配置导入失败:{e}") if __name__ == "__main__": root = tk.Tk() app = USBWeightMonitor(root) root.mainloop() 帮我分析这个代码为什么一采样就卡死
11-14
# #Variant you can use # user_param: 用户在弹窗输入的参数 #env: Odoo Environment on which the action is triggered #time, datetime, dateutil, pytz: useful Python libraries #logger: #UserError: Warning Exception to use with raise #'obj': record, #'wb': return of openpyxl.load_workbook(), #'ws': openpyxl.worksheet.worksheet.Worksheet, #'style': openpyxl.styles, #'add_img': add_img, #'copy': copy, # #Example: #ws['G9'] = obj.confirmation_date #ws["B1"].font=style.Font(name="宋体",size=17,color="00CCFF") #ws.cell(2,1).fill = style.PatternFill("solid",fgColor="00FF02") #ws.cell(row,col).alignment = style.Alignment(horizontal='center', vertical='justify', wrap_text=True) #ws.merge_cells('A1:D4') #ws.unmerge_cells('A1:D4') #row = 17 #idx = 1 #for line in obj.order_line: # ws.cell(row,1).value = idx # add_img(ws, (row,2), line.product_id.image_medium, (100, 100)) # ws.cell(row,3).value = line.product_id.with_context(lang=obj.partner_id.lang).name # ws.insert_rows(row,1) # # row=row+1 # idx=idx+1 from openpyxl.styles import Font, Alignment, PatternFill, Border, Side from bs4 import BeautifulSoup from datetime import datetime import calendar # 获取工作表对象 ws = wb.active # 或者传入具体的 ws # 设置字体和边框样式 header_font = Font(name='微软雅黑', size=11, bold=True) body_font = Font(name='微软雅黑', size=10) alignment_center = Alignment(horizontal='center', vertical='center', wrap_text=True) alignment_left = Alignment(horizontal='left', vertical='center', wrap_text=True) side = Side(style='thin', color='000000') border = Border(left=side, right=side, top=side, bottom=side) if obj and obj[0].partner_id: partner_name = obj[0].partner_id.name else: partner_name = '' if obj: dates = [r.date for r in obj if r.date] if dates: min_date = min(dates) # 获取当月第一天 first_day = min_date.replace(day=1) # 获取当月最后一天 last_day_num = calendar.monthrange(min_date.year, min_date.month)[1] last_day = min_date.replace(day=last_day_num) # 设置日期范围为当月第一天到最后一天 date_range = f'{first_day.month}.{first_day.day}-{last_day.month}.{last_day.day}' date_year = f'{min_date.strftime("%Y年%m月")}' else: date_range = '' else: date_range = '' # 1. 设置主标题 ws.merge_cells('B1:K1') ws['B1'] = f'新鸿贵{date_year}对账单 ({date_range})' ws['B1'].font = Font(name='微软雅黑', size=18, bold=True) ws['B1'].alignment = alignment_center ws.merge_cells('B2:K2') ws['B2'] = f'客户:{partner_name}' ws['B2'].font = Font(name='微软雅黑', size=12) ws['B2'].alignment = alignment_left # ws.merge_cells('B3:D3') # ws['B3'] = date_range # ws['B3'].font = Font(name='微软雅黑', size=11, bold=True) # ws['B3'].alignment = alignment_center # 5. 表头设置 headers = [ '序号', '日期', '单号', '产品规格', '车号', '发货数量(T)', '收货数量(T)', '单价', '金额', '备注' ] # 设置列宽 col_widths = [8, 12, 15, 15, 10, 12, 12, 10, 12, 15] for col_idx, width in enumerate(col_widths, 2): # B 列开始是第2列 ws.column_dimensions[chr(64 + col_idx)].width = width # 写入表头 for idx, header in enumerate(headers, 2): cell = ws.cell(row=3, column=idx, value=header) cell.font = header_font cell.alignment = alignment_center cell.border = border row_index = 4 for idx, record in enumerate(obj, 1): cell = ws.cell(row=row_index, column=2, value=idx) cell.font = body_font cell.alignment = alignment_center date_value = record.date.strftime('%Y-%m-%d') if record.date else '' cell = ws.cell(row=row_index, column=3, value=date_value) cell.font = body_font cell.alignment = alignment_center cell = ws.cell(row=row_index, column=4, value=record.name or '') cell.font = body_font cell.alignment = alignment_center # cell = ws.cell(row=row_index, column=5, value='') cell = ws.cell(row=row_index, column=5, value='') cell.font = body_font cell.alignment = alignment_center # cell = ws.cell(row=row_index, column=6, value=record.car_number or '') cell = ws.cell(row=row_index, column=6, value= '') cell.font = body_font cell.alignment = alignment_center # cell = ws.cell(row=row_index, column=7, value= '') cell = ws.cell(row=row_index, column=7, value= '') cell.font = body_font cell.alignment = alignment_center # cell = ws.cell(row=row_index, column=8, value=record.transaction_count or '') cell = ws.cell(row=row_index, column=8, value=record.transaction_count or '') cell.font = body_font cell.alignment = alignment_center # cell = ws.cell(row=row_index, column=9, value=record.price or '') cell = ws.cell(row=row_index, column=9, value= '') cell.font = body_font cell.alignment = alignment_center cell = ws.cell(row=row_index, column=10, value=record.amount_untaxed_in_currency_signed or '') cell.font = body_font cell.alignment = alignment_center # cell = ws.cell(row=row_index, column=11, value=record.notes or '') cell = ws.cell(row=row_index, column=11, value= '') cell.font = body_font cell.alignment = alignment_center # 添加边框 for col_idx in range(2, 12): ws.cell(row=row_index, column=col_idx).border = border row_index += 1 列表选择几条数据就导出几条,应该如何修改代码才可以实现
08-16
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!-- 导入样式 --> <link rel="stylesheet" href="//unpkg.com/element-plus/dist/index.css" /> <!-- 导入组件库 --> <script src="//unpkg.com/element-plus"></script> </head> <body> <div class="a_tree_box" id="app"> <el-table ref="cimsDictTable" default-expand-all :data="tableData" style="width: 100%" row-key="id" border stripe size="mini" class="data-table" tooltip-effect="dark" header-row-class-name="data-table-header" lazy :show-overflow-tooltip="true" :tree-props="{children:'childNode'}" @select="select22" @select-all="selectAll" > <el-table-column label="条件" min-width="400px"> <template slot-scope="scope"> <!-- 这部分是设置虚线逻辑 --> <template v-for="(item,l) in scope.row.level"> <!-- 根据delItemFlag判断 是true或者'true'则对应的竖虚线是多余的 需要删除 --> <span v-if="scope.row.delItemFlag[l] == 'false'" :key="l+1" class="l_bor1_box" :style="{'left':`${(l+1)*16 - 6}`+'px'}"></span> <!-- 这个是设置横的虚线 --> <span v-if="l == (scope.row.level -1)" :key="(l+1)*10000" class="l_bor2_box" :style="{'left':`${(l+1)*16 - 6}`+'px'}"></span> </template> <!-- 这个是设置每个层级最后的那个节点 多加一个竖线 覆盖多余的行高虚线 --> <span v-if="scope.row.moreOneDash" class="more_dash" :style="{'left':`${(scope.row.level)*16 - 6}`+'px'}"></span> <span class="showName">{{ scope.row.conditionName }}</span> <i v-if="scope.row.farOrSon=='first'|| scope.row.farOrSon=='middle'" style="font-size:18px;margin-left:5px;color:#00ff00;cursor: pointer;" class="el-icon-folder-add" @click="addOneRow(scope.row,scope.$index,'middle')"></i> <i v-if="scope.row.farOrSon=='first'|| scope.row.farOrSon=='middle'" style="font-size:18px;margin-left:5px;color:#00ff00;cursor: pointer;" class="el-icon-document-add" @click="addOneRow(scope.row,scope.$index,'last')"></i> <i v-if="scope.row.farOrSon=='middle'|| scope.row.farOrSon=='last'" style="font-size:18px;margin-left:5px;color:#f1ff;cursor: pointer;" class="el-icon-edit" @click="editRow(scope.row,scope.$index)"></i> <i v-if="scope.row.farOrSon=='middle'|| scope.row.farOrSon=='last'" style="font-size:18px;margin-left:5px;color:#1890FF;cursor: pointer;" type="primary" class="el-icon-close" @click="delRow(scope.row,scope.$index)"></i> </template> </el-table-column> <el-table-column label="交强险(%)"> <el-table-column label="上游" width="70px"> <template slot-scope="scope"> <el-input v-if="scope.row.strObj" v-model="scope.row.strObj.str1" placeholder @input="scope.row.strObj.str1=/^\d+\.?\d{0,2}$/.test(scope.row.strObj.str1)||scope.row.strObj.str1 == '' ? scope.row.strObj.str1 : scope.row.strObj.str1=''"></el-input> </template> </el-table-column> <el-table-column label="基数" width="70px"> <template slot-scope="scope"> <el-input v-if="scope.row.strObj" v-model="scope.row.strObj.str2" placeholder @input="scope.row.strObj.str2=/^\d+\.?\d{0,2}$/.test(scope.row.strObj.str2)||scope.row.strObj.str2 == '' ? scope.row.strObj.str2 : scope.row.strObj.str2=''"></el-input> </template> </el-table-column> </el-table-column> <el-table-column label="车船险(%)"> <el-table-column label="上游" width="70px"> <template slot-scope="scope"> <el-input v-if="scope.row.strObj" v-model="scope.row.strObj.str3" placeholder @input="scope.row.strObj.str3=/^\d+\.?\d{0,2}$/.test(scope.row.strObj.str3)||scope.row.strObj.str3 == '' ? scope.row.strObj.str3 : scope.row.strObj.str3=''"></el-input> </template> </el-table-column> <el-table-column label="基数" width="70px"> <template slot-scope="scope"> <el-input v-if="scope.row.strObj" v-model="scope.row.strObj.str4" placeholder @input="scope.row.strObj.str4=/^\d+\.?\d{0,2}$/.test(scope.row.strObj.str4)||scope.row.strObj.str4 == '' ? scope.row.strObj.str4 : scope.row.strObj.str4=''"></el-input> </template> </el-table-column> </el-table-column> <el-table-column label="商业险(%)"> <el-table-column label="上游" width="70px"> <template slot-scope="scope"> <el-input v-if="scope.row.strObj" v-model="scope.row.strObj.str5" placeholder @input="scope.row.strObj.str5=/^\d+\.?\d{0,2}$/.test(scope.row.strObj.str5)||scope.row.strObj.str5 == '' ? scope.row.strObj.str5 : scope.row.strObj.str5=''"></el-input> </template> </el-table-column> <el-table-column label="基数" width="70px"> <template slot-scope="scope"> <el-input v-if="scope.row.strObj" v-model="scope.row.strObj.str6" placeholder @input="scope.row.strObj.str6=/^\d+\.?\d{0,2}$/.test(scope.row.strObj.str6)||scope.row.strObj.str6 == '' ? scope.row.strObj.str6 : scope.row.strObj.str6=''"></el-input> </template> </el-table-column> </el-table-column> <el-table-column label="操作" width="200px"> <template slot-scope="scope"> <span v-if="scope.row.farOrSon=='first'|| scope.row.farOrSon=='middle'" style="color:#00ff00;cursor: pointer;" @click="addOneRow(scope.row,scope.$index,'middle')">新增夹</span> <span v-if="scope.row.farOrSon=='first'|| scope.row.farOrSon=='middle'" style="color:#00ff00;cursor: pointer;" @click="addOneRow(scope.row,scope.$index,'last')">新增页</span> <span v-if="scope.row.farOrSon=='middle'|| scope.row.farOrSon=='last'" style="color:#f1ff;cursor: pointer;" @click="editRow(scope.row,scope.$index)">编辑</span> <span v-if="scope.row.farOrSon=='middle'|| scope.row.farOrSon=='last'" style="color:#2593fc;cursor: pointer;" @click="delRow(scope.row,scope.$index)">删除</span> </template> </el-table-column> </el-table> <!-- 编辑回显的弹框 其实新增也可以用这个弹框但是没写 --> <el-dialog title="提示" class="dia_box" :visible.sync="dialogFlag" width="500px"> <el-form :model="formData"> <el-form-item label="条件名称:" label-width="128px"> <el-input v-model="formData.conditionName" autocomplete="off"></el-input> </el-form-item> <el-form-item v-if="formData.strObj" label="交强险(%)上游:" label-width="128px"> <el-input v-model="formData.strObj.str1" autocomplete="off"></el-input> </el-form-item> <el-form-item v-if="formData.strObj" label="交强险(%)基数:" label-width="128px"> <el-input v-model="formData.strObj.str2" autocomplete="off"></el-input> </el-form-item> <el-form-item v-if="formData.strObj" label="车船险(%)上游:" label-width="128px"> <el-input v-model="formData.strObj.str3" autocomplete="off"></el-input> </el-form-item> <el-form-item v-if="formData.strObj" label="车船险(%)基数:" label-width="128px"> <el-input v-model="formData.strObj.str4" autocomplete="off"></el-input> </el-form-item> <el-form-item v-if="formData.strObj" label="商业险(%)上游:" label-width="128px"> <el-input v-model="formData.strObj.str5" autocomplete="off"></el-input> </el-form-item> <el-form-item v-if="formData.strObj" label="商业险(%)基数:" label-width="128px"> <el-input v-model="formData.strObj.str6" autocomplete="off"></el-input> </el-form-item> </el-form> <span slot="footer" class="dialog-footer"> <el-button @click="dialogFlag = false">取 消</el-button> <el-button type="primary" @click="changeRow">确 定</el-button> </span> </el-dialog> <el-radio v-model="addOrEdit" :label="false">无数据列表</el-radio> <el-radio v-model="addOrEdit" :label="true">有数据列表</el-radio> <el-button @click="lookData">点击打印树表数据</el-button> </div> <script> // 创建Vue实例并挂载到id为app的根元素上 Vue.use(ElementPlus); var app = new Vue({ el: '#app', data: { addOrEdit:true, // false无数据列表 true是有数据列表 tableData:[], selectAddLast:null, cimsDictTable:[], sourceData:[{ id: 3, // 注意数据必须要有id 否则树状数据渲染会出问题 那么我们可以再前端新增时候以时间戳为id 真正保存到数据库后会有真实id conditionName: '顶级条件', strObj: null, parentId: null, childNode: [] // childNode有值 就会形成对应的嵌套下级 }], dialogFlag:false, formData:{ id: null, conditionName: '', strObj: { str1: '', str2: '', str3: '', str4: '', str5: '', str6: '' }}, }, watch: { addOrEdit (val) { if (!this.addOrEdit) { // 打开一个全新的列表 this.getNewData() } else { // 根据id找到某个数据列表 回显 假设id是123 this.getIdDate(123) } } }, methods:{ getNewData () { this.$set(this.sourceData[0], 'childNode', []) this.initData() }, // 获取页面数据回显 getIdDate (id) { // 总数据回显 --- 假设给的数据 这条数据是默认数据的childNode let dataArr = [ { id: 31, conditionName: '条件--【页】--页不可添加下级 是末梢last', strObj: { str1: '11', str2: '22', str3: '1', str4: 2, str5: '2', str6: '2' }, parentId: 3, childNode: [] }, { id: 32, conditionName: '条件--【夹】--夹可以添加下级 是中间middle', strObj: null, parentId: 3, childNode: [ { id: 311, conditionName: '机密的任务', strObj: null, parentId: 32, childNode: [ { id: 3221, conditionName: '这个条件名字比较长 会自动换行会自动换行会自动换行会自动换行会自动换行会自动换行会自动换行会自动换行', strObj: { str1: '11', str2: '121', str3: '1', str4: 2, str5: '2', str6: '2' }, parentId: 311, childNode: [] }, { id: 3222, conditionName: '9527', strObj: { str1: '11', str2: '1', str3: '1', str4: 2, str5: '2', str6: '2' }, parentId: 311, childNode: [] }, { id: 3223, conditionName: '夹文件', strObj: null, parentId: 311, childNode: [] } ] }, { id: 321, conditionName: '附加条件-日后再说', strObj: { str1: '11', str2: '33', str3: '1', str4: 2, str5: '2', str6: '2' }, parentId: 3, childNode: [] } ] }, { id: 33, conditionName: '条件夹', strObj: null, parentId: 3, childNode: [] } ] // // 设置id---没有id树渲染会报错-- // let num = 121210 // function setId (arr, pId) { // for (let j = 0; j < arr.length; j++) { // const element = arr[j] // if (element.childNode.length == 0) { // element['id'] = num // element['parentId'] = pId // num++ // } else { // element['id'] = num // element['parentId'] = pId // num++ // setId(element.childNode, element['id']) // } // } // } // setId(dataArr, 983873292643) // console.log(dataArr) // 获取数据嵌套的层级 function setLevel (arr, levelNum) { for (let j = 0; j < arr.length; j++) { const element = arr[j] if (element.childNode.length == 0) { element['level'] = levelNum } else { element['level'] = levelNum setLevel(element.childNode, levelNum + 1) } } } setLevel(dataArr, 1) this.$set(this.sourceData[0], 'childNode', dataArr) console.log(this.sourceData) this.initData() }, // 在树表每次数据变化时候 新增 删除 编辑 都需要设置出新的状态 判断是中间层 而没有子集 checkEveryItem () { // 这一步是找到空的中间层 需要添加页 let noChildAndNoRate = [] function checkIsNeedChild (arr, ruleName) { for (let j = 0; j < arr.length; j++) { const element = arr[j] if (element.childNode.length == 0) { if (element.strObj == null || element.strObj == 'null') { element['isNeedLast'] = true noChildAndNoRate.push({ cantName: ruleName.trim() + '/' + element.conditionName.trim() }) } else { element['isNeedLast'] = false } } else { element['isNeedLast'] = false checkIsNeedChild(element.childNode, ruleName.trim() + '/' + element.conditionName.trim()) } } } checkIsNeedChild(this.tableData, '') console.log('中间层的子集为空也就是没末梢', noChildAndNoRate) // 这个是找到每层级最后一个 判断是中间层 且 还有childNode 那么他的childNode就需要删除多余虚线(子集childNode多余的虚线就是父级的的那个嵌套层级 那条虚线) // 第一步先找到 父级(满足最后一层是中间层 且有子集childNode) function set_setLast (arr) { for (let j = 0; j < arr.length; j++) { const element = arr[j] if (element.childNode.length == 0) { if (j == (arr.length - 1)) { // element['isLastZhongjianAndHasChl'] = true if ((element.strObj == null || element.strObj == 'null') && element.childNode.length > 0) { element['isLastZhongjianAndHasChl'] = true } else { element['isLastZhongjianAndHasChl'] = false } } else { element['isLastZhongjianAndHasChl'] = false } } else { if (j == (arr.length - 1)) { // element['isLastZhongjianAndHasChl'] = true if ((element.strObj == null || element.strObj == 'null') && element.childNode.length > 0) { element['isLastZhongjianAndHasChl'] = true } else { element['isLastZhongjianAndHasChl'] = false } } else { element['isLastZhongjianAndHasChl'] = false } set_setLast(element.childNode) } } } // console.log(this.tableData[0].childNode) set_setLast(this.tableData[0].childNode) this.tableData[0]['isLastZhongjianAndHasChl'] = true // 第二步 根据isLastZhongjianAndHasChl和所需要删除的层次level 递归遍历 将需要删除虚线的标志为true fasle是不删 将几条虚线删不删的值放在一个数组内 function delArrItem (arr, flagDel, num, farArr) { for (let j = 0; j < arr.length; j++) { const element = arr[j] let delItemFlag = JSON.parse(JSON.stringify(farArr)) // 必须深拷贝 将父级的数组继承到子集使用 深拷贝 才不会改变父级数组值 for (let h = 0; h < element.level; h++) { if (flagDel == 'true' || flagDel == true) { if ((h + 1) == num) { delItemFlag[h] = 'true' } else { if (delItemFlag[h] == 'true' || delItemFlag[h] == true) { // 针对需要删除的 就不要再赋值 } else { delItemFlag[h] = 'false' } } } else { if (delItemFlag[h] == 'true' || delItemFlag[h] == true) { } else { delItemFlag[h] = 'false' } } } element['delItemFlag'] = delItemFlag if (element.childNode.length == 0) { } else { delArrItem(element.childNode, element.isLastZhongjianAndHasChl, element.level, element['delItemFlag']) } } } delArrItem(this.tableData[0].childNode, false, 0, []) // this.tableData[0]['isLastZhongjianAndHasChl'] = true // 这个是找到每层级最后一个 然后标志出来true 最后画虚线时候需要 单独多画一个线用来遮挡多余的当前层虚线(因为行高自适应 当某层的末梢行高超过50px时候 之前设置的top: -24px就不够了 会多出来一部分虚线 故需要遮挡掉) function set_lastMoreDash (arr) { for (let j = 0; j < arr.length; j++) { const element = arr[j] if (element.childNode.length == 0) { if (j == (arr.length - 1)) { element['moreOneDash'] = true } else { element['moreOneDash'] = false } } else { if (j == (arr.length - 1)) { element['moreOneDash'] = true } else { element['moreOneDash'] = false } set_lastMoreDash(element.childNode) } } } set_lastMoreDash(this.tableData[0].childNode) console.log('处理虚线', this.tableData) }, lookData () { console.log('tableData--', this.tableData) }, Menuclose () { this.dialogVisibleMenu = false }, setRowData (row, parentId, farOrSon, val) { let strObj = null if (farOrSon == 'last') { strObj = { str1: '', str2: '', str3: '', str4: '', str5: '', str6: '' } } else { strObj = null } return { id: new Date().valueOf(), conditionName: val.newName, parentId: parentId || null, strObj: strObj, edit: true, add: true, childNode: [], level: (row.level >= 0) ? (row.level + 1) : 1, farOrSon: farOrSon } }, // 手动勾选数据行 select22 (selection, row) { // 判断当前行是否选中 // 不需要判断 id, 因为引用地址相同 const selected = selection.some((item) => item === row) // 处理所有子级 this.selectChildren(row, selected) }, selectAll (selection) { /* * 这里引用别人的代码: * selectAll 只有两种状态: 全选和全不选 * 所以我们只需要判断一种状态即可 * 而且也不需要判断 id, 因为 selection 和 this.data 中对象引用地址是相同的 */ // tableData 第一层只要有在 selection 里面就是全选 const isSelect = this.tableData.some((item) => selection.includes(item)) if (isSelect) { selection.forEach((item) => { this.selectChildren(item, isSelect) }) } else { this.tableData.forEach((item) => { this.selectChildren(item, isSelect) }) } }, selectChildren (row, selected) { if (row['childNode'] && Array.isArray(row['childNode'])) { row['childNode'].forEach((item) => { this.toggleSelection(item, selected) this.selectChildren(item, selected) }) } }, selectionChange (selection) { this.debounce(this.tableSelectChange, 100, selection) }, toggleSelection (row, select) { row && this.$nextTick(() => { this.$refs[this.ref] && this.$refs[this.ref].toggleRowSelection(row, select) }) }, // 防抖 debounce (fun, wait, params) { clearTimeout(this.timeout) this.timeout = setTimeout(fun, wait, params) }, getSelectedList () { // 获取选中数据源 let list = JSON.parse(JSON.stringify(this.cimsDictTable)) list.forEach((e) => (e.childNode = null)) return list }, addRow (row, id, farOrSon, val) { console.log(row, id) // 去掉这个if和else是 为了达到可以不保存 就继续添加下级 // if (!row.add) { // 新增行数据 let addrow = this.setRowData(row, row.id, farOrSon, val) // 新增 if (row.childNode) { row.childNode.push(addrow) } else { // 添加数据 this.$set(row, 'childNode', [addrow]) } // 展开行 this.$nextTick(() => { // 更新后打开节点 this.$refs.cimsDictTable.toggleRowExpansion(row, true) // 刷新树 this.refTable() }) // } else { // this.$message({ // message: '请保存后再继续添加子节点!', // type: 'warning' // }) // } // 每次添加编辑删除树表需要 重新计算出新属性状态 this.checkEveryItem() }, addOneRow (row, index, farOrSon) { this.selectAddLast = farOrSon let item = {} let val = { newName: `新的条件${Math.floor(Math.random() * (1 - 1000) + 1000)}` }// 假设这个就是新增是一行的值 console.log(1123, val) function findRow (arrA, Id, val) { for (let h = 0; h < arrA.length; h++) { const element = arrA[h] if (element.childNode.length == 0) { if (element.id == Id) { item = element } } else { if (element.id == Id) { item = element } findRow(element.childNode, Id, val) } } } findRow(this.tableData, row.id, val) this.addRow(item, row.id, this.selectAddLast, val) }, updateTableTree (parentId, nodes) { // 更新需要先更新上级节点 this.$set( this.$refs.cimsDictTable.store.states.lazyTreeNodeMap, parentId, nodes ) }, refTable () { let _this = this function dg (data) { for (let i in data) { if (data[i].childNode) { _this.updateTableTree(data[i].id, data[i].childNode) dg(data[i].childNode) } } } dg(this.tableData) }, // 删除当前条及对应下级数据 deleteTable (row, index, arr) { for (let i = 0; i < arr.length; i++) { const element = arr[i] if (element.id == row.id) { arr.splice(i, 1) } else { if (element.childNode.length > 0) { this.deleteTable(row, index, element.childNode) } else { } } } }, delRow (row, index) { console.log(row, index) this.$confirm('确认删除该条件及下级条件?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.deleteTable(row, index, this.tableData) // 每次添加编辑删除树表需要 重新计算出新属性状态 this.checkEveryItem() }).catch(() => { this.$message({ type: 'info', message: '已取消删除' }) }) // 删除行 let delArr = [] function dg (data) { for (let i in data) { // 过滤当前新增的数据 if (!data[i].add) { delArr.push(data[i].id) } if (data[i].childNode) { dg(data[i].childNode) } } } dg([row]) // 删除 }, editRow (row, index) { // 编辑 this.$set(row, 'edit', true) // 使用深拷贝 否则会影响改弹框 就直接影响列表数据了 我们要的是改了弹框点击确认才改列表数据 故需要深拷贝 this.formData = JSON.parse(JSON.stringify(row)) this.dialogFlag = true }, // 点击了弹框的确定 changeRow () { let val = this.formData function findId (arrA, Id, val) { for (let h = 0; h < arrA.length; h++) { const element = arrA[h] if (element.childNode.length == 0) { if (element.id == Id) { element['conditionName'] = val.conditionName element['strObj'] = val.strObj console.log(12121, element, val) } } else { if (element.id == Id) { element['conditionName'] = val.conditionName element['strObj'] = val.strObj console.log(12121, element, val) } findId(element.childNode, Id, val) } } } findId(this.tableData, val.id, val) this.dialogFlag = false // 每次添加编辑删除树表需要 重新计算出新属性状态 this.checkEveryItem() }, proTableData (data) { let _this = this // 处理数据 function dg (data) { for (let i in data) { _this.$set(data[i], 'edit', false) if (data[i].childNode) { // 重要:树状节点数据刷新 _this.updateTableTree(data[i].id, data[i].childNode) dg(data[i].childNode) } } } dg(data) // 给数据判断添加父子节点标识 function setFarSon (data) { for (let index = 0; index < data.length; index++) { const element = data[index] // console.log(element.parentId, element.parentId == undefined) if (element.parentId == undefined || element.parentId == null) { element['farOrSon'] = 'first' setFarSon(element.childNode) } else { if (element.strObj == null || element.strObj == 'null') { element['farOrSon'] = 'middle' setFarSon(element.childNode) } else { element['farOrSon'] = 'last' } } } } setFarSon(data) }, initData () { // 数据加载 模仿数据请求 let res = JSON.parse(JSON.stringify(this.sourceData)) // 数据处理 添加编辑标识 this.proTableData(res) this.tableData = res // 每次添加编辑删除树表需要 重新计算出新属性状态 this.checkEveryItem() }, }, }) </script> <style> .a_tree_box { width: calc(100vw - 300px); height: 100%; margin-bottom: 20px; padding: 40px; .cd-tool { display: flex; flex-direction: row; } .data-table { .cell { display: flex; align-items: center; } td { height: 50px; padding: 0; } tbody { tr { overflow: hidden; td { .cell { padding: 0 !important; height: 100%; position: relative; overflow: visible !important; .l_bor1_box { top: -24px; display: inline-block; width: 1px; height: 100%; border-left: 1px dashed #ccc; position: absolute; } .l_bor2_box { display: inline-block; width: 30px; height: 1px; border-top: 1px dashed #ccc; position: absolute; } .more_dash { display: inline-block; width: 1px; height: 50%; border-top: 1px dashed #ccc; position: absolute; top: calc(50% - 1px); background-color: rgb(255, 253, 253); } .el-table__expand-icon { display: inline-block; width: 20px !important; min-width: 20px !important; position: relative; z-index: 999; // background-color: #1fff; } .el-table__placeholder { display: inline-block; width: 25px !important; max-width: 25px !important; min-width: 25px !important; } .l_bor3_box { display: inline-block; width: 1px; height: 15px; border-left: 1px dashed #ccc; top: 15px; left: -13px; position: relative; } } } td:nth-child(n + 2) { .cell { span { // background-color: #1fff; display: inline-block; width: 100%; text-align: center; } } } } } } .showName { display: inline-block; white-space: wrap; overflow: hidden; text-overflow: ellipsis; } .one_two { width: 20px; height: 20px; margin-right: 3px; } .isML10 { margin-left: 10px; } .abcd { width: 20px; height: 20px; cursor: pointer; margin-left: 3px; } .tip_icon { width: 20px; height: 20px; cursor: pointer; position: relative; top: -1px; left: 30px; } .tip_box { display: inline-block; width: fit-content !important; min-width: 105px; color: red; font-weight: 700; position: relative; top: 1px; margin-left: 33px; font-size: 17px; } .el-input__inner { margin-left: 3px !important; padding: 0 !important; width: 44px !important; height: 25px !important; text-align: center !important; } .dia_box { .el-input { text-align: left; .el-input__inner { width: 300px !important; height: 35px !important; text-align: left !important; } } } } </style> </body> </html>给我优化下,并保留当前功能
08-13
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值