feat: 统一后端分页查询与前端服务层适配
后端新增通用分页模块,为报销单、员工、预算、agent 资产等 端点统一接入分页参数和游标查询,优化 repository 层分页实 现,前端服务层适配分页响应结构,完善预算图表和全局样式, 优化侧边栏和企业选择器组件,引入 Element Plus 插件注册。
This commit is contained in:
95
server/src/app/services/pagination.py
Normal file
95
server/src/app/services/pagination.py
Normal file
@@ -0,0 +1,95 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from math import ceil
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.sql import Select
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
||||
DEFAULT_PAGE = 1
|
||||
DEFAULT_PAGE_SIZE = 20
|
||||
MAX_PAGE_SIZE = 100
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PageParams:
|
||||
page: int
|
||||
page_size: int
|
||||
|
||||
@property
|
||||
def offset(self) -> int:
|
||||
return (self.page - 1) * self.page_size
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PageResult(Generic[T]):
|
||||
items: list[T]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
|
||||
@property
|
||||
def total_pages(self) -> int:
|
||||
if self.total <= 0:
|
||||
return 0
|
||||
return ceil(self.total / self.page_size)
|
||||
|
||||
@property
|
||||
def has_next(self) -> bool:
|
||||
return self.page < self.total_pages
|
||||
|
||||
@property
|
||||
def has_previous(self) -> bool:
|
||||
return self.page > 1 and self.total_pages > 0
|
||||
|
||||
def map(self, mapper: Callable[[T], U]) -> PageResult[U]:
|
||||
return PageResult(
|
||||
items=[mapper(item) for item in self.items],
|
||||
total=self.total,
|
||||
page=self.page,
|
||||
page_size=self.page_size,
|
||||
)
|
||||
|
||||
|
||||
def normalize_page_params(
|
||||
page: int | None,
|
||||
page_size: int | None,
|
||||
*,
|
||||
max_page_size: int = MAX_PAGE_SIZE,
|
||||
) -> PageParams:
|
||||
normalized_page = max(DEFAULT_PAGE, int(page or DEFAULT_PAGE))
|
||||
normalized_page_size = max(1, int(page_size or DEFAULT_PAGE_SIZE))
|
||||
normalized_page_size = min(normalized_page_size, max_page_size)
|
||||
return PageParams(page=normalized_page, page_size=normalized_page_size)
|
||||
|
||||
|
||||
def paginate_select(
|
||||
db: Session,
|
||||
stmt: Select[Any],
|
||||
*,
|
||||
page: int | None,
|
||||
page_size: int | None,
|
||||
max_page_size: int = MAX_PAGE_SIZE,
|
||||
unique: bool = False,
|
||||
) -> PageResult[Any]:
|
||||
params = normalize_page_params(page, page_size, max_page_size=max_page_size)
|
||||
count_stmt = select(func.count()).select_from(stmt.order_by(None).subquery())
|
||||
total = int(db.scalar(count_stmt) or 0)
|
||||
|
||||
page_stmt = stmt.limit(params.page_size).offset(params.offset)
|
||||
scalars = db.execute(page_stmt).scalars()
|
||||
if unique:
|
||||
scalars = scalars.unique()
|
||||
|
||||
return PageResult(
|
||||
items=list(scalars.all()),
|
||||
total=total,
|
||||
page=params.page,
|
||||
page_size=params.page_size,
|
||||
)
|
||||
Reference in New Issue
Block a user