Skip to content

Commit 1abc074

Browse files
xlorneclaude
andcommitted
feat: optimize title expression UI design
- Add GroovyVariableMapping as common mapping class - Update UI display to use ${label} format - Add variable picker with label and value display - Update syntax conversion rules between UI and Groovy - Add dynamic form field mapping from FlowFromMeta Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5b92276 commit 1abc074

2 files changed

Lines changed: 648 additions & 566 deletions

File tree

designs/title-expression-ui/README.md

Lines changed: 205 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
### 设计原则
1414
- **所有标题本质都是Groovy脚本**,只是提供不同的配置方式
15-
- **普通模式**:通过UI交互选择变量插入
15+
- **普通模式**:通过UI交互选择变量插入,显示中文格式
1616
- **高级模式**:直接编写Groovy代码
1717
- 高级模式下无法解析回可视化,预览区域显示提示
1818
- 通过固定注释区分标题配置
@@ -63,55 +63,90 @@ public class TitleGroovyRequest {
6363
}
6464
```
6565

66-
### 2.2 可用变量列表
66+
---
67+
68+
## 3. 变量映射类设计(核心)
69+
70+
### 3.1 映射类结构
71+
72+
创建 `GroovyVariableMapping` 映射类,统一处理变量显示和转换(作为公共对象使用):
73+
74+
```java
75+
public class GroovyVariableMapping {
76+
private String label; // 中文显示名称:如"当前操作人"
77+
private String value; // 变量展示名:如"request.operatorName"
78+
private String expression; // 用户界面表达式:如"request.getOperatorName()"
79+
private String tag; // 分组,例如表单、流程、操作人等
80+
private int order; // 展示的数据顺序
81+
}
82+
```
83+
84+
### 3.2 预定义变量映射
85+
86+
| label(中文显示) | value(变量名) | expression(Groovy语法) | tag(分组) |
87+
|-----------------|---------------|----------------------|----------|
88+
| 当前操作人 | request.operatorName | request.getOperatorName() | 操作人相关 |
89+
| 当前操作人ID | request.operatorId | request.getOperatorId() | 操作人相关 |
90+
| 是否管理员 | request.isFlowManager | request.getIsFlowManager() | 操作人相关 |
91+
| 流程创建人 | request.creatorName | request.getCreatorName() | 操作人相关 |
92+
| 流程标题 | request.workflowTitle | request.getWorkflowTitle() | 流程相关 |
93+
| 流程编码 | request.workflowCode | request.getWorkflowCode() | 流程相关 |
94+
| 当前节点 | request.nodeName | request.getNodeName() | 流程相关 |
95+
| 节点类型 | request.nodeType | request.getNodeType() | 流程相关 |
96+
| 流程编号 | request.workCode | request.getWorkCode() | 流程编号 |
6797

68-
| 变量路径 | 说明 | 示例 |
69-
|---------|------|------|
70-
| `request.operatorName` | 当前操作人姓名 | "张三" |
71-
| `request.operatorId` | 当前操作人ID | 1001 |
72-
| `request.isFlowManager` | 是否流程管理员 | true/false |
73-
| `request.workflowTitle` | 流程标题 | "请假审批" |
74-
| `request.workflowCode` | 流程编码 | "LEAVE_001" |
75-
| `request.nodeName` | 当前节点名称 | "部门审批" |
76-
| `request.nodeType` | 当前节点类型 | "approval" |
77-
| `request.creatorName` | 流程创建人 | "李四" |
78-
| `request.formData("字段名")` | 表单字段值 | 任意 |
79-
| `request.workCode` | 流程编号 | "WORK_20260226" |
98+
### 3.3 表单字段映射
99+
100+
表单字段需要动态从 `FlowFromMeta.fields` 获取:
101+
102+
```java
103+
// 从FlowFromMeta动态构建
104+
for (FlowFormFieldMeta field : flowFromMeta.getFields()) {
105+
GroovyVariableMapping mapping = new GroovyVariableMapping();
106+
mapping.setLabel(field.getName()); // 如"请假天数"
107+
mapping.setValue("request.formData(\"" + field.getCode() + "\")"); // 如request.formData("days")
108+
mapping.setExpression("request.getFormData(\"" + field.getCode() + "\")"); // 如request.getFormData("days")
109+
mapping.setTag("表单字段(当前表单)");
110+
mapping.setOrder(100 + index);
111+
}
112+
```
80113

81114
---
82115

83-
## 3. 语法规范
116+
## 4. 语法规范
84117

85-
### 3.1 用户界面显示语法(预览/输入)
118+
### 4.1 用户界面显示语法
86119

87-
使用 `${}` 包裹变量
120+
在界面显示和编辑时,使用 `${中文标签}` 格式
88121

89122
```
90-
${request.operatorName}
91-
${request.formData("amount")}
92-
${request.workflowTitle}
123+
${当前操作人}
124+
${请假天数}
125+
${流程标题}
93126
```
94127

95128
**完整示例:**
96129
```
97-
你好,${request.operatorName},有一笔 ${request.formData("amount")} 元的审批
130+
你好,${当前操作人},有一笔${请假天数}元的审批
98131
```
99132

100-
### 3.2 实际Groovy脚本语法
133+
### 4.2 实际Groovy脚本语法
101134

102135
```groovy
103-
return "你好," + request.getOperatorName() + ",有一笔 " + request.getFormData("amount") + " 元的审批"
136+
return "你好," + request.getOperatorName() + ",有一笔 " + request.getFormData("days") + " 元的审批"
104137
```
105138

106-
### 3.3 语法转换规则
139+
### 4.3 语法转换规则
107140

108-
| 界面语法 | Groovy语法 |
109-
|---------|-----------|
110-
| `${request.operatorName}` | `request.getOperatorName()` |
111-
| `${request.formData("key")}` | `request.getFormData("key")` |
112-
| `${request.workflowTitle}` | `request.getWorkflowTitle()` |
141+
通过 GroovyVariableMapping 映射类进行转换:
113142

114-
### 3.4 通过注释区分标题配置
143+
| label(界面显示) | value(变量名) | expression(Groovy语法) |
144+
|-----------------|---------------|----------------------|
145+
| 当前操作人 | request.operatorName | request.getOperatorName() |
146+
| 请假天数 | request.formData("days") | request.getFormData("days") |
147+
| 流程标题 | request.workflowTitle | request.getWorkflowTitle() |
148+
149+
### 4.4 通过注释区分标题配置
115150

116151
在Groovy脚本中添加固定注释来标识这是标题配置:
117152

@@ -124,47 +159,47 @@ return "审批:" + request.getOperatorName()
124159

125160
---
126161

127-
## 4. 界面设计
162+
## 5. 界面设计
128163

129-
### 4.1 界面清单
164+
### 5.1 界面清单
130165

131166
| 界面 | 用途 | 位置 |
132167
|------|------|------|
133168
| 节点属性面板 | 展示标题内容 + 编辑按钮 | 节点属性面板中 |
134169
| 标题配置面板 | 点击编辑后的配置界面 | 弹框 |
135-
| 变量选择器 | 插入request变量 | 弹框 |
170+
| 变量选择器 | 插入变量(使用映射类数据) | 弹框 |
136171
| 高级配置 | Groovy代码编辑 | 弹框 |
137172

138-
### 4.2 节点属性面板
173+
### 5.2 节点属性面板
139174

140175
```
141176
┌─────────────────────────────────────┐
142177
│ 节点标题 │
143178
├─────────────────────────────────────┤
144-
当前:部门经理审批 [编辑] │
179+
${当前操作人}的审批 [编辑] │
145180
└─────────────────────────────────────┘
146181
```
147182

148-
- 展示当前配置的标题内容
183+
- 展示当前配置的标题内容(使用 `${label}` 格式)
149184
- 点击"编辑"按钮进入标题配置面板
150185

151-
### 4.3 标题配置面板
186+
### 5.3 标题配置面板
152187

153188
#### 普通模式
154189
```
155190
┌─────────────────────────────────────────────────────────┐
156191
│ 标题配置 │
157192
├─────────────────────────────────────────────────────────┤
158193
│ 预览 │
159-
│ 你好,${request.operatorName},有一笔${request.formData("amount")}元的审批
194+
│ 你好,${当前操作人},有一笔${请假天数}元的审批
160195
│ ───────────────────────────────────────────────────── │
161196
│ [插入变量] [高级配置] │
162197
│ ───────────────────────────────────────────────────── │
163198
│ 内容 │
164199
│ ┌─────────────────────────────────────────────────────┐│
165-
│ │ 你好,${request.operatorName},有一笔${request.formData("amount")}元的审批
200+
│ │ 你好,${当前操作人},有一笔${请假天数}元的审批 ││
166201
│ └─────────────────────────────────────────────────────┘│
167-
使用 ${request.xxx} 插入变量,点击上方按钮插入
202+
点击上方按钮插入变量,或直接输入文字内容
168203
│ ───────────────────────────────────────────────────── │
169204
│ [取消] [确定] │
170205
└─────────────────────────────────────────────────────────┘
@@ -195,7 +230,7 @@ return "审批:" + request.getOperatorName()
195230
- 高级模式下"高级配置"按钮变为"重置"按钮
196231
- 点击"重置"可清除高级配置,回到普通模式
197232

198-
### 4.4 变量选择器(弹框)
233+
### 5.4 变量选择器(弹框)
199234

200235
```
201236
┌─────────────────────────────┐
@@ -205,46 +240,153 @@ return "审批:" + request.getOperatorName()
205240
│ │ 🔍 搜索变量... ││
206241
│ └─────────────────────────┘│
207242
│ ▶ 操作人相关 │
208-
│ ● request.operatorName - 当前操作人姓名
209-
│ ● request.operatorId - 当前操作人ID
210-
│ ● request.creatorName - 流程创建人
243+
│ ● 当前操作人姓名 request.operatorName
244+
│ ● 当前操作人ID request.operatorId
245+
│ ● 是否管理员 request.isFlowManager
246+
│ ● 流程创建人 request.creatorName
211247
212248
│ ▶ 流程相关 │
213-
│ ● request.workflowTitle - 流程标题
214-
│ ● request.workflowCode - 流程编码
215-
│ ● request.nodeName - 当前节点名称
216-
│ ● request.nodeType - 当前节点类型
249+
│ ● 流程标题 request.workflowTitle
250+
│ ● 流程编码 request.workflowCode
251+
│ ● 当前节点 request.nodeName
252+
│ ● 节点类型 request.nodeType
217253
218-
│ ▶ 表单函数 │
219-
│ ● request.formData("key") - 获取表单字段值
254+
│ ▶ 表单字段(当前表单) │
255+
│ ● 请假天数 request.formData("days")
256+
│ ● 请假原因 request.formData("reason")
257+
│ ● 审批金额 request.formData("amount")
258+
│ ● 更多字段... 动态加载当前表单字段
220259
221260
│ ▶ 流程编号 │
222-
│ ● request.workCode - 流程编号
261+
│ ● 流程编号 request.workCode
223262
└─────────────────────────────┘
224263
```
225264

265+
**显示规则:**
266+
- 左侧:label(中文显示名称),如"当前操作人姓名"
267+
- 右侧:value(变量名),如"request.operatorName"
268+
226269
---
227270

228-
## 5. 实现方案
271+
## 6. 实现方案
229272

230-
### 5.1 涉及文件
273+
### 6.1 涉及文件
231274

232275
**后端(Java):**
233-
- 创建 `TitleRequest` 对象封装所需数据
276+
- 创建 `TitleGroovyRequest` 对象封装所需数据
277+
- 创建 `TitleVariableMapping` 映射类(用于变量选择和语法转换)
278+
279+
**前端(TypeScript):**
280+
- 修改 `node-title.tsx` - 节点标题组件
281+
- 新增 `VariablePicker.tsx` - 变量选择器组件
282+
283+
### 6.2 核心逻辑
234284

235-
**前端(frontend):**
236-
- `packages/flow-pc/flow-pc-design/src/components/design-editor/node-components/strategy/node-title.tsx` - 修改现有组件
237-
- 新增 `VariablePicker.tsx` - 变量选择器
285+
#### 6.2.1 变量映射服务
238286

239-
### 5.2 核心逻辑
287+
```typescript
288+
// 前端:GroovyVariableMapping 公共对象
289+
interface GroovyVariableMapping {
290+
label: string; // 中文显示名称:如"当前操作人"
291+
value: string; // 变量展示名:如"request.operatorName"
292+
expression: string; // 用户界面表达式:如"request.getOperatorName()"
293+
tag: string; // 分组:如"操作人相关"
294+
order: number; // 排序
295+
}
296+
297+
// 预定义变量映射
298+
const VARIABLE_MAPPINGS: GroovyVariableMapping[] = [
299+
{ label: '当前操作人', value: 'request.operatorName', expression: 'request.getOperatorName()', tag: '操作人相关', order: 1 },
300+
{ label: '当前操作人ID', value: 'request.operatorId', expression: 'request.getOperatorId()', tag: '操作人相关', order: 2 },
301+
// ...其他预定义变量
302+
];
303+
304+
// 获取表单字段映射(动态)
305+
function getFormFieldMappings(fields: FlowFormFieldMeta[]): GroovyVariableMapping[] {
306+
return fields.map((field, index) => ({
307+
label: field.name,
308+
value: 'request.formData("' + field.code + '")',
309+
expression: 'request.getFormData("' + field.code + '")',
310+
tag: '表单字段(当前表单)',
311+
order: 100 + index
312+
}));
313+
}
314+
```
315+
316+
#### 6.2.2 界面显示 → Groovy语法转换
317+
318+
```typescript
319+
function toGroovySyntax(content: string, mappings: GroovyVariableMapping[]): string {
320+
let result = content;
321+
for (const mapping of mappings) {
322+
// 将界面显示的label替换为Groovy expression
323+
result = result.split(mapping.label).join('${' + mapping.expression + '}');
324+
}
325+
// 添加 @TITLE 注释,并转换为Groovy字符串拼接
326+
return '// @TITLE\nreturn "' + result + '"';
327+
}
328+
```
329+
330+
#### 6.2.3 Groovy语法 → 界面显示转换
331+
332+
```typescript
333+
function toLabelExpression(groovyCode: string, mappings: GroovyVariableMapping[]): string {
334+
let result = groovyCode;
335+
// 移除 return " 和 "
336+
result = result.replace(/^.*return\s+"([^"]*)".*$/, '$1');
337+
// 替换 + 拼接为直接量
338+
result = result.replace(/"\s*\+\s*"/g, '');
339+
// 将 ${expression} 替换为 label
340+
for (const mapping of mappings) {
341+
result = result.replaceAll('${' + mapping.expression + '}', mapping.label);
342+
}
343+
return result;
344+
}
345+
```
240346

241-
1. **界面显示**:使用 `${request.xxx}` 语法显示和输入
242-
2. **保存转换**:将 `${request.xxx}` 转换为 `request.getXxx()``request.getFormData("xxx")`
243-
3. **加载识别**:通过 `// @TITLE` 注释识别标题配置
244-
4. **预览**:直接替换 `${request.xxx}` 为实际值(在有数据时)
347+
#### 6.2.4 变量选择器数据源
348+
349+
```typescript
350+
// 合并预定义变量 + 动态表单字段
351+
function getVariablePickerData(flowFromMeta?: FlowFromMeta): GroovyVariableMapping[] {
352+
const variables = [...VARIABLE_MAPPINGS];
353+
354+
// 添加表单字段(如果有)
355+
if (flowFromMeta?.fields) {
356+
variables.push(...getFormFieldMappings(flowFromMeta.fields));
357+
}
358+
359+
// 按tag和order排序
360+
return variables.sort((a, b) => {
361+
if (a.tag !== b.tag) return a.tag.localeCompare(b.tag);
362+
return a.order - b.order;
363+
});
364+
}
365+
```
366+
367+
### 6.3 数据流
368+
369+
```
370+
┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────┐
371+
│ 用户界面输入 │────▶│ GroovyVariableMapping │────▶│ Groovy脚本 │
372+
│ 当前操作人 │ │ 映射类 │ │ request.getXxx()│
373+
└─────────────────┘ └──────────────────────┘ └─────────────────┘
374+
375+
376+
┌──────────────────┐
377+
│ 变量选择器 │
378+
│ 数据源 │
379+
└──────────────────┘
380+
381+
382+
┌──────────────────┐
383+
│ FlowFromMeta │
384+
│ 动态获取字段 │
385+
└──────────────────┘
386+
```
245387

246388
---
247389

248-
## 6. 设计文件位置
390+
## 7. 设计文件位置
249391

250392
`designs/title-expression-ui/`

0 commit comments

Comments
 (0)