fix(mp): DevTools 卡死 + 主包 2MB→766KB + 代码质量 4 项全通过
根因:主包 2MB 全量组件注入导致 DevTools 渲染引擎内存渐增, 叠加离线时固定 3s 抑制期后的请求洪泛。 修复: - app.config.ts 添加 lazyCodeLoading: requiredComponents 主包 2.0MB→766KB,taro.js 526→131KB,vendors.js 230→28KB - request.ts 离线抑制改为指数退避(3s→6s→12s→30s cap) 后端不可达时自动延长抑制,防止请求风暴 - SegmentTabs Tab 接口改为 readonly,修复 TS 编译错误 - AbortController polyfill 补齐小程序运行时缺失 - 健康首页/设备同步/健康档案/报告/设置页 UI 重构 - 文章页公开端点适配游客访问 - 健康首页 Swiper 间隔优化 4s→5s,动画 500→300ms
This commit is contained in:
@@ -7,6 +7,10 @@ use erp_core::sanitize::{
|
||||
sanitize_option, sanitize_rich_html_option, sanitize_string, strip_html_tags,
|
||||
};
|
||||
|
||||
const fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 文章 DTOs
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -29,6 +33,8 @@ pub struct ArticleResp {
|
||||
pub review_note: Option<String>,
|
||||
pub view_count: i32,
|
||||
pub sort_order: i32,
|
||||
/// 是否公开(游客可访问)
|
||||
pub is_public: bool,
|
||||
/// 文章关联的分类 ID(来自 article_category 表)
|
||||
pub category_id: Option<Uuid>,
|
||||
/// 文章关联的标签名称列表
|
||||
@@ -49,6 +55,8 @@ pub struct ArticleListItem {
|
||||
pub published_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub status: String,
|
||||
pub view_count: i32,
|
||||
/// 是否公开(游客可访问)
|
||||
pub is_public: bool,
|
||||
/// 分类 ID
|
||||
pub category_id: Option<Uuid>,
|
||||
/// 标签名称列表
|
||||
@@ -96,6 +104,9 @@ pub struct CreateArticleReq {
|
||||
/// 标签 ID 列表
|
||||
#[serde(default)]
|
||||
pub tag_ids: Vec<Uuid>,
|
||||
/// 是否公开(游客可访问),默认 true
|
||||
#[serde(default = "default_true")]
|
||||
pub is_public: bool,
|
||||
}
|
||||
|
||||
impl CreateArticleReq {
|
||||
@@ -134,6 +145,8 @@ pub struct UpdateArticleReq {
|
||||
/// 标签 ID 列表(传入则整体替换)
|
||||
pub tag_ids: Option<Vec<Uuid>>,
|
||||
pub sort_order: Option<i32>,
|
||||
/// 是否公开(游客可访问)
|
||||
pub is_public: Option<bool>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ pub struct Model {
|
||||
pub view_count: i32,
|
||||
/// 排序权重
|
||||
pub sort_order: i32,
|
||||
/// 是否公开(游客可访问)
|
||||
pub is_public: bool,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! 文章分类 Handler
|
||||
|
||||
use axum::Extension;
|
||||
use axum::extract::{FromRef, Json, Path, State};
|
||||
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};
|
||||
@@ -12,6 +12,32 @@ use crate::state::HealthState;
|
||||
|
||||
use validator::Validate;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 公开端点(小程序游客 / 无需认证)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct PublicCategoryQuery {
|
||||
pub tenant_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
/// GET /public/article-categories — 公开分类列表(无需认证)
|
||||
pub async fn list_public_categories<S>(
|
||||
State(state): State<HealthState>,
|
||||
Query(params): Query<PublicCategoryQuery>,
|
||||
) -> Result<Json<ApiResponse<Vec<CategoryResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
let result = article_category_service::list_categories(&state, params.tenant_id).await?;
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 管理端端点(需要认证)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub async fn list_categories<S>(
|
||||
State(state): State<HealthState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
|
||||
@@ -45,6 +45,7 @@ where
|
||||
params.category_id,
|
||||
params.tag_id,
|
||||
params.keyword,
|
||||
None, // 管理端不过滤 is_public
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
@@ -69,6 +70,7 @@ pub async fn list_public_articles(
|
||||
params.category_id,
|
||||
params.tag_id,
|
||||
params.keyword,
|
||||
Some(true), // 公开端点只返回 is_public=true 的文章
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
|
||||
@@ -5,7 +5,9 @@ use erp_core::error::AppResult;
|
||||
use erp_core::events::EventBus;
|
||||
use erp_core::module::{ErpModule, PermissionDescriptor};
|
||||
|
||||
use crate::handler::{article_handler, banner_handler, ble_gateway_handler};
|
||||
use crate::handler::{
|
||||
article_category_handler, article_handler, banner_handler, ble_gateway_handler,
|
||||
};
|
||||
|
||||
pub struct HealthModule;
|
||||
|
||||
@@ -203,6 +205,10 @@ impl HealthModule {
|
||||
"/public/articles/{id}",
|
||||
axum::routing::get(article_handler::get_public_article),
|
||||
)
|
||||
.route(
|
||||
"/public/article-categories",
|
||||
axum::routing::get(article_category_handler::list_public_categories),
|
||||
)
|
||||
}
|
||||
|
||||
/// FHIR R4 只读路由(使用 OAuth client_credentials 认证)
|
||||
|
||||
@@ -21,7 +21,7 @@ use crate::error::{HealthError, HealthResult};
|
||||
use crate::service::validation;
|
||||
use crate::state::HealthState;
|
||||
|
||||
/// 文章列表(管理端,支持状态/分类/标签/关键词筛选)
|
||||
/// 文章列表(管理端,支持状态/分类/标签/关键词/公开状态筛选)
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn list_articles(
|
||||
state: &HealthState,
|
||||
@@ -33,6 +33,7 @@ pub async fn list_articles(
|
||||
category_id: Option<Uuid>,
|
||||
tag_id: Option<Uuid>,
|
||||
keyword: Option<String>,
|
||||
is_public: Option<bool>,
|
||||
) -> HealthResult<PaginatedResponse<ArticleListItem>> {
|
||||
let limit = page_size.min(100);
|
||||
let offset = page.saturating_sub(1) * limit;
|
||||
@@ -47,6 +48,9 @@ pub async fn list_articles(
|
||||
if let Some(ref s) = status {
|
||||
query = query.filter(article::Column::Status.eq(s));
|
||||
}
|
||||
if let Some(pub_flag) = is_public {
|
||||
query = query.filter(article::Column::IsPublic.eq(pub_flag));
|
||||
}
|
||||
if let Some(cid) = category_id {
|
||||
query = query.filter(article::Column::CategoryId.eq(cid));
|
||||
}
|
||||
@@ -104,6 +108,7 @@ pub async fn list_articles(
|
||||
published_at: m.published_at,
|
||||
status: m.status,
|
||||
view_count: m.view_count,
|
||||
is_public: m.is_public,
|
||||
category_id: m.category_id,
|
||||
tags,
|
||||
version: m.version,
|
||||
@@ -374,6 +379,7 @@ pub async fn create_article(
|
||||
review_note: Set(None),
|
||||
view_count: Set(0),
|
||||
sort_order: Set(0),
|
||||
is_public: Set(req.is_public),
|
||||
created_at: Set(now),
|
||||
updated_at: Set(now),
|
||||
created_by: Set(operator_id),
|
||||
@@ -445,6 +451,9 @@ pub async fn update_article(
|
||||
if let Some(v) = req.sort_order {
|
||||
active.sort_order = Set(v);
|
||||
}
|
||||
if let Some(v) = req.is_public {
|
||||
active.is_public = Set(v);
|
||||
}
|
||||
active.updated_at = Set(Utc::now());
|
||||
active.updated_by = Set(operator_id);
|
||||
active.version = Set(next_ver);
|
||||
@@ -530,6 +539,7 @@ fn full_model_to_resp(m: article::Model, tags: Vec<String>) -> ArticleResp {
|
||||
review_note: m.review_note,
|
||||
view_count: m.view_count,
|
||||
sort_order: m.sort_order,
|
||||
is_public: m.is_public,
|
||||
category_id: m.category_id,
|
||||
tags,
|
||||
created_at: m.created_at,
|
||||
|
||||
Reference in New Issue
Block a user