From dffa2dd47da89a101ea618778c565c25744544c6 Mon Sep 17 00:00:00 2001 From: iven Date: Wed, 29 Apr 2026 15:11:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(health+server+mp):=20=E5=AE=A1=E8=AE=A1=20P?= =?UTF-8?q?0=20=E6=89=B9=E6=AC=A1=E4=BF=AE=E5=A4=8D=20=E2=80=94=20?= =?UTF-8?q?=E7=A7=AF=E5=88=86=E5=86=B2=E7=AA=81/=E6=96=87=E7=AB=A0?= =?UTF-8?q?=E8=8D=89=E7=A8=BF=E6=B3=84=E9=9C=B2/=E5=95=86=E5=9F=8E?= =?UTF-8?q?=E7=A9=BA=E7=99=BD/=E6=A8=A1=E6=9D=BFID=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P0-1: 微信模板 ID 从硬编码空字符串改为环境变量注入 - wechat-templates.ts 读取 process.env.TARO_APP_WX_TEMPLATE_* - defineConstants 新增 5 个模板 ID 编译时注入 P0-2: 积分商城 Tab 空白降级 - mall/index.tsx 在 currentPatient 为 null 时先调用 loadPatients() - 仍无档案才显示空状态引导,而非直接阻断 P0-3: 消除 erp-points 重复路由冲突 - 从 erp-server 移除 erp-points 模块注册和路由 merge - 积分功能统一由 erp-health /health/points/* 提供 - erp-points crate 保留但不参与编译 P0-4: 文章列表按角色过滤防止草稿泄露 - list_articles handler: 非管理权限强制 status=published - get_article service: 新增 is_admin 参数控制状态过滤 --- Cargo.lock | 1 - apps/miniprogram/config/index.ts | 5 +++++ apps/miniprogram/src/pages/mall/index.tsx | 13 +++++++++---- .../miniprogram/src/services/wechat-templates.ts | 16 ++++++++-------- crates/erp-health/src/handler/article_handler.rs | 13 ++++++++++--- crates/erp-health/src/service/article_service.rs | 11 ++++++++--- crates/erp-server/Cargo.toml | 2 +- crates/erp-server/src/main.rs | 14 +++++--------- crates/erp-server/src/state.rs | 10 ---------- 9 files changed, 46 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd19d73..f715acf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1697,7 +1697,6 @@ dependencies = [ "erp-health", "erp-message", "erp-plugin", - "erp-points", "erp-server-migration", "erp-workflow", "metrics", diff --git a/apps/miniprogram/config/index.ts b/apps/miniprogram/config/index.ts index 6182532..0834fa1 100644 --- a/apps/miniprogram/config/index.ts +++ b/apps/miniprogram/config/index.ts @@ -14,6 +14,11 @@ export default defineConfig(async (merge) => { 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'), 'process.env.TARO_APP_API_URL': JSON.stringify(process.env.TARO_APP_API_URL || 'http://localhost:3000/api/v1'), 'process.env.TARO_APP_ENCRYPTION_KEY': JSON.stringify(process.env.TARO_APP_ENCRYPTION_KEY || ''), + 'process.env.TARO_APP_WX_TEMPLATE_APPOINTMENT': JSON.stringify(process.env.TARO_APP_WX_TEMPLATE_APPOINTMENT || ''), + 'process.env.TARO_APP_WX_TEMPLATE_FOLLOWUP': JSON.stringify(process.env.TARO_APP_WX_TEMPLATE_FOLLOWUP || ''), + 'process.env.TARO_APP_WX_TEMPLATE_REPORT': JSON.stringify(process.env.TARO_APP_WX_TEMPLATE_REPORT || ''), + 'process.env.TARO_APP_WX_TEMPLATE_CRITICAL_ALERT': JSON.stringify(process.env.TARO_APP_WX_TEMPLATE_CRITICAL_ALERT || ''), + 'process.env.TARO_APP_WX_TEMPLATE_HEALTH_ABNORMAL': JSON.stringify(process.env.TARO_APP_WX_TEMPLATE_HEALTH_ABNORMAL || ''), }, copy: { patterns: [], options: {} }, framework: 'react', diff --git a/apps/miniprogram/src/pages/mall/index.tsx b/apps/miniprogram/src/pages/mall/index.tsx index 85c8249..d3f1195 100644 --- a/apps/miniprogram/src/pages/mall/index.tsx +++ b/apps/miniprogram/src/pages/mall/index.tsx @@ -22,7 +22,7 @@ const TYPE_BG: Record = { }; export default function Mall() { - const { currentPatient } = useAuthStore(); + const { currentPatient, loadPatients } = useAuthStore(); const { account, checkinStatus, refresh: refreshPoints, doCheckin } = usePointsStore(); const [products, setProducts] = useState([]); const [productType, setProductType] = useState(''); @@ -66,13 +66,18 @@ export default function Mall() { async (type?: string) => { const t = type !== undefined ? type : productType; if (!currentPatient) { - setNoProfile(true); - return; + // 先尝试从服务端加载患者列表 + await loadPatients(); + const updated = useAuthStore.getState().currentPatient; + if (!updated) { + setNoProfile(true); + return; + } } setNoProfile(false); await Promise.all([refreshPoints(), fetchProducts(1, t, true)]); }, - [currentPatient, refreshPoints, fetchProducts, productType], + [currentPatient, loadPatients, refreshPoints, fetchProducts, productType], ); useDidShow(() => { diff --git a/apps/miniprogram/src/services/wechat-templates.ts b/apps/miniprogram/src/services/wechat-templates.ts index d3d963d..bd444bc 100644 --- a/apps/miniprogram/src/services/wechat-templates.ts +++ b/apps/miniprogram/src/services/wechat-templates.ts @@ -1,18 +1,18 @@ -// 微信订阅消息模板 ID — 需在微信公众平台注册后填入 +// 微信订阅消息模板 ID — 通过环境变量注入 // 注册路径:公众平台 → 功能 → 订阅消息 → 添加模板 -// TODO: 上线前必须配置 +// 环境变量:TARO_APP_WX_TEMPLATE_APPOINTMENT / FOLLOWUP / REPORT / CRITICAL_ALERT / HEALTH_ABNORMAL export const TEMPLATE_IDS = { - APPOINTMENT_REMINDER: '', - FOLLOWUP_REMINDER: '', - REPORT_NOTIFICATION: '', - CRITICAL_HEALTH_ALERT: '', - HEALTH_DATA_ABNORMAL: '', + APPOINTMENT_REMINDER: process.env.TARO_APP_WX_TEMPLATE_APPOINTMENT || '', + FOLLOWUP_REMINDER: process.env.TARO_APP_WX_TEMPLATE_FOLLOWUP || '', + REPORT_NOTIFICATION: process.env.TARO_APP_WX_TEMPLATE_REPORT || '', + CRITICAL_HEALTH_ALERT: process.env.TARO_APP_WX_TEMPLATE_CRITICAL_ALERT || '', + HEALTH_DATA_ABNORMAL: process.env.TARO_APP_WX_TEMPLATE_HEALTH_ABNORMAL || '', } as const; /** 检查模板 ID 是否已配置,未配置时返回 false 并打印警告 */ export function isTemplateConfigured(key: keyof typeof TEMPLATE_IDS): boolean { if (!TEMPLATE_IDS[key]) { - console.warn(`[wechat-templates] 模板 ${key} 未配置,请在微信公众平台注册并填入 ID`); + console.warn(`[wechat-templates] 模板 ${key} 未配置,请在微信公众平台注册并设置对应环境变量`); return false; } return true; diff --git a/crates/erp-health/src/handler/article_handler.rs b/crates/erp-health/src/handler/article_handler.rs index e6970db..9506ced 100644 --- a/crates/erp-health/src/handler/article_handler.rs +++ b/crates/erp-health/src/handler/article_handler.rs @@ -2,7 +2,7 @@ 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::rbac::{require_any_permission, require_permission}; use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext}; use crate::dto::article_dto::{ArticleListItem, ArticleListParams, ArticleResp, CreateArticleReq, ReviewArticleReq, UpdateArticleReq}; @@ -21,9 +21,15 @@ where require_permission(&ctx, "health.articles.list")?; let page = params.page.unwrap_or(1); let page_size = params.page_size.unwrap_or(20); + // 非管理权限用户只能查看已发布文章,防止草稿泄露 + let status = if require_any_permission(&ctx, &["health.articles.manage", "health.articles.review"]).is_ok() { + params.status + } else { + Some("published".to_string()) + }; let result = article_service::list_articles( &state, ctx.tenant_id, page, page_size, - params.category, params.status, params.category_id, params.tag_id, params.keyword, + params.category, status, params.category_id, params.tag_id, params.keyword, ) .await?; Ok(Json(ApiResponse::ok(result))) @@ -39,7 +45,8 @@ where S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.articles.list")?; - let result = article_service::get_article(&state, ctx.tenant_id, id).await?; + let is_admin = require_any_permission(&ctx, &["health.articles.manage", "health.articles.review"]).is_ok(); + let result = article_service::get_article(&state, ctx.tenant_id, id, is_admin).await?; Ok(Json(ApiResponse::ok(result))) } diff --git a/crates/erp-health/src/service/article_service.rs b/crates/erp-health/src/service/article_service.rs index 018eb00..6c0357f 100644 --- a/crates/erp-health/src/service/article_service.rs +++ b/crates/erp-health/src/service/article_service.rs @@ -99,16 +99,21 @@ pub async fn list_articles( Ok(PaginatedResponse { data, total, page, page_size: limit, total_pages }) } -/// 获取文章详情(管理端,不过滤发布状态) +/// 获取文章详情(管理端可查看任意状态,非管理端仅已发布) pub async fn get_article( state: &HealthState, tenant_id: Uuid, id: Uuid, + is_admin: bool, ) -> HealthResult { - let model = article::Entity::find() + let mut query = article::Entity::find() .filter(article::Column::Id.eq(id)) .filter(article::Column::TenantId.eq(tenant_id)) - .filter(article::Column::DeletedAt.is_null()) + .filter(article::Column::DeletedAt.is_null()); + if !is_admin { + query = query.filter(article::Column::Status.eq("published")); + } + let model = query .one(&state.db) .await? .ok_or(HealthError::ArticleNotFound)?; diff --git a/crates/erp-server/Cargo.toml b/crates/erp-server/Cargo.toml index 2cd9dae..6f60618 100644 --- a/crates/erp-server/Cargo.toml +++ b/crates/erp-server/Cargo.toml @@ -31,7 +31,7 @@ erp-plugin.workspace = true erp-health.workspace = true erp-ai.workspace = true erp-dialysis.workspace = true -erp-points.workspace = true +# erp-points 已禁用,积分功能统一由 erp-health 提供 anyhow.workspace = true uuid.workspace = true chrono.workspace = true diff --git a/crates/erp-server/src/main.rs b/crates/erp-server/src/main.rs index 573e80d..b690ba1 100644 --- a/crates/erp-server/src/main.rs +++ b/crates/erp-server/src/main.rs @@ -349,13 +349,9 @@ async fn main() -> anyhow::Result<()> { "AI module initialized" ); - // Initialize points module - let points_module = erp_points::PointsModule; - tracing::info!( - module = points_module.name(), - version = points_module.version(), - "Points module initialized" - ); + // Points module 已统一到 erp-health(/health/points/* 路由) + // erp-points 的 /points/* 路由为重复实现(大部分 501),已禁用 + // Initialize dialysis module let dialysis_module = erp_dialysis::DialysisModule; @@ -373,7 +369,7 @@ async fn main() -> anyhow::Result<()> { .register(message_module) .register(health_module) .register(ai_module) - .register(points_module) + // erp-points 已禁用,积分功能统一由 erp-health 提供 .register(dialysis_module); tracing::info!( module_count = registry.modules().len(), @@ -564,7 +560,7 @@ async fn main() -> anyhow::Result<()> { .merge(erp_plugin::module::PluginModule::protected_routes()) .merge(erp_health::HealthModule::protected_routes()) .merge(erp_ai::AiModule::protected_routes()) - .merge(erp_points::PointsModule::protected_routes()) + // erp-points 已禁用,积分路由统一由 erp-health /health/points/* 提供 .merge(erp_dialysis::DialysisModule::protected_routes()) .merge(handlers::audit_log::audit_log_router()) .route( diff --git a/crates/erp-server/src/state.rs b/crates/erp-server/src/state.rs index aedd4b7..af00d9b 100644 --- a/crates/erp-server/src/state.rs +++ b/crates/erp-server/src/state.rs @@ -123,16 +123,6 @@ impl FromRef for erp_ai::AiState { } } -/// Allow erp-points handlers to extract their required state. -impl FromRef for erp_points::PointsState { - fn from_ref(state: &AppState) -> Self { - Self { - db: state.db.clone(), - event_bus: state.event_bus.clone(), - } - } -} - /// Allow erp-dialysis handlers to extract their required state. impl FromRef for erp_dialysis::DialysisState { fn from_ref(state: &AppState) -> Self {