mergeBranch.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. <template>
  2. <!-- `branch-wrap--${nodeConfig.local_status}`-->
  3. <div class="branch-wrap">
  4. <div class="branch-box-wrap">
  5. <div class="branch-box" style="margin-bottom: 15px">
  6. <el-button class="add-branch" color="#345da2" plain round @click="addTerm"> 添加条件 </el-button>
  7. <div v-for="(item, index) in nodeConfig.inclusiveNodes" :key="index" class="col-box">
  8. <div class="condition-node">
  9. <div class="condition-node-box">
  10. <div :class="['auto-judge', `auto-judge--${item.local_status}`]" @click="show(index)">
  11. <!-- 不是第一个 也不是 最后一个-->
  12. <div v-if="index != 0 && index != nodeConfig.inclusiveNodes.length - 1" class="sort-left" @click.stop="arrTransfer(index, -1)">
  13. <el-icon><ArrowLeft /></el-icon>
  14. </div>
  15. <div class="title">
  16. <span style="display: inline-block; width: 115px"
  17. ><le-text class="flex-1" tag="b" :value="index === nodeConfig.inclusiveNodes.length - 1 ? '默认条件' : item.nodeName"
  18. /></span>
  19. <div v-if="index != nodeConfig.inclusiveNodes.length - 1" class="close">
  20. <el-icon class="mx-1" @click.stop="copyTerm(index)">
  21. <CopyDocument />
  22. </el-icon>
  23. <el-icon @click.stop="delTerm(index)">
  24. <Close />
  25. </el-icon>
  26. </div>
  27. </div>
  28. <div class="content" :class="[index === nodeConfig.inclusiveNodes.length - 1 ? 'last-child-title' : '']" style="width: 200px">
  29. <el-tooltip v-if="toText(nodeConfig, index)" effect="dark" placement="top">
  30. <div class="text-overflow_ellipsis_line_2" v-html="toText(nodeConfig, index)" />
  31. <template #content>
  32. <div v-html="toText(nodeConfig, index)" />
  33. </template>
  34. </el-tooltip>
  35. <span v-else class="placeholder"> 请设置条件 </span>
  36. </div>
  37. <!-- 最后一个没有,长度大于2倒数第二个没有 -->
  38. <div
  39. v-if="
  40. !(nodeConfig.inclusiveNodes.length == 2 && (index === nodeConfig.inclusiveNodes.length - 1 || index === 0)) &&
  41. !(
  42. (nodeConfig.inclusiveNodes.length > 2 && index === nodeConfig.inclusiveNodes.length - 1) ||
  43. index === nodeConfig.inclusiveNodes.length - 2
  44. )
  45. "
  46. class="sort-right"
  47. @click.stop="arrTransfer(index)"
  48. >
  49. <el-icon><ArrowRight /></el-icon>
  50. </div>
  51. </div>
  52. <add-node v-model="item.childNode" :disabled="disabled"></add-node>
  53. </div>
  54. </div>
  55. <slot v-if="item.childNode" :node="item"></slot>
  56. <div v-if="index == 0" class="top-left-cover-line"></div>
  57. <div v-if="index == 0" class="bottom-left-cover-line"></div>
  58. <div v-if="index == nodeConfig.inclusiveNodes.length - 1" class="top-right-cover-line"></div>
  59. <div v-if="index == nodeConfig.inclusiveNodes.length - 1" class="bottom-right-cover-line"></div>
  60. </div>
  61. <div class="svg-icon-box">
  62. <svg-icon style="font-size: 18px" color="#345da2" icon-class="flow-merge" />
  63. </div>
  64. </div>
  65. <add-node v-model="nodeConfig.childNode" :disabled="disabled"></add-node>
  66. </div>
  67. <el-drawer v-model="drawer" title="条件设置" destroy-on-close append-to-body :size="600">
  68. <template #header>
  69. <div class="node-wrap-drawer__title">
  70. <label v-if="!isEditTitle" @click="editTitle">
  71. {{ form.nodeName }}<el-icon class="node-wrap-drawer__title-edit"><Edit /></el-icon>
  72. <!-- <div @click="rmConditionGroup(conditionGroup)">-->
  73. <!-- -->
  74. <!-- </div>-->
  75. </label>
  76. <el-input
  77. v-if="isEditTitle"
  78. ref="nodeTitle"
  79. v-model="form.nodeName"
  80. clearable
  81. class="w-40"
  82. @blur="saveTitle"
  83. @keyup.enter="saveTitle"
  84. ></el-input>
  85. <el-input v-model="form.nodeKey" clearable class="w-40 pl-1.5" placeholder="请填写nodeKey"></el-input>
  86. </div>
  87. </template>
  88. <el-container>
  89. <el-main style="padding: 0 0 20px 0">
  90. <div class="top-tips">满足以下条件时进入当前分支</div>
  91. <template v-for="(conditionGroup, conditionGroupIdx) in form.conditionList" :key="conditionGroupIdx">
  92. <div v-if="conditionGroupIdx != 0" class="or-branch-link-tip">或满足</div>
  93. <div class="condition-group-editor">
  94. <div class="header">
  95. <span>条件组 {{ conditionGroupIdx + 1 }}</span>
  96. <div @click="deleteConditionGroup(conditionGroupIdx)">
  97. <el-icon class="branch-delete-icon"><Delete /></el-icon>
  98. </div>
  99. </div>
  100. <div class="main-content">
  101. <!-- 单个条件 -->
  102. <div class="condition-content-box cell-box">
  103. <div v-if="false">描述</div>
  104. <div>条件字段</div>
  105. <div>运算符</div>
  106. <div>值</div>
  107. </div>
  108. <div v-for="(condition, idx) in conditionGroup" :key="idx" class="condition-content">
  109. <div class="condition-relation">
  110. <span>{{ idx == 0 ? '当' : '且' }}</span>
  111. <div v-if="conditionGroup.length > 1" @click="deleteConditionList(conditionGroup, idx)">
  112. <el-icon class="branch-delete-icon"><Delete /></el-icon>
  113. </div>
  114. </div>
  115. <div class="condition-content">
  116. <div class="condition-content-box">
  117. <el-input
  118. v-if="condition.type === 'custom'"
  119. v-model="condition.label"
  120. placeholder="自定义条件字段"
  121. @input="getCurrentItemField(conditionGroupIdx, idx)"
  122. />
  123. <el-select
  124. v-if="condition.type === 'form'"
  125. v-model="condition.field"
  126. filterable
  127. placeholder="表单条件字段"
  128. @change="getCurrentItemLabel(conditionGroupIdx, idx)"
  129. >
  130. <el-option v-for="{ id, label, key } in expressionFormList" :key="id" :label="label" :value="key" />
  131. </el-select>
  132. <el-select v-model="condition.operator" placeholder="请选择表达式">
  133. <el-option v-for="{ value, label } in operatorType" :key="label" :label="label" :value="value"></el-option>
  134. </el-select>
  135. <el-input v-model="condition.value" placeholder="值" />
  136. </div>
  137. </div>
  138. </div>
  139. </div>
  140. <div class="sub-content">
  141. <el-button link type="primary" icon="Plus" @click="addConditionList(conditionGroup, 'custom')"> 添加自定义条件 </el-button>
  142. <el-button
  143. v-if="expressionFormList.length"
  144. link
  145. type="primary"
  146. icon="Plus"
  147. style="margin-left: 8px"
  148. @click="addConditionList(conditionGroup, 'form')"
  149. >
  150. 添加表单条件
  151. </el-button>
  152. </div>
  153. </div>
  154. </template>
  155. <el-button style="width: 100%" type="info" icon="Plus" text bg @click="addConditionGroup"> 添加条件组 </el-button>
  156. </el-main>
  157. <el-footer>
  158. <el-button type="primary" @click="save"> 保存 </el-button>
  159. <el-button @click="drawer = false">取消</el-button>
  160. </el-footer>
  161. </el-container>
  162. </el-drawer>
  163. </div>
  164. </template>
  165. <script>
  166. import addNode from './addNode.vue'
  167. import { operatorType } from './config'
  168. import useFlowStore from '@/store/modules/flow'
  169. import { Delete, Plus, ArrowLeft, Close, ArrowRight, Edit } from '@element-plus/icons-vue'
  170. import { mapState } from 'pinia'
  171. import { getNodeKey } from '@/utils/workflow'
  172. import { ElMessage } from 'element-plus'
  173. import { $log } from '@/utils'
  174. export default {
  175. components: {
  176. addNode
  177. },
  178. props: {
  179. modelValue: { type: Object, default: () => {} },
  180. disabled: {
  181. type: Boolean,
  182. default: false
  183. }
  184. },
  185. data() {
  186. return {
  187. operatorType,
  188. nodeConfig: {},
  189. drawer: false,
  190. isEditTitle: false,
  191. index: 0,
  192. form: {},
  193. expressionFormList: [],
  194. local_formStructure: {}
  195. }
  196. },
  197. computed: {
  198. ...mapState(useFlowStore, ['processForm']) //映射函数,取出processForm
  199. },
  200. watch: {
  201. modelValue() {
  202. this.nodeConfig = this.modelValue
  203. },
  204. processForm: {
  205. handler(processForm) {
  206. // console.error(val, 'processForm change...', typeof val)
  207. const { formStructure } = JSON.parse(processForm) // 表单设计字段
  208. this.local_formStructure = formStructure
  209. // 使用 filter 方法找到 required 属性为 true 的对象
  210. this.expressionFormList = (formStructure?.fields || []).filter(item => item.options && item.options.required === true)
  211. $log(this.expressionFormList, '这里打印出符合条件的表单对象')
  212. },
  213. immediate: true
  214. }
  215. },
  216. mounted() {
  217. this.nodeConfig = this.modelValue
  218. },
  219. methods: {
  220. show(index) {
  221. if (this.disabled) return
  222. if (index === this.nodeConfig.inclusiveNodes.length - 1) {
  223. // 最后一个节点不能编辑
  224. return
  225. }
  226. this.index = index
  227. this.form = {}
  228. this.form = JSON.parse(JSON.stringify(this.nodeConfig.inclusiveNodes[index]))
  229. this.drawer = true
  230. },
  231. editTitle() {
  232. this.isEditTitle = true
  233. this.$nextTick(() => {
  234. this.$refs.nodeTitle.focus()
  235. })
  236. },
  237. saveTitle() {
  238. this.isEditTitle = false
  239. },
  240. save() {
  241. if (!this.form.nodeKey) {
  242. return ElMessage.error('请填写nodeKey')
  243. }
  244. this.nodeConfig.inclusiveNodes[this.index] = this.form
  245. this.$emit('update:modelValue', this.nodeConfig)
  246. this.drawer = false
  247. },
  248. addTerm() {
  249. let len = this.nodeConfig.inclusiveNodes.length + 1
  250. this.nodeConfig.inclusiveNodes.push({
  251. nodeName: '包容条件' + len,
  252. nodeKey: getNodeKey(),
  253. type: 3,
  254. priorityLevel: len,
  255. conditionMode: 1,
  256. conditionList: []
  257. })
  258. },
  259. delTerm(index) {
  260. this.nodeConfig.inclusiveNodes.splice(index, 1)
  261. if (this.nodeConfig.inclusiveNodes.length == 1) {
  262. if (this.nodeConfig.childNode) {
  263. if (this.nodeConfig.inclusiveNodes[0].childNode) {
  264. this.reData(this.nodeConfig.inclusiveNodes[0].childNode, this.nodeConfig.childNode)
  265. } else {
  266. this.nodeConfig.inclusiveNodes[0].childNode = this.nodeConfig.childNode
  267. }
  268. }
  269. this.$emit('update:modelValue', this.nodeConfig.inclusiveNodes[0].childNode)
  270. }
  271. },
  272. copyTerm(index) {
  273. let len = this.nodeConfig.inclusiveNodes.length + 1
  274. const currentNode = JSON.parse(JSON.stringify(this.nodeConfig.inclusiveNodes[index]))
  275. const item = {
  276. ...currentNode,
  277. nodeKey: getNodeKey(),
  278. nodeName: currentNode.nodeName + '-Copy',
  279. priorityLevel: len
  280. }
  281. this.nodeConfig.inclusiveNodes.push(item)
  282. this.$emit('update:modelValue', this.nodeConfig)
  283. },
  284. reData(data, addData) {
  285. if (!data.childNode) {
  286. data.childNode = addData
  287. } else {
  288. this.reData(data.childNode, addData)
  289. }
  290. },
  291. arrTransfer(index, type = 1) {
  292. this.nodeConfig.inclusiveNodes[index] = this.nodeConfig.inclusiveNodes.splice(index + type, 1, this.nodeConfig.inclusiveNodes[index])[0]
  293. this.nodeConfig.inclusiveNodes.map((item, index) => {
  294. item.priorityLevel = index + 1
  295. })
  296. this.$emit('update:modelValue', this.nodeConfig)
  297. },
  298. addConditionList(conditionList, type) {
  299. conditionList.push({
  300. label: '',
  301. field: '',
  302. operator: '==',
  303. value: '',
  304. type
  305. })
  306. },
  307. deleteConditionList(conditionList, index) {
  308. conditionList.splice(index, 1)
  309. },
  310. addConditionGroup() {
  311. this.addConditionList(this.form.conditionList[this.form.conditionList.push([]) - 1], 'custom')
  312. },
  313. deleteConditionGroup(index) {
  314. this.form.conditionList.splice(index, 1)
  315. },
  316. toText(nodeConfig, index) {
  317. var { conditionList } = nodeConfig.inclusiveNodes[index]
  318. if (conditionList && conditionList.length) {
  319. const text = conditionList
  320. .map(conditionGroup =>
  321. conditionGroup
  322. .map(item => {
  323. const showOperator = this.operatorType.find(i => i.value === item.operator).label
  324. if (item.type === 'form') {
  325. let showLabel = item.showLabel
  326. if (!showLabel) showLabel = this.expressionFormList.find(i => i.key === item.field)?.label
  327. return `${showLabel}${showOperator}${item.value}`
  328. } else {
  329. return `${item.label}${showOperator}${item.value}`
  330. }
  331. })
  332. .join(' <span style="color: var(--el-color-warning)">且</span> ')
  333. )
  334. .join('<br/> <span style="color: var(--el-color-success)">或</span> <br/>')
  335. return text
  336. } else {
  337. if (index === nodeConfig.inclusiveNodes.length - 1) {
  338. return '未满足时其他条件时,将进入默认流程'
  339. } else {
  340. return ''
  341. }
  342. }
  343. },
  344. getCurrentItemLabel(conditionIdx, idx) {
  345. const currentCondition = this.form.conditionList[conditionIdx]
  346. const field = currentCondition[idx].field
  347. const labelObj = this.expressionFormList.find(i => i.key === field)
  348. currentCondition[idx].showLabel = labelObj.label
  349. currentCondition[idx].label = field
  350. },
  351. getCurrentItemField(conditionIdx, idx) {
  352. const currentCondition = this.form.conditionList[conditionIdx]
  353. currentCondition[idx].field = currentCondition[idx].label
  354. }
  355. }
  356. }
  357. </script>
  358. <style scoped lang="scss">
  359. .top-tips {
  360. display: flex;
  361. justify-content: space-between;
  362. align-items: center;
  363. margin-bottom: 12px;
  364. color: #646a73;
  365. }
  366. .or-branch-link-tip {
  367. margin: 10px 0;
  368. color: #646a73;
  369. }
  370. .condition-group-editor {
  371. user-select: none;
  372. border-radius: 4px;
  373. border: 1px solid #e4e5e7;
  374. position: relative;
  375. margin-bottom: 16px;
  376. .branch-delete-icon {
  377. font-size: 18px;
  378. }
  379. .header {
  380. background-color: #f4f6f8;
  381. padding: 0 12px;
  382. font-size: 14px;
  383. color: #171e31;
  384. height: 36px;
  385. display: flex;
  386. align-items: center;
  387. span {
  388. flex: 1;
  389. }
  390. }
  391. .main-content {
  392. padding: 0 12px;
  393. .condition-relation {
  394. color: #9ca2a9;
  395. display: flex;
  396. align-items: center;
  397. height: 36px;
  398. display: flex;
  399. justify-content: space-between;
  400. padding: 0 2px;
  401. }
  402. .condition-content-box {
  403. display: flex;
  404. justify-content: space-between;
  405. align-items: center;
  406. div {
  407. width: 100%;
  408. min-width: 120px;
  409. }
  410. div:not(:first-child) {
  411. margin-left: 16px;
  412. }
  413. }
  414. .cell-box {
  415. div {
  416. padding: 16px 0;
  417. width: 100%;
  418. min-width: 120px;
  419. color: #909399;
  420. font-size: 14px;
  421. font-weight: 600;
  422. text-align: center;
  423. }
  424. }
  425. .condition-content {
  426. display: flex;
  427. flex-direction: column;
  428. :deep(.el-input__wrapper) {
  429. border-top-left-radius: 0;
  430. border-bottom-left-radius: 0;
  431. }
  432. .content {
  433. flex: 1;
  434. padding: 0 0 4px 0;
  435. display: flex;
  436. align-items: center;
  437. min-height: 31.6px;
  438. flex-wrap: wrap;
  439. }
  440. }
  441. }
  442. .sub-content {
  443. padding: 12px;
  444. }
  445. }
  446. </style>