Files
TurboSu/static/js/pages/settings.js
T
2026-07-25 15:23:40 +08:00

186 lines
8.2 KiB
JavaScript

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-server-port')?.addEventListener('change', async (e) => {
Toast.show('服务器端口修改后需要重启程序', 'warning');
});
document.getElementById('settings-restart-telemetry')?.addEventListener('click', async () => {
await API.stopTelemetry();
await API.startTelemetry();
Toast.show('遥测监听已重启', 'success');
});
document.getElementById('btn-import-plugin')?.addEventListener('click', () => this._importPlugin());
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 data = await API.exportGamePlugin(pluginId);
if (data) {
this._downloadJson(`plugin_${pluginId}.json`, data);
Toast.show('插件已导出', 'success');
}
}
if (removeBtn) {
const pluginId = removeBtn.dataset.pluginId;
if (confirm('确定移除这个游戏插件?')) {
await API.removeGamePlugin(pluginId);
Toast.show('插件已移除', 'info');
this.render();
}
}
});
},
async _importPlugin() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
const text = await file.text();
const data = JSON.parse(text);
if (data.type === 'game_plugin') {
await API.installGamePlugin(data);
Toast.show('插件安装成功', 'success');
this.render();
} else {
Toast.show('无效的插件文件格式', 'error');
}
} catch (err) {
Toast.show('文件解析失败: ' + err.message, 'error');
}
};
input.click();
},
_downloadJson(filename, data) {
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
},
_gamePluginListHtml(games) {
if (!games || games.length === 0) {
return '<p style="color:var(--text-tertiary);font-size:13px;">暂无游戏插件</p>';
}
return games.map(g => `
<div class="glass-card" style="padding:16px;margin-bottom:10px;display:flex;align-items:center;justify-content:space-between;">
<div>
<div style="font-weight:600;">${g.name}</div>
<div style="font-size:12px;color:var(--text-tertiary);">
${g.description || ''} | v${g.version} | ${g.author || ''}
<span class="badge ${g.is_builtin ? 'badge-info' : 'badge-success'}" style="margin-left:6px;">
${g.is_builtin ? '内置' : '社区'}
</span>
</div>
</div>
<div style="display:flex;gap:6px;">
<button class="btn btn-sm btn-secondary btn-export-plugin" data-plugin-id="${g.id}">导出</button>
${!g.is_builtin ? `<button class="btn btn-sm btn-danger btn-remove-plugin" data-plugin-id="${g.id}">移除</button>` : ''}
</div>
</div>
`).join('');
},
_template(cfg, games) {
return `
<div class="animated">
<h2 style="font-size:24px;font-weight:700;margin-bottom:24px;">设置</h2>
<div class="settings-section">
<h3>连接设置</h3>
<div class="settings-row">
<div>
<div class="settings-label">遥测监听端口</div>
<div class="settings-desc">游戏内设置的数据输出端口</div>
</div>
<div class="settings-control">
<input type="number" id="settings-telemetry-port" value="${cfg.telemetry_port || 20777}" min="1024" max="65535">
</div>
</div>
<div class="settings-row">
<div>
<div class="settings-label">Web 服务器端口</div>
<div class="settings-desc">Web UI 服务的端口号</div>
</div>
<div class="settings-control">
<input type="number" id="settings-server-port" value="${cfg.server_port || 9527}" min="80" max="65535" disabled>
</div>
</div>
<div class="settings-row">
<div>
<div class="settings-label">重启遥测监听</div>
<div class="settings-desc">修改端口或切换游戏后需要重启监听</div>
</div>
<div class="settings-control">
<button id="settings-restart-telemetry" class="btn btn-secondary btn-sm">重启监听</button>
</div>
</div>
</div>
<div class="settings-section">
<h3>游戏插件管理</h3>
<div style="display:flex;gap:8px;margin-bottom:16px;">
<button id="btn-import-plugin" class="btn btn-secondary btn-sm">📥 导入插件</button>
<button id="btn-reload-plugins" class="btn btn-secondary btn-sm">🔄 重新加载</button>
</div>
<div id="settings-game-list">
${this._gamePluginListHtml(games)}
</div>
<div class="glass-card" style="padding:16px;margin-top:12px;font-size:12px;color:var(--text-tertiary);line-height:1.6;">
<strong style="color:var(--text-secondary);">社区开发指南:</strong><br>
1. 创建一个包含 <code>manifest.json</code> 和 <code>parser.py</code> 的文件夹<br>
2. <code>manifest.json</code> 定义游戏元信息,<code>parser.py</code> 实现 <code>get_parser()</code> 函数<br>
3. <code>get_parser()</code> 返回对象需实现 <code>game_id()</code> 和 <code>parse(data, addr)</code> 方法<br>
4. 通过"导入插件"或放入 <code>games/user/</code> 目录安装<br>
5. 导出你的插件分享给社区!
</div>
</div>
<div class="settings-section">
<h3>关于</h3>
<div class="settings-row">
<div>
<div class="settings-label">TurboSu</div>
<div class="settings-desc">赛车遥测仪表盘 v1.0.0</div>
</div>
</div>
</div>
</div>`;
}
};
window.PageSettings = PageSettings;