From f565437125739e4164f4623125ea7b5b7c3ff915 Mon Sep 17 00:00:00 2001 From: ZhangAo Date: Tue, 18 Aug 2026 17:28:17 +0800 Subject: [PATCH] 20260818172816 --- code/fms/FMS新系统架构设计.md | 134 ++- code/fms/FMS新系统核心表结构设计.md | 1137 ++++++++++++++++++ code/fms/fms-mvp-demo/.gitignore | 24 + code/fms/fms-mvp-demo/.oxlintrc.json | 8 + code/fms/fms-mvp-demo/README.md | 32 + code/fms/fms-mvp-demo/index.html | 13 + code/fms/fms-mvp-demo/package.json | 32 + code/fms/fms-mvp-demo/pnpm-lock.yaml | 1213 ++++++++++++++++++++ code/fms/fms-mvp-demo/public/favicon.svg | 1 + code/fms/fms-mvp-demo/public/icons.svg | 24 + code/fms/fms-mvp-demo/src/App.css | 280 +++++ code/fms/fms-mvp-demo/src/App.tsx | 305 +++++ code/fms/fms-mvp-demo/src/assets/hero.png | Bin 0 -> 13057 bytes code/fms/fms-mvp-demo/src/assets/react.svg | 1 + code/fms/fms-mvp-demo/src/assets/vite.svg | 1 + code/fms/fms-mvp-demo/src/index.css | 16 + code/fms/fms-mvp-demo/src/main.tsx | 10 + code/fms/fms-mvp-demo/tsconfig.app.json | 26 + code/fms/fms-mvp-demo/tsconfig.json | 7 + code/fms/fms-mvp-demo/tsconfig.node.json | 23 + code/fms/fms-mvp-demo/vite.config.ts | 8 + 21 files changed, 3281 insertions(+), 14 deletions(-) create mode 100644 code/fms/FMS新系统核心表结构设计.md create mode 100644 code/fms/fms-mvp-demo/.gitignore create mode 100644 code/fms/fms-mvp-demo/.oxlintrc.json create mode 100644 code/fms/fms-mvp-demo/README.md create mode 100644 code/fms/fms-mvp-demo/index.html create mode 100644 code/fms/fms-mvp-demo/package.json create mode 100644 code/fms/fms-mvp-demo/pnpm-lock.yaml create mode 100644 code/fms/fms-mvp-demo/public/favicon.svg create mode 100644 code/fms/fms-mvp-demo/public/icons.svg create mode 100644 code/fms/fms-mvp-demo/src/App.css create mode 100644 code/fms/fms-mvp-demo/src/App.tsx create mode 100644 code/fms/fms-mvp-demo/src/assets/hero.png create mode 100644 code/fms/fms-mvp-demo/src/assets/react.svg create mode 100644 code/fms/fms-mvp-demo/src/assets/vite.svg create mode 100644 code/fms/fms-mvp-demo/src/index.css create mode 100644 code/fms/fms-mvp-demo/src/main.tsx create mode 100644 code/fms/fms-mvp-demo/tsconfig.app.json create mode 100644 code/fms/fms-mvp-demo/tsconfig.json create mode 100644 code/fms/fms-mvp-demo/tsconfig.node.json create mode 100644 code/fms/fms-mvp-demo/vite.config.ts diff --git a/code/fms/FMS新系统架构设计.md b/code/fms/FMS新系统架构设计.md index 18388a05..343c4134 100644 --- a/code/fms/FMS新系统架构设计.md +++ b/code/fms/FMS新系统架构设计.md @@ -150,7 +150,7 @@ - 普通业务页面可以由通用运行时生成,复杂业务允许代码扩展。 - IT 开发人员可以方便地增加特殊 Vue 页面、业务 Handler 和特殊 POST 接口。 - 每次模块配置保存都生成不可变配置版本,默认可以立即生效,也可以先预览验证再激活,并支持回滚、导出、导入和跨环境迁移。 -- 菜单、动作、数据范围和配置管理权限由统一权限中心处理,字段级隐藏和脱敏作为后续扩展。 +- 菜单、动作、数据范围、字段级访问和配置管理权限由统一权限中心处理;首期支持按用户、角色或用户组配置字段查看和导出权限,复杂条件脱敏作为后续扩展。 - 菜单、页面、字段、动作、字典和主数据支持统一多语言解析与回退。 - 技术可观测日志和客户可见的业务操作日志分层存储并可关联追踪。 - 系统元数据具备完整业务语义,可直接供 AI 理解和调用。 @@ -281,9 +281,11 @@ entities: - ocean.booking - ocean.shipment - ocean.container -routes: - - /ocean/bookings - - /ocean/shipments +pages: + - ocean.booking.default-list + - ocean.booking.default-form +menus: + - ocean.booking ``` 模块依赖必须形成有向无环图,不能循环依赖。每个实体、动作、事件和特殊页面只能有一个所属模块;其他模块通过声明依赖后使用公开实体、查询、动作或事件,不能绕过通用运行时直接修改其他模块的数据表。 @@ -306,7 +308,18 @@ routes: 默认表单:ocean.booking.default-form ``` -一个实体可以有多个列表、表单和详情页面。页面配置不能反过来决定实体的数据结构。 +一个实体可以有多个列表、表单和详情页面。页面配置不能反过来决定实体的数据结构。页面是稳定的访问入口,负责绑定路由、视图/表单或特殊组件以及页面访问权限;菜单只负责组织页面入口,不重复维护内部页面路由。 + +页面和菜单的配置关系如下: + +```text +实体 +|- 视图 / 表单 +|- 页面(page_key + route + permission) +`- 菜单(menu_key + parent + page_key + sort + visible) +``` + +菜单类型至少包括 `directory`、`page` 和 `external`。目录菜单不直接代表业务页面;只有子页面存在且当前用户有页面访问权限时,目录才显示。内部 `page` 菜单的路由始终从页面资源解析,外链菜单才允许单独配置 URL。 ### 7.2 字段的稳定标识 @@ -396,6 +409,8 @@ writeMode: direct 客户、港口等共享主数据通常是普通引用,删除业务单不能删除客户;订舱明细、箱信息等可能属于组合关系,需要明确随主单归档或删除。`saveobjt` 的多实体事务必须按照关系元数据检查外键、所有权和删除顺序。 +组合关系可以作为主实体编辑表单中的子表区域使用。子表区域引用一个 `meta_relation`,并绑定子实体的列表列配置和编辑配置;通用运行时负责维护主外键、行排序、增删改和主子表事务。复杂的跨行计算、跨实体联动和状态变化仍通过动作或特殊 Handler 实现。 + ### 7.6 删除策略 不采用所有实体统一软删除。`meta_entity` 必须声明 `deletePolicy`,通用保存和动作引擎根据实体类型、引用关系和业务状态决定是否允许删除: @@ -433,10 +448,13 @@ writeMode: direct | `meta_relation` | 实体关系和引用规则 | | `meta_dictionary` | 固定字典和选项 | | `meta_query_source` | 复杂查询数据源 | +| `meta_page` | 页面入口、路由、页面类型、组件和访问权限 | +| `meta_menu` | 菜单层级、页面/外链绑定、权限、图标、顺序和可见性 | | `meta_view` | 列表、详情等视图定义 | | `meta_view_field` | 列表字段、顺序、宽度和格式 | | `meta_form` | 表单定义 | | `meta_form_field` | 表单分组、控件、校验和布局 | +| `meta_form_relation` | 表单中的主子关系区域、子表视图和编辑行为 | | `meta_action` | 通用动作和业务动作 | | `meta_i18n` | 多语言资源 | | `meta_change_set` | 配置和结构变更集 | @@ -496,6 +514,46 @@ updated_by 模块 bundle 必须携带多语言资源。售后可以补充和修改翻译,配置发布时一同生成变更集。AI 可以生成批量翻译草稿,但需要保留来源、模型版本并经过人工确认后发布。 +### 8.2 页面与菜单配置 + +页面和菜单都使用稳定业务 key,并随模块配置版本一起发布。推荐的页面配置如下: + +```yaml +page: + key: ocean.booking.default-list + module: ocean + type: runtime + route: /ocean/bookings + view: ocean.booking.default-list + permission: ocean.booking.view + visible: true +``` + +菜单配置只描述导航树,不重复描述页面布局: + +```yaml +menu: + key: ocean.booking + module: ocean + parent: ocean + type: page + page: ocean.booking.default-list + nameKey: menu.ocean.booking + icon: ship + sort: 10 + visible: true +``` + +配置校验必须保证: + +1. 页面引用的视图、表单、组件和权限资源存在且属于当前模块或其依赖模块。 +2. `page` 菜单必须绑定页面,`directory` 菜单不能绑定页面或 URL,`external` 菜单必须提供外部 URL 和菜单权限。 +3. 同一个激活 release 内,页面 route 唯一,菜单 key、页面 key 和父子关系无循环。 +4. 菜单显示继承页面访问权限;目录菜单下没有可访问页面时不显示。 +5. 页面、菜单名称和图标等显示信息通过统一 i18n 和资源配置解析。 + +新增或修改页面、菜单、顺序、可见性和路由时,只生成元数据变更集;新增特殊 Vue 组件仍需代码部署。 + ## 9. Select 与引用关系 Select 选项应当是一等模型,不再要求为每一个下拉框创建数据库视图。 @@ -861,6 +919,30 @@ removableAfterVersion 例如把已有的 `varchar(50)` 扩大到 `varchar(200)`,或把平台已支持的整数字段迁移为 decimal,属于配置发布和数据库迁移,不属于增加代码能力。变更前仍需完成数据可转换检查、依赖分析、备份检查和影响预览。缩短长度、改变精度或物理删除字段应采用第 11.3 节的渐进式迁移,不能因为“不需要重启”就直接执行危险 DDL。 +### 12.6 拖拽式主表单与子表配置 + +普通主表单和主子表页面由配置中心通过拖拽设计器生成。设计器提供字段、关系、分组和标准控件的组件面板,技术人员可以把字段拖入表单,把组合关系拖入子表区域,并配置顺序、宽度、分组、只读、必填、默认控件和子表操作。 + +推荐的元数据关系如下: + +```text +meta_form +|- meta_form_field 主实体字段项 +|- meta_form_relation 主子关系区域 +| |- relation_key +| |- child_view_key 子表列配置 +| |- child_form_key 子表编辑配置(可选) +| |- display_mode table / form / tabs +| |- editable +| |- allow_add +| |- allow_delete +| `- allow_sort +``` + +字段、关系、顺序、可见性、宽度和控件类型等稳定属性使用结构化元数据保存;控件特有的属性和布局扩展使用经过 Schema 校验的 JSON 配置。设计器保存时生成 `change_set` 和候选 release,支持预览、校验、发布和回滚,不直接修改当前运行版本。 + +主表和子表保存使用同一个 `saveobjt` 原子事务。请求可以同时提交主实体和子实体的新增、修改、删除,运行时根据 `meta_relation` 校验所有权、外键、唯一性、删除策略、字段权限、乐观锁和业务校验。子表不是简单地把前端数组覆盖到数据库,正式业务状态下的删除、拆分、合并和跨行计算必须调用动作或特殊 Handler。 + ## 13. 通用数据运行时 内部 ERP 以灵活性和开发效率为优先。后端保留少量、稳定、全部使用 POST 的通用能力接口,不为海运订舱、空运单、客户等每个普通实体新增 Controller 和 URL。 @@ -964,6 +1046,8 @@ page -> 返回当前页、总数、页码和分页大小 } ``` +对于主子表编辑,前端可以在一次请求中提交主实体和其组合关系下的子实体变更。通用运行时按关系元数据决定保存顺序、主键回填、行排序、删除策略和事务边界;涉及跨行汇总、状态变更、拆分合并或外部副作用时,必须改用 `action.execute` 或特殊 Handler。 + 通用运行时根据当前已发布的元数据完成: - 字段解析和类型转换 @@ -1087,15 +1171,17 @@ MetadataController - 字典和分类 - 普通业务数据列表 - 简单主表编辑 +- 主表编辑表单和一对多子表编辑表格 - 通用查询和导出 -复杂页面仍由开发人员编写 Vue 组件,例如: +复杂页面和复杂交互仍由 IT 开发人员编写 Vue 组件或 Handler,例如: - 海运工作台 - 订舱操作台 - 多箱、多提单联动编辑 - 费用结算 - 拖拽式单证编排 +- 多层嵌套、跨行公式和复杂状态联动 复杂页面仍然使用相同的实体、字段、引用、动作和通用数据能力,不应建立另一套数据定义。 @@ -1116,7 +1202,7 @@ MetadataController -> 随前端版本部署 ``` -特殊页面适合海运工作台、多箱多提单联动、费用结算、拖拽编排和高交互批量操作。不能为了追求完全配置化而降低开发人员实现复杂页面的效率。 +特殊页面适合海运工作台、多箱多提单联动、费用结算、拖拽编排和高交互批量操作。IT 开发人员可以继续复用同一套实体、关系、通用保存、动作和权限能力;不能为了追求完全配置化而降低开发人员实现复杂页面和复杂业务逻辑的效率。 ### 14.2 特殊页面注册 @@ -1251,11 +1337,12 @@ public class SplitBookingHandler { 统一权限中心 = RBAC 功能授权 + ABAC 数据策略 ++ 字段级访问策略 + 配置管理权限 + AI 工具权限 ``` -即:角色负责授予用户可以使用哪些功能,数据策略决定用户可以操作哪些记录,配置权限决定谁能修改实体、页面、权限和 Schema。字段级隐藏和脱敏暂不作为第一阶段要求。 +即:角色负责授予用户可以使用哪些功能,数据策略决定用户可以操作哪些记录,字段级访问策略决定用户可以查看、导出或脱敏哪些字段,配置权限决定谁能修改实体、页面、权限和 Schema。首期只做基于用户、角色或用户组的静态字段策略,不引入复杂的条件脱敏规则。 ### 16.1 权限主体 @@ -1294,6 +1381,8 @@ public class SplitBookingHandler { - 页面可以单独配置 `visible = false`,表示允许通过链接访问但不显示在菜单中。 - 菜单只是页面和模块的导航表现,不是最终权限判断点。 +菜单资源本身保存父子关系、顺序、图标、页面绑定和可见性;页面资源保存 route 和页面访问权限。权限中心返回可访问页面后,菜单运行时按当前 release 构建树并过滤无权页面,目录节点在没有可见子节点时自动隐藏。这样新增普通页面和菜单只需要提交配置变更集,不需要修改前端路由代码;特殊页面仍需先部署组件,再发布对应页面和菜单元数据。 + 建议的页面权限 key: ```text @@ -1380,15 +1469,27 @@ AND ### 16.6 字段显示与写入边界 -第一阶段不建设复杂的字段隐藏和脱敏权限。列表、表单是否显示某字段以及是否渲染为只读,由页面配置决定;后端是否允许写入由实体字段的 `writeMode` 决定。 +字段的页面展示、后端写入和权限可见性是三层不同概念。首期支持按用户、角色或用户组配置字段查看和导出权限,并预留基础脱敏方式;按记录条件、表达式或不同场景动态脱敏暂不纳入首期。 ```text 页面显示或只读 -> meta_view_field / meta_form_field +字段查看、导出和脱敏 -> auth_field_policy / 统一权限中心 后端允许普通写入 -> meta_field.writeMode 状态变化和受控字段 -> action.execute ``` -页面配置只是交互表现,不能把 `actionOnly`、`computed` 或 `system` 字段变成可写。以后出现采购底价、证件号等确实需要按角色隐藏或脱敏的字段时,再在统一权限中心增加可选字段策略,并让页面、数据 API、导出、报表和 AI 使用同一个策略;当前架构不预先实现这部分复杂度。 +字段策略至少支持以下维度: + +```text +view allow / deny +export allow / deny +mask none / partial / full +subject 用户、角色或用户组 +``` + +多个策略同时存在时,拒绝优先于允许;没有配置字段策略的普通字段按实体默认规则处理,敏感字段必须显式配置。字段权限必须由后端统一执行:列表、详情、通用查询、导入导出、报表、后台任务、特殊 Handler 和 AI 工具都必须经过同一个字段权限服务,不能只在前端隐藏列。查询引擎应在生成 SQL 投影或响应序列化阶段裁剪无权字段,导出和 AI 返回也不得通过别的接口绕过策略。 + +页面配置只是交互表现,不能把 `actionOnly`、`computed` 或 `system` 字段变成可写。字段可见性、导出权限和 `writeMode` 彼此独立;例如用户可以查看成本但不能导出,也可以导出脱敏后的值但不能修改。复杂的按数据条件脱敏、字段级表达式策略和细粒度临时授权留待后续扩展。 ### 16.7 配置管理权限 @@ -1452,9 +1553,10 @@ platform.ai.configure | `auth_role_permission` | 角色功能授权 | | `auth_data_policy` | 数据范围策略 | | `auth_role_data_policy` | 角色数据策略关系 | +| `auth_field_policy` | 字段查看、导出和脱敏策略 | | `auth_permission_snapshot` | 可选的编译缓存 | -权限定义、角色模板和数据策略应包含在模块 bundle 中,通过稳定 key 跨环境迁移。具体用户与角色的分配属于环境或租户数据,不应随模块定义覆盖。 +权限定义、角色模板、数据策略和字段策略应包含在模块 bundle 中,通过稳定 key 跨环境迁移。具体用户与角色的分配属于环境或租户数据,不应随模块定义覆盖。 ### 16.11 AI 权限继承 @@ -1464,7 +1566,7 @@ AI Gateway 必须继承发起用户的身份和权限上下文: 用户发起 AI 请求 -> AI Gateway 获取用户权限快照 -> 根据动作权限生成可用工具 --> 查询工具自动追加数据权限 +-> 查询工具自动追加数据权限和字段访问策略 -> 动作工具再次检查动作权限和业务前置条件 ``` @@ -2285,7 +2387,7 @@ created_at | 元数据 | 稳定 key、类型、关系、引用、公式和依赖合法性 | | Schema Compiler | 空库安装、快照差异计算、失败恢复、重复执行、结构对账和回滚兼容检查 | | 通用运行时 | 类型转换、关系保存、`writeMode`、删除策略、乐观锁、事务和批量操作 | -| 权限 | 菜单、动作、数据范围、导出、后台任务和 AI 使用相同决策 | +| 权限 | 菜单、动作、数据范围、字段查看/导出/基础脱敏、后台任务和 AI 使用相同决策 | | 数据源上下文 | 保持现有 Web 请求注入机制,并验证任务、发布和消息入口会正确设置及清理 `OrgContext` | | 幂等与任务 | 超时重试、重复回调、Worker 崩溃接管、死信和人工重跑 | | 模块兼容 | bundle 双向差异、稳定 key 冲突、特殊页面与 Handler 的 manifest 契约 | @@ -2348,7 +2450,7 @@ created_at | 复杂业务页面 | 编写代码 | 调整允许开放的配置 | | 复杂业务动作 | 编写 Handler | 绑定和配置动作 | | 特殊业务接口 | 使用扩展 SDK 开发 | 配置权限、菜单和参数 | -| 权限中心 | 提供决策引擎和策略类型 | 配置角色、菜单、动作和数据范围 | +| 权限中心 | 提供决策引擎和策略类型 | 配置角色、菜单、动作、数据范围和字段可见性 | | 双层日志 | 提供追踪、日志和审计框架 | 配置字段审计和客户可见范围 | | 模块迁移机制 | 提供 Compiler 和发布工具 | 导出、导入和发布变更集 | | AI 能力 | 提供工具和扩展点 | 使用 AI 生成配置候选版本 | @@ -2394,6 +2496,7 @@ created_at - 沿用并固化现有 `orgId -> OrgContext -> OrgRoutingDataSource` 数据源注入机制。 - 完成模块、实体、字段、关系、字典模型。 +- 完成页面入口和菜单树模型,明确页面路由、菜单绑定、排序、可见性和权限继承规则。 - 建立稳定 key 规则和语义描述规范。 - 确定真实列与扩展字段的边界。 - 实现不可变配置 release、当前指针、候选预览、回滚和变更集。 @@ -2416,9 +2519,12 @@ created_at - 实现 `writeMode`、乐观锁、请求幂等和数据库唯一约束。 - 实现字典和引用字段。 - 实现默认列表、表单和查询页面。 +- 实现页面运行时路由解析、按权限过滤菜单树和目录节点裁剪。 +- 实现拖拽式表单设计器、主子关系区域和一对多子表编辑表格。 - 实现运行时多语言解析、租户默认语言和用户语言切换。 - 实现售后配置中心。 - 实现动作和数据范围权限决策引擎。 +- 实现按用户、角色或用户组的字段查看、导出和基础脱敏策略,并在查询、保存、导出、报表、特殊 Handler 和 AI 工具中统一执行。 - 实现用户权限快照、缓存失效和权限解释。 - 实现统一 request/trace 上下文和结构化技术日志。 - 实现业务审计事件、字段变更和客户时间线。 diff --git a/code/fms/FMS新系统核心表结构设计.md b/code/fms/FMS新系统核心表结构设计.md new file mode 100644 index 00000000..c94b3765 --- /dev/null +++ b/code/fms/FMS新系统核心表结构设计.md @@ -0,0 +1,1137 @@ +# FMS 新系统核心表结构设计 + +## 1. 说明 + +本文档给出 FMS 新系统核心平台表的 SQL Server 建表语句,范围包括元数据、动态表单、配置版本、Schema 迁移、权限、技术日志、登录日志与业务审计。 + +约定如下: + +- 所有表名以 `s_` 开头,字段名以 `b_` 开头。 +- 表名、字段名、索引名和约束名统一使用 `snake_case`。 +- 不创建物理外键,关联字段只作为逻辑外键使用。 +- 逻辑外键必须创建必要的普通索引或唯一索引。 +- 一个租户使用一个独立业务数据库,普通业务表不重复保存 `b_tenant_id` 或 `b_org_id`;集中技术日志和登录日志允许使用 `b_org_id` 标识日志来源。 +- 内部主键统一使用 `bigint identity(1, 1)`,跨环境标识使用稳定的 `b_*_key`。 +- 同库内的逻辑关联 ID(例如 `b_form_id`、`b_release_id`)统一使用 `bigint`,不使用 `uniqueidentifier`。 +- 业务编号(例如 `b_change_set_no`、`b_release_no`)使用 `bigint`,由应用或数据库序列生成,不声明为 `identity`。 +- 时间点统一使用 `datetime2(3)`,应用层按 UTC 写入。 +- 稳定业务标识统一使用 `b_*_key`,跨环境导入导出不得依赖内部 `b_id`。 +- 扩展内容统一使用 `nvarchar(max)` 普通文本保存,由应用层按业务约定解析和校验,不使用 JSON 字段类型。 +- 原布尔语义字段统一使用 `tinyint`,取值约定为 `0`(否)或 `1`(是)。 +- 下面的 `b_*_id`、`b_*_key` 关联均为逻辑外键,由应用服务负责校验一致性。 + +本文档只定义平台核心表。海运订舱、集装箱、发票等业务实体表由 Schema Compiler 根据 `s_meta_entity`、`s_meta_field` 和 `s_meta_relation` 生成。 + +## 2. 元数据中心 + +### 2.1 模块 + +```sql +create table dbo.s_meta_module ( + b_id bigint identity(1, 1) not null, + b_module_key nvarchar(128) not null, + b_revision_no int not null, + b_name_key nvarchar(256) not null, + b_description nvarchar(1000) null, + b_origin nvarchar(32) not null + constraint df_s_meta_module_origin default N'platform', + b_enabled tinyint not null + constraint df_s_meta_module_enabled default 1, + b_protected tinyint not null + constraint df_s_meta_module_protected default 0, + b_dependency_text nvarchar(max) null, + b_config_text nvarchar(max) null, + b_created_at datetime2(3) not null + constraint df_s_meta_module_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_meta_module primary key (b_id), + constraint ux_s_meta_module_key_revision unique (b_module_key, b_revision_no) +); + +create index ix_s_meta_module_key + on dbo.s_meta_module (b_module_key, b_enabled); +``` + +### 2.2 实体 + +```sql +create table dbo.s_meta_entity ( + b_id bigint identity(1, 1) not null, + b_entity_key nvarchar(192) not null, + b_revision_no int not null, + b_module_key nvarchar(128) not null, + b_name_key nvarchar(256) not null, + b_description nvarchar(1000) null, + b_physical_table_name sysname not null, + b_primary_key_field nvarchar(128) not null + constraint df_s_meta_entity_primary_key_field default N'b_id', + b_delete_policy nvarchar(64) not null, + b_origin nvarchar(32) not null + constraint df_s_meta_entity_origin default N'platform', + b_enabled tinyint not null + constraint df_s_meta_entity_enabled default 1, + b_protected tinyint not null + constraint df_s_meta_entity_protected default 0, + b_required_by_code tinyint not null + constraint df_s_meta_entity_required_by_code default 0, + b_deprecated_since nvarchar(64) null, + b_removable_after_version nvarchar(64) null, + b_validator_key nvarchar(192) null, + b_config_text nvarchar(max) null, + b_created_at datetime2(3) not null + constraint df_s_meta_entity_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_meta_entity primary key (b_id), + constraint ux_s_meta_entity_key_revision unique (b_entity_key, b_revision_no) +); + +create index ix_s_meta_entity_module_key + on dbo.s_meta_entity (b_module_key, b_enabled); + +create index ix_s_meta_entity_physical_table_name + on dbo.s_meta_entity (b_physical_table_name); +``` + +### 2.3 字段 + +```sql +create table dbo.s_meta_field ( + b_id bigint identity(1, 1) not null, + b_entity_key nvarchar(192) not null, + b_field_key nvarchar(128) not null, + b_revision_no int not null, + b_name_key nvarchar(256) not null, + b_label nvarchar(256) not null, + b_description nvarchar(1000) null, + b_physical_column_name sysname not null, + b_data_type nvarchar(64) not null, + b_semantic_type nvarchar(128) null, + b_write_mode nvarchar(32) not null + constraint df_s_meta_field_write_mode default N'direct', + b_required tinyint not null + constraint df_s_meta_field_required default 0, + b_enabled tinyint not null + constraint df_s_meta_field_enabled default 1, + b_searchable tinyint not null + constraint df_s_meta_field_searchable default 0, + b_sortable tinyint not null + constraint df_s_meta_field_sortable default 0, + b_auditable tinyint not null + constraint df_s_meta_field_auditable default 0, + b_audit_visibility nvarchar(32) null, + b_sensitive tinyint not null + constraint df_s_meta_field_sensitive default 0, + b_max_length int null, + b_numeric_precision tinyint null, + b_numeric_scale tinyint null, + b_temporal_type nvarchar(32) null, + b_unit_key nvarchar(128) null, + b_default_value_text nvarchar(max) null, + b_aliases_text nvarchar(max) null, + b_validation_text nvarchar(max) null, + b_ai_hints_text nvarchar(max) null, + b_origin nvarchar(32) not null + constraint df_s_meta_field_origin default N'platform', + b_protected tinyint not null + constraint df_s_meta_field_protected default 0, + b_required_by_code tinyint not null + constraint df_s_meta_field_required_by_code default 0, + b_deprecated_since nvarchar(64) null, + b_removable_after_version nvarchar(64) null, + b_created_at datetime2(3) not null + constraint df_s_meta_field_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_meta_field primary key (b_id), + constraint ux_s_meta_field_key_revision unique ( + b_entity_key, + b_field_key, + b_revision_no + ) +); + +create index ix_s_meta_field_entity_key + on dbo.s_meta_field (b_entity_key, b_enabled, b_field_key); + +create index ix_s_meta_field_physical_column_name + on dbo.s_meta_field (b_entity_key, b_physical_column_name); +``` + +### 2.4 实体关系 + +```sql +create table dbo.s_meta_relation ( + b_id bigint identity(1, 1) not null, + b_relation_key nvarchar(192) not null, + b_revision_no int not null, + b_module_key nvarchar(128) not null, + b_source_entity_key nvarchar(192) not null, + b_source_field_key nvarchar(128) null, + b_target_entity_key nvarchar(192) not null, + b_target_field_key nvarchar(128) not null + constraint df_s_meta_relation_target_field_key default N'b_id', + b_relation_type nvarchar(32) not null, + b_ownership_type nvarchar(32) not null, + b_required tinyint not null + constraint df_s_meta_relation_required default 0, + b_delete_policy nvarchar(32) not null, + b_save_policy nvarchar(32) not null, + b_sort_field_key nvarchar(128) null, + b_unique_rule_text nvarchar(max) null, + b_enabled tinyint not null + constraint df_s_meta_relation_enabled default 1, + b_config_text nvarchar(max) null, + b_created_at datetime2(3) not null + constraint df_s_meta_relation_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_meta_relation primary key (b_id), + constraint ux_s_meta_relation_key_revision unique ( + b_relation_key, + b_revision_no + ) +); + +create index ix_s_meta_relation_source_entity_key + on dbo.s_meta_relation (b_source_entity_key, b_enabled); + +create index ix_s_meta_relation_target_entity_key + on dbo.s_meta_relation (b_target_entity_key, b_enabled); +``` + +### 2.5 字典与字典项 + +```sql +create table dbo.s_meta_dictionary ( + b_id bigint identity(1, 1) not null, + b_dictionary_key nvarchar(192) not null, + b_revision_no int not null, + b_module_key nvarchar(128) not null, + b_name_key nvarchar(256) not null, + b_description nvarchar(1000) null, + b_enabled tinyint not null + constraint df_s_meta_dictionary_enabled default 1, + b_config_text nvarchar(max) null, + b_created_at datetime2(3) not null + constraint df_s_meta_dictionary_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_meta_dictionary primary key (b_id), + constraint ux_s_meta_dictionary_key_revision unique ( + b_dictionary_key, + b_revision_no + ) +); + +create table dbo.s_meta_dictionary_item ( + b_id bigint identity(1, 1) not null, + b_dictionary_id bigint not null, + b_item_key nvarchar(128) not null, + b_item_value nvarchar(512) not null, + b_name_key nvarchar(256) not null, + b_sort_no int not null + constraint df_s_meta_dictionary_item_sort_no default 0, + b_enabled tinyint not null + constraint df_s_meta_dictionary_item_enabled default 1, + b_config_text nvarchar(max) null, + constraint pk_s_meta_dictionary_item primary key (b_id), + constraint ux_s_meta_dictionary_item_key unique ( + b_dictionary_id, + b_item_key + ) +); + +create index ix_s_meta_dictionary_item_dictionary_id + on dbo.s_meta_dictionary_item (b_dictionary_id, b_enabled, b_sort_no); +``` + +### 2.6 注册查询资源 + +```sql +create table dbo.s_meta_query_source ( + b_id bigint identity(1, 1) not null, + b_query_key nvarchar(192) not null, + b_revision_no int not null, + b_module_key nvarchar(128) not null, + b_name_key nvarchar(256) not null, + b_source_type nvarchar(32) not null, + b_entity_key nvarchar(192) null, + b_statement_text nvarchar(max) null, + b_parameter_schema_text nvarchar(max) null, + b_output_schema_text nvarchar(max) null, + b_permission_key nvarchar(192) null, + b_timeout_seconds int not null + constraint df_s_meta_query_source_timeout_seconds default 30, + b_max_rows int not null + constraint df_s_meta_query_source_max_rows default 1000, + b_enabled tinyint not null + constraint df_s_meta_query_source_enabled default 1, + b_config_text nvarchar(max) null, + b_created_at datetime2(3) not null + constraint df_s_meta_query_source_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_meta_query_source primary key (b_id), + constraint ux_s_meta_query_source_key_revision unique ( + b_query_key, + b_revision_no + ) +); + +create index ix_s_meta_query_source_module_key + on dbo.s_meta_query_source (b_module_key, b_enabled); +``` + +### 2.7 页面与菜单 + +页面是可访问的业务入口,负责把运行时视图、表单或特殊 Vue 组件绑定到稳定路由和页面权限。菜单是页面和模块的导航表现,可以配置目录、页面和外链,但不能替代页面访问权限判断。 + +```sql +create table dbo.s_meta_page ( + b_id bigint identity(1, 1) not null, + b_page_key nvarchar(192) not null, + b_revision_no int not null, + b_module_key nvarchar(128) not null, + b_name_key nvarchar(256) not null, + b_page_type nvarchar(32) not null, + b_route nvarchar(256) not null, + b_component_key nvarchar(192) null, + b_view_key nvarchar(192) null, + b_form_key nvarchar(192) null, + b_permission_key nvarchar(192) not null, + b_visible tinyint not null + constraint df_s_meta_page_visible default 1, + b_enabled tinyint not null + constraint df_s_meta_page_enabled default 1, + b_protected tinyint not null + constraint df_s_meta_page_protected default 0, + b_config_text nvarchar(max) null, + b_created_at datetime2(3) not null + constraint df_s_meta_page_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_meta_page primary key (b_id), + constraint ux_s_meta_page_key_revision unique (b_page_key, b_revision_no) +); + +create index ix_s_meta_page_module_key + on dbo.s_meta_page (b_module_key, b_enabled); + +create index ix_s_meta_page_route + on dbo.s_meta_page (b_route, b_enabled); + +create table dbo.s_meta_menu ( + b_id bigint identity(1, 1) not null, + b_menu_key nvarchar(192) not null, + b_revision_no int not null, + b_module_key nvarchar(128) not null, + b_parent_menu_key nvarchar(192) null, + b_name_key nvarchar(256) not null, + b_menu_type nvarchar(32) not null, + b_page_key nvarchar(192) null, + b_route nvarchar(256) null, + b_permission_key nvarchar(192) null, + b_icon_key nvarchar(128) null, + b_sort_no int not null + constraint df_s_meta_menu_sort_no default 0, + b_visible tinyint not null + constraint df_s_meta_menu_visible default 1, + b_enabled tinyint not null + constraint df_s_meta_menu_enabled default 1, + b_config_text nvarchar(max) null, + b_created_at datetime2(3) not null + constraint df_s_meta_menu_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_meta_menu primary key (b_id), + constraint ux_s_meta_menu_key_revision unique (b_menu_key, b_revision_no) +); + +create index ix_s_meta_menu_parent_key + on dbo.s_meta_menu (b_parent_menu_key, b_sort_no, b_enabled); + +create index ix_s_meta_menu_page_key + on dbo.s_meta_menu (b_page_key, b_enabled); +``` + +约束由元数据校验器执行:`directory` 菜单不能绑定页面或路由,`page` 菜单必须绑定 `b_page_key`,`external` 菜单必须填写 `b_route` 和 `b_permission_key`;内部页面菜单的路由必须从页面资源解析,不能单独维护第二份内部路由,权限默认继承所绑定页面的 `b_permission_key`。目录仅在至少存在一个可访问子页面时显示。 + +### 2.8 列表与详情视图 + +```sql +create table dbo.s_meta_view ( + b_id bigint identity(1, 1) not null, + b_view_key nvarchar(192) not null, + b_revision_no int not null, + b_module_key nvarchar(128) not null, + b_entity_key nvarchar(192) not null, + b_name_key nvarchar(256) not null, + b_view_type nvarchar(32) not null, + b_query_source_key nvarchar(192) null, + b_page_size int not null + constraint df_s_meta_view_page_size default 50, + b_enabled tinyint not null + constraint df_s_meta_view_enabled default 1, + b_config_text nvarchar(max) null, + b_created_at datetime2(3) not null + constraint df_s_meta_view_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_meta_view primary key (b_id), + constraint ux_s_meta_view_key_revision unique (b_view_key, b_revision_no) +); + +create table dbo.s_meta_view_field ( + b_id bigint identity(1, 1) not null, + b_view_id bigint not null, + b_field_key nvarchar(128) not null, + b_sort_no int not null + constraint df_s_meta_view_field_sort_no default 0, + b_visible tinyint not null + constraint df_s_meta_view_field_visible default 1, + b_width int null, + b_min_width int null, + b_fixed_position nvarchar(16) null, + b_sortable tinyint not null + constraint df_s_meta_view_field_sortable default 0, + b_filterable tinyint not null + constraint df_s_meta_view_field_filterable default 0, + b_formatter_key nvarchar(128) null, + b_align_type nvarchar(16) null, + b_config_text nvarchar(max) null, + constraint pk_s_meta_view_field primary key (b_id), + constraint ux_s_meta_view_field_key unique (b_view_id, b_field_key) +); + +create index ix_s_meta_view_entity_key + on dbo.s_meta_view (b_entity_key, b_view_type, b_enabled); + +create index ix_s_meta_view_field_view_id + on dbo.s_meta_view_field (b_view_id, b_sort_no); +``` + +### 2.9 动态表单与主子表区域 + +```sql +create table dbo.s_meta_form ( + b_id bigint identity(1, 1) not null, + b_form_key nvarchar(192) not null, + b_revision_no int not null, + b_module_key nvarchar(128) not null, + b_entity_key nvarchar(192) not null, + b_name_key nvarchar(256) not null, + b_form_type nvarchar(32) not null + constraint df_s_meta_form_form_type default N'edit', + b_column_count tinyint not null + constraint df_s_meta_form_column_count default 2, + b_enabled tinyint not null + constraint df_s_meta_form_enabled default 1, + b_layout_text nvarchar(max) null, + b_config_text nvarchar(max) null, + b_created_at datetime2(3) not null + constraint df_s_meta_form_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_meta_form primary key (b_id), + constraint ux_s_meta_form_key_revision unique (b_form_key, b_revision_no) +); + +create table dbo.s_meta_form_field ( + b_id bigint identity(1, 1) not null, + b_form_id bigint not null, + b_field_key nvarchar(128) not null, + b_group_key nvarchar(128) null, + b_row_no int not null, + b_column_no tinyint not null, + b_column_span tinyint not null + constraint df_s_meta_form_field_column_span default 1, + b_sort_no int not null + constraint df_s_meta_form_field_sort_no default 0, + b_component_type nvarchar(64) not null, + b_visible tinyint not null + constraint df_s_meta_form_field_visible default 1, + b_readonly tinyint not null + constraint df_s_meta_form_field_readonly default 0, + b_required_override tinyint null, + b_label_position nvarchar(16) null, + b_component_props_text nvarchar(max) null, + b_validation_text nvarchar(max) null, + b_config_text nvarchar(max) null, + constraint pk_s_meta_form_field primary key (b_id), + constraint ux_s_meta_form_field_key unique (b_form_id, b_field_key) +); + +create table dbo.s_meta_form_relation ( + b_id bigint identity(1, 1) not null, + b_form_id bigint not null, + b_relation_key nvarchar(192) not null, + b_group_key nvarchar(128) null, + b_child_view_key nvarchar(192) not null, + b_child_form_key nvarchar(192) null, + b_display_mode nvarchar(32) not null + constraint df_s_meta_form_relation_display_mode default N'table', + b_row_no int not null, + b_column_no tinyint not null + constraint df_s_meta_form_relation_column_no default 1, + b_column_span tinyint not null + constraint df_s_meta_form_relation_column_span default 2, + b_sort_no int not null + constraint df_s_meta_form_relation_sort_no default 0, + b_editable tinyint not null + constraint df_s_meta_form_relation_editable default 1, + b_allow_add tinyint not null + constraint df_s_meta_form_relation_allow_add default 1, + b_allow_delete tinyint not null + constraint df_s_meta_form_relation_allow_delete default 1, + b_allow_sort tinyint not null + constraint df_s_meta_form_relation_allow_sort default 0, + b_min_rows int null, + b_max_rows int null, + b_config_text nvarchar(max) null, + constraint pk_s_meta_form_relation primary key (b_id), + constraint ux_s_meta_form_relation_key unique (b_form_id, b_relation_key) +); + +create index ix_s_meta_form_entity_key + on dbo.s_meta_form (b_entity_key, b_form_type, b_enabled); + +create index ix_s_meta_form_field_form_id + on dbo.s_meta_form_field (b_form_id, b_row_no, b_column_no, b_sort_no); + +create index ix_s_meta_form_relation_form_id + on dbo.s_meta_form_relation (b_form_id, b_row_no, b_column_no, b_sort_no); +``` + +### 2.10 业务动作 + +```sql +create table dbo.s_meta_action ( + b_id bigint identity(1, 1) not null, + b_action_key nvarchar(192) not null, + b_revision_no int not null, + b_module_key nvarchar(128) not null, + b_entity_key nvarchar(192) null, + b_name_key nvarchar(256) not null, + b_action_type nvarchar(32) not null, + b_handler_key nvarchar(192) null, + b_permission_key nvarchar(192) not null, + b_input_schema_text nvarchar(max) null, + b_output_schema_text nvarchar(max) null, + b_precondition_text nvarchar(max) null, + b_audit_event_key nvarchar(192) null, + b_confirmation_mode nvarchar(32) null, + b_idempotency_required tinyint not null + constraint df_s_meta_action_idempotency_required default 1, + b_enabled tinyint not null + constraint df_s_meta_action_enabled default 1, + b_config_text nvarchar(max) null, + b_created_at datetime2(3) not null + constraint df_s_meta_action_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_meta_action primary key (b_id), + constraint ux_s_meta_action_key_revision unique (b_action_key, b_revision_no) +); + +create index ix_s_meta_action_entity_key + on dbo.s_meta_action (b_entity_key, b_enabled); + +create index ix_s_meta_action_permission_key + on dbo.s_meta_action (b_permission_key); +``` + +### 2.11 多语言 + +```sql +create table dbo.s_meta_i18n ( + b_id bigint identity(1, 1) not null, + b_resource_key nvarchar(256) not null, + b_locale nvarchar(32) not null, + b_revision_no int not null, + b_resource_value nvarchar(max) not null, + b_source_type nvarchar(32) not null + constraint df_s_meta_i18n_source_type default N'manual', + b_resource_status nvarchar(32) not null + constraint df_s_meta_i18n_resource_status default N'confirmed', + b_model_key nvarchar(128) null, + b_created_at datetime2(3) not null + constraint df_s_meta_i18n_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_meta_i18n primary key (b_id), + constraint ux_s_meta_i18n_key_locale_revision unique ( + b_resource_key, + b_locale, + b_revision_no + ) +); + +create index ix_s_meta_i18n_resource_key + on dbo.s_meta_i18n (b_resource_key, b_locale); +``` + +## 3. 配置版本与 Schema Compiler + +### 3.1 配置变更集 + +```sql +create table dbo.s_meta_change_set ( + b_id bigint identity(1, 1) not null, + b_change_set_no bigint not null, + b_base_release_id bigint null, + b_operation_type nvarchar(32) not null, + b_reason nvarchar(1000) null, + b_change_text nvarchar(max) not null, + b_ddl_plan_text nvarchar(max) null, + b_validation_text nvarchar(max) null, + b_impact_text nvarchar(max) null, + b_checksum varchar(128) not null, + b_execution_status nvarchar(32) not null, + b_created_at datetime2(3) not null + constraint df_s_meta_change_set_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + b_completed_at datetime2(3) null, + b_error_code nvarchar(128) null, + b_error_message nvarchar(2000) null, + constraint pk_s_meta_change_set primary key (b_id), + constraint ux_s_meta_change_set_no unique (b_change_set_no) +); + +create index ix_s_meta_change_set_base_release_id + on dbo.s_meta_change_set (b_base_release_id); + +create index ix_s_meta_change_set_execution_status + on dbo.s_meta_change_set (b_execution_status, b_created_at); +``` + +### 3.2 不可变配置版本 + +```sql +create table dbo.s_meta_release ( + b_id bigint identity(1, 1) not null, + b_release_no bigint not null, + b_change_set_id bigint not null, + b_checksum varchar(128) not null, + b_validation_status nvarchar(32) not null, + b_description nvarchar(1000) null, + b_created_at datetime2(3) not null + constraint df_s_meta_release_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_meta_release primary key (b_id), + constraint ux_s_meta_release_no unique (b_release_no), + constraint ux_s_meta_release_checksum unique (b_checksum) +); + +create table dbo.s_meta_release_resource ( + b_id bigint identity(1, 1) not null, + b_release_id bigint not null, + b_resource_type nvarchar(64) not null, + b_resource_key nvarchar(256) not null, + b_resource_id bigint not null, + b_resource_revision_no int not null, + b_checksum varchar(128) not null, + constraint pk_s_meta_release_resource primary key (b_id), + constraint ux_s_meta_release_resource_key unique ( + b_release_id, + b_resource_type, + b_resource_key + ) +); + +create index ix_s_meta_release_resource_release_id + on dbo.s_meta_release_resource (b_release_id, b_resource_type); + +create index ix_s_meta_release_resource_resource_id + on dbo.s_meta_release_resource (b_resource_id); +``` + +### 3.3 当前运行版本与激活历史 + +```sql +create table dbo.s_meta_runtime_state ( + b_id bigint identity(1, 1) not null, + b_state_key nvarchar(64) not null + constraint df_s_meta_runtime_state_state_key default N'default', + b_current_release_id bigint not null, + b_updated_at datetime2(3) not null + constraint df_s_meta_runtime_state_updated_at default sysutcdatetime(), + b_updated_by nvarchar(128) not null, + b_row_version rowversion not null, + constraint pk_s_meta_runtime_state primary key (b_id), + constraint ux_s_meta_runtime_state_key unique (b_state_key) +); + +create table dbo.s_meta_release_activation ( + b_id bigint identity(1, 1) not null, + b_release_id bigint not null, + b_previous_release_id bigint null, + b_activation_type nvarchar(32) not null, + b_reason nvarchar(1000) null, + b_request_id nvarchar(128) null, + b_trace_id nvarchar(128) null, + b_activated_at datetime2(3) not null + constraint df_s_meta_release_activation_activated_at default sysutcdatetime(), + b_activated_by nvarchar(128) not null, + constraint pk_s_meta_release_activation primary key (b_id) +); + +create index ix_s_meta_release_activation_release_id + on dbo.s_meta_release_activation (b_release_id, b_activated_at desc); +``` + +### 3.4 Schema 迁移任务与步骤 + +```sql +create table dbo.s_schema_migration ( + b_id bigint identity(1, 1) not null, + b_change_set_id bigint not null, + b_target_release_id bigint null, + b_migration_status nvarchar(32) not null, + b_lock_owner nvarchar(128) null, + b_lock_expires_at datetime2(3) null, + b_plan_checksum varchar(128) not null, + b_current_step_no int null, + b_started_at datetime2(3) null, + b_completed_at datetime2(3) null, + b_created_at datetime2(3) not null + constraint df_s_schema_migration_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + b_error_code nvarchar(128) null, + b_error_message nvarchar(2000) null, + constraint pk_s_schema_migration primary key (b_id), + constraint ux_s_schema_migration_change_set_id unique (b_change_set_id) +); + +create table dbo.s_schema_migration_step ( + b_id bigint identity(1, 1) not null, + b_migration_id bigint not null, + b_step_no int not null, + b_step_type nvarchar(32) not null, + b_object_key nvarchar(256) null, + b_statement_text nvarchar(max) null, + b_rollback_statement_text nvarchar(max) null, + b_checksum varchar(128) not null, + b_step_status nvarchar(32) not null, + b_started_at datetime2(3) null, + b_completed_at datetime2(3) null, + b_result_text nvarchar(max) null, + b_error_message nvarchar(2000) null, + constraint pk_s_schema_migration_step primary key (b_id), + constraint ux_s_schema_migration_step_no unique (b_migration_id, b_step_no) +); + +create index ix_s_schema_migration_status + on dbo.s_schema_migration (b_migration_status, b_created_at); + +create index ix_s_schema_migration_step_migration_id + on dbo.s_schema_migration_step (b_migration_id, b_step_status, b_step_no); +``` + +## 4. 统一权限中心 + +### 4.1 角色与用户角色关系 + +```sql +create table dbo.s_auth_role ( + b_id bigint identity(1, 1) not null, + b_role_key nvarchar(128) not null, + b_role_name nvarchar(256) not null, + b_description nvarchar(1000) null, + b_role_type nvarchar(32) not null + constraint df_s_auth_role_role_type default N'custom', + b_enabled tinyint not null + constraint df_s_auth_role_enabled default 1, + b_created_at datetime2(3) not null + constraint df_s_auth_role_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + b_updated_at datetime2(3) null, + b_updated_by nvarchar(128) null, + b_row_version rowversion not null, + constraint pk_s_auth_role primary key (b_id), + constraint ux_s_auth_role_key unique (b_role_key) +); + +create table dbo.s_auth_user_role ( + b_id bigint identity(1, 1) not null, + b_user_key nvarchar(128) not null, + b_role_key nvarchar(128) not null, + b_enabled tinyint not null + constraint df_s_auth_user_role_enabled default 1, + b_effective_from datetime2(3) null, + b_effective_to datetime2(3) null, + b_created_at datetime2(3) not null + constraint df_s_auth_user_role_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_auth_user_role primary key (b_id), + constraint ux_s_auth_user_role_key unique (b_user_key, b_role_key) +); + +create index ix_s_auth_user_role_role_key + on dbo.s_auth_user_role (b_role_key, b_enabled); +``` + +### 4.2 功能权限 + +```sql +create table dbo.s_auth_permission ( + b_id bigint identity(1, 1) not null, + b_permission_key nvarchar(192) not null, + b_module_key nvarchar(128) null, + b_resource_type nvarchar(32) not null, + b_resource_key nvarchar(256) not null, + b_action_type nvarchar(64) null, + b_name_key nvarchar(256) not null, + b_description nvarchar(1000) null, + b_enabled tinyint not null + constraint df_s_auth_permission_enabled default 1, + b_protected tinyint not null + constraint df_s_auth_permission_protected default 0, + b_created_at datetime2(3) not null + constraint df_s_auth_permission_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_auth_permission primary key (b_id), + constraint ux_s_auth_permission_key unique (b_permission_key) +); + +create table dbo.s_auth_role_permission ( + b_id bigint identity(1, 1) not null, + b_role_key nvarchar(128) not null, + b_permission_key nvarchar(192) not null, + b_permission_effect nvarchar(16) not null + constraint df_s_auth_role_permission_effect default N'allow', + b_created_at datetime2(3) not null + constraint df_s_auth_role_permission_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_auth_role_permission primary key (b_id), + constraint ux_s_auth_role_permission_key unique ( + b_role_key, + b_permission_key + ) +); + +create index ix_s_auth_permission_resource_key + on dbo.s_auth_permission (b_resource_type, b_resource_key, b_enabled); + +create index ix_s_auth_role_permission_permission_key + on dbo.s_auth_role_permission (b_permission_key, b_permission_effect); +``` + +### 4.3 数据范围权限 + +```sql +create table dbo.s_auth_data_policy ( + b_id bigint identity(1, 1) not null, + b_policy_key nvarchar(192) not null, + b_module_key nvarchar(128) null, + b_entity_key nvarchar(192) not null, + b_policy_name nvarchar(256) not null, + b_action_text nvarchar(max) not null, + b_scope_text nvarchar(max) not null, + b_enabled tinyint not null + constraint df_s_auth_data_policy_enabled default 1, + b_priority_no int not null + constraint df_s_auth_data_policy_priority_no default 0, + b_created_at datetime2(3) not null + constraint df_s_auth_data_policy_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + b_updated_at datetime2(3) null, + b_updated_by nvarchar(128) null, + b_row_version rowversion not null, + constraint pk_s_auth_data_policy primary key (b_id), + constraint ux_s_auth_data_policy_key unique (b_policy_key) +); + +create table dbo.s_auth_role_data_policy ( + b_id bigint identity(1, 1) not null, + b_role_key nvarchar(128) not null, + b_policy_key nvarchar(192) not null, + b_policy_effect nvarchar(16) not null + constraint df_s_auth_role_data_policy_effect default N'allow', + b_created_at datetime2(3) not null + constraint df_s_auth_role_data_policy_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + constraint pk_s_auth_role_data_policy primary key (b_id), + constraint ux_s_auth_role_data_policy_key unique ( + b_role_key, + b_policy_key + ) +); + +create index ix_s_auth_data_policy_entity_key + on dbo.s_auth_data_policy (b_entity_key, b_enabled, b_priority_no); + +create index ix_s_auth_role_data_policy_policy_key + on dbo.s_auth_role_data_policy (b_policy_key, b_policy_effect); +``` + +### 4.4 字段查看、导出和脱敏权限 + +```sql +create table dbo.s_auth_field_policy ( + b_id bigint identity(1, 1) not null, + b_policy_key nvarchar(256) not null, + b_entity_key nvarchar(192) not null, + b_field_key nvarchar(128) not null, + b_subject_type nvarchar(32) not null, + b_subject_key nvarchar(128) not null, + b_view_effect nvarchar(16) not null + constraint df_s_auth_field_policy_view_effect default N'allow', + b_export_effect nvarchar(16) not null + constraint df_s_auth_field_policy_export_effect default N'allow', + b_mask_mode nvarchar(32) not null + constraint df_s_auth_field_policy_mask_mode default N'none', + b_mask_config_text nvarchar(max) null, + b_priority_no int not null + constraint df_s_auth_field_policy_priority_no default 0, + b_enabled tinyint not null + constraint df_s_auth_field_policy_enabled default 1, + b_created_at datetime2(3) not null + constraint df_s_auth_field_policy_created_at default sysutcdatetime(), + b_created_by nvarchar(128) not null, + b_updated_at datetime2(3) null, + b_updated_by nvarchar(128) null, + b_row_version rowversion not null, + constraint pk_s_auth_field_policy primary key (b_id), + constraint ux_s_auth_field_policy_key unique (b_policy_key), + constraint ux_s_auth_field_policy_subject unique ( + b_entity_key, + b_field_key, + b_subject_type, + b_subject_key + ) +); + +create index ix_s_auth_field_policy_lookup + on dbo.s_auth_field_policy ( + b_subject_type, + b_subject_key, + b_entity_key, + b_enabled, + b_priority_no + ); + +create index ix_s_auth_field_policy_field_key + on dbo.s_auth_field_policy (b_entity_key, b_field_key, b_enabled); +``` + +### 4.5 权限快照 + +```sql +create table dbo.s_auth_permission_snapshot ( + b_id bigint identity(1, 1) not null, + b_user_key nvarchar(128) not null, + b_snapshot_version bigint not null, + b_release_id bigint not null, + b_permission_text nvarchar(max) not null, + b_checksum varchar(128) not null, + b_created_at datetime2(3) not null + constraint df_s_auth_permission_snapshot_created_at default sysutcdatetime(), + b_expires_at datetime2(3) null, + b_invalidated_at datetime2(3) null, + constraint pk_s_auth_permission_snapshot primary key (b_id), + constraint ux_s_auth_permission_snapshot_version unique ( + b_user_key, + b_snapshot_version + ) +); + +create index ix_s_auth_permission_snapshot_user_key + on dbo.s_auth_permission_snapshot (b_user_key, b_invalidated_at, b_expires_at); +``` + +## 5. 日志与审计 + +### 5.1 技术日志 + +技术日志主要用于请求链路、SQL、异常、性能和外部接口排查,推荐写入 Loki、OpenSearch、Elasticsearch 等日志平台。下面的表适合集中日志库或短期数据库兜底,不建议把所有高频技术日志长期写入租户业务库。 + +```sql +create table dbo.s_log_technical ( + b_id bigint identity(1, 1) not null, + b_occurred_at datetime2(3) not null + constraint df_s_log_technical_occurred_at default sysutcdatetime(), + b_log_level nvarchar(16) not null, + b_log_category nvarchar(64) not null, + b_operation nvarchar(192) not null, + b_org_id nvarchar(128) null, + b_user_key nvarchar(128) null, + b_source_type nvarchar(32) null, + b_module_key nvarchar(128) null, + b_entity_key nvarchar(192) null, + b_action_key nvarchar(192) null, + b_request_id nvarchar(128) null, + b_trace_id nvarchar(128) null, + b_application_version nvarchar(128) null, + b_server_node nvarchar(128) null, + b_duration_ms bigint null, + b_result_code nvarchar(128) null, + b_error_code nvarchar(128) null, + b_error_message nvarchar(2000) null, + b_exception_text nvarchar(max) null, + b_context_text nvarchar(max) null, + constraint pk_s_log_technical primary key (b_id) +); + +create index ix_s_log_technical_trace_id + on dbo.s_log_technical (b_trace_id, b_occurred_at desc); + +create index ix_s_log_technical_org_time + on dbo.s_log_technical (b_org_id, b_occurred_at desc); + +create index ix_s_log_technical_level_time + on dbo.s_log_technical (b_log_level, b_occurred_at desc); +``` + +### 5.2 登录日志 + +登录日志记录登录成功、失败、退出、刷新令牌和账号锁定等安全事件。它可以与技术日志使用相同的 `b_request_id`、`b_trace_id` 关联,但业务职责独立。 + +```sql +create table dbo.s_log_login ( + b_id bigint identity(1, 1) not null, + b_org_id nvarchar(128) null, + b_user_key nvarchar(128) null, + b_login_name nvarchar(256) null, + b_login_type nvarchar(32) not null, + b_login_status nvarchar(32) not null, + b_auth_method nvarchar(32) null, + b_failure_reason nvarchar(512) null, + b_ip_address varchar(64) null, + b_user_agent nvarchar(1000) null, + b_device_key nvarchar(256) null, + b_request_id nvarchar(128) null, + b_trace_id nvarchar(128) null, + b_occurred_at datetime2(3) not null + constraint df_s_log_login_occurred_at default sysutcdatetime(), + b_created_at datetime2(3) not null + constraint df_s_log_login_created_at default sysutcdatetime(), + constraint pk_s_log_login primary key (b_id) +); + +create index ix_s_log_login_user_time + on dbo.s_log_login (b_user_key, b_occurred_at desc); + +create index ix_s_log_login_name_time + on dbo.s_log_login (b_login_name, b_occurred_at desc); + +create index ix_s_log_login_status_time + on dbo.s_log_login (b_login_status, b_occurred_at desc); + +create index ix_s_log_login_trace_id + on dbo.s_log_login (b_trace_id, b_occurred_at desc); +``` + +### 5.3 业务审计事件与字段变更 + +```sql +create table dbo.s_log_audit ( + b_id bigint identity(1, 1) not null, + b_event_key nvarchar(192) not null, + b_module_key nvarchar(128) null, + b_entity_key nvarchar(192) not null, + b_entity_id nvarchar(128) not null, + b_business_no nvarchar(192) null, + b_action_key nvarchar(192) null, + b_operator_key nvarchar(128) not null, + b_operator_name nvarchar(256) null, + b_source_type nvarchar(32) not null, + b_visibility nvarchar(32) not null, + b_request_id nvarchar(128) null, + b_trace_id nvarchar(128) not null, + b_release_id bigint null, + b_business_event_id bigint null, + b_payload_text nvarchar(max) null, + b_occurred_at datetime2(3) not null + constraint df_s_log_audit_occurred_at default sysutcdatetime(), + constraint pk_s_log_audit primary key (b_id) +); + +create table dbo.s_log_audit_field ( + b_id bigint identity(1, 1) not null, + b_event_id bigint not null, + b_entity_key nvarchar(192) not null, + b_entity_id nvarchar(128) not null, + b_field_key nvarchar(128) not null, + b_field_label_snapshot nvarchar(256) null, + b_before_value_text nvarchar(max) null, + b_after_value_text nvarchar(max) null, + b_before_display_value nvarchar(1000) null, + b_after_display_value nvarchar(1000) null, + b_visibility nvarchar(32) not null, + constraint pk_s_log_audit_field primary key (b_id) +); + +create index ix_s_log_audit_entity + on dbo.s_log_audit (b_entity_key, b_entity_id, b_occurred_at desc); + +create index ix_s_log_audit_trace_id + on dbo.s_log_audit (b_trace_id); + +create index ix_s_log_audit_field_audit_event_id + on dbo.s_log_audit_field (b_event_id); +``` + +## 6. 逻辑外键清单 + +以下关联不创建物理外键,由服务层、发布校验器和 Schema Compiler 负责检查: + +| 表 | 字段 | 逻辑目标 | +| --- | --- | --- | +| `s_meta_entity` | `b_module_key` | `s_meta_module.b_module_key` | +| `s_meta_field` | `b_entity_key` | `s_meta_entity.b_entity_key` | +| `s_meta_relation` | `b_source_entity_key` | `s_meta_entity.b_entity_key` | +| `s_meta_relation` | `b_target_entity_key` | `s_meta_entity.b_entity_key` | +| `s_meta_dictionary_item` | `b_dictionary_id` | `s_meta_dictionary.b_id` | +| `s_meta_page` | `b_module_key` | `s_meta_module.b_module_key` | +| `s_meta_page` | `b_view_key` | `s_meta_view.b_view_key` | +| `s_meta_page` | `b_form_key` | `s_meta_form.b_form_key` | +| `s_meta_page` | `b_permission_key` | `s_auth_permission.b_permission_key` | +| `s_meta_menu` | `b_module_key` | `s_meta_module.b_module_key` | +| `s_meta_menu` | `b_parent_menu_key` | `s_meta_menu.b_menu_key` | +| `s_meta_menu` | `b_page_key` | `s_meta_page.b_page_key` | +| `s_meta_menu` | `b_permission_key` | `s_auth_permission.b_permission_key` | +| `s_meta_view` | `b_entity_key` | `s_meta_entity.b_entity_key` | +| `s_meta_view_field` | `b_view_id` | `s_meta_view.b_id` | +| `s_meta_form` | `b_entity_key` | `s_meta_entity.b_entity_key` | +| `s_meta_form_field` | `b_form_id` | `s_meta_form.b_id` | +| `s_meta_form_relation` | `b_form_id` | `s_meta_form.b_id` | +| `s_meta_form_relation` | `b_relation_key` | `s_meta_relation.b_relation_key` | +| `s_meta_form_relation` | `b_child_view_key` | `s_meta_view.b_view_key` | +| `s_meta_form_relation` | `b_child_form_key` | `s_meta_form.b_form_key` | +| `s_meta_release` | `b_change_set_id` | `s_meta_change_set.b_id` | +| `s_meta_release_resource` | `b_release_id` | `s_meta_release.b_id` | +| `s_meta_runtime_state` | `b_current_release_id` | `s_meta_release.b_id` | +| `s_schema_migration` | `b_change_set_id` | `s_meta_change_set.b_id` | +| `s_schema_migration_step` | `b_migration_id` | `s_schema_migration.b_id` | +| `s_auth_user_role` | `b_role_key` | `s_auth_role.b_role_key` | +| `s_auth_role_permission` | `b_permission_key` | `s_auth_permission.b_permission_key` | +| `s_auth_role_data_policy` | `b_policy_key` | `s_auth_data_policy.b_policy_key` | +| `s_auth_field_policy` | `b_entity_key` | `s_meta_entity.b_entity_key` | +| `s_auth_field_policy` | `b_field_key` | `s_meta_field.b_field_key` | +| `s_log_audit_field` | `b_event_id` | `s_log_audit.b_id` | +| `s_log_technical` | `b_user_key` | 当前身份系统的用户 `b_user_key` | +| `s_log_login` | `b_user_key` | 当前身份系统的用户 `b_user_key` | + +逻辑外键写入时应执行以下校验: + +```text +稳定 key 是否存在 +-> 目标资源是否属于当前 release +-> 目标资源是否启用 +-> 模块依赖是否合法 +-> 字段、关系和页面类型是否匹配 +-> 删除或停用前是否仍被其他资源引用 +``` + +## 7. 实施建议 + +建议按以下顺序创建和启用这些表: + +```text +元数据基础表 +-> 页面和菜单表 +-> 视图和动态表单表 +-> 配置 release 表 +-> Schema 迁移表 +-> 权限表 +-> 日志与审计表 +``` + +正式执行前还应补充: + +- 根据实际 SQL Server 2022 环境确认过滤索引和 `datetime2` 支持情况。 +- 根据现有用户表确定 `b_user_key`、`b_created_by` 和 `b_updated_by` 的实际数据类型。 +- 根据最终稳定 key 长度评估唯一索引的键长度。 +- 为高数据量审计表制定归档及清理策略。 +- 用独立测试数据库执行完整建表脚本,并验证重复安装、初始化 release 和回滚流程。 + diff --git a/code/fms/fms-mvp-demo/.gitignore b/code/fms/fms-mvp-demo/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/code/fms/fms-mvp-demo/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/code/fms/fms-mvp-demo/.oxlintrc.json b/code/fms/fms-mvp-demo/.oxlintrc.json new file mode 100644 index 00000000..6fa991da --- /dev/null +++ b/code/fms/fms-mvp-demo/.oxlintrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "oxc"], + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} diff --git a/code/fms/fms-mvp-demo/README.md b/code/fms/fms-mvp-demo/README.md new file mode 100644 index 00000000..d6af7e39 --- /dev/null +++ b/code/fms/fms-mvp-demo/README.md @@ -0,0 +1,32 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the Oxlint configuration + +If you are developing a production application, we recommend enabling type-aware lint rules by installing `oxlint-tsgolint` and editing `.oxlintrc.json`: + +```json +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "oxc"], + "options": { + "typeAware": true + }, + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} +``` + +See the [Oxlint rules documentation](https://oxc.rs/docs/guide/usage/linter/rules) for the full list of rules and categories. diff --git a/code/fms/fms-mvp-demo/index.html b/code/fms/fms-mvp-demo/index.html new file mode 100644 index 00000000..b57799b1 --- /dev/null +++ b/code/fms/fms-mvp-demo/index.html @@ -0,0 +1,13 @@ + + + + + + + fms-mvp-demo + + +
+ + + diff --git a/code/fms/fms-mvp-demo/package.json b/code/fms/fms-mvp-demo/package.json new file mode 100644 index 00000000..dc5f8d6a --- /dev/null +++ b/code/fms/fms-mvp-demo/package.json @@ -0,0 +1,32 @@ +{ + "name": "fms-mvp-demo", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "oxlint", + "preview": "vite preview" + }, + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "lucide-react": "^1.31.0", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "zustand": "^5.0.15" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.3.3", + "@types/node": "^24.13.3", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.4", + "oxlint": "^1.75.0", + "tailwindcss": "^4.3.3", + "typescript": "~6.0.2", + "vite": "^8.2.0" + } +} diff --git a/code/fms/fms-mvp-demo/pnpm-lock.yaml b/code/fms/fms-mvp-demo/pnpm-lock.yaml new file mode 100644 index 00000000..cf21f5cb --- /dev/null +++ b/code/fms/fms-mvp-demo/pnpm-lock.yaml @@ -0,0 +1,1213 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@dnd-kit/core': + specifier: ^6.3.1 + version: 6.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@dnd-kit/sortable': + specifier: ^10.0.0 + version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8) + '@dnd-kit/utilities': + specifier: ^3.2.2 + version: 3.2.2(react@19.2.8) + lucide-react: + specifier: ^1.31.0 + version: 1.31.0(react@19.2.8) + react: + specifier: ^19.2.8 + version: 19.2.8 + react-dom: + specifier: ^19.2.8 + version: 19.2.8(react@19.2.8) + zustand: + specifier: ^5.0.15 + version: 5.0.15(@types/react@19.2.18)(react@19.2.8) + devDependencies: + '@tailwindcss/vite': + specifier: ^4.3.3 + version: 4.3.3(vite@8.2.1(@types/node@24.13.3)(jiti@2.7.0)) + '@types/node': + specifier: ^24.13.3 + version: 24.13.3 + '@types/react': + specifier: ^19.2.17 + version: 19.2.18 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.4(@types/react@19.2.18) + '@vitejs/plugin-react': + specifier: ^6.0.4 + version: 6.0.5(vite@8.2.1(@types/node@24.13.3)(jiti@2.7.0)) + oxlint: + specifier: ^1.75.0 + version: 1.78.0 + tailwindcss: + specifier: ^4.3.3 + version: 4.3.3 + typescript: + specifier: ~6.0.2 + version: 6.0.3 + vite: + specifier: ^8.2.0 + version: 8.2.1(@types/node@24.13.3)(jiti@2.7.0) + +packages: + + '@dnd-kit/accessibility@3.1.1': + resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} + peerDependencies: + react: '>=16.8.0' + + '@dnd-kit/core@6.3.1': + resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@dnd-kit/sortable@10.0.0': + resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==} + peerDependencies: + '@dnd-kit/core': ^6.3.0 + react: '>=16.8.0' + + '@dnd-kit/utilities@3.2.2': + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@oxc-project/types@0.144.0': + resolution: {integrity: sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==} + + '@oxlint/binding-android-arm-eabi@1.78.0': + resolution: {integrity: sha512-Bu819lmAfZMUHErrpe0cEWj3iaefuUODHSU8+UbXy67V/r7/7f4K3FL0NmbD85E+wiFLDYuhP8Zlv0XnVeXshw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.78.0': + resolution: {integrity: sha512-CDfxZgB61B7buRdY2FJoAYYPPXCZ1EoC1LKscnC5dg3kjobdxiconvAvvN1BmHyW4PyFT3jRLDag/BY/roSNBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.78.0': + resolution: {integrity: sha512-2Y2U9Ahrz+OO0Ej88f9SJYq51/jUBp1Mc7iZu0ukrbeeZ3gpRGfzIFnoqfHDY96xr0GEfNrPUBFEy0nN5aD7HA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.78.0': + resolution: {integrity: sha512-rpych6eJq6m9jDRypTEaPD1xysaEW5h9+xuxhGK/QhOg+/xaqPZrCrTNoIl/f3nEjuJeCEmstNDlrE9rJi/3/g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.78.0': + resolution: {integrity: sha512-IcMGrQT3QizkOESUJd5et+rOhVqSkNDfNik1cvrKDqIbzqx9KMtRswpFgkCuNTSwylCFLKhGUu8KmqY1ZnC0Dg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.78.0': + resolution: {integrity: sha512-/uLdoJ0IXE6vo/0f0LKjinQAp+re+VMaCWaNT8ENIv2EOCkSsc8SGaflXAuW0Jua2dq5+GLVWm1NQK7P3UFSNQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.78.0': + resolution: {integrity: sha512-7xi4Wb/O8NRJhLoUXmDJMUVpNYvB5kefdhFU1Jb8rtae4QoXlTiLwI14X4YvAXVZLNZChP8m5qO9SQAlWQTbkQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.78.0': + resolution: {integrity: sha512-4hFW0+fVXa3OIh1Y4A5SPkmvI4wuuBSrCVKzOyE7PTjhc7yEqZ1pmvEEeS5Lj/MaqvegFxXyF33N+6jkehxdyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.78.0': + resolution: {integrity: sha512-oC0mvsgBJjlMijSDEhx9KuvR9zYeHXceA9MjbuXB1F8NSR78Yj2unOBrstEvTVaq+pko+kuue6DajC00eqvTdg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.78.0': + resolution: {integrity: sha512-XAllT5SUZS+ohjuZ3/5S0cwe0r7eboiuigeStCZ5DXRYx/2KVM2UvQXvAfyzXEimtQjAB7cDQ2YxDe2Zl2WNQQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.78.0': + resolution: {integrity: sha512-trucMER/0QtecoXvc1y/UVqE3kwJipDwrx4oHfj+nNm3dq2zjP44WT0CfHNDPM3G1DXIkx/gY6lAD21NSCZVhA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.78.0': + resolution: {integrity: sha512-cm3O4F/HQbdzOUX5mKHqG5KDL6E5w0pnlZ+fbBy2rmLryPOowkuLagFHTopQsEIpjcaZoPOrL+BmmAytAG9HFg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.78.0': + resolution: {integrity: sha512-33wRf6HqGNsybJ3qX4cGaQN2ODPxNmc1rMa0mrTmx3eFq1VzOnvQooi9bIGVYakW8a/wmqVx1mgsUm8R2xfTiw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.78.0': + resolution: {integrity: sha512-rRdISSYegj6VganMZ9tjRjijowfHJ09IZU01i0toBAqr6n5LEtwHq2IeS4FjW2RoskOHlb6efB26H5izYb3GEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.78.0': + resolution: {integrity: sha512-GmsP4rW0xTL6u5CVdcDsaN5Fbc7hBc382Wmar1kttbnwSEviM+rSINKOMQ+UQ6iH+AGwC+8gaAiwu134Tgh6Lg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.78.0': + resolution: {integrity: sha512-sy9yeYuADc8a+n4TLBayzMCZiHPW78DcIFVpOXTmdKHWQeM9xe5uzkqIIZmi326D5hY9XVwacipEB1p7tQjPAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.78.0': + resolution: {integrity: sha512-rjc2hF1KfMi8fZj1X/m3AmnHbdsF3rL0v6KQg0Uc880Yb2khjz+3U14sfdZ7jWTpRnN1m1NQa/TT7uU9lJWPrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.78.0': + resolution: {integrity: sha512-zcuXFVrEFHIafRfkCQT8w/Xe41o07ozl/vwHq7p94vB29xVzsB0sZGYORU1jhcYKv3Lr0J3HbJ2T4fHH5rWmvA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.78.0': + resolution: {integrity: sha512-Sb5ocmLSuYeOuXd+CFOToGKp/gjXUEWDnvIGwhnh8aq8wY4TMmEnKnvbogSW7RdMZv77JSARduS7/gv+khYEjA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/binding-android-arm64@1.2.4': + resolution: {integrity: sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.4': + resolution: {integrity: sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.4': + resolution: {integrity: sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.4': + resolution: {integrity: sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.4': + resolution: {integrity: sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.4': + resolution: {integrity: sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.4': + resolution: {integrity: sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.4': + resolution: {integrity: sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.4': + resolution: {integrity: sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.4': + resolution: {integrity: sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.4': + resolution: {integrity: sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.4': + resolution: {integrity: sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.4': + resolution: {integrity: sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.4': + resolution: {integrity: sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@tailwindcss/node@4.3.3': + resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} + + '@tailwindcss/oxide-android-arm64@4.3.3': + resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.3.3': + resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.3.3': + resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.3.3': + resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.3.3': + resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.3.3': + resolution: {integrity: sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@types/node@24.13.3': + resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} + + '@types/react-dom@19.2.4': + resolution: {integrity: sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.18': + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} + + '@vitejs/plugin-react@6.0.5': + resolution: {integrity: sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + enhanced-resolve@5.24.5: + resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} + engines: {node: '>=10.13.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + lucide-react@1.31.0: + resolution: {integrity: sha512-G8u2eEtoHUnUa9f8lbvqDhCiORMnYLdUEo06EEG9MQvHQrInKcX3Pa2TH39MM5qyzRcWETxB0+aOwAPI1g1kEg==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + oxlint@1.78.0: + resolution: {integrity: sha512-QgQePuxIqKOzo1KSjG2EnITEeWvWnKAm77eq8nrMtf6AGoA+zyGc4PFYtDNJSD25g/ibOwfQ851hZ4/SPkMVoA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=7.0.2001' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + engines: {node: ^10 || ^12 || >=14} + + react-dom@19.2.8: + resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==} + peerDependencies: + react: ^19.2.8 + + react@19.2.8: + resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==} + engines: {node: '>=0.10.0'} + + rolldown@1.2.4: + resolution: {integrity: sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + tailwindcss@4.3.3: + resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + vite@8.2.1: + resolution: {integrity: sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.4.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + zustand@5.0.15: + resolution: {integrity: sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + +snapshots: + + '@dnd-kit/accessibility@3.1.1(react@19.2.8)': + dependencies: + react: 19.2.8 + tslib: 2.8.1 + + '@dnd-kit/core@6.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + dependencies: + '@dnd-kit/accessibility': 3.1.1(react@19.2.8) + '@dnd-kit/utilities': 3.2.2(react@19.2.8) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + tslib: 2.8.1 + + '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@dnd-kit/utilities': 3.2.2(react@19.2.8) + react: 19.2.8 + tslib: 2.8.1 + + '@dnd-kit/utilities@3.2.2(react@19.2.8)': + dependencies: + react: 19.2.8 + tslib: 2.8.1 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@oxc-project/types@0.144.0': {} + + '@oxlint/binding-android-arm-eabi@1.78.0': + optional: true + + '@oxlint/binding-android-arm64@1.78.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.78.0': + optional: true + + '@oxlint/binding-darwin-x64@1.78.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.78.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.78.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.78.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.78.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.78.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.78.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.78.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.78.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.78.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.78.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.78.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.78.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.78.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.78.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.78.0': + optional: true + + '@rolldown/binding-android-arm64@1.2.4': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.4': + optional: true + + '@rolldown/binding-darwin-x64@1.2.4': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.4': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.4': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.4': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.4': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.4': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.4': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.4': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.4': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.4': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.4': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.4': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@tailwindcss/node@4.3.3': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.24.5 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.3 + + '@tailwindcss/oxide-android-arm64@4.3.3': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.3.3': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.3.3': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + optional: true + + '@tailwindcss/oxide@4.3.3': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-x64': 4.3.3 + '@tailwindcss/oxide-freebsd-x64': 4.3.3 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-x64-musl': 4.3.3 + '@tailwindcss/oxide-wasm32-wasi': 4.3.3 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 + + '@tailwindcss/vite@4.3.3(vite@8.2.1(@types/node@24.13.3)(jiti@2.7.0))': + dependencies: + '@tailwindcss/node': 4.3.3 + '@tailwindcss/oxide': 4.3.3 + tailwindcss: 4.3.3 + vite: 8.2.1(@types/node@24.13.3)(jiti@2.7.0) + + '@types/node@24.13.3': + dependencies: + undici-types: 7.18.2 + + '@types/react-dom@19.2.4(@types/react@19.2.18)': + dependencies: + '@types/react': 19.2.18 + + '@types/react@19.2.18': + dependencies: + csstype: 3.2.3 + + '@vitejs/plugin-react@6.0.5(vite@8.2.1(@types/node@24.13.3)(jiti@2.7.0))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.2.1(@types/node@24.13.3)(jiti@2.7.0) + + csstype@3.2.3: {} + + detect-libc@2.1.2: {} + + enhanced-resolve@5.24.5: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + + fsevents@2.3.3: + optional: true + + graceful-fs@4.2.11: {} + + jiti@2.7.0: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + lucide-react@1.31.0(react@19.2.8): + dependencies: + react: 19.2.8 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + nanoid@3.3.18: {} + + oxlint@1.78.0: + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.78.0 + '@oxlint/binding-android-arm64': 1.78.0 + '@oxlint/binding-darwin-arm64': 1.78.0 + '@oxlint/binding-darwin-x64': 1.78.0 + '@oxlint/binding-freebsd-x64': 1.78.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.78.0 + '@oxlint/binding-linux-arm-musleabihf': 1.78.0 + '@oxlint/binding-linux-arm64-gnu': 1.78.0 + '@oxlint/binding-linux-arm64-musl': 1.78.0 + '@oxlint/binding-linux-ppc64-gnu': 1.78.0 + '@oxlint/binding-linux-riscv64-gnu': 1.78.0 + '@oxlint/binding-linux-riscv64-musl': 1.78.0 + '@oxlint/binding-linux-s390x-gnu': 1.78.0 + '@oxlint/binding-linux-x64-gnu': 1.78.0 + '@oxlint/binding-linux-x64-musl': 1.78.0 + '@oxlint/binding-openharmony-arm64': 1.78.0 + '@oxlint/binding-win32-arm64-msvc': 1.78.0 + '@oxlint/binding-win32-ia32-msvc': 1.78.0 + '@oxlint/binding-win32-x64-msvc': 1.78.0 + + picocolors@1.1.1: {} + + picomatch@4.0.5: {} + + postcss@8.5.26: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + react-dom@19.2.8(react@19.2.8): + dependencies: + react: 19.2.8 + scheduler: 0.27.0 + + react@19.2.8: {} + + rolldown@1.2.4: + dependencies: + '@oxc-project/types': 0.144.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.2.4 + '@rolldown/binding-darwin-arm64': 1.2.4 + '@rolldown/binding-darwin-x64': 1.2.4 + '@rolldown/binding-freebsd-x64': 1.2.4 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.4 + '@rolldown/binding-linux-arm64-gnu': 1.2.4 + '@rolldown/binding-linux-arm64-musl': 1.2.4 + '@rolldown/binding-linux-ppc64-gnu': 1.2.4 + '@rolldown/binding-linux-s390x-gnu': 1.2.4 + '@rolldown/binding-linux-x64-gnu': 1.2.4 + '@rolldown/binding-linux-x64-musl': 1.2.4 + '@rolldown/binding-openharmony-arm64': 1.2.4 + '@rolldown/binding-win32-arm64-msvc': 1.2.4 + '@rolldown/binding-win32-x64-msvc': 1.2.4 + + scheduler@0.27.0: {} + + source-map-js@1.2.1: {} + + tailwindcss@4.3.3: {} + + tapable@2.3.3: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + tslib@2.8.1: {} + + typescript@6.0.3: {} + + undici-types@7.18.2: {} + + vite@8.2.1(@types/node@24.13.3)(jiti@2.7.0): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.5 + postcss: 8.5.26 + rolldown: 1.2.4 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.13.3 + fsevents: 2.3.3 + jiti: 2.7.0 + + zustand@5.0.15(@types/react@19.2.18)(react@19.2.8): + optionalDependencies: + '@types/react': 19.2.18 + react: 19.2.8 diff --git a/code/fms/fms-mvp-demo/public/favicon.svg b/code/fms/fms-mvp-demo/public/favicon.svg new file mode 100644 index 00000000..6893eb13 --- /dev/null +++ b/code/fms/fms-mvp-demo/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/code/fms/fms-mvp-demo/public/icons.svg b/code/fms/fms-mvp-demo/public/icons.svg new file mode 100644 index 00000000..e9522193 --- /dev/null +++ b/code/fms/fms-mvp-demo/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/fms/fms-mvp-demo/src/App.css b/code/fms/fms-mvp-demo/src/App.css new file mode 100644 index 00000000..043abdf6 --- /dev/null +++ b/code/fms/fms-mvp-demo/src/App.css @@ -0,0 +1,280 @@ +:root { + --ink: #26313c; + --muted: #74808a; + --muted-2: #98a2aa; + --line: #e0e5e7; + --line-dark: #d3dade; + --paper: #ffffff; + --canvas: #f7f9f9; + --sidebar: #26353c; + --sidebar-2: #30434b; + --teal: #157d78; + --teal-dark: #0b625e; + --teal-soft: #e8f4f2; + --amber: #bd7627; + --amber-soft: #fff3e1; + --red: #b95252; + --shadow: 0 9px 30px rgba(31, 49, 56, .07); +} + +.app-shell { min-height: 100vh; color: var(--ink); background: #eef1f2; } +.topbar { height: 72px; display: flex; align-items: center; padding: 0 28px; background: #fff; border-bottom: 1px solid var(--line); gap: 34px; } +.brand-block { display: flex; align-items: center; gap: 11px; width: 238px; flex: 0 0 238px; } +.brand-mark { width: 32px; height: 32px; display: grid; place-items: center; background: var(--teal); color: #fff; font-weight: 800; font-size: 18px; border-radius: 6px; letter-spacing: -.06em; } +.brand-name { font-weight: 760; letter-spacing: .04em; font-size: 14px; color: #304047; } +.brand-name span { color: var(--teal); margin: 0 2px; } +.brand-subtitle { color: var(--muted-2); text-transform: uppercase; font-size: 9px; letter-spacing: .16em; margin-top: 2px; } +.breadcrumb { display: flex; align-items: center; gap: 9px; color: #66747d; font-size: 13px; flex: 1; } +.breadcrumb strong { color: var(--ink); font-weight: 650; } +.breadcrumb .slash { color: #b3bdc2; margin: 0 2px; } +.muted { color: var(--muted-2); } +.topbar-actions { display: flex; align-items: center; gap: 12px; } +.local-badge { display: inline-flex; align-items: center; gap: 7px; font-size: 10px; color: #49766f; letter-spacing: .08em; border: 1px solid #c6e0dc; background: #f4fbfa; padding: 6px 9px; border-radius: 5px; } +.status-pulse, .release-dot, .green-dot { width: 6px; height: 6px; border-radius: 50%; background: #39a17b; display: inline-block; } +.status-pulse { box-shadow: 0 0 0 3px rgba(57, 161, 123, .13); } +.icon-button { width: 32px; height: 32px; border: 1px solid transparent; border-radius: 5px; display: inline-grid; place-items: center; color: #77838b; background: transparent; padding: 0; } +.icon-button:hover, .icon-button.is-active { color: var(--teal); border-color: #c9dedb; background: var(--teal-soft); } +.avatar { width: 31px; height: 31px; border-radius: 50%; display: grid; place-items: center; color: #fff; background: #cf7a54; font-size: 11px; font-weight: 750; } + +.workspace { min-height: calc(100vh - 72px); display: flex; } +.sidebar { width: 248px; flex: 0 0 248px; background: var(--sidebar); color: #c4d0d2; padding: 25px 13px 15px; display: flex; flex-direction: column; } +.sidebar-section-label { font-size: 10px; letter-spacing: .16em; color: #809198; padding: 0 12px 10px; font-weight: 700; } +.sidebar-section-label.with-action { display: flex; justify-content: space-between; align-items: center; padding-top: 27px; } +.sidebar-section-label.with-action .icon-button { color: #8da0a5; width: 24px; height: 24px; } +.sidebar-section-label.with-action .icon-button:hover { background: #3c535b; color: #fff; } +.nav-item, .tree-item { border: 0; background: transparent; color: inherit; display: flex; align-items: center; gap: 10px; width: 100%; min-height: 38px; padding: 0 12px; border-radius: 5px; text-align: left; font-size: 13px; } +.nav-item:hover, .tree-item:hover { background: rgba(255,255,255,.06); color: #fff; } +.nav-item.active { color: #fff; background: #36555b; box-shadow: inset 3px 0 0 #75c7bd; } +.nav-item .nav-count { margin-left: auto; font-size: 10px; color: #8fa4a7; } +.nav-item.active .nav-count { color: #b5e3dc; } +.nav-dot { margin-left: auto; width: 5px; height: 5px; border-radius: 50%; background: #e3a24f; } +.sidebar-divider { height: 1px; background: rgba(218, 236, 235, .12); margin: 18px 12px 0; } +.module-tree { display: flex; flex-direction: column; gap: 2px; } +.tree-item { color: #aebfc1; } +.tree-item.open { color: #e0ecec; } +.tree-item.child { padding-left: 37px; font-size: 12px; } +.tree-item.selected { color: #fff; background: #314950; } +.tree-icon { color: #7cc7bc; } +.tree-icon.finance { color: #e3ad67; } +.tree-icon.master { color: #93a9d2; } +.tree-state { margin-left: auto; font-size: 9px; letter-spacing: .08em; color: #8bd3c9; } +.sidebar-footer { margin-top: auto; border-top: 1px solid rgba(218, 236, 235, .12); padding: 17px 8px 2px; } +.tenant-row { display: flex; align-items: center; gap: 9px; color: #deebeb; } +.tenant-icon { width: 28px; height: 28px; display: grid; place-items: center; background: #38565c; color: #9dd7cf; border-radius: 5px; } +.tenant-row > div:nth-child(2) { flex: 1; min-width: 0; } +.tenant-row strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; font-weight: 600; } +.tenant-label { display: block; font-size: 8px; letter-spacing: .12em; color: #7f979b; margin-bottom: 3px; } +.sidebar-footer-links { display: flex; justify-content: space-between; align-items: center; color: #779095; font-size: 9px; margin: 11px 2px 0; } +.sidebar-footer-links span:first-child { display: flex; align-items: center; gap: 4px; } + +.main-content { flex: 1; min-width: 0; padding: 30px 33px 48px; } +.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 25px; max-width: 1510px; margin: 0 auto; } +.eyebrow, .panel-kicker { display: flex; align-items: center; gap: 8px; color: #839198; font-size: 10px; letter-spacing: .15em; font-weight: 750; } +.eyebrow-line { width: 18px; height: 2px; background: #d3984e; display: inline-block; } +.page-head h1 { margin: 8px 0 4px; font-size: 29px; line-height: 1.12; letter-spacing: -.035em; font-weight: 730; color: #26373d; } +.page-head p { margin: 0; color: #7e8a91; font-size: 13px; } +.head-actions { display: flex; align-items: center; gap: 8px; padding-bottom: 3px; } +.button { height: 34px; border-radius: 5px; padding: 0 12px; border: 1px solid transparent; display: inline-flex; align-items: center; justify-content: center; gap: 7px; color: #54636a; background: transparent; font-size: 12px; font-weight: 650; white-space: nowrap; } +.button:hover { transform: translateY(-1px); } +.button.subtle:hover { color: var(--teal); background: #eaf3f2; } +.button.outline { border-color: #d4dddf; background: #fff; box-shadow: 0 2px 7px rgba(40,60,65,.04); } +.button.outline:hover { border-color: #a6cbc5; color: var(--teal); } +.button.primary { background: var(--teal); color: #fff; box-shadow: 0 4px 10px rgba(21,125,120,.16); } +.button.primary:hover { background: var(--teal-dark); } +.button.small { height: 30px; padding: 0 9px; font-size: 11px; } +.button.full { width: 100%; } +.mode-strip { max-width: 1510px; margin: 27px auto 14px; height: 43px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--line); } +.mode-tabs { display: flex; align-items: center; gap: 18px; height: 100%; } +.mode-tabs button { height: 100%; padding: 0 2px; display: flex; align-items: center; gap: 7px; border: 0; background: transparent; color: #909ba0; font-size: 12px; font-weight: 650; position: relative; } +.mode-tabs button.active { color: var(--teal); } +.mode-tabs button.active::after { content: ''; position: absolute; height: 2px; bottom: -1px; left: 0; right: 0; background: var(--teal); } +.release-summary { display: flex; align-items: center; gap: 9px; color: #8b969c; font-size: 11px; } +.release-label { display: flex; align-items: center; gap: 5px; color: #5e958a; letter-spacing: .08em; font-size: 9px; font-weight: 750; } +.release-summary strong { color: #56646b; font-size: 11px; } +.release-divider { height: 14px; width: 1px; background: #dce2e3; } + +.builder-grid { display: grid; grid-template-columns: 235px minmax(500px, 1fr) 268px; gap: 13px; max-width: 1510px; margin: 0 auto; align-items: start; } +.builder-panel { background: var(--paper); border: 1px solid var(--line); border-radius: 7px; box-shadow: var(--shadow); min-width: 0; } +.palette-panel, .inspector-panel { padding: 18px 16px; } +.canvas-panel { padding: 18px 21px 23px; min-height: 740px; } +.panel-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; } +.panel-heading h2 { margin: 4px 0 0; font-size: 15px; letter-spacing: -.01em; color: #324047; font-weight: 720; } +.panel-kicker { font-size: 9px; letter-spacing: .15em; color: #91a0a5; } +.count-chip { color: #78868d; background: #f0f4f4; border-radius: 4px; padding: 4px 7px; font-size: 9px; } +.panel-intro { margin: 13px 0 14px; color: #8a969b; line-height: 1.5; font-size: 11px; } +.palette-search { height: 31px; border: 1px solid #dce3e4; border-radius: 4px; display: flex; align-items: center; gap: 7px; color: #9ba6aa; padding: 0 9px; background: #fbfcfc; } +.palette-search input { min-width: 0; border: 0; outline: 0; background: transparent; color: #57666c; font-size: 11px; width: 100%; } +.palette-group { margin-top: 18px; } +.palette-group-title { display: flex; align-items: center; justify-content: space-between; margin-bottom: 7px; font-size: 9px; letter-spacing: .1em; color: #a0abae; font-weight: 750; } +.palette-group-title span { font-size: 9px; color: #bbc3c5; letter-spacing: 0; } +.palette-item { width: 100%; min-height: 34px; border: 0; background: transparent; display: flex; align-items: center; gap: 8px; padding: 5px 4px; border-radius: 4px; color: #5b6a70; text-align: left; font-size: 11px; } +.palette-item:hover { background: #edf6f5; color: var(--teal-dark); } +.palette-item.is-dragging { opacity: .42; } +.palette-item small { margin-left: auto; color: #adb7ba; font-size: 9px; } +.palette-icon { width: 22px; height: 22px; display: grid; place-items: center; border: 1px solid #dce8e6; background: #f6fbfa; color: var(--teal); border-radius: 4px; } +.palette-divider { height: 1px; background: #edf0f0; margin: 17px 0; } +.relation-palette { width: 100%; display: flex; align-items: center; gap: 8px; border: 0; background: transparent; padding: 4px 2px; color: #55666d; text-align: left; } +.relation-palette:hover { color: var(--teal-dark); } +.relation-icon { width: 26px; height: 26px; display: grid; place-items: center; border-radius: 4px; color: #bd7627; background: #fff4e6; } +.relation-palette span:nth-child(2) { flex: 1; } +.relation-palette strong { display: block; font-size: 11px; font-weight: 650; } +.relation-palette small { display: block; color: #a1abad; font-size: 9px; margin-top: 2px; } +.new-field-button { width: 100%; display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 21px; border: 1px dashed #cbd9d8; color: #42847e; background: #f8fcfb; height: 31px; border-radius: 4px; font-size: 10px; font-weight: 650; } +.new-field-button:hover { border-color: #83bcb5; background: #edf7f5; } +.canvas-heading { align-items: center; } +.canvas-tools { display: flex; align-items: center; gap: 4px; } +.saved-indicator { display: flex; align-items: center; gap: 5px; color: #859397; font-size: 10px; margin-right: 5px; } +.saved-check { display: grid; place-items: center; width: 16px; height: 16px; border-radius: 50%; color: #fff; background: #43a17d; } +.canvas-meta { display: flex; align-items: center; gap: 15px; border-bottom: 1px solid #edf0f1; padding: 14px 0 13px; color: #99a4a8; font-size: 10px; } +.canvas-meta span { display: inline-flex; align-items: center; gap: 5px; } +.form-canvas { padding-top: 17px; } +.canvas-section { border: 1px solid #dfe6e7; border-radius: 6px; background: #fbfcfc; overflow: hidden; } +.canvas-section + .canvas-section { margin-top: 15px; } +.section-heading { min-height: 52px; padding: 0 15px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e8edef; background: #fff; } +.section-heading > div:first-child { display: flex; align-items: center; gap: 10px; } +.section-heading-actions { display: flex; align-items: center; gap: 9px; } +.layout-chip { display: inline-flex; align-items: center; gap: 5px; color: #7d9995; background: #f0f8f6; border: 1px solid #d9ebe8; border-radius: 4px; padding: 4px 6px; font-size: 9px; } +.section-heading strong { display: block; font-size: 12px; color: #3b4b52; font-weight: 700; } +.section-heading small { display: block; color: #9aa5a9; font-size: 10px; margin-top: 2px; } +.section-number { width: 23px; height: 23px; display: grid; place-items: center; color: #398f87; background: #e8f4f2; border-radius: 4px; font-size: 9px; font-weight: 760; } +.section-number.warm { color: #b87428; background: #fff1dd; } +.section-menu { border: 0; background: transparent; color: #a4afb2; padding: 5px; } +.section-menu:hover { color: var(--teal); } +.drop-zone { padding: 12px; min-height: 230px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; align-items: stretch; } +.field-card { display: flex; align-items: center; gap: 8px; min-height: 61px; padding: 0 10px 0 5px; border: 1px solid #e2e8e9; background: #fff; border-radius: 5px; cursor: pointer; transition: border-color .15s, box-shadow .15s; } +.field-card:hover { border-color: #b8d5d1; box-shadow: 0 2px 8px rgba(22, 102, 97, .06); } +.field-card.is-selected { border-color: #67aaa2; box-shadow: 0 0 0 2px #e7f4f2; } +.field-card.is-dragging, .column-chip.is-dragging { opacity: .48; } +.drag-handle, .column-grip { border: 0; background: transparent; color: #bcc6c8; padding: 5px; display: grid; place-items: center; cursor: grab; } +.drag-handle:active, .column-grip:active { cursor: grabbing; } +.field-card-main { flex: 1; min-width: 0; } +.field-card-title { display: flex; align-items: center; gap: 7px; color: #47575e; font-size: 11px; font-weight: 650; } +.required-mark { color: #c87940; font-size: 8px; font-weight: 700; } +.field-card-meta { display: flex; gap: 9px; align-items: center; margin-top: 4px; color: #a1acae; font-size: 9px; } +.field-card-meta code, .selected-object code { color: #77928f; background: #eef7f5; padding: 2px 4px; border-radius: 3px; font-size: 9px; } +.visibility-dot { color: #74aba3; display: grid; place-items: center; } +.visibility-dot.off { color: #bbc5c6; } +.drop-hint { grid-column: 1 / -1; min-height: 38px; border: 1px dashed #c9d9d8; color: #99aaa9; background: #fafdfc; border-radius: 4px; display: flex; align-items: center; justify-content: center; gap: 6px; font-size: 10px; } +.relation-section { background: #fff; } +.relation-actions { display: flex; align-items: center; gap: 11px; } +.relation-status { display: flex; align-items: center; gap: 5px; color: #6b9d91; font-size: 9px; } +.relation-config-line { display: flex; align-items: center; gap: 13px; min-height: 39px; padding: 0 15px; color: #879699; font-size: 10px; border-bottom: 1px solid #edf0f0; } +.relation-config-line span { display: inline-flex; align-items: center; gap: 5px; } +.relation-config-line button { margin-left: auto; border: 0; background: transparent; color: #4a9891; display: flex; align-items: center; gap: 4px; font-size: 10px; } +.relation-config-line button:hover { color: var(--teal-dark); } +.columns-drop-zone { display: flex; align-items: center; gap: 5px; padding: 12px 13px 7px; overflow-x: auto; } +.column-chip { height: 32px; flex: 0 0 auto; display: flex; align-items: center; gap: 4px; border: 1px solid #d9e5e4; background: #f7fbfa; color: #58746f; border-radius: 4px; padding: 0 5px 0 1px; cursor: pointer; font-size: 10px; } +.column-chip:hover, .column-chip.is-selected { border-color: #71afa8; background: #ecf7f5; color: #337d76; } +.column-chip small { color: #a0b2b0; font-size: 8px; margin-left: auto; } +.column-add { height: 32px; min-width: 31px; border: 1px dashed #c7d8d6; color: #86aaa5; display: grid; place-items: center; border-radius: 4px; } +.relation-preview-table { margin: 0 13px; border: 1px solid #e3e9e9; border-radius: 4px; overflow: hidden; } +.preview-row { min-height: 32px; display: flex; align-items: center; border-top: 1px solid #edf1f1; color: #68797e; font-size: 10px; background: #fff; } +.preview-row:first-child { border-top: 0; } +.preview-row span { flex: 0 0 auto; padding: 0 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.preview-row.preview-header { min-height: 30px; color: #8d9a9e; background: #f7f9f9; font-size: 9px; font-weight: 700; } +.preview-row .row-actions, .preview-row .row-actions-head { width: 36px; color: #abb7b9; margin-left: auto; } +.add-row-button { display: flex; align-items: center; gap: 5px; border: 0; background: transparent; color: #3c8c84; font-size: 10px; margin: 10px 13px 13px; padding: 2px 0; } +.add-row-button:hover { color: var(--teal-dark); } +.inspector-panel { min-height: 450px; } +.selected-object { display: flex; align-items: center; gap: 8px; background: #f7fafa; border: 1px solid #e1eaea; border-radius: 5px; padding: 11px 10px; margin-top: 16px; } +.object-icon { width: 26px; height: 26px; display: grid; place-items: center; border-radius: 4px; } +.field-object { color: #3b8e86; background: #e3f2f0; } +.table-object { color: #ad6b26; background: #fff0da; } +.selected-object > div:nth-child(2) { flex: 1; min-width: 0; } +.selected-object strong { display: block; color: #495960; font-size: 11px; } +.selected-object code { display: inline-block; margin-top: 4px; } +.object-type { align-self: flex-start; color: #a6b0b2; font-size: 8px; letter-spacing: .09em; } +.inspector-form { margin-top: 16px; display: flex; flex-direction: column; gap: 14px; } +.inspector-form label { display: flex; flex-direction: column; gap: 6px; color: #7e8e92; font-size: 10px; } +.inspector-form input, .select-like, .number-input { height: 32px; border: 1px solid #dce5e5; background: #fff; color: #53646a; border-radius: 4px; padding: 0 9px; outline: none; font-size: 11px; } +.inspector-form input:focus, .number-input:focus-within { border-color: #72b2aa; box-shadow: 0 0 0 2px #e8f5f3; } +.select-like, .number-input { display: flex; align-items: center; justify-content: space-between; } +.number-input input { border: 0; padding: 0; height: 100%; min-width: 0; width: 100%; } +.number-input span { color: #a0abad; font-size: 9px; } +.switch-row { flex-direction: row !important; justify-content: space-between; align-items: center; gap: 8px; } +.switch-row span:first-child { display: flex; flex-direction: column; gap: 3px; } +.switch-row strong { color: #56666c; font-size: 11px; font-weight: 650; } +.switch-row small { color: #a3afb1; font-size: 9px; } +.switch { width: 28px; height: 16px; border: 0; background: #d7dfe0; border-radius: 20px; padding: 2px; transition: background .15s; } +.switch span { display: block; width: 12px; height: 12px; border-radius: 50%; background: #fff; transition: transform .15s; box-shadow: 0 1px 2px rgba(0,0,0,.16); } +.switch.on { background: #55a49b; } +.switch.on span { transform: translateX(12px); } +.inspector-divider { height: 1px; background: #edf0f0; margin: 20px 0 14px; } +.permission-callout { display: flex; gap: 9px; padding: 11px 10px; background: #f1f8f6; border: 1px solid #d7ebe7; border-radius: 5px; } +.permission-callout.amber { background: #fff8ed; border-color: #f2dfbf; } +.callout-icon { color: #4b958a; padding-top: 1px; } +.permission-callout.amber .callout-icon { color: #ba7628; } +.permission-callout strong { color: #4e6968; font-size: 10px; } +.permission-callout p { color: #7d9694; margin: 4px 0 7px; font-size: 9px; line-height: 1.45; } +.permission-callout button { border: 0; padding: 0; background: transparent; color: #3e8e86; display: inline-flex; align-items: center; gap: 4px; font-size: 9px; } +.inspector-note { display: flex; align-items: center; gap: 6px; color: #9ba8ab; font-size: 9px; margin-top: 16px; } +.inspector-note b { color: #6c7d82; } +.empty-inspector { min-height: 200px; display: grid; place-items: center; align-content: center; gap: 8px; color: #aab5b7; font-size: 11px; text-align: center; } +.empty-inspector p { max-width: 150px; line-height: 1.45; } +.drag-overlay { min-height: 34px; display: flex; align-items: center; gap: 7px; padding: 0 11px; background: #274e50; color: #fff; border-radius: 4px; box-shadow: 0 7px 20px rgba(21, 65, 64, .25); font-size: 11px; } + +.runtime-shell { max-width: 1050px; margin: 0 auto; } +.runtime-topline { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; padding: 17px 0 19px; } +.runtime-topline h2 { margin: 7px 0 4px; color: #2d3e44; font-size: 23px; letter-spacing: -.025em; } +.runtime-topline p { margin: 0; color: #879499; font-size: 11px; } +.runtime-topline p b { color: #4d827a; font-weight: 650; } +.runtime-actions { display: flex; align-items: center; gap: 7px; } +.runtime-card { background: #fff; border: 1px solid var(--line); border-radius: 7px; box-shadow: var(--shadow); padding: 23px 26px 26px; } +.runtime-card-head { display: flex; align-items: center; justify-content: space-between; } +.runtime-card-head h3 { margin: 5px 0 0; color: #405158; font-size: 14px; font-weight: 720; } +.runtime-card-head h3 small { color: #9aa7aa; font-size: 10px; font-weight: 500; margin-left: 6px; } +.draft-chip { color: #ba7627; background: #fff2df; border: 1px solid #f1dfc1; border-radius: 4px; padding: 5px 7px; font-size: 9px; letter-spacing: .08em; font-weight: 750; } +.runtime-form-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 19px 16px; margin-top: 23px; } +.runtime-field { display: flex; flex-direction: column; gap: 7px; color: #708087; font-size: 10px; } +.runtime-field > span b { color: #c5723e; margin-left: 3px; } +.runtime-input { width: 100%; height: 36px; border: 1px solid #dce5e5; border-radius: 4px; padding: 0 10px; outline: none; color: #506169; background: #fff; font-size: 11px; } +.runtime-input:focus { border-color: #69ada4; box-shadow: 0 0 0 2px #e8f6f3; } +.input-wrap { position: relative; display: flex; align-items: center; } +.input-wrap svg { position: absolute; right: 10px; color: #9facaf; pointer-events: none; } +.input-wrap .runtime-input { padding-right: 28px; } +.runtime-divider { height: 1px; background: #e8edef; margin: 28px 0 22px; } +.compact-head { align-items: flex-end; } +.runtime-table-wrap { border: 1px solid #e0e7e7; border-radius: 5px; overflow: auto; margin-top: 16px; } +.runtime-table { min-width: 780px; width: 100%; border-collapse: collapse; table-layout: fixed; } +.runtime-table th, .runtime-table td { text-align: left; border-bottom: 1px solid #edf1f1; padding: 0 10px; height: 40px; font-size: 10px; color: #66777d; } +.runtime-table th { background: #f7f9f9; color: #88979b; font-weight: 700; } +.runtime-table tr:last-child td { border-bottom: 0; } +.runtime-table td input { height: 28px; width: 100%; border: 1px solid transparent; background: transparent; border-radius: 3px; padding: 0 6px; color: #56696f; font-size: 10px; outline: 0; } +.runtime-table td input:focus { border-color: #9dccca; background: #f7fcfb; } +.runtime-footnote { display: flex; align-items: center; gap: 7px; margin-top: 13px; color: #85979a; font-size: 10px; } +.runtime-footnote svg { color: #4e988d; } +.runtime-footnote button { border: 0; background: transparent; color: #3d8c84; font-size: 10px; display: flex; align-items: center; gap: 4px; margin-left: auto; } + +.modal-backdrop { position: fixed; inset: 0; display: grid; place-items: center; background: rgba(29, 47, 52, .34); z-index: 20; } +.help-modal { width: 375px; background: #fff; border-radius: 7px; box-shadow: 0 24px 65px rgba(24, 48, 53, .24); padding: 23px; } +.modal-title { display: flex; justify-content: space-between; align-items: flex-start; } +.modal-title h2 { margin: 6px 0 0; font-size: 18px; color: #35464c; } +.help-modal > p { margin: 19px 0; color: #75858a; line-height: 1.65; font-size: 12px; } +.help-points { display: flex; flex-direction: column; gap: 9px; color: #65847e; font-size: 11px; margin-bottom: 20px; } +.help-points span { display: flex; align-items: center; gap: 8px; } +.help-points svg { color: #4e9b90; } +.release-modal { width: 430px; background: #fff; border-radius: 7px; box-shadow: 0 24px 65px rgba(24, 48, 53, .24); padding: 23px; } +.release-list { display: flex; flex-direction: column; gap: 8px; margin-top: 20px; } +.release-item { min-height: 57px; display: flex; align-items: center; gap: 9px; border: 1px solid #e2e8e8; border-radius: 5px; padding: 0 11px; } +.release-item.current { border-color: #cfe5e1; background: #f5fbfa; } +.release-icon { width: 26px; height: 26px; display: grid; place-items: center; border-radius: 50%; color: #fff; background: #51a391; } +.release-icon.old { color: #9ca9ac; background: #f0f3f3; } +.release-item > div:nth-child(2) { flex: 1; } +.release-item strong { display: block; color: #52636a; font-size: 11px; } +.release-item span { display: block; color: #9aa8ab; font-size: 9px; margin-top: 3px; } +.release-pill { color: #4f9b8e !important; background: #e1f2ee; border-radius: 3px; padding: 4px 5px; font-size: 8px !important; letter-spacing: .07em; font-weight: 750; margin: 0 !important; } +.text-button { display: flex; align-items: center; gap: 4px; border: 0; background: transparent; color: #3e8e86; font-size: 10px; } +.text-button:hover { color: var(--teal-dark); } +.release-note { color: #9aa7aa; line-height: 1.5; font-size: 10px; margin: 16px 1px 0; } +.toast { position: fixed; right: 28px; bottom: 25px; z-index: 30; min-width: 315px; max-width: 400px; display: flex; gap: 10px; padding: 13px 15px; background: #27474d; color: #fff; border-radius: 5px; box-shadow: 0 12px 35px rgba(23, 49, 55, .24); } +.toast-icon { width: 22px; height: 22px; display: grid; place-items: center; border-radius: 50%; background: #3c9788; flex: 0 0 auto; } +.toast strong { display: block; font-size: 11px; font-weight: 700; } +.toast span { display: block; color: #b7cccb; font-size: 10px; line-height: 1.4; margin-top: 2px; } + +@media (max-width: 1260px) { + body { min-width: 1020px; } + .main-content { padding-inline: 20px; } + .builder-grid { grid-template-columns: 210px minmax(480px, 1fr) 245px; } + .page-head { align-items: flex-start; flex-direction: column; gap: 14px; } + .head-actions { align-self: flex-end; } +} diff --git a/code/fms/fms-mvp-demo/src/App.tsx b/code/fms/fms-mvp-demo/src/App.tsx new file mode 100644 index 00000000..3148a15d --- /dev/null +++ b/code/fms/fms-mvp-demo/src/App.tsx @@ -0,0 +1,305 @@ +import { useEffect, useMemo, useState } from 'react' +import type { CSSProperties, ReactNode } from 'react' +import { + DndContext, + DragOverlay, + PointerSensor, + type DragEndEvent, + type DragStartEvent, + useDraggable, + useSensor, + useSensors, +} from '@dnd-kit/core' +import { + SortableContext, + arrayMove, + horizontalListSortingStrategy, + rectSortingStrategy, + useSortable, +} from '@dnd-kit/sortable' +import { CSS } from '@dnd-kit/utilities' +import { + ArrowLeft, + ArrowRight, + Box, + Check, + ChevronDown, + ChevronRight, + CircleHelp, + Columns3, + Copy, + Database, + Eye, + EyeOff, + FileClock, + GripVertical, + LayoutDashboard, + Layers3, + ListFilter, + LockKeyhole, + MoreHorizontal, + PackageCheck, + Play, + Plus, + RotateCcw, + Save, + Settings2, + ShieldCheck, + Table2, + X, +} from 'lucide-react' +import './App.css' + +type FieldType = 'text' | 'reference' | 'date' | 'select' | 'number' +type Field = { + id: string + label: string + key: string + type: FieldType + required?: boolean + visible: boolean + helper?: string + role?: string +} +type Column = { id: string; label: string; key: string; width: number; visible: boolean } +type Toast = { title: string; message: string } + +const initialFields: Field[] = [ + { id: 'booking-no', label: '订舱编号', key: 'booking_no', type: 'text', required: true, visible: true, helper: '系统自动生成', role: '系统字段' }, + { id: 'customer', label: '客户', key: 'customer_id', type: 'reference', required: true, visible: true, helper: '选择客户主数据', role: '基础资料' }, + { id: 'carrier', label: '船公司', key: 'carrier_id', type: 'reference', required: true, visible: true, helper: '合作伙伴 / 承运人', role: '基础资料' }, + { id: 'etd', label: '预计开船日', key: 'etd', type: 'date', required: true, visible: true, role: '业务字段' }, + { id: 'pol', label: '起运港', key: 'pol_id', type: 'reference', visible: true, role: '业务字段' }, + { id: 'pod', label: '目的港', key: 'pod_id', type: 'reference', visible: true, role: '业务字段' }, + { id: 'trade-term', label: '贸易条款', key: 'trade_term', type: 'select', visible: true, role: '字典字段' }, + { id: 'notes', label: '操作备注', key: 'notes', type: 'text', visible: true, helper: '内部业务人员可见', role: '扩展字段' }, +] + +const initialColumns: Column[] = [ + { id: 'container-no', label: '箱号', key: 'container_no', width: 156, visible: true }, + { id: 'container-type', label: '箱型', key: 'container_type', width: 110, visible: true }, + { id: 'weight', label: '毛重 (KG)', key: 'weight', width: 122, visible: true }, + { id: 'packages', label: '件数', key: 'packages', width: 90, visible: true }, + { id: 'seal-no', label: '封条号', key: 'seal_no', width: 140, visible: true }, +] + +const fieldTypeLabel: Record = { + text: '单行文本', + reference: '引用选择', + date: '日期选择', + select: '固定字典', + number: '数值', +} + +const initialRows = [ + { id: '1', container_no: 'MSCU 482913 6', container_type: '40HQ', weight: '12,480', packages: '32', seal_no: 'SL-902183' }, + { id: '2', container_no: 'MSCU 482917 0', container_type: '40HQ', weight: '10,260', packages: '26', seal_no: 'SL-902184' }, +] + +function IconButton({ label, children, onClick, active = false }: { label: string; children: ReactNode; onClick?: () => void; active?: boolean }) { + return +} + +function SortableField({ field, selected, onSelect }: { field: Field; selected: boolean; onSelect: () => void }) { + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: `field:${field.id}`, data: { kind: 'field', id: field.id } }) + const style: CSSProperties = { transform: CSS.Transform.toString(transform), transition } + return ( +
+ +
+
{field.label}{field.required && 必填}
+
{field.key}{fieldTypeLabel[field.type]}
+
+ {field.visible ? : } +
+ ) +} + +function SortableColumn({ column, selected, onSelect }: { column: Column; selected: boolean; onSelect: () => void }) { + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: `column:${column.id}`, data: { kind: 'column', id: column.id } }) + const style: CSSProperties = { transform: CSS.Transform.toString(transform), transition, width: column.width } + return ( +
+ + {column.label} + {column.width} +
+ ) +} + +function PaletteField({ field, onAdd }: { field: Field; onAdd: () => void }) { + const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ id: `palette:${field.id}`, data: { kind: 'palette', field } }) + const onClick = (event: React.MouseEvent) => { event.stopPropagation(); onAdd() } + return ( + + ) +} + +function App() { + const [mode, setMode] = useState<'builder' | 'preview'>('builder') + const [activeNav, setActiveNav] = useState<'builder' | 'runtime' | 'releases'>('builder') + const [fields, setFields] = useState(initialFields) + const [columns, setColumns] = useState(initialColumns) + const [selected, setSelected] = useState<{ kind: 'field' | 'column'; id: string }>({ kind: 'field', id: 'carrier' }) + const [toast, setToast] = useState(null) + const [showHelp, setShowHelp] = useState(false) + const [showReleases, setShowReleases] = useState(false) + const [release, setRelease] = useState(42) + const [candidate, setCandidate] = useState(false) + const [rows, setRows] = useState(initialRows) + const [activeId, setActiveId] = useState(null) + const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } })) + + useEffect(() => { + const saved = window.localStorage.getItem('fms-mvp-layout') + if (saved) { + try { + const data = JSON.parse(saved) as { fields?: Field[]; columns?: Column[]; release?: number } + if (data.fields) setFields(data.fields) + if (data.columns) setColumns(data.columns) + if (data.release) setRelease(data.release) + } catch { /* Use the stable fixture when local data is incomplete. */ } + } + }, []) + + const visibleFields = useMemo(() => fields.filter((field) => field.visible), [fields]) + const activeItem = selected.kind === 'field' ? fields.find((field) => field.id === selected.id) : columns.find((column) => column.id === selected.id) + + function notify(title: string, message: string) { + setToast({ title, message }) + window.setTimeout(() => setToast(null), 3200) + } + + function saveCandidate() { + window.localStorage.setItem('fms-mvp-layout', JSON.stringify({ fields, columns, release })) + setCandidate(true) + notify('候选版本已保存', '变更已保存到 Release 42 · 候选版本,可进入预览验证。') + } + + function publish() { + const next = release + 1 + window.localStorage.setItem('fms-mvp-layout', JSON.stringify({ fields, columns, release: next })) + setRelease(next) + setCandidate(false) + notify('配置已发布', `Release ${next} 已激活,当前租户运行时已刷新。`) + } + + function rollback() { + setFields(initialFields) + setColumns(initialColumns) + setCandidate(false) + window.localStorage.setItem('fms-mvp-layout', JSON.stringify({ fields: initialFields, columns: initialColumns, release: 41 })) + setRelease(41) + notify('已回滚配置', '当前运行时已切换回 Release 41。') + } + + function addField(field: Field) { + if (fields.some((item) => item.id === field.id)) { + setSelected({ kind: 'field', id: field.id }) + return + } + setFields((current) => [...current, { ...field, visible: true }]) + setSelected({ kind: 'field', id: field.id }) + notify('字段已加入表单', `${field.label} 已添加到主表表单。`) + } + + function handleDragStart(event: DragStartEvent) { setActiveId(String(event.active.id)) } + function handleDragCancel() { setActiveId(null) } + function handleDragEnd(event: DragEndEvent) { + setActiveId(null) + const { active, over } = event + if (!over || active.id === over.id) return + const activeData = active.data.current + if (activeData?.kind === 'palette') { + addField(activeData.field as Field) + return + } + const activeKind = String(active.id).split(':')[0] + const overKind = String(over.id).split(':')[0] + if (activeKind !== overKind) return + if (activeKind === 'field') { + const oldIndex = fields.findIndex((item) => `field:${item.id}` === active.id) + const newIndex = fields.findIndex((item) => `field:${item.id}` === over.id) + if (oldIndex !== -1 && newIndex !== -1) setFields((current) => arrayMove(current, oldIndex, newIndex)) + } + if (activeKind === 'column') { + const oldIndex = columns.findIndex((item) => `column:${item.id}` === active.id) + const newIndex = columns.findIndex((item) => `column:${item.id}` === over.id) + if (oldIndex !== -1 && newIndex !== -1) setColumns((current) => arrayMove(current, oldIndex, newIndex)) + } + } + + function updateField(patch: Partial) { + if (selected.kind !== 'field') return + setFields((current) => current.map((field) => field.id === selected.id ? { ...field, ...patch } : field)) + } + + function updateColumn(patch: Partial) { + if (selected.kind !== 'column') return + setColumns((current) => current.map((column) => column.id === selected.id ? { ...column, ...patch } : column)) + } + + function selectNav(nav: typeof activeNav) { + setActiveNav(nav) + if (nav === 'builder') { setMode('builder'); return } + if (nav === 'runtime') { setMode('preview'); return } + setShowReleases(true) + } + + return ( +
+
+
F
FMS / 工作台
元数据工作台
+
海运订舱/默认编辑表单
+
本地演示 setShowHelp(true)}>
YL
+
+ +
+ + +
+
配置 / 实体

订舱工作台

配置订舱表单和集装箱子表,无需编写代码。

+
当前版本Release {release}{candidate ? '候选版本已保存' : '所有变更已发布'}
+ + {mode === 'builder' ? +
+
组件

字段面板

{fields.length} 个字段

将字段拖入画布,或点击字段将其加入表单。

订舱字段 8
{fields.map((field) => addField(field)} />)}
关系 01
+ +
表单 / OCEAN.BOOKING

默认编辑表单

刚刚保存
2 个分组 每行 2 个字段 1 个关系表 已接入权限
01
订舱信息拖拽字段调整行和列
2 列布局
`field:${field.id}`)} strategy={rectSortingStrategy}>{fields.map((field) => setSelected({ kind: 'field', id: field.id })} />)}
将字段拖到这里,自动放入当前行
02
集装箱一对多关系 · 可编辑表格
可编辑
ocean.container {columns.filter((column) => column.visible).length} 个可见列
`column:${column.id}`)} strategy={horizontalListSortingStrategy}>{columns.filter((column) => column.visible).map((column) => setSelected({ kind: 'column', id: column.id })} />)}
{columns.filter((column) => column.visible).map((column) => {column.label})}
{initialRows.slice(0, 2).map((row) =>
{columns.filter((column) => column.visible).map((column) => {row[column.key as keyof typeof row]})}
)}
+ + +
{activeId ?
{activeId.split(':')[1]}
: null}
+
: column.visible)} rows={rows} setRows={setRows} onBack={() => setMode('builder')} onNotify={notify} />} +
+
+ {showHelp &&
setShowHelp(false)}>
event.stopPropagation()}>
关于本演示

元数据驱动的配置流程

setShowHelp(false)}>

这是一个不接后端的 FMS 配置运行时演示。左侧字段可以拖入表单,中间区域支持排序,主子表列和运行时预览共用同一份本地元数据。

候选版本 主子表单 字段权限
} + {showReleases &&
setShowReleases(false)}>
event.stopPropagation()}>
配置历史

版本历史

setShowReleases(false)}>
Release {release}当前版本 · 刚刚激活
当前
Release 41上一版本 · 2 小时前

回滚会将本地演示切换到上一份元数据快照,不会影响生产数据。

} + {toast &&
{toast.title}{toast.message}
} +
+ ) +} + +function RuntimePreview({ fields, columns, rows, setRows, onBack, onNotify }: { fields: Field[]; columns: Column[]; rows: typeof initialRows; setRows: React.Dispatch>; onBack: () => void; onNotify: (title: string, message: string) => void }) { + const addRow = () => { setRows((current) => [...current, { id: String(current.length + 1), container_no: `MSCU 48292${current.length}`, container_type: '40HQ', weight: '—', packages: '—', seal_no: '—' }]); onNotify('子表行已新增', '新的集装箱行已加入当前草稿。') } + return
运行时预览 / 候选版本

新建订舱

当前以操作团队身份预览 Release 42 · 候选版本。

OCEAN.BOOKING

订舱信息

草稿
{fields.slice(0, 6).map((field) => )}
关系 / OCEAN.CONTAINER

集装箱 {rows.length} 行

{columns.map((column) => )}{rows.map((row) => {columns.map((column) => )})}
{column.label}
setRows((current) => current.map((item) => item.id === row.id ? { ...item, [column.key]: event.target.value } : item))} />
字段可见性和导出权限由当前用户的权限快照统一决定。
+} + +function renderRuntimeInput(field: Field) { + if (field.type === 'reference') return
+ if (field.type === 'date') return + if (field.type === 'select') return
+ return +} + +export default App diff --git a/code/fms/fms-mvp-demo/src/assets/hero.png b/code/fms/fms-mvp-demo/src/assets/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..02251f4b956c55af2d76fd0788124d7eee2b45eb GIT binary patch literal 13057 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 P00000NkvXXu0mjft=yBf literal 0 HcmV?d00001 diff --git a/code/fms/fms-mvp-demo/src/assets/react.svg b/code/fms/fms-mvp-demo/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/code/fms/fms-mvp-demo/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/code/fms/fms-mvp-demo/src/assets/vite.svg b/code/fms/fms-mvp-demo/src/assets/vite.svg new file mode 100644 index 00000000..5101b674 --- /dev/null +++ b/code/fms/fms-mvp-demo/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/code/fms/fms-mvp-demo/src/index.css b/code/fms/fms-mvp-demo/src/index.css new file mode 100644 index 00000000..23302ef4 --- /dev/null +++ b/code/fms/fms-mvp-demo/src/index.css @@ -0,0 +1,16 @@ +@import "tailwindcss"; + +:root { + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + color: #26313c; + background: #eef1f2; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; +} + +* { box-sizing: border-box; } +body { margin: 0; min-width: 1120px; } +button, input { font: inherit; } +button { cursor: pointer; } +#root { min-height: 100vh; } diff --git a/code/fms/fms-mvp-demo/src/main.tsx b/code/fms/fms-mvp-demo/src/main.tsx new file mode 100644 index 00000000..bef5202a --- /dev/null +++ b/code/fms/fms-mvp-demo/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/code/fms/fms-mvp-demo/tsconfig.app.json b/code/fms/fms-mvp-demo/tsconfig.app.json new file mode 100644 index 00000000..6830b6f7 --- /dev/null +++ b/code/fms/fms-mvp-demo/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM"], + "module": "esnext", + "types": ["vite/client"], + "allowArbitraryExtensions": true, + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/code/fms/fms-mvp-demo/tsconfig.json b/code/fms/fms-mvp-demo/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/code/fms/fms-mvp-demo/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/code/fms/fms-mvp-demo/tsconfig.node.json b/code/fms/fms-mvp-demo/tsconfig.node.json new file mode 100644 index 00000000..8455dcbc --- /dev/null +++ b/code/fms/fms-mvp-demo/tsconfig.node.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "module": "nodenext", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/code/fms/fms-mvp-demo/vite.config.ts b/code/fms/fms-mvp-demo/vite.config.ts new file mode 100644 index 00000000..c4069b77 --- /dev/null +++ b/code/fms/fms-mvp-demo/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react(), tailwindcss()], +})