Python tkinter写的《电脑装配单》和 Html版 可打印 可导出 excel 文件

 Python版 样图:

 说明书:

```markdown
# 电脑配置单使用说明书

## 一、软件简介

电脑配置单是一个用于创建和比较两套电脑配置方案的工具软件。用户可以选择各种电脑配件,输入数量和价格,软件会自动计算总金额,并支持导出和打印配置单。

## 二、主要功能

1. **双配置方案对比**
   - 支持同时编辑两套配置方案
   - 每个配置方案包含CPU、主板、内存等15种常用配件
   - 可以分别查看每套方案的总数量和总金额

2. **配件数据管理**
   - 内置配件型号数据库
   - 支持添加、修改和删除配件型号及价格
   - 配件数据分为配置1和配置2两种方案

3. **导出打印功能** 
   - 支持导出为Excel表格
   - 支持生成打印预览并打印
   - 导出文件包含完整的配置信息和合计数据

## 三、使用说明

### 1. 创建配置单

1. 启动软件后,界面分为左右两个配置区域
2. 在每个配件行:
   - 从下拉框选择配件型号
   - 输入数量
   - 价格会自动填充(也可手动修改)
   - 金额会自动计算
3. 底部会显示每个配置的总数量和总金额

### 2. 编辑配件数据

1. 点击"编辑配件数据"按钮
2. 在弹出窗口中:
   - 选择配件类型
   - 选择配置方案(1或2)
   - 输入型号和价格
   - 点击"添加/更新"保存
3. 可以选中列表中的项目进行删除

### 3. 导出配置单

1. 点击"导出配置"按钮
2. 选择保存位置和文件名
3. 系统会生成包含两套配置方案的Excel文件

### 4. 打印配置单

1. 点击"打印配置"按钮
2. 系统会在浏览器中打开打印预览页面
3. 点击打印按钮执行打印

## 四、注意事项

1. 数量和价格必须输入数字,否则可能导致计算错误
2. 编辑配件数据时请确保输入正确的价格格式
3. 导出前建议检查配置信息是否完整
4. 打印前请先在预览中确认格式是否正确

Python 代码:

# 作者:Hoye
# 日期:2024-12-21
# 功能:装机配置单
# 版本:1.0import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import json
from datetime import datetime
import os
import openpyxl
from openpyxl.styles import Alignment, Font, Border, Side
import tempfile
import webbrowserclass ComponentsData:def __init__(self):self.json_file = os.path.join(os.path.dirname(__file__), 'components_data.json')self.load_data()def load_data(self):"""从JSON文件加载配件数据"""try:with open(self.json_file, 'r', encoding='utf-8') as f:self.data = json.load(f)except FileNotFoundError:messagebox.showerror('错误', f'找不到配件数据文件: {self.json_file}')self.data = {}except json.JSONDecodeError:messagebox.showerror('错误', '配件数据文件格式错误')self.data = {}def save_data(self):"""保存配件数据到JSON文件"""try:with open(self.json_file, 'w', encoding='utf-8') as f:json.dump(self.data, f, ensure_ascii=False, indent=4)except Exception as e:messagebox.showerror('错误', f'保存配件数据失败: {str(e)}')def get_models(self, part_name, option_type):"""获取指定配件的型号列表"""return self.data.get(part_name, {}).get(option_type, [])def get_price(self, part_name, option_type, model):"""获取指定配件型号的价格"""models = self.get_models(part_name, option_type)for item in models:if item['model'] == model:return item['price']return 0def update_price(self, part_name, option_type, model, new_price):"""更新指定配件型号的价格"""if part_name in self.data and option_type in self.data[part_name]:for item in self.data[part_name][option_type]:if item['model'] == model:item['price'] = new_priceself.save_data()return Truereturn Falseclass ConfigRow:def __init__(self, parent1, parent2, part_name, row, components_data, on_amount_change=None):self.components_data = components_dataself.on_amount_change = on_amount_changeself.part_name = part_name# 创建配置1的行self.frame1 = ttk.Frame(parent1)self.frame1.pack(fill='x', padx=5, pady=2)# 配置1的组件ttk.Label(self.frame1, text=part_name, width=15).pack(side='left', padx=5)self.model1_var = tk.StringVar()self.model1 = ttk.Combobox(self.frame1, textvariable=self.model1_var, width=30)self.model1.pack(side='left', padx=5)self.qty1_var = tk.StringVar(value='1')self.qty1 = ttk.Entry(self.frame1, textvariable=self.qty1_var, width=8)self.qty1.pack(side='left', padx=5)self.price1_var = tk.StringVar(value='0')self.price1 = ttk.Entry(self.frame1, textvariable=self.price1_var, width=10)self.price1.pack(side='left', padx=5)self.amount1_var = tk.StringVar(value='0')ttk.Label(self.frame1, textvariable=self.amount1_var, width=12).pack(side='left', padx=5)# 创建配置2的行self.frame2 = ttk.Frame(parent2)self.frame2.pack(fill='x', padx=5, pady=2)# 配置2的组件ttk.Label(self.frame2, text=part_name, width=15).pack(side='left', padx=5)self.model2_var = tk.StringVar()self.model2 = ttk.Combobox(self.frame2, textvariable=self.model2_var, width=30)self.model2.pack(side='left', padx=5)self.qty2_var = tk.StringVar(value='1')self.qty2 = ttk.Entry(self.frame2, textvariable=self.qty2_var, width=8)self.qty2.pack(side='left', padx=5)self.price2_var = tk.StringVar(value='0')self.price2 = ttk.Entry(self.frame2, textvariable=self.price2_var, width=10)self.price2.pack(side='left', padx=5)self.amount2_var = tk.StringVar(value='0')ttk.Label(self.frame2, textvariable=self.amount2_var, width=12).pack(side='left', padx=5)# 设置下拉框的值if part_name in components_data.data:models1 = [item['model'] for item in components_data.get_models(part_name, 'option1')]models2 = [item['model'] for item in components_data.get_models(part_name, 'option2')]self.model1['values'] = models1self.model2['values'] = models2# 绑定事件self.model1.bind('<<ComboboxSelected>>', self.update_price1)self.model2.bind('<<ComboboxSelected>>', self.update_price2)self.qty1_var.trace_add('write', self.calculate_amount1)self.qty2_var.trace_add('write', self.calculate_amount2)self.price1_var.trace_add('write', self.calculate_amount1)self.price2_var.trace_add('write', self.calculate_amount2)def update_price1(self, event=None):if self.model1.get() != '其他':price = self.components_data.get_price(self.part_name, 'option1', self.model1.get())self.price1_var.set(str(price))self.calculate_amount1()def update_price2(self, event=None):if self.model2.get() != '其他':price = self.components_data.get_price(self.part_name, 'option2', self.model2.get())self.price2_var.set(str(price))self.calculate_amount2()def calculate_amount1(self, *args):try:qty = float(self.qty1_var.get() or 0)price = float(self.price1_var.get() or 0)self.amount1_var.set(f'{qty * price:.2f}')if self.on_amount_change:  # 调用回调函数self.on_amount_change()except ValueError:self.amount1_var.set('0.00')if self.on_amount_change:self.on_amount_change()def calculate_amount2(self, *args):try:qty = float(self.qty2_var.get() or 0)price = float(self.price2_var.get() or 0)self.amount2_var.set(f'{qty * price:.2f}')if self.on_amount_change:  # 调用回调函数self.on_amount_change()except ValueError:self.amount2_var.set('0.00')if self.on_amount_change:self.on_amount_change()class ComputerConfigApp(tk.Tk):def __init__(self):super().__init__()self.title('电脑配置单')self.geometry('1600x900')  # 加大窗口尺寸# 设置整体样式style = ttk.Style()style.configure('Header.TLabel', font=('Arial', 10, 'bold'))style.configure('Title.TLabel', font=('Arial', 12, 'bold'))style.configure('Config.TFrame', padding=5)style.configure('Total.TFrame', padding=10, relief='solid')# 加载配件数据self.components_data = ComponentsData()# 创建主容器main_container = ttk.Frame(self)main_container.pack(fill='both', expand=True, padx=10, pady=5)# 创建标题title_frame = ttk.Frame(main_container)title_frame.pack(fill='x', pady=10)ttk.Label(title_frame, text='电脑配置单', style='Title.TLabel').pack()# 创建日期选择date_frame = ttk.Frame(main_container)date_frame.pack(fill='x', pady=5)ttk.Label(date_frame, text='日期:').pack(side='left')self.date_var = tk.StringVar(value=datetime.now().strftime('%Y-%m-%d'))ttk.Entry(date_frame, textvariable=self.date_var, width=15).pack(side='left')# 创建配置区域容器configs_container = ttk.Frame(main_container)configs_container.pack(fill='both', expand=True)# 创建左右两个配置面板self.config1_frame = ttk.LabelFrame(configs_container, text='配置方案1', padding=10)self.config1_frame.pack(side='left', fill='both', expand=True, padx=5)self.config2_frame = ttk.LabelFrame(configs_container, text='配置方案2', padding=10)self.config2_frame.pack(side='left', fill='both', expand=True, padx=5)# 创建滚动区域self.create_scrollable_frame(self.config1_frame, self.config2_frame)# 创建合计区域self.create_total_area(main_container)# 创建按钮区域self.create_buttons(main_container)def create_scrollable_frame(self, frame1, frame2):"""创建可滚动的配置区域"""# 配置1的滚动区域canvas1 = tk.Canvas(frame1)scrollbar1 = ttk.Scrollbar(frame1, orient="vertical", command=canvas1.yview)self.scrollable_frame1 = ttk.Frame(canvas1)canvas1.configure(yscrollcommand=scrollbar1.set)# 配置2的滚动区域canvas2 = tk.Canvas(frame2)scrollbar2 = ttk.Scrollbar(frame2, orient="vertical", command=canvas2.yview)self.scrollable_frame2 = ttk.Frame(canvas2)canvas2.configure(yscrollcommand=scrollbar2.set)# 布局scrollbar1.pack(side="right", fill="y")canvas1.pack(side="left", fill="both", expand=True)canvas1.create_window((0, 0), window=self.scrollable_frame1, anchor="nw")scrollbar2.pack(side="right", fill="y")canvas2.pack(side="left", fill="both", expand=True)canvas2.create_window((0, 0), window=self.scrollable_frame2, anchor="nw")# 创建表头self.create_headers()# 创建配件行self.create_component_rows()# 绑定滚动事件self.scrollable_frame1.bind("<Configure>", lambda e: canvas1.configure(scrollregion=canvas1.bbox("all")))self.scrollable_frame2.bind("<Configure>", lambda e: canvas2.configure(scrollregion=canvas2.bbox("all")))def create_headers(self):"""创建表头"""# 配置1表头header1 = ttk.Frame(self.scrollable_frame1)header1.pack(fill='x', pady=5)ttk.Label(header1, text='配件名称', width=15, style='Header.TLabel').pack(side='left', padx=5)ttk.Label(header1, text='型号', width=30, style='Header.TLabel').pack(side='left', padx=5)ttk.Label(header1, text='数量', width=8, style='Header.TLabel').pack(side='left', padx=5)ttk.Label(header1, text='单价', width=10, style='Header.TLabel').pack(side='left', padx=5)ttk.Label(header1, text='金额', width=12, style='Header.TLabel').pack(side='left', padx=5)# 配置2表头header2 = ttk.Frame(self.scrollable_frame2)header2.pack(fill='x', pady=5)ttk.Label(header2, text='配件名称', width=15, style='Header.TLabel').pack(side='left', padx=5)ttk.Label(header2, text='型号', width=30, style='Header.TLabel').pack(side='left', padx=5)ttk.Label(header2, text='数量', width=8, style='Header.TLabel').pack(side='left', padx=5)ttk.Label(header2, text='单价', width=10, style='Header.TLabel').pack(side='left', padx=5)ttk.Label(header2, text='金额', width=12, style='Header.TLabel').pack(side='left', padx=5)def create_total_area(self, parent):"""创建合计区域"""total_frame = ttk.Frame(parent, style='Total.TFrame')total_frame.pack(fill='x', pady=10)# 配置1合计total1_frame = ttk.LabelFrame(total_frame, text='配置1合计')total1_frame.pack(side='left', padx=20, pady=5)self.total1_var = tk.StringVar(value='0.00')self.qty_total1_var = tk.StringVar(value='0')ttk.Label(total1_frame, text='数量:').pack(side='left')ttk.Label(total1_frame, textvariable=self.qty_total1_var).pack(side='left', padx=5)ttk.Label(total1_frame, text='合计:').pack(side='left', padx=10)ttk.Label(total1_frame, textvariable=self.total1_var).pack(side='left')# 配置2合计total2_frame = ttk.LabelFrame(total_frame, text='配置2合计')total2_frame.pack(side='left', padx=20, pady=5)self.total2_var = tk.StringVar(value='0.00')self.qty_total2_var = tk.StringVar(value='0')ttk.Label(total2_frame, text='数量:').pack(side='left')ttk.Label(total2_frame, textvariable=self.qty_total2_var).pack(side='left', padx=5)ttk.Label(total2_frame, text='合计:').pack(side='left', padx=10)ttk.Label(total2_frame, textvariable=self.total2_var).pack(side='left')def create_buttons(self, parent):"""创建按钮区域"""button_frame = ttk.Frame(parent)button_frame.pack(pady=10)style = ttk.Style()style.configure('Action.TButton', padding=5)ttk.Button(button_frame, text='导出配置', style='Action.TButton', command=self.export_config).pack(side='left', padx=10)ttk.Button(button_frame, text='打印配置', style='Action.TButton',command=self.print_config).pack(side='left', padx=10)ttk.Button(button_frame, text='编辑配件数据', style='Action.TButton',command=self.edit_components_data).pack(side='left', padx=10)def create_component_rows(self):self.rows = []  # 初始化行列表parts = ['CPU', '主板', '内存', '硬盘', 'SSD固态盘', '显卡', '机箱', '电源', '显示器', '键鼠套装', '键盘', '鼠标', '散热器', '音箱', '光存储']for i, part in enumerate(parts):row = ConfigRow(self.scrollable_frame1,  # 配置1的父容器self.scrollable_frame2,  # 配置2的父容器part, i, self.components_data,on_amount_change=self.calculate_totals)self.rows.append(row)def calculate_totals(self):"""计算总金额和总数量"""total1 = 0total2 = 0qty_total1 = 0qty_total2 = 0for row in self.rows:try:# 计算金额total1 += float(row.amount1_var.get() or 0)total2 += float(row.amount2_var.get() or 0)# 计算数量qty1 = float(row.qty1_var.get() or 0)qty2 = float(row.qty2_var.get() or 0)if qty1 > 0:  # 只计算非零数量qty_total1 += qty1if qty2 > 0:  # 只计算非零数量qty_total2 += qty2except ValueError:continue# 更新显示self.total1_var.set(f'{total1:.2f}')self.total2_var.set(f'{total2:.2f}')self.qty_total1_var.set(str(int(qty_total1)))self.qty_total2_var.set(str(int(qty_total2)))def export_config(self):"""导出配置到Excel文件"""try:# 创建新的工作簿wb = openpyxl.Workbook()ws = wb.activews.title = "电脑配置单"# 设置列宽ws.column_dimensions['A'].width = 15ws.column_dimensions['B'].width = 25ws.column_dimensions['C'].width = 8ws.column_dimensions['D'].width = 10ws.column_dimensions['E'].width = 12ws.column_dimensions['F'].width = 25ws.column_dimensions['G'].width = 8ws.column_dimensions['H'].width = 10# 设置标题ws['A1'] = "电脑配置单"ws.merge_cells('A1:H1')ws['A1'].font = Font(size=14, bold=True)ws['A1'].alignment = Alignment(horizontal='center')# 设置日期ws['A2'] = f"日期:{datetime.now().strftime('%Y-%m-%d')}"ws.merge_cells('A2:H2')# 设置表头headers = ['配件名称', '型号[1]', '数量[1]', '单价[1]', '金额[1]', '型号[2]', '数量[2]', '单价[2]', '金额[2]']for col, header in enumerate(headers, 1):cell = ws.cell(row=3, column=col)cell.value = headercell.font = Font(bold=True)cell.alignment = Alignment(horizontal='center')# 添加数据for row_idx, config_row in enumerate(self.rows, 4):ws.cell(row=row_idx, column=1, value=config_row.part_name)ws.cell(row=row_idx, column=2, value=config_row.model1.get())ws.cell(row=row_idx, column=3, value=float(config_row.qty1_var.get() or 0))ws.cell(row=row_idx, column=4, value=float(config_row.price1_var.get() or 0))ws.cell(row=row_idx, column=5, value=float(config_row.amount1_var.get() or 0))ws.cell(row=row_idx, column=6, value=config_row.model2.get())ws.cell(row=row_idx, column=7, value=float(config_row.qty2_var.get() or 0))ws.cell(row=row_idx, column=8, value=float(config_row.price2_var.get() or 0))ws.cell(row=row_idx, column=9, value=float(config_row.amount2_var.get() or 0))# 添加合计行total_row = len(self.rows) + 4ws.cell(row=total_row, column=1, value="合计")ws.cell(row=total_row, column=3, value=f"数量1:{self.qty_total1_var.get()}")ws.cell(row=total_row, column=5, value=f"金额1:{self.total1_var.get()}")ws.cell(row=total_row, column=7, value=f"数量2:{self.qty_total2_var.get()}")ws.cell(row=total_row, column=9, value=f"金额2:{self.total2_var.get()}")# 设置边框thin_border = Border(left=Side(style='thin'),right=Side(style='thin'),top=Side(style='thin'),bottom=Side(style='thin'))for row in ws.iter_rows(min_row=3, max_row=total_row, min_col=1, max_col=9):for cell in row:cell.border = thin_bordercell.alignment = Alignment(horizontal='center')# 保存文件file_path = filedialog.asksaveasfilename(defaultextension=".xlsx",filetypes=[("Excel files", "*.xlsx")],initialfile=f"电脑配置单_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx")if file_path:wb.save(file_path)messagebox.showinfo("成功", "配置已成功导出到Excel文件!")except Exception as e:messagebox.showerror("错误", f"导出失败:{str(e)}")def print_config(self):"""生成打印预览HTML并在浏览器中打开"""try:html_content = f"""<!DOCTYPE html><html><head><meta charset="UTF-8"><title>电脑配置单</title><style>body {{ font-family: Arial, sans-serif; margin: 20px; }}table {{ width: 100%; border-collapse: collapse; margin-top: 20px; }}th, td {{ border: 1px solid black; padding: 8px; text-align: center; }}th {{ background-color: #f2f2f2; }}.total-row {{ background-color: #f9f9f9; font-weight: bold; }}@media print {{button {{ display: none; }}}}</style></head><body><h2 style="text-align: center;">电脑配置单</h2><p>日期:{datetime.now().strftime('%Y-%m-%d')}</p><table><tr><th>配件名称</th><th>型号[1]</th><th>数量[1]</th><th>单价[1]</th><th>金额[1]</th><th>型号[2]</th><th>数量[2]</th><th>单价[2]</th><th>金额[2]</th></tr>"""# 添加数据行for row in self.rows:html_content += f"""<tr><td>{row.part_name}</td><td>{row.model1.get()}</td><td>{row.qty1_var.get()}</td><td>{row.price1_var.get()}</td><td>{row.amount1_var.get()}</td><td>{row.model2.get()}</td><td>{row.qty2_var.get()}</td><td>{row.price2_var.get()}</td><td>{row.amount2_var.get()}</td></tr>"""# 添加合计行html_content += f"""<tr class="total-row"><td colspan="2">合计</td><td>数量1:{self.qty_total1_var.get()}</td><td></td><td>金额1:{self.total1_var.get()}</td><td></td><td>数量2:{self.qty_total2_var.get()}</td><td></td><td>金额2:{self.total2_var.get()}</td></tr></table><button onclick="window.print()" style="margin-top: 20px;">打印</button></body></html>"""# 创建临时HTML文件with tempfile.NamedTemporaryFile('w', delete=False, suffix='.html', encoding='utf-8') as f:f.write(html_content)temp_path = f.name# 在浏览器中打开webbrowser.open('file://' + temp_path)except Exception as e:messagebox.showerror("错误", f"打印预览失败:{str(e)}")def edit_components_data(self):"""编辑配件数据对话框"""edit_window = tk.Toplevel(self)edit_window.title('编辑配件数据')edit_window.geometry('800x600')# 创建主框架main_frame = ttk.Frame(edit_window, padding=10)main_frame.pack(fill='both', expand=True)# 创建树形视图tree_frame = ttk.Frame(main_frame)tree_frame.pack(fill='both', expand=True)tree = ttk.Treeview(tree_frame, columns=('型号', '价格', '配置'), show='headings')tree.heading('型号', text='型号')tree.heading('价格', text='价格')tree.heading('配置', text='配置方案')# 添加滚动条scrollbar = ttk.Scrollbar(tree_frame, orient='vertical', command=tree.yview)tree.configure(yscrollcommand=scrollbar.set)tree.pack(side='left', fill='both', expand=True)scrollbar.pack(side='right', fill='y')# 创建编辑区域edit_frame = ttk.LabelFrame(main_frame, text='编辑', padding=10)edit_frame.pack(fill='x', pady=10)ttk.Label(edit_frame, text='配件:').grid(row=0, column=0, padx=5, pady=5)part_var = tk.StringVar()part_combo = ttk.Combobox(edit_frame, textvariable=part_var, state='readonly')part_combo['values'] = list(self.components_data.data.keys())part_combo.grid(row=0, column=1, padx=5, pady=5)ttk.Label(edit_frame, text='配置方案:').grid(row=0, column=2, padx=5, pady=5)option_var = tk.StringVar(value='option1')option1_radio = ttk.Radiobutton(edit_frame, text='配置1', variable=option_var, value='option1')option2_radio = ttk.Radiobutton(edit_frame, text='配置2', variable=option_var, value='option2')option1_radio.grid(row=0, column=3, padx=5, pady=5)option2_radio.grid(row=0, column=4, padx=5, pady=5)ttk.Label(edit_frame, text='型号:').grid(row=1, column=0, padx=5, pady=5)model_var = tk.StringVar()model_entry = ttk.Entry(edit_frame, textvariable=model_var)model_entry.grid(row=1, column=1, padx=5, pady=5)ttk.Label(edit_frame, text='价格:').grid(row=1, column=2, padx=5, pady=5)price_var = tk.StringVar()price_entry = ttk.Entry(edit_frame, textvariable=price_var)price_entry.grid(row=1, column=3, padx=5, pady=5)def update_tree(*args):tree.delete(*tree.get_children())part = part_var.get()if part in self.components_data.data:for option in ['option1', 'option2']:for item in self.components_data.data[part][option]:tree.insert('', 'end', values=(item['model'],item['price'],'配置1' if option == 'option1' else '配置2'))def add_item():try:part = part_var.get()option = option_var.get()model = model_var.get()price = float(price_var.get())if not part or not model:messagebox.showwarning('警告', '请选择配件并输入型号')return# 检查是否已存在for item in self.components_data.data[part][option]:if item['model'] == model:item['price'] = pricebreakelse:self.components_data.data[part][option].append({'model': model,'price': price})# 保存并更新self.components_data.save_data()update_tree()# 清空输入model_var.set('')price_var.set('')except ValueError:messagebox.showerror('错误', '价格必须是数字')def delete_item():selected = tree.selection()if not selected:messagebox.showwarning('警告', '请选择要删除的项目')returnif messagebox.askyesno('确认', '确定要删除选中的项目吗?'):for item_id in selected:values = tree.item(item_id)['values']model = values[0]option = 'option1' if values[2] == '配置1' else 'option2'part = part_var.get()# 删除项目self.components_data.data[part][option] = [item for item in self.components_data.data[part][option]if item['model'] != model]# 保存并更新self.components_data.save_data()update_tree()# 添加按钮button_frame = ttk.Frame(main_frame)button_frame.pack(fill='x', pady=10)ttk.Button(button_frame, text='添加/更新', command=add_item).pack(side='left', padx=5)ttk.Button(button_frame, text='删除', command=delete_item).pack(side='left', padx=5)# 绑定事件part_var.trace_add('write', update_tree)def on_tree_select(event):selected = tree.selection()if selected:values = tree.item(selected[0])['values']model_var.set(values[0])price_var.set(values[1])option_var.set('option1' if values[2] == '配置1' else 'option2')tree.bind('<<TreeviewSelect>>', on_tree_select)if __name__ == '__main__':app = ComputerConfigApp()app.mainloop()# pyinstaller --onefile --windowed pc.py

 json/components_data.json

{"CPU": {"option1": [{"model": "Intel i5-13600KF","price": 2099},{"model": "Intel i7-13700K","price": 2899},{"model": "AMD R7 7700X","price": 2499},{"model": "其他","price": 0}],"option2": [{"model": "Intel i9-13900K","price": 4299},{"model": "AMD R9 7950X","price": 4499},{"model": "其他","price": 0}]},"主板": {"option1": [{"model": "华硕 PRIME B760M-K","price": 799},{"model": "微星 PRO B760M-P","price": 699},{"model": "其他","price": 0}],"option2": [{"model": "华硕 ROG STRIX Z790-A","price": 2799},{"model": "微星 MPG Z790","price": 2599},{"model": "其他","price": 0}]},"内存": {"option1": [{"model": "金士顿 16GB DDR4 3200","price": 299},{"model": "海盗船 16GB DDR4 3600","price": 399},{"model": "其他","price": 0}],"option2": [{"model": "芝奇 32GB DDR5 6000","price": 999},{"model": "英睿达 32GB DDR5 5600","price": 899},{"model": "其他","price": 0}]},"硬盘": {"option1": [{"model": "西数蓝盘 2TB","price": 299},{"model": "希捷酷鱼 2TB","price": 319},{"model": "其他","price": 0}],"option2": [{"model": "西数紫盘 4TB","price": 699},{"model": "希捷酷狼 4TB","price": 799},{"model": "其他","price": 0}]},"SSD固态盘": {"option1": [{"model": "三星 980 500GB","price": 299},{"model": "西数 SN570 500GB","price": 279},{"model": "其他","price": 0}],"option2": [{"model": "三星 990 PRO 1TB","price": 799},{"model": "西数 SN850X 1TB","price": 699},{"model": "其他","price": 0}]},"显卡": {"option1": [{"model": "RTX 4060 8GB","price": 2299},{"model": "RX 7600 8GB","price": 1999},{"model": "其他","price": 0}],"option2": [{"model": "RTX 4080 16GB","price": 8999},{"model": "RX 7900 XTX","price": 7999},{"model": "其他","price": 0}]},"机箱": {"option1": [{"model": "先马鲁班3","price": 199},{"model": "爱国者T16","price": 169},{"model": "其他","price": 0}],"option2": [{"model": "联力O11 AIR MINI","price": 499},{"model": "Be quiet! 500DX","price": 599},{"model": "其他","price": 0}]},"电源": {"option1": [{"model": "长城 G6 650W","price": 399},{"model": "航嘉 GX650W","price": 369},{"model": "其他","price": 0}],"option2": [{"model": "海韵 FOCUS 850W","price": 899},{"model": "振华 LEADEX G 850W","price": 799},{"model": "其他","price": 0}]},"显示器": {"option1": [{"model": "AOC 24G2 24寸","price": 999},{"model": "创维 F24G1Q 24寸","price": 799},{"model": "其他","price": 0}],"option2": [{"model": "三星 G7 27寸","price": 3999},{"model": "LG 27GP850 27寸","price": 2999},{"model": "其他","price": 0}]},"键鼠套装": {"option1": [{"model": "罗技 MK120","price": 89},{"model": "双飞燕 KR-85","price": 69},{"model": "其他","price": 0}],"option2": [{"model": "罗技 MK850","price": 599},{"model": "雷蛇 无线套装","price": 799},{"model": "其他","price": 0}]},"键盘": {"option1": [{"model": "达尔优 A87","price": 199},{"model": "黑爵 K870T","price": 169},{"model": "其他","price": 0}],"option2": [{"model": "CHERRY MX 8.0","price": 999},{"model": "FILCO 忍者87","price": 1299},{"model": "其他","price": 0}]},"鼠标": {"option1": [{"model": "罗技 G102","price": 129},{"model": "达尔优 A960","price": 99},{"model": "其他","price": 0}],"option2": [{"model": "罗技 G PRO X","price": 699},{"model": "雷蛇 毒蝰 V3","price": 599},{"model": "其他","price": 0}]},"散热器": {"option1": [{"model": "九州风神 玄冰400","price": 89},{"model": "利民 AX120","price": 129},{"model": "其他","price": 0}],"option2": [{"model": "利民 PA120","price": 299},{"model": "猫头鹰 NH-D15","price": 699},{"model": "其他","price": 0}]},"音箱": {"option1": [{"model": "漫步者 R12U","price": 99},{"model": "惠威 D200","price": 299},{"model": "其他","price": 0}],"option2": [{"model": "惠威 D1090","price": 999},{"model": "漫步者 S2000","price": 799},{"model": "其他","price": 0}]},"光存储": {"option1": [{"model": "华硕 DVD刻录机","price": 129},{"model": "建兴 DVD刻录机","price": 119},{"model": "其他","price": 0}],"option2": [{"model": "先锋 蓝光刻录机","price": 599},{"model": "华硕 蓝光刻录机","price": 699},{"model": "其他","price": 0}]}
}

Html 版

 说明书:

我来为这个装机配置单网页写一个使用说明书:

```markdown
# 装机配置单使用说明书

## 1. 基本功能介绍

这是一个用于创建电脑装机配置单的网页工具。用户可以:
- 选择各种电脑配件的型号和数量
- 自动计算总价
- 导出Excel文件
- 打印配置单

## 2. 界面说明

界面主要包含以下部分:
- 日期选择框
- 配件清单表格(包含两套配置方案)
- 导出和打印按钮

### 2.1 表格列说明
- 操作:删除当前行
- 配件名称:预设的配件类型
- 品牌型号[1]:第一套配置的型号选择
- 数量[1]:第一套配置的数量
- 单价[1]:第一套配置的单价
- 金额[1]:自动计算的总金额
- 品牌型号[2]:第二套配置的型号选择
- 数量[2]:第二套配置的数量
- 单价[2]:第二套配置的单价
- 金额[2]:自动计算的总金额

## 3. 使用步骤

### 3.1 创建配置单
1. 选择日期
2. 对每个配件:
   - 从下拉菜单选择品牌型号
   - 如选择"其他",可手动输入型号
   - 输入数量
   - 价格会自动填充(选择"其他"时需手动输入)
   - 金额会自动计算

### 3.2 修改配置
- 可随时修改数量和价格
- 点击"删除"按钮可删除不需要的配件行
- 所有修改会自动重新计算总价

### 3.3 导出配置单
1. 点击"导出Excel"按钮
2. 文件会自动下载,文件名格式为:"装机配置单_日期.xlsx"

### 3.4 打印配置单
1. 点击"打印表格"按钮
2. 在打印预览中确认内容
3. 选择打印机进行打印

## 4. 特殊功能说明

### 4.1 自定义型号
- 当预设型号不满足需求时,可选择"其他"
- 选择"其他"后会出现输入框,可输入自定义型号
- 自定义型号需手动输入价格

### 4.2 价格显示
- 非零金额会以红色显示
- 总计会自动更新并显示在表格底部

## 5. 注意事项

1. 导出前请确保所有数据正确
2. 打印前建议先预览
3. 修改数量或价格后会自动重新计算
4. 删除行操作不可撤销,请谨慎操作

## 6. 常见问题

Q: 如何添加自定义型号?
A: 在型号下拉菜单中选择"其他",然后在出现的输入框中输入。

Q: 为什么金额显示为红色?
A: 这是正常的,所有非零金额都会以红色显示,以方便识别。

Q: 如何修改已输入的数据?
A: 直接点击相应的输入框进行修改,系统会自动重新计算。
```
 

Html 版代码: 

<!-- 作者:Hoye -->
<!-- 日期:2024-12-21 -->
<!-- 功能:装机配置单 -->
<!-- 版本:1.0 -->
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>装机配置单</title><script src="https://cdn.sheetjs.com/xlsx-0.19.3/package/dist/xlsx.full.min.js"></script><style>table {width: 100%;border-collapse: collapse;}th, td {border: 1px solid #ccc;padding: 4px;text-align: left;}th {background-color: #f0f0f0;}.total-row {background-color: #e6f3ff;}input {width: 90%;padding: 4px;border: 1px solid #ddd;}.calculate-btn {margin: 20px 0;padding: 10px 20px;background-color: #4CAF50;color: white;border: none;cursor: pointer;}.calculate-btn:hover {background-color: #45a049;}.export-btn {background-color: #2196F3;}.export-btn:hover {background-color: #1976D2;}.delete-btn {background-color: #f44336;color: white;border: none;padding: 4px 8px;cursor: pointer;margin-right: 5px;}.delete-btn:hover {background-color: #da190b;}@media print {.calculate-btn, .delete-btn {display: none;}body {margin: 0;padding: 20px;}table {width: 100%;border-collapse: collapse;}th, td {border: 1px solid #000;padding: 8px;}input {border: none;width: 100%;padding: 0;}thead {display: table-header-group;}tr {page-break-inside: avoid;}}.print-btn {background-color: #607D8B;}.print-btn:hover {background-color: #455A64;}select {width: 90%;padding: 4px;border: 1px solid #ddd;}.custom-input-container {display: none;margin-top: 5px;}.custom-input-container.show {display: block;}.custom-input {width: 85%;padding: 4px;border: 1px solid #ddd;}.amount1[value]:not([value="0"]):not([value=""]),.amount2[value]:not([value="0"]):not([value=""]) {color: red;}</style>
</head>
<body><h2>1.装机配置单</h2><p>日期: <input type="date" id="date" value="2024-12-15"></p><table id="configTable"><tr><th>操作</th><th>配件名称</th><th>品牌型号[1]</th><th>数量[1]</th><th>单价[1]</th><th>金额[1]</th><th>品牌型号[2]</th><th>数量[2]</th><th>单价[2]</th><th>金额[2]</th></tr><tr><td><button class="delete-btn" onclick="deleteRow(this)">删除</button></td><td><input type="text" value="CPU"></td><td><input type="text" value=""></td><td><input type="number" class="qty1" value="1"></td><td><input type="number" class="price1" value="0"></td><td><input type="number" class="amount1" readonly></td><td><input type="text" value=""></td><td><input type="number" class="qty2" value="1"></td><td><input type="number" class="price2" value="0"></td><td><input type="number" class="amount2" readonly></td></tr><tr><td><button class="delete-btn" onclick="deleteRow(this)">删除</button></td><td><input type="text" value="主板"></td><td><input type="text" value=""></td><td><input type="number" class="qty1" value="1"></td><td><input type="number" class="price1" value="0"></td><td><input type="number" class="amount1" readonly></td><td><input type="text" value=""></td><td><input type="number" class="qty2" value="1"></td><td><input type="number" class="price2" value="0"></td><td><input type="number" class="amount2" readonly></td></tr><tr><td><button class="delete-btn" onclick="deleteRow(this)">删除</button></td><td><input type="text" value="内存"></td><td><input type="text" value=""></td><td><input type="number" class="qty1" value="1"></td><td><input type="number" class="price1" value="0"></td><td><input type="number" class="amount1" readonly></td><td><input type="text" value=""></td><td><input type="number" class="qty2" value="1"></td><td><input type="number" class="price2" value="0"></td><td><input type="number" class="amount2" readonly></td></tr><tr><td><button class="delete-btn" onclick="deleteRow(this)">删除</button></td><td><input type="text" value="硬盘"></td><td><input type="text" value=""></td><td><input type="number" class="qty1" value="1"></td><td><input type="number" class="price1" value="0"></td><td><input type="number" class="amount1" readonly></td><td><input type="text" value=""></td><td><input type="number" class="qty2" value="1"></td><td><input type="number" class="price2" value="0"></td><td><input type="number" class="amount2" readonly></td></tr><tr><td><button class="delete-btn" onclick="deleteRow(this)">删除</button></td><td><input type="text" value="SSD固态盘"></td><td><input type="text" value=""></td><td><input type="number" class="qty1" value="1"></td><td><input type="number" class="price1" value="0"></td><td><input type="number" class="amount1" readonly></td><td><input type="text" value=""></td><td><input type="number" class="qty2" value="1"></td><td><input type="number" class="price2" value="0"></td><td><input type="number" class="amount2" readonly></td></tr><tr><td><button class="delete-btn" onclick="deleteRow(this)">删除</button></td><td><input type="text" value="显卡"></td><td><input type="text" value=""></td><td><input type="number" class="qty1" value="1"></td><td><input type="number" class="price1" value="0"></td><td><input type="number" class="amount1" readonly></td><td><input type="text" value=""></td><td><input type="number" class="qty2" value="1"></td><td><input type="number" class="price2" value="0"></td><td><input type="number" class="amount2" readonly></td></tr><tr><td><button class="delete-btn" onclick="deleteRow(this)">删除</button></td><td><input type="text" value="机箱"></td><td><input type="text" value=""></td><td><input type="number" class="qty1" value="1"></td><td><input type="number" class="price1" value="0"></td><td><input type="number" class="amount1" readonly></td><td><input type="text" value=""></td><td><input type="number" class="qty2" value="1"></td><td><input type="number" class="price2" value="0"></td><td><input type="number" class="amount2" readonly></td></tr><tr><td><button class="delete-btn" onclick="deleteRow(this)">删除</button></td><td><input type="text" value="电源"></td><td><input type="text" value=""></td><td><input type="number" class="qty1" value="1"></td><td><input type="number" class="price1" value="0"></td><td><input type="number" class="amount1" readonly></td><td><input type="text" value=""></td><td><input type="number" class="qty2" value="1"></td><td><input type="number" class="price2" value="0"></td><td><input type="number" class="amount2" readonly></td></tr><tr><td><button class="delete-btn" onclick="deleteRow(this)">删除</button></td><td><input type="text" value="显示器"></td><td><input type="text" value=""></td><td><input type="number" class="qty1" value="1"></td><td><input type="number" class="price1" value="0"></td><td><input type="number" class="amount1" readonly></td><td><input type="text" value=""></td><td><input type="number" class="qty2" value="1"></td><td><input type="number" class="price2" value="0"></td><td><input type="number" class="amount2" readonly></td></tr><tr><td><button class="delete-btn" onclick="deleteRow(this)">删除</button></td><td><input type="text" value="键鼠套装"></td><td><input type="text" value=""></td><td><input type="number" class="qty1" value="1"></td><td><input type="number" class="price1" value="0"></td><td><input type="number" class="amount1" readonly></td><td><input type="text" value=""></td><td><input type="number" class="qty2" value="1"></td><td><input type="number" class="price2" value="0"></td><td><input type="number" class="amount2" readonly></td></tr><tr><td><button class="delete-btn" onclick="deleteRow(this)">删除</button></td><td><input type="text" value="键盘"></td><td><input type="text" value=""></td><td><input type="number" class="qty1" value="0"></td><td><input type="number" class="price1" value="0"></td><td><input type="number" class="amount1" readonly></td><td><input type="text" value=""></td><td><input type="number" class="qty2" value="0"></td><td><input type="number" class="price2" value="0"></td><td><input type="number" class="amount2" readonly></td></tr><tr><td><button class="delete-btn" onclick="deleteRow(this)">删除</button></td><td><input type="text" value="鼠标"></td><td><input type="text" value=""></td><td><input type="number" class="qty1" value="0"></td><td><input type="number" class="price1" value="0"></td><td><input type="number" class="amount1" readonly></td><td><input type="text" value=""></td><td><input type="number" class="qty2" value="0"></td><td><input type="number" class="price2" value="0"></td><td><input type="number" class="amount2" readonly></td></tr><tr><td><button class="delete-btn" onclick="deleteRow(this)">删除</button></td><td><input type="text" value="散热器"></td><td><input type="text" value=""></td><td><input type="number" class="qty1" value="1"></td><td><input type="number" class="price1" value="0"></td><td><input type="number" class="amount1" readonly></td><td><input type="text" value=""></td><td><input type="number" class="qty2" value="1"></td><td><input type="number" class="price2" value="0"></td><td><input type="number" class="amount2" readonly></td></tr><tr><td><button class="delete-btn" onclick="deleteRow(this)">删除</button></td><td><input type="text" value="音箱"></td><td><input type="text" value=""></td><td><input type="number" class="qty1" value="1"></td><td><input type="number" class="price1" value="0"></td><td><input type="number" class="amount1" readonly></td><td><input type="text" value=""></td><td><input type="number" class="qty2" value="1"></td><td><input type="number" class="price2" value="0"></td><td><input type="number" class="amount2" readonly></td></tr><tr><td><button class="delete-btn" onclick="deleteRow(this)">删除</button></td><td><input type="text" value="光存储"></td><td><input type="text" value=""></td><td><input type="number" class="qty1" value="0"></td><td><input type="number" class="price1" value="0"></td><td><input type="number" class="amount1" readonly></td><td><input type="text" value=""></td><td><input type="number" class="qty2" value="0"></td><td><input type="number" class="price2" value="0"></td><td><input type="number" class="amount2" readonly></td></tr><tr class="total-row"><td colspan="3">数量[1]:<span id="qty_total1">0</span></td><td colspan="2"></td><td>合计[1]:<span id="total1">0</span></td><td colspan="2">数量[2]:<span id="qty_total2">0</span></td><td></td><td>合计[2]:<span id="total2">0</span></td></tr></table><button class="calculate-btn export-btn" onclick="exportToExcel()">导出Excel</button><button class="calculate-btn print-btn" onclick="printTable()">打印表格</button><script>// 扩展配件选项数组,包含所有配件const partOptions = {CPU: {option1: [{model: "I3 8100", price: 150},{model: "I3 12100", price: 585},{model: "I5 12400", price: 735},{model: "其他", price: 0}],option2: [{model: "Intel酷睿 i3-10100", price: 520},{model: "Intel酷睿 i3-12100", price: 585},{model: "Intel酷睿 i5-12400", price: 735},{model: "其他", price: 0}]},主板: {option1: [{model: "精粤H310M-G D4", price: 275},{model: "技嘉H610M", price: 465},{model: "铭瑄H610M-R", price: 355},{model: "精粤H610", price: 325},{model: "其他", price: 0}],option2: [{model: "华硕PRIME H610M-K(HDMI)", price: 499},{model: "其他", price: 0}]},内存: {option1: [{model: "金百达 3200 DDR4 16G", price: 128},{model: "金百达 3600 DDR4 16G", price: 159},{model: "其他", price: 0}],option2: [{model: "威刚DDR4 16GB", price: 137},{model: "其他", price: 0}]},硬盘: {option1: [{model: "西部数据蓝盘 1TB", price: 345},{model: "西部数据蓝盘 2TB", price: 375},{model: "其他", price: 0}],option2: [{model: "西部数据蓝盘 2TB", price: 375},{model: "其他", price: 0}]},"SSD固态盘": {option1: [{model: "玩翔(PANXIANG)m.2 512G", price: 210},{model: "西数SN580 512G", price: 395},{model: "昂达M.2 512G", price: 175},{model: "梵西 512G", price: 205},{model: "其他", price: 0}],option2: [{model: "闪迪 240 SSD", price: 159},{model: "其他", price: 0}]},显卡: {option1: [{model: "七彩虹GT1030", price: 459},{model: "技嘉GT1030", price: 499},{model: "其他", price: 0}],option2: [{model: "七彩虹GT1030", price: 459},{model: "其他", price: 0}]},机箱: {option1: [{model: "金河田", price: 40},{model: "金刚之星", price: 70},{model: "华硕机箱", price: 75},{model: "小机箱", price: 40},{model: "其他", price: 0}],option2: [{model: "金河田", price: 75},{model: "其他", price: 0}]},电源: {option1: [{model: "长城300W", price: 115},{model: "航嘉300W", price: 118},{model: "OX 300W", price: 70},{model: "其他", price: 0}],option2: [{model: "300W电源", price: 75},{model: "其他", price: 0}]},显示器: {option1: [{model: "联想 27寸 显示器", price: 485},{model: "联想 24寸 显示器", price: 385},{model: "AOC 24寸 显示器", price: 410},{model: "其他", price: 0}],option2: [{model: "联想 24寸 显示器", price: 599},{model: "其他", price: 0}]},键鼠套装: {option1: [{model: "双飞燕键鼠套装", price: 75},{model: "普通键鼠套装", price: 30},{model: "DT键鼠套装", price: 38},{model: "其他", price: 0}],option2: [{model: "无线光电套装", price: 69},{model: "其他", price: 0}]},键盘: {option1: [{model: "双飞燕键盘", price: 25},{model: "其他", price: 0}],option2: [{model: "无线键盘", price: 49},{model: "其他", price: 0}]},鼠标: {option1: [{model: "双飞燕鼠标", price: 15},{model: "其他", price: 0}],option2: [{model: "无线鼠标", price: 29},{model: "其他", price: 0}]},散热器: {option1: [{model: "超频三散热器", price: 15},{model: "双铜管散热器", price: 21},{model: "四铜管散热器", price: 37},{model: "其他", price: 0}],option2: [{model: "九州风神散热器", price: 39},{model: "其他", price: 0}]},音箱: {option1: [{model: "普通音箱", price: 30},{model: "小音箱", price: 15},{model: "其他", price: 0}],option2: [{model: "无线网卡", price: 39},{model: "其他", price: 0}]},光存储: {option1: [{model: "DVD刻录机", price: 100},{model: "DVD光驱", price: 90},{model: "其他", price: 0}],option2: [{model: "DVD刻录机", price: 100},{model: "其他", price: 0}]}};// 处理号选择变化function handleModelChange(select, priceClass) {const selectedOption = select.options[select.selectedIndex];const row = select.closest('tr');const priceInput = row.querySelector('.' + priceClass);const customContainer = select.parentNode.querySelector('.custom-input-container');if (selectedOption.value === "其他") {customContainer.classList.add('show');priceInput.value = 0;} else {if (customContainer) {customContainer.classList.remove('show');}if (selectedOption.dataset.price) {priceInput.value = selectedOption.dataset.price;}}calculateTotals();}// 生成选择框HTMLfunction createSelectHtml(options, priceClass, index) {return `<select onchange="handleModelChange(this, '${priceClass}')" style="width: 90%;"><option value="" selected>请选择型号</option>${options.map(item => `<option value="${item.model}" data-price="${item.price}">${item.model}</option>`).join('')}</select><div class="custom-input-container"><input type="text" class="custom-input" placeholder="请输入型号"oninput="handleCustomInput(this, this.parentNode.previousElementSibling)"></div>`;}// 初始化表格document.addEventListener('DOMContentLoaded', function() {const rows = document.querySelectorAll('#configTable tr:not(:first-child)');rows.forEach(row => {const partNameInput = row.querySelector('td:nth-child(2) input');if (partNameInput && partOptions[partNameInput.value]) {const td3 = row.querySelector('td:nth-child(3)');const td7 = row.querySelector('td:nth-child(7)');const originalModel1 = row.querySelector('td:nth-child(3) input')?.value || '';const originalModel2 = row.querySelector('td:nth-child(7) input')?.value || '';const originalPrice1 = row.querySelector('.price1')?.value || '0';const originalPrice2 = row.querySelector('.price2')?.value || '0';td3.innerHTML = createSelectHtml(partOptions[partNameInput.value].option1, 'price1', 1);td7.innerHTML = createSelectHtml(partOptions[partNameInput.value].option2, 'price2', 2);// 设置第一列的值const select1 = td3.querySelector('select');if (select1) {const option1 = Array.from(select1.options).find(opt => opt.value === originalModel1);if (option1) {select1.value = originalModel1;} else if (originalModel1) {select1.value = "其他";const customInput1 = td3.querySelector('.custom-input');customInput1.value = originalModel1;td3.querySelector('.custom-input-container').classList.add('show');}}// 设置第二列的值const select2 = td7.querySelector('select');if (select2) {const option2 = Array.from(select2.options).find(opt => opt.value === originalModel2);if (option2) {select2.value = originalModel2;} else if (originalModel2) {select2.value = "其他";const customInput2 = td7.querySelector('.custom-input');customInput2.value = originalModel2;td7.querySelector('.custom-input-container').classList.add('show');}}// 设置价格const price1Input = row.querySelector('.price1');const price2Input = row.querySelector('.price2');if (price1Input) price1Input.value = originalPrice1;if (price2Input) price2Input.value = originalPrice2;}});calculateTotals();});// 更新配件选项function updatePartOptions(select) {const row = select.closest('tr');const partName = select.value;if (partOptions[partName]) {const td3 = row.querySelector('td:nth-child(3)');const td7 = row.querySelector('td:nth-child(7)');td3.innerHTML = createSelectHtml(partOptions[partName].option1, 'price1', 1);td7.innerHTML = createSelectHtml(partOptions[partName].option2, 'price2', 2);// 重置价格row.querySelector('.price1').value = '0';row.querySelector('.price2').value = '0';calculateTotals();}}// 修改calculateTotals函数function calculateTotals() {let total1 = 0;let total2 = 0;let qtyTotal1 = 0;let qtyTotal2 = 0;const rows = document.querySelectorAll('#configTable tr:not(:first-child):not(:last-child)');rows.forEach(row => {// 第一列计算const qty1 = parseFloat(row.querySelector('.qty1')?.value) || 0;const price1 = parseFloat(row.querySelector('.price1')?.value) || 0;const amount1 = qty1 * price1;const amount1Input = row.querySelector('.amount1');if (amount1Input) {amount1Input.value = amount1.toFixed(2);amount1Input.style.color = amount1 > 0 ? 'red' : '';total1 += amount1;qtyTotal1 += qty1;}// 第二列计算const qty2 = parseFloat(row.querySelector('.qty2')?.value) || 0;const price2 = parseFloat(row.querySelector('.price2')?.value) || 0;const amount2 = qty2 * price2;const amount2Input = row.querySelector('.amount2');if (amount2Input) {amount2Input.value = amount2.toFixed(2);amount2Input.style.color = amount2 > 0 ? 'red' : '';total2 += amount2;qtyTotal2 += qty2;}});document.getElementById('total1').textContent = total1.toFixed(2);document.getElementById('total2').textContent = total2.toFixed(2);document.getElementById('qty_total1').textContent = qtyTotal1;document.getElementById('qty_total2').textContent = qtyTotal2;}// 确保页面加载时自动计算document.addEventListener('DOMContentLoaded', function() {calculateTotals();// 监听数量和价格变化const table = document.getElementById('configTable');table.addEventListener('input', function(e) {if (e.target.matches('.qty1, .qty2, .price1, .price2')) {calculateTotals();}});});// 在删除行时也要重新计算function deleteRow(btn) {const row = btn.parentNode.parentNode;if (confirm('确定要删除这一行吗?')) {row.parentNode.removeChild(row);calculateTotals();}}// 修改导出Excel函数function exportToExcel() {calculateTotals();const data = [];const table = document.getElementById('configTable');const rows = table.getElementsByTagName('tr');data.push(['装机配置单']);data.push(['日期: ' + document.getElementById('date').value]);data.push([]);// 添加表头const headers = [];const headerCells = rows[0].getElementsByTagName('th');for (let i = 1; i < headerCells.length; i++) {headers.push(headerCells[i].textContent);}data.push(headers);// 添加数据行for (let i = 1; i < rows.length - 1; i++) {const rowData = [];const cells = rows[i].getElementsByTagName('td');// 配件名称rowData.push(cells[1].querySelector('input')?.value || cells[1].querySelector('select')?.value || '');// 品牌型号[1]const model1Select = cells[2].querySelector('select');const model1Custom = cells[2].querySelector('.custom-input');rowData.push(model1Select?.value === '其他' ? model1Custom?.value : model1Select?.value || '');// 数量[1]、单价[1]、金额[1]rowData.push(cells[3].querySelector('input')?.value || '');rowData.push(cells[4].querySelector('input')?.value || '');rowData.push(cells[5].querySelector('input')?.value || '');// 品牌型号[2]const model2Select = cells[6].querySelector('select');const model2Custom = cells[6].querySelector('.custom-input');rowData.push(model2Select?.value === '其他' ? model2Custom?.value : model2Select?.value || '');// 数量[2]、单价[2]、金额[2]rowData.push(cells[7].querySelector('input')?.value || '');rowData.push(cells[8].querySelector('input')?.value || '');rowData.push(cells[9].querySelector('input')?.value || '');data.push(rowData);}// 添加合计行const totalRow = ['数量[1]:' + document.getElementById('qty_total1').textContent,'合计[1]:' + document.getElementById('total1').textContent,'','数量[2]:' + document.getElementById('qty_total2').textContent,'合计[2]:' + document.getElementById('total2').textContent];data.push(totalRow);const ws = XLSX.utils.aoa_to_sheet(data);const wb = XLSX.utils.book_new();XLSX.utils.book_append_sheet(wb, ws, "配置清单");XLSX.writeFile(wb, "装机配置单_" + document.getElementById('date').value + ".xlsx");}// 修改打印表格的样式function printTable() {calculateTotals();// 在打印前临时保存选择框的值const selects = document.querySelectorAll('#configTable select');selects.forEach(select => {if (select.value === '其他') {const customInput = select.parentNode.querySelector('.custom-input');if (customInput) {select.setAttribute('data-print-value', customInput.value);}} else {select.setAttribute('data-print-value', select.value);}});// 添加打印样式const style = document.createElement('style');style.textContent = `@media print {.calculate-btn, .delete-btn, .custom-input-container {display: none !important;}select {border: none !important;-webkit-appearance: none !important;-moz-appearance: none !important;appearance: none !important;padding: 0 !important;}select::before {content: attr(data-print-value) !important;}}`;document.head.appendChild(style);window.print();// 打印后移除样式document.head.removeChild(style);}</script>
</body>
</html>

更高版本,以为2025年后 使用过程 再改进。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/493821.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android studio更改应用图片,和应用名字。

更改应用图标&#xff0c;和名字 先打开AndroidManifest.xml文件。 更改图片文件名字&#xff08; 右键-->构建-->重命名&#xff08;R&#xff09;&#xff09;

Vue Web开发(十)

1. 用户管理新增&#xff0c;搜索&#xff0c;编辑&#xff0c;删除 本节课完成用户列表表单设计&#xff0c;使用table组件&#xff0c;同样模块化组件&#xff0c;CommonTable.vue组件&#xff0c;并且在User页面中引入&#xff0c;mock实现数据模拟&#xff0c;最终完成用户…

人工智能在VR展览中扮演什么角色?

人工智能&#xff08;AI&#xff09;在VR展览中扮演着多重关键角色&#xff0c;这些角色不仅增强了用户体验&#xff0c;还为展览的组织者提供了强大的工具。 接下来&#xff0c;由专业从事VR展览制作的圆桌3D云展厅平台为大家介绍AI在VR展览中的一些主要作用&#xff1a; 个性…

学工管理系统-职校信息化管理平台

学工管理系统是一种致力于提升职校管理效率和信息化水平的重要工具。它综合运用了现代信息技术和学工管理理念&#xff0c;为学校提供了全面、科学、高效的管理平台。 学工管理系统在学校管理中发挥着重要的作用。它能够实现学生信息的完整管理&#xff0c;包括学籍、课程、成绩…

如何测量分辨率

一、什么是分辨率&#xff1f; 分辨率指的是分清物体细节的能力。分辨率是一个成像系统还原空间频率的能力。一些人只是简单的用分辨率去描述极限分辨率&#xff0c;但是相机在在不同的对比度的情况下还原低&#xff0c;中和高频率的能力&#xff0c;也可以显示全面综合的信息。…

MySQL数据库下载及安装教程

链接&#xff1a;MySQL数据库下载及安装教程&#xff08;最最新版&#xff09;_mysql下载安装-CSDN博客 亲测安装成功了&#x1f495; 把这个路径放到系统环境变量里头 MD!我这安到C盘去了&#xff0c;就很烦&#x1f92c;&#x1f621; 在CMD登录试一下 mysql -h localhos…

RadiAnt DICOM - 基本主题 :从 PACS 服务器打开研究

正版序列号获取&#xff1a;https://r-g.io/42ZopE RadiAnt DICOM Viewer PACS 客户端功能允许您从 PACS 主机&#xff08;图片存档和通信系统&#xff09;搜索和下载研究。 在开始之前&#xff0c;您需要确保您的 PACS 服务器和 RadiAnt 已正确配置。有关配置说明&#xff0c…

时间管理系统|Java|SSM|JSP|

【技术栈】 1⃣️&#xff1a;架构: B/S、MVC 2⃣️&#xff1a;系统环境&#xff1a;Windowsh/Mac 3⃣️&#xff1a;开发环境&#xff1a;IDEA、JDK1.8、Maven、Mysql5.7 4⃣️&#xff1a;技术栈&#xff1a;Java、Mysql、SSM、Mybatis-Plus、JSP、jquery,html 5⃣️数据库可…

智能座舱进阶-应用框架层-Handler分析

首先明确&#xff0c; handler是为了解决单进程内的线程之间的通信问题的。我也需要理解Android系统中进程和线程的概念&#xff0c; APP启动后&#xff0c;会有三四个线程启动起来&#xff0c;其中&#xff0c;有一条mainUITread的线程&#xff0c;专门用来处理UI事件&#xf…

构建高性能异步任务引擎:FastAPI + Celery + Redis

在现代应用开发中&#xff0c;异步任务处理是一个常见的需求。无论是数据处理、图像生成&#xff0c;还是复杂的计算任务&#xff0c;异步执行都能显著提升系统的响应速度和吞吐量。今天&#xff0c;我们将通过一个实际项目&#xff0c;探索如何使用 FastAPI、Celery 和 Redis …

go面试问题

1 Go的内存逃逸如何分析 go build -gcflags-m main_pointer.go 2 http状态码 300 请求的资源可包括多个位置&#xff0c;相应可返回一个资源特征与地址的列表用于用户终端&#xff08;例如&#xff1a;浏览器&#xff09;选择 301 永久移动。请求的资源已被永久的移动到新U…

JVM性能优化一:初识内存泄露-内存溢出-垃圾回收

本文主要是让你充分的认识到什么叫做内存泄露&#xff0c;什么叫做内存溢出&#xff0c;别再傻傻分不清了&#xff0c;别再动不动的升级服务器的内存了。 文章目录 1.基本概念1.1.内存泄露1.2.内存溢出1.3.垃圾回收1.4.内存泄露-垃圾回收-内存溢出三者的关系关系 2.代码示例2.…

安装milvus以及向量库增删改操作

首先电脑已经安装了docker windows电脑可下载yml文件 https://github.com/milvus-io/milvus/releases/download/v2.4.6/milvus-standalone-docker-compose.yml 创建milvus文件夹&#xff0c;并在这个目录下创建五个文件夹&#xff1a;conf、db、logs、pic、volumes、wal 然后…

ARP..

ARP 0 前言 真正接触到现网才发现ARP十分重要&#xff0c;无论是排错还是S-MLAG都需要用到ARP这个协议&#xff0c;以前对于ARP的理解比较混乱&#xff1b;所以这次对其中的主要内容做个梳理&#xff1b;一定要学好ARP&#xff01;&#xff01;&#xff01; 1 ARP的概念 Ar…

单片机上电后程序不运行怎么排查问题?

1.电源检查。使用电压表测量单片机的电源电压是否正常&#xff0c;确保电压在规定的范围内&#xff0c;如常见的5V。 2.复位检查。检查复位引脚的电压是否正常&#xff0c;在单片机接通电源时&#xff0c;复位引脚通常会有一个高电平&#xff0c;按下复位按钮时&#xff0c;复位…

SKETCHPAD——允许语言模型生成中间草图,在几何、函数、图算法和游戏策略等所有数学任务中持续提高基础模型的性能

概述 论文地址&#xff1a;https://arxiv.org/pdf/2406.09403 素描是一种应用广泛的有效工具&#xff0c;包括产生创意和解决问题。由于素描能直接传达无法用语言表达的视觉和空间信息&#xff0c;因此从古代岩画到现代建筑图纸&#xff0c;素描在世界各地被用于各种用途。儿童…

Linux应用开发————mysql数据库表

mysql数据库表操作 查看表的结构 mysql> desc / describe 表名; 或者&#xff1a; mysql> show create table 表名; 常见数据库引擎&#xff1a; innodb, myISAM... 删除表 mysql> drop tabl…

【C#】实现Json转Lua (Json2Lua)

关键词: C#、JsonToLua、Json2Lua、对象序列化Lua 前提需引入NewtonsofJson&#xff0c;引入方法可先在Visual Studio 2019 将Newtonsoft.Json.dll文件导入Unity的Plugins下。 Json格式字符串转Lua格式字符串&#xff0c;效果如下&#xff1a; json字符串 {"1": &q…

2.4 网络概念(分层、TCP)

网络层与传输层概述 网络层&#xff1a; 抽象概念&#xff1a;网络层是基于 IP 的抽象概念&#xff0c;与数据链路层用 MAC 地址标记设备不同。MAC 地址是一种具体化的概念&#xff0c;绑定于所在的物理网络&#xff0c;而 IP 地址可以是固定的&#xff0c;也可以通过路由动态…

LabVIEW伸缩臂参数监控系统

LabVIEW开发伸缩臂越野叉车参数监控系统主要应用于工程机械中的越野叉车&#xff0c;以提高车辆的作业效率和故障诊断能力。系统通过PEAK CAN硬件接口和LabVIEW软件平台实现对叉车作业参数的实时监控和故障分析&#xff0c;具有良好的实用性和推广价值。 系统组成 系统主要由P…