提交 | 用户 | 时间
3e359e 1 <template>
H 2   <el-drawer
3     :append-to-body="true"
4     v-model="settingVisible"
5     :show-close="false"
6     :size="588"
7     :before-close="handleClose"
8   >
9     <template #header>
10       <div class="config-header">
11         <input
12           v-if="showInput"
13           type="text"
14           class="config-editable-input"
15           @blur="blurEvent()"
16           v-mountedFocus
17           v-model="currentNode.name"
18           :placeholder="currentNode.name"
19         />
20         <div v-else class="node-name"
21           >{{ currentNode.name }}
22           <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()"
23         /></div>
24
25         <div class="divide-line"></div>
26       </div>
27     </template>
28     <div>
9259c2 29       <div class="mb-3 font-size-16px" v-if="currentNode.defaultFlow"
H 30         >未满足其它条件时,将进入此分支(该分支不可编辑和删除)</div
31       >
3e359e 32       <div v-else>
9259c2 33         <el-form ref="formRef" :model="currentNode" :rules="formRules" label-position="top">
3e359e 34           <el-form-item label="配置方式" prop="conditionType">
9259c2 35             <el-radio-group v-model="currentNode.conditionType" @change="changeConditionType">
3e359e 36               <el-radio
H 37                 v-for="(dict, index) in conditionConfigTypes"
38                 :key="index"
39                 :value="dict.value"
40                 :label="dict.value"
41               >
42                 {{ dict.label }}
43               </el-radio>
44             </el-radio-group>
45           </el-form-item>
46
47           <el-form-item
48             v-if="currentNode.conditionType === 1"
49             label="条件表达式"
50             prop="conditionExpression"
51           >
52             <el-input
53               type="textarea"
54               v-model="currentNode.conditionExpression"
55               clearable
56               style="width: 100%"
57             />
58           </el-form-item>
59           <el-form-item v-if="currentNode.conditionType === 2" label="条件规则">
60             <div class="condition-group-tool">
61               <div class="flex items-center">
62                 <div class="mr-4">条件组关系</div>
63                 <el-switch
64                   v-model="conditionGroups.and"
65                   inline-prompt
66                   active-text="且"
67                   inactive-text="或"
68                 />
69               </div>
70             </div>
71             <el-space direction="vertical" :spacer="conditionGroups.and ? '且' : '或'">
72               <el-card
73                 class="condition-group"
74                 style="width: 530px"
75                 v-for="(condition, cIdx) in conditionGroups.conditions"
76                 :key="cIdx"
77               >
78                 <div class="condition-group-delete" v-if="conditionGroups.conditions.length > 1">
79                   <Icon
80                     color="#0089ff"
81                     icon="ep:circle-close-filled"
82                     :size="18"
83                     @click="deleteConditionGroup(cIdx)"
84                   />
85                 </div>
86                 <template #header>
87                   <div class="flex items-center justify-between">
88                     <div>条件组</div>
89                     <div class="flex">
90                       <div class="mr-4">规则关系</div>
91                       <el-switch
92                         v-model="condition.and"
93                         inline-prompt
94                         active-text="且"
95                         inactive-text="或"
96                       />
97                     </div>
98                   </div>
99                 </template>
100
101                 <div class="flex pt-2" v-for="(rule, rIdx) in condition.rules" :key="rIdx">
102                   <div class="mr-2">
103                     <el-select style="width: 160px" v-model="rule.leftSide">
104                       <el-option
9259c2 105                         v-for="(item, index) in fieldOptions"
3e359e 106                         :key="index"
H 107                         :label="item.title"
108                         :value="item.field"
9259c2 109                         :disabled="!item.required"
3e359e 110                       />
H 111                     </el-select>
112                   </div>
113                   <div class="mr-2">
114                     <el-select v-model="rule.opCode" style="width: 100px">
115                       <el-option
116                         v-for="item in COMPARISON_OPERATORS"
117                         :key="item.value"
118                         :label="item.label"
119                         :value="item.value"
120                       />
121                     </el-select>
122                   </div>
123                   <div class="mr-2">
124                     <el-input v-model="rule.rightSide" style="width: 160px" />
125                   </div>
126                   <div class="mr-1 flex items-center" v-if="condition.rules.length > 1">
127                     <Icon
128                       icon="ep:delete"
129                       :size="18"
130                       @click="deleteConditionRule(condition, rIdx)"
131                     />
132                   </div>
133                   <div class="flex items-center">
134                     <Icon icon="ep:plus" :size="18" @click="addConditionRule(condition, rIdx)" />
135                   </div>
136                 </div>
137               </el-card>
138             </el-space>
139             <div title="添加条件组" class="mt-4 cursor-pointer">
140               <Icon color="#0089ff" icon="ep:plus" :size="24" @click="addConditionGroup" />
141             </div>
142           </el-form-item>
143         </el-form>
144       </div>
145     </div>
146     <template #footer>
147       <el-divider />
148       <div>
149         <el-button type="primary" @click="saveConfig">确 定</el-button>
150         <el-button @click="closeDrawer">取 消</el-button>
151       </div>
152     </template>
153   </el-drawer>
154 </template>
155 <script setup lang="ts">
156 import {
157   SimpleFlowNode,
158   CONDITION_CONFIG_TYPES,
159   ConditionType,
160   COMPARISON_OPERATORS,
161   ConditionGroup,
162   Condition,
9259c2 163   ConditionRule,
H 164   ProcessVariableEnum
3e359e 165 } from '../consts'
H 166 import { getDefaultConditionNodeName } from '../utils'
167 import { useFormFields } from '../node'
9259c2 168 import { BpmModelFormType } from '@/utils/constants'
3e359e 169 const message = useMessage() // 消息弹窗
H 170 defineOptions({
171   name: 'ConditionNodeConfig'
172 })
173 const formType = inject<Ref<number>>('formType') // 表单类型
174 const conditionConfigTypes = computed(() => {
175   return CONDITION_CONFIG_TYPES.filter((item) => {
176     // 业务表单暂时去掉条件规则选项
9259c2 177     if (formType?.value === BpmModelFormType.CUSTOM && item.value === ConditionType.RULE) {
H 178       return false
3e359e 179     } else {
H 180       return true
181     }
182   })
183 })
184
185 const props = defineProps({
186   conditionNode: {
187     type: Object as () => SimpleFlowNode,
188     required: true
189   },
190   nodeIndex: {
191     type: Number,
192     required: true
193   }
194 })
195 const settingVisible = ref(false)
196 const open = () => {
197   if (currentNode.value.conditionType === ConditionType.RULE) {
198     if (currentNode.value.conditionGroups) {
199       conditionGroups.value = currentNode.value.conditionGroups
200     }
201   }
202   settingVisible.value = true
203 }
204
205 watch(
206   () => props.conditionNode,
207   (newValue) => {
208     currentNode.value = newValue
209   }
210 )
211 // 显示名称输入框
212 const showInput = ref(false)
213
214 const clickIcon = () => {
215   showInput.value = true
216 }
217 // 输入框失去焦点
218 const blurEvent = () => {
219   showInput.value = false
220   currentNode.value.name =
221     currentNode.value.name ||
222     getDefaultConditionNodeName(props.nodeIndex, currentNode.value?.defaultFlow)
223 }
224
225 const currentNode = ref<SimpleFlowNode>(props.conditionNode)
226
227 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
228
229 // 关闭
230 const closeDrawer = () => {
231   settingVisible.value = false
232 }
233
234 const handleClose = async (done: (cancel?: boolean) => void) => {
235   const isSuccess = await saveConfig()
236   if (!isSuccess) {
237     done(true) // 传入 true 阻止关闭
238   } else {
239     done()
240   }
241 }
242 // 表单校验规则
243 const formRules = reactive({
244   conditionType: [{ required: true, message: '配置方式不能为空', trigger: 'blur' }],
245   conditionExpression: [{ required: true, message: '条件表达式不能为空', trigger: 'blur' }]
246 })
247 const formRef = ref() // 表单 Ref
248
249 // 保存配置
250 const saveConfig = async () => {
251   if (!currentNode.value.defaultFlow) {
252     // 校验表单
253     if (!formRef) return false
254     const valid = await formRef.value.validate()
255     if (!valid) return false
256     const showText = getShowText()
257     if (!showText) {
258       return false
259     }
260     currentNode.value.showText = showText
261     if (currentNode.value.conditionType === ConditionType.EXPRESSION) {
262       currentNode.value.conditionGroups = undefined
263     }
264     if (currentNode.value.conditionType === ConditionType.RULE) {
265       currentNode.value.conditionExpression = undefined
266       currentNode.value.conditionGroups = conditionGroups.value
267     }
268   }
269   settingVisible.value = false
270   return true
271 }
272 const getShowText = (): string => {
273   let showText = ''
274   if (currentNode.value.conditionType === ConditionType.EXPRESSION) {
275     if (currentNode.value.conditionExpression) {
276       showText = `表达式:${currentNode.value.conditionExpression}`
277     }
278   }
279   if (currentNode.value.conditionType === ConditionType.RULE) {
280     // 条件组是否为与关系
281     const groupAnd = conditionGroups.value.and
282     let warningMesg: undefined | string = undefined
283     const conditionGroup = conditionGroups.value.conditions.map((item) => {
284       return (
285         '(' +
286         item.rules
287           .map((rule) => {
288             if (rule.leftSide && rule.rightSide) {
289               return (
290                 getFieldTitle(rule.leftSide) + ' ' + getOpName(rule.opCode) + ' ' + rule.rightSide
291               )
292             } else {
293               // 有一条规则不完善。提示错误
294               warningMesg = '请完善条件规则'
295               return ''
296             }
297           })
298           .join(item.and ? ' 且 ' : ' 或 ') +
299         ' ) '
300       )
301     })
302     if (warningMesg) {
303       message.warning(warningMesg)
304       showText = ''
305     } else {
306       showText = conditionGroup.join(groupAnd ? ' 且 ' : ' 或 ')
307     }
308   }
309   return showText
310 }
311
312 // 改变条件配置方式
313 const changeConditionType = () => {}
314
315 const conditionGroups = ref<ConditionGroup>({
316   and: true,
317   conditions: [
318     {
319       and: true,
320       rules: [
321         {
322           type: 1,
323           opName: '等于',
324           opCode: '==',
325           leftSide: '',
326           rightSide: ''
327         }
328       ]
329     }
330   ]
331 })
332 // 添加条件组
333 const addConditionGroup = () => {
334   const condition = {
335     and: true,
336     rules: [
337       {
338         type: 1,
339         opName: '等于',
340         opCode: '==',
341         leftSide: '',
342         rightSide: ''
343       }
344     ]
345   }
346   conditionGroups.value.conditions.push(condition)
347 }
348 // 删除条件组
349 const deleteConditionGroup = (idx: number) => {
350   conditionGroups.value.conditions.splice(idx, 1)
351 }
352
353 // 添加条件规则
354 const addConditionRule = (condition: Condition, idx: number) => {
355   const rule: ConditionRule = {
356     type: 1,
357     opName: '等于',
358     opCode: '==',
359     leftSide: '',
360     rightSide: ''
361   }
362   condition.rules.splice(idx + 1, 0, rule)
363 }
364
365 const deleteConditionRule = (condition: Condition, idx: number) => {
366   condition.rules.splice(idx, 1)
367 }
368 const fieldsInfo = useFormFields()
369
9259c2 370 /** 条件规则可选择的表单字段 */
H 371 const fieldOptions = computed(() => {
372   const fieldsCopy = fieldsInfo.slice()
373   // 固定添加发起人 ID 字段
374   fieldsCopy.unshift({
375     field: ProcessVariableEnum.START_USER_ID,
376     title: '发起人',
377     required: true
378   })
379   return fieldsCopy
380 })
381
382 /** 获取字段名称 */
3e359e 383 const getFieldTitle = (field: string) => {
H 384   const item = fieldsInfo.find((item) => item.field === field)
385   return item?.title
386 }
387
9259c2 388 /** 获取操作符名称 */
3e359e 389 const getOpName = (opCode: string): string => {
9259c2 390   const opName = COMPARISON_OPERATORS.find((item: any) => item.value === opCode)
3e359e 391   return opName?.label
H 392 }
393 </script>
394
395 <style lang="scss" scoped>
396 .condition-group-tool {
397   display: flex;
398   justify-content: space-between;
399   width: 500px;
400   margin-bottom: 20px;
401 }
402
403 .condition-group {
404   position: relative;
405
406   &:hover {
407     border-color: #0089ff;
408
409     .condition-group-delete {
410       opacity: 1;
411     }
412   }
413
414   .condition-group-delete {
415     position: absolute;
416     top: 0;
417     left: 0;
418     display: flex;
419     cursor: pointer;
420     opacity: 0;
421   }
422 }
423
424 ::v-deep(.el-card__header) {
425   padding: 8px var(--el-card-padding);
426   border-bottom: 1px solid var(--el-card-border-color);
427   box-sizing: border-box;
428 }
429 </style>