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:
iven
2026-05-24 11:32:40 +08:00
parent 675f8a4b10
commit 1e59007bd5
58 changed files with 4950 additions and 494 deletions

View File

@@ -168,6 +168,7 @@ mod m20260521_000163_reorganize_menus_by_business_flow;
mod m20260521_000164_reorganize_menus_scheme_b;
mod m20260522_000160_article_add_is_public;
mod m20260522_000161_patient_points_manage_perm;
mod m20260522_000162_seed_patient_miniprogram_permissions;
pub struct Migrator;
@@ -343,6 +344,7 @@ impl MigratorTrait for Migrator {
Box::new(m20260521_000164_reorganize_menus_scheme_b::Migration),
Box::new(m20260522_000160_article_add_is_public::Migration),
Box::new(m20260522_000161_patient_points_manage_perm::Migration),
Box::new(m20260522_000162_seed_patient_miniprogram_permissions::Migration),
]
}
}

View File

@@ -0,0 +1,37 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// alerts 表新增 source告警来源和 original_id关联原始告警字段
manager
.alter_table(
Table::alter()
.table(Alias::new("alerts"))
.add_column(
ColumnDef::new(Alias::new("source"))
.string()
.not_null()
.default("rule_engine"),
)
.add_column(ColumnDef::new(Alias::new("original_id")).uuid().null())
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(Alias::new("alerts"))
.drop_column(Alias::new("source"))
.drop_column(Alias::new("original_id"))
.to_owned(),
)
.await
}
}

View File

@@ -0,0 +1,108 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// 1. patient 表新增 phone 和 phone_hash 字段
manager
.alter_table(
Table::alter()
.table(Alias::new("patient"))
.add_column(ColumnDef::new(Alias::new("phone")).text().null())
.add_column(ColumnDef::new(Alias::new("phone_hash")).text().null())
.to_owned(),
)
.await?;
// 2. 为所有现有活跃患者自动授予 data_processing 同意(默认拒绝策略下保持向后兼容)
let seed_consent_sql_1 = r#"
INSERT INTO consent (id, tenant_id, patient_id, consent_type, consent_scope, status, granted_at, consent_method, created_at, updated_at, version)
SELECT
gen_random_uuid(),
p.tenant_id,
p.id,
'data_processing',
'all',
'granted',
NOW(),
'system_auto',
NOW(),
NOW(),
1
FROM patient p
WHERE p.status = 'active'
AND p.deleted_at IS NULL
AND NOT EXISTS (
SELECT 1 FROM consent c
WHERE c.patient_id = p.id
AND c.tenant_id = p.tenant_id
AND c.consent_type = 'data_processing'
AND c.deleted_at IS NULL
)
"#;
manager
.get_connection()
.execute_unprepared(seed_consent_sql_1)
.await?;
// 3. 为所有现有活跃患者自动授予 health_data_collection 同意
let seed_consent_sql_2 = r#"
INSERT INTO consent (id, tenant_id, patient_id, consent_type, consent_scope, status, granted_at, consent_method, created_at, updated_at, version)
SELECT
gen_random_uuid(),
p.tenant_id,
p.id,
'health_data_collection',
'all',
'granted',
NOW(),
'system_auto',
NOW(),
NOW(),
1
FROM patient p
WHERE p.status = 'active'
AND p.deleted_at IS NULL
AND NOT EXISTS (
SELECT 1 FROM consent c
WHERE c.patient_id = p.id
AND c.tenant_id = p.tenant_id
AND c.consent_type = 'health_data_collection'
AND c.deleted_at IS NULL
)
"#;
manager
.get_connection()
.execute_unprepared(seed_consent_sql_2)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// 删除系统自动生成的 consent 记录
let delete_sql = r#"
DELETE FROM consent WHERE consent_method = 'system_auto'
"#;
manager
.get_connection()
.execute_unprepared(delete_sql)
.await?;
// 移除 phone 和 phone_hash 列
manager
.alter_table(
Table::alter()
.table(Alias::new("patient"))
.drop_column(Alias::new("phone"))
.drop_column(Alias::new("phone_hash"))
.to_owned(),
)
.await?;
Ok(())
}
}

View File

@@ -0,0 +1,34 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(Alias::new("article"))
.add_column(
ColumnDef::new(Alias::new("is_public"))
.boolean()
.not_null()
.default(true),
)
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(Alias::new("article"))
.drop_column(Alias::new("is_public"))
.to_owned(),
)
.await
}
}

View File

@@ -0,0 +1,170 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();
// 1) 注册 system.analytics.submit 幽灵权限(代码中 require_permission 使用但未注册)
let sys = "00000000-0000-0000-0000-000000000000";
db.execute_unprepared(&format!(
"INSERT INTO permissions (id, tenant_id, name, code, resource, action, description, created_at, updated_at, created_by, updated_by, deleted_at, version) \
SELECT gen_random_uuid(), t.id, '提交埋点数据', 'system.analytics.submit', 'system', 'submit', '小程序端埋点数据批量提交', NOW(), NOW(), '{sys}', '{sys}', NULL, 1 \
FROM tenant t \
WHERE NOT EXISTS (SELECT 1 FROM permissions p WHERE p.tenant_id = t.id AND p.code = 'system.analytics.submit' AND p.deleted_at IS NULL)"
)).await?;
// 2) 患者角色缺失的 .manage 权限(小程序端写入操作)
let patient_manage_perms: &[&str] = &[
// 体征录入
"health.health-data.manage",
// 日常监测创建
"health.daily-monitoring.manage",
// 预约创建/取消
"health.appointment.manage",
// 医生列表(预约选医生)
"health.doctor.list",
// 随访提交
"health.follow-up.manage",
// 咨询创建/发送消息
"health.consultation.manage",
// 药物提醒 CRUD
"health.medication-reminders.manage",
// 知情同意授权/撤回
"health.consent.manage",
// 设备数据上传
"health.device-readings.manage",
// 患者自更新(绑定手机、自助建档)
"health.patient.manage",
// AI 分析报告查看
"ai.analysis.list",
// AI 聊天会话列表
"ai.chat.session.list",
// AI 聊天会话管理
"ai.chat.session.manage",
// 埋点提交
"system.analytics.submit",
];
// 为所有租户的 patient 角色批量分配幂等data_scope=self
assign_perms_by_codes(db, "patient", patient_manage_perms).await?;
// 3) 患者角色缺失的 .list 权限
let patient_list_perms: &[&str] = &[
// 化验报告 + 健康记录 + 诊断记录 + 体征列表(共享 health.health-data.list
"health.health-data.list",
// 行动收件箱(首页工作台)
"health.action-inbox.list",
];
assign_perms_by_codes(db, "patient", patient_list_perms).await?;
// 4) 为 admin/doctor/nurse/health_manager 角色 also 分配 system.analytics.submit
// 这些角色可能也需要埋点权限
let analytics_roles: &[&str] = &["admin", "doctor", "nurse", "health_manager"];
for role in analytics_roles {
assign_single_perm(db, role, "system.analytics.submit").await?;
}
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();
// 移除 patient 角色新增的权限关联
let remove_codes: &[&str] = &[
"health.health-data.manage",
"health.health-data.list",
"health.daily-monitoring.manage",
"health.appointment.manage",
"health.doctor.list",
"health.follow-up.manage",
"health.consultation.manage",
"health.medication-reminders.manage",
"health.consent.manage",
"health.device-readings.manage",
"health.patient.manage",
"ai.analysis.list",
"ai.chat.session.list",
"ai.chat.session.manage",
"system.analytics.submit",
"health.action-inbox.list",
];
let codes_csv: String = remove_codes
.iter()
.map(|c| format!("'{}'", c))
.collect::<Vec<_>>()
.join(",");
db.execute_unprepared(&format!(
"DELETE FROM role_permissions \
WHERE role_id IN (SELECT id FROM roles WHERE code = 'patient') \
AND permission_id IN (SELECT id FROM permissions WHERE code IN ({codes_csv}))"
))
.await?;
// 移除其他角色的 system.analytics.submit
let analytics_roles: &[&str] = &["admin", "doctor", "nurse", "health_manager"];
for role in analytics_roles {
db.execute_unprepared(&format!(
"DELETE FROM role_permissions \
WHERE role_id IN (SELECT id FROM roles WHERE code = '{role}') \
AND permission_id IN (SELECT id FROM permissions WHERE code = 'system.analytics.submit')"
)).await?;
}
// 软删除 system.analytics.submit 权限
db.execute_unprepared(
"UPDATE permissions SET deleted_at = NOW() WHERE code = 'system.analytics.submit' AND deleted_at IS NULL"
).await?;
Ok(())
}
}
async fn assign_perms_by_codes(
db: &sea_orm_migration::prelude::SchemaManagerConnection<'_>,
role_code: &str,
perm_codes: &[&str],
) -> Result<(), DbErr> {
let codes_csv: String = perm_codes
.iter()
.map(|c| format!("'{}'", c))
.collect::<Vec<_>>()
.join(",");
db.execute_unprepared(&format!(
"INSERT INTO role_permissions (role_id, permission_id, tenant_id, data_scope, created_at, updated_at, created_by, updated_by, deleted_at, version) \
SELECT r.id, p.id, r.tenant_id, 'self', NOW(), NOW(), r.id, r.id, NULL, 1 \
FROM roles r \
JOIN permissions p ON p.tenant_id = r.tenant_id AND p.code IN ({codes_csv}) AND p.deleted_at IS NULL \
WHERE r.code = '{role_code}' AND r.deleted_at IS NULL \
ON CONFLICT (role_id, permission_id) WHERE deleted_at IS NULL \
DO UPDATE SET deleted_at = NULL, version = role_permissions.version + 1, updated_at = NOW()"
)).await?;
Ok(())
}
async fn assign_single_perm(
db: &sea_orm_migration::prelude::SchemaManagerConnection<'_>,
role_code: &str,
perm_code: &str,
) -> Result<(), DbErr> {
db.execute_unprepared(&format!(
"INSERT INTO role_permissions (role_id, permission_id, tenant_id, data_scope, created_at, updated_at, created_by, updated_by, deleted_at, version) \
SELECT r.id, p.id, r.tenant_id, 'all', NOW(), NOW(), r.id, r.id, NULL, 1 \
FROM roles r \
JOIN permissions p ON p.tenant_id = r.tenant_id AND p.code = '{perm_code}' AND p.deleted_at IS NULL \
WHERE r.code = '{role_code}' AND r.deleted_at IS NULL \
ON CONFLICT (role_id, permission_id) WHERE deleted_at IS NULL \
DO UPDATE SET deleted_at = NULL, version = role_permissions.version + 1, updated_at = NOW()"
)).await?;
Ok(())
}