20260911173133

This commit is contained in:
oneao committed 2026-09-11 17:31:34 +08:00
1 parent 3838ed74b1
commit f3ad727f1c
60 files changed
+4971 -6621

No files matched your search

+16 -16
View File
@@ -10,19 +10,19 @@
4. **修改范围最小化** — 只修改完成当前需求所必需的文件;发现无关的脏改动、临时文件或历史问题时,不擅自回滚或顺手重构。
5. **当前系统按内部 ERP 处理** — 默认使用场景是已登录的内部用户和受控网络。当前需求阶段不额外引入复杂的安全防护层、SQL 注入专项改造或前后端重复校验;除非用户明确提出,不因安全假设新增接口、中间层或配置系统。查询条件可以信任前端传入,后端重点完成查询、分页和数据格式转换。
5. **当前系统按内部 ERP 处理** — 默认使用场景是已登录的内部用户和受控网络。当前需求阶段不额外引入复杂的安全防护层或前后端重复校验;除非用户明确提出,不因安全假设新增接口、中间层或配置系统。查询条件和查询 SQL 可以由调用方传入,后端重点完成查询、分页、数据格式转换以及只执行查询语句的边界控制。
6. **SQL 驱动优先** — 本系统是 SQL 驱动的内部 ERP。能通过 SQL 完成的查询、关联、过滤、排序、分页、聚合、权限过滤和数据范围过滤,优先在 SQL 层完成,不在业务代码中加载大量数据后再处理。
7. **优先使用配置驱动的动态 SQL** — 模块、字段、查询条件、排序、字段权限和数据范围等存在于表结构中的规则,应由通用查询逻辑读取配置并动态生成 SQL。动态部分只允许来自受控的模块元数据、字段定义、操作符白名单和权限配置;业务值使用参数传递,不把任意前端字符串直接拼接为 SQL。
7. **优先使用配置驱动的动态 SQL** — 模块、字段、查询条件、排序、字段权限和数据范围等存在于表结构中的规则,应由通用查询逻辑读取配置并动态生成 SQL。对于需要快速适配复杂场景的查询,允许调用方或模块配置直接传入完整 SQL;该入口只允许执行 `SELECT` 查询,不限制表、字段、关联、函数或条件的具体写法。结构化条件和参数化值仍可按调用场景使用。
8. **动态配置不等于任意 SQL** — 配置可以决定查询哪些表、字段和条件,但必须经过现有元数据和业务规则校验。不得为了追求灵活,把整段未审查的 SQL 保存到配置表并直接执行;确需使用原生 SQL 时,应限制在明确的模块或服务边界内。
8. **原生 SQL 仅限查询** — `b_query_sql`、查询 SQL 扩展和调用方传入的 SQL 可以是任意合法的 `SELECT` 语句,包括子查询、CTE、关联、聚合、排序和组合条件。禁止执行 `INSERT`、`UPDATE`、`DELETE`、DDL、存储过程调用及其他非查询语句;保存和业务写入仍走模块保存逻辑或业务处理器。
9. **固定 SQL 只用于稳定且明确的特殊逻辑** — 当 SQL 由固定业务规则组成,或动态生成会明显降低可读性、性能和可维护性时,可以使用固定 SQL。固定 SQL 应保持通用,不为单个页面或单个用户复制一套接口和查询。
10. **权限过滤以后端 SQL 为准** — 前端可以根据权限隐藏菜单、按钮和字段,但最终的模块操作权限、字段可见/可查询/可导出权限以及数据范围,必须在后端查询或写入 SQL 中落实。用户个性化配置只能调整布局,不能放宽权限。
SQL 驱动的实现应优先复用通用查询、保存和权限计算逻辑。模块之间的差异通过 `s_module`、`s_field`、`s_field_view`、`s_field_edit`、`s_field_query`、权限表和数据范围表配置;新增模块或字段时,优先增加配置数据,不复制一套模块专用 SQL。动态 SQL 只动态替换受控的标识符和条件结构,参数值仍单独传递。
SQL 驱动的实现应优先复用通用查询、保存和权限计算逻辑。模块之间的差异通过 `s_module`、`s_field`、`s_module_schema`、权限表和数据范围表配置;新增模块或字段时,优先增加配置数据,不复制一套模块专用 SQL。查询既可以由模块元数据动态组合,也可以使用模块配置或调用方传入的完整 `SELECT` 语句;不得把非查询 SQL 交给执行器。
## 前端代码风格
@@ -60,8 +60,8 @@ SQL 驱动的实现应优先复用通用查询、保存和权限计算逻辑。
// 后端业务数据和原始基线:snake_case
const maindata = ref(null)
const maindata_org = ref(null)
const list_config = ref([])
const list_config_org = ref([])
const view_schema = ref(null)
const view_schema_org = ref(null)
```
2. **方法名统一使用 camelCase** — 采用职责明确的动词开头,例如 `loadModuleConfig()`、`handleSave()`、`buildSaveData()`、`clearModuleData()`。不使用 `loadMod()`、`dealData()`、`doIt()` 等含义不明确的缩写或泛化名称。
@@ -94,7 +94,7 @@ SQL 驱动的实现应优先复用通用查询、保存和权限计算逻辑。
### 查询列表页面设计
1. **查询条件由页面和模块配置共同决定** — 页面需要传入的业务固定条件(例如当前客户、当前单据 ID、费用类型)由业务代码组装;客户在列表或选择器中可以填写的条件,使用模块已有的查询配置(如 `s_field_query`)。
1. **查询条件由页面和模块配置共同决定** — 页面需要传入的业务固定条件(例如当前客户、当前单据 ID、费用类型)由业务代码组装;客户在列表或选择器中可以填写的条件,使用模块已有的查询配置(`s_module_schema.b_schema_type = 'query'`)。复杂查询可以直接使用模块的 `b_query_sql` 或调用方传入的完整 `SELECT`。
2. **不为每个业务动作编写特殊查询接口** — 优先使用现有通用列表查询接口,或新增一个通用的记录查询接口。接口接收模块、查询条件、分页和排序参数,不按“历史费用”“历史发货人”等场景分别新增接口。
@@ -126,7 +126,7 @@ SQL 驱动的实现应优先复用通用查询、保存和权限计算逻辑。
### 表单数据与变更检测
1. **数据与基线成对命名 `xxx` / `xxx_org`** — 编辑页面加载完成后,每份数据保留"可编辑副本 `xxx`"与"原始基线 `xxx_org`"两个 ref:`xxx` 是页面修改作用的对象,`xxx_org` 是从数据库加载、不可直接编辑的基线(保存 diff、放弃修改还原用)。主对象用 `maindata / maindata_org`,关联数据用 `fields / fields_org`、`list_config / list_config_org` 等。**禁止用 `drafts` / `originals` 两个聚合对象,即使页面只有一个主对象也不例外。** 不要为每个字段单独维护一份旧值。
1. **数据与基线成对命名 `xxx` / `xxx_org`** — 编辑页面加载完成后,每份数据保留"可编辑副本 `xxx`"与"原始基线 `xxx_org`"两个 ref:`xxx` 是页面修改作用的对象,`xxx_org` 是从数据库加载、不可直接编辑的基线(保存 diff、放弃修改还原用)。主对象用 `maindata / maindata_org`,关联数据用 `fields / fields_org`、`view_schema / view_schema_org` 等。**禁止用 `drafts` / `originals` 两个聚合对象,即使页面只有一个主对象也不例外。** 不要为每个字段单独维护一份旧值。
2. **保存使用 diff** — 保存前将当前值与 `_org` 基线做差异对比,只提交新增、修改和删除的数据;没有变化时不调用保存接口。
@@ -176,7 +176,7 @@ async function handleSave() {
1. **编排层不写中转回调** — 页面组件维护每份数据,各面板用 `defineModel` 直接读写属于自己的那份;不在父级写 `onModuleUpdate(section, data)` 这类"子面板 emit → 父级按 section 写回"的中转函数,也不在各面板各自重复加载或维护同一份状态。
2. **数据成对命名,不用两个聚合对象** — 每份数据一对 ref:`xxx` 为当前值(面板绑定),`xxx_org` 为该表从数据库加载的原始基线(保存 diff、放弃修改还原用),小写下划线命名。禁止用 `drafts` / `originals` 两个聚合对象包住全部子数据——**即使页面只有一个主对象,也一律 `maindata / maindata_org` 成对命名**。主表行(如 `maindata`)是单对象,其余是行数组(`fields`、`list_config`、`edit_config`、`query_config`、`autocode`、`i18n`)。
2. **数据成对命名,不用两个聚合对象** — 每份数据一对 ref:`xxx` 为当前值(面板绑定),`xxx_org` 为该表从数据库加载的原始基线(保存 diff、放弃修改还原用),小写下划线命名。禁止用 `drafts` / `originals` 两个聚合对象包住全部子数据——**即使页面只有一个主对象,也一律 `maindata / maindata_org` 成对命名**。主表行(如 `maindata`)是单对象;字段使用 `fields`,模块界面配置按 `view`、`edit`、`query` 三类维护,分别对应 `s_module_schema` 的配置行;自动编码和多语言资源按各自模块数据维护。
3. **加载就绪以主数据为判据** — 以主表行(如 `maindata`)是否为空决定配置区是否进入「加载中 / 暂无」占位,模板统一 `v-if="maindata"`。整批清空收敛到一个 `clearModuleData()`(或 `clearXxx()`)辅助函数,供切换对象、取消选择、加载失败复用。
@@ -184,11 +184,11 @@ async function handleSave() {
5. **表格编辑就地 patch + 换引用** — 表格组件对同一引用数组的 push 不敏感,行编辑采用"就地改行对象、提交时换新数组引用"触发渲染,不逐行深拷贝后再整表 emit。
6. **子表新增行用本地负数临时 id** — 新增行用 `createTempId()` 占位(`-1`、`-2`…),不逐个请求后端取号;保存时批量 `nextIdApi(N)` 一次换取 N 个真实雪花 id。主表(`s_module`)新建模块仍可单独取号。
6. **模块配置不使用雪花临时 ID** — 模块管理中的 `s_module` 使用业务编码,`s_field`、`s_module_schema`、`s_autocode`、`s_relation` 等配置表使用业务键或联合主键;新增配置行直接使用业务字段组成的临时唯一键,不调用 `nextIdApi`,保存时也不做“临时 ID → 雪花 ID”转换。其他确实使用 `bigint` 雪花主键的业务子表,才适用本条之外的通用临时 ID 方案。
7. **`_` / `v_` 前缀是内部派生字段** — 行内这类字段(参与勾选、受管标记等)不参与 diff、脏判断,也不随保存提交;提交前统一剥离。剥离会产生副本,会切断请求行与草稿行的引用共享,因此**必须在完成「临时 id → 真实主键 + 跨表外键重映射」之后**再剥离。
7. **`_` / `v_` 前缀是内部派生字段** — 行内这类字段(参与勾选、受管标记等)不参与 diff、脏判断,也不随保存提交;提交前统一剥离。对于使用临时 ID 的业务表,先完成真实主键和跨表外键重映射,再剥离;模块配置表没有这一步。
8. **`buildSaveData` 可为 async** — diff 出请求后,直接在函数内完成临时 id 换真实主键与跨表外键重映射再返回,无需再单独包一层 `prepareXxx()`;没有任何变化时返回 `null`。其余流程仍按「表单数据与变更检测」的 `handleSave → beforeSave → buildSaveData → 保存 → afterSave` 主流程。
8. **`buildSaveData` 可为 async** — diff 出请求后,直接在函数内完成必要的主键和跨表外键处理再返回,无需再单独包一层 `prepareXxx()`;模块配置按业务键和联合主键直接组装,不做雪花 ID 换号。没有任何变化时返回 `null`。其余流程仍按「表单数据与变更检测」的 `handleSave → beforeSave → buildSaveData → 保存 → afterSave` 主流程。
9. **受管多语言不做实时响应** — 模块名/字段名/分组标题等自动生成的多语言行(受管键)只做两件事:加载模块后 reconcile 一次、保存前 reconcile 一次再 diff;不写 watch + 防抖去实时补齐。人工翻译不被 reconcile 覆盖。
@@ -202,14 +202,14 @@ async function handleSave() {
## 多语言(i18n)约定
1. **module 目录下的页面不做多语言翻译** — 模块管理、菜单管理、多语言类型等内部管理页面仅面向 IT 人员,界面文案固定使用中文,不接入 s_i18n 翻译体系、不提供多语言编辑入口。
2. 面向客户使用的业务页面才需要做多语言翻译(界面文案、s_i18n 键管理、多语言编辑入口)。
1. **区分管理界面与业务资源** — module 目录下的模块管理、菜单管理、多语言类型等页面自身面向 IT 人员,界面文案固定使用中文,不把这些管理界面文案接入业务多语言体系;但模块名称、字段名称和 JSON 布局节点等面向客户的业务资源仍可维护 `s_i18n` 资源键和翻译。
2. 面向客户使用的业务页面需要做多语言翻译;模块管理页可以维护这些业务资源的 key 和翻译数据,但不要求管理页自身多语言化。
3. **区分界面语言与业务语言** — UI 当前语言、UI 翻译目录、`s_i18n_type` 业务语言清单统一由 `stores/i18n.js` 管理;页面不得自行维护第二份语言状态。
3. **区分界面语言、语言清单和资源键** — UI 当前语言、UI 翻译目录以及 `s_i18n_type` 语言区域清单统一由 `stores/i18n.js` 管理;`s_i18n.b_locale` 对应语言区域,资源来源通过 `b_key` 的命名空间表达(如 `module.`、`menu.`、`field.`、`group.`),不再依赖 `b_source` / `b_source_id` 字段,页面不得自行维护第二份语言状态。
4. **多语言 key 规则就近维护** — 只服务于模块管理的 key 生成和同步逻辑放在 `module-management/utils.js`;不得放入全局 `src/utils`。单页面使用一次的校验、透视合并或字段转换逻辑直接写在页面中。
5. **默认语言必须按标记判断** — 使用 `b_default` / `default` 字段确定默认语言,不得依赖语言数组第一项;语言列表为空时不得静默伪造默认语言掩盖接口或配置错误。
5. **默认语言必须按显式配置判断** — 使用 `s_i18n_type.b_default` 确定默认语言,不得依赖语言数组第一项;有效默认语言只能有一个,语言列表为空时不得静默伪造默认语言掩盖接口或配置错误。
### 变更验收