@jiangood/open-admin-flowable 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +38 -0
- package/src/layouts/index.jsx +13 -0
- package/src/pages/flowable/design/PropertiesPanel.jsx +167 -0
- package/src/pages/flowable/design/contextPad.js +50 -0
- package/src/pages/flowable/design/customTranslate/customTranslate.js +16 -0
- package/src/pages/flowable/design/customTranslate/translations-properties-panel.js +10 -0
- package/src/pages/flowable/design/customTranslate/translations.js +144 -0
- package/src/pages/flowable/design/descriptors/flowable.json +1109 -0
- package/src/pages/flowable/design/index.css +7 -0
- package/src/pages/flowable/design/index.jsx +163 -0
- package/src/pages/flowable/design/provider/properties/AssignmentSection.jsx +74 -0
- package/src/pages/flowable/design/provider/properties/ConditionDesign.jsx +292 -0
- package/src/pages/flowable/design/provider/properties/ConditionExpressionUtils.js +53 -0
- package/src/pages/flowable/design/provider/properties/ConditionProps.jsx +66 -0
- package/src/pages/flowable/design/provider/properties/DelegateExpressionProps.jsx +29 -0
- package/src/pages/flowable/design/provider/properties/FormProps.jsx +28 -0
- package/src/pages/flowable/design/provider/properties/GeneralSection.jsx +21 -0
- package/src/pages/flowable/design/provider/properties/MultiInstanceProps.jsx +37 -0
- package/src/pages/flowable/index.jsx +87 -0
- package/src/pages/flowable/monitor/definition.jsx +87 -0
- package/src/pages/flowable/monitor/instance/index.jsx +82 -0
- package/src/pages/flowable/monitor/instance/view.jsx +102 -0
- package/src/pages/flowable/monitor/task.jsx +98 -0
- package/src/pages/flowable/simulate/index.jsx +400 -0
- package/src/pages/flowable/user-task/form.jsx +183 -0
- package/src/pages/flowable/user-task/index.jsx +184 -0
- package/src/pages/flowable/user-task/instance/view.jsx +85 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {Button, Card, message, Modal, Space, Splitter} from "antd";
|
|
3
|
+
|
|
4
|
+
import 'bpmn-js/dist/assets/diagram-js.css'
|
|
5
|
+
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'
|
|
6
|
+
import BpmnModeler from 'bpmn-js/lib/Modeler'
|
|
7
|
+
|
|
8
|
+
import './index.css'
|
|
9
|
+
import customTranslate from "./customTranslate/customTranslate";
|
|
10
|
+
import contextPad from "./contextPad";
|
|
11
|
+
import {CloudUploadOutlined, SaveOutlined} from "@ant-design/icons";
|
|
12
|
+
import {HttpUtils, PageLoading, PageUtils, ProTable} from "@jiangood/open-admin";
|
|
13
|
+
import 'bpmn-js/dist/assets/bpmn-js.css';
|
|
14
|
+
import flowableJson from './descriptors/flowable';
|
|
15
|
+
import PropertiesPanel from './PropertiesPanel';
|
|
16
|
+
|
|
17
|
+
export default class extends React.Component {
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
state = {
|
|
21
|
+
id: null,
|
|
22
|
+
model: null,
|
|
23
|
+
bpmnModeler: null,
|
|
24
|
+
deployedModal: false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
bpmRef = React.createRef()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async componentDidMount() {
|
|
31
|
+
const params = PageUtils.currentParams()
|
|
32
|
+
const rs = await HttpUtils.get('admin/flowable/model/detail', {id: params.id})
|
|
33
|
+
this.setState({model: rs, id: params.id}, this.initBpmn)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
initBpmn = () => {
|
|
37
|
+
let container = this.bpmRef.current;
|
|
38
|
+
let xml = this.state.model.content;
|
|
39
|
+
|
|
40
|
+
this.bpmnModeler = new BpmnModeler({
|
|
41
|
+
container: container,
|
|
42
|
+
additionalModules: [
|
|
43
|
+
{translate: ['value', customTranslate]},
|
|
44
|
+
contextPad,
|
|
45
|
+
],
|
|
46
|
+
moddleExtensions: {
|
|
47
|
+
flowable: flowableJson
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
console.log('导入的xml内容如下')
|
|
52
|
+
console.log(xml)
|
|
53
|
+
this.bpmnModeler.importXML(xml)
|
|
54
|
+
this.bpmnModeler.on('element.contextmenu', e => e.preventDefault()) // 关闭右键,影响操作
|
|
55
|
+
this.setState({bpmnModeler: this.bpmnModeler});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
showXML = () => {
|
|
60
|
+
this.bpmnModeler.saveXML({format: true}).then(res => {
|
|
61
|
+
Modal.info({content: <pre style={{overflowX: "auto", height: '64vh'}}>{res.xml}</pre>, width: 1024})
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
handleSave = async () => {
|
|
67
|
+
let id = this.state.id;
|
|
68
|
+
const hide = message.loading('正在保存...', 0)
|
|
69
|
+
try {
|
|
70
|
+
const res = await this.bpmnModeler.saveXML();
|
|
71
|
+
await HttpUtils.post('admin/flowable/model/saveContent', {id, content: res.xml});
|
|
72
|
+
} finally {
|
|
73
|
+
hide()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
handleDeploy = async () => {
|
|
78
|
+
let id = this.state.id;
|
|
79
|
+
const hide = message.loading('正在部署...', 0)
|
|
80
|
+
try {
|
|
81
|
+
const res = await this.bpmnModeler.saveXML();
|
|
82
|
+
await HttpUtils.post('admin/flowable/model/deploy', {id, content: res.xml});
|
|
83
|
+
} finally {
|
|
84
|
+
hide()
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
render() {
|
|
90
|
+
if (this.state.model == null) {
|
|
91
|
+
return <PageLoading />
|
|
92
|
+
}
|
|
93
|
+
return <Card title={'流程设计 ' + this.state.model?.name}
|
|
94
|
+
extra={<Space>
|
|
95
|
+
<Button type='primary' icon={<SaveOutlined/>} onClick={this.handleSave}>暂存</Button>
|
|
96
|
+
<Button type='primary' danger icon={<CloudUploadOutlined/>}
|
|
97
|
+
onClick={this.handleDeploy}>部署</Button>
|
|
98
|
+
<Button onClick={this.showXML}>XML</Button>
|
|
99
|
+
<Button
|
|
100
|
+
onClick={() => PageUtils.open('/flowable/simulate?id=' + this.state.id, "流程仿真")}> 仿真 </Button>
|
|
101
|
+
|
|
102
|
+
<Button title='查看已部署的历史版本' onClick={() => {
|
|
103
|
+
this.setState({deployedModal: true})
|
|
104
|
+
}}>历史版本</Button>
|
|
105
|
+
</Space>}>
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
<Splitter style={{minHeight: 'calc(100vh - 200px)'}}>
|
|
109
|
+
<Splitter.Panel>
|
|
110
|
+
<div ref={this.bpmRef} style={{width: '100%', height: '100%'}}></div>
|
|
111
|
+
</Splitter.Panel>
|
|
112
|
+
|
|
113
|
+
<Splitter.Panel defaultSize={300}>
|
|
114
|
+
<PropertiesPanel modeler={this.state.bpmnModeler} />
|
|
115
|
+
</Splitter.Panel>
|
|
116
|
+
</Splitter>
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
<Modal title='已部署版本' width={800} footer={null}
|
|
120
|
+
open={this.state.deployedModal}
|
|
121
|
+
destroyOnHidden
|
|
122
|
+
onCancel={() => this.setState({deployedModal: false})}>
|
|
123
|
+
|
|
124
|
+
<ProTable
|
|
125
|
+
columns={[
|
|
126
|
+
{
|
|
127
|
+
dataIndex: 'key',
|
|
128
|
+
title: '编码'
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
dataIndex: 'name',
|
|
132
|
+
title: '名称'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
dataIndex: 'version',
|
|
136
|
+
title: '版本号'
|
|
137
|
+
}, {
|
|
138
|
+
title: '操作',
|
|
139
|
+
dataIndex:'id',
|
|
140
|
+
render:(_, record)=> {
|
|
141
|
+
return <Button type='primary' onClick={()=>{
|
|
142
|
+
HttpUtils.get('admin/flowable/model/getDefinitionContent',{id: record.id}).then(xml=>{
|
|
143
|
+
this.bpmnModeler.importXML(xml)
|
|
144
|
+
this.setState({deployedModal:false})
|
|
145
|
+
})
|
|
146
|
+
}}>加载</Button>
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
]}
|
|
150
|
+
request={params => {
|
|
151
|
+
params.key = this.state.model.key
|
|
152
|
+
return HttpUtils.get('admin/flowable/model/definitionPage', params)
|
|
153
|
+
}}>
|
|
154
|
+
|
|
155
|
+
</ProTable>
|
|
156
|
+
|
|
157
|
+
</Modal>
|
|
158
|
+
|
|
159
|
+
</Card>
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {Form, Radio} from "antd";
|
|
2
|
+
import {FieldRemoteSelect, FieldRemoteSelectMultipleInline, StringUtils} from "@jiangood/open-admin";
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
export default function AssignmentSection(props) {
|
|
6
|
+
const {element, modeling} = props
|
|
7
|
+
const businessObject = element.businessObject
|
|
8
|
+
|
|
9
|
+
const getMode = () => {
|
|
10
|
+
if (businessObject.assignee) return 'assignee'
|
|
11
|
+
if (businessObject.candidateGroups) return 'candidateGroups'
|
|
12
|
+
if (businessObject.candidateUsers) return 'candidateUsers'
|
|
13
|
+
return 'assignee'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const [mode, setMode] = React.useState(getMode())
|
|
17
|
+
const [formKey, setFormKey] = React.useState(0)
|
|
18
|
+
|
|
19
|
+
const initialValues = {
|
|
20
|
+
assignee: businessObject.assignee,
|
|
21
|
+
candidateGroups: businessObject.candidateGroups,
|
|
22
|
+
candidateUsers: StringUtils.split(businessObject.candidateUsers, ','),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const handleModeChange = (e) => {
|
|
26
|
+
const newMode = e.target.value
|
|
27
|
+
if (newMode === mode) return
|
|
28
|
+
|
|
29
|
+
// 切换模式时清空所有指派字段,确保三选一
|
|
30
|
+
modeling.updateProperties(element, {
|
|
31
|
+
assignee: undefined,
|
|
32
|
+
candidateGroups: undefined,
|
|
33
|
+
candidateUsers: undefined,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
setMode(newMode)
|
|
37
|
+
setFormKey(k => k + 1)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (<div style={{padding: 8}}>
|
|
41
|
+
<Radio.Group
|
|
42
|
+
optionType="button"
|
|
43
|
+
buttonStyle="solid"
|
|
44
|
+
value={mode}
|
|
45
|
+
onChange={handleModeChange}
|
|
46
|
+
options={[
|
|
47
|
+
{label: '直接指派', value: 'assignee'},
|
|
48
|
+
{label: '候选组', value: 'candidateGroups'},
|
|
49
|
+
{label: '候选人', value: 'candidateUsers'},
|
|
50
|
+
]}
|
|
51
|
+
style={{marginBottom: 12, display: 'flex'}}
|
|
52
|
+
/>
|
|
53
|
+
<Form key={formKey} layout='vertical' initialValues={initialValues}
|
|
54
|
+
onValuesChange={(changedValues) => {
|
|
55
|
+
modeling.updateProperties(element, changedValues);
|
|
56
|
+
}}>
|
|
57
|
+
{mode === 'assignee' && (
|
|
58
|
+
<Form.Item label="办理人" name='assignee'>
|
|
59
|
+
<FieldRemoteSelect url='admin/flowable/model/assigneeOptions'/>
|
|
60
|
+
</Form.Item>
|
|
61
|
+
)}
|
|
62
|
+
{mode === 'candidateGroups' && (
|
|
63
|
+
<Form.Item label="候选组" name='candidateGroups'>
|
|
64
|
+
<FieldRemoteSelect url='admin/flowable/model/candidateGroupsOptions'/>
|
|
65
|
+
</Form.Item>
|
|
66
|
+
)}
|
|
67
|
+
{mode === 'candidateUsers' && (
|
|
68
|
+
<Form.Item label="候选人" name='candidateUsers'>
|
|
69
|
+
<FieldRemoteSelectMultipleInline url='admin/flowable/model/candidateUsersOptions'/>
|
|
70
|
+
</Form.Item>
|
|
71
|
+
)}
|
|
72
|
+
</Form>
|
|
73
|
+
</div>)
|
|
74
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import {Button, Input, InputNumber, Modal, Select} from "antd";
|
|
2
|
+
import {Component} from "react";
|
|
3
|
+
import {FieldBoolean, FieldTable, HttpUtils, ObjectUtils, StringUtils, ThemeUtils} from "@jiangood/open-admin";
|
|
4
|
+
import {ConditionExpressionUtils} from "./ConditionExpressionUtils";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
// 字符串的双引号
|
|
8
|
+
const QUOTE = '"';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const OPERATOR_DEFINITIONS = [
|
|
12
|
+
{
|
|
13
|
+
type: 'STRING',
|
|
14
|
+
label: '等于',
|
|
15
|
+
key: '==',
|
|
16
|
+
component: Input,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
type: 'STRING',
|
|
20
|
+
label: '不等于',
|
|
21
|
+
key: '!=',
|
|
22
|
+
component: Input,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
type: 'STRING',
|
|
26
|
+
label: '包含',
|
|
27
|
+
key: '.contains',
|
|
28
|
+
component: Input,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
type: 'STRING',
|
|
32
|
+
label: '开头等于',
|
|
33
|
+
key: '.startWith',
|
|
34
|
+
component: Input,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
type: 'STRING',
|
|
38
|
+
label: '结尾等于',
|
|
39
|
+
key: '.endWith',
|
|
40
|
+
component: Input,
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
// =================== 数字 ================
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
type: 'NUMBER',
|
|
47
|
+
label: '等于',
|
|
48
|
+
key: '==',
|
|
49
|
+
component: Input,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: 'NUMBER',
|
|
53
|
+
label: '不等于',
|
|
54
|
+
key: '!=',
|
|
55
|
+
component: Input,
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
{
|
|
59
|
+
type: 'NUMBER',
|
|
60
|
+
label: '大于',
|
|
61
|
+
key: '>',
|
|
62
|
+
component: InputNumber,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: 'NUMBER',
|
|
66
|
+
label: '小于',
|
|
67
|
+
key: '<',
|
|
68
|
+
component: InputNumber,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: 'NUMBER',
|
|
72
|
+
label: '大于等于',
|
|
73
|
+
key: '>=',
|
|
74
|
+
component: InputNumber,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: 'NUMBER',
|
|
78
|
+
label: '小于等于',
|
|
79
|
+
key: '<=',
|
|
80
|
+
component: InputNumber,
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
// ===================== 布尔值 =======================
|
|
85
|
+
|
|
86
|
+
{
|
|
87
|
+
type: 'BOOLEAN',
|
|
88
|
+
label: '等于',
|
|
89
|
+
key: '==',
|
|
90
|
+
component: FieldBoolean,
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
function encode(data) {
|
|
97
|
+
let {left, op, right} = data;
|
|
98
|
+
if (left == null || op == null || right == null) {
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const isFun = op.startsWith('.')
|
|
103
|
+
if (isFun) {
|
|
104
|
+
return left + op + '("' + right + '")';
|
|
105
|
+
}
|
|
106
|
+
const isStr = right.startsWith('"')
|
|
107
|
+
if (isStr) {
|
|
108
|
+
right = '"' + right + '"';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return left + op + right;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function decode(expression) {
|
|
115
|
+
const isFun = ConditionExpressionUtils.isFunction(expression);
|
|
116
|
+
if (isFun) {
|
|
117
|
+
return ConditionExpressionUtils.parseStrFunction(expression)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return ConditionExpressionUtils.parse(expression)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
export class ConditionDesignButton extends Component {
|
|
125
|
+
|
|
126
|
+
state = {
|
|
127
|
+
open: false,
|
|
128
|
+
varList: [],
|
|
129
|
+
varOptions: [],
|
|
130
|
+
editingArr: [], // 弹窗内本地编辑状态,点击确定后才提交
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
componentDidMount() {
|
|
134
|
+
const {processId} = this.props;
|
|
135
|
+
console.log('流程id', processId)
|
|
136
|
+
|
|
137
|
+
HttpUtils.get('admin/flowable/model/varList', {code: processId}).then(rs => {
|
|
138
|
+
const options = rs.map(r => {
|
|
139
|
+
return {
|
|
140
|
+
label: r.label,
|
|
141
|
+
value: r.name
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
this.setState({varList: rs, varOptions: options})
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 弹窗内实时更新本地状态,不提交到模型
|
|
149
|
+
handleTableChange = arr => {
|
|
150
|
+
this.setState({ editingArr: arr })
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// 确定:将本地状态提交到模型
|
|
154
|
+
handleOk = () => {
|
|
155
|
+
const str = this.convertArrToStr(this.state.editingArr)
|
|
156
|
+
this.props.setValue(str, this.props.element, this.props.modeling, this.props.bpmnFactory)
|
|
157
|
+
this.setState({ open: false })
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 取消:丢弃本地修改
|
|
161
|
+
handleCancel = () => {
|
|
162
|
+
this.setState({ open: false })
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 打开弹窗时,从当前元素读取最新表达式
|
|
166
|
+
handleOpen = () => {
|
|
167
|
+
let value = this.props.getValue(this.props.element);
|
|
168
|
+
let arrValue = this.convertStrToArr(value);
|
|
169
|
+
this.setState({ open: true, editingArr: arrValue })
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
getOptionsByItem = (record) => {
|
|
173
|
+
let options = []
|
|
174
|
+
let {varList} = this.state;
|
|
175
|
+
let varItem = varList.find(t => t.name === record.left)
|
|
176
|
+
|
|
177
|
+
if (varItem) {
|
|
178
|
+
const {valueType} = varItem;
|
|
179
|
+
const os = OPERATOR_DEFINITIONS.filter(o => o.type === valueType)
|
|
180
|
+
for (let o of os) {
|
|
181
|
+
options.push({
|
|
182
|
+
label: o.label,
|
|
183
|
+
value: o.key
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return options;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
columns = [
|
|
192
|
+
{
|
|
193
|
+
dataIndex: 'left', title: '变量名称',
|
|
194
|
+
render: () => {
|
|
195
|
+
return <Select options={this.state.varOptions} style={{width: 200}}></Select>
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
dataIndex: 'op', title: '操作符',
|
|
200
|
+
render: (v, record) => {
|
|
201
|
+
const options = this.getOptionsByItem(record)
|
|
202
|
+
|
|
203
|
+
return <Select options={options} style={{width: 100}}></Select>
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
{dataIndex: 'right', title: '值', width: 200},
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
render() {
|
|
210
|
+
const { editingArr } = this.state
|
|
211
|
+
const previewExpression = this.convertArrToStr(editingArr)
|
|
212
|
+
|
|
213
|
+
return <div style={{display: 'flex', justifyContent: 'right', padding: 8}}>
|
|
214
|
+
<Button type='primary'
|
|
215
|
+
size='small'
|
|
216
|
+
|
|
217
|
+
styles={{
|
|
218
|
+
root: {
|
|
219
|
+
backgroundColor: ThemeUtils.getColor('primary-color')
|
|
220
|
+
}
|
|
221
|
+
}}
|
|
222
|
+
|
|
223
|
+
onClick={this.handleOpen}
|
|
224
|
+
|
|
225
|
+
>条件编辑器</Button>
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
<Modal title='条件编辑器'
|
|
229
|
+
open={this.state.open}
|
|
230
|
+
width={600}
|
|
231
|
+
onOk={this.handleOk}
|
|
232
|
+
onCancel={this.handleCancel}
|
|
233
|
+
mask={{blur: false}}
|
|
234
|
+
destroyOnHidden
|
|
235
|
+
>
|
|
236
|
+
<FieldTable
|
|
237
|
+
columns={this.columns}
|
|
238
|
+
value={editingArr}
|
|
239
|
+
onChange={this.handleTableChange}
|
|
240
|
+
/>
|
|
241
|
+
|
|
242
|
+
<div style={{
|
|
243
|
+
marginTop: 8,
|
|
244
|
+
color: '#999',
|
|
245
|
+
fontSize: 12,
|
|
246
|
+
}}>
|
|
247
|
+
提示:暂不支持复杂表达式,复杂表达式请手动编辑
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<div style={{
|
|
251
|
+
marginTop: 16,
|
|
252
|
+
padding: '8px 12px',
|
|
253
|
+
background: '#f6f8fa',
|
|
254
|
+
borderRadius: 6,
|
|
255
|
+
border: '1px solid #d9d9d9',
|
|
256
|
+
fontFamily: 'monospace',
|
|
257
|
+
fontSize: 13,
|
|
258
|
+
wordBreak: 'break-all',
|
|
259
|
+
minHeight: 36,
|
|
260
|
+
display: 'flex',
|
|
261
|
+
alignItems: 'center',
|
|
262
|
+
}}>
|
|
263
|
+
<span style={{ color: '#999', marginRight: 8, flexShrink: 0 }}>表达式预览:</span>
|
|
264
|
+
<span style={{ color: previewExpression ? '#1a1a1a' : '#bbb' }}>
|
|
265
|
+
{previewExpression || '(空)'}
|
|
266
|
+
</span>
|
|
267
|
+
</div>
|
|
268
|
+
</Modal>
|
|
269
|
+
|
|
270
|
+
</div>
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
convertStrToArr(value) {
|
|
274
|
+
if (value) {
|
|
275
|
+
value = StringUtils.removePrefixAndSuffix(value, "${", "}")
|
|
276
|
+
const strArr = StringUtils.split(value, '&&');
|
|
277
|
+
return strArr.map(decode).filter(t => t != null)
|
|
278
|
+
}
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
convertArrToStr = arrValue => {
|
|
284
|
+
const str = arrValue.map(encode).join('&&')
|
|
285
|
+
|
|
286
|
+
return "${" + str + "}"
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {StringUtils} from "@jiangood/open-admin";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export class ConditionExpressionUtils {
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 判断表达式是否是函数
|
|
8
|
+
* @param expr 如 name.contains("aa")
|
|
9
|
+
*/
|
|
10
|
+
static isFunction(expr) {
|
|
11
|
+
const regExp = /\w+\.\w+\(.*\)/;
|
|
12
|
+
return regExp.test(expr);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 解析函数, 得到变量,函数名,参数
|
|
17
|
+
* 注意:只支持单参数函数
|
|
18
|
+
* @param expr
|
|
19
|
+
*/
|
|
20
|
+
static parseStrFunction(expr) {
|
|
21
|
+
const regExp = /^(\w+)(\.\w+)\("(.*)"\)$/;
|
|
22
|
+
const match = expr.match(regExp);
|
|
23
|
+
if (!match) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const [, left, op, right] = match;
|
|
27
|
+
return { left, op,right };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 解析普通表达式, 如 a==1, b>5, c=="你好"
|
|
32
|
+
*
|
|
33
|
+
*/
|
|
34
|
+
static parse(expr) {
|
|
35
|
+
// 支持的比较操作符, 注意顺序,先比较的操作符长的
|
|
36
|
+
const operators = ['==', '!=', '<=', '>=', '<', '>'];
|
|
37
|
+
|
|
38
|
+
const op = operators.find(op => StringUtils.contains(expr, op))
|
|
39
|
+
if (!op) {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const index = expr.indexOf(op);
|
|
44
|
+
const left = expr.substring(0, index).trim();
|
|
45
|
+
let right = expr.substring(index + op.length).trim();
|
|
46
|
+
right = StringUtils.removePrefixAndSuffix(right, '"', '"')
|
|
47
|
+
|
|
48
|
+
return {left, op, right};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Input } from 'antd';
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { ConditionDesignButton } from './ConditionDesign';
|
|
4
|
+
|
|
5
|
+
function getExpressionValue(element) {
|
|
6
|
+
const condition = element.businessObject?.conditionExpression;
|
|
7
|
+
return condition ? condition.body : '';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function setExpressionValue(value, element, modeling, moddle) {
|
|
11
|
+
const businessObject = element.businessObject;
|
|
12
|
+
let conditionExpression = businessObject.conditionExpression;
|
|
13
|
+
|
|
14
|
+
if (!value) {
|
|
15
|
+
modeling.updateProperties(element, { conditionExpression: undefined });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!conditionExpression) {
|
|
20
|
+
conditionExpression = moddle.create('bpmn:FormalExpression');
|
|
21
|
+
modeling.updateProperties(element, { conditionExpression });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
modeling.updateModdleProperties(element, conditionExpression, { body: value });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function ConditionSection({ element, modeling, moddle, processId }) {
|
|
28
|
+
const [value, setValue] = useState(getExpressionValue(element));
|
|
29
|
+
const timerRef = useRef(null);
|
|
30
|
+
|
|
31
|
+
const handleChange = (e) => {
|
|
32
|
+
const v = e.target.value;
|
|
33
|
+
setValue(v);
|
|
34
|
+
clearTimeout(timerRef.current);
|
|
35
|
+
timerRef.current = setTimeout(() => {
|
|
36
|
+
setExpressionValue(v, element, modeling, moddle);
|
|
37
|
+
}, 300);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div style={{ padding: 8 }}>
|
|
42
|
+
<div style={{ marginBottom: 4, fontSize: 12, color: '#666' }}>
|
|
43
|
+
条件表达式(JUEL)
|
|
44
|
+
</div>
|
|
45
|
+
<Input.TextArea
|
|
46
|
+
value={value}
|
|
47
|
+
onChange={handleChange}
|
|
48
|
+
placeholder="条件表达式(JUEL)"
|
|
49
|
+
rows={3}
|
|
50
|
+
/>
|
|
51
|
+
<div style={{ display: 'flex', justifyContent: 'right', marginTop: 8 }}>
|
|
52
|
+
<ConditionDesignButton
|
|
53
|
+
element={element}
|
|
54
|
+
modeling={modeling}
|
|
55
|
+
bpmnFactory={moddle}
|
|
56
|
+
processId={processId}
|
|
57
|
+
getValue={getExpressionValue}
|
|
58
|
+
setValue={(v, el, mod, mdl) => {
|
|
59
|
+
setExpressionValue(v, el || element, mod || modeling, mdl || moddle);
|
|
60
|
+
setValue(v);
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {Select} from 'antd';
|
|
2
|
+
import {useEffect, useState} from 'react';
|
|
3
|
+
import {HttpUtils} from "@jiangood/open-admin";
|
|
4
|
+
|
|
5
|
+
export default function DelegateExpressionField({element, modeling}) {
|
|
6
|
+
const [options, setOptions] = useState([]);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
HttpUtils.get('admin/flowable/model/javaDelegateOptions').then(rs => {
|
|
10
|
+
setOptions((rs || []).map(o => typeof o === 'string' ? {label: o, value: o} : o));
|
|
11
|
+
});
|
|
12
|
+
}, []);
|
|
13
|
+
|
|
14
|
+
const value = element.businessObject?.delegateExpression || '';
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div style={{padding: 8}}>
|
|
18
|
+
<div style={{marginBottom: 4, fontSize: 12, color: '#666'}}>delegateExpression</div>
|
|
19
|
+
<Select
|
|
20
|
+
style={{width: '100%'}}
|
|
21
|
+
value={value || undefined}
|
|
22
|
+
placeholder="实现JavaDelegate接口的Bean名称, 如 ${demoDelegate}"
|
|
23
|
+
options={options}
|
|
24
|
+
onChange={(val) => modeling.updateProperties(element, {delegateExpression: val || ''})}
|
|
25
|
+
allowClear
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|