feat: Cloudflare视图(按账号分组展示zone/worker/r2/tunnel/mail子资产)

This commit is contained in:
gouki
2026-08-02 23:53:18 +00:00
parent af17294f24
commit b049eaf03c
+49 -1
View File
@@ -416,6 +416,52 @@ const SubscriptionsView = {
}, },
}; };
const SUB_TYPE_LABELS = { zone: '解析', worker: 'Worker', r2: 'R2', tunnel: 'Tunnel', mail: 'Mail', dns_record: 'DNS记录', other: '其他' };
const CloudflareView = {
template: `
<div class="space-y-4">
<div v-if="!groups.length" class="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-8 text-center text-slate-400 text-sm">
暂无 Cloudflare 子资产(新增资产时选 Cloudflare 类型即可)
</div>
<div v-for="g in groups" :key="g.account" class="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-4">
<div class="flex justify-between items-center mb-3">
<div class="font-semibold text-sm">☁️ {{ g.account || '未指定账号' }}</div>
<span class="text-xs text-slate-400">{{ g.items.length }} 个子资产</span>
</div>
<div class="space-y-2">
<div v-for="a in g.items" :key="a.id" class="flex items-center justify-between px-3 py-2 rounded-lg bg-slate-50 dark:bg-slate-800/50 text-sm">
<div class="min-w-0 flex items-center gap-2">
<span class="text-xs px-2 py-0.5 rounded-full shrink-0" :class="subTypeBadge(a.cloudflare_detail.sub_type)">{{ SUB_TYPE_LABELS[a.cloudflare_detail.sub_type] || a.cloudflare_detail.sub_type }}</span>
<span class="font-medium truncate">{{ a.cloudflare_detail.sub_name || a.cloudflare_detail.zone_name || a.name }}</span>
<span v-if="a.cloudflare_detail.zone_name && a.cloudflare_detail.sub_type !== 'zone'" class="text-xs text-slate-400">@{{ a.cloudflare_detail.zone_name }}</span>
</div>
<div class="flex items-center gap-3 text-xs shrink-0">
<span v-if="a.cloudflare_detail.status" class="text-slate-500">{{ a.cloudflare_detail.status }}</span>
<button @click="edit(a)" class="text-blue-600 dark:text-blue-400 hover:underline">编辑</button>
</div>
</div>
</div>
</div>
</div>`,
setup() {
const groups = computed(() => {
const cfAssets = store.assets.filter(a => a.asset_type === 'cloudflare' && a.cloudflare_detail);
const map = {};
for (const a of cfAssets) {
const acc = a.cloudflare_detail.account_email || '';
if (!map[acc]) map[acc] = { account: a.cloudflare_detail.account_email, items: [] };
map[acc].items.push(a);
}
return Object.values(map);
});
function subTypeBadge(t) {
return { zone: 'bg-blue-500/10 text-blue-600 dark:text-blue-400', worker: 'bg-orange-500/10 text-orange-600 dark:text-orange-400', r2: 'bg-purple-500/10 text-purple-600 dark:text-purple-400', tunnel: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400', mail: 'bg-pink-500/10 text-pink-600 dark:text-pink-400', dns_record: 'bg-slate-500/10 text-slate-600', other: 'bg-slate-500/10 text-slate-500' }[t] || 'bg-slate-500/10 text-slate-500';
}
return { store, groups, subTypeBadge, SUB_TYPE_LABELS, edit: openAssetEdit };
},
};
const ProvidersView = { const ProvidersView = {
template: ` template: `
<div class="space-y-3"> <div class="space-y-3">
@@ -979,12 +1025,13 @@ const AppRoot = {
{ key: 'assets', label: '资产', icon: '📦' }, { key: 'assets', label: '资产', icon: '📦' },
{ key: 'servers', label: '服务器', icon: '🖥️' }, { key: 'servers', label: '服务器', icon: '🖥️' },
{ key: 'domains', label: '域名', icon: '🌐' }, { key: 'domains', label: '域名', icon: '🌐' },
{ key: 'cloudflare', label: 'Cloudflare', icon: '☁️' },
{ key: 'subscriptions', label: '订阅', icon: '💳' }, { key: 'subscriptions', label: '订阅', icon: '💳' },
{ key: 'ai', label: 'AI账号', icon: '🤖' }, { key: 'ai', label: 'AI账号', icon: '🤖' },
{ key: 'providers', label: '平台', icon: '🏢' }, { key: 'providers', label: '平台', icon: '🏢' },
{ key: 'settings', label: '设置', icon: '⚙️' }, { key: 'settings', label: '设置', icon: '⚙️' },
]; ];
const viewMap = { dashboard: 'dashboard-view', assets: 'assets-view', servers: 'servers-view', domains: 'domains-view', subscriptions: 'subscriptions-view', ai: 'ai-view', providers: 'providers-view', settings: 'settings-view' }; const viewMap = { dashboard: 'dashboard-view', assets: 'assets-view', servers: 'servers-view', domains: 'domains-view', cloudflare: 'cloudflare-view', subscriptions: 'subscriptions-view', ai: 'ai-view', providers: 'providers-view', settings: 'settings-view' };
const currentNav = computed(() => navs.find(n => n.key === store.view) || navs[0]); const currentNav = computed(() => navs.find(n => n.key === store.view) || navs[0]);
const viewComponent = computed(() => viewMap[store.view] || 'dashboard-view'); const viewComponent = computed(() => viewMap[store.view] || 'dashboard-view');
function go(key) { store.view = key; if (key !== 'servers') store.serverMetrics = {}; if (key === 'ai') { store.filterType = ''; store.filterStatus = ''; store.search = ''; loadAssets(); } } function go(key) { store.view = key; if (key !== 'servers') store.serverMetrics = {}; if (key === 'ai') { store.filterType = ''; store.filterStatus = ''; store.search = ''; loadAssets(); } }
@@ -1006,6 +1053,7 @@ app.component('assets-view', AssetsView);
app.component('providers-view', ProvidersView); app.component('providers-view', ProvidersView);
app.component('servers-view', ServersView); app.component('servers-view', ServersView);
app.component('domains-view', DomainsView); app.component('domains-view', DomainsView);
app.component('cloudflare-view', CloudflareView);
app.component('subscriptions-view', SubscriptionsView); app.component('subscriptions-view', SubscriptionsView);
app.component('ai-view', AIAccountsView); app.component('ai-view', AIAccountsView);
app.component('settings-view', SettingsView); app.component('settings-view', SettingsView);