use axum::Extension; use axum::extract::{FromRef, Json, Path, Query, State}; use erp_core::error::AppError; use erp_core::rbac::require_permission; use erp_core::types::{ApiResponse, TenantContext}; use crate::dto::banner_dto::{ BannerResp, CreateBannerReq, PublicBannerResp, SortBannerReq, UpdateBannerReq, }; use crate::service::banner_service; use crate::state::HealthState; // --------------------------------------------------------------------------- // 本地请求结构体 // --------------------------------------------------------------------------- #[derive(Debug, serde::Deserialize)] pub struct DeleteVersionReq { pub version: i32, } #[derive(Debug, serde::Deserialize)] pub struct BannerListQuery { pub status: Option, } #[derive(Debug, serde::Deserialize)] pub struct PublicBannerQuery { pub tenant_id: Option, } // --------------------------------------------------------------------------- // 管理端端点 // --------------------------------------------------------------------------- /// GET /health/banners — 列出租户所有轮播图 pub async fn list_banners( State(state): State, Extension(ctx): Extension, Query(params): Query, ) -> Result>>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.banners.list")?; let result = banner_service::list_banners(&state, ctx.tenant_id, params.status).await?; Ok(Json(ApiResponse::ok(result))) } /// POST /health/banners — 创建轮播图 pub async fn create_banner( State(state): State, Extension(ctx): Extension, mut req: Json, ) -> Result>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.banners.manage")?; req.sanitize(); let result = banner_service::create_banner(&state, ctx.tenant_id, ctx.user_id, req.0).await?; Ok(Json(ApiResponse::ok(result))) } /// PUT /health/banners/{id} — 更新轮播图 pub async fn update_banner( State(state): State, Extension(ctx): Extension, Path(id): Path, mut req: Json, ) -> Result>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.banners.manage")?; req.sanitize(); let result = banner_service::update_banner(&state, ctx.tenant_id, id, ctx.user_id, req.0).await?; Ok(Json(ApiResponse::ok(result))) } /// DELETE /health/banners/{id} — 软删除轮播图 pub async fn delete_banner( State(state): State, Extension(ctx): Extension, Path(id): Path, Json(req): Json, ) -> Result>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.banners.manage")?; banner_service::delete_banner(&state, ctx.tenant_id, id, ctx.user_id, req.version).await?; Ok(Json(ApiResponse::ok(()))) } /// PUT /health/banners/sort — 批量更新排序 pub async fn sort_banners( State(state): State, Extension(ctx): Extension, Json(req): Json, ) -> Result>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.banners.manage")?; banner_service::sort_banners(&state, ctx.tenant_id, req.items).await?; Ok(Json(ApiResponse::ok(()))) } // --------------------------------------------------------------------------- // 公开端点(小程序 / 无需认证) // --------------------------------------------------------------------------- /// GET /public/banners — 公开轮播图列表 pub async fn list_public_banners( State(state): State, Query(params): Query, headers: axum::http::HeaderMap, ) -> Result>>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { // 从 X-Tenant-Id 请求头或查询参数中解析租户 ID let tenant_id = headers .get("X-Tenant-Id") .and_then(|v| v.to_str().ok()) .and_then(|v| v.parse::().ok()) .or(params.tenant_id) .ok_or_else(|| AppError::Validation("缺少 tenant_id".to_string()))?; let base_url = "http://localhost:3000".to_string(); let result = banner_service::list_public_banners(&state, tenant_id, &base_url).await?; Ok(Json(ApiResponse::ok(result))) }