This commit is contained in:
2025-12-23 00:30:36 +08:00
parent 192c05707a
commit 033a1acef3
16 changed files with 2870 additions and 1342 deletions

504
main.py Normal file
View File

@@ -0,0 +1,504 @@
"""
服装布料计算管理器 - 专业版(重构版主程序)
- 模块化设计,代码分离
- 所有功能完整可用
"""
import sys
import os
from datetime import datetime
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QGridLayout, QHBoxLayout,
QLabel, QLineEdit, QPushButton, QComboBox, QTextEdit, QMessageBox,
QGroupBox, QDoubleSpinBox, QSpinBox, QDialog, QScrollArea
)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont
# 导入自定义模块
from database import DatabaseManager, get_db_connection
from login_dialog import LoginDialog
from stock_dialog import StockInDialog
from raw_material_dialog import RawMaterialLibraryDialog
from garment_dialogs import GarmentLibraryDialog
from purchase_order_dialog import PurchaseOrderDialog
class FabricManager(QMainWindow):
"""主应用程序窗口"""
def __init__(self, is_admin=False):
super().__init__()
self.is_admin = is_admin
# 设置数据库路径
exe_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(__file__)
self.db_path = os.path.join(exe_dir, "fabric_library.db")
# 初始化数据库
self.db_manager = DatabaseManager(self.db_path)
# 设置窗口
mode_text = "(管理员模式)" if is_admin else "(普通模式)"
self.setWindowTitle("服装布料计算管理器 - 专业版 " + mode_text)
self.resize(1300, 800)
# 设置样式
self.setStyleSheet("""
QMainWindow { background-color: #f0fff8; }
QGroupBox { font-weight: bold; color: #2e8b57; border: 2px solid #90ee90; border-radius: 10px; margin-top: 10px; padding-top: 8px; background-color: #ffffff; }
QPushButton { background-color: #4caf50; color: white; padding: 10px; border-radius: 8px; font-size: 13px; font-weight: bold; }
QTextEdit { background-color: #e8f5e9; border: 2px solid #a5d6a7; border-radius: 8px; padding: 10px; font-size: 13px; }
QLineEdit, QDoubleSpinBox, QSpinBox, QComboBox { padding: 6px; border: 2px solid #a5d6a7; border-radius: 6px; font-size: 13px; }
QLabel { font-size: 13px; color: #2e8b57; }
""")
self.init_ui()
self.load_garment_list()
def get_conn(self):
"""获取数据库连接"""
return get_db_connection(self.db_path)
def init_ui(self):
"""初始化用户界面"""
scroll = QScrollArea()
scroll.setWidgetResizable(True)
central_widget = QWidget()
scroll.setWidget(central_widget)
self.setCentralWidget(scroll)
main_layout = QVBoxLayout(central_widget)
# 标题
title = QLabel("服装布料计算管理器 - 专业版")
title.setFont(QFont("Microsoft YaHei", 18, QFont.Bold))
title.setAlignment(Qt.AlignCenter)
title.setStyleSheet("color: #228b22; padding: 15px;")
main_layout.addWidget(title)
# 顶部按钮区域
top_buttons = QHBoxLayout()
guide_btn = QPushButton("📖 查看使用说明")
guide_btn.clicked.connect(self.show_guide)
top_buttons.addWidget(guide_btn)
garment_btn = QPushButton("👔 衣服库管理")
garment_btn.clicked.connect(self.open_garment_library)
top_buttons.addWidget(garment_btn)
library_btn = QPushButton("🗄️ 原料库管理")
library_btn.clicked.connect(self.open_library)
top_buttons.addWidget(library_btn)
stock_in_btn = QPushButton("📦 快速原料入库")
stock_in_btn.clicked.connect(self.quick_stock_in)
top_buttons.addWidget(stock_in_btn)
top_buttons.addStretch()
main_layout.addLayout(top_buttons)
# 主要内容区域
content_layout = QHBoxLayout()
# 左侧区域
left_widget = QWidget()
left_layout = QVBoxLayout(left_widget)
# 批量计算组
calc_group = QGroupBox("批量计算(按衣服款号)")
calc_layout = QGridLayout()
calc_layout.setVerticalSpacing(12)
calc_layout.addWidget(QLabel("选择衣服款号:"), 0, 0, Qt.AlignRight)
self.garment_combo = QComboBox()
self.garment_combo.setEditable(True)
self.garment_combo.currentIndexChanged.connect(self.load_garment_materials)
calc_layout.addWidget(self.garment_combo, 0, 1, 1, 2)
calc_layout.addWidget(QLabel("生产件数:"), 1, 0, Qt.AlignRight)
self.quantity_input = QSpinBox()
self.quantity_input.setRange(1, 1000000)
self.quantity_input.setValue(1000)
calc_layout.addWidget(self.quantity_input, 1, 1)
calc_layout.addWidget(QLabel("损耗率 (%):"), 2, 0, Qt.AlignRight)
self.loss_input = QDoubleSpinBox()
self.loss_input.setRange(0, 50)
self.loss_input.setValue(5)
calc_layout.addWidget(self.loss_input, 2, 1)
calc_btn = QPushButton("计算本次总用量")
calc_btn.clicked.connect(self.load_garment_materials)
calc_layout.addWidget(calc_btn, 3, 0, 1, 3)
calc_group.setLayout(calc_layout)
left_layout.addWidget(calc_group)
# 结果显示组
result_group = QGroupBox("本次生产用量明细")
result_layout = QVBoxLayout()
self.result_text = QTextEdit()
self.result_text.setReadOnly(True)
self.result_text.setMinimumHeight(400)
result_layout.addWidget(self.result_text)
# 操作按钮
btn_layout = QHBoxLayout()
po_btn = QPushButton("生成采购单")
po_btn.clicked.connect(self.generate_purchase_order)
po_btn.setStyleSheet("background-color: #ff9800; font-weight: bold; padding: 12px;")
btn_layout.addWidget(po_btn)
record_btn = QPushButton("记录本次消耗到库存")
record_btn.clicked.connect(self.record_current_consumption)
record_btn.setStyleSheet("background-color: #e91e63; color: white; font-weight: bold; padding: 12px;")
btn_layout.addWidget(record_btn)
result_layout.addLayout(btn_layout)
result_group.setLayout(result_layout)
left_layout.addWidget(result_group)
content_layout.addWidget(left_widget, stretch=3)
# 右侧单位换算计算器
right_group = QGroupBox("单位换算计算器")
right_layout = QGridLayout()
right_layout.setVerticalSpacing(10)
right_layout.addWidget(QLabel("米数:"), 0, 0, Qt.AlignRight)
self.calc_m = QDoubleSpinBox()
self.calc_m.setRange(0, 100000)
self.calc_m.setDecimals(3)
self.calc_m.valueChanged.connect(self.convert_units)
right_layout.addWidget(self.calc_m, 0, 1)
right_layout.addWidget(QLabel("码数:"), 1, 0, Qt.AlignRight)
self.calc_yard = QDoubleSpinBox()
self.calc_yard.setRange(0, 100000)
self.calc_yard.setDecimals(3)
self.calc_yard.valueChanged.connect(self.convert_units)
right_layout.addWidget(self.calc_yard, 1, 1)
right_layout.addWidget(QLabel("公斤:"), 2, 0, Qt.AlignRight)
self.calc_kg = QDoubleSpinBox()
self.calc_kg.setRange(0, 100000)
self.calc_kg.setDecimals(6)
self.calc_kg.valueChanged.connect(self.convert_units)
right_layout.addWidget(self.calc_kg, 2, 1)
right_layout.addWidget(QLabel("幅宽 (cm):"), 3, 0, Qt.AlignRight)
self.calc_width = QDoubleSpinBox()
self.calc_width.setRange(50, 300)
self.calc_width.setValue(150)
self.calc_width.valueChanged.connect(self.convert_units)
right_layout.addWidget(self.calc_width, 3, 1)
right_layout.addWidget(QLabel("克重 (g/m²):"), 4, 0, Qt.AlignRight)
self.calc_gsm = QDoubleSpinBox()
self.calc_gsm.setRange(50, 1000)
self.calc_gsm.setValue(200)
self.calc_gsm.valueChanged.connect(self.convert_units)
right_layout.addWidget(self.calc_gsm, 4, 1)
right_group.setLayout(right_layout)
content_layout.addWidget(right_group, stretch=1)
main_layout.addLayout(content_layout)
main_layout.addStretch()
def quick_stock_in(self):
"""快速入库"""
dialog = StockInDialog(self.db_path)
dialog.exec_()
def generate_purchase_order(self):
"""生成采购单"""
style_number = self.garment_combo.currentText().strip()
if not style_number:
QMessageBox.warning(self, "提示", "请先选择或计算一款号!")
return
quantity = self.quantity_input.value()
loss_rate = self.loss_input.value() / 100
dialog = PurchaseOrderDialog(self.db_path, style_number, quantity, loss_rate)
dialog.exec_()
def record_current_consumption(self):
"""记录当前消耗"""
style_number = self.garment_combo.currentText().strip()
if not style_number:
QMessageBox.warning(self, "提示", "请先选择一款号并计算用量!")
return
quantity = self.quantity_input.value()
loss_rate = self.loss_input.value() / 100
try:
with self.get_conn() as conn:
cursor = conn.execute('''
SELECT category, fabric_type, usage_per_piece, unit
FROM garment_materials
WHERE style_number = ?
''', (style_number,))
rows = cursor.fetchall()
inserted = 0
for category, fabric_type, usage_per_piece, unit in rows:
if usage_per_piece == 0:
continue
# 获取该原料在入库时使用的单位
stock_cursor = conn.execute('''
SELECT unit FROM fabric_stock_in
WHERE model = ?
ORDER BY purchase_date DESC
LIMIT 1
''', (category,))
stock_unit_row = stock_cursor.fetchone()
# 如果有入库记录,使用入库单位;否则使用原来的单位
if stock_unit_row:
stock_unit = stock_unit_row[0]
# 如果单位不同,需要转换
if unit != stock_unit:
consume_qty = self.convert_unit_value(usage_per_piece * quantity * (1 + loss_rate), unit, stock_unit, category)
final_unit = stock_unit
else:
consume_qty = usage_per_piece * quantity * (1 + loss_rate)
final_unit = unit
else:
# 没有入库记录,使用原单位
consume_qty = usage_per_piece * quantity * (1 + loss_rate)
final_unit = unit
conn.execute('''
INSERT INTO fabric_consumption
(style_number, model, single_usage, quantity_made, loss_rate, consume_quantity, consume_date, unit)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (style_number, category, usage_per_piece, quantity, loss_rate, consume_qty, datetime.now().strftime('%Y-%m-%d'), final_unit))
inserted += 1
conn.commit()
if inserted > 0:
QMessageBox.information(self, "成功", "本次生产消耗已记录到库存!")
else:
QMessageBox.information(self, "提示", "本次没有可记录的原料消耗")
except Exception as e:
QMessageBox.critical(self, "错误", str(e))
def open_library(self):
"""打开原料库"""
try:
dialog = RawMaterialLibraryDialog(self.db_path, self.is_admin)
dialog.exec_()
except Exception as e:
QMessageBox.critical(self, "错误", f"打开原料库失败: {str(e)}")
def open_garment_library(self):
"""打开服装库"""
try:
dialog = GarmentLibraryDialog(self.db_path)
dialog.exec_()
self.load_garment_list()
except Exception as e:
QMessageBox.critical(self, "错误", f"打开衣服库失败: {str(e)}")
def load_garment_list(self):
"""加载服装列表"""
current = self.garment_combo.currentText()
self.garment_combo.blockSignals(True)
self.garment_combo.clear()
self.garment_combo.addItem("")
try:
with self.get_conn() as conn:
cursor = conn.execute("SELECT style_number FROM garments ORDER BY style_number")
for row in cursor.fetchall():
self.garment_combo.addItem(row[0])
except:
pass
self.garment_combo.blockSignals(False)
if current:
self.garment_combo.setCurrentText(current)
def load_garment_materials(self):
"""加载服装材料"""
style_number = self.garment_combo.currentText().strip()
if not style_number:
self.result_text.clear()
return
qty = self.quantity_input.value()
loss = self.loss_input.value() / 100
text = f"款号: {style_number}\n生产件数: {qty}\n损耗率: {self.loss_input.value()}%\n\n"
try:
with self.get_conn() as conn:
cursor = conn.execute("SELECT category, fabric_type, usage_per_piece, unit FROM garment_materials WHERE style_number = ? ORDER BY id", (style_number,))
for category, fabric_type, usage, unit in cursor.fetchall():
if usage:
total = usage * qty * (1 + loss)
# 显示材料名称(如果有类型则显示类目-类型,否则只显示类目)
material_name = f"{category}-{fabric_type}" if fabric_type else category
text += f"{material_name}\n单件: {usage:.3f} {unit}\n总用量: {total:.3f} {unit}\n\n"
except Exception as e:
text += "计算失败: " + str(e)
self.result_text.setText(text)
def show_guide(self):
"""显示使用说明"""
guide_text = """
【服装布料计算管理器 - 专业版 详细使用说明】
• 启动时弹出登录界面,默认密码均为 123456
• 管理员可修改密码
• 原料库支持大类/子类/供应商多条件筛选
• 胸杯自动归类到"辅料",单位固定"一对"
• 衣服编辑支持从原料库选择具体型号(可选),自动填充单位
• 支持材料行上移/下移/删除
• 主界面支持:
- 批量计算用量(含损耗)
- 生成采购单复制或保存TXT
- "记录本次消耗到库存":自动记录生产消耗
• 原料库"库存跟踪"Tab
- 显示每种原料的总采购、总消耗、当前剩余
- 查看明细(入库和消耗历史)
- 一键清零剩余(盘点用完时使用)
• 在"原料入库管理"或原料库可多次记录采购入库
祝使用愉快!
"""
dialog = QDialog(self)
dialog.setWindowTitle("详细使用说明")
dialog.resize(800, 700)
layout = QVBoxLayout(dialog)
text_area = QTextEdit()
text_area.setReadOnly(True)
text_area.setFont(QFont("Microsoft YaHei", 12))
text_area.setText(guide_text)
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setWidget(text_area)
layout.addWidget(scroll)
close_btn = QPushButton("关闭")
close_btn.clicked.connect(dialog.accept)
layout.addWidget(close_btn)
dialog.exec_()
def convert_unit_value(self, value, from_unit, to_unit, fabric_model=None):
"""单位转换函数:将数值从一个单位转换为另一个单位"""
if from_unit == to_unit:
return value
# 米 <-> 码 转换
if from_unit == "" and to_unit == "":
return value / 0.9144
elif from_unit == "" and to_unit == "":
return value * 0.9144
# 长度单位转换为重量单位(需要面料的幅宽和克重信息)
elif (from_unit in ["", ""] and to_unit == "公斤") or (from_unit == "公斤" and to_unit in ["", ""]):
if fabric_model:
try:
with self.get_conn() as conn:
cursor = conn.execute("SELECT width, gsm FROM fabrics WHERE model = ?", (fabric_model,))
fabric_info = cursor.fetchone()
if fabric_info and fabric_info[0] and fabric_info[1]:
width, gsm = fabric_info
if width > 0 and gsm > 0:
if from_unit == "" and to_unit == "公斤":
# 米转公斤:米数 * 幅宽(m) * 克重(kg/m²)
return value * (width / 100) * (gsm / 1000)
elif from_unit == "" and to_unit == "公斤":
# 码转公斤:先转米,再转公斤
meters = value * 0.9144
return meters * (width / 100) * (gsm / 1000)
elif from_unit == "公斤" and to_unit == "":
# 公斤转米:公斤 / (幅宽(m) * 克重(kg/m²))
return value / ((width / 100) * (gsm / 1000))
elif from_unit == "公斤" and to_unit == "":
# 公斤转码:先转米,再转码
meters = value / ((width / 100) * (gsm / 1000))
return meters / 0.9144
except Exception:
pass
# 如果无法转换,返回原值
return value
def convert_units(self):
"""单位换算"""
sender = self.sender()
try:
if sender == self.calc_m:
m = self.calc_m.value()
self.calc_yard.blockSignals(True)
self.calc_yard.setValue(m / 0.9144)
self.calc_yard.blockSignals(False)
weight = (m * self.calc_width.value() / 100 * self.calc_gsm.value()) / 1000
self.calc_kg.blockSignals(True)
self.calc_kg.setValue(weight)
self.calc_kg.blockSignals(False)
elif sender == self.calc_yard:
yard = self.calc_yard.value()
m = yard * 0.9144
self.calc_m.blockSignals(True)
self.calc_m.setValue(m)
self.calc_m.blockSignals(False)
weight = (m * self.calc_width.value() / 100 * self.calc_gsm.value()) / 1000
self.calc_kg.blockSignals(True)
self.calc_kg.setValue(weight)
self.calc_kg.blockSignals(False)
elif sender == self.calc_kg:
kg = self.calc_kg.value()
if self.calc_width.value() > 0 and self.calc_gsm.value() > 0:
m = (kg * 1000 * 100) / (self.calc_width.value() * self.calc_gsm.value())
self.calc_m.blockSignals(True)
self.calc_m.setValue(m)
self.calc_m.blockSignals(False)
self.calc_yard.blockSignals(True)
self.calc_yard.setValue(m / 0.9144)
self.calc_yard.blockSignals(False)
elif sender == self.calc_width or sender == self.calc_gsm:
# 当幅宽或克重改变时,重新计算公斤数(基于当前米数)
m = self.calc_m.value()
if m > 0:
weight = (m * self.calc_width.value() / 100 * self.calc_gsm.value()) / 1000
self.calc_kg.blockSignals(True)
self.calc_kg.setValue(weight)
self.calc_kg.blockSignals(False)
except Exception:
pass
def main():
"""主函数"""
app = QApplication(sys.argv)
# 设置数据库路径
exe_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(__file__)
db_path = os.path.join(exe_dir, "fabric_library.db")
# 显示登录对话框
login = LoginDialog(db_path)
if login.exec_() == QDialog.Accepted:
# 创建并显示主窗口
window = FabricManager(login.is_admin)
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()