/* Copyright (c) 2025-2026 马建军. All rights reserved. * 专有软件 — 未经授权禁止复制、传播、转售。 * 详见 LICENSE.zh-CN.txt */ (function () { function bootSettingsPage() { if (!document.querySelector('.settings-page')) return; var sel = document.getElementById('position-sizing-mode'); var lotsField = document.getElementById('field-fixed-lots'); var amountField = document.getElementById('field-fixed-amount'); function syncSizingFields() { if (!sel) return; var isAmount = sel.value === 'amount'; if (lotsField) lotsField.hidden = isAmount; if (amountField) amountField.hidden = !isAmount; } if (sel && !sel.dataset.settingsBound) { sel.dataset.settingsBound = '1'; sel.addEventListener('change', syncSizingFields); } syncSizingFields(); var aiProviderSel = document.getElementById('ai-provider-select'); function syncAiProviderCards() { if (!aiProviderSel) return; var val = aiProviderSel.value; document.querySelectorAll('.settings-ai-card[data-ai-provider]').forEach(function (card) { var active = card.getAttribute('data-ai-provider') === val; card.classList.toggle('is-active', active); var badge = card.querySelector('.settings-ai-card-head .badge'); if (badge) badge.style.display = active ? '' : 'none'; }); } if (aiProviderSel && !aiProviderSel.dataset.settingsBound) { aiProviderSel.dataset.settingsBound = '1'; aiProviderSel.addEventListener('change', syncAiProviderCards); } syncAiProviderCards(); var SETTINGS_FOLD_KEY = 'qihuo_settings_fold'; function setSettingsFold(el, collapsed) { if (!el) return; el.classList.toggle('is-collapsed', collapsed); var head = el.querySelector('.settings-fold-head'); if (head) head.setAttribute('aria-expanded', collapsed ? 'false' : 'true'); } function saveSettingsFoldState() { var state = {}; document.querySelectorAll('[data-settings-fold]').forEach(function (el) { state[el.getAttribute('data-settings-fold')] = el.classList.contains('is-collapsed'); }); try { localStorage.setItem(SETTINGS_FOLD_KEY, JSON.stringify(state)); } catch (e) { /* ignore */ } } function loadSettingsFoldState() { try { var raw = localStorage.getItem(SETTINGS_FOLD_KEY); if (!raw) return; var state = JSON.parse(raw); document.querySelectorAll('[data-settings-fold]').forEach(function (el) { var key = el.getAttribute('data-settings-fold'); if (Object.prototype.hasOwnProperty.call(state, key)) { setSettingsFold(el, !!state[key]); } }); } catch (e) { /* ignore */ } } document.querySelectorAll('.settings-fold-head').forEach(function (btn) { if (btn.dataset.settingsBound) return; btn.dataset.settingsBound = '1'; btn.addEventListener('click', function () { var panel = btn.closest('[data-settings-fold]'); if (!panel) return; setSettingsFold(panel, !panel.classList.contains('is-collapsed')); saveSettingsFoldState(); }); }); loadSettingsFoldState(); var CTP_FOLD_KEY = 'qihuo_ctp_fold'; function setCtpFold(el, collapsed) { if (!el) return; el.classList.toggle('is-collapsed', collapsed); var head = el.querySelector('.settings-ctp-fold-head'); if (head) head.setAttribute('aria-expanded', collapsed ? 'false' : 'true'); } function saveCtpFoldState() { var state = {}; document.querySelectorAll('[data-ctp-fold]').forEach(function (el) { state[el.getAttribute('data-ctp-fold')] = el.classList.contains('is-collapsed'); }); try { localStorage.setItem(CTP_FOLD_KEY, JSON.stringify(state)); } catch (e) { /* ignore */ } } function loadCtpFoldState() { try { var raw = localStorage.getItem(CTP_FOLD_KEY); if (!raw) return; var state = JSON.parse(raw); document.querySelectorAll('[data-ctp-fold]').forEach(function (el) { var key = el.getAttribute('data-ctp-fold'); if (Object.prototype.hasOwnProperty.call(state, key)) { setCtpFold(el, !!state[key]); } }); } catch (e) { /* ignore */ } } document.querySelectorAll('.settings-ctp-fold-head').forEach(function (btn) { if (btn.dataset.settingsBound) return; btn.dataset.settingsBound = '1'; btn.addEventListener('click', function () { var panel = btn.closest('[data-ctp-fold]'); if (!panel) return; setCtpFold(panel, !panel.classList.contains('is-collapsed')); saveCtpFoldState(); }); }); loadCtpFoldState(); function initBackupPanel() { var uploadBtn = document.getElementById('backup-upload-btn'); var uploadInput = document.getElementById('backup-upload-file'); var statusEl = document.getElementById('backup-restore-status'); var pollTimer = null; function setRestoreStatus(data) { if (!statusEl || !data) return; var state = data.state || 'idle'; statusEl.hidden = state === 'idle'; statusEl.classList.remove('is-running', 'is-error', 'is-done'); if (state === 'pending' || state === 'running') { statusEl.classList.add('is-running'); statusEl.textContent = data.message || '恢复进行中…'; } else if (state === 'done') { statusEl.classList.add('is-done'); statusEl.textContent = data.message || '恢复完成'; } else if (state === 'error') { statusEl.classList.add('is-error'); statusEl.textContent = '恢复失败:' + (data.message || '未知错误'); } else { statusEl.textContent = data.message || ''; } } function setBusy(busy) { document.querySelectorAll('[data-backup-restore], #backup-upload-btn').forEach(function (btn) { btn.disabled = !!busy; }); } function pollRestoreStatus() { fetch('/api/backup/restore/status', { credentials: 'same-origin' }) .then(function (res) { return res.json(); }) .then(function (data) { setRestoreStatus(data); var active = data.state === 'pending' || data.state === 'running'; setBusy(active); if (active) { if (!pollTimer) { pollTimer = window.setInterval(pollRestoreStatus, 2500); } } else if (pollTimer) { window.clearInterval(pollTimer); pollTimer = null; if (data.state === 'done') { window.setTimeout(function () { window.location.reload(); }, 1200); } } }) .catch(function () { /* ignore */ }); } if (uploadBtn && uploadInput && !uploadBtn.dataset.settingsBound) { uploadBtn.dataset.settingsBound = '1'; uploadBtn.addEventListener('click', function () { var file = uploadInput.files && uploadInput.files[0]; if (!file) { window.alert('请先选择 .tar.gz 备份文件'); return; } if (!/\.tar\.gz$/i.test(file.name)) { window.alert('仅支持 .tar.gz 格式'); return; } var form = new FormData(); form.append('file', file); uploadBtn.disabled = true; uploadBtn.textContent = '上传中…'; fetch('/api/backup/upload', { method: 'POST', body: form, credentials: 'same-origin' }) .then(function (res) { return res.json().then(function (body) { return { ok: res.ok, body: body }; }); }) .then(function (result) { if (!result.ok) { throw new Error((result.body && result.body.error) || '上传失败'); } window.alert('上传成功:' + (result.body.name || file.name)); window.location.reload(); }) .catch(function (err) { window.alert(err.message || '上传失败'); }) .finally(function () { uploadBtn.disabled = false; uploadBtn.textContent = '上传并校验'; }); }); } document.querySelectorAll('[data-backup-restore]').forEach(function (btn) { if (btn.dataset.settingsBound) return; btn.dataset.settingsBound = '1'; btn.addEventListener('click', function () { var name = btn.getAttribute('data-backup-restore') || ''; if (!name) return; var ok = window.confirm( '确定要恢复备份「' + name + '」吗?\n\n' + '将停止服务并覆盖当前数据库、uploads 与 .env,完成后自动重启。\n' + '此操作不可撤销,请确认已做好当前数据备份。' ); if (!ok) return; var typed = window.prompt('请输入 RESTORE 确认恢复:'); if (typed !== 'RESTORE') { if (typed !== null) window.alert('确认文字不正确,已取消'); return; } btn.disabled = true; fetch('/api/backup/restore', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ filename: name, confirm: 'RESTORE' }) }) .then(function (res) { return res.json().then(function (body) { return { ok: res.ok, body: body }; }); }) .then(function (result) { if (!result.ok) { throw new Error((result.body && result.body.error) || '恢复启动失败'); } setRestoreStatus({ state: 'pending', message: result.body.message || '恢复已开始…' }); setBusy(true); pollRestoreStatus(); }) .catch(function (err) { window.alert(err.message || '恢复启动失败'); btn.disabled = false; }); }); }); if (statusEl && !statusEl.hidden) { pollRestoreStatus(); } } initBackupPanel(); var ctpForm = document.getElementById('ctp-settings-form'); if (ctpForm && !ctpForm.dataset.settingsBound) { ctpForm.dataset.settingsBound = '1'; ctpForm.addEventListener('submit', function (ev) { var ctpCard = document.querySelector('[data-settings-fold="ctp"]'); if (ctpCard) setSettingsFold(ctpCard, false); var simnowFold = document.querySelector('[data-ctp-fold="simnow"]'); if (simnowFold) setCtpFold(simnowFold, false); var pwd = document.getElementById('simnow_password'); var pwdVal = pwd && pwd.value ? pwd.value.trim() : ''; var pwdWasSet = ctpForm.getAttribute('data-simnow-pwd-set') === '1'; if (pwdWasSet && !pwdVal) { var ok = window.confirm( 'SimNow 交易密码为空,保存后不会更新密码(仍用旧密码)。\n\n' + '若快期已改密,请取消后在「交易密码」框手打新密码再保存。\n\n仍要保存其他项?' ); if (!ok) ev.preventDefault(); } }); } } if (window.qihuoPageBoot) window.qihuoPageBoot(bootSettingsPage, '.settings-page'); else if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bootSettingsPage); else bootSettingsPage(); })();