当前位置: 首页 > article >正文

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.0

import 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 webbrowser

class 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 0

    def 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_price
                    self.save_data()
                    return True
        return False

class ConfigRow:
    def __init__(self, parent1, parent2, part_name, row, components_data, on_amount_change=None):
        self.components_data = components_data
        self.on_amount_change = on_amount_change
        self.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'] = models1
            self.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 = 0
        total2 = 0
        qty_total1 = 0
        qty_total2 = 0
        
        for 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 += qty1
                if qty2 > 0:  # 只计算非零数量
                    qty_total2 += qty2
                
            except 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.active
            ws.title = "电脑配置单"
            
            # 设置列宽
            ws.column_dimensions['A'].width = 15
            ws.column_dimensions['B'].width = 25
            ws.column_dimensions['C'].width = 8
            ws.column_dimensions['D'].width = 10
            ws.column_dimensions['E'].width = 12
            ws.column_dimensions['F'].width = 25
            ws.column_dimensions['G'].width = 8
            ws.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 = header
                cell.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) + 4
            ws.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_border
                    cell.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'] = price
                        break
                else:
                    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('警告', '请选择要删除的项目')
                return
            
            if 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();
        }

        // 生成选择框HTML
        function 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.kler.cn/a/447605.html

相关文章:

  • 强基计划之编程:开启科研精英培养新路径
  • Vue(二)
  • 【win10+RAGFlow+Ollama】搭建本地大模型助手(教程+源码)
  • RabbitMQ消息可靠性保证机制7--可靠性分析-rabbitmq_tracing插件
  • 探索 Python编程 调试案例:计算小程序中修复偶数的bug
  • 设计模式12:状态模式
  • CV算法在工作中有哪些实际应用?
  • 数据挖掘之认识数据
  • C++9--前置++和后置++重载,const,日期类的实现(对前几篇知识点的应用)
  • docker hub上下载使用postgis官方插件
  • 【python 字典(dict)和集合(set)】创建、访问、基本操作及各自的特点】
  • keil已有项目改工程名
  • 1387. 将整数按权重排序 中等
  • 3大Excel免费功能
  • 吉快科技荣膺“金边奖·最佳大模型一体机”,引领AI边缘新时代
  • 江苏计算机专转本 技能Mysql知识点总结(二)
  • C05S07-Tomcat服务架设
  • 15款行业大数据报告下载网站
  • H5 ios软键盘弹起遮挡输入框
  • #渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍06-基于子查询的SQL注入(Subquery-Based SQL Injection)
  • macOS 配置 vscode 命令行启动
  • pat乙级1072 开学寄语
  • WebRTC服务质量(07)- 重传机制(04) 接收NACK消息
  • 基于微信小程序的消防隐患在线举报系统
  • Tensor
  • sh cmake-linux.sh -- --skip-license --prefix = $MY_INSTALL_DIR