const PageSettings = { _el: null, init() { this._el = document.getElementById('page-settings'); }, async render() { const cfg = await API.getConfig(); const games = await API.getGames(); this._el.innerHTML = this._template(cfg, games); this._bindEvents(); }, _bindEvents() { document.getElementById('settings-telemetry-port')?.addEventListener('change', async (e) => { await API.updateConfig({ telemetry_port: parseInt(e.target.value) || 20777 }); Toast.show('遥测端口已更新,重启监听后生效', 'info'); }); document.getElementById('settings-restart-telemetry')?.addEventListener('click', async () => { await API.stopTelemetry(); await API.startTelemetry(); Toast.show('遥测监听已重启', 'success'); }); document.getElementById('btn-import-plugin')?.addEventListener('click', () => { const input = document.createElement('input'); input.type = 'file'; input.accept = '.tsp'; input.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; const formData = new FormData(); formData.append('file', file); try { const res = await fetch('/api/games/install', { method: 'POST', body: formData }); const data = await res.json(); if (data.ok) { Toast.show('游戏插件安装成功', 'success'); this.render(); } else Toast.show('安装失败', 'error'); } catch (err) { Toast.show('安装失败', 'error'); } }; input.click(); }); document.getElementById('btn-reload-plugins')?.addEventListener('click', async () => { await API.reloadGamePlugins(); Toast.show('插件已重新加载', 'success'); this.render(); }); document.getElementById('settings-game-list')?.addEventListener('click', async (e) => { const exportBtn = e.target.closest('.btn-export-plugin'); const removeBtn = e.target.closest('.btn-remove-plugin'); if (exportBtn) { const pluginId = exportBtn.dataset.pluginId; const a = document.createElement('a'); a.href = `/api/games/${pluginId}/export`; a.download = `${pluginId}.tsp`; a.click(); Toast.show('正在下载 .tsp 文件...', 'info'); } if (removeBtn) { const pluginId = removeBtn.dataset.pluginId; if (confirm('确定移除这个游戏插件?')) { await API.removeGamePlugin(pluginId); Toast.show('插件已移除', 'info'); this.render(); } } }); document.getElementById('btn-import-dashboard')?.addEventListener('click', () => { const input = document.createElement('input'); input.type = 'file'; input.accept = '.tsd'; input.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; const formData = new FormData(); formData.append('file', file); try { const res = await fetch('/api/dashboards/import', { method: 'POST', body: formData }); const data = await res.json(); if (data.ok) Toast.show('仪表盘导入成功', 'success'); else Toast.show('导入失败', 'error'); } catch (err) { Toast.show('导入失败', 'error'); } }; input.click(); }); }, _gamePluginListHtml(games) { if (!games || games.length === 0) { return '

暂无游戏插件

'; } return games.map(g => `
${g.name}
${g.description || ''} | v${g.version} | ${g.author || ''} ${g.is_builtin ? '内置' : '社区'}
${!g.is_builtin ? `` : ''}
`).join(''); }, _template(cfg, games) { return `

设置

连接设置

遥测监听端口
游戏内设置的数据输出端口 (默认 20777)
重启遥测监听
修改端口或切换游戏后需要重启

仪表盘管理

右键点击仪表盘卡片即可导出

游戏插件管理

${this._gamePluginListHtml(games)}
社区插件开发指南:
1. 创建包含 manifest.json + parser.py 的文件夹
2. manifest.json 定义元信息,parser.py 需有 get_parser() 函数
3. 返回对象实现 game_id()parse(data, addr) 方法
4. 打包时只需要 zip 这两个文件,重命名后缀为 .tsp 即可导入
5. 完整的 TelemetryData 字段参考见 server/telemetry/data.py

关于

TurboSu
赛车遥测仪表盘 v1.0.0 · Yei.J. (AskaEth)
`; } }; window.PageSettings = PageSettings;