376 lines
13 KiB
Python
376 lines
13 KiB
Python
|
|
"""
|
||
|
|
映射编辑对话框
|
||
|
|
用于创建和编辑映射关系
|
||
|
|
"""
|
||
|
|
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}")
|