feat(mp+health): 小程序分包迁移 + 积分商城后台列表 API
- 小程序页面迁移到 pkg-health/pkg-mall/pkg-profile 分包目录 - 删除旧 pages/health/input、pages/mall/detail 等旧路径 - 导航路径更新为分包路径(/pages/pkg-mall/exchange/index 等) - TrendChart 组件优化 - 后台添加 admin_list_products API(支持查看已下架商品) - config/index.ts 添加 defineConstants 环境变量 - mp e2e check-readiness 路径修正
This commit is contained in:
@@ -23,6 +23,11 @@ pub struct ProductTypeParam {
|
||||
pub product_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct AdminProductFilter {
|
||||
pub is_active: Option<bool>,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 患者端:积分账户 + 打卡
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -259,6 +264,24 @@ where HealthState: FromRef<S>, S: Clone + Send + Sync + 'static,
|
||||
Ok(Json(ApiResponse::ok(())))
|
||||
}
|
||||
|
||||
pub async fn admin_list_products<S>(
|
||||
State(state): State<HealthState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Query(params): Query<ProductTypeParam>,
|
||||
Query(page): Query<PaginationParams>,
|
||||
Query(filter): Query<AdminProductFilter>,
|
||||
) -> Result<Json<ApiResponse<PaginatedResponse<PointsProductResp>>>, AppError>
|
||||
where HealthState: FromRef<S>, S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "health.points.list")?;
|
||||
let p = page.page.unwrap_or(1);
|
||||
let ps = page.page_size.unwrap_or(20);
|
||||
let result = points_service::admin_list_products(
|
||||
&state, ctx.tenant_id, params.product_type, filter.is_active, p, ps,
|
||||
).await?;
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
}
|
||||
|
||||
pub async fn admin_create_product<S>(
|
||||
State(state): State<HealthState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
|
||||
@@ -589,6 +589,50 @@ pub async fn list_products(
|
||||
Ok(PaginatedResponse { data, total, page, page_size: limit, total_pages })
|
||||
}
|
||||
|
||||
/// 管理端商品列表 — 不过滤 is_active,显示全部商品
|
||||
pub async fn admin_list_products(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
product_type: Option<String>,
|
||||
is_active: Option<bool>,
|
||||
page: u64,
|
||||
page_size: u64,
|
||||
) -> HealthResult<PaginatedResponse<PointsProductResp>> {
|
||||
let limit = page_size.min(100);
|
||||
let offset = page.saturating_sub(1) * limit;
|
||||
|
||||
let mut query = points_product::Entity::find()
|
||||
.filter(points_product::Column::TenantId.eq(tenant_id))
|
||||
.filter(points_product::Column::DeletedAt.is_null());
|
||||
|
||||
if let Some(ref pt) = product_type {
|
||||
query = query.filter(points_product::Column::ProductType.eq(pt.as_str()));
|
||||
}
|
||||
if let Some(active) = is_active {
|
||||
query = query.filter(points_product::Column::IsActive.eq(active));
|
||||
}
|
||||
|
||||
let total = query.clone().count(&state.db).await?;
|
||||
let models = query
|
||||
.order_by_asc(points_product::Column::SortOrder)
|
||||
.order_by_desc(points_product::Column::CreatedAt)
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.all(&state.db)
|
||||
.await?;
|
||||
|
||||
let total_pages = total.div_ceil(limit.max(1));
|
||||
let data = models.into_iter().map(|m| PointsProductResp {
|
||||
id: m.id, name: m.name, product_type: m.product_type,
|
||||
points_cost: m.points_cost, stock: m.stock,
|
||||
image_url: m.image_url, description: m.description,
|
||||
is_active: m.is_active, sort_order: m.sort_order,
|
||||
created_at: m.created_at, updated_at: m.updated_at, version: m.version,
|
||||
}).collect();
|
||||
|
||||
Ok(PaginatedResponse { data, total, page, page_size: limit, total_pages })
|
||||
}
|
||||
|
||||
pub async fn get_product(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
|
||||
Reference in New Issue
Block a user