From 74551d48e607305630ce50db5ad257f98667678d Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 2 Jun 2026 12:24:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(server):=20=E6=B7=BB=E5=8A=A0=E6=9A=96?= =?UTF-8?q?=E8=AE=B0=E6=97=A5=E8=AE=B0=E7=AE=A1=E7=90=86=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E7=A7=8D=E5=AD=90=E6=95=B0=E6=8D=AE=20+=20=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E6=B3=A8=E5=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增迁移 m20260602_000301_diary_menu_seed - 插入'日记管理'目录菜单 (BookOutlined, sort=50) - 子菜单: 班级管理/日记审核/主题管理/贴纸管理 - 关联 admin + teacher 角色 (menu_roles) - 图标注册: BookOutlined, ScheduleOutlined, SmileOutlined --- apps/web/src/utils/iconRegistry.tsx | 9 + crates/erp-server/migration/src/lib.rs | 2 + .../src/m20260602_000301_diary_menu_seed.rs | 208 ++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 crates/erp-server/migration/src/m20260602_000301_diary_menu_seed.rs diff --git a/apps/web/src/utils/iconRegistry.tsx b/apps/web/src/utils/iconRegistry.tsx index 3de6490..86d6b69 100644 --- a/apps/web/src/utils/iconRegistry.tsx +++ b/apps/web/src/utils/iconRegistry.tsx @@ -49,6 +49,10 @@ import { SolutionOutlined, SwapOutlined, WifiOutlined, + // 暖记日记模块 + BookOutlined, + ScheduleOutlined, + SmileOutlined, } from '@ant-design/icons'; import type { ReactNode } from 'react'; @@ -99,6 +103,11 @@ export const iconRegistry: Record = { SolutionOutlined: , SwapOutlined: , WifiOutlined: , + + // 暖记日记模块 + BookOutlined: , + ScheduleOutlined: , + SmileOutlined: , }; export function getIcon(name?: string): ReactNode { diff --git a/crates/erp-server/migration/src/lib.rs b/crates/erp-server/migration/src/lib.rs index b5a8120..201218e 100644 --- a/crates/erp-server/migration/src/lib.rs +++ b/crates/erp-server/migration/src/lib.rs @@ -71,6 +71,7 @@ mod m20260531_000182_create_user_settings; mod m20260531_000183_diary_indexes_and_fts; mod m20260531_000184_diary_seed_data; mod m20260601_000300_diary_role_seed; +mod m20260602_000301_diary_menu_seed; pub struct Migrator; @@ -148,6 +149,7 @@ impl MigratorTrait for Migrator { Box::new(m20260531_000183_diary_indexes_and_fts::Migration), Box::new(m20260531_000184_diary_seed_data::Migration), Box::new(m20260601_000300_diary_role_seed::Migration), + Box::new(m20260602_000301_diary_menu_seed::Migration), ] } } diff --git a/crates/erp-server/migration/src/m20260602_000301_diary_menu_seed.rs b/crates/erp-server/migration/src/m20260602_000301_diary_menu_seed.rs new file mode 100644 index 0000000..8cd330d --- /dev/null +++ b/crates/erp-server/migration/src/m20260602_000301_diary_menu_seed.rs @@ -0,0 +1,208 @@ +// 暖记菜单种子 — 日记管理侧边栏菜单(班级管理/日记审核/主题管理/贴纸管理) + +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 conn = manager.get_connection(); + let tid = "'00000000-0000-0000-0000-000000000000'::uuid"; + + // 1. 创建"日记管理"顶级目录菜单 + conn.execute(sea_orm::Statement::from_string( + sea_orm::DatabaseBackend::Postgres, + format!( + r#"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 ( + 'a0000001-0000-0000-0000-000000000001'::uuid, + {tid}, + NULL, + '日记管理', + NULL, + 'BookOutlined', + 50, + true, + 'directory', + NULL, + now(), now(), {tid}, {tid}, NULL, 1 + ) + ON CONFLICT (id) DO NOTHING"#, + ), + )) + .await + .map_err(|e| DbErr::Custom(e.to_string()))?; + + // 2. 子菜单: 班级管理 + conn.execute(sea_orm::Statement::from_string( + sea_orm::DatabaseBackend::Postgres, + format!( + r#"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 ( + 'a0000001-0000-0000-0000-000000000010'::uuid, + {tid}, + 'a0000001-0000-0000-0000-000000000001'::uuid, + '班级管理', + '/diary/classes', + 'TeamOutlined', + 1, + true, + 'menu', + 'diary.class.manage', + now(), now(), {tid}, {tid}, NULL, 1 + ) + ON CONFLICT (id) DO NOTHING"#, + ), + )) + .await + .map_err(|e| DbErr::Custom(e.to_string()))?; + + // 3. 子菜单: 日记审核 + conn.execute(sea_orm::Statement::from_string( + sea_orm::DatabaseBackend::Postgres, + format!( + r#"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 ( + 'a0000001-0000-0000-0000-000000000020'::uuid, + {tid}, + 'a0000001-0000-0000-0000-000000000001'::uuid, + '日记审核', + '/diary/journals', + 'FileSearchOutlined', + 2, + true, + 'menu', + 'diary.journal.read', + now(), now(), {tid}, {tid}, NULL, 1 + ) + ON CONFLICT (id) DO NOTHING"#, + ), + )) + .await + .map_err(|e| DbErr::Custom(e.to_string()))?; + + // 4. 子菜单: 主题管理 + conn.execute(sea_orm::Statement::from_string( + sea_orm::DatabaseBackend::Postgres, + format!( + r#"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 ( + 'a0000001-0000-0000-0000-000000000030'::uuid, + {tid}, + 'a0000001-0000-0000-0000-000000000001'::uuid, + '主题管理', + '/diary/topics', + 'ScheduleOutlined', + 3, + true, + 'menu', + 'diary.topic.assign', + now(), now(), {tid}, {tid}, NULL, 1 + ) + ON CONFLICT (id) DO NOTHING"#, + ), + )) + .await + .map_err(|e| DbErr::Custom(e.to_string()))?; + + // 5. 子菜单: 贴纸管理 + conn.execute(sea_orm::Statement::from_string( + sea_orm::DatabaseBackend::Postgres, + format!( + r#"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 ( + 'a0000001-0000-0000-0000-000000000040'::uuid, + {tid}, + 'a0000001-0000-0000-0000-000000000001'::uuid, + '贴纸管理', + '/diary/stickers', + 'SmileOutlined', + 4, + true, + 'menu', + 'diary.journal.read', + now(), now(), {tid}, {tid}, NULL, 1 + ) + ON CONFLICT (id) DO NOTHING"#, + ), + )) + .await + .map_err(|e| DbErr::Custom(e.to_string()))?; + + // 6. 将日记管理目录及其子菜单关联到 admin 角色 + let menu_ids = [ + "a0000001-0000-0000-0000-000000000001", // 日记管理 (directory) + "a0000001-0000-0000-0000-000000000010", // 班级管理 + "a0000001-0000-0000-0000-000000000020", // 日记审核 + "a0000001-0000-0000-0000-000000000030", // 主题管理 + "a0000001-0000-0000-0000-000000000040", // 贴纸管理 + ]; + + for mid in &menu_ids { + conn.execute(sea_orm::Statement::from_string( + sea_orm::DatabaseBackend::Postgres, + format!( + r#"INSERT INTO menu_roles (id, menu_id, role_id, tenant_id, created_at, updated_at, created_by, updated_by, version) + SELECT gen_random_uuid(), '{mid}'::uuid, r.id, r.tenant_id, now(), now(), {tid}, {tid}, 1 + FROM roles r + WHERE r.code = 'admin' AND r.tenant_id = {tid} AND r.deleted_at IS NULL + ON CONFLICT DO NOTHING"#, + ), + )) + .await + .map_err(|e| DbErr::Custom(e.to_string()))?; + } + + // 7. 也将菜单关联到 teacher 角色 + for mid in &menu_ids { + conn.execute(sea_orm::Statement::from_string( + sea_orm::DatabaseBackend::Postgres, + format!( + r#"INSERT INTO menu_roles (id, menu_id, role_id, tenant_id, created_at, updated_at, created_by, updated_by, version) + SELECT gen_random_uuid(), '{mid}'::uuid, r.id, r.tenant_id, now(), now(), {tid}, {tid}, 1 + FROM roles r + WHERE r.code = 'teacher' AND r.tenant_id = {tid} AND r.deleted_at IS NULL + ON CONFLICT DO NOTHING"#, + ), + )) + .await + .map_err(|e| DbErr::Custom(e.to_string()))?; + } + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let conn = manager.get_connection(); + + // 先删除 menu_roles 引用 + let menu_ids = [ + "a0000001-0000-0000-0000-000000000001", + "a0000001-0000-0000-0000-000000000010", + "a0000001-0000-0000-0000-000000000020", + "a0000001-0000-0000-0000-000000000030", + "a0000001-0000-0000-0000-000000000040", + ]; + + for mid in &menu_ids { + conn.execute(sea_orm::Statement::from_string( + sea_orm::DatabaseBackend::Postgres, + format!("DELETE FROM menu_roles WHERE menu_id = '{mid}'::uuid"), + )) + .await + .map_err(|e| DbErr::Custom(e.to_string()))?; + } + + // 删除菜单 (子菜单会被 CASCADE 自动删除) + conn.execute(sea_orm::Statement::from_string( + sea_orm::DatabaseBackend::Postgres, + "DELETE FROM menus WHERE id = 'a0000001-0000-0000-0000-000000000001'::uuid".to_string(), + )) + .await + .map_err(|e| DbErr::Custom(e.to_string()))?; + + Ok(()) + } +}