一个简单的自定义工作流设计器实现
Vue

一个简单的自定义工作流设计器实现

朱治龙
2021-06-30 / 0 评论 / 161 阅读 / 正在检测是否收录...

kqyt100j.png

先瞅瞅产品提供的丑陋至极的原型效果

kqj6b43m.png

再看看经过本人精雕细琢之后的惊艳效果

flow.gif

Show me the code

流程设计弹出层

<template>
  <el-dialog width="760px" class="hkt-dlg-darkblue" :title="`流程设计:${formData.name}`" :visible.sync="isShow" destroy-on-close :close-on-click-modal="false">
    <div class="dialog-wrap" v-loading="loading">
      <div style="min-height:50vh">
        <div class="info-block">
          <el-row class="hkt-block-title">
            <el-col :span="12"><div><span class="title-name">流程配置</span></div></el-col>
            <el-col :span="12" class="text-right">
              <el-button type="text" @click="formModalChange('New', null)"><i class="el-icon-plus"></i> 添加审核节点</el-button>
            </el-col>
          </el-row>
          <div class="info-content">
            <el-steps direction="vertical" :active="nodes.length">
              <el-step v-for="(step, stepIndex) in nodes" class="step-item-wrap" :class="'step-item-signmodel-' + step.signModel" :key="step + stepIndex">
                <div slot="title">
                  <div style="line-height:32px;heihgt:38px;">
                    <el-row>
                      <el-col :span="12">
                        <span class="step-name" v-text="step.nodeName"></span>
                      </el-col>
                      <el-col :span="12">
                        <div class="step-item-oprations text-right">
                          <el-button size="mini" type="text" @click="stepSort(stepIndex, 'up')" v-if="stepIndex > 0"><i class="el-icon-top"></i> 上移</el-button>
                          <el-button size="mini" type="text"@click="stepSort(stepIndex, 'down')"  v-if="stepIndex < nodes.length - 1"><i class="el-icon-bottom"></i> 下移</el-button>
                          <el-button size="mini" type="text" @click="formModalChange('Edit', step, stepIndex)"><i class="el-icon-edit"></i> 修改</el-button>
                          <el-button size="mini" type="text" @click="removeStep(step, stepIndex)"><i class="el-icon-close"></i> 删除</el-button>
                          <el-button size="mini" type="text" @click="addPersons(step, stepIndex)"><i class="el-icon-user"></i> 配置审批人</el-button>
                        </div>
                      </el-col>
                    </el-row>
                  </div>
                </div>
                <div slot="description">
                  <div v-if="step.approveNames.length === 0">
                    <span class="color-gray">暂未配置审批人员</span>
                  </div>
                  <div v-else class="step-item-content-wrap">
                    <div class="person-item">
                      <el-tag v-for="(person, personIndex) in step.approveNames" :key="person" disable-transitions @close="removePerson(stepIndex, personIndex)" closable>{{person}}</el-tag>
                    </div>
                  </div>
                </div>
              </el-step>
            </el-steps>
            <div v-if="nodes.length === 0">
              <h3 style="padding-top:40px;font-weight:normal;color:#dedede;font-size: 22px; text-align:center"><p>当前流程暂未配置审核节点信息,</p><p>请单击右上角的“添加审核节点”按钮进行配置</p></h3>
            </div>
          </div>

        </div>

      </div>

    </div>
    <div slot="footer" class="dialog-footer-wrap text-center">
      <el-button @click="isShow = false">取 消</el-button>
      <el-button type="primary" :loading="submitting" @click="save">{{submitting ? '保存中': '确 定'}}</el-button>
    </div>
    <step-form-modal :show.sync="formModalVisible" v-if="formModalVisible" :formData="currentRow" :mode="formMode" @callback="formCallback" />
  </el-dialog>
</template>
<script>
import stepFormModal from './stepFormModal.vue'
export default {
  name: 'flowDesignModal',
  desc: '流程设计器',
  components: {
    stepFormModal
  },
  props: {
    show: {
      type: Boolean,
      required: true,
      default: false
    },
    formData: {
      // 适用于编辑
      type: Object,
      required: false
    },
    mode: {
      type: String,
      required: false
    }
  },
  data () {
    return {
      loading: true,
      form: {
      },
      nodes: [],
      formModalVisible: false,
      currentRow: null,
      currentStepIndex: null,
      formMode:'New', // New || Edit
      submitting: false
    }
  },
  computed: {
    isShow: {
      get () {
        return this.show
      },
      set (val) {
        this.$emit('update:show', val)
      }
    }
  },
  created() {
    this.init()
  },
  methods: {
    init () {
      if (this.formData) {
        this.getFlowNodes()
        // this.form = this.$hktUtils.deepClone(this.formData)
      }
    },
    stepSort (index, type) {
      const steps = this.nodes
      if (type === 'up') {
        ;[steps[index], steps[index - 1]] = [steps[index - 1], steps[index]]
      } else {
        ;[steps[index], steps[index + 1]] = [steps[index + 1], steps[index]]
      }
      // 触发页面更新
      this.nodes = steps.concat()
    },
    async getFlowNodes () {
      this.loading = true
      const res = await this.$request.get(`/v1/manager/flow/node/listByFlowId?flowId=${this.formData.id}`)
      if (res.success) {
        this.nodes = res.data
      }
      this.loading = false
    },
    // 添加或编辑流程节点
    formModalChange (mode, formData, stepIndex) {
      this.formMode = mode
      this.currentRow = formData
      this.currentStepIndex = stepIndex
      this.formModalVisible = true
    },
    formCallback (item) {
      // TODO 判断步骤名称是否已存在
      if (this.formMode === 'New') {
        item.flowId = this.formData.id
        item.approveArray = []
        item.approveNames = []
        this.nodes.push(item)
      } else if (this.currentStepIndex >= 0) {
        this.nodes[this.currentStepIndex] = item
        this.currentStepIndex = null
      }
    },
    addPersons (row, stepIndex) {
      const dialogProps = {
        multipleFlag: 'Y'
      }
      if (row.approveArray && row.approveArray.length > 0) {
        dialogProps.initRightUsers = row.approveArray.join(',')
      }
      window.hktCommon.selectUser(`配置 ${row.nodeName} 节点的审批人`, dialogProps, (res) => {
        row.approveArray = res.map(item => item.id)
        row.approveNames = res.map(item => item.name)
        this.nodes.splice(stepIndex, 1, this.$hktUtils.deepClone(row))
      })

    },
    removePerson(stepIndex, personIndex) {
      this.nodes[stepIndex].approveArray.splice(personIndex, 1)
      this.nodes[stepIndex].approveNames.splice(personIndex, 1)
    },
    removeStep(stepItem, stepIndex) {
      this.$confirm(`本操作不可恢复,确定删除该步骤(${stepItem.nodeName})?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }).then(() => {
        this.nodes.splice(stepIndex, 1)
      }).catch(() => {})
    },

    // 保存
    async save () {
      const flowNodes = this.$hktUtils.deepClone(this.nodes)
      const existNodeNames = []// 用于校验名称是否重复
      for (let i = 0; i < flowNodes.length; i++) {
        const flowNode = flowNodes[i]
        if (flowNode.approveArray.length === 0) {
          this.$message({
            message: `审核步骤(${flowNode.nodeName})至少需选择一个的审批人`,
            type: 'warning'
          })
          return
        }
        if (existNodeNames.includes(flowNode.nodeName)) {
          this.$message({
            message: `存在相同的审核节点名称:${flowNode.nodeName}`,
            type: 'warning'
          })
          return
        }
        existNodeNames.push(flowNode.nodeName)

        flowNode.orders = i +1
        flowNode.approveIds = flowNode.approveArray.join(',')

      }
      this.submitting = true
      // 执行保存入库相关操作
      const res = await this.$request.post(`/v1/manager/flow/node/add`, flowNodes).catch(err => {
        this.submitting = false
      })
      this.submitting = false
      if (res && res.success) {
        this.$emit('callback', flowNodes)
        this.isShow = false
      }
    }
  }
}
</script>
<style lang="scss" scoped>
  .form-body-item {
    background-color:#fff;
    padding:10px;
    border-radius: 4px;
    margin-bottom: 10px;

  }
  .form-item-zones {
    border-bottom: solid 1px #DCDFE6 ;
    margin-bottom:20px;
    .block-title {
      font-weight:bold;
      display: inline-block;
      line-height: 36px;
      font-size:14px;
    }

  }
  .step-item-oprations {
    display: none;
  }
  .step-item-wrap {
    /deep/ .el-step__main {
      padding: 0 10px 10px;
    }
  }
  .step-item-wrap:hover {
    /deep/ .el-step__main {
      background-color:#F5F7FA;
      border-radius: 5px;
    }
    .step-item-oprations {
      display: block;
    }
  }
  .step-item-content-wrap {
    padding: 10px;
  }
  .btn-choose {
    margin-left:10px;
    vertical-align: middle;
  }

  .person-item /deep/ {
    display: inline-block;
    .el-tag {
      vertical-align: middle;
      position: relative;
      overflow: initial;
      margin-bottom:10px;
      .el-tag__close {
        padding: 2px;
        box-sizing: content-box;
        position:absolute;
        background-color: #D1E9FF;
        // display: none;
        top:-10px;
        right: -10px;
        &:hover {
          background-color: #1890ff;
        }
      }
      &:hover {
        .el-tag__close {
          display:block;
        }
      }
    }
    .el-tag + .el-tag {
      margin-left: 40px;
      &:before {
        content: '或';
        position: absolute;
        left: -26px;
        color: #666666;
      }
    }
  }
  // 会签模式步骤的审批人之间是用和
  .info-content /deep/ .step-item-signmodel-1 .person-item {
    .el-tag + .el-tag {
      &:before {
        content: '和'
      }
    }
  }
  .step-name {
    color:#101010;
    font-weight: bold;
  }
</style>

步骤编辑弹出层

<template>
  <el-dialog width="460px" class="hkt-dlg-darkblue" :title="(form.id ? '编辑' : '新建') + '步骤'" :visible.sync="isShow" append-to-body destroy-on-close :close-on-click-modal="false">
    <div class="dialog-wrap">
      <el-form ref="form" :model="form">
        <el-form-item label="步骤名称" prop="nodeName" :rules="[{required: true, message: '请填写步骤名称', trigger: 'change'}]">
          <el-input v-model="form.nodeName" style="width:250px"></el-input>
        </el-form-item>
        <el-form-item label="是否会签模式">
          <el-switch v-model="form.signModel" :active-value="1" :inactive-value="2" active-text="是" inactive-text="否"></el-switch>
        </el-form-item>
        <el-form-item label="是否可办结">
          <el-switch v-model="form.isOver" :active-value="1" :inactive-value="0" active-text="是" inactive-text="否"></el-switch>
        </el-form-item>
      </el-form>

    </div>
    <div slot="footer" class="dialog-footer-wrap text-center">
        <el-button @click="isShow = false">取 消</el-button>
        <el-button type="primary" @click="save">确 定</el-button>
      </div>
  </el-dialog>
</template>
<script>
export default {
  name: 'stepFormModal',
  desc: '流程步骤表单',
  props: {
    show: {
      type: Boolean,
      required: true,
      default: false
    },
    formData: {
      // 适用于编辑
      type: Object,
      required: false
    },
    mode: {
      type: String,
      required: false
    }
  },
  data () {
    return {
      form: {
        nodeName: '',
        signModel: 2,
        isOver: 0
      }
    }
  },
  computed: {
    isShow: {
      get () {
        return this.show
      },
      set (val) {
        this.$emit('update:show', val)
      }
    }
  },
  created() {
    this.init()
  },
  methods: {
    init () {
      if (this.formData) {
        this.form = this.$hktUtils.deepClone(this.formData)
      }
    },
    save () {
      this.$refs['form'].validate(async (valid) => {
        if (valid) {
          this.$emit('callback', this.form)
          this.isShow = false
        }
      })
    }
  }
}
</script>
<style lang="scss" scoped>
  .form-body-item {
    background-color:#fff;
    padding:10px;
    border-radius: 4px;
    margin-bottom: 10px;

  }
  .form-item-zones {
    border-bottom: solid 1px #DCDFE6 ;
    margin-bottom:20px;
    .block-title {
      font-weight:bold;
      display: inline-block;
      line-height: 36px;
      font-size:14px;
    }

  }
  .step-item-oprations {
    display: none;
  }
  .step-item-wrap:hover {
    background-color:#D1E9FF;
    .step-item-oprations {
      display: block;
    }
  }
  .step-item-content-wrap {
    padding: 10px;
  }
  .btn-choose {
    margin-left:10px;
    vertical-align: middle;
  }

  .person-item /deep/ {
    display: inline-block;
    .el-tag {
      vertical-align: middle;
      position: relative;
      overflow: initial;
      .el-tag__close {
        padding: 2px;
        box-sizing: content-box;
        position:absolute;
        background-color: #D1E9FF;
        // display: none;
        top:-10px;
        right: -10px;
        &:hover {
          background-color: #1890ff;
        }
      }
      &:hover {
        .el-tag__close {
          display:block;
        }
      }
    }
    .el-tag + .el-tag {
      margin-left: 40px;
      &:before {
        content: '或';
        position: absolute;
        left: -26px;
        color: #666666;
      }
    }
  }
  .step-name {
    color:#101010;
    font-weight: bold;
  }
</style>

该组件涉及人员选择相关公共组件的封装,详细见 分享一个在管理系统中一些公共组件的调用方式

0

评论 (0)

取消