import wx # wx作为GUI库123
import sqlite3
import os
import datetime
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Image
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from PIL import Image as PILImage
from io import BytesIO
import tempfile
import re
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from pathlib import Path
# 系统字体目录路径(Windows系统)
font_dir = Path("C:/Windows/Fonts")
# 常用中文字体映射
font_mapping = {
"SimHei": font_dir / "simhei.ttf", # 黑体
"SimSun": font_dir / "simsun.ttc", # 宋体
"Microsoft YaHei": font_dir / "msyh.ttc" # 微软雅黑
}
# 注册字体
for font_name, font_path in font_mapping.items():
if font_path.exists():
pdfmetrics.registerFont(TTFont(font_name, str(font_path)))
print(f"已注册字体: {font_name}")
else:
print(f"字体文件不存在: {font_path}")
class ProductionOrderApp(wx.Frame):
def __init__(self, parent, title):
super(ProductionOrderApp, self).__init__(parent, title=title, size=(1000, 800))
# 创建数据库连接
self.db_conn = sqlite3.connect('production_orders.db')
self.create_tables()
# 创建UI
self.panel = wx.ScrolledWindow(self, style=wx.VSCROLL)
self.panel.SetScrollRate(10, 10)
self.init_ui()
self.load_orders()
# 初始化图片变量
self.current_images = {'pcb': None, 'appearance': None}
self.current_order_id = None
# 初始化PDF样式
self.pdf_styles = None
self.Centre()
self.Show()
def create_tables(self):
"""创建数据库表结构"""
cursor = self.db_conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS production_orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_number TEXT NOT NULL UNIQUE,
date TEXT NOT NULL,
product_name TEXT NOT NULL,
product_code TEXT NOT NULL,
shell_height REAL,
pcb_model TEXT,
pin TEXT,
polarity TEXT,
light_color TEXT,
chip_type TEXT,
chip_count INTEGER,
packaging_requirements TEXT,
product_marking TEXT,
order_quantity INTEGER,
pcb_image BLOB,
appearance_image BLOB
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS production_steps (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER NOT NULL,
step_name TEXT NOT NULL,
step_type TEXT NOT NULL, -- "quantity", "operator", "date"
value TEXT,
FOREIGN KEY (order_id) REFERENCES production_orders(id)
)
''')
self.db_conn.commit()
def generate_order_number(self):
"""生成唯一的工单号,格式为YYYYMMDDHHMMSS"""
now = datetime.datetime.now()
return now.strftime("%Y%m%d%H%M%S")
def init_ui(self):
"""初始化用户界面"""
main_sizer = wx.BoxSizer(wx.VERTICAL)
# 顶部按钮区域
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.new_btn = wx.Button(self.panel, label="新建工单")
self.new_btn.Bind(wx.EVT_BUTTON, self.new_order)
self.save_btn = wx.Button(self.panel, label="保存工单")
self.save_btn.Bind(wx.EVT_BUTTON, self.save_order)
self.delete_btn = wx.Button(self.panel, label="删除工单")
self.delete_btn.Bind(wx.EVT_BUTTON, self.delete_order)
self.export_btn = wx.Button(self.panel, label="导出PDF")
self.export_btn.Bind(wx.EVT_BUTTON, self.export_pdf)
btn_sizer.Add(self.new_btn, 0, wx.ALL, 5)
btn_sizer.Add(self.save_btn, 0, wx.ALL, 5)
btn_sizer.Add(self.delete_btn, 0, wx.ALL, 5)
btn_sizer.Add(self.export_btn, 0, wx.ALL, 5)
# 工单列表
order_list_box = wx.StaticBox(self.panel, label="工单列表")
order_list_sizer = wx.StaticBoxSizer(order_list_box, wx.VERTICAL)
self.order_list = wx.ListCtrl(self.panel, style=wx.LC_REPORT|wx.BORDER_SUNKEN|wx.LC_SINGLE_SEL, size=(-1, 150))
self.order_list.InsertColumn(0, "工单号", width=120)
self.order_list.InsertColumn(1, "日期", width=100)
self.order_list.InsertColumn(3, "产品识别码", width=120)
self.order_list.InsertColumn(2, "产品名称", width=150)
self.order_list.InsertColumn(4, "下单数量", width=80)
self.order_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_order_select)
order_list_sizer.Add(self.order_list, 1, wx.EXPAND|wx.ALL, 5)
# 表单区域
form_box = wx.StaticBox(self.panel, label="工单详情")
form_sizer = wx.StaticBoxSizer(form_box, wx.VERTICAL)
# ==== 顶部区域 (日期、工单号、记录文本) ====
top_row_sizer = wx.BoxSizer(wx.HORIZONTAL)
# 日期
date_sizer = wx.BoxSizer(wx.HORIZONTAL)
date_sizer.Add(wx.StaticText(self.panel, label="日期:"), 0, wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, 5)
self.date_input = wx.TextCtrl(self.panel, style=wx.TE_READONLY, size=(150, -1))
self.date_input.SetValue(datetime.date.today().strftime("%Y-%m-%d"))
date_sizer.Add(self.date_input, 0)
top_row_sizer.Add(date_sizer, 0, wx.ALL, 5)
# 工单号
order_num_sizer = wx.BoxSizer(wx.HORIZONTAL)
order_num_sizer.Add(wx.StaticText(self.panel, label="工单号:"), 0, wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, 5)
self.order_num_input = wx.TextCtrl(self.panel, style=wx.TE_READONLY, size=(200, -1))
self.order_num_input.SetValue(self.generate_order_number())
order_num_sizer.Add(self.order_num_input, 0)
top_row_sizer.Add(order_num_sizer, 0, wx.ALL, 5)
# 记录文本
info_label = wx.StaticText(self.panel, label="记录整张单据调用免填写")
info_label.SetForegroundColour(wx.Colour(100, 100, 100))
top_row_sizer.Add(info_label, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
form_sizer.Add(top_row_sizer, 0, wx.EXPAND|wx.ALL, 5)
# ==== 产品名称和代码区域 ====
product_row_sizer = wx.BoxSizer(wx.HORIZONTAL)
# 产品识别码
code_sizer = wx.BoxSizer(wx.HORIZONTAL)
code_sizer.Add(wx.StaticText(self.panel, label="产品识别码:"), 0, wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, 5)
self.product_code_input = wx.TextCtrl(self.panel, size=(200, -1))
code_sizer.Add(self.product_code_input, 0)
product_row_sizer.Add(code_sizer, 0, wx.ALL, 5)
form_sizer.Add(product_row_sizer, 0, wx.EXPAND|wx.ALL, 5)
# 产品名称
name_sizer = wx.BoxSizer(wx.HORIZONTAL)
name_sizer.Add(wx.StaticText(self.panel, label="产品名称:"), 0, wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, 5)
self.product_name_input = wx.TextCtrl(self.panel, size=(200, -1))
name_sizer.Add(self.product_name_input, 0)
product_row_sizer.Add(name_sizer, 0, wx.ALL, 5)
# ==== 主体区域 (参数和图片) ====
body_sizer = wx.BoxSizer(wx.HORIZONTAL)
# 左侧参数区域
params_box = wx.StaticBox(self.panel, label="产品参数")
params_sizer = wx.StaticBoxSizer(params_box, wx.VERTICAL)
# 使用网格布局
grid = wx.FlexGridSizer(cols=2, vgap=5, hgap=10)
grid.AddGrowableCol(1, 1)
# 产品外壳高度
grid.Add(wx.StaticText(self.panel, label="产品外壳高度:"), 0, wx.ALIGN_CENTER_VERTICAL)
self.shell_height_input = wx.TextCtrl(self.panel)
grid.Add(self.shell_height_input, 0, wx.EXPAND)
# PCB厂家型号
grid.Add(wx.StaticText(self.panel, label="PCB厂家型号:"), 0, wx.ALIGN_CENTER_VERTICAL)
self.pcb_model_input = wx.TextCtrl(self.panel)
grid.Add(self.pcb_model_input, 0, wx.EXPAND)
# Pin针
grid.Add(wx.StaticText(self.panel, label="Pin针:"), 0, wx.ALIGN_CENTER_VERTICAL)
self.pin_input = wx.TextCtrl(self.panel)
grid.Add(self.pin_input, 0, wx.EXPAND)
# 极性
grid.Add(wx.StaticText(self.panel, label="极性:"), 0, wx.ALIGN_CENTER_VERTICAL)
self.polarity_choice = wx.Choice(self.panel, choices=["正极", "负极", "双向"])
self.polarity_choice.SetSelection(0)
grid.Add(self.polarity_choice, 0, wx.EXPAND)
# 发光颜色
grid.Add(wx.StaticText(self.panel, label="发光颜色:"), 0, wx.ALIGN_CENTER_VERTICAL)
self.light_color_choice = wx.Choice(self.panel, choices=["红", "绿", "蓝", "白", "黄", "RGB"])
self.light_color_choice.SetSelection(0)
grid.Add(self.light_color_choice, 0, wx.EXPAND)
# 芯片类型
grid.Add(wx.StaticText(self.panel, label="芯片类型:"), 0, wx.ALIGN_CENTER_VERTICAL)
self.chip_type_input = wx.TextCtrl(self.panel)
grid.Add(self.chip_type_input, 0, wx.EXPAND)
# 芯片数量
grid.Add(wx.StaticText(self.panel, label="芯片数量:"), 0, wx.ALIGN_CENTER_VERTICAL)
self.chip_count_input = wx.SpinCtrl(self.panel, min=0, max=10000, initial=0)
grid.Add(self.chip_count_input, 0, wx.EXPAND)
# 封装要求
grid.Add(wx.StaticText(self.panel, label="封装要求:"), 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_TOP)
self.packaging_input = wx.TextCtrl(self.panel, style=wx.TE_MULTILINE, size=(-1, 60))
grid.Add(self.packaging_input, 0, wx.EXPAND)
# 产品喷码
grid.Add(wx.StaticText(self.panel, label="产品喷码:"), 0, wx.ALIGN_CENTER_VERTICAL)
self.marking_input = wx.TextCtrl(self.panel)
grid.Add(self.marking_input, 0, wx.EXPAND)
# 下单数量
order_quantity_sizer = wx.BoxSizer(wx.HORIZONTAL)
order_quantity_sizer.Add(wx.StaticText(self.panel, label="下单数量:"), 0, wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, 5)
# 优化下单数量的填写控件
self.quantity_input = wx.SpinCtrl(self.panel, min=0, max=1000000, initial=0, size=(120, -1))
order_quantity_sizer.Add(self.quantity_input, 0, wx.RIGHT, 5)
# 添加批量调整按钮
self.quantity_plus_btn = wx.Button(self.panel, label="+10", size=(50, -1))
self.quantity_plus_btn.Bind(wx.EVT_BUTTON, lambda e: self.adjust_quantity(10))
order_quantity_sizer.Add(self.quantity_plus_btn, 0, wx.RIGHT, 5)
self.quantity_minus_btn = wx.Button(self.panel, label="-10", size=(50, -1))
self.quantity_minus_btn.Bind(wx.EVT_BUTTON, lambda e: self.adjust_quantity(-10))
order_quantity_sizer.Add(self.quantity_minus_btn, 0)
grid.Add(order_quantity_sizer, 0, wx.EXPAND)
params_sizer.Add(grid, 1, wx.EXPAND|wx.ALL, 5)
body_sizer.Add(params_sizer, 1, wx.EXPAND|wx.RIGHT, 10)
# ==== 右侧图片区域 ====
image_sizer = wx.BoxSizer(wx.VERTICAL)
# PCB图片区域
pcb_box = wx.StaticBox(self.panel, label="pcb图片")
pcb_sizer = wx.StaticBoxSizer(pcb_box, wx.VERTICAL)
self.pcb_image_btn = wx.Button(self.panel, label="上传PCB图片")
self.pcb_image_btn.Bind(wx.EVT_BUTTON, lambda e: self.upload_image('pcb'))
pcb_sizer.Add(self.pcb_image_btn, 0, wx.ALL|wx.ALIGN_CENTER, 5)
self.pcb_image_display = wx.StaticBitmap(self.panel, size=(250, 180))
pcb_sizer.Add(self.pcb_image_display, 0, wx.ALIGN_CENTER|wx.ALL, 5)
# 外观图片区域
appearance_box = wx.StaticBox(self.panel, label="外观图片")
appearance_sizer = wx.StaticBoxSizer(appearance_box, wx.VERTICAL)
self.appearance_image_btn = wx.Button(self.panel, label="上传外观图片")
self.appearance_image_btn.Bind(wx.EVT_BUTTON, lambda e: self.upload_image('appearance'))
appearance_sizer.Add(self.appearance_image_btn, 0, wx.ALL|wx.ALIGN_CENTER, 5)
self.appearance_image_display = wx.StaticBitmap(self.panel, size=(250, 180))
appearance_sizer.Add(self.appearance_image_display, 0, wx.ALIGN_CENTER|wx.ALL, 5)
image_sizer.Add(pcb_sizer, 0, wx.EXPAND|wx.BOTTOM, 10)
image_sizer.Add(appearance_sizer, 0, wx.EXPAND)
body_sizer.Add(image_sizer, 0, wx.EXPAND)
form_sizer.Add(body_sizer, 0, wx.EXPAND|wx.ALL, 10)
# ==== 生产流程区域 ====
production_box = wx.StaticBox(self.panel, label="生产流程")
production_sizer = wx.StaticBoxSizer(production_box, wx.VERTICAL)
# 表头
header_grid = wx.GridSizer(1, 7, 5, 5)
header_grid.Add(wx.StaticText(self.panel, label=""), 0, wx.ALIGN_CENTER) # 左上角空白
header_grid.Add(wx.StaticText(self.panel, label="穿pin"), 0, wx.ALIGN_CENTER)
header_grid.Add(wx.StaticText(self.panel, label="固晶"), 0, wx.ALIGN_CENTER)
header_grid.Add(wx.StaticText(self.panel, label="焊线"), 0, wx.ALIGN_CENTER)
header_grid.Add(wx.StaticText(self.panel, label="半测"), 0, wx.ALIGN_CENTER)
header_grid.Add(wx.StaticText(self.panel, label="封装"), 0, wx.ALIGN_CENTER)
header_grid.Add(wx.StaticText(self.panel, label="成品"), 0, wx.ALIGN_CENTER)
production_sizer.Add(header_grid, 0, wx.EXPAND|wx.BOTTOM, 5)
# 生产数量行
quantity_grid = wx.GridSizer(1, 7, 5, 5)
quantity_grid.Add(wx.StaticText(self.panel, label="生产数量"), 0, wx.ALIGN_CENTER_VERTICAL)
self.production_quantity = []
for i in range(6):
# 优化生产数量的填写控件
sizer = wx.BoxSizer(wx.HORIZONTAL)
ctrl = wx.SpinCtrl(self.panel, min=0, max=1000000, initial=0, size=(80, -1))
sizer.Add(ctrl, 0, wx.RIGHT, 5)
# 为每个生产数量添加批量调整按钮
plus_btn = wx.Button(self.panel, label="+10", size=(40, -1))
plus_btn.Bind(wx.EVT_BUTTON, lambda e, idx=i: self.adjust_production_quantity(idx, 10))
sizer.Add(plus_btn, 0, wx.RIGHT, 5)
minus_btn = wx.Button(self.panel, label="-10", size=(40, -1))
minus_btn.Bind(wx.EVT_BUTTON, lambda e, idx=i: self.adjust_production_quantity(idx, -10))
sizer.Add(minus_btn, 0)
self.production_quantity.append(ctrl)
quantity_grid.Add(sizer, 0, wx.EXPAND)
production_sizer.Add(quantity_grid, 0, wx.EXPAND|wx.BOTTOM, 5)
# 操作工行
operator_grid = wx.GridSizer(1, 7, 5, 5)
operator_grid.Add(wx.StaticText(self.panel, label="操作工"), 0, wx.ALIGN_CENTER_VERTICAL)
self.production_operators = []
for _ in range(6):
ctrl = wx.TextCtrl(self.panel, size=(80, -1))
self.production_operators.append(ctrl)
operator_grid.Add(ctrl, 0, wx.EXPAND)
production_sizer.Add(operator_grid, 0, wx.EXPAND|wx.BOTTOM, 5)
# 日期行
date_grid = wx.GridSizer(1, 7, 5, 5)
date_grid.Add(wx.StaticText(self.panel, label="日期"), 0, wx.ALIGN_CENTER_VERTICAL)
self.production_dates = []
for _ in range(6):
ctrl = wx.TextCtrl(self.panel, size=(80, -1))
# 添加日期验证
ctrl.Bind(wx.EVT_KILL_FOCUS, lambda e, idx=_: self.validate_date(e, idx))
self.production_dates.append(ctrl)
date_grid.Add(ctrl, 0, wx.EXPAND)
production_sizer.Add(date_grid, 0, wx.EXPAND)
form_sizer.Add(production_sizer, 0, wx.EXPAND|wx.ALL, 10)
# 主布局
main_sizer.Add(btn_sizer, 0, wx.ALIGN_CENTER|wx.ALL, 10)
main_sizer.Add(order_list_sizer, 0, wx.EXPAND|wx.ALL, 10)
main_sizer.Add(form_sizer, 1, wx.EXPAND|wx.ALL, 10)
self.panel.SetSizer(main_sizer)
self.Layout()
def validate_date(self, event, idx):
"""验证日期格式"""
date_str = self.production_dates[idx].GetValue()
if date_str:
# 简单的日期格式验证,支持 YYYY-MM-DD 格式
if not re.match(r'^\d{4}-\d{2}-\d{2}$', date_str):
wx.MessageBox("日期格式不正确,请使用 YYYY-MM-DD 格式", "提示", wx.OK|wx.ICON_INFORMATION)
self.production_dates[idx].SetFocus()
event.Skip()
def upload_image(self, image_type):
"""上传图片并显示预览"""
with wx.FileDialog(self, "选择图片", wildcard="图片文件 (*.jpg;*.png)|*.jpg;*.png") as fileDialog:
if fileDialog.ShowModal() == wx.ID_CANCEL:
return
path = fileDialog.GetPath()
# 显示预览
try:
img = wx.Image(path, wx.BITMAP_TYPE_ANY)
img = img.Scale(250, 180, wx.IMAGE_QUALITY_HIGH)
bitmap = wx.Bitmap(img)
if image_type == 'pcb':
self.pcb_image_display.SetBitmap(bitmap)
else:
self.appearance_image_display.SetBitmap(bitmap)
# 存储到变量中
self.current_images[image_type] = path
except Exception as e:
wx.MessageBox(f"无法加载图片: {str(e)}", "错误", wx.OK | wx.ICON_ERROR)
def load_orders(self):
"""从数据库加载工单列表"""
self.order_list.DeleteAllItems()
cursor = self.db_conn.cursor()
cursor.execute("SELECT id, order_number, date, product_name, product_code, order_quantity FROM production_orders")
for row in cursor.fetchall():
index = self.order_list.InsertItem(0, row[1]) # order_number
self.order_list.SetItem(index, 1, row[2]) # date
self.order_list.SetItem(index, 2, row[3]) # product_name
self.order_list.SetItem(index, 3, row[4]) # product_code
self.order_list.SetItem(index, 4, str(row[5])) # order_quantity
self.order_list.SetItemData(index, row[0]) # 存储数据库ID到项中
def new_order(self, event):
"""创建新工单"""
self.current_order_id = None
self.clear_form()
self.date_input.SetValue(datetime.date.today().strftime("%Y-%m-%d"))
self.order_num_input.SetValue(self.generate_order_number())
self.current_images = {'pcb': None, 'appearance': None}
def clear_form(self):
"""清除表单数据"""
# 清除所有输入控件的内容
self.product_name_input.SetValue("")
self.product_code_input.SetValue("")
self.shell_height_input.SetValue("")
self.pcb_model_input.SetValue("")
self.pin_input.SetValue("")
self.chip_type_input.SetValue("")
self.marking_input.SetValue("")
self.packaging_input.SetValue("")
self.polarity_choice.SetSelection(0)
self.light_color_choice.SetSelection(0)
self.chip_count_input.SetValue(0)
self.quantity_input.SetValue(0)
# 清除图片
self.pcb_image_display.SetBitmap(wx.NullBitmap)
self.appearance_image_display.SetBitmap(wx.NullBitmap)
self.current_images = {'pcb': None, 'appearance': None}
# 清除生产流程表格
for ctrl in self.production_quantity:
ctrl.SetValue(0)
for ctrl in self.production_operators:
ctrl.SetValue("")
for ctrl in self.production_dates:
ctrl.SetValue("")
def on_order_select(self, event):
"""当选择工单时加载详细信息"""
item_index = event.GetIndex()
self.current_order_id = self.order_list.GetItemData(item_index)
cursor = self.db_conn.cursor()
cursor.execute("SELECT * FROM production_orders WHERE id=?", (self.current_order_id,))
order_data = cursor.fetchone()
if order_data:
# 填充基本信息
self.date_input.SetValue(order_data[2])
self.order_num_input.SetValue(order_data[1])
self.product_name_input.SetValue(order_data[3])
self.product_code_input.SetValue(order_data[4])
self.shell_height_input.SetValue(str(order_data[5]) if order_data[5] else "")
self.pcb_model_input.SetValue(order_data[6] if order_data[6] else "")
self.pin_input.SetValue(order_data[7] if order_data[7] else "")
# 设置下拉选择框
polarity_options = ["正极", "负极", "双向"]
if order_data[8] in polarity_options:
self.polarity_choice.SetSelection(polarity_options.index(order_data[8]))
light_color_options = ["红", "绿", "蓝", "白", "黄", "RGB"]
if order_data[9] in light_color_options:
self.light_color_choice.SetSelection(light_color_options.index(order_data[9]))
self.chip_type_input.SetValue(order_data[10] if order_data[10] else "")
self.chip_count_input.SetValue(order_data[11] if order_data[11] else 0)
self.packaging_input.SetValue(order_data[12] if order_data[12] else "")
self.marking_input.SetValue(order_data[13] if order_data[13] else "")
self.quantity_input.SetValue(order_data[14] if order_data[14] else 0)
# 加载图片预览
if order_data[15]: # PCB图片
self.load_image_preview(order_data[15], 'pcb')
if order_data[16]: # 外观图片
self.load_image_preview(order_data[16], 'appearance')
# 加载生产流程数据
self.load_production_steps()
def load_image_preview(self, image_data, image_type):
"""从二进制数据加载图片预览"""
try:
# 创建临时文件
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp:
tmp.write(image_data)
tmp_path = tmp.name
# 加载并缩放图片
img = wx.Image(tmp_path, wx.BITMAP_TYPE_ANY)
img = img.Scale(250, 180, wx.IMAGE_QUALITY_HIGH)
bitmap = wx.Bitmap(img)
# 显示图片
if image_type == 'pcb':
self.pcb_image_display.SetBitmap(bitmap)
self.current_images['pcb'] = tmp_path
else:
self.appearance_image_display.SetBitmap(bitmap)
self.current_images['appearance'] = tmp_path
# 不需要时删除临时文件
wx.CallLater(5000, os.remove, tmp_path)
except Exception as e:
wx.LogError(f"无法加载图片预览: {str(e)}")
def load_production_steps(self):
"""从数据库加载生产流程数据"""
if not self.current_order_id:
return
cursor = self.db_conn.cursor()
cursor.execute("SELECT * FROM production_steps WHERE order_id=?", (self.current_order_id,))
production_steps = cursor.fetchall()
# 按步骤分组
step_values = {}
for step in production_steps:
step_name = step[2]
step_type = step[3]
value = step[4]
if step_name not in step_values:
step_values[step_name] = {}
step_values[step_name][step_type] = value
# 工序顺序
step_order = ["穿pin", "固晶", "焊线", "半测", "封装", "成品"]
# 填充表格
for step_idx, step_name in enumerate(step_order):
if step_name in step_values:
# 生产数量
if "quantity" in step_values[step_name]:
self.production_quantity[step_idx].SetValue(
int(step_values[step_name]["quantity"]))
# 操作工
if "operator" in step_values[step_name]:
self.production_operators[step_idx].SetValue(
step_values[step_name]["operator"])
# 日期
if "date" in step_values[step_name]:
self.production_dates[step_idx].SetValue(
step_values[step_name]["date"])
def adjust_quantity(self, amount):
"""调整下单数量"""
current_value = self.quantity_input.GetValue()
new_value = max(0, current_value + amount)
self.quantity_input.SetValue(new_value)
def adjust_production_quantity(self, index, amount):
"""调整生产流程中的数量"""
current_value = self.production_quantity[index].GetValue()
new_value = max(0, current_value + amount)
self.production_quantity[index].SetValue(new_value)
def save_order(self, event):
"""保存工单到数据库"""
# 验证必填字段
if not self.product_name_input.GetValue():
wx.MessageBox("产品名称不能为空!", "错误", wx.OK|wx.ICON_ERROR)
return
if not self.product_code_input.GetValue():
wx.MessageBox("产品识别码不能为空!", "错误", wx.OK|wx.ICON_ERROR)
return
if not self.quantity_input.GetValue():
wx.MessageBox("下单数量不能为空!", "错误", wx.OK|wx.ICON_ERROR)
return
cursor = self.db_conn.cursor()
order_number = self.order_num_input.GetValue()
# 加载图片数据
pcb_image_data = None
appearance_image_data = None
if 'pcb' in self.current_images and self.current_images['pcb']:
try:
with open(self.current_images['pcb'], 'rb') as f:
pcb_image_data = f.read()
except Exception as e:
wx.MessageBox(f"无法读取PCB图片: {str(e)}", "错误", wx.OK|wx.ICON_ERROR)
return
if 'appearance' in self.current_images and self.current_images['appearance']:
try:
with open(self.current_images['appearance'], 'rb') as f:
appearance_image_data = f.read()
except Exception as e:
wx.MessageBox(f"无法读取外观图片: {str(e)}", "错误", wx.OK|wx.ICON_ERROR)
return
try:
# 保存工单主表
if self.current_order_id is None:
# 新增工单
cursor.execute('''
INSERT INTO production_orders (
order_number, date, product_name, product_code, shell_height, pcb_model,
pin, polarity, light_color, chip_type, chip_count, packaging_requirements,
product_marking, order_quantity, pcb_image, appearance_image
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
order_number,
self.date_input.GetValue(),
self.product_name_input.GetValue(),
self.product_code_input.GetValue(),
self.shell_height_input.GetValue() or None,
self.pcb_model_input.GetValue() or None,
self.pin_input.GetValue() or None,
self.polarity_choice.GetStringSelection(),
self.light_color_choice.GetStringSelection(),
self.chip_type_input.GetValue() or None,
self.chip_count_input.GetValue(),
self.packaging_input.GetValue() or None,
self.marking_input.GetValue() or None,
self.quantity_input.GetValue(),
pcb_image_data,
appearance_image_data
))
self.current_order_id = cursor.lastrowid
else:
# 更新工单
cursor.execute('''
UPDATE production_orders SET
date = ?,
product_name = ?,
product_code = ?,
shell_height = ?,
pcb_model = ?,
pin = ?,
polarity = ?,
light_color = ?,
chip_type = ?,
chip_count = ?,
packaging_requirements = ?,
product_marking = ?,
order_quantity = ?,
pcb_image = ?,
appearance_image = ?
WHERE id = ?
''', (
self.date_input.GetValue(),
self.product_name_input.GetValue(),
self.product_code_input.GetValue(),
self.shell_height_input.GetValue() or None,
self.pcb_model_input.GetValue() or None,
self.pin_input.GetValue() or None,
self.polarity_choice.GetStringSelection(),
self.light_color_choice.GetStringSelection(),
self.chip_type_input.GetValue() or None,
self.chip_count_input.GetValue(),
self.packaging_input.GetValue() or None,
self.marking_input.GetValue() or None,
self.quantity_input.GetValue(),
pcb_image_data,
appearance_image_data,
self.current_order_id
))
# 保存生产流程
# 先删除现有记录
cursor.execute("DELETE FROM production_steps WHERE order_id = ?", (self.current_order_id,))
# 添加新记录
step_names = ["穿pin", "固晶", "焊线", "半测", "封装", "成品"]
for step_idx in range(6): # 6个工序
# 保存生产数量
quantity = self.production_quantity[step_idx].GetValue()
if quantity > 0:
cursor.execute('''
INSERT INTO production_steps (order_id, step_name, step_type, value)
VALUES (?, ?, ?, ?)
''', (
self.current_order_id,
step_names[step_idx],
"quantity",
str(quantity)
))
# 保存操作工
operator = self.production_operators[step_idx].GetValue()
if operator:
cursor.execute('''
INSERT INTO production_steps (order_id, step_name, step_type, value)
VALUES (?, ?, ?, ?)
''', (
self.current_order_id,
step_names[step_idx],
"operator",
operator
))
# 保存日期
date = self.production_dates[step_idx].GetValue()
if date:
# 验证日期格式
if not re.match(r'^\d{4}-\d{2}-\d{2}$', date):
wx.MessageBox(f"日期格式不正确: {date},请使用 YYYY-MM-DD 格式", "错误", wx.OK|wx.ICON_ERROR)
continue
cursor.execute('''
INSERT INTO production_steps (order_id, step_name, step_type, value)
VALUES (?, ?, ?, ?)
''', (
self.current_order_id,
step_names[step_idx],
"date",
date
))
self.db_conn.commit()
wx.MessageBox("工单保存成功!", "成功", wx.OK|wx.ICON_INFORMATION)
self.load_orders()
except sqlite3.IntegrityError as e:
wx.MessageBox(f"保存失败: {str(e)}", "错误", wx.OK|wx.ICON_ERROR)
except Exception as e:
wx.MessageBox(f"保存过程中发生错误: {str(e)}", "错误", wx.OK|wx.ICON_ERROR)
def delete_order(self, event):
"""删除当前选中的工单"""
if self.current_order_id is None:
wx.MessageBox("请先选择要删除的工单", "提示", wx.OK|wx.ICON_INFORMATION)
return
confirm = wx.MessageBox("确定要删除此工单吗?", "确认删除", wx.YES_NO|wx.ICON_QUESTION)
if confirm == wx.YES:
cursor = self.db_conn.cursor()
try:
cursor.execute("DELETE FROM production_steps WHERE order_id = ?", (self.current_order_id,))
cursor.execute("DELETE FROM production_orders WHERE id = ?", (self.current_order_id,))
self.db_conn.commit()
wx.MessageBox("工单已删除", "成功", wx.OK|wx.ICON_INFORMATION)
self.load_orders()
self.clear_form()
self.current_order_id = None
except Exception as e:
wx.MessageBox(f"删除失败: {str(e)}", "错误", wx.OK|wx.ICON_ERROR)
def export_pdf(self, event):
"""导出当前工单为PDF文件"""
if self.current_order_id is None:
wx.MessageBox("请先选择或创建工单", "提示", wx.OK|wx.ICON_INFORMATION)
return
# 选择保存路径
with wx.FileDialog(self, "保存PDF文件", wildcard="PDF文件 (*.pdf)|*.pdf",
defaultFile=f"{self.order_num_input.GetValue()}.pdf",
style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) as dlg:
if dlg.ShowModal() == wx.ID_CANCEL:
return
save_path = dlg.GetPath()
# 生成PDF
self.generate_pdf(save_path)
wx.MessageBox(f"PDF已成功导出到:\n{save_path}", "导出成功", wx.OK|wx.ICON_INFORMATION)
def initialize_pdf_styles(self):
"""初始化PDF样式,确保只创建一次"""
if self.pdf_styles is not None:
return
self.pdf_styles = getSampleStyleSheet()
# 检查并添加自定义样式(如果不存在)
if 'Heading1' not in self.pdf_styles:
self.pdf_styles.add(ParagraphStyle(
name='Heading1',
fontName='SimHei',
fontSize=16,
leading=24
))
if 'Heading3' not in self.pdf_styles:
self.pdf_styles.add(ParagraphStyle(
name='Heading3',
fontName='SimHei',
fontSize=12,
leading=16
))
if 'Normal' not in self.pdf_styles:
self.pdf_styles.add(ParagraphStyle(
name='Normal',
fontName='SimHei',
fontSize=10,
leading=14
))
def generate_pdf(self, save_path):
"""生成PDF文件"""
# 初始化样式(确保只初始化一次)
self.initialize_pdf_styles()
# 创建PDF文档
doc = SimpleDocTemplate(save_path, pagesize=A4, topMargin=1*cm, bottomMargin=1*cm)
styles = self.pdf_styles
elements = []
# 修改标题样式的字体
heading_style = styles['Heading1']
heading_style.fontName = 'SimHei'
# 标题
title = Paragraph("生产工单", styles['Heading1'])
elements.append(title)
# 获取工单数据
cursor = self.db_conn.cursor()
cursor.execute("SELECT * FROM production_orders WHERE id = ?", (self.current_order_id,))
order_data = cursor.fetchone()
if not order_data:
wx.MessageBox("找不到工单数据", "错误", wx.OK|wx.ICON_ERROR)
return
# 基本信息表格 - 优化表头样式,统一为第二个表格的样式
info_data = [
["日期", order_data[2], "工单号", order_data[1]],
["产品名称", order_data[3], "产品识别码", order_data[4]],
["产品外壳高度", order_data[5] or "", "下单数量", str(order_data[14])],
["PCB厂家型号", order_data[6] or "", "Pin针", order_data[7] or ""],
["极性", order_data[8] or "", "发光颜色", order_data[9] or ""],
["芯片类型", order_data[10] or "", "芯片数量", str(order_data[11])],
["封装要求", order_data[12] or "", "产品喷码", order_data[13] or ""],
]
# 统一为第二个表格的样式
info_table = Table(info_data, colWidths=[3*cm, 5*cm, 3*cm, 5*cm]) # 保持原列宽
info_table.setStyle(TableStyle([
('GRID', (0,0), (-1,-1), 0.5, colors.grey), # 细化网格线
('BACKGROUND', (0,0), (-1,0), colors.lightgrey), # 表头背景色
('TEXTCOLOR', (0,0), (-1,0), colors.black), # 表头文字颜色
('ALIGN', (0,0), (-1,-1), 'CENTER'), # 居中对齐
('FONTNAME', (0,0), (-1,0), 'SimHei'), # 表头字体
('FONTSIZE', (0,0), (-1,0), 10), # 表头字体大小
('BOTTOMPADDING', (0,0), (-1,0), 6), # 表头底部间距
('TOPPADDING', (0,0), (-1,0), 6), # 表头顶部间距
('TEXTCOLOR', (0,1), (-1,-1), colors.black), # 表格内容文字颜色
('BACKGROUND', (0,1), (-1,1), colors.whitesmoke), # 第一行内容背景色
('BACKGROUND', (0,2), (-1,2), colors.white), # 第二行内容背景色
('BACKGROUND', (0,3), (-1,3), colors.whitesmoke), # 第三行内容背景色
('BACKGROUND', (0,4), (-1,4), colors.white), # 第四行内容背景色
('BACKGROUND', (0,5), (-1,5), colors.whitesmoke), # 第五行内容背景色
('BACKGROUND', (0,6), (-1,6), colors.white), # 第六行内容背景色
('BACKGROUND', (0,7), (-1,7), colors.whitesmoke), # 第七行内容背景色
('FONTNAME', (0,1), (-1,-1), 'SimHei'), # 内容字体
]))
elements.append(info_table)
# 添加一些间距
elements.append(Paragraph("\n", styles['Normal']))
# 生产流程表格 - 保持原样式
step_names = ["", "穿pin", "固晶", "焊线", "半测", "封装", "成品"]
step_data = [step_names]
# 获取生产流程数据
cursor.execute("SELECT * FROM production_steps WHERE order_id = ?", (self.current_order_id,))
all_steps = cursor.fetchall()
# 按步骤分组
step_values = {}
for step in all_steps:
step_name = step[2]
step_type = step[3]
value = step[4]
if step_name not in step_values:
step_values[step_name] = {}
step_values[step_name][step_type] = value
# 生产数量行
quantity_row = ["生产数量"]
for step in step_names[1:]:
if step in step_values and "quantity" in step_values[step]:
quantity_row.append(str(step_values[step]["quantity"]))
else:
quantity_row.append("")
# 操作工行
operator_row = ["操作工"]
for step in step_names[1:]:
if step in step_values and "operator" in step_values[step]:
operator_row.append(step_values[step]["operator"])
else:
operator_row.append("")
# 日期行
date_row = ["日期"]
for step in step_names[1:]:
if step in step_values and "date" in step_values[step]:
date_row.append(step_values[step]["date"])
else:
date_row.append("")
step_data.extend([quantity_row, operator_row, date_row])
# 生产流程表格样式
step_table = Table(step_data, colWidths=[2.5*cm] + [3*cm] * 6) # 调整列宽
step_table.setStyle(TableStyle([
('GRID', (0,0), (-1,-1), 0.5, colors.grey), # 细化网格线
('BACKGROUND', (0,0), (-1,0), colors.lightgrey), # 表头背景色
('TEXTCOLOR', (0,0), (-1,0), colors.black), # 表头文字颜色
('ALIGN', (0,0), (-1,-1), 'CENTER'), # 居中对齐
('FONTNAME', (0,0), (-1,0), 'SimHei'), # 表头字体
('FONTSIZE', (0,0), (-1,0), 10), # 表头字体大小
('BOTTOMPADDING', (0,0), (-1,0), 6), # 表头底部间距
('TOPPADDING', (0,0), (-1,0), 6), # 表头顶部间距
('TEXTCOLOR', (0,1), (-1,-1), colors.black), # 表格内容文字颜色
('BACKGROUND', (0,1), (-1,1), colors.whitesmoke), # 第一行内容背景色
('BACKGROUND', (0,2), (-1,2), colors.white), # 第二行内容背景色
('BACKGROUND', (0,3), (-1,3), colors.whitesmoke), # 第三行内容背景色
('FONTNAME', (0,1), (-1,-1), 'SimHei'), # 内容字体
]))
elements.append(step_table)
# 添加一些间距
elements.append(Paragraph("\n", styles['Normal']))
# 添加图片
try:
# 添加PCB图片
if order_data[15]:
elements.append(Paragraph("PCB图片:", styles['Heading3']))
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp:
tmp.write(order_data[15])
tmp_path = tmp.name
elements.append(Image(tmp_path, width=15*cm, height=10*cm, hAlign='CENTER'))
wx.CallLater(5000, os.remove, tmp_path)
# 添加外观图片
if order_data[16]:
elements.append(Paragraph("外观图片:", styles['Heading3']))
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp:
tmp.write(order_data[16])
tmp_path = tmp.name
elements.append(Image(tmp_path, width=15*cm, height=10*cm, hAlign='CENTER'))
wx.CallLater(5000, os.remove, tmp_path)
except Exception as e:
print(f"添加图片时出错: {str(e)}")
# 添加页脚,使用中文字体
footer = Paragraph(f"生成日期: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
styles['Normal'])
elements.append(footer)
# 生成PDF
doc.build(elements)
if __name__ == "__main__":
app = wx.App()
ProductionOrderApp(None, "工单管理系统")
app.MainLoop()
将pdf风格改为黑白简约风