feat(ui): 平台页默认隐藏未使用平台(无资产/账号/API配置),可一键展开全部,偏好持久化

This commit is contained in:
gouki
2026-08-08 23:36:13 +00:00
parent fcb3153c50
commit 6829b211ac
2 changed files with 20 additions and 4 deletions
+1 -1
View File
@@ -1 +1 @@
0.3.2 0.3.3
+19 -3
View File
@@ -6,13 +6,14 @@ const ProvidersView = {
<div class="text-xs text-slate-500 bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-800 rounded-lg px-3 py-2"> <div class="text-xs text-slate-500 bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-800 rounded-lg px-3 py-2">
🏢 平台 = 资产的服务商(Vultr / 新网 / OpenAI…)。在「资产」中添加项目时选择归属平台;支持 API 对接的平台配置好密钥后,可「测试连接」或「同步资产」拉取真实数据。 🏢 平台 = 资产的服务商(Vultr / 新网 / OpenAI…)。在「资产」中添加项目时选择归属平台;支持 API 对接的平台配置好密钥后,可「测试连接」或「同步资产」拉取真实数据。
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2 flex-wrap">
<button @click="seed" class="text-sm px-3 py-1.5 rounded-lg border border-slate-300 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800">初始化预设平台</button> <button @click="seed" class="text-sm px-3 py-1.5 rounded-lg border border-slate-300 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800">初始化预设平台</button>
<button @click="create" class="text-sm px-3 py-1.5 rounded-lg bg-blue-600 text-white hover:bg-blue-700">+ 自定义平台</button> <button @click="create" class="text-sm px-3 py-1.5 rounded-lg bg-blue-600 text-white hover:bg-blue-700">+ 自定义平台</button>
<button @click="manageAccounts" class="text-sm px-3 py-1.5 rounded-lg border border-blue-300 dark:border-blue-700 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20">👤 账号管理</button> <button @click="manageAccounts" class="text-sm px-3 py-1.5 rounded-lg border border-blue-300 dark:border-blue-700 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20">👤 账号管理</button>
<button @click="toggleShowAll" class="text-sm px-3 py-1.5 rounded-lg border border-slate-300 dark:border-slate-700 text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800">{{ showAll ? '只显示在用' : '显示全部平台(' + store.providers.length + '' }}</button>
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
<div v-for="p in store.providers" :key="p.id" class="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-4"> <div v-for="p in visibleProviders" :key="p.id" 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-start"> <div class="flex justify-between items-start">
<div> <div>
<div class="font-medium text-sm">{{ p.name }}</div> <div class="font-medium text-sm">{{ p.name }}</div>
@@ -44,6 +45,9 @@ const ProvidersView = {
<div v-if="results[p.id]" class="mt-2 text-xs px-2 py-1.5 rounded-lg break-words" :class="results[p.id].ok ? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400' : 'bg-amber-500/10 text-amber-600 dark:text-amber-400'">{{ results[p.id].text }}</div> <div v-if="results[p.id]" class="mt-2 text-xs px-2 py-1.5 rounded-lg break-words" :class="results[p.id].ok ? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400' : 'bg-amber-500/10 text-amber-600 dark:text-amber-400'">{{ results[p.id].text }}</div>
</div> </div>
</div> </div>
<button v-if="!showAll && hiddenCount" @click="toggleShowAll" class="w-full text-center text-xs text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 py-2">
已隐藏 {{ hiddenCount }} 个未使用平台(无资产/账号/API 配置)· 点击显示全部
</button>
</div>`, </div>`,
setup() { setup() {
const testing = Vue.reactive({}); const testing = Vue.reactive({});
@@ -67,6 +71,18 @@ const ProvidersView = {
function accountsCountOf(p) { function accountsCountOf(p) {
return store.accounts.filter(a => a.platform === p.slug).length; return store.accounts.filter(a => a.platform === p.slug).length;
} }
// 在用判定:有资产/有账号/已配置 API 任一即算在用
function isUsed(p) {
return countOf(p) > 0 || accountsCountOf(p) > 0 || p.has_api_config;
}
// 默认隐藏未使用平台,避免预设平台撑满屏幕;偏好持久化到 localStorage
const showAll = Vue.ref(localStorage.getItem('vps_show_all_providers') === '1');
function toggleShowAll() {
showAll.value = !showAll.value;
localStorage.setItem('vps_show_all_providers', showAll.value ? '1' : '0');
}
const visibleProviders = Vue.computed(() => showAll.value ? store.providers : store.providers.filter(isUsed));
const hiddenCount = Vue.computed(() => store.providers.length - visibleProviders.value.length);
function serviceList(p) { function serviceList(p) {
return (p.services || '').split(',').map(s => s.trim()).filter(Boolean); return (p.services || '').split(',').map(s => s.trim()).filter(Boolean);
} }
@@ -102,6 +118,6 @@ const ProvidersView = {
} catch (e) { results[p.id] = { ok: false, text: '✗ ' + e.message }; } } catch (e) { results[p.id] = { ok: false, text: '✗ ' + e.message }; }
finally { syncing[p.id] = false; } finally { syncing[p.id] = false; }
} }
return { store, Fmt, testing, syncing, results, countOf, accountsCountOf, serviceList, isSupported, canSync, fmtTime, test, sync, seed: seedProviders, create: openProviderCreate, edit: openProviderEdit, del: deleteProvider, openAccounts: (p) => openAccountsView(p.slug), manageAccounts: () => openAccountsView(null) }; return { store, Fmt, testing, syncing, results, countOf, accountsCountOf, serviceList, showAll, toggleShowAll, visibleProviders, hiddenCount, isSupported, canSync, fmtTime, test, sync, seed: seedProviders, create: openProviderCreate, edit: openProviderEdit, del: deleteProvider, openAccounts: (p) => openAccountsView(p.slug), manageAccounts: () => openAccountsView(null) };
}, },
}; };