/* 设置视图 */ const SettingsView = { template: `

API Key(写操作鉴权)

若服务端 .env 配置了 API_KEY,请在此填写相同的值,前端写操作会自动携带。

通知与续费提醒

已配置渠道: {{ notifyChannels.join('、') }} 未配置(在 .env 设置 TELEGRAM_*/SMTP_*)
{{ notifyResult }}

外观

数据管理(迁移/备份)

导出所有资产为 JSON,可导入到新部署的实例,实现数据迁移。

{{ dataResult }}

版本信息

当前运行版本:v{{ version }} ({{ commit }})

与本地 git rev-parse --short HEAD 比对,可确认线上是否为最新代码。

`, setup() { const notifyChannels = Vue.ref([]); const notifyResult = Vue.ref(''); const dataResult = Vue.ref(''); const version = Api.CFG.version || 'dev'; const commit = Api.CFG.commit || 'unknown'; Vue.onMounted(async () => { try { const r = await Api.get('/notify/channels'); notifyChannels.value = r.channels || []; } catch (e) { /* ignore */ } }); async function testNotify() { notifyResult.value = '发送中…'; try { const r = await Api.post('/notify/test', {}); notifyResult.value = r.notifications.map(n => '[' + n.channel + '] ' + (n.ok ? '成功' : '失败:' + n.message)).join('\n'); } catch (e) { notifyResult.value = '失败:' + e.message; } } async function checkRenewals() { notifyResult.value = '检查中…'; try { const r = await Api.post('/notify/renewals?send=true', {}); let text = '即将到期 ' + r.expiring_count + ' 项(阈值 ' + r.threshold_days + ' 天)'; if (r.expiring.length) text += '\n' + r.expiring.map(i => '• ' + i.name + ' [' + i.provider + '] ' + i.days + ' 天后').join('\n'); if (r.notifications && r.notifications.length) text += '\n\n通知:' + r.notifications.map(n => '[' + n.channel + '] ' + (n.ok ? '成功' : '失败:' + n.message)).join(','); notifyResult.value = text; } catch (e) { notifyResult.value = '失败:' + e.message; } } async function exportData() { dataResult.value = '导出中…'; try { const r = await Api.get('/assets/export'); const blob = new Blob([JSON.stringify(r, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'vps-manager-export-' + new Date().toISOString().slice(0, 10) + '.json'; a.click(); URL.revokeObjectURL(url); dataResult.value = '导出成功:' + r.count + ' 个资产'; } catch (e) { dataResult.value = '导出失败:' + e.message; } } function onImportFile(e) { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = async (ev) => { dataResult.value = '导入中…'; try { const data = JSON.parse(ev.target.result); const r = await Api.post('/assets/import', data); dataResult.value = '导入完成:新增 ' + r.created + ',更新 ' + r.updated + (r.errors && r.errors.length ? ',错误 ' + r.errors.length : ''); await loadAssets(); } catch (err) { dataResult.value = '导入失败:' + err.message; } }; reader.readAsText(file); } return { store, settings, save: saveSettings, applyDark, notifyChannels, notifyResult, testNotify, checkRenewals, dataResult, exportData, onImportFile, version, commit }; }, };