@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.
Files changed (27) hide show
  1. package/package.json +38 -0
  2. package/src/layouts/index.jsx +13 -0
  3. package/src/pages/flowable/design/PropertiesPanel.jsx +167 -0
  4. package/src/pages/flowable/design/contextPad.js +50 -0
  5. package/src/pages/flowable/design/customTranslate/customTranslate.js +16 -0
  6. package/src/pages/flowable/design/customTranslate/translations-properties-panel.js +10 -0
  7. package/src/pages/flowable/design/customTranslate/translations.js +144 -0
  8. package/src/pages/flowable/design/descriptors/flowable.json +1109 -0
  9. package/src/pages/flowable/design/index.css +7 -0
  10. package/src/pages/flowable/design/index.jsx +163 -0
  11. package/src/pages/flowable/design/provider/properties/AssignmentSection.jsx +74 -0
  12. package/src/pages/flowable/design/provider/properties/ConditionDesign.jsx +292 -0
  13. package/src/pages/flowable/design/provider/properties/ConditionExpressionUtils.js +53 -0
  14. package/src/pages/flowable/design/provider/properties/ConditionProps.jsx +66 -0
  15. package/src/pages/flowable/design/provider/properties/DelegateExpressionProps.jsx +29 -0
  16. package/src/pages/flowable/design/provider/properties/FormProps.jsx +28 -0
  17. package/src/pages/flowable/design/provider/properties/GeneralSection.jsx +21 -0
  18. package/src/pages/flowable/design/provider/properties/MultiInstanceProps.jsx +37 -0
  19. package/src/pages/flowable/index.jsx +87 -0
  20. package/src/pages/flowable/monitor/definition.jsx +87 -0
  21. package/src/pages/flowable/monitor/instance/index.jsx +82 -0
  22. package/src/pages/flowable/monitor/instance/view.jsx +102 -0
  23. package/src/pages/flowable/monitor/task.jsx +98 -0
  24. package/src/pages/flowable/simulate/index.jsx +400 -0
  25. package/src/pages/flowable/user-task/form.jsx +183 -0
  26. package/src/pages/flowable/user-task/index.jsx +184 -0
  27. package/src/pages/flowable/user-task/instance/view.jsx +85 -0
@@ -0,0 +1,400 @@
1
+ import React from "react";
2
+ import {Button, Card, Empty, Form, Input, message, Modal, Select, Space, Spin, Splitter, Table, Tag, Typography} from "antd";
3
+ import {HttpUtils, PageLoading, PageUtils, StringUtils} from "@jiangood/open-admin";
4
+ import {CheckCircleOutlined, CloseCircleOutlined, HistoryOutlined, PlayCircleOutlined, ReloadOutlined} from "@ant-design/icons";
5
+
6
+ const PHASE_INIT = 'init';
7
+ const PHASE_RUNNING = 'running';
8
+ const PHASE_FINISHED = 'finished';
9
+
10
+ export default class extends React.Component {
11
+
12
+ state = {
13
+ phase: PHASE_INIT,
14
+ loading: false,
15
+ submitting: false,
16
+
17
+ // init phase
18
+ model: undefined,
19
+ users: [],
20
+ historyList: [],
21
+ historyLoading: false,
22
+
23
+ // running / finished phase
24
+ instanceId: null,
25
+ status: null,
26
+
27
+ // task form values: { [taskId]: { assignee, comment } }
28
+ taskFormValues: {},
29
+ }
30
+
31
+ componentDidMount() {
32
+ const params = PageUtils.currentParams();
33
+ const id = this.id = params.id;
34
+
35
+ // 加载模型元数据
36
+ HttpUtils.get('admin/flowable/simulate/get', {id}).then(rs => {
37
+ this.setState({model: rs}, this.loadHistory);
38
+ });
39
+
40
+ // 加载用户列表
41
+ this.loadUsers();
42
+ }
43
+
44
+ loadUsers = (searchText) => {
45
+ HttpUtils.get('admin/flowable/simulate/users', {searchText}).then(rs => {
46
+ this.setState({users: rs || []});
47
+ });
48
+ }
49
+
50
+ loadHistory = () => {
51
+ const {model} = this.state;
52
+ if (!model?.key) return;
53
+ this.setState({historyLoading: true});
54
+ HttpUtils.get('admin/flowable/simulate/list', {key: model.key}).then(rs => {
55
+ this.setState({historyList: rs || [], historyLoading: false});
56
+ }).catch(() => {
57
+ this.setState({historyLoading: false});
58
+ });
59
+ }
60
+
61
+ // ========== Init Phase ==========
62
+
63
+ handleStart = values => {
64
+ this.setState({submitting: true});
65
+ HttpUtils.post('admin/flowable/simulate/start', values).then(rs => {
66
+ const instanceId = rs.instanceId;
67
+ message.success('仿真流程已启动');
68
+ this.loadStatus(instanceId);
69
+ }).catch(e => {
70
+ message.error(e);
71
+ this.setState({submitting: false});
72
+ });
73
+ }
74
+
75
+ // ========== Running / Finished Phase ==========
76
+
77
+ loadStatus = (instanceId) => {
78
+ this.setState({loading: true, instanceId, phase: PHASE_RUNNING, taskFormValues: {}});
79
+ HttpUtils.get('admin/flowable/simulate/status', {instanceId}).then(rs => {
80
+ const phase = rs.finished ? PHASE_FINISHED : PHASE_RUNNING;
81
+ this.setState({status: rs, phase, loading: false, submitting: false});
82
+ }).catch(e => {
83
+ message.error(e);
84
+ this.setState({loading: false, submitting: false});
85
+ });
86
+ }
87
+
88
+ handleAssigneeChange = (taskId, value) => {
89
+ this.setState(prev => ({
90
+ taskFormValues: {
91
+ ...prev.taskFormValues,
92
+ [taskId]: { ...prev.taskFormValues[taskId], assignee: value }
93
+ }
94
+ }));
95
+ }
96
+
97
+ handleCommentChange = (taskId, e) => {
98
+ const value = e.target.value;
99
+ this.setState(prev => ({
100
+ taskFormValues: {
101
+ ...prev.taskFormValues,
102
+ [taskId]: { ...prev.taskFormValues[taskId], comment: value }
103
+ }
104
+ }));
105
+ }
106
+
107
+ handleTask = (taskId, action) => {
108
+ const {taskFormValues} = this.state;
109
+ const formValue = taskFormValues[taskId] || {};
110
+ const handleUserId = formValue.assignee;
111
+
112
+ if (!handleUserId) {
113
+ message.warning('请选择处理人');
114
+ return;
115
+ }
116
+
117
+ this.setState({submitting: true});
118
+ HttpUtils.post('admin/flowable/simulate/task/handle', {
119
+ taskId,
120
+ action,
121
+ comment: formValue.comment || '',
122
+ handleUserId,
123
+ }).then(() => {
124
+ message.success(action === 'APPROVE' ? '已同意' : '已拒绝');
125
+ this.loadStatus(this.state.instanceId);
126
+ }).catch(e => {
127
+ message.error(e);
128
+ this.setState({submitting: false});
129
+ });
130
+ }
131
+
132
+ handleReset = () => {
133
+ this.setState({
134
+ phase: PHASE_INIT,
135
+ instanceId: null,
136
+ status: null,
137
+ loading: false,
138
+ submitting: false,
139
+ taskFormValues: {},
140
+ }, this.loadHistory);
141
+ }
142
+
143
+ handleViewHistory = (instanceId) => {
144
+ this.loadStatus(instanceId);
145
+ }
146
+
147
+ handleDeleteHistory = (instanceId) => {
148
+ Modal.confirm({
149
+ title: '确认删除',
150
+ content: '确定要物理删除此仿真记录吗?删除后不可恢复。',
151
+ okText: '确认删除',
152
+ okType: 'danger',
153
+ cancelText: '取消',
154
+ onOk: () => {
155
+ HttpUtils.post('admin/flowable/simulate/delete', {instanceId}).then(() => {
156
+ message.success('仿真记录已删除');
157
+ this.loadHistory();
158
+ });
159
+ },
160
+ });
161
+ }
162
+
163
+ onImgClick = () => {
164
+ Modal.info({
165
+ title: '流程图',
166
+ width: '70vw',
167
+ content: <div style={{width: '100%', overflow: 'auto', maxHeight: '80vh'}}>
168
+ <img src={this.state.status?.img} style={{maxWidth: '100%'}}/>
169
+ </div>
170
+ });
171
+ }
172
+
173
+ // ========== Render ==========
174
+
175
+ render() {
176
+ const {model} = this.state;
177
+
178
+ if (model === undefined) {
179
+ return <PageLoading/>;
180
+ }
181
+
182
+ const {phase, loading, submitting, status} = this.state;
183
+
184
+ return (
185
+ <Card title={'流程仿真 / 【' + model.name + '】 / ' + model.key}
186
+ extra={phase === PHASE_FINISHED ? (
187
+ <Space>
188
+ <Button icon={<HistoryOutlined/>} onClick={this.handleReset}>历史记录</Button>
189
+ <Button icon={<ReloadOutlined/>} onClick={this.handleReset}>重新仿真</Button>
190
+ </Space>
191
+ ) : null}>
192
+ {phase === PHASE_INIT && this.renderInitPhase()}
193
+ {phase !== PHASE_INIT && (
194
+ loading ? <Spin style={{display: 'block', margin: '80px auto'}}/> :
195
+ status ? this.renderRunningPhase() : null
196
+ )}
197
+ </Card>
198
+ );
199
+ }
200
+
201
+ renderInitPhase = () => {
202
+ const {model, users, submitting, historyList, historyLoading} = this.state;
203
+ return (
204
+ <Splitter>
205
+ <Splitter.Panel defaultSize="55%">
206
+ <Form onFinish={this.handleStart} layout="vertical">
207
+ <Form.Item name="key" noStyle initialValue={model.key}/>
208
+
209
+ <Form.Item label="业务标识" name="id"
210
+ rules={[{required: true, message: '请输入业务标识'}]}
211
+ initialValue={StringUtils.random(16)}>
212
+ <Input/>
213
+ </Form.Item>
214
+
215
+ <Form.Item label="发起人" name="initiatorId"
216
+ rules={[{required: true, message: '请选择发起人'}]}>
217
+ <Select showSearch placeholder="搜索并选择用户"
218
+ filterOption={false}
219
+ onSearch={this.loadUsers}
220
+ options={users.map(u => ({label: u.name, value: u.id}))}
221
+ style={{width: 300}}/>
222
+ </Form.Item>
223
+
224
+ {model.variables?.map(item => (
225
+ <Form.Item key={item.name} name={['variables', item.name]} label={item.label}>
226
+ <Input/>
227
+ </Form.Item>
228
+ ))}
229
+
230
+ <Form.Item>
231
+ <Button type="primary" htmlType="submit" icon={<PlayCircleOutlined/>}
232
+ loading={submitting}>
233
+ 启动仿真
234
+ </Button>
235
+ </Form.Item>
236
+ </Form>
237
+ </Splitter.Panel>
238
+ <Splitter.Panel defaultSize="45%">
239
+ {this.renderHistoryList(historyList, historyLoading)}
240
+ </Splitter.Panel>
241
+ </Splitter>
242
+ );
243
+ }
244
+
245
+ renderHistoryList = (list, loading) => (
246
+ <div style={{paddingLeft: 16}}>
247
+ <Typography.Title level={5}>
248
+ <HistoryOutlined/> 仿真历史
249
+ </Typography.Title>
250
+ {loading ? <Spin/> : list.length === 0 ? (
251
+ <Empty description="暂无仿真记录" image={Empty.PRESENTED_IMAGE_SIMPLE}/>
252
+ ) : (
253
+ <Table dataSource={list}
254
+ size="small" pagination={false} rowKey="instanceId"
255
+ columns={[
256
+ {
257
+ title: '实例', dataIndex: 'name', ellipsis: true,
258
+ render: (text, record) => (
259
+ <a onClick={() => this.handleViewHistory(record.instanceId)}>{text}</a>
260
+ ),
261
+ },
262
+ {title: '发起人', dataIndex: 'starter', width: 80},
263
+ {
264
+ title: '状态', dataIndex: 'finished', width: 80,
265
+ render: (finished, record) => finished ? (
266
+ record.deleteReason ? (
267
+ <Tag color="error">已终止</Tag>
268
+ ) : (
269
+ <Tag color="success">已完成</Tag>
270
+ )
271
+ ) : (
272
+ <Tag color="processing">运行中</Tag>
273
+ ),
274
+ },
275
+ {title: '发起时间', dataIndex: 'startTime', width: 100},
276
+ {
277
+ title: '操作', width: 60,
278
+ render: (_, record) => (
279
+ <Button type="link" danger size="small"
280
+ onClick={() => this.handleDeleteHistory(record.instanceId)}>
281
+ 删除
282
+ </Button>
283
+ ),
284
+ },
285
+ ]}/>
286
+ )}
287
+ </div>
288
+ );
289
+
290
+ renderRunningPhase = () => {
291
+ const {status, submitting, taskFormValues, users} = this.state;
292
+ const {img, commentList, tasks, finished, deleteReason} = status;
293
+
294
+ return (
295
+ <Splitter>
296
+ <Splitter.Panel defaultSize="60%">
297
+ <div style={{paddingRight: 16}}>
298
+ <Typography.Title level={5}>流程图</Typography.Title>
299
+ <img src={img} style={{maxWidth: '100%', cursor: 'pointer'}}
300
+ onClick={this.onImgClick}/>
301
+ <div style={{marginTop: 16}}>
302
+ <Typography.Title level={5}>处理记录</Typography.Title>
303
+ <Table dataSource={commentList || []}
304
+ size="small" pagination={false} rowKey="time"
305
+ columns={[
306
+ {dataIndex: 'content', title: '操作'},
307
+ {dataIndex: 'user', title: '处理人', width: 120},
308
+ {dataIndex: 'time', title: '处理时间', width: 160},
309
+ ]}/>
310
+ </div>
311
+ </div>
312
+ </Splitter.Panel>
313
+ <Splitter.Panel defaultSize="40%">
314
+ <div style={{paddingLeft: 16}}>
315
+ <Space style={{marginBottom: 12}}>
316
+ <Typography.Text strong>实例名称:</Typography.Text>
317
+ <Typography.Text>{status.name}</Typography.Text>
318
+ </Space>
319
+ <br/>
320
+ <Space style={{marginBottom: 12}}>
321
+ <Typography.Text strong>业务标识:</Typography.Text>
322
+ <Typography.Text>{status.businessKey}</Typography.Text>
323
+ </Space>
324
+ <br/>
325
+ <Space style={{marginBottom: 12}}>
326
+ <Typography.Text strong>发起人:</Typography.Text>
327
+ <Typography.Text>{status.starter}</Typography.Text>
328
+ </Space>
329
+ <br/>
330
+ <Space style={{marginBottom: 12}}>
331
+ <Typography.Text strong>发起时间:</Typography.Text>
332
+ <Typography.Text>{status.startTime}</Typography.Text>
333
+ </Space>
334
+ <br/>
335
+ <Space style={{marginBottom: 16}}>
336
+ <Typography.Text strong>状态:</Typography.Text>
337
+ {finished ? (
338
+ deleteReason ? (
339
+ <Tag icon={<CloseCircleOutlined/>} color="error">已终止</Tag>
340
+ ) : (
341
+ <Tag icon={<CheckCircleOutlined/>} color="success">已完成</Tag>
342
+ )
343
+ ) : (
344
+ <Tag color="processing">运行中</Tag>
345
+ )}
346
+ </Space>
347
+
348
+ {finished && deleteReason && (
349
+ <Card size="small" title="终止原因" style={{marginBottom: 16}}>
350
+ <Typography.Text type="secondary">{deleteReason}</Typography.Text>
351
+ </Card>
352
+ )}
353
+
354
+ {!finished && tasks?.map(task => (
355
+ <Card key={task.taskId} size="small" title={task.taskName}
356
+ style={{marginBottom: 12}}>
357
+ <Space direction="vertical" style={{width: '100%'}}>
358
+ <div>
359
+ <Typography.Text strong>处理人:</Typography.Text>
360
+ <Select showSearch placeholder="选择处理人"
361
+ value={(taskFormValues[task.taskId] || {}).assignee}
362
+ style={{width: '100%'}}
363
+ filterOption={false}
364
+ onSearch={this.loadUsers}
365
+ onChange={value => this.handleAssigneeChange(task.taskId, value)}
366
+ options={users.map(u => ({label: u.name, value: u.id}))}/>
367
+ </div>
368
+ <div>
369
+ <Typography.Text strong>审批意见:</Typography.Text>
370
+ <Input.TextArea rows={2}
371
+ value={(taskFormValues[task.taskId] || {}).comment}
372
+ onChange={e => this.handleCommentChange(task.taskId, e)}/>
373
+ </div>
374
+ <Space>
375
+ <Button type="primary"
376
+ icon={<CheckCircleOutlined/>}
377
+ loading={submitting}
378
+ onClick={() => this.handleTask(task.taskId, 'APPROVE')}>
379
+ 同意
380
+ </Button>
381
+ <Button danger
382
+ icon={<CloseCircleOutlined/>}
383
+ loading={submitting}
384
+ onClick={() => this.handleTask(task.taskId, 'REJECT')}>
385
+ 不同意
386
+ </Button>
387
+ </Space>
388
+ </Space>
389
+ </Card>
390
+ ))}
391
+
392
+ {!finished && (!tasks || tasks.length === 0) && (
393
+ <Empty description="暂无活跃任务"/>
394
+ )}
395
+ </div>
396
+ </Splitter.Panel>
397
+ </Splitter>
398
+ );
399
+ }
400
+ }
@@ -0,0 +1,183 @@
1
+ import React from "react";
2
+ import {Button, Card, Empty, Form, Input, message, Modal, Radio, Spin, Splitter, Table, Tabs, Typography,} from "antd";
3
+ import {history} from "umi";
4
+ import {FormRegistryUtils, Gap, HttpUtils, Page, PageUtils} from "@jiangood/open-admin";
5
+ import {FormOutlined, ShareAltOutlined} from "@ant-design/icons";
6
+
7
+ export default class extends React.Component {
8
+
9
+ state = {
10
+ submitLoading: false,
11
+
12
+
13
+ formData: {},
14
+
15
+
16
+ instanceCommentList: [],
17
+ vars: {},
18
+
19
+
20
+ data: {
21
+ taskId: null,
22
+ commentList: [],
23
+ img: null
24
+ },
25
+ loading: true,
26
+
27
+ errorMsg: null
28
+ }
29
+
30
+ componentDidMount() {
31
+ const {taskId} = PageUtils.currentParams()
32
+
33
+
34
+ HttpUtils.get("admin/flowable/user-task/getInstanceInfoByTaskId", {taskId}).then(rs => {
35
+ this.setState({data: rs})
36
+ }).catch(e => {
37
+ this.setState({errorMsg: e})
38
+ }).finally(() => {
39
+ this.setState({loading: false})
40
+ })
41
+
42
+
43
+ }
44
+
45
+ onImgClick = () => {
46
+ Modal.info({
47
+ title: '流程图',
48
+ width: '70vw',
49
+ content: <div style={{width: '100%', overflow: 'auto', maxHeight: '80vh'}}>
50
+ <img src={this.state.data.img}/>
51
+ </div>
52
+ })
53
+ };
54
+
55
+
56
+ handleTask = async value => {
57
+ this.setState({submitLoading: true});
58
+ try {
59
+ if (value.result === 'APPROVE') {
60
+ value.formData = this.state.formData
61
+ }
62
+ value.taskId = this.state.data.taskId
63
+ await HttpUtils.post("admin/flowable/user-task/handleTask", value)
64
+
65
+ PageUtils.closeCurrent()
66
+ } catch (error) {
67
+ message.error(error)
68
+ } finally {
69
+ this.setState({submitLoading: false})
70
+ }
71
+
72
+ }
73
+
74
+ render() {
75
+ const {submitLoading} = this.state
76
+
77
+ const {data, loading} = this.state
78
+ const {commentList, img} = data
79
+ if (loading) {
80
+ return <Spin/>
81
+ }
82
+ return <Page padding>
83
+
84
+ <Splitter>
85
+ <Splitter.Panel>
86
+ <Typography.Title level={4}>{data.name}</Typography.Title>
87
+ <Typography.Text type="secondary">{data.starter} &nbsp;&nbsp; {data.startTime}</Typography.Text>
88
+ <Gap></Gap>
89
+ <Tabs
90
+ items={[
91
+ {
92
+ key: '1',
93
+ label: '表单',
94
+ icon: <FormOutlined/>,
95
+ children: this.renderForm()
96
+ },
97
+ {
98
+ key: '2',
99
+ label: '流程图',
100
+ icon: <ShareAltOutlined/>,
101
+ children: this.renderProcess(img, commentList)
102
+ }
103
+ ]}>
104
+
105
+ </Tabs>
106
+ </Splitter.Panel>
107
+ <Splitter.Panel defaultSize={400}>
108
+ <Card title='审批意见'>
109
+ <Form
110
+ layout='vertical'
111
+ onFinish={this.handleTask}
112
+ disabled={submitLoading}
113
+ >
114
+
115
+ <Form.Item label='审批结果' name='result' rules={[{required: true, message: '请选择'}]}
116
+ initialValue={'APPROVE'}>
117
+ <Radio.Group>
118
+ <Radio value='APPROVE'>同意</Radio>
119
+ <Radio value='REJECT'>不同意</Radio>
120
+ </Radio.Group>
121
+ </Form.Item>
122
+ <Form.Item label='审批意见' name='comment'
123
+ rules={[{required: true, message: '请输入审批意见'}]}>
124
+ <Input.TextArea/>
125
+ </Form.Item>
126
+ <div>
127
+ <Button type='primary' htmlType='submit' loading={submitLoading}
128
+ size={"middle"}>提交</Button>
129
+ </div>
130
+ </Form>
131
+ </Card>
132
+ </Splitter.Panel>
133
+
134
+ </Splitter>
135
+
136
+
137
+ </Page>
138
+
139
+
140
+ }
141
+
142
+ renderProcess = (img, commentList) => <Card title='处理记录'>
143
+ <img src={img} style={{maxWidth: '100%'}}
144
+ onClick={this.onImgClick}/>
145
+ <Gap></Gap>
146
+ <Table dataSource={commentList}
147
+
148
+ size='small'
149
+ pagination={false}
150
+ rowKey='id'
151
+ columns={[
152
+ {
153
+ dataIndex: 'content',
154
+ title: '操作'
155
+ },
156
+ {
157
+ dataIndex: 'user',
158
+ title: '处理人',
159
+ },
160
+ {
161
+ dataIndex: 'time',
162
+ title: '处理时间'
163
+ },
164
+ ]}
165
+ />
166
+ </Card>;
167
+
168
+ renderForm = () => {
169
+ const {data, formData} = this.state
170
+ const {businessKey} = data
171
+ const formKey = data.formKey;
172
+ const formName = data.formKey + 'Form'
173
+
174
+ let ExForm = FormRegistryUtils.get(formName);
175
+ if (!ExForm) {
176
+ console.error(" 表单不存在: " + formName + ",请检查表单源代码:src/forms/" + formName + ".jsx")
177
+ return <Empty description={"表单不存在: " + formName}></Empty>
178
+ }
179
+
180
+ return <ExForm id={businessKey} formKey={formKey} value={formData}
181
+ onChange={v => this.setState({formData: v})}></ExForm>
182
+ }
183
+ }