1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-17 23:13:03 +08:00

Compare commits

...

3 Commits

9 changed files with 473 additions and 743 deletions
+27 -17
View File
@@ -32,6 +32,21 @@ _TYPE_DEFAULTS = {
"String": ""
}
# Mapping from Python type to AutoScript type name for error messages
_PYTHON_TO_AS_TYPE = {
bool: "Boolean",
int: "Int",
float: "Float",
str: "String",
date: "Date",
time: "Time",
}
def _asTypeName(
value
) -> str:
return _PYTHON_TO_AS_TYPE.get(type(value), "UnknowType")
class ASObject:
"""
@@ -174,34 +189,29 @@ class ASObject:
target_data: dict = None
):
"""
Assign a new value to this variable, with type coercion.
Assign a new value to this variable, with strict type checking.
Performs coercion for Boolean (string -> bool), Int, and Float types.
For config variables, dates/times are converted back to strings before
writing into target_data.
AutoScript is strongly typed: only values whose Python type matches the
declared variable type are accepted. Int->Float widening is allowed;
all other cross-type assignments raise ValueError.
Args:
value: The value to assign.
target_data (dict): The application data dict (required for config vars).
Raises:
ValueError: If the variable is read-only or value cannot be coerced.
ValueError: If the variable is read-only or value type mismatches the variable type.
"""
if self.read_only:
raise ValueError(f"不能修改只读变量 '{self.name}'")
if self.var_type == "Boolean" and not isinstance(value, bool):
value = (str(value).upper() == "TRUE")
if self.var_type == "Int" and not isinstance(value, int):
try:
value = int(value)
except (ValueError, TypeError):
raise ValueError(f"无法将值 '{value}' 转换为 Int 类型")
if self.var_type == "Float" and not isinstance(value, float):
try:
value = float(value)
except (ValueError, TypeError):
raise ValueError(f"无法将值 '{value}' 转换为 Float 类型")
vt = self.var_type
value_type = _asTypeName(value)
if vt != value_type and not (vt == "Float" and value_type == "Int"):
raise ValueError(
f"{vt} 类型变量 '{self.name}' 不能接受 {value_type} 类型的值"
)
if self.is_config:
if self.var_type == "Date" and isinstance(value, date):
value = value.strftime("%Y-%m-%d")
+2
View File
@@ -28,6 +28,8 @@ __all__ = [
"buildMockTargetData",
"META_VARS",
"ALL_VARIABLES",
"_TARGET_VAR_DEFS",
"_MOCK_TYPE_VALUES",
"ASTokenizer",
"Stmt",
"Script",
+320 -62
View File
@@ -7,7 +7,9 @@ This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
from PySide6.QtCore import Qt, Slot
from copy import deepcopy
from PySide6.QtCore import QDate, Qt, QTime, QTimer, Slot
from PySide6.QtGui import (
QColor,
QFont,
@@ -16,21 +18,40 @@ from PySide6.QtGui import (
)
from PySide6.QtWidgets import (
QApplication,
QComboBox,
QDateEdit,
QDialog,
QDialogButtonBox,
QDoubleSpinBox,
QFormLayout,
QFrame,
QGridLayout,
QGroupBox,
QHBoxLayout,
QHeaderView,
QLabel,
QLineEdit,
QMessageBox,
QPlainTextEdit,
QPushButton,
QSpinBox,
QSplitter,
QStyle,
QTabWidget,
QTableWidget,
QTableWidgetItem,
QTimeEdit,
QVBoxLayout,
QWidget,
)
from autoscript import ALL_VARIABLES
from autoscript import (
ALL_VARIABLES,
_MOCK_TYPE_VALUES,
_TARGET_VAR_DEFS,
execute,
registerDefaultTargetVars,
)
class ALScriptHighlighter(QSyntaxHighlighter):
@@ -99,16 +120,47 @@ class ALScriptHighlighter(QSyntaxHighlighter):
self.setFormat(start, length, fmt)
class _DebugResultDialog(QDialog):
def __init__(
self,
changes: list,
parent = None
):
super().__init__(parent)
self.setWindowTitle("调试运行结果 - AutoLibrary")
self.setMinimumSize(600, 200)
layout = QVBoxLayout(self)
table = QTableWidget(len(changes), 3)
table.setHorizontalHeaderLabels(["目标变量", "原始数据", "运行后数据"])
table.horizontalHeader().setStretchLastSection(True)
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
for row, (display_name, name, var_type, before_val, after_val) in enumerate(changes):
label = f"{display_name}: {name}({var_type})"
table.setItem(row, 0, QTableWidgetItem(label))
table.setItem(row, 1, QTableWidgetItem(str(before_val)))
table.setItem(row, 2, QTableWidgetItem(str(after_val)))
layout.addWidget(table)
btnBox = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok)
btnBox.button(QDialogButtonBox.StandardButton.Ok).setText("确定")
btnBox.accepted.connect(self.accept)
layout.addWidget(btnBox)
class ALAutoScriptEditDialog(QDialog):
def __init__(
self,
parent = None,
script: str = ""
script: str = "",
mockData: dict = None
):
super().__init__(parent)
self._fontSize = 19
self._mockWidgets = {}
self.setupUi()
self.connectSignals()
@@ -116,6 +168,8 @@ class ALAutoScriptEditDialog(QDialog):
self._highlighter = ALScriptHighlighter(
self.textEdit.document()
)
if mockData:
self.setMockData(mockData)
def setupUi(
@@ -123,10 +177,10 @@ class ALAutoScriptEditDialog(QDialog):
):
self.setWindowTitle("AutoScript 编辑 - AutoLibrary")
self.setMinimumSize(640, 600)
self.setMinimumSize(660, 600)
layout = QVBoxLayout(self)
layout.setSpacing(4)
layout.setContentsMargins(4, 4, 4, 4)
layout.setSpacing(3)
layout.setContentsMargins(3, 3, 3, 3)
toolbarLayout = QHBoxLayout()
self.zoomInBtn = QPushButton("")
self.zoomInBtn.setFixedSize(25, 25)
@@ -141,6 +195,19 @@ class ALAutoScriptEditDialog(QDialog):
self.zoomResetBtn.setToolTip("重置缩放")
self.zoomLabel = QLabel(f"{self._fontSize}px")
self.zoomLabel.setFixedHeight(25)
self.orchBtn = QPushButton("编排")
self.orchBtn.setFixedHeight(25)
self.orchBtn.setToolTip("可视化生成 AutoScript 代码并插入到光标位置")
toolbarLayout.addWidget(self.orchBtn)
self.debugBtn = QPushButton("▶ 调试运行")
self.debugBtn.setFixedHeight(25)
self.debugBtn.setToolTip("使用右侧模拟数据执行脚本,查看目标变量变化")
toolbarLayout.addWidget(self.debugBtn)
sep = QFrame()
sep.setFrameShape(QFrame.Shape.VLine)
sep.setFrameShadow(QFrame.Shadow.Sunken)
sep.setFixedWidth(1)
toolbarLayout.addWidget(sep)
toolbarLayout.addWidget(self.zoomInBtn)
toolbarLayout.addWidget(self.zoomOutBtn)
toolbarLayout.addWidget(self.zoomResetBtn)
@@ -181,46 +248,38 @@ class ALAutoScriptEditDialog(QDialog):
parent_layout
):
tab_widget = QTabWidget()
tab_widget.setMaximumHeight(200)
basic_widget = QWidget()
basic_layout = QGridLayout(basic_widget)
basic_layout.setSpacing(4)
basic_layout.setContentsMargins(4, 4, 4, 4)
basic_layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
control_buttons = [
splitter = QSplitter(Qt.Orientation.Horizontal)
tabWidget = QTabWidget()
tabWidget.setMaximumHeight(150)
basicWidget = QWidget()
basicLayout = QGridLayout(basicWidget)
basicLayout.setSpacing(4)
basicLayout.setContentsMargins(4, 4, 4, 4)
basicLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
controlButtons = [
("IF", "IF()\n \nEND IF"),
("ELSE IF", "ELSE IF()\n "),
("ELSE", "ELSE"),
("END IF", "END IF"),
("PASS", "PASS"),
]
self._addButtonsToGrid(basic_layout, control_buttons, 0, 0, 5)
assign_buttons = [
self._addButtonsToGrid(basicLayout, controlButtons, 0, 0, 5)
assignButtons = [
("SET", "SET = "),
]
self._addButtonsToGrid(basic_layout, assign_buttons, 0, 5, 1)
func_buttons = [
("DATE()", "DATE()"),
("TIME()", "TIME()"),
]
self._addButtonsToGrid(basic_layout, func_buttons, 1, 0, 2)
tab_widget.addTab(basic_widget, "基本语法")
operator_widget = QWidget()
operator_layout = QGridLayout(operator_widget)
operator_layout.setSpacing(4)
operator_layout.setContentsMargins(4, 4, 4, 4)
operator_layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
arithmetic_buttons = [
self._addButtonsToGrid(basicLayout, assignButtons, 0, 5, 1)
tabWidget.addTab(basicWidget, "基本语法")
operatorWidget = QWidget()
operatorLayout = QGridLayout(operatorWidget)
operatorLayout.setSpacing(4)
operatorLayout.setContentsMargins(4, 4, 4, 4)
operatorLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
arithmeticButtons = [
(".ADD.", ".ADD."),
(".SUB.", ".SUB."),
]
self._addButtonsToGrid(operator_layout, arithmetic_buttons, 0, 0, 2)
compare_buttons = [
self._addButtonsToGrid(operatorLayout, arithmeticButtons, 0, 0, 2)
compareButtons = [
(".EQ.", ".EQ."),
(".NEQ.", ".NEQ."),
(".BGT.", ".BGT."),
@@ -228,42 +287,54 @@ class ALAutoScriptEditDialog(QDialog):
(".BGE.", ".BGE."),
(".BLE.", ".BLE."),
]
self._addButtonsToGrid(operator_layout, compare_buttons, 1, 0, 6)
self._addButtonsToGrid(operatorLayout, compareButtons, 1, 0, 6)
logic_buttons = [
(".AND.", ".AND."),
(".OR.", ".OR."),
]
self._addButtonsToGrid(operator_layout, logic_buttons, 2, 0, 2)
tab_widget.addTab(operator_widget, "运算符")
literal_widget = QWidget()
literal_layout = QGridLayout(literal_widget)
literal_layout.setSpacing(4)
literal_layout.setContentsMargins(4, 4, 4, 4)
literal_layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
self._addButtonsToGrid(operatorLayout, logic_buttons, 2, 0, 2)
tabWidget.addTab(operatorWidget, "运算符")
literalWidget = QWidget()
literalLayout = QGridLayout(literalWidget)
literalLayout.setSpacing(4)
literalLayout.setContentsMargins(4, 4, 4, 4)
literalLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
bool_buttons = [
(".TRUE.", ".TRUE."),
(".FALSE.", ".FALSE."),
]
self._addButtonsToGrid(literal_layout, bool_buttons, 0, 0, 2)
hint_buttons = [
("字符串", "'文本'"),
("数字", "123"),
("注释", "// 注释"),
self._addButtonsToGrid(literalLayout, bool_buttons, 0, 0, 2)
dateTimeButtons = [
("DATE()", "DATE(2025-01-01)"),
("TIME()", "TIME(00:00)"),
]
self._addButtonsToGrid(literal_layout, hint_buttons, 1, 0, 3)
tab_widget.addTab(literal_widget, "字面量")
var_widget = QWidget()
var_layout = QGridLayout(var_widget)
var_layout.setSpacing(4)
var_layout.setContentsMargins(4, 4, 4, 4)
var_layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
var_buttons = [
self._addButtonsToGrid(literalLayout, dateTimeButtons, 1, 0, 2)
hintButtons = [
("字符串", "'请输入文本'"),
("数字", "123"),
("注释", "// 请输入注释"),
]
self._addButtonsToGrid(literalLayout, hintButtons, 2, 0, 3)
tabWidget.addTab(literalWidget, "字面量")
varWidget = QWidget()
varLayout = QGridLayout(varWidget)
varLayout.setSpacing(4)
varLayout.setContentsMargins(4, 4, 4, 4)
varLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
varButtons = [
(display_name, name) for display_name, (name, _) in ALL_VARIABLES.items()
]
self._addButtonsToGrid(var_layout, var_buttons, 0, 0, 5)
tab_widget.addTab(var_widget, "变量")
parent_layout.addWidget(tab_widget)
self._addButtonsToGrid(varLayout, varButtons, 0, 0, 5)
tabWidget.addTab(varWidget, "变量")
mockPanel = self._createMockPanel()
mockPanel.setMinimumWidth(260)
splitter.addWidget(tabWidget)
splitter.addWidget(mockPanel)
splitter.setStretchFactor(0, 1)
splitter.setStretchFactor(1, 0)
splitter.setSizes([660, 400])
parent_layout.addWidget(splitter)
def _addButtonsToGrid(
@@ -283,7 +354,7 @@ class ALAutoScriptEditDialog(QDialog):
btn.setProperty("template", template)
btn.clicked.connect(self._insertTemplate)
btn.setFixedWidth(100)
btn.setFixedHeight(30)
btn.setFixedHeight(25)
btn.setToolTip(f"插入: {template}")
grid_layout.addWidget(btn, row, col)
@@ -307,12 +378,186 @@ class ALAutoScriptEditDialog(QDialog):
cursor.insertText(template)
def _createMockPanel(
self
) -> QGroupBox:
group = QGroupBox("模拟目标数据")
form = QFormLayout(group)
form.setSpacing(4)
form.setContentsMargins(5, 10, 5, 5)
self._mockWidgets = {}
for name, var_type, key_path, display_name in _TARGET_VAR_DEFS:
default = _MOCK_TYPE_VALUES.get(var_type, "")
widget = self._makeMockInput(var_type, default)
label = QLabel(f"{display_name}: {name}({var_type})")
form.addRow(label, widget)
self._mockWidgets[name] = (widget, var_type, key_path)
return group
def _makeMockInput(
self,
var_type: str,
default
) -> QWidget:
if var_type == "String":
w = QLineEdit()
w.setText(str(default))
return w
if var_type == "Boolean":
w = QComboBox()
w.addItems(["", ""])
w.setCurrentIndex(0 if default else 1)
return w
if var_type == "Date":
w = QDateEdit()
w.setCalendarPopup(True)
w.setDisplayFormat("yyyy-MM-dd")
w.setDate(QDate.fromString(str(default), "yyyy-MM-dd"))
return w
if var_type == "Time":
w = QTimeEdit()
w.setDisplayFormat("HH:mm")
w.setTime(QTime.fromString(str(default), "HH:mm"))
return w
if var_type == "Int":
w = QSpinBox()
w.setMinimum(-999999)
w.setMaximum(999999)
w.setValue(int(default) if default else 0)
return w
if var_type == "Float":
w = QDoubleSpinBox()
w.setMinimum(-999999.0)
w.setMaximum(999999.0)
w.setDecimals(2)
w.setValue(float(default) if default else 0.0)
return w
w = QLineEdit()
w.setText(str(default))
return w
def getMockData(
self
) -> dict:
data = {}
for name, var_type, key_path, display_name in _TARGET_VAR_DEFS:
widget, _, _ = self._mockWidgets[name]
value = self._getMockValue(widget, var_type)
d = data
for key in key_path[:-1]:
d = d.setdefault(key, {})
d[key_path[-1]] = value
return data
def setMockData(
self,
data: dict
):
if not data:
return
for name, var_type, key_path, display_name in _TARGET_VAR_DEFS:
d = data
try:
for key in key_path:
d = d[key]
except (KeyError, TypeError):
continue
widget, _, _ = self._mockWidgets[name]
self._setMockValue(widget, var_type, d)
def _getMockValue(
self,
widget: QWidget,
var_type: str
):
if var_type == "Boolean":
return widget.currentIndex() == 0
if var_type == "Date":
return widget.date().toString("yyyy-MM-dd")
if var_type == "Time":
return widget.time().toString("HH:mm")
if var_type == "Int":
return widget.value()
if var_type == "Float":
return widget.value()
return widget.text()
def _setMockValue(
self,
widget: QWidget,
var_type: str,
value
):
if var_type == "Boolean":
widget.setCurrentIndex(0 if value else 1)
elif var_type == "Date":
widget.setDate(QDate.fromString(str(value), "yyyy-MM-dd"))
elif var_type == "Time":
widget.setTime(QTime.fromString(str(value), "HH:mm"))
elif var_type == "Int":
widget.setValue(int(value))
elif var_type == "Float":
widget.setValue(float(value))
else:
widget.setText(str(value))
@Slot()
def onDebugRun(
self
):
script = self.textEdit.toPlainText().strip()
if not script:
QMessageBox.warning(self, "提示", "脚本内容为空。")
return
target_data = self.getMockData()
before = deepcopy(target_data)
try:
registerDefaultTargetVars()
execute(script, target_data)
except ValueError as e:
QMessageBox.warning(self, "运行错误", str(e))
return
changes = []
for name, var_type, key_path, display_name in _TARGET_VAR_DEFS:
before_val = before
after_val = target_data
try:
for key in key_path:
before_val = before_val[key]
after_val = after_val[key]
except (KeyError, TypeError):
continue
if before_val != after_val:
changes.append((display_name, name, var_type, before_val, after_val))
if not changes:
QMessageBox.information(self, "调试运行", "目标变量未发生变化。")
return
dlg = _DebugResultDialog(changes, self)
dlg.exec()
dlg.deleteLater()
def connectSignals(
self
):
self.btnBox.accepted.connect(self.accept)
self.btnBox.rejected.connect(self.reject)
self.orchBtn.clicked.connect(self.onOpenOrchDialog)
self.debugBtn.clicked.connect(self.onDebugRun)
self.zoomInBtn.clicked.connect(self.onZoomIn)
self.zoomOutBtn.clicked.connect(self.onZoomOut)
self.zoomResetBtn.clicked.connect(self.onZoomReset)
@@ -375,8 +620,21 @@ class ALAutoScriptEditDialog(QDialog):
original = self.copyBtn.text()
self.copyBtn.setText("已复制")
self.copyBtn.setEnabled(False)
from PySide6.QtCore import QTimer
QTimer.singleShot(2000, lambda: (
self.copyBtn.setText(original),
self.copyBtn.setEnabled(True)
))
@Slot()
def onOpenOrchDialog(
self
):
from gui.ALAutoScriptOrchDialog import ALAutoScriptOrchDialog
dlg = ALAutoScriptOrchDialog(self)
if dlg.exec() == QDialog.DialogCode.Accepted:
script = dlg.getScript()
if script:
cursor = self.textEdit.textCursor()
cursor.insertText(script)
dlg.deleteLater()
+3 -141
View File
@@ -13,27 +13,15 @@ from PySide6.QtWidgets import (
QWidget,
)
from gui.ALAutoScriptOrchDialog._precheck import precheck
from gui.ALAutoScriptOrchDialog._orchestrate import parseBlocks
from gui.ALAutoScriptOrchDialog._helpers import (
COMPARE_OPERATORS,
PRESET_NAMES,
VariableManager,
findOperatorIn,
splitTopLevel,
stripOuterParens,
)
from gui.ALAutoScriptOrchDialog._helpers import VariableManager
from gui.ALAutoScriptOrchDialog._blocks import ConditionalBlock
from gui.ALAutoScriptOrchDialog._widgets import ConditionRowFrame
class ALAutoScriptOrchDialog(QDialog):
def __init__(
self,
parent = None,
existingScript: str = ""
parent = None
):
super().__init__(parent)
@@ -42,10 +30,7 @@ class ALAutoScriptOrchDialog(QDialog):
self.setupUi()
self.connectSignals()
if existingScript and existingScript.strip():
self.loadFromScript(existingScript)
else:
self.addBlock()
self.addBlock()
self.scrollLayout.addStretch()
@@ -171,126 +156,3 @@ class ALAutoScriptOrchDialog(QDialog):
QMessageBox.warning(self, "提示", "脚本内容为空,请添加至少一个操作步骤。")
return
self.accept()
@staticmethod
def precheckScriptForOrchestration(
script: str
) -> tuple[bool, str]:
return precheck(script, allowed_vars=PRESET_NAMES)
def loadFromScript(
self,
script: str
):
if not script.strip():
self.addBlock()
return
ok, err = self.precheckScriptForOrchestration(script)
if not ok:
QMessageBox.warning(
self, "无法编排",
f"脚本检查失败:\n{err}\n\n"
"请通过\"编辑\"按钮打开脚本编辑窗口进行修改。"
)
self.addBlock()
return
# Structured block data via observer-based parsing — no duplicate logic
typeIdxMap = {"IF": 0, "ELSE IF": 1, "ELSE": 2}
parsedBlocks = parseBlocks(script)
self._blocks.clear()
while self.scrollLayout.count():
item = self.scrollLayout.takeAt(0)
if item.widget():
item.widget().deleteLater()
try:
for blockType, condition, actions in parsedBlocks:
self.addBlock()
block = self._blocks[-1]
idx = typeIdxMap.get(blockType, 0)
block.typeCombo.setCurrentIndex(idx)
block.onTypeChanged(idx)
for oldStep in list(block._actionWidgets):
block.removeActionStep(oldStep)
for target, valueExpr, opType in actions:
block.addActionStep()
step = block.getActionSteps()[-1]
step.setOpType(opType)
step.loadFromScript(target, valueExpr)
if blockType in ("IF", "ELSE IF") and condition:
self._parseConditions(block, condition)
except Exception:
self._blocks.clear()
while self.scrollLayout.count():
item = self.scrollLayout.takeAt(0)
if item.widget():
item.widget().deleteLater()
self._updateBlockTypeRestrictions()
if not self._blocks:
self.addBlock()
def _parseConditions(
self,
block: ConditionalBlock,
condStr: str
):
s = condStr.strip()
if not s:
return
s = stripOuterParens(s)
orParts = splitTopLevel(s, ".OR.")
allSubConds = []
allLogics = []
for pi, part in enumerate(orParts):
part = part.strip()
if pi > 0:
allLogics.append(".OR.")
andParts = splitTopLevel(part, ".AND.")
for ai, ap in enumerate(andParts):
ap = ap.strip()
if ai > 0:
allLogics.append(".AND.")
allSubConds.append(ap)
for row in list(block._conditionRows):
block.condRowsLayout.removeWidget(row)
row.hide()
row.deleteLater()
block._conditionRows.clear()
for i, subCond in enumerate(allSubConds):
subCond = subCond.strip()
subCond = stripOuterParens(subCond)
isFirst = (i == 0)
row = ConditionRowFrame(
self._varMgr, block.blockIndex,
isFirst=isFirst, parent=block
)
if not isFirst:
row.deleteBtn.clicked.connect(
lambda _checked=False, r=row: block.removeConditionRow(r)
)
if i - 1 < len(allLogics):
logic = allLogics[i - 1]
for li in range(row.logicCombo.count()):
if row.logicCombo.itemData(li) == logic:
row.logicCombo.setCurrentIndex(li)
break
block._conditionRows.append(row)
block.condRowsLayout.addWidget(row)
subUp = subCond.upper()
if subUp in (".TRUE.", ".FALSE."):
row.loadFromParts(subUp, "", "")
else:
opSyms = [op for _, op in COMPARE_OPERATORS]
result = findOperatorIn(subCond, opSyms)
if result:
idx, op = result
leftPart = subCond[:idx].strip()
rightPart = subCond[idx + len(op):].strip()
row.loadFromParts(leftPart, op, rightPart)
else:
row.loadFromParts(subCond, "", "")
if not block._conditionRows:
block.addInitialConditionRow()
+84 -7
View File
@@ -271,6 +271,7 @@ class _DateInputContainer(QWidget):
):
super().__init__(parent)
self._dynamicItems = {} # index -> raw expression, for one-way parsed items
self.setupUi()
@@ -303,6 +304,9 @@ class _DateInputContainer(QWidget):
layout.addWidget(self._stack)
layout.addStretch()
_RE_CURRENT_DATE_OFFSET = re.compile(
r'^CURRENT_DATE\s*([+-])\s*(\d+)$', re.IGNORECASE
)
def getValue(
self
@@ -310,6 +314,9 @@ class _DateInputContainer(QWidget):
mode = self._modeCombo.currentData()
if mode == "relative":
idx = self._relCombo.currentIndex()
if idx in self._dynamicItems:
return self._dynamicItems[idx]
return self._relCombo.currentText()
return self._dateEdit.date().toString("yyyy-MM-dd")
@@ -331,6 +338,23 @@ class _DateInputContainer(QWidget):
if idx is not None:
self._modeCombo.setCurrentIndex(0)
self._relCombo.setCurrentIndex(idx)
elif self._RE_CURRENT_DATE_OFFSET.match(s):
m = self._RE_CURRENT_DATE_OFFSET.match(s)
sign = m.group(1)
n = int(m.group(2))
offset = n if sign == "+" else -n
label = f"{n}天后" if offset >= 0 else f"{n}天前"
raw = f"CURRENT_DATE {'+' if sign == '+' else '-'} {n}"
self._modeCombo.setCurrentIndex(0)
# Add dynamic item if not already present
for ci in range(self._relCombo.count()):
if ci in self._dynamicItems and self._dynamicItems[ci] == raw:
self._relCombo.setCurrentIndex(ci)
return
idx = self._relCombo.count()
self._relCombo.addItem(label)
self._dynamicItems[idx] = raw
self._relCombo.setCurrentIndex(idx)
elif s.startswith("DATE("):
self._modeCombo.setCurrentIndex(1)
m = re.match(r"DATE\((\d{4}-\d{2}-\d{2})\)", s)
@@ -565,13 +589,14 @@ def encodeValueStr(
if var_type == "Time":
if raw_value.startswith("+") or raw_value.startswith("-"):
return raw_value
if raw_value.startswith("TIME_OFFSET"):
m = re.match(r"TIME_OFFSET\(([+-]\d+),(\w+)\)", raw_value)
if m:
return m.group(1)
if raw_value.upper().startswith("TIME("):
return raw_value
return f"TIME({raw_value})"
if var_type == "Date":
if raw_value.upper().startswith("DATE("):
return raw_value
if raw_value.upper().startswith("CURRENT_DATE"):
return raw_value
relMap = {
"前天": "CURRENT_DATE - 2",
"昨天": "CURRENT_DATE - 1",
@@ -611,8 +636,11 @@ def stripOuterParens(
return s
# Pre-compiled pattern for detecting arithmetic expressions like "A + B" / "C - D"
_RE_ARITH_EXPR = re.compile(r'^.+?\s+[+-]\s+.+$')
# Pre-compiled patterns for detecting arithmetic expressions (A + B / A - B)
# Must match both spaced form (CURRENT_DATE + 1) and no-space form (RESERVE_DATE+1),
# consistent with ASEngine._resolveArithExpr.
_RE_ARITH_SPACED = re.compile(r'^(.+?)\s+([+-])\s+(.+)$')
_RE_ARITH_NOSPACE = re.compile(r'^([A-Za-z_]\w*)([+-])(\d+|[A-Za-z_]\w*)$')
def isArithExpr(
@@ -622,7 +650,56 @@ def isArithExpr(
Return True if expr looks like a two-operand arithmetic expression (A ± B).
"""
return bool(_RE_ARITH_EXPR.match(expr.strip()))
s = expr.strip()
return bool(_RE_ARITH_SPACED.match(s) or _RE_ARITH_NOSPACE.match(s))
# Pre-compiled patterns for SET value whitelist validation,
# matching the priority order of ASEngine._resolveValue.
_RE_VAL_TIME = re.compile(r'^TIME\(\d{1,2}:\d{2}\)$', re.IGNORECASE)
_RE_VAL_DATE = re.compile(r'^DATE\(\d{4}-\d{2}-\d{2}\)$', re.IGNORECASE)
_RE_VAL_ARITH_SPACED = _RE_ARITH_SPACED
_RE_VAL_ARITH_NOSPACE = _RE_ARITH_NOSPACE
_RE_VAL_VAR_REF = re.compile(r'^[A-Z_][A-Z0-9_]*$', re.IGNORECASE)
def _isValidSetValue(
value: str
) -> bool:
"""
Whitelist validation: return True if value matches one of the
legal SET-value patterns recognised by ASEngine._resolveValue.
Order matches _resolveValue priority: TIME → DATE → bool →
quoted string → int → float → arith expr → variable reference.
"""
s = value.strip()
if not s:
return False
if _RE_VAL_TIME.match(s):
return True
if _RE_VAL_DATE.match(s):
return True
if s.upper() in (".TRUE.", ".FALSE."):
return True
if (s.startswith("'") and s.endswith("'")) or (s.startswith('"') and s.endswith('"')):
return True
try:
int(s)
return True
except ValueError:
pass
try:
float(s)
return True
except ValueError:
pass
if _RE_VAL_ARITH_SPACED.match(s) or _RE_VAL_ARITH_NOSPACE.match(s):
return True
if _RE_VAL_VAR_REF.match(s):
return True
return False
def isVarReference(
@@ -1,109 +0,0 @@
"""
Orchestration observer for AutoScript scripts.
Subscribes to ASTokenizer parsing events to produce a structured
block representation for the orchestration dialog UI.
"""
from autoscript.ASObserver import ParsingObserver
from autoscript.ASTokenizer import (
ASTokenizer,
K_IF,
K_ELSE_IF,
K_ELSE,
K_ENDIF,
K_SET,
K_ADD,
K_SUB,
)
__all__ = ["ScriptOrchObserver", "parseBlocks"]
class ScriptOrchObserver(ParsingObserver):
"""
Builds an ordered list of (block_type, condition, actions) tuples
from tokenization events.
Each block:
(type: str, condition: str | None, actions: list[(target, value_expr, op_type)])
"""
def __init__(
self
):
super().__init__()
self._blocks = []
self._current_type = None
self._current_condition = None
self._current_actions = []
def onTokenParsed(
self,
kind: str | None,
data,
line_num: int,
raw_line: str
):
if kind in (K_IF, K_ELSE_IF, K_ELSE):
self._flushCurrentBlock()
self._current_type = kind
self._current_condition = data if kind != K_ELSE else None
self._current_actions = []
elif kind in (K_SET, K_ADD, K_SUB):
target, value = data
if kind == K_SET:
self._current_actions.append((target, value, "set"))
elif kind == K_ADD:
prefixed = value if value.startswith("-") else f"+{value}"
self._current_actions.append((target, prefixed, "add"))
else:
prefixed = value if value.startswith("-") else f"-{value}"
self._current_actions.append((target, prefixed, "sub"))
elif kind == K_ENDIF:
self._flushCurrentBlock()
self._current_type = None
self._current_condition = None
self._current_actions = []
def onParseComplete(
self,
statements: list
):
self._flushCurrentBlock()
def _flushCurrentBlock(
self
):
if self._current_type is not None:
self._blocks.append((
self._current_type,
self._current_condition,
list(self._current_actions),
))
@property
def blocks(
self
) -> list:
return list(self._blocks)
def parseBlocks(
script: str
) -> list:
"""
Tokenize a script via observer pipeline and return its
structured block representation.
"""
observer = ScriptOrchObserver()
ASTokenizer.tokenizeWithObservers(script, [observer])
return observer.blocks
-163
View File
@@ -1,163 +0,0 @@
"""
Pre-check observer for AutoScript scripts.
Subscribes to ASTokenizer parsing events to validate script syntax
before it reaches the orchestration dialog, eliminating duplicate parsing.
"""
from autoscript.ASObserver import ParsingObserver
from autoscript.ASTokenizer import (
K_IF,
K_ELSE_IF,
K_ELSE,
K_ENDIF,
K_SET,
K_ADD,
K_SUB,
ASTokenizer,
)
__all__ = ["ScriptPrecheckObserver", "precheck"]
class ScriptPrecheckObserver(ParsingObserver):
"""
Validates script syntax and structure during tokenization.
Checks performed:
- IF/ENDIF depth matching
- No nested IF blocks (orchestration limitation)
- ELSE IF / ELSE appear only inside an IF block
- Only allowed variables appear in SET/ADD/SUB targets
- No completely unrecognized syntax lines
"""
def __init__(
self,
allowed_vars: set = None
):
super().__init__()
self._allowed = allowed_vars or set()
self._if_depth = 0
self.errors = []
self._stmts = []
def onTokenParsed(
self,
kind: str | None,
data,
line_num: int,
raw_line: str
):
if kind == K_IF:
self._if_depth += 1
if self._if_depth > 1:
self.errors.append(
f"静态检查:错误(第{line_num}行): 检测到嵌套 IF,编排窗口不支持嵌套条件块。"
)
elif kind == K_ELSE_IF:
if self._if_depth < 1:
self.errors.append(
f"静态检查:错误(第{line_num}行): ELSE IF 前缺少 IF。"
)
elif kind == K_ELSE:
if self._if_depth < 1:
self.errors.append(
f"静态检查:错误(第{line_num}行): ELSE 前缺少 IF。"
)
elif kind == K_ENDIF:
self._if_depth -= 1
if self._if_depth < 0:
self.errors.append(
f"静态检查:错误(第{line_num}行): 多余的 ENDIF。"
)
elif kind is None:
self.errors.append(
f"静态检查:错误(第{line_num}行): 无法识别的语法 '{raw_line}'"
)
elif kind in (K_SET, K_ADD, K_SUB):
target = data[0] if isinstance(data, tuple) else ""
if self._allowed and target.upper() not in self._allowed:
self.errors.append(
f"静态检查:错误(第{line_num}行): 目标变量 '{target}' 不是预设变量,"
f"编排窗口不支持。"
)
def onParseComplete(
self,
statements: list
):
if self._if_depth != 0:
self.errors.append(
f"静态检查:错误(不适用): IF 与 ENDIF 不匹配。")
self._stmts = statements
@property
def valid(
self
) -> bool:
return len(self.errors) == 0
def getErrorMessage(
self
) -> str:
return self.errors[0] if self.errors else ""
def buildSimplifiedScript(
self
) -> str:
"""Replace all non-control-flow statements with PASS for engine validation."""
lines = []
for stmt in self._stmts:
if stmt.kind in (K_IF, K_ELSE_IF, K_ELSE, K_ENDIF):
lines.append(stmt.raw_line)
else:
lines.append("PASS")
return "\n".join(lines)
def precheck(
script: str,
allowed_vars: set = None
) -> tuple[bool, str]:
"""
Run the full precheck pipeline on a script.
Steps:
1. Create a ScriptPrecheckObserver and subscribe it to an ASTokenizer.
2. Tokenize the observer validates syntax during token events.
3. Replace action lines with PASS and run engine validation
with mock target data.
"""
if not script or not script.strip():
return True, ""
observer = ScriptPrecheckObserver(allowed_vars=allowed_vars)
ASTokenizer.tokenizeWithObservers(script, [observer])
if not observer.valid:
return False, observer.getErrorMessage()
simplified = observer.buildSimplifiedScript()
if not simplified.strip():
return True, ""
try:
from autoscript import (
registerDefaultTargetVars,
buildMockTargetData,
execute
)
registerDefaultTargetVars()
execute(simplified, buildMockTargetData())
except ValueError as e:
return False, f"运行时检查: {e}"
except Exception:
return False, "执行环境异常,请检查 AutoScript 配置。"
return True, ""
+29 -223
View File
@@ -1,8 +1,6 @@
"""
Widget components for the AutoScript orchestration dialog.
"""
import re
from PySide6.QtCore import Slot
from PySide6.QtWidgets import (
QComboBox,
@@ -24,13 +22,11 @@ from gui.ALAutoScriptOrchDialog._helpers import (
encodeValueStr,
getValueFromWidget,
isArithExpr,
isVarReference,
makeComboWidget,
makeLabel,
makeOffsetWidget,
makeValueWidget,
makeVarRefCombo,
setWidgetValue,
)
@@ -114,7 +110,23 @@ class ConditionRowFrame(QFrame):
self
):
wasBool = self._isBoolMode
boolName = None
if wasBool:
data = self.leftVarCombo.currentData()
if data:
boolName = data[0]
self._varMgr.populateCombo(self.leftVarCombo)
# Append boolean literal sentinels at the end
self.leftVarCombo.insertSeparator(self.leftVarCombo.count())
self.leftVarCombo.addItem(".TRUE.", (".TRUE.", "Boolean"))
self.leftVarCombo.addItem(".FALSE.", (".FALSE.", "Boolean"))
if wasBool and boolName:
for ci in range(self.leftVarCombo.count()):
d = self.leftVarCombo.itemData(ci)
if d and d[0] == boolName:
self.leftVarCombo.setCurrentIndex(ci)
break
def populateRhsVarCombo(
@@ -143,8 +155,14 @@ class ConditionRowFrame(QFrame):
data = self.leftVarCombo.itemData(idx)
if not data:
return
_, vartype = data
self.updateRhsLiteralWidget(vartype)
name, vartype = data
isBool = name in (".TRUE.", ".FALSE.")
self._isBoolMode = isBool
self.opCombo.setVisible(not isBool)
self._compTypeCombo.setVisible(not isBool)
self.rhsStack.setVisible(not isBool)
if not isBool:
self.updateRhsLiteralWidget(vartype)
def updateRhsLiteralWidget(
@@ -181,6 +199,8 @@ class ConditionRowFrame(QFrame):
) -> str:
data = self.leftVarCombo.currentData()
if self._isBoolMode and data:
return data[0]
if not data:
return ""
name, vartype = data
@@ -205,95 +225,6 @@ class ConditionRowFrame(QFrame):
return ""
def loadFromParts(
self,
operandName: str,
opSym: str,
valueExpr: str
):
self._rawRhsExpr = ""
self.leftVarCombo.blockSignals(True)
self.opCombo.blockSignals(True)
self._compTypeCombo.blockSignals(True)
try:
for ci in range(self.leftVarCombo.count()):
d = self.leftVarCombo.itemData(ci)
if d and d[0] == operandName:
self.leftVarCombo.setCurrentIndex(ci)
break
if opSym:
for oi in range(self.opCombo.count()):
if self.opCombo.itemData(oi) == opSym:
self.opCombo.setCurrentIndex(oi)
break
finally:
self.leftVarCombo.blockSignals(False)
self.opCombo.blockSignals(False)
self._compTypeCombo.blockSignals(False)
data = self.leftVarCombo.currentData()
vartype = data[1] if data else "String"
self.updateRhsLiteralWidget(vartype)
if not valueExpr:
return
up = valueExpr.strip().upper()
if isVarReference(valueExpr) or self._isKnownVar(up):
self._compTypeCombo.setCurrentIndex(1)
self.populateRhsVarCombo()
found = self._varMgr.findExactNameEntry(self.rhsVarCombo, up)
if found >= 0:
self.rhsVarCombo.setCurrentIndex(found)
else:
self.rhsVarCombo.addItem(up, (up, "String"))
self.rhsVarCombo.setCurrentIndex(self.rhsVarCombo.count() - 1)
elif isArithExpr(valueExpr):
self._tryLoadCondArithExpr(valueExpr, vartype)
else:
self._compTypeCombo.setCurrentIndex(0)
w = self.literalWidgets.get(vartype)
if w:
setWidgetValue(w, vartype, valueExpr)
def _tryLoadCondArithExpr(
self,
expr: str,
vartype: str
):
"""Try to decompose a condition RHS arithmetic expression into UI state."""
s = expr.strip()
m = re.match(r'^(.+?)\s+([+-])\s+(.+)$', s)
if not m:
self._rawRhsExpr = s
return
left = m.group(1).strip()
op = m.group(2).strip()
right = m.group(3).strip()
left_up = left.upper()
if vartype == "Date" and left_up == "CURRENT_DATE":
try:
n = int(right)
offset = n if op == "+" else -n
if offset in (-2, -1, 0, 1, 2):
self._compTypeCombo.setCurrentIndex(0)
w = self.literalWidgets.get("Date")
if w and hasattr(w, "setValue"):
w.setValue(s)
return
except ValueError:
pass
self._rawRhsExpr = s
def _isKnownVar(
self,
name: str
) -> bool:
return self._varMgr.getInfoByName(name) is not None
def refreshVarCombos(
self
):
@@ -301,7 +232,6 @@ class ConditionRowFrame(QFrame):
self.populateLeftVarCombo()
self.populateRhsVarCombo()
class ActionStepFrame(QFrame):
def __init__(
@@ -471,9 +401,11 @@ class ActionStepFrame(QFrame):
) -> str:
target = self.getTargetName()
op = self.opTypeCombo.currentData()
if op == "pass":
return " PASS"
if not target:
return ""
op = self.opTypeCombo.currentData()
rawVal = self._getValueRaw()
if op == "set":
vartype = self._currentTargetType
@@ -514,132 +446,6 @@ class ActionStepFrame(QFrame):
return ""
def setOpType(
self,
opType: str
):
for i in range(self.opTypeCombo.count()):
if self.opTypeCombo.itemData(i) == opType:
self.opTypeCombo.setCurrentIndex(i)
break
def loadFromScript(
self,
targetVar: str,
valueExpr: str
):
targetUp = targetVar.upper().strip()
for ci in range(self.targetCombo.count()):
d = self.targetCombo.itemData(ci)
if d and d[0] == targetUp:
self.targetCombo.setCurrentIndex(ci)
break
self._setValueFromExpr(valueExpr)
def _setValueFromExpr(
self,
expr: str
):
s = expr.strip()
if not s:
return
op = self.opTypeCombo.currentData()
if op in ("add", "sub") and s.startswith("-"):
s = s[1:]
self.opTypeCombo.setCurrentIndex(
2 if op == "add" else 1
)
up = s.upper()
if isVarReference(s):
self.valueSrcCombo.setCurrentIndex(1)
self._varMgr.populateCombo(self.existingVarCombo)
idx = self._varMgr.findExactNameEntry(self.existingVarCombo, up)
if idx >= 0:
self.existingVarCombo.setCurrentIndex(idx)
else:
self.existingVarCombo.addItem(up, (up, "String"))
self.existingVarCombo.setCurrentIndex(self.existingVarCombo.count() - 1)
elif isArithExpr(s):
self._tryLoadArithExpr(s)
else:
self.valueSrcCombo.setCurrentIndex(0)
w = self.valueStack.currentWidget()
if w:
setWidgetValue(w, self._currentTargetType, expr)
def _tryLoadArithExpr(
self,
expr: str
):
"""Try to decompose an arithmetic expression into UI state."""
s = expr.strip()
m = re.match(r'^(.+?)\s+([+-])\s+(.+)$', s)
if not m:
self._storeAsCustomExpr(s)
return
left = m.group(1).strip()
op = m.group(2).strip()
right = m.group(3).strip()
left_up = left.upper()
# CURRENT_DATE ± N for Date targets — try relative combo for ±0/1/2,
# otherwise store as custom expression to preserve relative semantics
if self._currentTargetType == "Date" and left_up == "CURRENT_DATE":
try:
n = int(right)
offset = n if op == "+" else -n
if offset in (-2, -1, 0, 1, 2):
w = self._literalWidgets.get("Date")
if w and hasattr(w, "setValue"):
w.setValue(s)
self.valueSrcCombo.setCurrentIndex(0)
return
except ValueError:
pass
self._storeAsCustomExpr(s)
return
# CURRENT_TIME ± N for Time targets — map to add/sub with offset
if self._currentTargetType == "Time" and left_up == "CURRENT_TIME":
try:
hours = int(right)
if op == "-":
hours = -hours
self.opTypeCombo.setCurrentIndex(
1 if hours >= 0 else 2
)
self.valueSrcCombo.setCurrentIndex(0)
w = self._offsetWidgets.get("Time")
if w and hasattr(w, "setValue"):
w.setValue(str(abs(hours)))
return
except ValueError:
pass
self._storeAsCustomExpr(s)
def _storeAsCustomExpr(
self,
expr: str
):
"""Store a raw expression in the variable combo when it can't be decomposed."""
self.valueSrcCombo.setCurrentIndex(1)
self._varMgr.populateCombo(self.existingVarCombo)
found = self._varMgr.findExactNameEntry(self.existingVarCombo, expr)
if found < 0:
self.existingVarCombo.addItem(expr, (expr, self._currentTargetType))
self.existingVarCombo.setCurrentIndex(self.existingVarCombo.count() - 1)
else:
self.existingVarCombo.setCurrentIndex(found)
def refreshVarCombos(
self
):
+8 -21
View File
@@ -17,7 +17,6 @@ from PySide6.QtGui import QDesktopServices
from PySide6.QtWidgets import QLabel, QDialog, QWidget, QSpinBox, QHBoxLayout, QVBoxLayout, QGridLayout, QDateTimeEdit, QGroupBox, QPushButton
from gui.resources.ui.Ui_ALTimerTaskAddDialog import Ui_ALTimerTaskAddDialog
from gui.ALAutoScriptOrchDialog import ALAutoScriptOrchDialog
from utils.TimerUtils import TimerUtils
@@ -102,10 +101,6 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
self.AutoScriptLayout.setContentsMargins(3, 3, 3, 3)
self.AutoScriptLayout.setSpacing(3)
autoScriptBtnLayout = QHBoxLayout()
self.AutoScriptSetButton = QPushButton("设置指令")
self.AutoScriptSetButton.setMinimumHeight(25)
self.AutoScriptSetButton.setFixedWidth(130)
autoScriptBtnLayout.addWidget(self.AutoScriptSetButton)
self.AutoScriptPreviewButton = QPushButton("编辑")
self.AutoScriptPreviewButton.setMinimumHeight(25)
self.AutoScriptPreviewButton.setFixedWidth(60)
@@ -136,6 +131,7 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
)
self.AutoScriptGroupBox.setVisible(False)
self.__auto_script = ""
self.__mock_target_data = None
def loadTask(
@@ -173,6 +169,9 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
self.__auto_script = auto_script
self.AutoScriptStatusLabel.setText("已设置")
self.AutoScriptStatusLabel.setStyleSheet("color: #4CAF50;")
mock_data = task.get("mock_target_data")
if mock_data:
self.__mock_target_data = mock_data
self.ConfirmButton.setText("保存")
@@ -184,7 +183,6 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
self.ConfirmButton.clicked.connect(self.accept)
self.TimerTypeComboBox.currentIndexChanged.connect(self.onTimerTypeComboBoxIndexChanged)
self.RepeatCheckBox.toggled.connect(self.onRepeatCheckBoxToggled)
self.AutoScriptSetButton.clicked.connect(self.onSetAutoScript)
self.AutoScriptPreviewButton.clicked.connect(self.onPreviewAutoScript)
self.AutoScriptHelpButton.clicked.connect(self.onAutoScriptHelp)
@@ -220,6 +218,7 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
task_data["status"] = ALTimerTaskStatus.PENDING
task_data["executed"] = False
task_data["repeat_auto_script"] = self.__auto_script
task_data["mock_target_data"] = self.__mock_target_data
else:
task_data = {
"name": name,
@@ -232,6 +231,7 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
"executed": False,
"repeat": self.RepeatCheckBox.isChecked(),
"repeat_auto_script": self.__auto_script,
"mock_target_data": self.__mock_target_data,
}
repeat = self.RepeatCheckBox.isChecked()
@@ -292,27 +292,14 @@ class ALTimerTaskAddDialog(QDialog, Ui_ALTimerTaskAddDialog):
self.SunCheckBox.setEnabled(checked)
self.AutoScriptGroupBox.setVisible(checked)
@Slot()
def onSetAutoScript(self):
dlg = ALAutoScriptOrchDialog(self, existingScript=self.__auto_script)
if dlg.exec() == QDialog.DialogCode.Accepted:
script = dlg.getScript()
self.__auto_script = script
if script:
self.AutoScriptStatusLabel.setText("已设置")
self.AutoScriptStatusLabel.setStyleSheet("color: #4CAF50;")
else:
self.AutoScriptStatusLabel.setText("未设置")
self.AutoScriptStatusLabel.setStyleSheet("color: #969696;")
dlg.deleteLater()
@Slot()
def onPreviewAutoScript(self):
from gui.ALAutoScriptEditDialog import ALAutoScriptEditDialog
dlg = ALAutoScriptEditDialog(self, self.__auto_script)
dlg = ALAutoScriptEditDialog(self, self.__auto_script, self.__mock_target_data)
if dlg.exec() == QDialog.DialogCode.Accepted:
script = dlg.getScript()
self.__auto_script = script
self.__mock_target_data = dlg.getMockData()
if script:
self.AutoScriptStatusLabel.setText("已设置")
self.AutoScriptStatusLabel.setStyleSheet("color: #4CAF50;")