feat: 统一后端分页查询与前端服务层适配
后端新增通用分页模块,为报销单、员工、预算、agent 资产等 端点统一接入分页参数和游标查询,优化 repository 层分页实 现,前端服务层适配分页响应结构,完善预算图表和全局样式, 优化侧边栏和企业选择器组件,引入 Element Plus 插件注册。
This commit is contained in:
26
server/src/app/api/pagination.py
Normal file
26
server/src/app/api/pagination.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated, Any
|
||||
|
||||
from fastapi import Query
|
||||
|
||||
from app.services.pagination import PageResult
|
||||
|
||||
PageNumber = Annotated[int | None, Query(ge=1, description="页码,从 1 开始。")]
|
||||
PageSize = Annotated[int | None, Query(ge=1, le=100, description="每页条数,最多 100。")]
|
||||
|
||||
|
||||
def wants_page(page: int | None, page_size: int | None) -> bool:
|
||||
return page is not None or page_size is not None
|
||||
|
||||
|
||||
def page_payload(result: PageResult[Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"items": result.items,
|
||||
"total": result.total,
|
||||
"page": result.page,
|
||||
"page_size": result.page_size,
|
||||
"total_pages": result.total_pages,
|
||||
"has_next": result.has_next,
|
||||
"has_previous": result.has_previous,
|
||||
}
|
||||
@@ -14,6 +14,7 @@ from app.api.deps import (
|
||||
require_rule_editor_user,
|
||||
require_rule_reviewer_user,
|
||||
)
|
||||
from app.api.pagination import PageNumber, PageSize, page_payload, wants_page
|
||||
from app.db.session import get_session_factory
|
||||
from app.schemas.agent_asset import (
|
||||
AgentAssetCreate,
|
||||
@@ -43,7 +44,7 @@ from app.schemas.agent_asset import (
|
||||
AgentAssetVersionRead,
|
||||
AgentAssetVersionTimelineItemRead,
|
||||
)
|
||||
from app.schemas.common import ErrorResponse
|
||||
from app.schemas.common import ErrorResponse, PaginatedResponse
|
||||
from app.services.agent_assets import AgentAssetService
|
||||
from app.services.risk_rule_generation_jobs import RiskRuleGenerationJobService
|
||||
|
||||
@@ -94,7 +95,7 @@ def _complete_risk_rule_generation_task(
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=list[AgentAssetListItem],
|
||||
response_model=list[AgentAssetListItem] | PaginatedResponse[AgentAssetListItem],
|
||||
summary="查询 Agent 资产列表",
|
||||
description="按资产类型、状态、领域和关键字筛选规则、技能、MCP 与任务资产。",
|
||||
)
|
||||
@@ -116,8 +117,22 @@ def list_agent_assets(
|
||||
str | None,
|
||||
Query(description="资产编码、名称关键字模糊查询。"),
|
||||
] = None,
|
||||
) -> list[AgentAssetListItem]:
|
||||
return AgentAssetService(db).list_assets(
|
||||
page: PageNumber = None,
|
||||
page_size: PageSize = None,
|
||||
) -> list[AgentAssetListItem] | PaginatedResponse[AgentAssetListItem]:
|
||||
service = AgentAssetService(db)
|
||||
if wants_page(page, page_size):
|
||||
return page_payload(
|
||||
service.list_assets_page(
|
||||
asset_type=asset_type,
|
||||
status=status_value,
|
||||
domain=domain,
|
||||
keyword=keyword,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
)
|
||||
return service.list_assets(
|
||||
asset_type=asset_type,
|
||||
status=status_value,
|
||||
domain=domain,
|
||||
|
||||
@@ -4,8 +4,7 @@ from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import func, or_, select
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm import Session, selectinload
|
||||
|
||||
from app.api.deps import (
|
||||
CurrentUserContext,
|
||||
@@ -15,8 +14,9 @@ from app.api.deps import (
|
||||
require_budget_editor_user,
|
||||
require_budget_viewer_user,
|
||||
)
|
||||
from app.models.employee import Employee
|
||||
from app.api.pagination import PageNumber, PageSize, page_payload, wants_page
|
||||
from app.models.budget import BudgetAllocation
|
||||
from app.models.employee import Employee
|
||||
from app.schemas.budget import (
|
||||
BudgetAllocationCreate,
|
||||
BudgetAllocationRead,
|
||||
@@ -27,7 +27,7 @@ from app.schemas.budget import (
|
||||
BudgetSummaryRead,
|
||||
BudgetTransactionRead,
|
||||
)
|
||||
from app.schemas.common import ErrorResponse
|
||||
from app.schemas.common import ErrorResponse, PaginatedResponse
|
||||
from app.services.budget import BudgetControlError, BudgetService
|
||||
|
||||
router = APIRouter(prefix="/budgets")
|
||||
@@ -67,7 +67,7 @@ def get_budget_summary(
|
||||
|
||||
@router.get(
|
||||
"/allocations",
|
||||
response_model=list[BudgetAllocationRead],
|
||||
response_model=list[BudgetAllocationRead] | PaginatedResponse[BudgetAllocationRead],
|
||||
summary="查询预算额度列表",
|
||||
)
|
||||
def list_budget_allocations(
|
||||
@@ -78,7 +78,9 @@ def list_budget_allocations(
|
||||
department_id: str | None = None,
|
||||
department_name: str | None = None,
|
||||
cost_center: str | None = None,
|
||||
) -> list[BudgetAllocationRead]:
|
||||
page: PageNumber = None,
|
||||
page_size: PageSize = None,
|
||||
) -> list[BudgetAllocationRead] | PaginatedResponse[BudgetAllocationRead]:
|
||||
scope = _resolve_budget_query_scope(
|
||||
db,
|
||||
current_user,
|
||||
@@ -86,7 +88,18 @@ def list_budget_allocations(
|
||||
department_name=department_name,
|
||||
cost_center=cost_center,
|
||||
)
|
||||
return BudgetService(db).list_allocations(
|
||||
service = BudgetService(db)
|
||||
if wants_page(page, page_size):
|
||||
return page_payload(
|
||||
service.list_allocations_page(
|
||||
fiscal_year=fiscal_year,
|
||||
period_key=period_key,
|
||||
**scope,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
)
|
||||
return service.list_allocations(
|
||||
fiscal_year=fiscal_year,
|
||||
period_key=period_key,
|
||||
**scope,
|
||||
@@ -119,20 +132,30 @@ def create_budget_allocation(
|
||||
|
||||
@router.get(
|
||||
"/allocations/{allocation_id}/transactions",
|
||||
response_model=list[BudgetTransactionRead],
|
||||
response_model=list[BudgetTransactionRead] | PaginatedResponse[BudgetTransactionRead],
|
||||
summary="读取预算交易台账",
|
||||
)
|
||||
def list_budget_transactions(
|
||||
allocation_id: str,
|
||||
db: DbSession,
|
||||
current_user: BudgetViewer,
|
||||
) -> list[BudgetTransactionRead]:
|
||||
allocation = BudgetService(db).get_allocation_row(allocation_id)
|
||||
page: PageNumber = None,
|
||||
page_size: PageSize = None,
|
||||
) -> list[BudgetTransactionRead] | PaginatedResponse[BudgetTransactionRead]:
|
||||
service = BudgetService(db)
|
||||
allocation = service.get_allocation_row(allocation_id)
|
||||
if allocation is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="预算额度不存在。")
|
||||
if not _allocation_visible_to_user(db, current_user, allocation):
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="不能查看其他部门预算流水。")
|
||||
return BudgetService(db).list_transactions(allocation_id)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="不能查看其他部门预算流水。",
|
||||
)
|
||||
if wants_page(page, page_size):
|
||||
return page_payload(
|
||||
service.list_transactions_page(allocation_id, page=page, page_size=page_size)
|
||||
)
|
||||
return service.list_transactions(allocation_id)
|
||||
|
||||
|
||||
@router.post(
|
||||
|
||||
@@ -7,7 +7,8 @@ from fastapi.responses import Response
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_db
|
||||
from app.schemas.common import ErrorResponse
|
||||
from app.api.pagination import PageNumber, PageSize, page_payload, wants_page
|
||||
from app.schemas.common import ErrorResponse, PaginatedResponse
|
||||
from app.schemas.employee import (
|
||||
EmployeeCreate,
|
||||
EmployeeImportResultRead,
|
||||
@@ -16,6 +17,7 @@ from app.schemas.employee import (
|
||||
EmployeeUpdate,
|
||||
)
|
||||
from app.services.employee import EmployeeService
|
||||
from app.services.employee_pagination import EmployeePaginationService
|
||||
|
||||
router = APIRouter()
|
||||
DbSession = Annotated[Session, Depends(get_db)]
|
||||
@@ -33,7 +35,7 @@ def get_employee_meta(db: DbSession) -> EmployeeMetaRead:
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=list[EmployeeRead],
|
||||
response_model=list[EmployeeRead] | PaginatedResponse[EmployeeRead],
|
||||
summary="查询员工列表",
|
||||
description="按状态和关键字筛选员工目录。",
|
||||
)
|
||||
@@ -47,7 +49,18 @@ def list_employees(
|
||||
str | None,
|
||||
Query(description="姓名、工号、邮箱等关键字模糊查询。"),
|
||||
] = None,
|
||||
) -> list[EmployeeRead]:
|
||||
page: PageNumber = None,
|
||||
page_size: PageSize = None,
|
||||
) -> list[EmployeeRead] | PaginatedResponse[EmployeeRead]:
|
||||
if wants_page(page, page_size):
|
||||
return page_payload(
|
||||
EmployeePaginationService(db).list_employees_page(
|
||||
status=status_filter,
|
||||
keyword=keyword,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
)
|
||||
return EmployeeService(db).list_employees(status=status_filter, keyword=keyword)
|
||||
|
||||
|
||||
|
||||
@@ -7,8 +7,9 @@ from fastapi.responses import FileResponse
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import CurrentUserContext, get_current_user, get_db
|
||||
from app.api.pagination import PageNumber, PageSize, page_payload, wants_page
|
||||
from app.schemas.budget import BudgetClaimAnalysisRead
|
||||
from app.schemas.common import ErrorResponse
|
||||
from app.schemas.common import ErrorResponse, PaginatedResponse
|
||||
from app.schemas.reimbursement import (
|
||||
ExpenseClaimAttachmentActionResponse,
|
||||
ExpenseClaimActionResponse,
|
||||
@@ -25,8 +26,8 @@ from app.schemas.reimbursement import (
|
||||
TravelReimbursementCalculatorRequest,
|
||||
TravelReimbursementCalculatorResponse,
|
||||
)
|
||||
from app.services.expense_claims import ExpenseClaimService
|
||||
from app.services.budget import BudgetService
|
||||
from app.services.expense_claims import ExpenseClaimService
|
||||
from app.services.reimbursement import ReimbursementService
|
||||
from app.services.travel_reimbursement_calculator import TravelReimbursementCalculatorService
|
||||
|
||||
@@ -37,12 +38,19 @@ CurrentUser = Annotated[CurrentUserContext, Depends(get_current_user)]
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=list[ReimbursementRead],
|
||||
response_model=list[ReimbursementRead] | PaginatedResponse[ReimbursementRead],
|
||||
summary="查询报销申请列表",
|
||||
description="返回当前系统中的报销申请列表。",
|
||||
)
|
||||
def list_reimbursements(db: DbSession) -> list[ReimbursementRead]:
|
||||
return ReimbursementService(db).list_reimbursements()
|
||||
def list_reimbursements(
|
||||
db: DbSession,
|
||||
page: PageNumber = None,
|
||||
page_size: PageSize = None,
|
||||
) -> list[ReimbursementRead] | PaginatedResponse[ReimbursementRead]:
|
||||
service = ReimbursementService(db)
|
||||
if wants_page(page, page_size):
|
||||
return page_payload(service.list_reimbursements_page(page=page, page_size=page_size))
|
||||
return service.list_reimbursements()
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -81,32 +89,60 @@ def calculate_travel_reimbursement(
|
||||
|
||||
@router.get(
|
||||
"/claims",
|
||||
response_model=list[ExpenseClaimRead],
|
||||
response_model=list[ExpenseClaimRead] | PaginatedResponse[ExpenseClaimRead],
|
||||
summary="查询个人报销单列表",
|
||||
description="返回当前登录用户可见的真实个人报销单据列表。",
|
||||
)
|
||||
def list_expense_claims(db: DbSession, current_user: CurrentUser) -> list[ExpenseClaimRead]:
|
||||
return ExpenseClaimService(db).list_claims(current_user)
|
||||
def list_expense_claims(
|
||||
db: DbSession,
|
||||
current_user: CurrentUser,
|
||||
page: PageNumber = None,
|
||||
page_size: PageSize = None,
|
||||
) -> list[ExpenseClaimRead] | PaginatedResponse[ExpenseClaimRead]:
|
||||
service = ExpenseClaimService(db)
|
||||
if wants_page(page, page_size):
|
||||
return page_payload(service.list_claims_page(current_user, page=page, page_size=page_size))
|
||||
return service.list_claims(current_user)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/claims/approvals",
|
||||
response_model=list[ExpenseClaimRead],
|
||||
response_model=list[ExpenseClaimRead] | PaginatedResponse[ExpenseClaimRead],
|
||||
summary="查询当前用户审批待办报销单列表",
|
||||
description="返回当前登录用户有权处理的待审批报销单据,不混入个人报销列表。",
|
||||
)
|
||||
def list_expense_claim_approvals(db: DbSession, current_user: CurrentUser) -> list[ExpenseClaimRead]:
|
||||
return ExpenseClaimService(db).list_approval_claims(current_user)
|
||||
def list_expense_claim_approvals(
|
||||
db: DbSession,
|
||||
current_user: CurrentUser,
|
||||
page: PageNumber = None,
|
||||
page_size: PageSize = None,
|
||||
) -> list[ExpenseClaimRead] | PaginatedResponse[ExpenseClaimRead]:
|
||||
service = ExpenseClaimService(db)
|
||||
if wants_page(page, page_size):
|
||||
return page_payload(
|
||||
service.list_approval_claims_page(current_user, page=page, page_size=page_size)
|
||||
)
|
||||
return service.list_approval_claims(current_user)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/claims/archives",
|
||||
response_model=list[ExpenseClaimRead],
|
||||
response_model=list[ExpenseClaimRead] | PaginatedResponse[ExpenseClaimRead],
|
||||
summary="查询归档中心报销单列表",
|
||||
description="返回公司已归档入账的报销单据,供财务与审计角色集中查阅。",
|
||||
)
|
||||
def list_archived_expense_claims(db: DbSession, current_user: CurrentUser) -> list[ExpenseClaimRead]:
|
||||
return ExpenseClaimService(db).list_archived_claims(current_user)
|
||||
def list_archived_expense_claims(
|
||||
db: DbSession,
|
||||
current_user: CurrentUser,
|
||||
page: PageNumber = None,
|
||||
page_size: PageSize = None,
|
||||
) -> list[ExpenseClaimRead] | PaginatedResponse[ExpenseClaimRead]:
|
||||
service = ExpenseClaimService(db)
|
||||
if wants_page(page, page_size):
|
||||
return page_payload(
|
||||
service.list_archived_claims_page(current_user, page=page, page_size=page_size)
|
||||
)
|
||||
return service.list_archived_claims(current_user)
|
||||
|
||||
|
||||
@router.get(
|
||||
|
||||
Reference in New Issue
Block a user