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:
3
crates/erp-server/_server_err3.txt
Normal file
3
crates/erp-server/_server_err3.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.81s
|
||||
Running `G:\hms\target\debug\erp-server.exe`
|
||||
error: process didn't exit successfully: `G:\hms\target\debug\erp-server.exe` (exit code: 1)
|
||||
@@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -24,6 +24,7 @@ fn default_create_article_req() -> CreateArticleReq {
|
||||
content_type: None,
|
||||
category_id: None,
|
||||
tag_ids: vec![],
|
||||
is_public: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,6 +207,7 @@ async fn test_article_update() {
|
||||
category_id: None,
|
||||
tag_ids: None,
|
||||
sort_order: None,
|
||||
is_public: None,
|
||||
version: article.version,
|
||||
},
|
||||
)
|
||||
@@ -248,6 +250,7 @@ async fn test_article_list_filter() {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -263,6 +266,7 @@ async fn test_article_list_filter() {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -417,6 +421,7 @@ async fn test_tag_crud_and_article_association() {
|
||||
content_type: None,
|
||||
category_id: None,
|
||||
sort_order: None,
|
||||
is_public: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -489,6 +494,7 @@ async fn test_article_version_conflict() {
|
||||
category_id: None,
|
||||
tag_ids: None,
|
||||
sort_order: None,
|
||||
is_public: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -514,6 +520,7 @@ async fn test_article_version_conflict() {
|
||||
category_id: None,
|
||||
tag_ids: None,
|
||||
sort_order: None,
|
||||
is_public: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
Reference in New Issue
Block a user