Переглянути джерело

仿钉钉设计流程:增加抄送人节点

jason 1 рік тому
батько
коміт
f79c29d168

+ 13 - 3
src/components/SimpleProcessDesigner/src/addNode.vue

@@ -24,6 +24,12 @@
             <p>条件分支</p>
           </a>
           -->
+          <a class="add-node-popover-item notifier" @click="addType(2)">
+            <div class="item-wrapper">
+              <span class="iconfont"></span>
+            </div>
+            <p>抄送人</p>
+          </a>
           <a class="add-node-popover-item condition" @click="addType(4)">
             <div class="item-wrapper">
               <span class="iconfont"></span>
@@ -86,9 +92,13 @@ const addType = (type) => {
       data = {
         name: '抄送人',
         type: 2,
-        ccSelfSelectFlag: 1,
-        childNode: props.childNodeP,
-        nodeUserList: []
+        error: true,
+         // 抄送节点配置
+        attributes : {
+          candidateStrategy: undefined,
+          candidateParam: undefined
+        },
+        childNode: props.childNodeP
       }
     }
     emits('update:childNodeP', data)

+ 4 - 26
src/components/SimpleProcessDesigner/src/drawer/approverDrawer.vue

@@ -2,7 +2,6 @@
   <el-drawer
     :append-to-body="true"
     v-model="visible"
-    class="set_promoter"
     :show-close="false"
     :size="550"
     :before-close="saveConfig"
@@ -165,12 +164,7 @@ import * as DeptApi from '@/api/system/dept'
 import * as PostApi from '@/api/system/post'
 import * as UserApi from '@/api/system/user'
 import * as UserGroupApi from '@/api/bpm/userGroup'
-defineProps({
-  directorMaxLevel: {
-    type: Number,
-    default: 0
-  }
-})
+
 const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
 const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
 const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
@@ -183,7 +177,7 @@ const candidateConfig = ref({
 })
 let approverConfig = ref({})
 let store = useWorkFlowStoreWithOut()
-let { setApprover, setUserTaskConfig } = store
+let { setApproverDrawer, setUserTaskConfig } = store
 let approverConfig1 = computed(() => store.approverConfig1)
 let approverDrawer = computed(() => store.approverDrawer)
 const userTaskConfig = computed(() => store.userTaskConfig)
@@ -233,32 +227,16 @@ const saveConfig = () => {
   //     flag: true,
   //     id: approverConfig1.value.id
   // })
-  const showText = getApproverShowText()
   setUserTaskConfig({
     value: rawConfig.value,
     flag: true,
     id: userTaskConfig.value.id,
-    showText
   })
-  console.log('after is userTaskConfig', userTaskConfig.value)
   closeDrawer()
 }
-const getApproverShowText = () => {
-  let appoveMethodText = ''
-  approveMethods.forEach((item) => {
-    if (item.value == candidateConfig.value.approveMethod) {
-      appoveMethodText = item.label
-    }
-  })
-  const strategyText = getDictLabel(
-    DICT_TYPE.BPM_TASK_CANDIDATE_STRATEGY,
-    candidateConfig.value.candidateStrategy
-  )
-  return `审批方式:${appoveMethodText} <br/>
-          审批人规则类型:按${strategyText}`
-}
+
 const closeDrawer = () => {
-  setApprover(false)
+  setApproverDrawer(false)
 }
 const changecandidateStrategy = () => {
   candidateConfig.value.candidateParam = []

+ 246 - 0
src/components/SimpleProcessDesigner/src/drawer/copyerDrawer.vue

@@ -0,0 +1,246 @@
+<template>
+  <el-drawer
+    :append-to-body="true"
+    v-model="visible"
+    :show-close="false"
+    :size="550"
+    :before-close="saveConfig"
+  >
+    <template #header>
+      <div class="copy-task-header">抄送人设置</div>
+    </template>
+    <div>
+      <el-form label-position="top" label-width="100px">
+        <el-form-item label="选择抄送人" prop="candidateStrategy">
+          <el-select v-model="candidateConfig.candidateStrategy" style="width: 100%" clearable @change="changecandidateStrategy">
+            <el-option
+              v-for="dict in getIntDictOptions(DICT_TYPE.BPM_TASK_CANDIDATE_STRATEGY)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item
+          v-if="candidateConfig.candidateStrategy == 10"
+          label="指定角色"
+          prop="candidateParam"
+        >
+          <el-select
+            v-model="candidateConfig.candidateParam"
+            clearable
+            multiple
+            style="width: 100%"
+          >
+            <el-option
+              v-for="item in roleOptions"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item
+          v-if="candidateConfig.candidateStrategy == 20 || candidateConfig.candidateStrategy == 21"
+          label="指定部门"
+          prop="candidateParam"
+          span="24"
+        >
+          <el-tree-select
+            ref="treeRef"
+            v-model="candidateConfig.candidateParam"
+            :data="deptTreeOptions"
+            :props="defaultProps"
+            empty-text="加载中,请稍后"
+            multiple
+            node-key="id"
+            style="width: 100%"
+            show-checkbox
+          />
+        </el-form-item>
+        <el-form-item
+          v-if="candidateConfig.candidateStrategy == 22"
+          label="指定岗位"
+          prop="candidateParam"
+          span="24"
+        >
+          <el-select
+            v-model="candidateConfig.candidateParam"
+            clearable
+            multiple
+            style="width: 100%"
+          >
+            <el-option
+              v-for="item in postOptions"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item
+          v-if="
+            candidateConfig.candidateStrategy == 30 ||
+            candidateConfig.candidateStrategy == 31 ||
+            candidateConfig.candidateStrategy == 32
+          "
+          label="指定用户"
+          prop="candidateParam"
+          span="24"
+        >
+          <el-select
+            v-model="candidateConfig.candidateParam"
+            clearable
+            multiple
+            style="width: 100%"
+          >
+            <el-option
+              v-for="item in userOptions"
+              :key="item.id"
+              :label="item.nickname"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item
+          v-if="candidateConfig.candidateStrategy === 40"
+          label="指定用户组"
+          prop="candidateParam"
+        >
+          <el-select
+            v-model="candidateConfig.candidateParam"
+            clearable
+            multiple
+            style="width: 100%"
+          >
+            <el-option
+              v-for="item in userGroupOptions"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item
+          v-if="candidateConfig.candidateStrategy === 60"
+          label="流程表达式"
+          prop="candidateParam"
+        >
+          <el-input
+            type="textarea"
+            v-model="candidateConfig.candidateParam[0]"
+            clearable
+            style="width: 100%"
+          />
+        </el-form-item>
+      </el-form>
+    </div>
+    <div class="demo-drawer__footer clear">
+      <el-button type="primary" @click="saveConfig">确 定</el-button>
+      <el-button @click="closeDrawer">取 消</el-button>
+    </div>
+  </el-drawer>
+</template>
+<script lang="ts" setup>
+import { ref, watch, computed, toRaw } from 'vue'
+import { useWorkFlowStoreWithOut } from '@/store/modules/simpleWorkflow'
+import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as RoleApi from '@/api/system/role'
+import * as DeptApi from '@/api/system/dept'
+import * as PostApi from '@/api/system/post'
+import * as UserApi from '@/api/system/user'
+import * as UserGroupApi from '@/api/bpm/userGroup'
+
+const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
+const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
+const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
+const deptTreeOptions = ref() // 部门树
+const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
+const candidateConfig = ref({
+  candidateStrategy: undefined,
+  candidateParam: [],
+})
+const store = useWorkFlowStoreWithOut()
+const { setCopyerDrawer, setCopyerConfig } = store
+
+const copyerDrawer = computed(() => store.copyerDrawer)
+const copyerConfig = computed(() => store.copyerConfig)
+
+const visible = computed({
+  get() {
+    return copyerDrawer.value
+  },
+  set() {
+    closeDrawer()
+  }
+})
+watch(copyerConfig, (val) => {
+  if (val.value.attributes) {
+    console.log('val.value.attributes', val.value.attributes);
+    candidateConfig.value.candidateStrategy = val.value.attributes.candidateStrategy
+    const candidateParamStr =  val.value.attributes.candidateParam;
+    if(val.value.attributes.candidateStrategy === 60) {
+      candidateConfig.value.candidateParam = [candidateParamStr]
+    } else {
+      if(candidateParamStr){
+        candidateConfig.value.candidateParam =  candidateParamStr.split(',').map((item) => +item)
+      }
+    }
+    
+    // candidateConfig.value = val.value.attributes
+  }
+})
+
+
+const saveConfig = () => {
+  const rawConfig = toRaw(copyerConfig.value)
+  const { candidateStrategy , candidateParam} = toRaw(candidateConfig.value);
+  const candidateParamStr = candidateParam.join(',')
+  rawConfig.value.attributes = {
+    candidateStrategy,
+    candidateParam: candidateParamStr
+  } 
+  rawConfig.flag = true
+  // TODO 进行校验
+  // setApproverConfig({
+  //     value: approverConfig.value,
+  //     flag: true,
+  //     id: approverConfig1.value.id
+  // })
+
+  setCopyerConfig({
+    value: rawConfig.value,
+    flag: true,
+    id: copyerConfig.value.id,
+  })
+  console.log('after is copyerConfig', copyerConfig.value)
+  closeDrawer()
+}
+
+const closeDrawer = () => {
+  setCopyerDrawer(false)
+}
+const changecandidateStrategy = () => {
+  candidateConfig.value.candidateParam = []
+}
+onMounted(async () => {
+  // 获得角色列表
+  roleOptions.value = await RoleApi.getSimpleRoleList()
+
+  postOptions.value = await PostApi.getSimplePostList()
+  // 获得用户列表
+  userOptions.value = await UserApi.getSimpleUserList()
+  // 获得部门列表
+  const deptOptions = await DeptApi.getSimpleDeptList()
+  deptTreeOptions.value = handleTree(deptOptions, 'id')
+  // 获得用户组列表
+  userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
+})
+</script>
+<style lang="scss" scoped>
+.copy-task-header {
+  font-size: 16px !important;
+}
+</style>

+ 61 - 50
src/components/SimpleProcessDesigner/src/nodeWrap.vue

@@ -22,12 +22,13 @@
             v-if="isInput"
             type="text"
             class="ant-input editable-title-input"
-            @blur="blurEvent()"
-            @focus="$event.currentTarget.select()"
+            @blur="blurEvent(-1)"
+            @focus="$event.currentTarget?.select()"
+            v-mountedFoucs
             v-model="nodeConfig.name"
             :placeholder="defaultText"
           />
-          <span v-else class="editable-title" @click="clickEvent()">{{ nodeConfig.name }}</span>
+          <span v-else class="editable-title" @click="clickEvent(-1)">{{ nodeConfig.name }}</span>
           <i class="anticon anticon-close close" @click="delNode"></i>
         </template>
       </div>
@@ -60,7 +61,8 @@
                   type="text"
                   class="ant-input editable-title-input"
                   @blur="blurEvent(index)"
-                  @focus="$event.currentTarget.select()"
+                  @focus="$event.currentTarget?.select()"
+                  v-mountedFoucs
                   v-model="item.name"
                 />
                 <span v-else class="editable-title" @click="clickEvent(index)">{{
@@ -135,9 +137,8 @@ let props = defineProps({
 let defaultText = computed(() => {
   return placeholderList[props.nodeConfig.type]
 })
-
-let isInputList = ref([])
-let isInput = ref(false)
+const isInputList = ref<boolean[]>([])
+const isInput = ref<boolean>(false)
 const resetConditionNodesErr = () => {
   for (var i = 0; i < props.nodeConfig.conditionNodes.length; i++) {
     // eslint-disable-next-line vue/no-mutating-props
@@ -160,20 +161,20 @@ onMounted(() => {
 let emits = defineEmits(['update:nodeConfig'])
 let store = useWorkFlowStoreWithOut()
 let {
-  setPromoter,
-  setApprover,
-  setCopyer,
-  setCondition,
+  setApproverDrawer,
+  setCopyerDrawer,
+  // setCondition,
   setCopyerConfig,
-  setConditionsConfig,
+  // setConditionsConfig,
   setUserTaskConfig
 } = store
+// ???
+const  isTried = computed(() => store.isTried) 
 // 审批节点的配置
 const userTaskConfig = computed(() => store.userTaskConfig)
-let isTried = computed(() => store.isTried)
-let approverConfig1 = computed(() => store.approverConfig1)
-let copyerConfig1 = computed(() => store.copyerConfig1)
-let conditionsConfig1 = computed(() => store.conditionsConfig1)
+// 抄送节点的配置
+const copyerConfig = computed(() => store.copyerConfig)
+// let conditionsConfig1 = computed(() => store.conditionsConfig1)
 const showText = computed(() => {
   if (props.nodeConfig.type == 0) return '发起人'
   if (props.nodeConfig.type == 1) {
@@ -186,42 +187,49 @@ const showText = computed(() => {
       return ''
     }
   }
-  return copyerStr(props.nodeConfig)
-})
-watch(userTaskConfig, (approver) => {
-  if (approver.flag && approver.id === _uid) {
-    emits('update:nodeConfig', approver.value)
+  if(props.nodeConfig.type === 2) {
+    if(props.nodeConfig.attributes) {
+      return copyerStr( props.nodeConfig.attributes.candidateStrategy)
+    } else {
+      return ''
+    }
+    
   }
+  return ''
 })
-watch(approverConfig1, (approver) => {
+watch(userTaskConfig, (approver) => {
   if (approver.flag && approver.id === _uid) {
     emits('update:nodeConfig', approver.value)
   }
 })
-watch(copyerConfig1, (copyer) => {
+watch(copyerConfig, (copyer) => {
+  console.log('copyer',copyer)
   if (copyer.flag && copyer.id === _uid) {
+    console.log('copyer id is equal',copyer)
     emits('update:nodeConfig', copyer.value)
   }
 })
-watch(conditionsConfig1, (condition) => {
-  if (condition.flag && condition.id === _uid) {
-    emits('update:nodeConfig', condition.value)
-  }
-})
+
+// watch(conditionsConfig1, (condition) => {
+//   if (condition.flag && condition.id === _uid) {
+//     emits('update:nodeConfig', condition.value)
+//   }
+// })
 
 const clickEvent = (index) => {
-  if (index || index === 0) {
+  if (index >= 0) {
     isInputList.value[index] = true
   } else {
     isInput.value = true
   }
 }
+
 const blurEvent = (index) => {
-  if (index || index === 0) {
+  if (index >= 0) {
     isInputList.value[index] = false
     // eslint-disable-next-line vue/no-mutating-props
     props.nodeConfig.conditionNodes[index].name =
-      props.nodeConfig.conditionNodes[index].name || '条件'
+    props.nodeConfig.conditionNodes[index].name || '条件'
   } else {
     isInput.value = false
     // eslint-disable-next-line vue/no-mutating-props
@@ -278,42 +286,45 @@ const reData = (data, addData) => {
   }
 }
 const setPerson = (priorityLevel) => {
-  var { type } = props.nodeConfig
+  console.log('priorityLevel',priorityLevel)
+  const { type } = props.nodeConfig
+  console.log('type',type)
   if (type == 0) {
-    setPromoter(true)
+    // setPromoter(true)
   } else if (type == 1) {
-    setApprover(true)
-    let showText = undefined
-    if (_uid === userTaskConfig.value.id) {
-      showText = userTaskConfig.value.showText
-    }
+    setApproverDrawer(true)
+    // if (_uid === userTaskConfig.value.id) {
+    //   showText = userTaskConfig.value.showText
+    // }
     setUserTaskConfig({
       value: {
         ...JSON.parse(JSON.stringify(props.nodeConfig)),
         id: 'Activity_' + _uid
       },
       flag: false,
-      id: _uid,
-      showText
+      id: _uid
     })
   } else if (type == 2) {
-    setCopyer(true)
+    setCopyerDrawer(true)
     setCopyerConfig({
-      value: JSON.parse(JSON.stringify(props.nodeConfig)),
-      flag: false,
-      id: _uid
-    })
-  } else {
-    setCondition(true)
-    setConditionsConfig({
       value: {
         ...JSON.parse(JSON.stringify(props.nodeConfig)),
-        id: 'Gateway_' + _uid
+        id: 'Activity_' + _uid
       },
-      priorityLevel,
       flag: false,
       id: _uid
     })
+  } else {
+    // setCondition(true)
+    // setConditionsConfig({
+    //   value: {
+    //     ...JSON.parse(JSON.stringify(props.nodeConfig)),
+    //     id: 'Gateway_' + _uid
+    //   },
+    //   priorityLevel,
+    //   flag: false,
+    //   id: _uid
+    // })
   }
 }
 // const arrTransfer = (index, type = 1) => {

+ 17 - 6
src/components/SimpleProcessDesigner/src/util.ts

@@ -80,13 +80,24 @@ export const getApproverShowText = (approveMethod :number, candidateStrategy: nu
   }
 }
 
-export const copyerStr = (nodeConfig: any) => {
-  if (nodeConfig.nodeUserList.length != 0) {
-    return arrToStr(nodeConfig.nodeUserList)
+export const copyerStr = ( candidateStrategy: number) => {
+  // if (nodeConfig.nodeUserList.length != 0) {
+  //   return arrToStr(nodeConfig.nodeUserList)
+  // } else {
+  //   if (nodeConfig.ccSelfSelectFlag == 1) {
+  //     return '发起人自选'
+  //   }
+  // }
+  console.log('candidateStrategy', candidateStrategy);
+  
+  if(candidateStrategy) {
+    const strategyText = getDictLabel(
+      DICT_TYPE.BPM_TASK_CANDIDATE_STRATEGY,
+      candidateStrategy
+    )
+    return `抄送人类型:按${strategyText}`
   } else {
-    if (nodeConfig.ccSelfSelectFlag == 1) {
-      return '发起人自选'
-    }
+      return ''
   }
 }
 export const conditionStr = (nodeConfig, index) => {

+ 26 - 23
src/components/SimpleProcessDesigner/theme/workflow.css

@@ -1163,11 +1163,7 @@ html {
     box-shadow: 0 0 6px 0 rgba(50, 150, 250, .3)
 }
 
-.dingflow-design .auto-judge.active .close,
-.dingflow-design .auto-judge:active .close,
-.dingflow-design .auto-judge:hover .close {
-    display: block
-}
+
 
 .dingflow-design .auto-judge.error:after {
     border: 1px solid #f25643;
@@ -1183,6 +1179,7 @@ html {
     line-height: 24px;
     width: 258px;
 } */
+
 .dingflow-design  .title-wrapper {
     display: flex;
     justify-content: space-between;
@@ -1195,18 +1192,39 @@ html {
     line-height: 24px;
     width: 220px;
     color: #fff;
+    padding-right: 10px;
+    padding-left: 10px;
 }
+
 .dingflow-design  .title-wrapper .editable-title {
     max-width: 120px;
-    padding-left: 10px;
     overflow: hidden;
     white-space: nowrap;
     text-overflow: ellipsis
 }
-.dingflow-design  .title-wrapper .close {
-    padding-right: 10px;
+
+.dingflow-design .title-wrapper .close {
+    display: none;
+    position: absolute;
+    right: 10px;
+    top: 2px;
+    width: 20px;
+    height: 20px;
+    font-size: 14px;
+    color:#fff;
+    border-radius: 50%;
+    text-align: center;
+    line-height: 20px;
+    z-index: 2
 }
 
+.dingflow-design .title-wrapper:hover .close {
+    display: block
+}
+/* .dingflow-design  .title-wrapper .close {
+    padding-right: 2px;
+} */
+
 /* .dingflow-design  .title-wrapper .priority-title {
     display: inline-block;
     float: right;
@@ -1233,21 +1251,6 @@ html {
     color: #bfbfbf
 }
 
-.dingflow-design .auto-judge .close {
-    display: none;
-    position: absolute;
-    right: -10px;
-    top: -10px;
-    width: 20px;
-    height: 20px;
-    font-size: 14px;
-    color: rgba(0, 0, 0, .25);
-    border-radius: 50%;
-    text-align: center;
-    line-height: 20px;
-    z-index: 2
-}
-
 .dingflow-design .auto-judge .content {
     font-size: 14px;
     color: #191f25;

+ 12 - 0
src/directives/index.ts

@@ -10,4 +10,16 @@ import { hasPermi } from './permission/hasPermi'
 export const setupAuth = (app: App<Element>) => {
   hasRole(app)
   hasPermi(app)
+ 
+}
+
+/**
+ * 导出指令:v-mountedFoucs
+ */
+export const setupMountedFoucs= (app: App<Element>) => {
+  app.directive('mountedFoucs', {
+    mounted(el) {
+      el.focus();
+    }
+  })
 }

+ 4 - 2
src/main.ts

@@ -28,8 +28,8 @@ import '@/plugins/animate.css'
 // 路由
 import router, { setupRouter } from '@/router'
 
-// 权限
-import { setupAuth } from '@/directives'
+// 其它指令 
+import { setupAuth, setupMountedFoucs } from '@/directives'
 
 import { createApp } from 'vue'
 
@@ -60,6 +60,8 @@ const setupAll = async () => {
 
   setupAuth(app)
 
+  setupMountedFoucs(app)
+
   await router.isReady()
 
   app.use(VueDOMPurifyHTML)

+ 4 - 8
src/store/modules/simpleWorkflow.ts

@@ -6,11 +6,10 @@ export const useWorkFlowStore = defineStore('simpleWorkflow', {
     tableId: '',
     isTried: false,
     promoterDrawer: false,
-    flowPermission1: {},
     approverDrawer: false,
     approverConfig1: {},
     copyerDrawer: false,
-    copyerConfig1: {},
+    copyerConfig: {},
     conditionDrawer: false,
     conditionsConfig1: {
       conditionNodes: []
@@ -27,20 +26,17 @@ export const useWorkFlowStore = defineStore('simpleWorkflow', {
     setPromoter(payload) {
       this.promoterDrawer = payload
     },
-    setFlowPermission(payload) {
-      this.flowPermission1 = payload
-    },
-    setApprover(payload) {
+    setApproverDrawer(payload) {
       this.approverDrawer = payload
     },
     setApproverConfig(payload) {
       this.approverConfig1 = payload
     },
-    setCopyer(payload) {
+    setCopyerDrawer(payload) {
       this.copyerDrawer = payload
     },
     setCopyerConfig(payload) {
-      this.copyerConfig1 = payload
+      this.copyerConfig = payload
     },
     setCondition(payload) {
       this.conditionDrawer = payload

+ 2 - 0
src/views/bpm/simpleWorkflow/index.vue

@@ -24,10 +24,12 @@
     </section>
   </div>
   <approverDrawer />
+  <copyerDrawer />
 </template>
 <script lang="ts" setup>
 import nodeWrap from '@/components/SimpleProcessDesigner/src/nodeWrap.vue'
 import approverDrawer from '@/components/SimpleProcessDesigner/src/drawer/approverDrawer.vue'
+import copyerDrawer from '@/components/SimpleProcessDesigner/src/drawer/copyerDrawer.vue'
 import { WorkFlowNode } from '@/components/SimpleProcessDesigner/src/consts'
 import { ref } from 'vue'
 import { saveBpmSimpleModel, getBpmSimpleModel } from '@/api/bpm/simple'