feat: 版本号管理机制(VERSION文件+git哈希,部署/启动/UI/health均展示) + 平台账号管理(accounts表CRUD,资产所属账号候选联动,重命名同步引用)

This commit is contained in:
gouki
2026-08-08 20:07:03 +00:00
parent 4883837dda
commit a33a2b71de
15 changed files with 373 additions and 16 deletions
+28 -1
View File
@@ -9,6 +9,28 @@ const ProvidersView = {
<div class="flex gap-2">
<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="addAccount" 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>
</div>
<!-- 账号管理:同一平台多账号时区分资产归属,资产表单「所属账号」引用此处 -->
<div 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-2">
<h3 class="font-semibold text-sm">👤 我的账号({{ store.accounts.length }}</h3>
<button @click="addAccount" class="text-xs text-blue-600 dark:text-blue-400">+ 新增账号</button>
</div>
<p v-if="!store.accounts.length" class="text-xs text-slate-400">暂无账号。添加后,可在资产表单「所属账号」中快速选择,区分同一平台下的多个账号。</p>
<div v-else class="divide-y divide-slate-100 dark:divide-slate-800">
<div v-for="a in store.accounts" :key="a.id" class="py-2 flex items-center gap-2">
<div class="flex-1 min-w-0">
<div class="text-sm truncate">{{ a.name }}
<span v-if="a.platform" class="text-xs text-slate-400">@ {{ platformName(a.platform) }}</span>
</div>
<div class="text-xs text-slate-400">关联 {{ a.asset_count }} 个资产<span v-if="a.remark"> · {{ a.remark }}</span></div>
</div>
<button @click="editAccount(a)" class="text-xs text-blue-600 dark:text-blue-400">编辑</button>
<button @click="delAccount(a)" class="text-xs text-red-600 dark:text-red-400">删除</button>
</div>
</div>
</div>
<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">
@@ -64,6 +86,11 @@ const ProvidersView = {
function serviceList(p) {
return (p.services || '').split(',').map(s => s.trim()).filter(Boolean);
}
// 账号所属平台显示名:platform 存的是 slug,回退显示原值
function platformName(slug) {
const p = store.providers.find(x => x.slug === slug);
return p ? p.name : slug;
}
Vue.onMounted(async () => {
try { const r = await Api.get('/providers/adapters'); supported.value = r.supported_types || []; capabilities.value = r.capabilities || {}; } catch (e) { /* ignore */ }
});
@@ -96,6 +123,6 @@ const ProvidersView = {
} catch (e) { results[p.id] = { ok: false, text: '✗ ' + e.message }; }
finally { syncing[p.id] = false; }
}
return { store, Fmt, testing, syncing, results, countOf, serviceList, isSupported, canSync, fmtTime, test, sync, seed: seedProviders, create: openProviderCreate, edit: openProviderEdit, del: deleteProvider };
return { store, Fmt, testing, syncing, results, countOf, serviceList, platformName, isSupported, canSync, fmtTime, test, sync, seed: seedProviders, create: openProviderCreate, edit: openProviderEdit, del: deleteProvider, addAccount: () => openAccountCreate(''), editAccount: openAccountEdit, delAccount: deleteAccount };
},
};
+8 -1
View File
@@ -37,11 +37,18 @@ const SettingsView = {
</div>
<div v-if="dataResult" class="mt-2 text-xs px-3 py-2 rounded-lg bg-slate-50 dark:bg-slate-800/50">{{ dataResult }}</div>
</div>
<div class="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-4">
<h3 class="font-semibold text-sm mb-2">版本信息</h3>
<div class="text-xs text-slate-500">当前运行版本:<span class="text-slate-700 dark:text-slate-300 font-mono">v{{ version }} ({{ commit }})</span></div>
<p class="text-xs text-slate-400 mt-1">与本地 git rev-parse --short HEAD 比对,可确认线上是否为最新代码。</p>
</div>
</div>`,
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 */ }
});
@@ -91,6 +98,6 @@ const SettingsView = {
};
reader.readAsText(file);
}
return { store, settings, save: saveSettings, applyDark, notifyChannels, notifyResult, testNotify, checkRenewals, dataResult, exportData, onImportFile };
return { store, settings, save: saveSettings, applyDark, notifyChannels, notifyResult, testNotify, checkRenewals, dataResult, exportData, onImportFile, version, commit };
},
};