feat(config): 菜单动态化改造 — 侧边栏从后端 API 加载
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- 新增 seed 迁移插入完整菜单树(4 directory + 23 menu = 27 条)
- 新增 GET /api/v1/menus/user 端点(仅需登录,无需 menu.list 权限)
- MainLayout 从 API 动态获取菜单树替换硬编码数组
- 扩展图标映射表覆盖 22 个 Ant Design 图标
- Header 标题从动态菜单数据查找,保留 fallback
This commit is contained in:
iven
2026-04-26 01:55:01 +08:00
parent 2539e5fc44
commit e3177f262c
6 changed files with 323 additions and 138 deletions

View File

@@ -58,6 +58,7 @@ mod m20260425_000055_points_checkin_standard_fields;
mod m20260426_000056_create_diagnosis;
mod m20260426_000057_rename_points_transaction_type_column;
mod m20260426_000058_merge_daily_monitoring_into_vital_signs;
mod m20260426_000059_seed_menus;
pub struct Migrator;
@@ -123,6 +124,7 @@ impl MigratorTrait for Migrator {
Box::new(m20260426_000056_create_diagnosis::Migration),
Box::new(m20260426_000057_rename_points_transaction_type_column::Migration),
Box::new(m20260426_000058_merge_daily_monitoring_into_vital_signs::Migration),
Box::new(m20260426_000059_seed_menus::Migration),
]
}
}

View File

@@ -0,0 +1,128 @@
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();
// 软删除已有菜单数据
db.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
"UPDATE menus SET deleted_at = NOW() WHERE deleted_at IS NULL".to_string(),
))
.await?;
// 获取默认租户 ID
let result = db.query_one(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
"SELECT id::text FROM tenant LIMIT 1".to_string(),
))
.await?;
let tid = match result {
Some(row) => row.try_get_by_index::<String>(0).unwrap_or_default(),
None => return Ok(()),
};
let sys = "00000000-0000-0000-0000-000000000000";
let nil = "NULL";
// === Directory 节点 ===
insert_dir(db, &tid, "a0000000-0000-0000-0000-000000000001", "基础模块", 1, sys).await?;
insert_dir(db, &tid, "a0000000-0000-0000-0000-000000000002", "业务模块", 2, sys).await?;
insert_dir(db, &tid, "a0000000-0000-0000-0000-000000000003", "健康管理", 3, sys).await?;
insert_dir(db, &tid, "a0000000-0000-0000-0000-000000000004", "系统", 4, sys).await?;
// === 基础模块菜单 ===
let d1 = "a0000000-0000-0000-0000-000000000001";
insert_menu(db, &tid, d1, "b0000001-0000-0000-0000-000000000001", "工作台", "/", "HomeOutlined", 0, sys).await?;
insert_menu(db, &tid, d1, "b0000001-0000-0000-0000-000000000002", "用户管理", "/users", "UserOutlined", 1, sys).await?;
insert_menu(db, &tid, d1, "b0000001-0000-0000-0000-000000000003", "权限管理", "/roles", "SafetyOutlined", 2, sys).await?;
insert_menu(db, &tid, d1, "b0000001-0000-0000-0000-000000000004", "组织架构", "/organizations", "ApartmentOutlined", 3, sys).await?;
// === 业务模块菜单 ===
let d2 = "a0000000-0000-0000-0000-000000000002";
insert_menu(db, &tid, d2, "b0000002-0000-0000-0000-000000000001", "工作流", "/workflow", "PartitionOutlined", 0, sys).await?;
insert_menu(db, &tid, d2, "b0000002-0000-0000-0000-000000000002", "消息中心", "/messages", "MessageOutlined", 1, sys).await?;
// === 健康管理菜单 ===
let d3 = "a0000000-0000-0000-0000-000000000003";
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000001", "统计报表", "/health/statistics", "DashboardOutlined", 0, sys).await?;
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000002", "患者管理", "/health/patients", "TeamOutlined", 1, sys).await?;
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000003", "医护管理", "/health/doctors", "MedicineBoxOutlined", 2, sys).await?;
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000004", "预约排班", "/health/appointments", "CalendarOutlined", 3, sys).await?;
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000005", "排班管理", "/health/schedules", "HeartOutlined", 4, sys).await?;
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000006", "随访管理", "/health/follow-up-tasks", "PhoneOutlined", 5, sys).await?;
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000007", "咨询管理", "/health/consultations", "CommentOutlined", 6, sys).await?;
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000008", "标签管理", "/health/tags", "TagsOutlined", 7, sys).await?;
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000009", "积分规则", "/health/points-rules", "TrophyOutlined", 8, sys).await?;
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000010", "商品管理", "/health/points-products", "ShopOutlined", 9, sys).await?;
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000011", "订单管理", "/health/points-orders", "FileTextOutlined", 10, sys).await?;
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000012", "线下活动", "/health/offline-events", "CalendarOutlined", 11, sys).await?;
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000013", "AI Prompt 管理", "/health/ai-prompts", "RobotOutlined", 12, sys).await?;
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000014", "AI 分析历史", "/health/ai-analysis", "HistoryOutlined", 13, sys).await?;
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000015", "AI 用量统计", "/health/ai-usage", "BarChartOutlined", 14, sys).await?;
// === 系统菜单 ===
let d4 = "a0000000-0000-0000-0000-000000000004";
insert_menu(db, &tid, d4, "b0000004-0000-0000-0000-000000000001", "系统设置", "/settings", "SettingOutlined", 0, sys).await?;
insert_menu(db, &tid, d4, "b0000004-0000-0000-0000-000000000002", "插件管理", "/plugins/admin", "AppstoreOutlined", 1, sys).await?;
let _ = nil;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();
db.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
"DELETE FROM menus WHERE id LIKE 'a0000000-0000-0000-0000-00000000000%' OR id LIKE 'b000000%-0000-0000-0000-00000000000%'".to_string(),
))
.await?;
Ok(())
}
}
async fn insert_dir(
db: &sea_orm_migration::SchemaManagerConnection<'_>,
tenant_id: &str,
id: &str,
title: &str,
sort: i32,
sys: &str,
) -> Result<(), DbErr> {
db.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
format!(
"INSERT INTO menus (id, tenant_id, parent_id, title, path, icon, sort_order, visible, menu_type, permission, created_at, updated_at, created_by, updated_by, deleted_at, version) \
VALUES ('{id}', '{tenant_id}', NULL, '{title}', NULL, NULL, {sort}, true, 'directory', NULL, NOW(), NOW(), '{sys}', '{sys}', NULL, 1) ON CONFLICT (id) DO NOTHING"
),
))
.await?;
Ok(())
}
async fn insert_menu(
db: &sea_orm_migration::SchemaManagerConnection<'_>,
tenant_id: &str,
parent_id: &str,
id: &str,
title: &str,
path: &str,
icon: &str,
sort: i32,
sys: &str,
) -> Result<(), DbErr> {
db.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
format!(
"INSERT INTO menus (id, tenant_id, parent_id, title, path, icon, sort_order, visible, menu_type, permission, created_at, updated_at, created_by, updated_by, deleted_at, version) \
VALUES ('{id}', '{tenant_id}', '{parent_id}', '{title}', '{path}', '{icon}', {sort}, true, 'menu', NULL, NOW(), NOW(), '{sys}', '{sys}', NULL, 1) ON CONFLICT (id) DO NOTHING"
),
))
.await?;
Ok(())
}