fix: reuse upstream keys for account import

This commit is contained in:
SmartUp Developer
2026-05-24 23:18:40 +08:00
parent 6044b00685
commit 3a31d185a4
6 changed files with 253 additions and 27 deletions
+1
View File
@@ -123,6 +123,7 @@ export interface GeneratedUpstreamKey {
imported_account_id: string | null
imported_at: string | null
created_at: string | null
has_key_value: boolean
}
export interface GenerateKeysByGroupsForm {
+76 -17
View File
@@ -98,6 +98,16 @@
<div class="panel-title">我的网站分组</div>
<div class="panel-sub">{{ selectedWebsite?.name || '请选择网站' }}</div>
</div>
<el-select
:model-value="selectedWebsite?.id"
size="small"
filterable
class="site-switcher"
placeholder="切换网站"
@change="onSelectedWebsiteChange"
>
<el-option v-for="site in websites" :key="site.id" :label="site.name" :value="site.id" />
</el-select>
<el-button size="small" :disabled="!selectedWebsite" :loading="groupsLoading" @click="loadWebsiteGroups">拉取分组</el-button>
</div>
<el-table :data="websiteGroups" v-loading="groupsLoading" row-key="id" size="small" style="width:100%">
@@ -116,11 +126,24 @@
<div class="panel">
<div class="panel-head">
<div class="panel-title">分组绑定</div>
<div>
<div class="panel-title">分组绑定</div>
<div class="panel-sub">{{ selectedWebsite?.name || '请选择网站' }}</div>
</div>
<el-select
:model-value="selectedWebsite?.id"
size="small"
filterable
class="site-switcher"
placeholder="切换网站"
@change="onSelectedWebsiteChange"
>
<el-option v-for="site in websites" :key="site.id" :label="site.name" :value="site.id" />
</el-select>
<el-button size="small" text :disabled="websites.length === 0" @click="openBindingCreate(selectedWebsite || websites[0])">新增绑定</el-button>
</div>
<div class="binding-list" v-loading="bindingLoading">
<div v-for="binding in bindings" :key="binding.id" class="binding-item">
<div v-for="binding in selectedWebsiteBindings" :key="binding.id" class="binding-item">
<div class="binding-main">
<div class="binding-title">{{ binding.website_name }} / {{ binding.target_group_name || binding.target_group_id }}</div>
<div class="binding-meta">
@@ -135,7 +158,7 @@
<el-button size="small" text type="danger" @click="deleteBinding(binding)"><el-icon><Delete /></el-icon></el-button>
</div>
</div>
<div v-if="!bindingLoading && bindings.length === 0" class="empty-hint">暂无绑定</div>
<div v-if="!bindingLoading && selectedWebsiteBindings.length === 0" class="empty-hint">暂无绑定</div>
</div>
</div>
</div>
@@ -331,7 +354,7 @@
<el-icon><Refresh /></el-icon>刷新导入状态
</el-button>
<span v-if="importSyncStatus" style="font-size:12px;color:var(--text-muted);margin-left:8px">
已校验 {{ importSyncStatus.total }} 清除 {{ importSyncStatus.cleared }} 个失效标记
已校验已导入标记 {{ importSyncStatus.total }} 清除 {{ importSyncStatus.cleared }} 个失效标记
</span>
</el-col>
</el-row>
@@ -366,6 +389,9 @@
<el-button size="small" text @click="selectAllImportableKeys">全选可导入 Key</el-button>
<el-button size="small" text @click="clearImportAccountSelection">清空</el-button>
</div>
<div v-if="importGeneratedKeys.length" class="import-stats">
总计 {{ importGeneratedKeys.length }} 现可导入 {{ importableGeneratedKeys.length }} 已导入 {{ importedGeneratedKeyCount }} 无明文 {{ missingPlaintextKeyCount }}
</div>
<el-select
v-model="importAccountsForm.upstream_key_ids"
multiple
@@ -573,6 +599,7 @@ const importSourceGroups = computed(() => snapshotsByUpstream.value[importGroups
function isImportableGeneratedKey(item: GeneratedUpstreamKey) {
return item.id !== null
&& item.has_key_value
&& item.status !== 'failed'
&& !(item.imported_website_id === importAccountsForm.value.website_id && item.imported_account_id)
}
@@ -581,6 +608,21 @@ const importableGeneratedKeys = computed(() =>
importGeneratedKeys.value.filter(isImportableGeneratedKey),
)
const importedGeneratedKeyCount = computed(() =>
importGeneratedKeys.value.filter((item) =>
item.imported_website_id === importAccountsForm.value.website_id && Boolean(item.imported_account_id),
).length,
)
const missingPlaintextKeyCount = computed(() =>
importGeneratedKeys.value.filter((item) => !item.has_key_value).length,
)
const selectedWebsiteBindings = computed(() => {
if (!selectedWebsite.value) return []
return bindings.value.filter((binding) => binding.website_id === selectedWebsite.value?.id)
})
const selectedAccountGroups = computed(() => {
const selected = new Set(importAccountsForm.value.upstream_key_ids)
const rows = importableGeneratedKeys.value.filter((item) => item.id !== null && selected.has(item.id))
@@ -605,6 +647,14 @@ async function loadWebsites() {
}
}
async function onSelectedWebsiteChange(value: number | string) {
const nextId = Number(value)
const next = websites.value.find((site) => site.id === nextId)
if (!next) return
selectedWebsite.value = next
await loadWebsiteGroups()
}
async function loadUpstreamGroups() {
const upstreamRes = await upstreamsApi.list()
upstreams.value = upstreamRes.data
@@ -682,12 +732,13 @@ async function loadLogs() {
async function syncImportStatus() {
const websiteId = importAccountsForm.value.website_id
const upstreamId = importAccountsForm.value.upstream_id
if (!websiteId || !upstreamId) return
if (!websiteId || !upstreamId) return false
syncingImportStatus.value = true
let reloaded = false
try {
const res = await websitesApi.syncImportedUpstreamKeys(websiteId, { upstream_id: upstreamId })
// 校验请求完成时表单未切换
if (importAccountsForm.value.website_id !== websiteId || importAccountsForm.value.upstream_id !== upstreamId) return
if (importAccountsForm.value.website_id !== websiteId || importAccountsForm.value.upstream_id !== upstreamId) return false
const items = res.data.items
importSyncStatus.value = {
total: items.length,
@@ -699,12 +750,14 @@ async function syncImportStatus() {
}
if (importAccountsForm.value.upstream_id === upstreamId) {
await loadImportGeneratedKeys(upstreamId)
reloaded = true
}
} catch (e: any) {
ElMessage.error(e.response?.data?.detail || '同步导入状态失败')
} finally {
syncingImportStatus.value = false
}
return reloaded
}
async function loadImportGeneratedKeys(upstreamId: number) {
@@ -977,30 +1030,26 @@ async function openImportAccounts(site?: WebsiteData | null) {
}
importAccountResults.value = []
importSyncStatus.value = null
await Promise.all([
loadImportTargetGroups(importAccountsForm.value.website_id),
loadImportGeneratedKeys(importAccountsForm.value.upstream_id),
])
await loadImportTargetGroups(importAccountsForm.value.website_id)
// 打开弹窗后自动同步导入状态(校验远端账号是否仍存在)
await syncImportStatus()
await loadImportGeneratedKeys(importAccountsForm.value.upstream_id)
const reloaded = await syncImportStatus()
if (!reloaded) await loadImportGeneratedKeys(importAccountsForm.value.upstream_id)
importAccountsDialog.value = true
}
async function onImportAccountWebsiteChange(value: number) {
importAccountsForm.value.target_group_map = {}
await loadImportTargetGroups(value)
await syncImportStatus()
await loadImportGeneratedKeys(importAccountsForm.value.upstream_id)
const reloaded = await syncImportStatus()
if (!reloaded) await loadImportGeneratedKeys(importAccountsForm.value.upstream_id)
}
async function onImportAccountUpstreamChange(value: number) {
importAccountsForm.value.upstream_key_ids = []
importAccountsForm.value.target_group_map = {}
importAccountResults.value = []
await loadImportGeneratedKeys(value)
await syncImportStatus()
await loadImportGeneratedKeys(value)
const reloaded = await syncImportStatus()
if (!reloaded) await loadImportGeneratedKeys(value)
}
function onPlatformModeChange(value: string) {
@@ -1199,6 +1248,11 @@ onMounted(loadAll)
color: var(--el-color-danger);
}
.site-switcher {
width: 180px;
flex-shrink: 0;
}
.binding-actions {
display: flex;
align-items: center;
@@ -1216,6 +1270,11 @@ onMounted(loadAll)
--el-button-border-color: var(--el-color-danger-light-5, #fbc4c4);
}
.binding-list { min-height: 120px; }
.import-stats {
margin: 0 0 8px;
color: var(--text-muted);
font-size: 12px;
}
.binding-item {
display: flex;
align-items: center;