20260904173136
This commit is contained in:
1 parent
0928437c67
commit
4e167df0f0
40 files changed
+3471
-2314
No files matched your search
@@ -0,0 +1,619 @@
|
||||
# 历史记录选择器配置设计讨论
|
||||
|
||||
## 一、这份设计要解决什么问题
|
||||
|
||||
业务页面经常需要从以前的单据中找一条或几条记录,然后带回当前页面。例如:
|
||||
|
||||
- 从以前的业务单中选择发货人;
|
||||
- 从以前的业务单中选择收货人或通知人;
|
||||
- 从以前的费用记录中选择费用,加入当前费用明细;
|
||||
- 从以前的业务单中选择箱子、货物等子表数据。
|
||||
|
||||
现在每个页面都自己写一个弹窗,自己拼查询 SQL,自己决定选中后怎么回填。这样做有几个明显问题:代码重复,字段改名后容易出错,同一套功能不能复用,业务人员也不能只通过配置调整规则。
|
||||
|
||||
这次希望把它做成一个通用能力:
|
||||
|
||||
```text
|
||||
业务动作配置 + 通用选择器 + 字段映射
|
||||
```
|
||||
|
||||
简单说就是:
|
||||
|
||||
- 通用选择器只负责“查数据、让用户选择”;
|
||||
- 动作配置负责“查哪个模块、固定过滤什么、选中后写到哪里”;
|
||||
- 业务页面只提供当前表单数据,并接收选择结果,最后仍由页面统一保存。
|
||||
|
||||
## 二、整体使用方式
|
||||
|
||||
在模块管理中增加“动作”页签。例如,在 `bs_business` 模块下配置:
|
||||
|
||||
```text
|
||||
选择历史发货人
|
||||
选择历史收货人
|
||||
选择历史通知人
|
||||
选择历史费用
|
||||
```
|
||||
|
||||
业务页面加载当前模块的动作配置,由页面自己决定按钮放在哪里、什么时候显示。点击业务页面上的按钮后,打开统一组件:
|
||||
|
||||
```text
|
||||
src/components/fms-record-picker/FmsRecordPickerModal.vue
|
||||
```
|
||||
|
||||
页面只需要告诉组件:
|
||||
|
||||
```text
|
||||
动作编码:select_history_shipper
|
||||
当前主表数据
|
||||
当前正在编辑的子表行(如果有)
|
||||
当前页面已经存在的目标数据
|
||||
```
|
||||
|
||||
页面不再传 `wheresql`,也不再自己拼接 SQL。
|
||||
|
||||
### 配置入口放在哪里
|
||||
|
||||
动作配置放在现有的**模块管理页面**中,不另外新建一个独立的配置页面。
|
||||
|
||||
使用方式是:
|
||||
|
||||
```text
|
||||
模块管理
|
||||
→ 选择目标模块:cw_business
|
||||
→ 动作
|
||||
├─ 选择历史费用
|
||||
├─ 选择历史发货人
|
||||
└─ 选择历史收货人
|
||||
```
|
||||
|
||||
这样做是因为动作属于目标业务模块:它描述的是“当前模块可以执行什么选择和回填操作”。在这里配置动作编码、来源模块、选择方式、是否允许重复以及字段映射等内容即可。
|
||||
|
||||
弹窗中让客户填写的查询条件,仍然配置在**来源模块**的“查询”页签中:
|
||||
|
||||
```text
|
||||
模块管理
|
||||
→ 选择来源模块:cw_business_history
|
||||
→ 查询
|
||||
├─ 业务单号
|
||||
├─ 发货人
|
||||
├─ 发生日期
|
||||
└─ 状态
|
||||
```
|
||||
|
||||
两者的职责要分开:
|
||||
|
||||
```text
|
||||
目标模块 → 动作页签 → 配置从哪里选择、选中后写到哪里
|
||||
来源模块 → 查询页签 → 配置弹窗中客户可以填写哪些筛选条件
|
||||
```
|
||||
|
||||
按钮放在主工具栏、子表工具栏还是字段旁边,由业务页面代码决定,不在模块管理中配置。这样可以复用模块管理现有结构,又不会把页面布局做成低代码配置。
|
||||
|
||||
## 三、动作配置写什么
|
||||
|
||||
### 1. 基本信息
|
||||
|
||||
```text
|
||||
动作编码:select_history_shipper
|
||||
动作名称:选择历史发货人
|
||||
动作类型:copy
|
||||
启用状态:启用
|
||||
排序号:10
|
||||
```
|
||||
|
||||
动作编码必须稳定。它用于程序识别、配置导入导出和不同环境之间迁移,不能随意修改。
|
||||
|
||||
### 2. 从哪里选择
|
||||
|
||||
```text
|
||||
来源模块:bs_business_history
|
||||
选择模式:单选 / 多选
|
||||
```
|
||||
|
||||
来源模块直接复用已有模块元数据:
|
||||
|
||||
- 列表默认显示 `s_field_view`;
|
||||
- 查询条件默认使用 `s_field_query`;
|
||||
- 字段标题、类型和格式使用 `s_field`。
|
||||
|
||||
动作可以另外指定展示列,但不复制一整套列表和查询配置。
|
||||
|
||||
## 四、单选、多选和重复选择是三件事
|
||||
|
||||
这三个概念不能混在一起:
|
||||
|
||||
```text
|
||||
选择模式 一次点击“确定”可以选几条
|
||||
重复选择策略 同一条来源记录以后能不能再加入
|
||||
去重标识 系统凭什么判断两次选择的是同一条记录
|
||||
```
|
||||
|
||||
动作配置建议增加以下内容:
|
||||
|
||||
```text
|
||||
选择模式:single / multiple
|
||||
重复选择策略:forbid / allow
|
||||
重复时处理:skip / warn / error
|
||||
去重字段:来源模块的稳定主键
|
||||
```
|
||||
|
||||
含义如下:
|
||||
|
||||
- `single`:一次只能选一条;
|
||||
- `multiple`:一次可以选多条;
|
||||
- `forbid`:已经加入过的来源记录,之后不能再次加入;
|
||||
- `allow`:允许再次打开选择器并再次加入同一条来源记录;
|
||||
- `skip`:重复记录直接跳过;
|
||||
- `warn`:提示用户后跳过;
|
||||
- `error`:发现重复就不允许确认。
|
||||
|
||||
默认建议:
|
||||
|
||||
```text
|
||||
pick / copy single + forbid + error
|
||||
append / replace multiple + forbid + warn
|
||||
```
|
||||
|
||||
只有业务明确允许重复时,才配置 `allow`。
|
||||
|
||||
要注意:`allow` 是指“以后可以再次选择”,不表示一次确认时可以把同一行选两遍。若要一条记录生成多行,应另加数量或复制次数功能,不放进选择器的基本选择逻辑里。
|
||||
|
||||
### 去重必须有稳定的来源 ID
|
||||
|
||||
不能用表格行号判断重复,也不能只比较名称、编码等容易重复的字段。应使用来源模块的稳定主键,例如 `b_id` 或 `subid`。
|
||||
|
||||
如果目标是子表,建议把来源主键保存到目标子表的隐藏字段中:
|
||||
|
||||
```text
|
||||
来源 subid → 目标 mx_source_subid
|
||||
```
|
||||
|
||||
这个隐藏字段不显示给用户,但用于:
|
||||
|
||||
- 再次打开选择器时排除已经选过的数据;
|
||||
- 防止分页或重复打开造成重复加入;
|
||||
- 追踪当前行来自哪条历史记录;
|
||||
- 后续撤销、同步和问题排查。
|
||||
|
||||
## 五、查询条件怎么设计
|
||||
|
||||
查询条件分两部分:
|
||||
|
||||
### 查询条件到底配置在哪里
|
||||
|
||||
客户在选择器中看到并填写的查询项,配置在**来源模块**的查询配置中,也就是现有的 `s_field_query`(模块管理中的“查询”页签)。
|
||||
|
||||
例如来源模块 `bs_business_history` 的“查询”页签配置了:
|
||||
|
||||
```text
|
||||
业务单号:包含
|
||||
发货人:包含
|
||||
发生日期:日期范围
|
||||
客户:下拉选择
|
||||
```
|
||||
|
||||
打开 `FmsRecordPickerModal` 时,组件读取这个来源模块的查询配置,自动显示这些查询控件。客户填写后点击“查询”,这些条件只影响本次弹窗的数据列表。
|
||||
|
||||
动作配置中的 `s_module_action_filter` 是另一回事。它保存的是动作必须带上的固定条件,客户看不到,也不能修改,例如“只能查当前客户的历史记录”。
|
||||
|
||||
可以这样理解:
|
||||
|
||||
```text
|
||||
s_field_query 客户可以填写的查询条件
|
||||
s_module_action_filter 系统强制追加的固定条件
|
||||
```
|
||||
|
||||
最终查询是两者合在一起:
|
||||
|
||||
```text
|
||||
来源模块查询条件 AND 动作固定条件
|
||||
```
|
||||
|
||||
第一版建议直接复用来源模块的 `s_field_query`,不再为每个动作单独复制一套查询字段。只有以后出现“同一个来源模块,不同动作需要显示完全不同的查询项”时,再增加动作专用查询字段配置。
|
||||
|
||||
### 1. 动作固定条件
|
||||
|
||||
这是动作本身规定的条件,用户不能修改。例如“选择历史费用”只能查同一个客户、同一种费用类型:
|
||||
|
||||
```text
|
||||
历史费用.客户 = 当前主表.客户
|
||||
历史费用.费用类型 = 当前操作的费用类型
|
||||
历史费用.来源单据 ID != 当前单据 ID
|
||||
```
|
||||
|
||||
### 2. 用户临时查询条件
|
||||
|
||||
这是用户在弹窗中输入的条件,例如费用名称、日期、状态、编码等。默认复用来源模块的 `s_field_query` 配置,使用和普通列表页面相同的查询控件。
|
||||
|
||||
例如来源模块配置了:
|
||||
|
||||
```text
|
||||
费用名称:包含
|
||||
发生日期:日期范围
|
||||
币种:下拉选择
|
||||
```
|
||||
|
||||
选择器就显示这些查询项。用户点击查询后,条件和动作固定条件一起生效。
|
||||
|
||||
### 3. 前端只传结构化条件,不传 SQL
|
||||
|
||||
前端请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"actionCode": "select_history_fee",
|
||||
"context": {
|
||||
"main": {
|
||||
"b_customer_id": 8,
|
||||
"b_id": 125
|
||||
},
|
||||
"detail": {
|
||||
"mx_type_id": "应收"
|
||||
}
|
||||
},
|
||||
"query": {
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"conditions": [
|
||||
{
|
||||
"fieldId": 610,
|
||||
"operator": "like",
|
||||
"value": "海运"
|
||||
},
|
||||
{
|
||||
"fieldId": 611,
|
||||
"operator": "gte",
|
||||
"value": "2026-01-01"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
前端和数据库配置中都不出现 SQL。后端根据字段 ID、操作符和字段类型生成参数化查询。
|
||||
|
||||
### 4. 动作条件的值从哪里来
|
||||
|
||||
建议支持以下值来源:
|
||||
|
||||
```text
|
||||
current.main.field 当前主表字段
|
||||
current.detail.field 当前子表字段
|
||||
constant 固定值
|
||||
user.id 当前用户
|
||||
user.orgId 当前机构
|
||||
today 当前日期
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
来源字段:mx_dw_id
|
||||
操作符:等于
|
||||
值来源:current.main.field
|
||||
当前字段:b_customer_id
|
||||
```
|
||||
|
||||
运行时就是“来源记录的 `mx_dw_id` 等于当前主表的 `b_customer_id`”。
|
||||
|
||||
### 5. 空值不能被悄悄忽略
|
||||
|
||||
如果当前客户还没有填写,不能因为值为空就直接删掉“客户相等”这个条件,否则可能查出所有客户的历史记录。
|
||||
|
||||
每个固定条件建议配置空值处理方式:
|
||||
|
||||
```text
|
||||
reject 不允许打开选择器,并提示先填写条件
|
||||
empty 返回空结果
|
||||
is_null 按 IS NULL 查询
|
||||
ignore 忽略这个条件(只对明确允许的场景使用)
|
||||
```
|
||||
|
||||
历史记录选择默认使用 `reject` 或 `empty`。
|
||||
|
||||
### 6. 条件组合
|
||||
|
||||
第一阶段只支持多个条件之间使用 `AND`,已经可以覆盖大部分历史记录场景:
|
||||
|
||||
```text
|
||||
客户相同 AND 当前单据不同 AND 费用类型相同
|
||||
```
|
||||
|
||||
以后确实需要时,再增加条件组和 `OR`。不要一开始就允许用户输入任意 SQL 或任意表达式。
|
||||
|
||||
## 六、字段映射怎么设计
|
||||
|
||||
选中的来源字段和目标字段不一定同名,所以要配置映射。例如选择历史发货人:
|
||||
|
||||
```text
|
||||
来源字段 目标字段
|
||||
b_shipper b_shippermemo
|
||||
b_shipper_contact b_shipper_contact
|
||||
b_shipper_phone b_shipper_phone
|
||||
b_customer_id b_customer_id
|
||||
```
|
||||
|
||||
选择历史费用并追加到费用子表:
|
||||
|
||||
```text
|
||||
来源 b_fee_code → 目标 b_fee_code
|
||||
来源 b_fee_name → 目标 b_fee_name
|
||||
来源 b_amount → 目标 b_amount
|
||||
来源 b_currency → 目标 b_currency
|
||||
来源 subid → 目标 mx_source_subid(隐藏去重字段)
|
||||
```
|
||||
|
||||
映射配置应从源模块和目标模块的字段元数据中选择字段,保存字段 ID,不让用户手工填写字段名。
|
||||
|
||||
每条映射还可以配置:
|
||||
|
||||
```text
|
||||
目标范围:主表 / 子表
|
||||
写入方式:覆盖 / 追加 / 合并
|
||||
转换方式:原值 / 数字 / 日期 / 拼接
|
||||
是否必需:是 / 否
|
||||
是否为去重字段:是 / 否
|
||||
```
|
||||
|
||||
如果目标是子表,运行时根据 `s_relation` 自动补齐父 ID、临时 ID 和排序号。目标字段的只读、计算、系统字段规则仍然有效,不能通过动作配置绕过。
|
||||
|
||||
## 七、重复选择的运行规则
|
||||
|
||||
系统需要同时考虑三种“已经选过”的数据:
|
||||
|
||||
```text
|
||||
本次弹窗已经选过的记录
|
||||
当前页面草稿中已经存在的记录
|
||||
数据库中已经保存的关联记录
|
||||
```
|
||||
|
||||
当动作配置为 `reselectPolicy = forbid` 时,这三类记录都应显示为“已选择”并禁用,而不是简单从表格中删除。这样用户知道它们存在,也知道为什么不能选。
|
||||
|
||||
确认时还要再次检查重复,不能只依赖前端界面:
|
||||
|
||||
```text
|
||||
用户点击确定
|
||||
↓
|
||||
系统根据去重字段重新检查
|
||||
↓
|
||||
按 skip / warn / error 处理
|
||||
↓
|
||||
生成最终的回填结果
|
||||
```
|
||||
|
||||
当配置为 `allow` 时,同一来源记录可以再次生成新的目标行;系统不自动去重,但仍应保留来源 ID,方便追踪。
|
||||
|
||||
## 八、动作类型
|
||||
|
||||
第一阶段只支持有限的动作类型:
|
||||
|
||||
```text
|
||||
pick 选择一条记录,返回关联值
|
||||
copy 选择一条记录,复制到当前主表
|
||||
append 选择多条记录,追加到目标子表
|
||||
replace 选择多条记录,替换目标子表
|
||||
```
|
||||
|
||||
不要一开始支持任意脚本或任意客户端函数。遇到特殊转换时,增加明确命名的转换类型或受控处理器。
|
||||
|
||||
## 九、建议的数据表
|
||||
|
||||
第一版使用规范化配置表,不把所有内容塞进一个 JSON 字段。
|
||||
|
||||
### `s_module_action`
|
||||
|
||||
保存动作基本信息:
|
||||
|
||||
```text
|
||||
b_id
|
||||
b_code 动作编码
|
||||
b_name 动作名称
|
||||
b_module_id 目标模块
|
||||
b_action_type pick/copy/append/replace
|
||||
b_source_module_id 来源模块
|
||||
b_selection_mode single/multiple
|
||||
b_reselect_policy forbid/allow
|
||||
b_duplicate_behavior skip/warn/error
|
||||
b_target_scope main/detail
|
||||
b_dedupe_field_id 来源去重字段
|
||||
b_canuse
|
||||
b_xh
|
||||
```
|
||||
|
||||
### `s_module_action_filter`
|
||||
|
||||
保存动作固定过滤条件:
|
||||
|
||||
```text
|
||||
b_id
|
||||
b_action_id
|
||||
b_source_field_id
|
||||
b_operator
|
||||
b_value_type
|
||||
b_value_field_id
|
||||
b_value_text
|
||||
b_empty_behavior reject/empty/is_null/ignore
|
||||
b_logic_group 第一阶段可固定为 0
|
||||
b_logic_operator 第一阶段固定为 AND
|
||||
b_xh
|
||||
```
|
||||
|
||||
### `s_module_action_column`
|
||||
|
||||
保存动作专用展示列覆盖:
|
||||
|
||||
```text
|
||||
b_id
|
||||
b_action_id
|
||||
b_source_field_id
|
||||
b_visible
|
||||
b_width
|
||||
b_xh
|
||||
```
|
||||
|
||||
没有动作列配置时,直接使用来源模块的 `s_field_view`。
|
||||
|
||||
### `s_module_action_mapping`
|
||||
|
||||
保存选择结果的字段映射:
|
||||
|
||||
```text
|
||||
b_id
|
||||
b_action_id
|
||||
b_source_field_id
|
||||
b_target_module_id
|
||||
b_target_field_id
|
||||
b_write_mode
|
||||
b_transform
|
||||
b_required
|
||||
b_is_dedupe_key
|
||||
b_xh
|
||||
```
|
||||
|
||||
## 十、运行时流程
|
||||
|
||||
```text
|
||||
业务页面加载目标模块动作配置
|
||||
↓
|
||||
业务页面上的按钮打开 FmsRecordPickerModal,并传入当前表单上下文
|
||||
↓
|
||||
读取来源模块的列表列和查询配置
|
||||
↓
|
||||
解析动作固定条件
|
||||
↓
|
||||
合并用户输入的查询条件
|
||||
↓
|
||||
按分页查询并显示“已选择/可选择”状态
|
||||
↓
|
||||
用户选择一条或多条记录
|
||||
↓
|
||||
检查重复选择和必填映射
|
||||
↓
|
||||
根据 mapping 生成主表 patch 或子表新行
|
||||
↓
|
||||
回写当前页面草稿
|
||||
↓
|
||||
由页面统一 handleSave()
|
||||
```
|
||||
|
||||
选择器不负责保存数据库。映射结果先进入当前页面的草稿、diff 和校验流程,和手工新增的数据一样,最后统一保存。
|
||||
|
||||
建议选择确认事件返回记录或 patch,不返回 SQL:
|
||||
|
||||
```json
|
||||
{
|
||||
"actionCode": "select_history_fee",
|
||||
"rows": [
|
||||
{
|
||||
"subid": "fee-001",
|
||||
"mx_fee_code": "F001",
|
||||
"mx_fee_name": "海运费",
|
||||
"mx_amount": 1200
|
||||
}
|
||||
],
|
||||
"patch": {
|
||||
"detailRows": [
|
||||
{
|
||||
"mx_source_subid": "fee-001",
|
||||
"mx_fee_code": "F001",
|
||||
"mx_amount": 1200
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
页面通常只需要把 `patch` 合并到草稿;`rows` 可用于提示、审计或特殊业务处理。
|
||||
|
||||
## 十一、组件和后端各自负责什么
|
||||
|
||||
### `FmsRecordPickerModal`
|
||||
|
||||
- 显示弹窗、查询区、表格和分页;
|
||||
- 使用来源模块元数据渲染查询项和展示列;
|
||||
- 显示哪些记录已选择;
|
||||
- 管理本次操作中的选中状态;
|
||||
- 返回用户选择的记录或动作执行结果。
|
||||
|
||||
### 动作执行器(建议放在后端或独立服务层)
|
||||
|
||||
- 读取并校验动作配置;
|
||||
- 校验用户对来源模块的访问权限;
|
||||
- 解析当前主表、子表、用户和日期等值来源;
|
||||
- 校验字段 ID、目标模块和操作符;
|
||||
- 生成参数化查询;
|
||||
- 执行去重检查和字段映射。
|
||||
|
||||
### 业务页面
|
||||
|
||||
- 提供当前页面上下文;
|
||||
- 接收 patch 并更新草稿;
|
||||
- 执行页面自己的计算和校验;
|
||||
- 通过统一的保存流程提交。
|
||||
|
||||
## 十二、安全和校验要求
|
||||
|
||||
- 来源模块必须通过当前用户权限校验;
|
||||
- 后端不能信任前端传来的任意字段、任意 SQL 或任意脚本;
|
||||
- 过滤条件必须使用字段 ID、操作符和值的结构化表达;
|
||||
- 后端按字段类型限制可用操作符;
|
||||
- 映射目标必须属于目标模块或目标子模块;
|
||||
- `readOnly`、`computed`、`system` 字段的写入规则继续生效;
|
||||
- 来源字段删除或停用后,动作配置显示为无效并禁止启用;
|
||||
- 配置导入导出使用动作编码和关系编码,运行时再解析内部 ID;
|
||||
- 数据库配置中不保存可执行的任意 JavaScript;
|
||||
- 确认时重新检查权限、字段合法性和重复记录,不能只依赖前端禁用状态。
|
||||
|
||||
## 十三、建议的实施顺序
|
||||
|
||||
### 第一阶段:先做通用选择器
|
||||
|
||||
先用内存中的声明式配置验证:
|
||||
|
||||
- 来源模块加载;
|
||||
- 复用默认列表列和查询字段;
|
||||
- 单选、多选;
|
||||
- 动作固定过滤条件;
|
||||
- 当前主表字段取值;
|
||||
- 主表字段映射;
|
||||
- 子表追加;
|
||||
- `forbid` 和 `allow` 两种重复选择策略;
|
||||
- 分页和重新打开弹窗后的已选状态。
|
||||
|
||||
### 第二阶段:增加数据库配置
|
||||
|
||||
增加以下表和模块管理中的“动作”页签:
|
||||
|
||||
- `s_module_action`;
|
||||
- `s_module_action_filter`;
|
||||
- `s_module_action_column`;
|
||||
- `s_module_action_mapping`。
|
||||
|
||||
同时增加动作配置的启用校验和无效字段提示。
|
||||
|
||||
### 第三阶段:迁移业务操作
|
||||
|
||||
优先实现:
|
||||
|
||||
1. 选择历史发货人;
|
||||
2. 选择历史收货人;
|
||||
3. 选择历史通知人;
|
||||
4. 选择历史费用;
|
||||
5. 选择箱子或其他业务子表记录。
|
||||
|
||||
迁移完成后,删除页面中按业务名称编写的专用选择弹窗和字段匹配代码。
|
||||
|
||||
## 十四、仍需确定的事项
|
||||
|
||||
- 动作按钮是否只复用模块权限,还是需要单独的动作权限;
|
||||
- `append` 追加时重复记录默认提示后跳过,还是直接阻止确认;
|
||||
- `replace` 替换时是否需要保留被替换行的来源关系;
|
||||
- 是否允许业务配置自定义去重字段组合,而不只是单个主键;
|
||||
- 特殊字段转换是否需要一个受控的转换器注册表;
|
||||
- 是否需要动作配置的预览和测试执行功能;
|
||||
- 来源模块是视图时,如何配置稳定主键;
|
||||
- 业务页面是否允许少量自定义动作插槽。
|
||||
|
||||
## 十五、一句话总结
|
||||
|
||||
把“历史记录选择”当成一种可配置的业务动作:配置决定查什么、怎么筛、能不能重复选、选中后写到哪里;通用选择器负责展示和选择;业务页面只接收结果并统一保存。这样既能复用现有模块元数据,也能避免页面代码里出现 SQL 拼接和隐含的字段对应关系。
|
||||
Reference in new issue
Block a user