第 8 篇:模板库 + 一键场景加载
引言
在之前的开发中,我们已经实现了宏编辑器(MacroEditorPanel),允许用户手动编写和运行宏脚本。但对于现场维护工具来说,许多场景(如停车场巡航、周界警戒)是常见的重复操作。如果每次都从零编写宏,不仅效率低下,还容易出错。为此,本篇引入模板库功能:预置常见宏模板,支持参数化占位符,用户可一键加载到编辑器中编辑/运行。
这大大简化了用户操作,例如,选择"停车场巡航"模板,即可自动填充脚本,用户只需调整参数(如循环次数)即可运行。模板基于 JSON 文件管理,便于扩展和维护。同时,我们在 UI 中集成模板选择界面,实现"一键场景加载"。此外,模板系统支持参数输入对话框(通过 ParamProvider 类),允许用户在加载或运行时动态填充值。
关键收益:
- 标准化:统一常见场景的宏逻辑,避免用户自行编写出错。
- 参数化:使用 Mustache-style 占位符(如 {{preset}}),加载后可通过 UI 对话框自动填充或手动替换。
- 可扩展:用户或开发者可添加新模板,无需修改代码。
- 集成性:模板库作为独立面板,嵌入 RightPanel 的 Tabs 中,便于切换。
本篇将逐步说明模板文件结构、参数化机制、UI 集成及配套代码。通过此功能,Pelco KBD300A 模拟器进一步向"现场维护工具"转型,提供开箱即用的场景解决方案。
1. 模板文件结构:templates.json
模板库的核心是 templates.json 文件,位于 resources/ 目录下(可动态加载)。每个模板是一个 JSON 对象,包含名称、描述、脚本及参数列表。
文件结构示例(基于当前项目文件):
json
{
"templates": [
{
"name": "停车场巡航",
"description": "调用预置位 1-4,每位停留 5 秒,循环 3 次",
"script": "loop(3){\n send_preset(1)\n delay(5000)\n send_preset(2)\n delay(5000)\n send_preset(3)\n delay(5000)\n send_preset(4)\n delay(5000)\n}",
"params": []
},
{
"name": "周界警戒",
"description": "辅助开关 + PTZ 移动,带参数",
"script": "aux_on({{aux_id}})\npan_tilt({{pan}}, {{tilt}})\ndelay(2000)\nsend_preset({{preset}})",
"params": ["aux_id", "pan", "tilt", "preset"]
},
{
"name": "球机调试",
"description": "球机基本调试:Zoom/Focus/Iris 测试",
"script": "zoom(1, in)\ndelay(1000)\nzoom(1, out)\ndelay(1000)\nfocus(1, far)\ndelay(1000)\nfocus(1, near)\ndelay(1000)\niris(1, open)\ndelay(1000)\niris(1, close)",
"params": []
},
{
"name": "巡航 + 报警联动",
"description": "带参数的巡航宏:可设置巡航次数、预置位范围、报警 AUX 编号",
"script": "loop({{loops}}){\n # 巡航预置位范围\n for(p={{start}}; p<={{end}}; p++){\n send_preset(1, p)\n delay(3000)\n }\n}\n\n# 报警联动动作\naux_on(1, {{aux_id}})\ndelay(2000)\nsend_preset(1, {{alarm_preset}})\n",
"params": ["loops", "start", "end", "aux_id", "alarm_preset"]
}
]
}
- name:模板名称,用于 UI 显示。
- description:简要描述,用于提示用户。
- script:宏脚本主体,支持我们的宏语法(loop/for/delay/send_preset 等)。
- params:参数列表(数组),表示脚本中需要替换的占位符(如 {{aux_id}})。如果为空,则模板无参数,直接加载。
设计考虑:
- 使用 JSON 便于解析和扩展(通过 TemplateLibrary 类加载)。
- 脚本中占位符采用 {{var}} 或 {{{var}}} 格式(类似 Mustache),优先匹配 {{{var}}} 以避免冲突。
- 默认提供多个示例模板,覆盖巡航、警戒、调试、联动场景。
- 文件路径:resources/templates.json,不存在时自动创建默认模板(DEFAULT_TEMPLATES)。
2. 模板参数化机制
参数化是模板的核心,提升复用性。核心类包括:
-
TemplateRenderer(core/template/renderer.py):负责替换占位符。
- 支持 {{key}} 和 {{{key}}} 两种格式,先替换 {{{key}}} 避免冲突。
- 示例:
TemplateRenderer.render(script, {"aux_id": 1})将 {{aux_id}} 替换为 1。
-
ParamProvider(core/template/params.py):提供参数输入对话框。
- 支持多种类型:int, float, str, bool 等。
- 通过 QInputDialog 获取用户输入,支持默认值和描述。
- 示例:
param_provider.get_params([{"name": "aux_id", "type": "int"}])返回 {"aux_id": 用户输入值}。
-
TemplateController 和 TemplateRunner(core/template/controller.py 和 runner.py):集成渲染和运行。
- Controller 处理加载到编辑器;Runner 使用 MacroEngine 执行渲染后的脚本。
示例流程:
- 用户选择"周界警戒"模板。
- 系统弹出参数对话框:输入 aux_id, pan, tilt, preset。
- 渲染脚本:替换 {{aux_id}} 等为用户值。
- 填充到宏编辑器或直接运行。
这比纯静态模板更灵活,适合现场自定义。
3. UI 集成:模板库面板
我们在 RightPanel 的 Tabs 中添加一个新面板:TemplateLibraryPanel,包含模板列表、描述、操作按钮。
UI 布局:
-
搜索框:QLineEdit,支持名称/描述过滤。
-
模板列表:QListWidget 显示所有模板名称,支持右键菜单(编辑/删除)。
-
描述区域:QTextEdit 显示选中模板的描述和脚本预览。
-
按钮栏:加载到编辑器、直接运行、新增/编辑/删除模板。
-
集成到 RightPanel :在 ui/right_panel/panel.py 的
__init__中添加:pythonself.template_panel = TemplateLibraryPanel(self) self.tabs.addTab(self.template_panel, "模板库")
用户交互:
- 选择模板 → 显示描述 → 点击"加载" → 参数对话框(若有) → 渲染脚本填充到宏编辑器(切换到宏编辑 Tab)。
- 点击"运行" → 参数对话框(若有) → 渲染并直接执行模板脚本。
- 新增/编辑:弹出 TemplateEditDialog,支持输入名称、描述、脚本、参数列表。
信号连接(在 RightPanel):
-
template_panel.load_template.connect(self.macro_editor.set_script):加载到编辑器。 -
template_panel.run_template.connect(self.run_macro):直接运行。

4. 配套代码:模板管理类与面板实现
模板管理类(TemplateLibrary)
位于 core/template/library.py:
python
class TemplateLibrary:
def __init__(self, path):
self.path = path
self.templates = []
self.load()
def load(self):
if not os.path.exists(self.path):
logger.warning(f"模板文件不存在: {self.path}")
self._create_default_templates()
return self.templates
try:
with open(self.path, "r", encoding="utf-8") as f:
data = json.load(f)
self.templates = data.get("templates", [])
logger.info(f"加载了 {len(self.templates)} 个模板")
except Exception as e:
logger.error(f"加载模板失败: {e}")
self.templates = []
self._create_default_templates()
return self.templates
def save(self):
try:
with open(self.path, "w", encoding="utf-8") as f:
json.dump({"templates": self.templates}, f, indent=4, ensure_ascii=False)
logger.info("模板已保存")
except Exception as e:
logger.error(f"保存模板失败: {e}")
def find(self, name: str):
return next((t for t in self.templates if t["name"] == name), None)
def _create_default_templates(self):
"""创建默认模板"""
self.templates = [
# ... 默认模板列表,与 templates.json 一致
]
logger.info("创建了默认模板")
- 支持加载/保存/查找模板。
- 错误处理:加载失败时回退到默认模板。
模板库面板(TemplateLibraryPanel)
位于 ui/right_panel/template_library.py(简化版代码):
python
class TemplateLibraryPanel(QtWidgets.QWidget):
load_template = QtCore.pyqtSignal(str)
run_template = QtCore.pyqtSignal(str)
error_occurred = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super().__init__(parent)
self.lib = TemplateLibrary("resources/templates.json")
self.param_provider = ParamProvider() # 参数输入提供器
self._init_ui()
self._reload()
def _init_ui(self):
# ... 布局代码,与文章中示例一致,包括搜索、列表、描述、按钮、右键菜单
def _load_to_editor(self):
tpl = self.lib.templates[self.template_list.currentRow()]
script = self._prepare_script(tpl)
if script:
self.load_template.emit(script)
def _run_template(self):
tpl = self._current_tpl()
if tpl:
script = self._prepare_script(tpl)
if script:
self.run_template.emit(script)
def _prepare_script(self, tpl):
params = tpl.get("params", [])
if params:
values = self.param_provider.get_params(params)
if values is None:
QMessageBox.warning(self, "参数错误", "参数获取取消或失败")
return None
try:
return TemplateRenderer.render(tpl["script"], values)
except Exception as e:
QMessageBox.critical(self, "渲染错误", f"参数渲染失败: {str(e)}")
return None
return tpl["script"]
- 使用信号解耦:不直接依赖宏编辑器。
_prepare_script集成参数获取和渲染。
5. 测试与优化
- 单元测试:在 tests/right_panel/test_template_library_ui.py 中测试 JSON 加载、参数提取、渲染。
- 集成测试:在 tests/integration/test_template_to_editor.py 中测试模板加载到编辑器的流程。
- 端到端测试:在 tests/e2e/test_template_e2e.py 中模拟用户选择模板 → 输入参数 → 加载/运行。
- 优化:如果 params 非空,弹出对话框自动渲染;支持参数类型校验(int/float 等);JSON 损坏时回退到 DEFAULT_TEMPLATES。
- 边界处理:无模板时显示默认;参数取消时不加载。
结语
通过模板库,我们为 Pelco KBD300A 模拟器注入了"一键场景加载"的实用性,用户无需深究宏语法,即可快速部署常见维护任务。这标志着项目从"模拟器"向"智能工具"的转变。下篇将聚焦实时接收数据解析与协议反馈处理,进一步增强现场处理能力。