Improve website groups loading state
This commit is contained in:
@@ -53,7 +53,7 @@
|
||||
</div>
|
||||
|
||||
<div class="content-grid">
|
||||
<div class="panel">
|
||||
<div class="panel website-groups-panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<div class="panel-title">我的网站分组</div>
|
||||
@@ -69,10 +69,39 @@
|
||||
>
|
||||
<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>
|
||||
<el-button size="small" type="primary" :disabled="!selectedWebsite" @click="openGroupCreate">新增分组</el-button>
|
||||
<el-button size="small" :disabled="!selectedWebsite || groupsLoading" :loading="groupsLoading" @click="loadWebsiteGroups">拉取分组</el-button>
|
||||
<el-button size="small" type="primary" :disabled="!selectedWebsite || groupsLoading" @click="openGroupCreate">新增分组</el-button>
|
||||
</div>
|
||||
<el-table :data="sortedWebsiteGroups" v-loading="groupsLoading" row-key="id" size="small" style="width:100%">
|
||||
<div class="website-groups-body">
|
||||
<div v-if="!selectedWebsite" class="groups-state-panel">
|
||||
<div class="groups-state-title">请选择网站</div>
|
||||
<div class="groups-state-sub">选择一个目标网站后查看分组。</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="groupsLoading && sortedWebsiteGroups.length === 0" class="groups-state-panel groups-state-loading">
|
||||
<span class="groups-spinner" aria-hidden="true" />
|
||||
<div class="groups-state-title">正在拉取网站分组...</div>
|
||||
<div class="groups-state-sub">如果上游响应较慢,请稍等,超时后会显示具体错误。</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="groupsError" class="groups-state-panel groups-state-error">
|
||||
<div class="groups-state-title">分组拉取失败</div>
|
||||
<div class="groups-state-sub">{{ groupsError }}</div>
|
||||
<el-button size="small" :loading="groupsLoading" @click="loadWebsiteGroups">重新拉取</el-button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="sortedWebsiteGroups.length === 0" class="groups-state-panel">
|
||||
<div class="groups-state-title">暂无分组</div>
|
||||
<div class="groups-state-sub">当前网站没有返回分组,或还没有点击拉取。</div>
|
||||
<el-button size="small" :loading="groupsLoading" @click="loadWebsiteGroups">拉取分组</el-button>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div v-if="groupsLoading" class="groups-refresh-strip">
|
||||
<span class="groups-spinner groups-spinner-small" aria-hidden="true" />
|
||||
正在刷新分组...
|
||||
</div>
|
||||
<el-table :data="sortedWebsiteGroups" row-key="id" size="small" style="width:100%">
|
||||
<el-table-column prop="name" label="分组" min-width="150" />
|
||||
<el-table-column prop="id" label="ID" min-width="130" />
|
||||
<el-table-column prop="description" label="描述" min-width="150" />
|
||||
@@ -81,11 +110,13 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="140">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" text :disabled="!selectedWebsite" @click="openBindingCreate(selectedWebsite, row)">绑定</el-button>
|
||||
<el-button size="small" text :disabled="!selectedWebsite" @click="openGroupEdit(row)">编辑</el-button>
|
||||
<el-button size="small" text :disabled="!selectedWebsite || groupsLoading" @click="openBindingCreate(selectedWebsite, row)">绑定</el-button>
|
||||
<el-button size="small" text :disabled="!selectedWebsite || groupsLoading" @click="openGroupEdit(row)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
@@ -1050,6 +1081,8 @@ const websites = ref<(WebsiteData & { _testing?: boolean })[]>([])
|
||||
const upstreams = ref<UpstreamData[]>([])
|
||||
const selectedWebsite = ref<WebsiteData | null>(null)
|
||||
const websiteGroups = ref<WebsiteGroup[]>([])
|
||||
const groupsError = ref('')
|
||||
const groupRequestSeq = ref(0)
|
||||
const sortedWebsiteGroups = computed(() => {
|
||||
return [...websiteGroups.value].sort((a, b) => {
|
||||
const rateA = a.rate_multiplier !== null && a.rate_multiplier !== undefined && a.rate_multiplier !== '—'
|
||||
@@ -1303,6 +1336,8 @@ async function onSelectedWebsiteChange(value: number | string) {
|
||||
const next = websites.value.find((site) => site.id === nextId)
|
||||
if (!next) return
|
||||
selectedWebsite.value = next
|
||||
websiteGroups.value = []
|
||||
groupsError.value = ''
|
||||
await loadWebsiteGroups()
|
||||
}
|
||||
|
||||
@@ -1322,16 +1357,30 @@ async function loadUpstreamGroups() {
|
||||
}
|
||||
|
||||
async function loadWebsiteGroups() {
|
||||
if (!selectedWebsite.value) return
|
||||
if (!selectedWebsite.value) {
|
||||
groupRequestSeq.value += 1
|
||||
websiteGroups.value = []
|
||||
groupsError.value = ''
|
||||
groupsLoading.value = false
|
||||
return
|
||||
}
|
||||
const websiteId = selectedWebsite.value.id
|
||||
const requestSeq = ++groupRequestSeq.value
|
||||
groupsLoading.value = true
|
||||
groupsError.value = ''
|
||||
try {
|
||||
const res = await websitesApi.groups(selectedWebsite.value.id)
|
||||
const res = await websitesApi.groups(websiteId)
|
||||
if (requestSeq !== groupRequestSeq.value || selectedWebsite.value?.id !== websiteId) return
|
||||
websiteGroups.value = res.data
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.response?.data?.detail || '拉取分组失败')
|
||||
if (requestSeq !== groupRequestSeq.value || selectedWebsite.value?.id !== websiteId) return
|
||||
groupsError.value = e.response?.data?.detail || e.message || '拉取分组失败'
|
||||
ElMessage.error(groupsError.value)
|
||||
} finally {
|
||||
if (requestSeq === groupRequestSeq.value && selectedWebsite.value?.id === websiteId) {
|
||||
groupsLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadBindingWebsiteGroups(websiteId: number) {
|
||||
@@ -1460,6 +1509,8 @@ async function loadAll() {
|
||||
|
||||
function selectWebsite(row: WebsiteData) {
|
||||
selectedWebsite.value = row
|
||||
websiteGroups.value = []
|
||||
groupsError.value = ''
|
||||
loadWebsiteGroups()
|
||||
}
|
||||
|
||||
@@ -1547,6 +1598,9 @@ async function deleteWebsite(row: WebsiteData) {
|
||||
ElMessage.success('已删除')
|
||||
selectedWebsite.value = null
|
||||
websiteGroups.value = []
|
||||
groupsError.value = ''
|
||||
groupRequestSeq.value += 1
|
||||
groupsLoading.value = false
|
||||
await loadAll()
|
||||
} catch {}
|
||||
}
|
||||
@@ -2365,6 +2419,81 @@ onMounted(loadAll)
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.website-groups-body {
|
||||
min-height: 220px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.groups-state-panel {
|
||||
min-height: 220px;
|
||||
padding: 32px 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.02), transparent),
|
||||
rgba(255, 255, 255, 0.01);
|
||||
}
|
||||
|
||||
.groups-state-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.groups-state-sub {
|
||||
max-width: 360px;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
color: var(--text-muted);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.groups-state-loading .groups-state-title {
|
||||
color: var(--color-primary-strong);
|
||||
}
|
||||
|
||||
.groups-state-error .groups-state-title {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.groups-spinner {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(166, 141, 133, 0.25);
|
||||
border-top-color: var(--color-primary-strong);
|
||||
animation: groups-spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
.groups-spinner-small {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.groups-refresh-strip {
|
||||
min-height: 34px;
|
||||
padding: 0 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
@keyframes groups-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.binding-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user