/* 资产视图:TAB 容器(全部/VPS/域名/Cloudflare/订阅/AI账号) * 注意:专有视图(DomainsView 等)在后续脚本才定义,必须在 setup 运行时求值, * 不能在组件定义处直接引用(会因加载顺序抛 ReferenceError)。 */ const AssetsView = { template: `
🏢 平台:{{ providerName(store.filterProvider) }}
`, setup() { // tip 为 TAB 悬浮说明:让用户一眼明白每个分类装的是什么 const tabs = [ { key: '', label: '全部', tip: '所有类型的资产' }, { key: 'vps', label: 'VPS', tip: '云服务器(可查看资源监控)' }, { key: 'domain', label: '域名', tip: '域名与解析' }, { key: 'cloudflare', label: 'Cloudflare', tip: 'Cloudflare 账号下的资源' }, { key: 'subscription', label: '付费资产', tip: '有费用或到期日、需要续费的项目(跨类型汇总)' }, { key: 'ai_agent', label: 'AI账号', tip: 'ChatGPT 等 AI 服务账号与余额' }, ]; const tabView = Vue.computed(() => { const t = store.assetTab; if (t === 'domain') return DomainsView; if (t === 'cloudflare') return CloudflareView; if (t === 'subscription') return SubscriptionsView; if (t === 'ai_agent') return AIAccountsView; return null; }); // 通用列表:全部显示所有,VPS 前端过滤类型;「异常」状态是后端不支持的组合筛选,也走前端过滤 const list = Vue.computed(() => { let arr = store.assets; if (store.assetTab === 'vps') arr = arr.filter(a => a.asset_type === 'vps'); if (store.filterStatus === 'abnormal') arr = arr.filter(a => a.status === 'stopped' || a.status === 'expired'); return arr; }); function switchTab(k) { store.assetTab = k; // 专有视图(域名/Cloudflare/订阅/AI)有自己的筛选逻辑,通用的类型/状态筛选切过去后会失效, // 直接清空避免用户以为筛过了但实际看不出来 if (k !== '' && k !== 'vps' && (store.filterType || store.filterStatus)) { store.filterType = ''; store.filterStatus = ''; loadAssets(); } } // 平台筛选标签显示名:filterProvider 存 slug,回退显示原值 function providerName(slug) { const p = store.providers.find(x => x.slug === slug); return p ? p.name : slug; } async function refreshBalance(a) { if (!confirm('刷新「' + a.name + '」的余额?')) return; try { const r = await Api.post('/assets/' + a.id + '/refresh-balance', {}); alert('余额已更新:' + r.balance + ' ' + r.currency); await loadAssets(); } catch (e) { alert('刷新失败:' + e.message); } } async function syncAllAI() { if (!confirm('同步所有 AI 账号的余额?')) return; try { const r = await Api.post('/sync/ai-balances', {}); // skipped:平台本身没余额接口(如 OpenAI 已下线 billing),不属于失败 alert('同步完成:成功 ' + r.success + ',失败 ' + r.failed + (r.skipped ? ',跳过 ' + r.skipped + '(平台无余额接口)' : '') + (r.errors && r.errors.length ? '\n' + r.errors.join('\n') : '')); await loadAssets(); } catch (e) { alert('同步失败:' + e.message); } } async function syncAllAssets() { if (!confirm('从云端拉取所有已配 API 凭证账号下的 VPS 与域名?\n\n会按 (平台, 云端实例ID) 去重:已存在则更新状态/到期日,不存在则新建。')) return; try { const r = await Api.post('/sync/assets', {}); const lines = [ '同步完成:成功 ' + r.success + ',失败 ' + r.failed, '新增 ' + r.created + ',更新 ' + r.updated, ]; // 未配凭证 / 平台无适配器属于「跳过」而非失败,但必须如实报出来, // 否则用户以为同步过了、实际一条都没拉 if (r.skipped_no_config) lines.push('跳过(账号未配 API 凭证):' + r.skipped_no_config); if (r.skipped_no_adapter) lines.push('跳过(平台无可用适配器):' + r.skipped_no_adapter); if (r.errors && r.errors.length) lines.push('\n' + r.errors.join('\n')); alert(lines.join('\n')); await Promise.all([loadAssets(), loadOverview()]); } catch (e) { alert('同步失败:' + e.message); } } return { store, Fmt, tabs, tabView, list, switchTab, providerName, reload: loadAssets, clearProvider: clearProviderFilter, edit: openAssetEdit, del: deleteAsset, viewServer, refreshBalance, syncAllAI, syncAllAssets, canControl: assetCanControl, openAct: openActions }; }, };