feat: persist browser sessions and update admin workflows

This commit is contained in:
liumangmang
2026-05-29 16:00:43 +08:00
parent e3151a7ea6
commit c5778bb3e7
19 changed files with 829 additions and 369 deletions
+38 -91
View File
@@ -1,23 +1,11 @@
<template>
<div class="shell-page shell-page-fluid page-section websites-page">
<div class="page-header surface-card page-block">
<div class="page-heading">
<p class="page-kicker">Sync Orchestration</p>
<h2 class="page-title">网站管理</h2>
<p class="page-desc">管理自己的 sub2api 网站并把网站分组倍率同步到上游监听结果</p>
</div>
<el-button type="primary" @click="openWebsiteCreate">
<el-icon><Plus /></el-icon> 新增网站
</el-button>
</div>
<div class="panel">
<div class="panel-head">
<div class="panel-title">网站</div>
<div class="panel-title">检测与变更控制台</div>
<div class="panel-actions">
<el-button size="small" text :disabled="websites.length === 0" @click="openImportGroups(selectedWebsite || websites[0])">导入上游分组</el-button>
<el-button size="small" text :disabled="websites.length === 0" @click="openImportAccounts(selectedWebsite || websites[0])">导入为账号管理账号</el-button>
<el-button size="small" text @click="loadAll">刷新</el-button>
<el-button size="small" type="primary" @click="openWebsiteCreate">新增网站</el-button>
</div>
</div>
<el-table :data="websites" v-loading="websiteLoading" row-key="id" style="width:100%">
@@ -48,43 +36,16 @@
<span v-else class="muted"></span>
</template>
</el-table-column>
<el-table-column label="操作" width="240" align="right">
<el-table-column label="操作" width="340">
<template #default="{ row }">
<div class="action-row">
<el-tooltip content="编辑网站配置" placement="top" :show-after="300">
<el-button size="small" text class="btn-edit" @click="openWebsiteEdit(row)">
<el-icon class="btn-edit-icon"><Edit /></el-icon><span>编辑</span>
</el-button>
</el-tooltip>
<el-tooltip content="查看分组" placement="top" :show-after="300">
<el-button size="small" circle text @click="selectWebsite(row)">
<el-icon><Grid /></el-icon>
</el-button>
</el-tooltip>
<el-dropdown trigger="click" @command="(cmd: string) => handleMoreAction(cmd, row)">
<el-button size="small" text class="btn-more" :loading="row._testing">
更多<el-icon v-if="!row._testing" class="el-icon--right"><ArrowDown /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="test" :disabled="row._testing">
<el-icon><Connection /></el-icon>连接测试
</el-dropdown-item>
<el-dropdown-item command="binding">
<el-icon><Link /></el-icon>新增绑定
</el-dropdown-item>
<el-dropdown-item command="importGroups">
<el-icon><Upload /></el-icon>导入上游分组
</el-dropdown-item>
<el-dropdown-item command="importAccounts">
<el-icon><Key /></el-icon>导入为账号管理账号
</el-dropdown-item>
<el-dropdown-item divided command="delete" class="btn-more-delete">
<el-icon><Delete /></el-icon>删除
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button size="small" text @click="openWebsiteEdit(row)" title="编辑网站配置(API凭证、参数等)">编辑</el-button>
<el-button size="small" text @click="selectWebsite(row)" title="查看当前网站的分组和绑定配置">查看</el-button>
<el-button size="small" text @click="testWebsite(row)" :loading="row._testing" title="仅验证连通性,测试API是否正常响应">测试连接</el-button>
<el-button size="small" text @click="openBindingCreate(row)" title="为此网站创建一个新的上游倍率绑定规则">新增绑定</el-button>
<el-button size="small" text @click="openImportGroups(row)" title="一键从上游导入可用分组到此网站">导入分组</el-button>
<el-button size="small" text @click="openImportAccounts(row)" title="将此网站下的所有账户批量导入账号管理">导入账号</el-button>
<el-button size="small" text type="danger" @click="deleteWebsite(row)" title="删除此网站及其所有的绑定配置">删除</el-button>
</div>
</template>
</el-table-column>
@@ -380,7 +341,25 @@
</el-col>
<el-col :span="12">
<el-form-item label="优先级">
<el-input-number v-model="importAccountsForm.priority" :min="0" style="width:100%" />
<template #label>
<span>优先级</span>
<el-tooltip content="按倍率自动分配优先级:倍率最低的上游分组优先级最高(priority=1),依次递增" placement="top" :show-after="300">
<el-icon style="margin-left:4px;vertical-align:middle;color:var(--text-muted)"><WarningFilled /></el-icon>
</el-tooltip>
</template>
<div style="display:flex;align-items:center;gap:8px">
<el-switch
v-model="importAccountsForm.auto_priority_by_rate"
active-text="按倍率自动分配"
inactive-text="手动设置"
/>
<el-input-number
v-if="!importAccountsForm.auto_priority_by_rate"
v-model="importAccountsForm.priority"
:min="0"
style="width:160px"
/>
</div>
</el-form-item>
</el-col>
</el-row>
@@ -458,7 +437,7 @@ import { computed, onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
import dayjs from 'dayjs'
import { ArrowDown, Delete, Edit, Plus, Grid, Connection, Link, Upload, Key, Refresh } from '@element-plus/icons-vue'
import { ArrowDown, Delete, Edit, Plus, Grid, Connection, Link, Upload, Key, Refresh, WarningFilled } from '@element-plus/icons-vue'
import {
upstreamsApi,
websitesApi,
@@ -571,6 +550,7 @@ const importAccountsForm = ref({
platform_mode: 'auto',
concurrency: 10,
priority: 1,
auto_priority_by_rate: true,
})
const importAccountResults = ref<ImportAccountItem[]>([])
@@ -1027,6 +1007,7 @@ async function openImportAccounts(site?: WebsiteData | null) {
platform_mode: 'auto',
concurrency: 10,
priority: 1,
auto_priority_by_rate: true,
}
importAccountResults.value = []
importSyncStatus.value = null
@@ -1151,6 +1132,7 @@ async function submitImportAccounts() {
platform_mode: importAccountsForm.value.platform_mode,
concurrency: importAccountsForm.value.concurrency,
priority: importAccountsForm.value.priority,
auto_priority_by_rate: importAccountsForm.value.auto_priority_by_rate,
})
importAccountResults.value = res.data.items
ElMessage[res.data.success ? 'success' : 'warning'](res.data.message)
@@ -1205,47 +1187,12 @@ onMounted(loadAll)
.error-text { font-size: 12px; color: var(--color-danger); cursor: pointer; }
.rate-value { font-weight: 600; color: var(--color-primary-strong); font-family: monospace; }
.action-row {
display: flex;
align-items: center;
justify-content: flex-end;
flex-wrap: nowrap;
gap: 6px;
min-width: 0;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0;
}
.action-row .el-button.is-circle {
width: 26px;
height: 26px;
margin-left: 0;
}
.action-row .btn-edit {
color: var(--text-primary);
font-weight: 500;
gap: 3px;
padding: 0 6px;
white-space: nowrap;
flex-shrink: 0;
}
.action-row .btn-edit-icon {
font-size: 13px;
}
.action-row .btn-edit:hover {
color: var(--el-color-primary);
background: var(--el-color-primary-light-9);
border-radius: 4px;
}
.action-row .btn-more {
color: var(--text-muted);
font-size: 12px;
padding: 0 4px;
}
.action-row .btn-more:hover {
color: var(--el-color-primary);
}
.action-row .btn-more .el-icon--right {
margin-left: 1px;
}
.btn-more-delete {
color: var(--el-color-danger);
.action-row .el-button {
justify-self: center;
}
.site-switcher {