我的网站分组
@@ -69,23 +69,54 @@
>
-
拉取分组
-
新增分组
+
拉取分组
+
新增分组
+
+
+
+
请选择网站
+
选择一个目标网站后查看分组。
+
+
+
+
+
正在拉取网站分组...
+
如果上游响应较慢,请稍等,超时后会显示具体错误。
+
+
+
+
分组拉取失败
+
{{ groupsError }}
+
重新拉取
+
+
+
+
暂无分组
+
当前网站没有返回分组,或还没有点击拉取。
+
拉取分组
+
+
+
+
+
+ 正在刷新分组...
+
+
+
+
+
+
+ {{ row.rate_multiplier ?? '—' }}
+
+
+
+ 绑定
+ 编辑
+
+
+
+
-
-
-
-
-
- {{ row.rate_multiplier ?? '—' }}
-
-
-
- 绑定
- 编辑
-
-
-
@@ -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;