SIT/ui/dialogs/mapping_dialog.py

376 lines
13 KiB
Python
Raw Normal View History

2026-01-29 09:08:31 +00:00
"""
映射编辑对话框
用于创建和编辑映射关系
"""
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}")