Files
cangku/garment_dialogs.py
2025-12-23 00:30:36 +08:00

777 lines
32 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
服装管理模块 - 处理服装款式和材料用量管理
"""
import os
from datetime import datetime
from PIL import Image
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QGridLayout, QLabel, QLineEdit,
QPushButton, QComboBox, QTableWidget, QTableWidgetItem, QHeaderView,
QMessageBox, QFileDialog, QDoubleSpinBox, QWidget, QCompleter
)
from PyQt5.QtCore import Qt, QStringListModel, QTimer
from PyQt5.QtGui import QPixmap
from database import get_db_connection
class SearchableComboBox(QComboBox):
"""支持模糊搜索的下拉框"""
def __init__(self, parent=None):
super().__init__(parent)
self.setEditable(True)
self.setInsertPolicy(QComboBox.NoInsert)
# 存储所有选项
self.all_items = []
self.all_data = []
self.is_filtering = False
# 设置自动完成
self.completer = QCompleter(self)
self.completer.setCompletionMode(QCompleter.PopupCompletion)
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
self.completer.setFilterMode(Qt.MatchContains)
self.setCompleter(self.completer)
# 连接信号
self.lineEdit().textChanged.connect(self.on_text_changed)
def addItem(self, text, userData=None):
"""添加选项"""
# 临时断开信号连接防止textChanged触发on_text_changed
self.lineEdit().textChanged.disconnect()
super().addItem(text, userData)
if text not in self.all_items:
self.all_items.append(text)
self.all_data.append(userData)
self.update_completer()
# 重新连接信号
self.lineEdit().textChanged.connect(self.on_text_changed)
def addItems(self, texts):
"""批量添加选项"""
for text in texts:
self.addItem(text)
def clear(self):
"""清空所有选项"""
if not self.is_filtering:
super().clear()
self.all_items.clear()
self.all_data.clear()
self.update_completer()
def reset_items(self):
"""重置所有选项"""
# 临时断开信号连接防止textChanged触发on_text_changed
self.lineEdit().textChanged.disconnect()
self.is_filtering = True
super().clear()
for i, item in enumerate(self.all_items):
super().addItem(item, self.all_data[i] if i < len(self.all_data) else None)
self.is_filtering = False
# 重新连接信号
self.lineEdit().textChanged.connect(self.on_text_changed)
def update_completer(self):
"""更新自动完成列表"""
model = QStringListModel(self.all_items)
self.completer.setModel(model)
def on_text_changed(self, text):
"""文本改变时的处理"""
if self.is_filtering:
return
if not text or text in ["—— 选择型号 ——"]:
self.reset_items()
# 如果获得焦点且有选项,显示下拉列表
if self.hasFocus() and self.count() > 0:
self.showPopup()
return
# 模糊搜索匹配
filtered_items = []
filtered_data = []
for i, item in enumerate(self.all_items):
if text.lower() in item.lower():
filtered_items.append(item)
filtered_data.append(self.all_data[i] if i < len(self.all_data) else None)
# 更新下拉列表
self.is_filtering = True
super().clear()
for i, item in enumerate(filtered_items):
super().addItem(item, filtered_data[i])
self.is_filtering = False
# 如果有匹配项且获得焦点,显示下拉列表
if filtered_items and self.hasFocus():
self.showPopup()
class GarmentLibraryDialog(QDialog):
"""服装库管理对话框"""
def __init__(self, db_path):
super().__init__()
self.db_path = db_path
self.setWindowTitle("衣服款号管理")
self.resize(1300, 750)
self.setup_ui()
self.load_garments()
def setup_ui(self):
"""设置用户界面"""
layout = QVBoxLayout(self)
# 操作按钮区域
op_layout = QHBoxLayout()
op_layout.addWidget(QLabel("搜索款号:"))
self.search_input = QLineEdit()
self.search_input.textChanged.connect(self.load_garments)
op_layout.addWidget(self.search_input)
add_btn = QPushButton("新增/编辑款号")
add_btn.clicked.connect(self.edit_garment)
op_layout.addWidget(add_btn)
del_btn = QPushButton("删除选中款号")
del_btn.clicked.connect(self.delete_garment)
op_layout.addWidget(del_btn)
refresh_btn = QPushButton("刷新")
refresh_btn.clicked.connect(self.load_garments)
op_layout.addWidget(refresh_btn)
layout.addLayout(op_layout)
# 服装表格
self.garment_table = QTableWidget()
self.garment_table.setColumnCount(3)
self.garment_table.setHorizontalHeaderLabels(["款号", "类目数量", "款式图预览"])
self.garment_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.garment_table.itemDoubleClicked.connect(self.edit_garment_from_table)
layout.addWidget(self.garment_table)
def get_conn(self):
"""获取数据库连接"""
return get_db_connection(self.db_path)
def load_garments(self):
"""加载服装列表"""
keyword = self.search_input.text().strip()
try:
with self.get_conn() as conn:
query = "SELECT style_number, image_path FROM garments"
params = []
if keyword:
query += " WHERE style_number LIKE ?"
params = ["%" + keyword + "%"]
query += " ORDER BY style_number"
cursor = conn.execute(query, params)
rows = cursor.fetchall()
self.garment_table.setRowCount(len(rows))
for i in range(len(rows)):
self.garment_table.setRowHeight(i, 140)
for row_idx, (style_number, image_path) in enumerate(rows):
self.garment_table.setItem(row_idx, 0, QTableWidgetItem(style_number))
# 查询材料数量
with self.get_conn() as conn:
cursor2 = conn.execute("SELECT COUNT(*) FROM garment_materials WHERE style_number = ?", (style_number,))
count = cursor2.fetchone()[0]
self.garment_table.setItem(row_idx, 1, QTableWidgetItem(str(count)))
# 显示图片预览
image_item = QTableWidgetItem()
image_item.setTextAlignment(Qt.AlignCenter)
if image_path and os.path.exists(image_path):
try:
pixmap = QPixmap(image_path).scaled(130, 130, Qt.KeepAspectRatio, Qt.SmoothTransformation)
image_item.setData(Qt.DecorationRole, pixmap)
except:
image_item.setText("加载失败")
else:
image_item.setText("无图片")
self.garment_table.setItem(row_idx, 2, image_item)
except Exception as e:
QMessageBox.critical(self, "加载失败", "错误: " + str(e))
def edit_garment_from_table(self):
"""从表格编辑服装"""
row = self.garment_table.currentRow()
if row >= 0:
style_number = self.garment_table.item(row, 0).text()
self.edit_garment(style_number)
def edit_garment(self, style_number=None):
"""编辑服装"""
dialog = GarmentEditDialog(self.db_path, style_number)
if dialog.exec_():
self.load_garments()
def delete_garment(self):
"""删除服装"""
row = self.garment_table.currentRow()
if row < 0:
QMessageBox.warning(self, "提示", "请先选中一款号")
return
style_number = self.garment_table.item(row, 0).text()
reply = QMessageBox.question(self, "确认", f"删除款号 '{style_number}' 及其所有信息?")
if reply == QMessageBox.Yes:
try:
with self.get_conn() as conn:
conn.execute("DELETE FROM garment_materials WHERE style_number = ?", (style_number,))
conn.execute("DELETE FROM garments WHERE style_number = ?", (style_number,))
conn.commit()
self.load_garments()
QMessageBox.information(self, "成功", "删除完成")
except Exception as e:
QMessageBox.critical(self, "错误", "删除失败: " + str(e))
class GarmentEditDialog(QDialog):
"""服装编辑对话框"""
def __init__(self, db_path, style_number=None):
super().__init__()
self.db_path = db_path
self.style_number = style_number
self.current_image_path = None
self.setWindowTitle("编辑款号" if style_number else "新增款号")
self.resize(1300, 850)
self.setup_ui()
if style_number:
self.load_garment_data()
def setup_ui(self):
"""设置用户界面"""
layout = QVBoxLayout(self)
# 基本信息区域
basic_layout = QGridLayout()
basic_layout.addWidget(QLabel("款号:"), 0, 0, Qt.AlignRight)
self.style_input = QLineEdit()
if self.style_number:
self.style_input.setText(self.style_number)
self.style_input.setEnabled(not self.style_number)
basic_layout.addWidget(self.style_input, 0, 1)
basic_layout.addWidget(QLabel("款式图:"), 1, 0, Qt.AlignRight)
self.image_label = QLabel("无图片")
self.image_label.setFixedSize(300, 300)
self.image_label.setStyleSheet("border: 1px solid gray;")
self.image_label.setAlignment(Qt.AlignCenter)
self.image_label.setScaledContents(True)
basic_layout.addWidget(self.image_label, 1, 1, 5, 1)
upload_btn = QPushButton("上传/更换图片")
upload_btn.clicked.connect(self.upload_image)
basic_layout.addWidget(upload_btn, 1, 2)
layout.addLayout(basic_layout)
# 材料用量区域
layout.addWidget(QLabel("材料用量(单件):"))
self.material_table = QTableWidget()
self.material_table.setColumnCount(6)
self.material_table.setHorizontalHeaderLabels(["类目", "类型", "型号", "单件用量", "单位", "删除"])
self.material_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
layout.addWidget(self.material_table)
# 按钮区域
btn_layout = QHBoxLayout()
add_default_btn = QPushButton("快速添加标准类目")
add_default_btn.clicked.connect(self.add_default_categories)
btn_layout.addWidget(add_default_btn)
add_custom_btn = QPushButton("添加自定义类目")
add_custom_btn.clicked.connect(lambda: self.add_material_row())
btn_layout.addWidget(add_custom_btn)
layout.addLayout(btn_layout)
# 保存/取消按钮
buttons = QHBoxLayout()
save_btn = QPushButton("保存")
save_btn.clicked.connect(self.save_garment)
buttons.addWidget(save_btn)
cancel_btn = QPushButton("取消")
cancel_btn.clicked.connect(self.reject)
buttons.addWidget(cancel_btn)
layout.addLayout(buttons)
def get_conn(self):
"""获取数据库连接"""
return get_db_connection(self.db_path)
def upload_image(self):
"""上传图片"""
file_path, _ = QFileDialog.getOpenFileName(self, "选择图片", "", "Images (*.png *.jpg *.jpeg *.bmp)")
if file_path:
try:
img = Image.open(file_path).convert("RGB")
img.thumbnail((800, 800))
os.makedirs("images", exist_ok=True)
filename = os.path.basename(file_path)
save_path = os.path.join("images", filename)
img.save(save_path, "JPEG", quality=85)
self.current_image_path = save_path
pixmap = QPixmap(save_path).scaled(300, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.image_label.setPixmap(pixmap)
except Exception as e:
QMessageBox.critical(self, "错误", "上传图片失败: " + str(e))
def load_garment_data(self):
"""加载服装数据"""
try:
with self.get_conn() as conn:
cursor = conn.execute("SELECT image_path FROM garments WHERE style_number = ?", (self.style_number,))
row = cursor.fetchone()
if row and row[0] and os.path.exists(row[0]):
self.current_image_path = row[0]
pixmap = QPixmap(row[0]).scaled(300, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.image_label.setPixmap(pixmap)
self.load_materials()
except Exception as e:
QMessageBox.critical(self, "错误", "加载失败: " + str(e))
def load_materials(self):
"""加载材料列表"""
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", (self.style_number,))
for category, fabric_type, usage, unit in cursor.fetchall():
display_category = ""
display_type = ""
display_model = ""
# category字段可能存储型号或类目-类型组合
if category:
# 首先检查是否是型号在fabrics表中查找
fabric_cursor = conn.execute("SELECT category, model FROM fabrics WHERE model = ?", (category,))
fabric_row = fabric_cursor.fetchone()
if fabric_row:
# 是型号从fabrics表获取类目信息
fabric_category, model = fabric_row
display_model = model
if fabric_category and "-" in fabric_category:
parts = fabric_category.split("-", 1)
display_category = parts[0]
display_type = parts[1]
else:
display_category = fabric_category or ""
else:
# 不是型号,按类目-类型格式解析
if "-" in category:
parts = category.split("-", 1)
display_category = parts[0]
display_type = parts[1]
else:
display_category = category
# 如果有单独的fabric_type字段优先使用
if fabric_type:
display_type = fabric_type
self.add_material_row(display_category, display_type, usage or 0, unit or "", display_model)
except Exception as e:
QMessageBox.critical(self, "错误", "加载材料失败: " + str(e))
def add_default_categories(self):
"""添加默认类目"""
defaults = [("A料", "", ""), ("B料", "", ""), ("C料", "", ""), ("D料", "", ""),
("花边", "", ""), ("胸杯", "", "一对"), ("拉链", "", ""), ("辅料", "", "")]
for cat, fabric_type, unit in defaults:
self.add_material_row(cat, fabric_type, 0, unit)
def add_material_row(self, category="", fabric_type="", usage=0.0, unit="", model=""):
"""添加材料行"""
row = self.material_table.rowCount()
self.material_table.insertRow(row)
# 列0: 类目下拉框
cat_combo = QComboBox()
cat_combo.setEditable(True)
# 最后添加自定义选项
cat_combo.addItem("—— 自定义类目 ——")
# 先添加所有类目选项
try:
with self.get_conn() as conn:
# 只获取纯类目(提取"-"前面的部分)
cursor = conn.execute("""
SELECT DISTINCT
CASE
WHEN category LIKE '%-%' THEN SUBSTR(category, 1, INSTR(category, '-') - 1)
ELSE category
END as major_category
FROM fabrics
WHERE category IS NOT NULL AND category != ''
ORDER BY major_category
""")
categories = set()
for cat_row in cursor.fetchall():
if cat_row[0] and cat_row[0].strip():
categories.add(cat_row[0])
# 添加默认类目
categories.update(["布料", "辅料", "其他"])
for cat in sorted(categories):
cat_combo.addItem(cat)
except:
# 如果查询失败,使用默认类目
cat_combo.addItem("布料")
cat_combo.addItem("辅料")
cat_combo.addItem("其他")
if category:
cat_combo.setCurrentText(category)
else:
# 如果没有指定类目,默认选择第一个实际类目而不是"自定义类目"
if cat_combo.count() > 1:
cat_combo.setCurrentIndex(0)
cat_combo.currentTextChanged.connect(lambda text, r=row: self.on_category_changed(text, r))
self.material_table.setCellWidget(row, 0, cat_combo)
# 列1: 类型下拉框
type_combo = QComboBox()
type_combo.setEditable(True)
# 先添加所有类型选项
try:
with self.get_conn() as conn:
cursor = conn.execute("""
SELECT DISTINCT
CASE
WHEN category LIKE '%-%' THEN SUBSTR(category, INSTR(category, '-') + 1)
ELSE '默认类型'
END as fabric_type
FROM fabrics
WHERE category IS NOT NULL AND category != ''
ORDER BY fabric_type
""")
types = cursor.fetchall()
for type_row in types:
if type_row[0] and type_row[0] != '默认类型':
type_combo.addItem(type_row[0])
except:
pass
# 最后添加选择提示
type_combo.addItem("—— 选择类型 ——")
if fabric_type:
type_combo.setCurrentText(fabric_type)
else:
# 如果没有指定类型,默认选择第一个实际类型而不是"选择类型"
if type_combo.count() > 1:
type_combo.setCurrentIndex(0)
type_combo.currentTextChanged.connect(lambda text, r=row: self.on_type_changed(text, r))
self.material_table.setCellWidget(row, 1, type_combo)
# 列2: 型号下拉框(支持模糊搜索)
model_combo = SearchableComboBox()
model_combo.addItem("—— 选择型号 ——")
# 初始化时加载所有型号
try:
with self.get_conn() as conn:
cursor = conn.execute("""
SELECT DISTINCT model, color, unit
FROM fabrics
ORDER BY model
""")
models = cursor.fetchall()
for model_row in models:
model, color, unit = model_row
# 显示格式:型号-颜色(如果有颜色的话)
display_text = model
if color and color.strip():
display_text = f"{model}-{color}"
model_combo.addItem(display_text, model)
except Exception as e:
pass
# 确保默认选中第一项("—— 选择型号 ——"
model_combo.setCurrentIndex(0)
model_combo.currentTextChanged.connect(lambda text, r=row: self.on_model_selected(text, r))
self.material_table.setCellWidget(row, 2, model_combo)
# 列3: 单件用量
usage_spin = QDoubleSpinBox()
usage_spin.setRange(0, 1000)
usage_spin.setValue(usage)
usage_spin.setDecimals(3)
self.material_table.setCellWidget(row, 3, usage_spin)
# 列4: 单位
unit_combo = QComboBox()
unit_combo.setEditable(True)
unit_combo.addItems(["", "", "公斤", "一对", "", ""])
unit_combo.setCurrentText(unit)
self.material_table.setCellWidget(row, 4, unit_combo)
# 列5: 删除按钮
del_btn = QPushButton("删除")
del_btn.clicked.connect(lambda _, r=row: self.material_table.removeRow(r))
self.material_table.setCellWidget(row, 5, del_btn)
# 初始化类型和型号选项
self.on_category_changed(cat_combo.currentText(), row)
# 如果没有选择具体类目,初始化时显示全部型号
if cat_combo.currentText() == "—— 自定义类目 ——":
self.on_type_changed("—— 选择类型 ——", row)
# 如果有指定的型号,需要在初始化完成后设置
if model:
# 先设置类型(如果有的话)
if fabric_type:
type_combo.setCurrentText(fabric_type)
self.on_type_changed(fabric_type, row)
# 然后设置型号 - 使用SearchableComboBox的setCurrentText方法
model_combo = self.material_table.cellWidget(row, 2)
if isinstance(model_combo, SearchableComboBox):
# 确保型号在选项列表中
found = False
for i in range(model_combo.count()):
item_data = model_combo.itemData(i)
item_text = model_combo.itemText(i)
if item_data == model or item_text == model:
model_combo.setCurrentIndex(i)
found = True
break
# 如果没找到直接设置文本SearchableComboBox支持
if not found:
model_combo.setCurrentText(model)
def on_category_changed(self, category_text, row):
"""当类目改变时,更新类型下拉框"""
type_combo = self.material_table.cellWidget(row, 1)
model_combo = self.material_table.cellWidget(row, 2)
# 清空类型下拉框
type_combo.clear()
type_combo.addItem("—— 选择类型 ——")
# 重新初始化型号下拉框,显示所有型号
model_combo.clear()
model_combo.addItem("—— 选择型号 ——")
try:
with self.get_conn() as conn:
# 加载所有类型
cursor = conn.execute("""
SELECT DISTINCT
CASE
WHEN category LIKE '%-%' THEN SUBSTR(category, INSTR(category, '-') + 1)
ELSE '默认类型'
END as fabric_type
FROM fabrics
WHERE category IS NOT NULL AND category != ''
ORDER BY fabric_type
""")
# 如果选择了具体类目,则过滤
if category_text and category_text != "—— 自定义类目 ——":
cursor = conn.execute("""
SELECT DISTINCT
CASE
WHEN category LIKE '%-%' THEN SUBSTR(category, INSTR(category, '-') + 1)
ELSE '默认类型'
END as fabric_type
FROM fabrics
WHERE category LIKE ? OR category = ?
ORDER BY fabric_type
""", (f"{category_text}-%", category_text))
types = cursor.fetchall()
for type_row in types:
if type_row[0] and type_row[0] != '默认类型':
type_combo.addItem(type_row[0])
# 连接类型改变事件
type_combo.currentTextChanged.connect(lambda text, r=row: self.on_type_changed(text, r))
# 加载所有型号到型号下拉框
cursor = conn.execute("""
SELECT DISTINCT model, color, unit
FROM fabrics
ORDER BY model
""")
models = cursor.fetchall()
for model_row in models:
model, color, unit = model_row
# 显示格式:型号-颜色(如果有颜色的话)
display_text = model
if color and color.strip():
display_text = f"{model}-{color}"
model_combo.addItem(display_text, model)
# 确保默认选中第一项("—— 选择型号 ——"
model_combo.setCurrentIndex(0)
except Exception as e:
pass
def on_type_changed(self, type_text, row):
"""当类型改变时,更新型号下拉框"""
cat_combo = self.material_table.cellWidget(row, 0)
model_combo = self.material_table.cellWidget(row, 2)
# 重新初始化型号下拉框,显示所有型号
if hasattr(model_combo, 'clear'):
model_combo.clear()
model_combo.addItem("—— 选择型号 ——")
# 始终显示所有型号,不进行过滤
try:
with self.get_conn() as conn:
cursor = conn.execute("""
SELECT DISTINCT model, color, unit
FROM fabrics
ORDER BY model
""")
models = cursor.fetchall()
for model_row in models:
model, color, unit = model_row
# 显示格式:型号-颜色(如果有颜色的话)
display_text = model
if color and color.strip():
display_text = f"{model}-{color}"
model_combo.addItem(display_text, model)
# 确保默认选中第一项("—— 选择型号 ——"
model_combo.setCurrentIndex(0)
except Exception as e:
pass
def on_model_selected(self, model_text, row):
"""当型号选择时,自动设置单位并填充类目和类型"""
if not model_text or model_text == "—— 选择型号 ——":
return
cat_combo = self.material_table.cellWidget(row, 0)
type_combo = self.material_table.cellWidget(row, 1)
model_combo = self.material_table.cellWidget(row, 2)
unit_combo = self.material_table.cellWidget(row, 4)
# 获取选中项的数据
current_index = model_combo.currentIndex()
if current_index > 0:
model = model_combo.itemData(current_index)
if model:
try:
with self.get_conn() as conn:
cursor = conn.execute("SELECT category, unit FROM fabrics WHERE model = ?", (model,))
row_db = cursor.fetchone()
if row_db:
category, unit = row_db
# 自动填充单位
if unit:
unit_combo.setCurrentText(unit)
# 自动填充类目和类型
if category:
# 解析类目信息,可能是"类目-类型"格式或单独的类目
if '-' in category:
parts = category.split('-', 1)
cat_text = parts[0]
type_text = parts[1] if len(parts) > 1 else ""
# 设置类目
cat_combo.setCurrentText(cat_text)
# 更新类型下拉框选项
self.on_category_changed(cat_text, row)
# 设置类型
if type_text:
type_combo.setCurrentText(type_text)
else:
# 只有类目,没有类型
cat_combo.setCurrentText(category)
self.on_category_changed(category, row)
except:
pass
def save_garment(self):
"""保存服装"""
style_number = self.style_input.text().strip()
if not style_number:
QMessageBox.warning(self, "错误", "请输入款号")
return
try:
with self.get_conn() as conn:
conn.execute('INSERT OR REPLACE INTO garments (style_number, image_path) VALUES (?, ?)',
(style_number, self.current_image_path))
conn.execute("DELETE FROM garment_materials WHERE style_number = ?", (style_number,))
for row in range(self.material_table.rowCount()):
# 获取各列的值
category_widget = self.material_table.cellWidget(row, 0) # 类目
type_widget = self.material_table.cellWidget(row, 1) # 类型
model_widget = self.material_table.cellWidget(row, 2) # 型号
usage_widget = self.material_table.cellWidget(row, 3) # 单件用量
unit_widget = self.material_table.cellWidget(row, 4) # 单位
category = category_widget.currentText().strip()
fabric_type = type_widget.currentText().strip()
model = model_widget.currentText().strip()
# 处理类目和类型
if category == "—— 自定义类目 ——":
category = ""
if fabric_type == "—— 选择类型 ——":
fabric_type = ""
# 如果选择了具体型号,获取型号的实际值
final_model = ""
if model and model != "—— 选择型号 ——":
model_data = model_widget.itemData(model_widget.currentIndex())
final_model = model_data if model_data else model
# 至少需要有类目或型号
if not category and not final_model:
continue
usage = usage_widget.value()
unit = unit_widget.currentText().strip() or ""
# 分别存储类目、类型和型号信息
material_identifier = final_model if final_model else (f"{category}-{fabric_type}" if fabric_type else category)
conn.execute("INSERT INTO garment_materials (style_number, category, fabric_type, usage_per_piece, unit) VALUES (?, ?, ?, ?, ?)",
(style_number, material_identifier, fabric_type, usage, unit))
conn.commit()
QMessageBox.information(self, "成功", "保存完成")
self.accept()
except Exception as e:
QMessageBox.critical(self, "错误", "保存失败: " + str(e))