mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
feat(providers): provider 摘要列表排序增加启用状态优先
后端查询和前端展示均按 is_active 降序、priority 升序、created_at 升序排列, 确保已启用的 provider 始终排在前面。
This commit is contained in:
@@ -154,7 +154,7 @@
|
|||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<ProviderTableRow
|
<ProviderTableRow
|
||||||
v-for="provider in providers"
|
v-for="provider in displayedProviders"
|
||||||
:key="provider.id"
|
:key="provider.id"
|
||||||
:provider="provider"
|
:provider="provider"
|
||||||
:editing-description-id="editingDescriptionId"
|
:editing-description-id="editingDescriptionId"
|
||||||
@@ -189,7 +189,7 @@
|
|||||||
class="xl:hidden divide-y divide-border/40"
|
class="xl:hidden divide-y divide-border/40"
|
||||||
>
|
>
|
||||||
<ProviderMobileCard
|
<ProviderMobileCard
|
||||||
v-for="provider in providers"
|
v-for="provider in displayedProviders"
|
||||||
:key="provider.id"
|
:key="provider.id"
|
||||||
:provider="provider"
|
:provider="provider"
|
||||||
:editing-description-id="editingDescriptionId"
|
:editing-description-id="editingDescriptionId"
|
||||||
@@ -468,6 +468,20 @@ const opsConfigProviderWebsite = ref('')
|
|||||||
// 内联编辑备注
|
// 内联编辑备注
|
||||||
const editingDescriptionId = ref<string | null>(null)
|
const editingDescriptionId = ref<string | null>(null)
|
||||||
|
|
||||||
|
function sortProvidersByActiveAndPriority(items: ProviderWithEndpointsSummary[]) {
|
||||||
|
return [...items].sort((a, b) => {
|
||||||
|
if (a.is_active !== b.is_active) {
|
||||||
|
return a.is_active ? -1 : 1
|
||||||
|
}
|
||||||
|
if (a.provider_priority !== b.provider_priority) {
|
||||||
|
return a.provider_priority - b.provider_priority
|
||||||
|
}
|
||||||
|
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayedProviders = computed(() => sortProvidersByActiveAndPriority(providers.value))
|
||||||
|
|
||||||
function startEditDescription(_event: Event, provider: ProviderWithEndpointsSummary) {
|
function startEditDescription(_event: Event, provider: ProviderWithEndpointsSummary) {
|
||||||
editingDescriptionId.value = provider.id
|
editingDescriptionId.value = provider.id
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Provider 摘要与健康监控 API
|
|||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Any
|
from typing import Any, Protocol
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query, Request
|
from fastapi import APIRouter, Depends, Query, Request
|
||||||
from sqlalchemy import case, func
|
from sqlalchemy import case, func
|
||||||
@@ -43,10 +43,26 @@ from src.services.cache.model_cache import ModelCacheService
|
|||||||
from src.services.cache.provider_cache import ProviderCacheService
|
from src.services.cache.provider_cache import ProviderCacheService
|
||||||
from src.utils.cache_decorator import cache_result
|
from src.utils.cache_decorator import cache_result
|
||||||
|
|
||||||
|
|
||||||
|
class _HasProviderSortFields(Protocol):
|
||||||
|
is_active: Any
|
||||||
|
provider_priority: Any
|
||||||
|
created_at: Any
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(tags=["Provider Summary"])
|
router = APIRouter(tags=["Provider Summary"])
|
||||||
pipeline = get_pipeline()
|
pipeline = get_pipeline()
|
||||||
|
|
||||||
|
|
||||||
|
def _provider_summary_ordering(provider_model: _HasProviderSortFields) -> tuple[Any, Any, Any]:
|
||||||
|
"""Provider 摘要列表排序:启用在前,其次按优先级与创建时间。"""
|
||||||
|
return (
|
||||||
|
case((provider_model.is_active == True, 0), else_=1).asc(),
|
||||||
|
provider_model.provider_priority.asc(),
|
||||||
|
provider_model.created_at.asc(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/summary", response_model=ProviderSummaryPageResponse)
|
@router.get("/summary", response_model=ProviderSummaryPageResponse)
|
||||||
async def get_providers_summary(
|
async def get_providers_summary(
|
||||||
request: Request,
|
request: Request,
|
||||||
@@ -821,7 +837,7 @@ class AdminProviderSummaryAdapter(AdminApiAdapter):
|
|||||||
total = query.count()
|
total = query.count()
|
||||||
|
|
||||||
providers = (
|
providers = (
|
||||||
query.order_by(Provider.provider_priority.asc(), Provider.created_at.asc())
|
query.order_by(*_provider_summary_ordering(Provider))
|
||||||
.offset((self.page - 1) * self.page_size)
|
.offset((self.page - 1) * self.page_size)
|
||||||
.limit(self.page_size)
|
.limit(self.page_size)
|
||||||
.all()
|
.all()
|
||||||
|
|||||||
77
tests/services/test_provider_summary_ordering.py
Normal file
77
tests/services/test_provider_summary_ordering.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
from sqlalchemy import Boolean, Column, DateTime, Integer, String, create_engine, select
|
||||||
|
from sqlalchemy.orm import Session, declarative_base
|
||||||
|
|
||||||
|
from src.api.admin.providers.summary import _provider_summary_ordering
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
class DemoProvider(Base):
|
||||||
|
__tablename__ = "demo_providers"
|
||||||
|
|
||||||
|
id = Column(String(36), primary_key=True)
|
||||||
|
name = Column(String(100), nullable=False)
|
||||||
|
is_active = Column(Boolean, nullable=False)
|
||||||
|
provider_priority = Column(Integer, nullable=False)
|
||||||
|
created_at = Column(DateTime(timezone=True), nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_provider_summary_ordering_puts_inactive_providers_last() -> None:
|
||||||
|
engine = create_engine("sqlite:///:memory:")
|
||||||
|
try:
|
||||||
|
Base.metadata.create_all(engine)
|
||||||
|
|
||||||
|
with Session(engine) as db:
|
||||||
|
base_time = datetime(2026, 3, 15, tzinfo=timezone.utc)
|
||||||
|
db.add_all(
|
||||||
|
[
|
||||||
|
DemoProvider(
|
||||||
|
id="inactive-top-priority",
|
||||||
|
name="inactive-top-priority",
|
||||||
|
is_active=False,
|
||||||
|
provider_priority=0,
|
||||||
|
created_at=base_time,
|
||||||
|
),
|
||||||
|
DemoProvider(
|
||||||
|
id="active-lower-priority",
|
||||||
|
name="active-lower-priority",
|
||||||
|
is_active=True,
|
||||||
|
provider_priority=20,
|
||||||
|
created_at=base_time + timedelta(seconds=1),
|
||||||
|
),
|
||||||
|
DemoProvider(
|
||||||
|
id="active-higher-priority-newer",
|
||||||
|
name="active-higher-priority-newer",
|
||||||
|
is_active=True,
|
||||||
|
provider_priority=5,
|
||||||
|
created_at=base_time + timedelta(seconds=3),
|
||||||
|
),
|
||||||
|
DemoProvider(
|
||||||
|
id="active-higher-priority-older",
|
||||||
|
name="active-higher-priority-older",
|
||||||
|
is_active=True,
|
||||||
|
provider_priority=5,
|
||||||
|
created_at=base_time + timedelta(seconds=2),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
ordered = (
|
||||||
|
db.execute(select(DemoProvider).order_by(*_provider_summary_ordering(DemoProvider)))
|
||||||
|
.scalars()
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert [provider.id for provider in ordered] == [
|
||||||
|
"active-higher-priority-older",
|
||||||
|
"active-higher-priority-newer",
|
||||||
|
"active-lower-priority",
|
||||||
|
"inactive-top-priority",
|
||||||
|
]
|
||||||
|
finally:
|
||||||
|
engine.dispose()
|
||||||
Reference in New Issue
Block a user