""" 映射编辑对话框 用于创建和编辑映射关系 """ from PyQt5.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QLineEdit, QComboBox, QTextEdit, QPushButton, QLabel, QMessageBox, QGroupBox, QListWidget, QRadioButton, QButtonGroup, QStackedWidget, QWidget ) from PyQt5.QtCore import Qt, pyqtSignal from models.mapping import OperatorMapping, CodeMapping from models.field import Field from config import OperatorType, LanguageType from controllers.field_controller import FieldController from ui.widgets.code_editor_widget import CodeEditorWidget from utils.logger import get_logger logger = get_logger(__name__) class MappingDialog(QDialog): """映射编辑对话框""" mapping_saved = pyqtSignal(object) # OperatorMapping 或 CodeMapping def __init__(self, parent=None, mapping=None): super().__init__(parent) self.mapping = mapping self.field_controller = FieldController() self.init_ui() if mapping: self.load_mapping_data(mapping) self.setWindowTitle("编辑映射") else: self.setWindowTitle("新建映射") def init_ui(self): """初始化UI""" self.setModal(True) self.setMinimumSize(700, 600) # 主布局 main_layout = QVBoxLayout(self) # 映射类型选择 type_group = QGroupBox("映射类型") type_layout = QHBoxLayout(type_group) self.type_button_group = QButtonGroup() self.operator_radio = QRadioButton("操作符映射") self.operator_radio.setChecked(True) self.type_button_group.addButton(self.operator_radio, 0) type_layout.addWidget(self.operator_radio) self.code_radio = QRadioButton("代码映射") self.type_button_group.addButton(self.code_radio, 1) type_layout.addWidget(self.code_radio) type_layout.addStretch() main_layout.addWidget(type_group) # 堆叠窗口(根据类型切换) self.stacked_widget = QStackedWidget() # 操作符映射页面 operator_page = self.create_operator_page() self.stacked_widget.addWidget(operator_page) # 代码映射页面 code_page = self.create_code_page() self.stacked_widget.addWidget(code_page) main_layout.addWidget(self.stacked_widget) # 连接信号 self.type_button_group.buttonClicked.connect(self.on_type_changed) # 按钮 button_layout = QHBoxLayout() self.save_btn = QPushButton("保存") self.save_btn.clicked.connect(self.on_save) self.save_btn.setDefault(True) self.cancel_btn = QPushButton("取消") self.cancel_btn.clicked.connect(self.reject) button_layout.addStretch() button_layout.addWidget(self.save_btn) button_layout.addWidget(self.cancel_btn) main_layout.addLayout(button_layout) def create_operator_page(self) -> QWidget: """创建操作符映射页面""" page = QWidget() layout = QVBoxLayout(page) # 字段选择 field_group = QGroupBox("字段配置") field_layout = QFormLayout(field_group) # 源字段 self.source_field_combo = QComboBox() self.load_fields_to_combo(self.source_field_combo) field_layout.addRow("源字段*:", self.source_field_combo) # 目标字段 self.target_field_combo = QComboBox() self.load_fields_to_combo(self.target_field_combo) field_layout.addRow("目标字段*:", self.target_field_combo) layout.addWidget(field_group) # 操作符配置 operator_group = QGroupBox("操作符配置") operator_layout = QFormLayout(operator_group) # 操作符类型 self.operator_combo = QComboBox() for op_type in OperatorType: self.operator_combo.addItem(op_type.value, op_type) operator_layout.addRow("操作符*:", self.operator_combo) # 操作数 self.operand_edit = QLineEdit() self.operand_edit.setPlaceholderText("例如: 10 (用于加减乘除等操作)") operator_layout.addRow("操作数:", self.operand_edit) # 描述 self.op_description_edit = QTextEdit() self.op_description_edit.setPlaceholderText("映射的详细描述...") self.op_description_edit.setMaximumHeight(80) operator_layout.addRow("描述:", self.op_description_edit) layout.addWidget(operator_group) layout.addStretch() return page def create_code_page(self) -> QWidget: """创建代码映射页面""" page = QWidget() layout = QVBoxLayout(page) # 字段选择 field_group = QGroupBox("字段配置") field_layout = QVBoxLayout(field_group) # 源字段(可多选) source_label = QLabel("源字段*:") field_layout.addWidget(source_label) self.source_fields_list = QListWidget() self.source_fields_list.setSelectionMode(QListWidget.ExtendedSelection) self.source_fields_list.setMaximumHeight(100) self.load_fields_to_list(self.source_fields_list) field_layout.addWidget(self.source_fields_list) # 目标字段 target_layout = QHBoxLayout() target_label = QLabel("目标字段*:") self.code_target_field_combo = QComboBox() self.load_fields_to_combo(self.code_target_field_combo) target_layout.addWidget(target_label) target_layout.addWidget(self.code_target_field_combo) field_layout.addLayout(target_layout) layout.addWidget(field_group) # 代码配置 code_group = QGroupBox("代码配置") code_layout = QVBoxLayout(code_group) # 语言选择 lang_layout = QHBoxLayout() lang_label = QLabel("编程语言*:") self.language_combo = QComboBox() for lang in LanguageType: self.language_combo.addItem(lang.value, lang) lang_layout.addWidget(lang_label) lang_layout.addWidget(self.language_combo) lang_layout.addStretch() code_layout.addLayout(lang_layout) # 代码编辑器 code_label = QLabel("转换代码*:") code_layout.addWidget(code_label) self.code_editor = CodeEditorWidget() self.code_editor.setPlaceholderText( "// 示例代码:\n" "// target = source1 * 2 + source2;\n" ) code_layout.addWidget(self.code_editor) # 描述 desc_label = QLabel("描述:") code_layout.addWidget(desc_label) self.code_description_edit = QTextEdit() self.code_description_edit.setPlaceholderText("映射的详细描述...") self.code_description_edit.setMaximumHeight(60) code_layout.addWidget(self.code_description_edit) layout.addWidget(code_group) return page def load_fields_to_combo(self, combo: QComboBox): """加载字段到下拉框""" fields, _ = self.field_controller.get_all_fields(page=1, page_size=1000) combo.clear() for field in fields: combo.addItem(f"{field.full_name} ({field.type.value})", field) def load_fields_to_list(self, list_widget: QListWidget): """加载字段到列表""" fields, _ = self.field_controller.get_all_fields(page=1, page_size=1000) list_widget.clear() for field in fields: from PyQt5.QtWidgets import QListWidgetItem item = QListWidgetItem(f"{field.full_name} ({field.type.value})") item.setData(Qt.UserRole, field) list_widget.addItem(item) def on_type_changed(self, button): """映射类型改变""" if button == self.operator_radio: self.stacked_widget.setCurrentIndex(0) else: self.stacked_widget.setCurrentIndex(1) def load_mapping_data(self, mapping): """加载映射数据""" if isinstance(mapping, OperatorMapping): self.operator_radio.setChecked(True) self.stacked_widget.setCurrentIndex(0) # 设置源字段 for i in range(self.source_field_combo.count()): field = self.source_field_combo.itemData(i) if field.id == mapping.source_field.id: self.source_field_combo.setCurrentIndex(i) break # 设置目标字段 for i in range(self.target_field_combo.count()): field = self.target_field_combo.itemData(i) if field.id == mapping.target_field.id: self.target_field_combo.setCurrentIndex(i) break # 设置操作符 index = self.operator_combo.findData(mapping.operator) if index >= 0: self.operator_combo.setCurrentIndex(index) if mapping.operand: self.operand_edit.setText(mapping.operand) if mapping.description: self.op_description_edit.setText(mapping.description) elif isinstance(mapping, CodeMapping): self.code_radio.setChecked(True) self.stacked_widget.setCurrentIndex(1) # 设置源字段(多选) for i in range(self.source_fields_list.count()): item = self.source_fields_list.item(i) field = item.data(Qt.UserRole) if any(f.id == field.id for f in mapping.source_fields): item.setSelected(True) # 设置目标字段 for i in range(self.code_target_field_combo.count()): field = self.code_target_field_combo.itemData(i) if field.id == mapping.target_field.id: self.code_target_field_combo.setCurrentIndex(i) break # 设置语言 index = self.language_combo.findData(mapping.language) if index >= 0: self.language_combo.setCurrentIndex(index) # 设置代码 self.code_editor.setPlainText(mapping.code) if mapping.description: self.code_description_edit.setText(mapping.description) def validate_operator_mapping(self) -> tuple[bool, str]: """验证操作符映射""" if self.source_field_combo.currentIndex() < 0: return False, "请选择源字段" if self.target_field_combo.currentIndex() < 0: return False, "请选择目标字段" return True, "" def validate_code_mapping(self) -> tuple[bool, str]: """验证代码映射""" if len(self.source_fields_list.selectedItems()) == 0: return False, "请至少选择一个源字段" if self.code_target_field_combo.currentIndex() < 0: return False, "请选择目标字段" if not self.code_editor.toPlainText().strip(): return False, "转换代码不能为空" return True, "" def on_save(self): """保存映射""" try: if self.operator_radio.isChecked(): # 操作符映射 is_valid, error_msg = self.validate_operator_mapping() if not is_valid: QMessageBox.warning(self, "验证失败", error_msg) return mapping = OperatorMapping( source_field=self.source_field_combo.currentData(), target_field=self.target_field_combo.currentData(), operator=self.operator_combo.currentData(), operand=self.operand_edit.text().strip() or None, description=self.op_description_edit.toPlainText().strip() or None ) if self.mapping and hasattr(self.mapping, 'id'): mapping.id = self.mapping.id else: # 代码映射 is_valid, error_msg = self.validate_code_mapping() if not is_valid: QMessageBox.warning(self, "验证失败", error_msg) return source_fields = [ item.data(Qt.UserRole) for item in self.source_fields_list.selectedItems() ] mapping = CodeMapping( source_fields=source_fields, target_field=self.code_target_field_combo.currentData(), language=self.language_combo.currentData(), code=self.code_editor.toPlainText().strip(), description=self.code_description_edit.toPlainText().strip() or None ) if self.mapping and hasattr(self.mapping, 'id'): mapping.id = self.mapping.id self.mapping_saved.emit(mapping) self.accept() except Exception as e: QMessageBox.critical(self, "错误", f"创建映射对象失败: {str(e)}") logger.error(f"Failed to create mapping object: {e}")