From 97fe2ff306d6c3f3e31fc34a869f8bdc6e3683f8 Mon Sep 17 00:00:00 2001 From: ZhangAo Date: Mon, 12 Oct 2026 17:32:50 +0800 Subject: [PATCH] u --- code/g3soft-erp/docs/后端架构设计.md | 117 + code/g3soft-erp/docs/核心表结构设计.md | 195 ++ .../web/src/layouts/DefaultLayout.vue | 11 +- .../web/src/layouts/LayoutTabbar.vue | 2 +- .../components/PreferencesDrawer.vue | 49 +- code/g3soft-erp/web/src/preferences/config.ts | 5 +- code/g3soft-erp/web/src/preferences/index.ts | 1 + .../web/src/preferences/style-presets.ts | 53 + code/g3soft-erp/web/src/preferences/types.ts | 19 +- .../src/preferences/update-css-variables.ts | 25 +- code/g3soft-erp/web/src/stores/app.ts | 35 +- code/g3soft-erp/web/src/styles/index.scss | 2 + .../web/src/styles/skin-shadcn.scss | 174 ++ code/g3soft-libs/.changeset/attachment.md | 18 - code/g3soft-libs/docs/.vitepress/config.mts | 2 +- .../docs/content/ui/data/attachment.md | 234 -- .../g3soft-libs/docs/content/ui/data/table.md | 183 ++ .../g3soft-libs/docs/content/ui/form/input.md | 6 +- .../docs/content/ui/form/rich-text.md | 252 ++- .../docs/content/ui/overlay/dropdown.md | 17 + .../docs/examples/attachment/basic.vue | 80 - .../docs/examples/attachment/modal.vue | 58 - .../docs/examples/attachment/readonly.vue | 20 - .../docs/examples/attachment/select.vue | 61 - .../docs/examples/rich-text/tools.vue | 39 + .../docs/examples/table/actions.vue | 47 + .../g3soft-libs/docs/examples/table/basic.vue | 25 + .../docs/examples/table/editable.vue | 50 + .../docs/examples/table/resizable.vue | 65 + .../docs/examples/table/selection.vue | 45 + .../docs/examples/table/sorting.vue | 25 + .../docs/examples/table/virtual.vue | 33 + code/g3soft-libs/docs/table-design.md | 285 +++ .../packages/ui/e2e/attachment.spec.ts | 56 - .../packages/ui/e2e/rich-text-table.spec.ts | 143 +- .../packages/ui/e2e/rich-text-toolbar.spec.ts | 484 +++- .../packages/ui/e2e/rich-text-tools.spec.ts | 441 ++++ .../g3soft-libs/packages/ui/e2e/table.spec.ts | 162 ++ code/g3soft-libs/packages/ui/package.json | 4 +- .../packages/ui/playground/main.ts | 113 +- .../ui/src/attachment/Attachment.test.ts | 273 --- .../packages/ui/src/attachment/Attachment.vue | 393 ---- .../ui/src/attachment/AttachmentCard.vue | 376 ---- .../ui/src/attachment/AttachmentList.vue | 848 ------- .../ui/src/attachment/AttachmentPreview.vue | 241 -- .../packages/ui/src/attachment/file.test.ts | 90 - .../packages/ui/src/attachment/file.ts | 116 - .../packages/ui/src/attachment/icons.ts | 26 - .../packages/ui/src/attachment/index.ts | 29 - .../packages/ui/src/attachment/types.ts | 204 -- code/g3soft-libs/packages/ui/src/index.ts | 6 +- .../packages/ui/src/input/Input.test.ts | 107 + .../packages/ui/src/input/Input.vue | 68 +- .../packages/ui/src/locale/index.ts | 84 - .../packages/ui/src/popover/Popover.vue | 2 +- .../packages/ui/src/rich-text/LinkMenu.vue | 308 +++ .../ui/src/rich-text/ListTypeIcon.vue | 124 ++ .../ui/src/rich-text/PreviewDialog.vue | 250 +++ .../packages/ui/src/rich-text/RichText.vue | 244 ++- .../ui/src/rich-text/SourceCodeDialog.vue | 263 +++ .../packages/ui/src/rich-text/TableMenu.vue | 28 +- .../packages/ui/src/rich-text/Toolbar.vue | 1948 +++++++++++++++-- .../ui/src/rich-text/extensions.test.ts | 87 + .../packages/ui/src/rich-text/extensions.ts | 201 +- .../packages/ui/src/rich-text/format.ts | 237 ++ .../packages/ui/src/rich-text/index.ts | 11 + .../packages/ui/src/rich-text/print.ts | 177 ++ .../packages/ui/src/rich-text/types.ts | 45 + .../packages/ui/src/table/G3Table.test.ts | 368 ++++ .../packages/ui/src/table/G3Table.vue | 868 ++++++++ .../packages/ui/src/table/index.ts | 19 + .../ui/src/table/style/_overrides.scss | 123 ++ .../packages/ui/src/table/style/index.scss | 22 + .../packages/ui/src/table/types.ts | 200 ++ .../packages/ui/src/table/utils/actions.ts | 248 +++ .../ui/src/table/utils/column.test.ts | 184 ++ .../packages/ui/src/table/utils/column.ts | 144 ++ .../ui/src/table/utils/editable.test.ts | 406 ++++ .../packages/ui/src/table/utils/editable.ts | 312 +++ .../ui/src/table/utils/selection.test.ts | 237 ++ .../packages/ui/src/table/utils/selection.ts | 174 ++ code/g3soft-libs/packages/ui/vite.config.ts | 7 + code/g3soft-libs/pnpm-lock.yaml | 11 + 83 files changed, 10083 insertions(+), 3662 deletions(-) create mode 100644 code/g3soft-erp/docs/后端架构设计.md create mode 100644 code/g3soft-erp/docs/核心表结构设计.md create mode 100644 code/g3soft-erp/web/src/preferences/style-presets.ts create mode 100644 code/g3soft-erp/web/src/styles/skin-shadcn.scss delete mode 100644 code/g3soft-libs/.changeset/attachment.md delete mode 100644 code/g3soft-libs/docs/content/ui/data/attachment.md create mode 100644 code/g3soft-libs/docs/content/ui/data/table.md delete mode 100644 code/g3soft-libs/docs/examples/attachment/basic.vue delete mode 100644 code/g3soft-libs/docs/examples/attachment/modal.vue delete mode 100644 code/g3soft-libs/docs/examples/attachment/readonly.vue delete mode 100644 code/g3soft-libs/docs/examples/attachment/select.vue create mode 100644 code/g3soft-libs/docs/examples/rich-text/tools.vue create mode 100644 code/g3soft-libs/docs/examples/table/actions.vue create mode 100644 code/g3soft-libs/docs/examples/table/basic.vue create mode 100644 code/g3soft-libs/docs/examples/table/editable.vue create mode 100644 code/g3soft-libs/docs/examples/table/resizable.vue create mode 100644 code/g3soft-libs/docs/examples/table/selection.vue create mode 100644 code/g3soft-libs/docs/examples/table/sorting.vue create mode 100644 code/g3soft-libs/docs/examples/table/virtual.vue create mode 100644 code/g3soft-libs/docs/table-design.md delete mode 100644 code/g3soft-libs/packages/ui/e2e/attachment.spec.ts create mode 100644 code/g3soft-libs/packages/ui/e2e/rich-text-tools.spec.ts create mode 100644 code/g3soft-libs/packages/ui/e2e/table.spec.ts delete mode 100644 code/g3soft-libs/packages/ui/src/attachment/Attachment.test.ts delete mode 100644 code/g3soft-libs/packages/ui/src/attachment/Attachment.vue delete mode 100644 code/g3soft-libs/packages/ui/src/attachment/AttachmentCard.vue delete mode 100644 code/g3soft-libs/packages/ui/src/attachment/AttachmentList.vue delete mode 100644 code/g3soft-libs/packages/ui/src/attachment/AttachmentPreview.vue delete mode 100644 code/g3soft-libs/packages/ui/src/attachment/file.test.ts delete mode 100644 code/g3soft-libs/packages/ui/src/attachment/file.ts delete mode 100644 code/g3soft-libs/packages/ui/src/attachment/icons.ts delete mode 100644 code/g3soft-libs/packages/ui/src/attachment/index.ts delete mode 100644 code/g3soft-libs/packages/ui/src/attachment/types.ts create mode 100644 code/g3soft-libs/packages/ui/src/rich-text/LinkMenu.vue create mode 100644 code/g3soft-libs/packages/ui/src/rich-text/ListTypeIcon.vue create mode 100644 code/g3soft-libs/packages/ui/src/rich-text/PreviewDialog.vue create mode 100644 code/g3soft-libs/packages/ui/src/rich-text/SourceCodeDialog.vue create mode 100644 code/g3soft-libs/packages/ui/src/rich-text/extensions.test.ts create mode 100644 code/g3soft-libs/packages/ui/src/rich-text/format.ts create mode 100644 code/g3soft-libs/packages/ui/src/rich-text/print.ts create mode 100644 code/g3soft-libs/packages/ui/src/table/G3Table.test.ts create mode 100644 code/g3soft-libs/packages/ui/src/table/G3Table.vue create mode 100644 code/g3soft-libs/packages/ui/src/table/index.ts create mode 100644 code/g3soft-libs/packages/ui/src/table/style/_overrides.scss create mode 100644 code/g3soft-libs/packages/ui/src/table/style/index.scss create mode 100644 code/g3soft-libs/packages/ui/src/table/types.ts create mode 100644 code/g3soft-libs/packages/ui/src/table/utils/actions.ts create mode 100644 code/g3soft-libs/packages/ui/src/table/utils/column.test.ts create mode 100644 code/g3soft-libs/packages/ui/src/table/utils/column.ts create mode 100644 code/g3soft-libs/packages/ui/src/table/utils/editable.test.ts create mode 100644 code/g3soft-libs/packages/ui/src/table/utils/editable.ts create mode 100644 code/g3soft-libs/packages/ui/src/table/utils/selection.test.ts create mode 100644 code/g3soft-libs/packages/ui/src/table/utils/selection.ts diff --git a/code/g3soft-erp/docs/后端架构设计.md b/code/g3soft-erp/docs/后端架构设计.md new file mode 100644 index 00000000..be9201fd --- /dev/null +++ b/code/g3soft-erp/docs/后端架构设计.md @@ -0,0 +1,117 @@ +# FMS 后端架构设计 + +> 文档版本:V1.1(精简方案) +> 编制日期:2026-10-12 +> 状态:待评审 +> 适用范围:沿用现有 FMS API 与 SQL Server 模块配置方式 +> 配套文档:[核心表结构设计.md](./核心表结构设计.md) + +## 1. 设计目标 + +本文以已经使用的 FMS 数据库和 `fms-api` 接口为基础,只补充模块配置的版本历史能力。目标是让新 ERP 后端和其他 IT 团队能够沿用熟悉的表结构、SQL 和通用数据接口,而不是重新建设一套元数据运行时。 + +- 保留 `s_module`、`s_field`、`s_module_schema`、`s_relation` 的现有职责和字段约定。 +- 元数据描述常用模块、字段和页面配置;SQL 是可选的正式扩展方式,遇到配置不便表达的查询时使用。 +- 沿用通用数据 API、机构上下文和 SQL Server 数据源路由;不为每个业务模块新增专用 CRUD API。 +- 只新增轻量模块版本历史;不引入模板引擎、编译发布管线、运行时快照目录或复杂审批流程。 + +## 2. 总体结构 + +```text +模块管理界面 + ├─ s_module:模块树、表/视图、SQL、保存目标 + ├─ s_field:模块字段及业务类型 + ├─ s_module_schema:列表 / 表单 / 查询 JSON + └─ s_relation:模块数据关系 + │ + ├─ 普通数据请求 → 现有 /data 通用接口 → SQL Server + ├─ 自定义查询 SQL → 只读 SQL 校验 → SQL Server + └─ 显式版本保存/恢复 → s_module_version +``` + +运行时继续读取现有模块配置表。`s_module_version` 只做历史记录与恢复,不成为普通业务请求的读取入口。 + +## 3. 模块配置如何驱动请求 + +### 3.1 模块与页面配置 + +- 模块编码 `s_module.b_id` 是稳定业务编码,由配置方提供,不使用运行时雪花 ID。 +- `b_module_type` 使用现有 `module`、`data`、`virtual` 约定:分组、标准数据对象和虚拟业务对象。 +- `b_view_table`、`b_save_table`、`b_key_field` 描述普通查询和保存目标;数据库对象默认使用 `dbo`。 +- `s_field` 声明模块字段编码、名称和业务类型;具体 SQL 类型通过数据库结构探测获得。 +- `s_module_schema` 的 `view`、`edit`、`query` JSON 控制列表、表单和查询界面;字段语义不在多个 JSON 中重复定义。 +- `s_relation` 声明模块间字段关系;`b_parent_id` 只用于模块树,不表示业务数据关系。 + +### 3.2 SQL 兜底 + +- 常规简单查询使用配置的 `b_view_table` 和通用查询接口。 +- 关联、聚合或其他配置表达起来不合适的查询,可在模块配置中使用 `b_query_sql`;它是模块配置数据,不要求在后端代码中为每家公司登记 SQL 编码。 +- 对于按机构隔离的数据库,机构数据源路由继续使用现有 `OrgContext` / `OrgDataSourceManager`。各机构可在自己的配置库维护不同模块 SQL,不新增租户配置层。 +- 运行查询 SQL 必须经过现有只读校验路径;只允许查询语句,不允许用查询 SQL 执行新增、修改、删除或 DDL。写入走现有保存/删除接口或明确的业务服务。 +- `b_order_sql` 是模块默认排序;排序标识符按现有 `DbUtils` 校验规则处理。查询参数和值不得通过未校验的字符串拼接进入 SQL。 + +## 4. 沿用的通用数据 API + +已有的 `/data/*` 契约继续作为数据访问入口,不因模块配置版本化而更换: + +| 接口 | 用途 | +|---|---| +| `POST /data/loaddata` | 按表/视图、条件、排序和字段列表查询 | +| `POST /data/page` | 分页查询 | +| `POST /data/loaddatabysql` | 执行只读 `SELECT` / `WITH` 查询 | +| `POST /data/saveobjt` | 在一个事务中保存多表新增、修改 | +| `POST /data/delete` | 按模块或受控目标删除 | +| `POST /data/describe` | 探测表或视图列结构 | +| `POST /data/nextid`、`/data/nextcode` | 取雪花 ID 或模块业务编码 | + +`loaddata` / `page` 使用表或视图名、结构化筛选和排序参数;`loaddatabysql` 用于复杂只读查询。已有 `DbUtils` 负责标识符验证、SQL 只读检查和 SQL 执行。通用保存服务依据真实表结构生成参数化写入,不通过 `b_query_sql` 写业务数据。 + +新接入系统应沿用这些请求/响应格式和既有身份、机构、权限上下文;模块管理页面负责读取模块配置并组织请求,不需要另造一套模块专属 CRUD API。 + +## 5. 模块配置版本历史 + +### 5.1 行为边界 + +- 版本功能只增加 `s_module_version` 一张表;快照 JSON 包含模块及其字段、界面、关系和相关模块级扩展配置。 +- 显式创建版本时,后端从现有配置表读取当前状态,分配递增版本号并保存不可变快照。 +- 查看历史只读取版本表;恢复历史版本时,后端校验后在一个事务中恢复现有模块配置表。 +- 恢复成功后业务运行仍读取原有 `s_module` 等表;不切换运行时版本指针,也不改变 `/data/*` 接口。 +- 恢复内容可另存为新的版本记录;禁止覆盖历史快照。 + +因此这项能力是**配置备份、版本查看与恢复**,不是草稿/发布隔离。配置当前仍按现有保存机制生效;若之后出现明确的灰度发布需求,再单独扩展。 + +### 5.2 最小后端改动 + +增加一个模块版本服务,负责: + +1. 创建版本:在数据库事务中读取模块及子配置,验证并序列化快照。 +2. 查询版本:按模块编码列出版本号、说明、创建人和时间。 +3. 恢复版本:校验快照结构和关联目标,在同一事务内更新模块配置,并记录恢复结果。 + +版本操作可通过模块管理 API 暴露;业务数据 API、模块表主键、普通保存流程和 SQL 查询流程不变。版本号由后端分配,不依赖客户端提供。 + +## 6. SQL 与数据安全约定 + +- 客户端不能以任意表名或列名绕过后端标识符验证。 +- 普通查询的筛选值以参数传递;字段、排序方向等 SQL 标识由校验后的配置控制。 +- `b_query_sql` 和 `/data/loaddatabysql` 仅用于只读查询,并使用现有 `DbUtils.validateReadOnlySql()` 校验。 +- SQL 配置及执行需经过现有机构、用户和数据范围权限上下文;不能因为使用 SQL 扩展绕过权限校验。 +- 写操作使用 `/data/saveobjt`、`/data/delete` 或已有业务服务,并纳入现有事务和审计规则。 +- 版本恢复同样执行配置权限校验、引用校验和事务处理。 + +## 7. 不在本次范围内 + +- 不重构现有 FMS `DataController` 接口,也不改变 SQL Server、JDBC 或机构数据源路由方式。 +- 不增加 `s_template` / `s_template_version`、`s_module_snapshot`、用户偏好新表或权限/工作流新表。 +- 不强制运行时从 JSON 快照读取模块定义,不引入 `ModuleDefinition` 编译缓存层。 +- 不把 SQL 移到后端固定代码目录;保留模块配置中的可选 SQL 兜底。 +- 不重新定义业务表、菜单、用户、权限、工作流、文件和审计子系统。 +- 不把模块版本管理等同于数据库 migration;业务表结构仍通过已有 SQL migration 管理。 + +## 8. 接入检查清单 + +1. 确认机构/数据库连接按现有路由建立,业务对象位于 `dbo`。 +2. 普通表格和表单配置 `s_module`、`s_field`、`s_module_schema`;复杂查询按需配置 `b_query_sql`。 +3. 用 `/data/describe` 检查实际表/视图字段,用 `s_field` 和视图 JSON 保持编码一致。 +4. 查询、保存、删除分别调用现有通用接口;不从客户端接收未经验证的 DML SQL。 +5. 重要配置变更后显式保存模块版本;恢复时通过版本服务执行,不手工覆盖版本历史表。 diff --git a/code/g3soft-erp/docs/核心表结构设计.md b/code/g3soft-erp/docs/核心表结构设计.md new file mode 100644 index 00000000..b0ea9f95 --- /dev/null +++ b/code/g3soft-erp/docs/核心表结构设计.md @@ -0,0 +1,195 @@ +# FMS 后端核心表结构设计 + +> 文档版本:V1.1(精简方案) +> 编制日期:2026-10-12 +> 状态:待评审 +> 数据库:SQL Server +> 配套文档:[后端架构设计.md](./后端架构设计.md) + +## 1. 范围与原则 + +本文沿用现有 FMS 的模块表结构和 SQL 驱动方式,只说明模块运行需要的核心元数据表,并增加一张轻量的模块版本历史表。业务表、用户/权限、菜单、多语言、工作流等既有表和接口不在本文重定义。 + +- 业务数据仍保存在 SQL Server 的真实表和视图中。 +- `s_module`、`s_field`、`s_module_schema`、`s_relation` 描述模块及其界面、字段和关联;不采用 EAV 存业务记录。 +- 常规功能用模块配置;配置不够或 SQL 更直接时,可通过模块的 `b_query_sql` 使用 SQL 查询。 +- 运行时继续读取现有模块配置表;版本表仅保存可查看、可恢复的历史配置,不引入新的运行时快照目录。 +- 保持现有命名和数据类型;不增加模板体系、草稿/发布状态机、额外快照表或元数据编译器。 +- 与现有 FMS 数据库约定一致:模块关联完整性、JSON 和类型校验由应用层负责,不依赖外键、`CHECK` 约束或触发器。 + +## 2. 核心表清单 + +| 表 | 职责 | 是否新增 | +|---|---|---| +| `s_module` | 模块树、查询来源、保存目标和默认行为 | 否,沿用现有设计 | +| `s_field` | 模块字段编码、名称和业务类型 | 否,沿用现有设计 | +| `s_module_schema` | 列表、表单、查询等界面 JSON 配置 | 否,沿用现有设计 | +| `s_relation` | 模块之间的字段关系 | 否,沿用现有设计 | +| `s_module_version` | 手动保存的模块配置历史及恢复来源 | 是,唯一新增表 | + +现有 `s_user_module_pref`、`s_autocode`、`s_delete_rule` 等辅助配置表继续按原设计使用;权限、菜单和工作流表也保持原状。它们不因本方案改名或迁移。 + +## 3. 模块 `s_module` + +```sql +create table dbo.s_module ( + b_id varchar(50) not null primary key, -- 模块编码,稳定业务键 + b_parent_id varchar(50) null, -- 父模块编码,根节点为空 + b_depth int not null default 0, -- 模块树层级,根节点为 0 + b_path varchar(1000) not null, -- 模块树路径,由应用层维护 + b_name nvarchar(200) not null, -- 模块名称 + b_i18n varchar(150) null, -- 多语言资源编码 + b_module_type varchar(20) not null, -- module / data / virtual + b_view_table varchar(128) null, -- 默认查询表或视图名 + b_save_table varchar(128) null, -- 默认保存目标表名 + b_key_field varchar(50) null, -- 数据主键字段 + b_scope_field varchar(50) null, -- 数据范围字段,如记录归属人字段 + b_order_sql varchar(500) null, -- 默认排序规则 + b_query_sql nvarchar(max) null, -- 可选查询 SQL 或 SQL 模板 + b_config_json nvarchar(max) null, -- 低频模块扩展配置 + b_canuse tinyint not null default 1, -- 是否启用:1 启用,0 停用 + b_xh int not null default 0, -- 同级模块显示顺序 + b_bz nvarchar(2000) null, -- 备注 + b_updated_at datetime2 null -- 模块配置最后保存时间 +); + +create index ix_s_module_parent + on dbo.s_module (b_parent_id, b_canuse, b_depth, b_xh, b_id); + +create index ix_s_module_path + on dbo.s_module (b_path, b_canuse, b_id); + +create index ix_s_module_type + on dbo.s_module (b_module_type, b_canuse, b_xh, b_id); + +create index ix_s_module_view_table + on dbo.s_module (b_view_table, b_canuse, b_id); + +create index ix_s_module_save_table + on dbo.s_module (b_save_table, b_canuse, b_id); +``` + +### 3.1 字段约定 + +- `module` 是模块分组;`data` 是标准数据模块;`virtual` 是由查询 SQL 或业务实现提供数据的虚拟模块。 +- `b_view_table` 是普通查询的数据源;配置 `b_query_sql` 时,模块查询使用该 SQL;两者都没有时,由虚拟模块的业务实现提供数据。 +- `b_save_table` 和 `b_key_field` 描述标准保存目标;业务写入仍走现有保存接口或业务服务。 +- `b_query_sql` 是可选的 SQL 兜底配置,不是后端代码中的固定 SQL 编码。可按现有机构/数据库配置维护不同 SQL。它用于查询,不替代标准保存流程。 +- `b_order_sql` 保存该模块的默认排序;值按现有 SQL 标识符和排序校验规则处理。 +- `b_parent_id` 是模块树的权威关系;`b_depth`、`b_path` 是派生字段,新增或移动节点时由应用层在事务内维护。 + +## 4. 字段 `s_field` + +```sql +create table dbo.s_field ( + b_module_id varchar(50) not null, -- 所属 data / virtual 模块编码 + b_field varchar(50) not null, -- 模块字段编码或查询结果别名 + b_name nvarchar(200) not null, -- 字段名称 + b_i18n varchar(150) null, -- 多语言资源编码 + b_type varchar(30) not null, -- 字段业务类型 + b_canuse tinyint not null default 1, -- 是否启用:1 启用,0 停用 + b_xh int not null default 0, -- 字段默认顺序 + primary key (b_module_id, b_field) +); + +create index ix_s_field_module_order + on dbo.s_field (b_module_id, b_canuse, b_xh, b_field); +``` + +`b_type` 表示业务层字段类型;SQL Server 实际类型通过表/视图结构探测获得,不在 `s_field` 重复保存。字段分组、控件、必填、只读、显示顺序等界面行为放在 `s_module_schema` 的 JSON 中。 + +## 5. 界面配置 `s_module_schema` + +```sql +create table dbo.s_module_schema ( + b_module_id varchar(50) not null, -- 所属 data / virtual 模块编码 + b_schema_type varchar(20) not null, -- view / edit / query + b_schema_json nvarchar(max) not null, -- 对应界面的完整 JSON 配置 + b_canuse tinyint not null default 1, -- 是否启用:1 启用,0 停用 + b_updated_by varchar(50) null, -- 最后修改人编码 + b_updated_at datetime2 null, -- 最后修改时间 + primary key (b_module_id, b_schema_type) +); + +create index ix_s_module_schema_type + on dbo.s_module_schema (b_module_id, b_schema_type, b_canuse); +``` + +每个模块按需配置 `view`(列表)、`edit`(表单)、`query`(查询条件)。每种类型当前一份 JSON;JSON 结构版本由 JSON 顶层的 `schemaVersion` 管理。字段编码引用 `s_field.b_field`。个人偏好仍使用现有 `s_user_module_pref`,不混入系统默认配置。 + +## 6. 模块关系 `s_relation` + +```sql +create table dbo.s_relation ( + b_source_module_id varchar(50) not null, -- 源模块编码 + b_source_field varchar(50) not null, -- 源字段编码 + b_target_module_id varchar(50) not null, -- 目标模块编码 + b_target_field varchar(50) not null, -- 目标字段编码 + b_relation_type varchar(20) not null, -- one_to_one / one_to_many / many_to_one + b_on_delete varchar(20) not null default 'restrict', -- restrict / cascade / none + b_canuse tinyint not null default 1, -- 是否启用:1 启用,0 停用 + b_xh int not null default 0, -- 关系显示顺序 + primary key ( + b_source_module_id, + b_source_field, + b_target_module_id, + b_target_field + ) +); + +create index ix_s_relation_source + on dbo.s_relation (b_source_module_id, b_canuse, b_xh, b_source_field, b_target_module_id); + +create index ix_s_relation_target + on dbo.s_relation (b_target_module_id, b_canuse, b_source_module_id); +``` + +模块树只表达业务分组;主子表、引用和多对多关系通过 `s_relation` 或中间表模块表达。关系目标及删除策略由应用层校验,不建立通用业务外键。 + +## 7. 模块版本历史 `s_module_version` + +该表是本方案唯一新增表。它保存模块配置的历史副本,用于查看和恢复;不引入模板版本、独立快照表、发布状态机或运行时版本目录。 + +```sql +create table dbo.s_module_version ( + b_module_id varchar(50) not null, -- 所属模块编码 + b_version_no int not null, -- 模块内递增版本号 + b_snapshot_json nvarchar(max) not null, -- 该版本的完整模块配置快照 + b_change_note nvarchar(1000) null, -- 版本说明 + b_created_by varchar(50) null, -- 创建版本的用户编码 + b_created_at datetime2 not null default sysdatetime(), -- 创建版本时间 + primary key (b_module_id, b_version_no) +); +``` + +快照 JSON 使用独立的 `snapshotVersion` 描述快照格式。它至少包含模块行、字段行、界面配置和相关关系;如需完整恢复模块行为,也包含该模块对应的自动编码、删除规则等配置。示意: + +```json +{ + "snapshotVersion": 1, + "module": {}, + "fields": [], + "schemas": [], + "relations": [], + "autocodes": [], + "deleteRules": [] +} +``` + +### 7.1 保存与恢复规则 + +1. 管理员显式创建版本时,后端在同一事务中读取当前模块配置并写入 `s_module_version`;版本记录创建后不修改。 +2. `b_version_no` 按模块递增;并发创建时由后端事务锁定模块并分配下一个号码。 +3. 恢复版本时先校验快照格式和模块引用,再在一个事务内将快照恢复到现有配置表。 +4. 恢复操作成功后,当前运行行为仍由 `s_module` 等现有配置表决定;为保留操作历史,可将恢复结果另存为一个新版本,不覆盖旧版本。 +5. 版本快照只保存模块配置,不保存业务数据、用户个人偏好、权限授权、菜单、翻译内容或数据库建表脚本。 + +这套版本历史不提供独立草稿或“线上发布版本”隔离:模块配置仍按现有方式生效。若未来确实需要草稿/发布隔离,再单独评估,不作为本次改造前提。 + +## 8. 既有表与校验边界 + +- `s_user_module_pref`:沿用个人视图偏好;它是用户级显示覆盖,不属于模块版本快照。 +- `s_autocode`、`s_delete_rule`:继续按现有规则工作;模块版本需要完整回滚时,其模块配置一并纳入快照。 +- `s_menu`、权限、多语言、日志、工作流及业务表:沿用各自现有设计,不在本文件重建。 +- 模块/字段/关系存在性、`b_module_type`、`b_schema_type` 和 JSON 内容由后端校验;不新增外键、`CHECK` 或触发器。 +- 业务数据库结构变更仍通过 SQL migration 管理;模块版本不替代数据库 migration。 diff --git a/code/g3soft-erp/web/src/layouts/DefaultLayout.vue b/code/g3soft-erp/web/src/layouts/DefaultLayout.vue index 9e4665cf..f38130f5 100644 --- a/code/g3soft-erp/web/src/layouts/DefaultLayout.vue +++ b/code/g3soft-erp/web/src/layouts/DefaultLayout.vue @@ -54,6 +54,15 @@ const showHeader = computed(() => pref.value.header.enable && !isContentFullscre /** 页签栏(局部全屏时保留) */ const showTabbar = computed(() => pref.value.tabbar.enable) +/** + * 页面缓存(KeepAlive)的 include 名单,受 tabbar.keepAlive 偏好控制。 + * + * 关闭时传空数组而不是 undefined:KeepAlive 的 include 语义是「白名单」—— + * 空数组没有任何 name 能匹配上 → 一律不缓存(已缓存的实例会被其内部的 + * include watch 清掉);而 undefined 表示「不过滤」→ 反而会缓存所有页面。 + */ +const keepAliveInclude = computed(() => (pref.value.tabbar.keepAlive ? appStore.cachedKeys : [])) + /** 内容区样式:内边距走偏好 */ const contentStyle = computed(() => ({ padding: `${pref.value.app.contentPadding}px`, @@ -180,7 +189,7 @@ watch( :name="pref.transition.enable ? pref.transition.name : undefined" mode="out-in" > - + diff --git a/code/g3soft-erp/web/src/layouts/LayoutTabbar.vue b/code/g3soft-erp/web/src/layouts/LayoutTabbar.vue index 1bd1b312..374ef462 100644 --- a/code/g3soft-erp/web/src/layouts/LayoutTabbar.vue +++ b/code/g3soft-erp/web/src/layouts/LayoutTabbar.vue @@ -214,7 +214,7 @@ onMounted(async () => { /* 首次挂载也要定位激活页签 —— 修复「刷新后页签不居中」。 * * 为什么不能只靠下面的 watch: - * 页签是持久化的(g3erp:app:v2),刷新时 pinia 在组件挂载【之前】就把 tabs 还原好了, + * 页签是持久化的(g3erp:app),刷新时 pinia 在组件挂载【之前】就把 tabs 还原好了, * 而 route.fullPath 在路由解析完成时也已确定。 * 于是 watch 的两个依赖在挂载这一刻【already 是初始值】, * 没有 immediate 的 watch 根本不会触发 → revealActiveTab 一次都没跑 diff --git a/code/g3soft-erp/web/src/preferences/components/PreferencesDrawer.vue b/code/g3soft-erp/web/src/preferences/components/PreferencesDrawer.vue index 78388768..b012d798 100644 --- a/code/g3soft-erp/web/src/preferences/components/PreferencesDrawer.vue +++ b/code/g3soft-erp/web/src/preferences/components/PreferencesDrawer.vue @@ -10,6 +10,7 @@ import PrefBlock from './PrefBlock.vue' import SelectItem from './SelectItem.vue' import SwitchItem from './SwitchItem.vue' import ToggleItem from './ToggleItem.vue' +import { resolvePrimaryColor } from '../style-presets' import type { Preferences } from '../types' /** @@ -43,6 +44,12 @@ const tabs = [ // 外观页签 // --------------------------------------------------------------------------- +/** 界面风格(换皮预设):default 保持现状,shadcn 现代极简 */ +const styleOptions = [ + { label: '默认', value: 'default' }, + { label: '现代极简', value: 'shadcn' }, +] + /** 主题模式卡片(对照 vben theme.vue 的 THEME_PRESET) */ const themePreset = [ { icon: Sun, name: 'light' as const, label: '浅色' }, @@ -159,9 +166,9 @@ function setThemeMode(name: 'light' | 'dark' | 'auto'): void { store.update({ theme: { mode: name } }) } -/** 主题色切换(直接写三段式色值) */ +/** 主题色切换(直接写三段式色值);标记为用户显式选择,风格切换不再覆盖它 */ function setColorPrimary(value: string): void { - store.update({ theme: { colorPrimary: value } }) + store.update({ theme: { colorPrimary: value, colorPrimaryCustom: true } }) } /** @@ -235,9 +242,13 @@ function hexToHslTriplet(hex: string): string { return `${Math.round(h)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%` } -/** 当前主题色对应的 hex,喂给取色器。已是 hex/rgb/hsl() 的直接用。 */ +/** + * 当前主题色对应的 hex,喂给取色器。 + * 未显式选过色时(colorPrimaryCustom=false)解析为「当前风格的内置默认色」, + * 这样 shadcn 风格下取色器显示的是中性黑,而不是被忽略的 colorPrimary。 + */ const customColorHex = computed(() => { - const value = pref.value.theme.colorPrimary + const value = resolvePrimaryColor(pref.value.theme, pref.value.app.style, store.isDark) if (value.startsWith('#') || value.startsWith('rgb') || value.startsWith('hsl')) return value return hslTripletToHex(value) ?? '#165dff' }) @@ -276,6 +287,19 @@ const hasDiff = computed(() => true)