From 0cce5d53fe013dac51955a3483f177086304b438 Mon Sep 17 00:00:00 2001 From: SmartUp Developer Date: Sat, 4 Jul 2026 00:25:01 +0800 Subject: [PATCH] Improve website groups loading state --- frontend/src/views/Websites.vue | 171 ++++++++++++++++++++++++++++---- 1 file changed, 150 insertions(+), 21 deletions(-) diff --git a/frontend/src/views/Websites.vue b/frontend/src/views/Websites.vue index 0722164..1b84cd5 100644 --- a/frontend/src/views/Websites.vue +++ b/frontend/src/views/Websites.vue @@ -53,7 +53,7 @@
-
+
我的网站分组
@@ -69,23 +69,54 @@ > - 拉取分组 - 新增分组 + 拉取分组 + 新增分组 +
+
+
+
请选择网站
+
选择一个目标网站后查看分组。
+
+ +
+
+ +
+
分组拉取失败
+
{{ groupsError }}
+ 重新拉取 +
+ +
+
暂无分组
+
当前网站没有返回分组,或还没有点击拉取。
+ 拉取分组 +
+ +
- - - - - - - - - - -
@@ -1050,6 +1081,8 @@ const websites = ref<(WebsiteData & { _testing?: boolean })[]>([]) const upstreams = ref([]) const selectedWebsite = ref(null) const websiteGroups = ref([]) +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,15 +1357,29 @@ 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 { - groupsLoading.value = false + if (requestSeq === groupRequestSeq.value && selectedWebsite.value?.id === websiteId) { + groupsLoading.value = false + } } } @@ -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;