Bläddra i källkod

仿钉钉流程设计器- 审批节点,抄送节点增加表单字段权限配置

jason 1 år sedan
förälder
incheckning
672db903f3

+ 8 - 4
src/components/SimpleProcessDesignerV2/src/NodeHandler.vue

@@ -52,11 +52,13 @@ const props = defineProps({
 
 const emits = defineEmits(['update:childNode'])
 
+
 const addNode = (type: number) => {
   popoverShow.value = false
   if (type === NodeType.USER_TASK_NODE) {
+    const id =  'Activity_'+ generateUUID();
     const data: SimpleFlowNode = {
-      id:  'Activity_'+ generateUUID(),
+      id:  id,
       name: NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string,
       showText: '',
       type: NodeType.USER_TASK_NODE,
@@ -64,11 +66,12 @@ const addNode = (type: number) => {
       attributes: {
         approveMethod: 1,
         candidateStrategy: CandidateStrategy.USER,
-        candidateParam: undefined
+        candidateParam: undefined,
+        fieldsPermission: undefined,
       },
       childNode: props.childNode
     }
-    emits('update:childNode', data)
+    emits('update:childNode', data);
   }
   if (type === NodeType.COPY_TASK_NODE) {
     const data: SimpleFlowNode = {
@@ -79,7 +82,8 @@ const addNode = (type: number) => {
       // 审批节点配置
       attributes: {
         candidateStrategy: CandidateStrategy.USER,
-        candidateParam: undefined
+        candidateParam: undefined,
+        fieldsPermission: undefined
       },
       childNode: props.childNode
     }

+ 7 - 4
src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue

@@ -1,6 +1,9 @@
 <template>
   <!-- 开始节点 -->
-  <StartEventNode v-if="currentNode && currentNode.type === NodeType.START_EVENT_NODE" :flow-node ="currentNode" />
+  <StartEventNode
+        v-if="currentNode && currentNode.type === NodeType.START_EVENT_NODE" 
+        :flow-node ="currentNode" 
+        @update:model-value="handleModelValueUpdate" />
   <!-- 审批节点 -->
   <UserTaskNode 
         v-if="currentNode && currentNode.type === NodeType.USER_TASK_NODE" 
@@ -39,11 +42,11 @@ const emits = defineEmits(['update:flowNode'])
 
 const currentNode = ref<SimpleFlowNode>(props.flowNode);
 
-// 重要:监控节点变化. 能动态新增、删除节点
+// 重要:监控节点变化. 重新绘制节点
 watch(() => props.flowNode, (newValue) => {  
-  console.log("Flow Nodes changed", newValue);
   currentNode.value = newValue;  
-});
+}
+);
 
 const handleModelValueUpdate = (updateValue) => {
   console.log('Process Node Tree handleModelValueUpdate', updateValue)

+ 1 - 3
src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue

@@ -65,7 +65,6 @@ const saveSimpleFlowModel = async () => {
   }
   errorNodes = []
   validateNode(processNodeTree.value, errorNodes)
-  console.log('errorNodes is ', errorNodes)
   if (errorNodes.length > 0) {
     errorDialogVisible.value = true
     return;
@@ -76,7 +75,6 @@ const saveSimpleFlowModel = async () => {
   }
 
   const result = await saveBpmSimpleModel(data)
-  console.log('save the result is ', result)
   if (result) {
     message.success('修改成功')
     close()
@@ -148,7 +146,7 @@ const zoomIn = () => {
 onMounted(async () => {
   const result = await getBpmSimpleModel(props.modelId)
   if (result) {
-    console.log('get the result is ', result)
+    console.log('the result is ', result)
     processNodeTree.value = result
   }
 })

+ 49 - 13
src/components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue

@@ -143,6 +143,39 @@
           </el-form>
         </div>
       </el-tab-pane>
+      <el-tab-pane label="表单字段权限" v-if ="formType === 10">
+        <div class="field-setting-pane">
+          <div class="field-setting-desc">字段权限</div>
+          <div class="field-permit-title">
+            <div class="setting-title-label first-title">
+              字段名称
+            </div>
+            <div class="other-titles">
+              <span class="setting-title-label">可编辑</span>
+              <span class="setting-title-label">只读</span>
+              <span class="setting-title-label">隐藏</span>
+            </div>
+          </div>
+          <div
+            class="field-setting-item"
+            v-for="(item, index) in currentNode.attributes.fieldsPermission"
+            :key="index"
+          >
+            <div class="field-setting-item-label"> {{ item.title }} </div>
+            <el-radio-group class="field-setting-item-group" v-model="item.permission">
+              <div class="item-radio-wrap">
+                <el-radio value="1" size="large" label="1" disabled><span></span></el-radio>
+              </div>
+              <div class="item-radio-wrap">
+                <el-radio value="2" size="large" label="2"><span></span></el-radio>
+              </div>
+              <div class="item-radio-wrap">
+                <el-radio value="3" size="large" label="3"><span></span></el-radio>
+              </div>
+            </el-radio-group>
+          </div>
+        </div>
+      </el-tab-pane>
     </el-tabs>
     <template #footer>
       <el-divider />
@@ -155,6 +188,7 @@
 </template>
 <script setup lang='ts'>
 import { SimpleFlowNode, CandidateStrategy,NodeType, NODE_DEFAULT_NAME } from '../consts'
+import { getDefaultFieldsPermission } from '../utils'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import * as RoleApi from '@/api/system/role'
 import * as DeptApi from '@/api/system/dept'
@@ -171,13 +205,9 @@ const props = defineProps({
     required: true
   }
 })
-// 定义事件,更新父组件
-const emits = defineEmits<{
-  'update:modelValue': [node: SimpleFlowNode]
-}>()
 // 是否可见
 const settingVisible = ref(false)
-// 当前节点信息,保存后,需要更新父组件
+// 当前节点信息
 const currentNode = ref<SimpleFlowNode>(props.flowNode)
 const roleOptions = inject<Ref<RoleApi.RoleVO[]>>('roleList') // 角色列表
 const postOptions = inject<Ref<PostApi.PostVO[]>>('postList') // 岗位列表
@@ -185,6 +215,8 @@ const userOptions = inject<Ref<UserApi.UserVO[]>>('userList') // 用户列表
 const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList')  // 部门列表
 const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList') // 用户组列表
 const deptTreeOptions = inject('deptTree') // 部门树
+const formType = inject('formType') // 表单类型
+const formFields = inject<Ref<string[]>>('formFields')
 
 // 抄送人策略, 去掉发起人自选
 const copyUserStrategies = computed( ()=> {
@@ -203,14 +235,24 @@ const closeDrawer = () => {
 const saveConfig = () => {
   currentNode.value.attributes.candidateParam = candidateParamArray.value?.join(',')
   currentNode.value.showText = getShowText()
-  emits('update:modelValue', currentNode.value)
   settingVisible.value = false
 }
 
 const open = () => {
   settingVisible.value = true
 }
-defineExpose({ open }) // 提供 open 方法,用于打开抄送人配置抽屉
+//  修改当前编辑的节点, 由父组件传过来
+const setCurrentNode = (node:SimpleFlowNode) => {
+  currentNode.value = node;
+  currentNode.value.attributes.fieldsPermission = node.attributes.fieldsPermission || getDefaultFieldsPermission(formFields?.value) 
+  const strCandidateParam = node.attributes?.candidateParam
+  if(strCandidateParam) {
+    candidateParamArray.value = strCandidateParam.split(',').map(item=> +item)
+  }
+}
+
+defineExpose({ open, setCurrentNode }) // 暴露方法给父组件
+
 
 const changeCandidateStrategy = () => {
   candidateParamArray.value = []
@@ -227,7 +269,6 @@ const getShowText = () : string => {
           candidateNames.push(item.nickname)
         }
       })
-      console.log("candidateNames is ", candidateNames)
       showText = `指定成员:${candidateNames.join(',')}`
     }
   }
@@ -304,11 +345,6 @@ const blurEvent = () => {
   currentNode.value.name = currentNode.value.name || NODE_DEFAULT_NAME.get(NodeType.COPY_TASK_NODE) as string
 }
 
-onMounted(async () => {
-  console.log('candidateParam', currentNode.value.attributes?.candidateParam)
-  candidateParamArray.value = currentNode.value.attributes?.candidateParam?.split(',').map(item=> +item)
-  console.log('candidateParamArray.value', candidateParamArray.value)
-})
 </script>
 
 <style lang="scss" scoped>

+ 55 - 17
src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue

@@ -160,6 +160,39 @@
           </el-form>
         </div>
       </el-tab-pane>
+      <el-tab-pane label="表单字段权限" v-if ="formType === 10">
+        <div class="field-setting-pane">
+          <div class="field-setting-desc">字段权限</div>
+          <div class="field-permit-title">
+            <div class="setting-title-label first-title">
+              字段名称
+            </div>
+            <div class="other-titles">
+              <span class="setting-title-label">可编辑</span>
+              <span class="setting-title-label">只读</span>
+              <span class="setting-title-label">隐藏</span>
+            </div>
+          </div>
+          <div
+            class="field-setting-item"
+            v-for="(item, index) in currentNode.attributes.fieldsPermission"
+            :key="index"
+          >
+            <div class="field-setting-item-label"> {{ item.title }} </div>
+            <el-radio-group class="field-setting-item-group" v-model="item.permission">
+              <div class="item-radio-wrap">
+                <el-radio value="1" size="large" label="1"><span></span></el-radio>
+              </div>
+              <div class="item-radio-wrap">
+                <el-radio value="2" size="large" label="2"><span></span></el-radio>
+              </div>
+              <div class="item-radio-wrap">
+                <el-radio value="3" size="large" label="3"><span></span></el-radio>
+              </div>
+            </el-radio-group>
+          </div>
+        </div>
+      </el-tab-pane>
     </el-tabs>
     <template #footer>
       <el-divider />
@@ -174,6 +207,7 @@
 <script setup lang="ts">
 import { SimpleFlowNode, APPROVE_METHODS, CandidateStrategy, NodeType, NODE_DEFAULT_NAME } from '../consts'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { getDefaultFieldsPermission } from '../utils'
 import { defaultProps } from '@/utils/tree'
 import * as RoleApi from '@/api/system/role'
 import * as DeptApi from '@/api/system/dept'
@@ -190,11 +224,9 @@ const props = defineProps({
     required: true
   }
 })
-const emits = defineEmits<{
-  'update:modelValue': [node: SimpleFlowNode]
-}>()
+
 const notAllowedMultiApprovers = ref(false)
-const currentNode = ref<SimpleFlowNode>(props.flowNode)
+const currentNode = ref<SimpleFlowNode>(props.flowNode);
 const settingVisible = ref(false)
 const roleOptions = inject<Ref<RoleApi.RoleVO[]>>('roleList') // 角色列表
 const postOptions = inject<Ref<PostApi.PostVO[]>>('postList') // 岗位列表
@@ -202,6 +234,8 @@ const userOptions = inject<Ref<UserApi.UserVO[]>>('userList') // 用户列表
 const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList')  // 部门列表
 const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList') // 用户组列表
 const deptTreeOptions = inject('deptTree') // 部门树
+const formType = inject('formType') // 表单类型
+const formFields = inject<Ref<string[]>>('formFields')
 const candidateParamArray = ref<any[]>([])
 
 const closeDrawer = () => {
@@ -210,8 +244,6 @@ const closeDrawer = () => {
 const saveConfig = () => {
   currentNode.value.attributes.candidateParam = candidateParamArray.value?.join(',')
   currentNode.value.showText = getShowText()
-  console.log('currentNode value is ', currentNode.value)
-  emits('update:modelValue', currentNode.value)
   settingVisible.value = false
 }
 const getShowText = () : string => {
@@ -225,7 +257,6 @@ const getShowText = () : string => {
           candidateNames.push(item.nickname)
         }
       })
-      console.log("candidateNames is ", candidateNames)
       showText = `指定成员:${candidateNames.join(',')}`
     }
   }
@@ -300,7 +331,23 @@ const getShowText = () : string => {
 const open = () => {
   settingVisible.value = true
 }
-defineExpose({ open }) // 提供 open 方法,用于打开抽屉
+//  修改当前编辑的节点, 由父组件传过来
+const setCurrentNode = (node:SimpleFlowNode) => {
+  currentNode.value = node;
+  currentNode.value.attributes.fieldsPermission = node.attributes.fieldsPermission || getDefaultFieldsPermission(formFields?.value) 
+  const strCandidateParam = node.attributes?.candidateParam
+  if(strCandidateParam) {
+    candidateParamArray.value = strCandidateParam.split(',').map(item=> +item)
+  }
+  if (currentNode.value.attributes?.candidateStrategy === CandidateStrategy.USER && candidateParamArray.value?.length <= 1) {
+    notAllowedMultiApprovers.value = true
+  } else {
+    notAllowedMultiApprovers.value = false
+  }
+}
+
+defineExpose({ open, setCurrentNode }) // 暴露方法给父组件
+
 const changeCandidateStrategy = () => {
   candidateParamArray.value = []
   currentNode.value.attributes.approveMethod = 1
@@ -330,15 +377,6 @@ const blurEvent = () => {
   showInput.value = false
   currentNode.value.name = currentNode.value.name || NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string
 }
-onMounted(async () => {
-  candidateParamArray.value = currentNode.value.attributes?.candidateParam?.split(',').map(item=> +item)
-  console.log('candidateParamArray.value', candidateParamArray.value)
-  if (currentNode.value.attributes?.candidateStrategy === CandidateStrategy.USER && candidateParamArray.value?.length <= 1) {
-    notAllowedMultiApprovers.value = true
-  } else {
-    notAllowedMultiApprovers.value = false
-  }
-})
 </script>
 
 <style lang="scss" scoped>

+ 4 - 8
src/components/SimpleProcessDesignerV2/src/nodes/CopyTaskNode.vue

@@ -8,7 +8,7 @@
             v-if="showInput"
             type="text"
             class="editable-title-input"
-            @blur="blurEvent($event)"
+            @blur="blurEvent()"
             v-mountedFocus
             v-model="currentNode.name"
             :placeholder="currentNode.name"
@@ -34,11 +34,11 @@
       <!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
       <NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
     </div>
+    <!-- 其实只需要一个全局抄送节点配置就行, 不需要多个。点击配置的时候传值.  TODO 后面优化 -->
     <CopyTaskNodeConfig
       v-if="currentNode"
       ref="nodeSetting"
       :flow-node="currentNode"
-      @update:model-value="handleModelValueUpdate"
      />
   </div>
 </template>
@@ -72,7 +72,7 @@ watch(
 // 显示节点名称输入框
 const showInput = ref(false)
 // 节点名称输入框失去焦点
-const blurEvent = (event) => {
+const blurEvent = () => {
   showInput.value = false
   currentNode.value.name = currentNode.value.name || NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string
 }
@@ -83,6 +83,7 @@ const clickEvent = () => {
 const nodeSetting = ref()
 // 打开节点配置
 const openNodeConfig = () => {
+  nodeSetting.value.setCurrentNode(currentNode.value);
   nodeSetting.value.open()
 }
 
@@ -107,11 +108,6 @@ const copyNode = () => {
   currentNode.value = newCopyNode
   emits('update:modelValue', currentNode.value)
 }
-// 接收抄送人配置组件传过来的事件,并且更新节点 信息
-const handleModelValueUpdate = (updateValue) => {
-  emits('update:modelValue', updateValue)
-}
-
 </script>
 
 <style lang='scss' scoped>

+ 0 - 1
src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue

@@ -108,7 +108,6 @@ const clickEvent = (index: number) => {
 }
 
 const conditionNodeConfig = (nodeId: string) => {
-  console.log("proxy.$refs", proxy.$refs);
   const conditionNode = proxy.$refs[nodeId][0];
   conditionNode.open()
 }

+ 13 - 5
src/components/SimpleProcessDesignerV2/src/nodes/StartEventNode.vue

@@ -7,7 +7,7 @@
         <Icon icon="ep:arrow-right-bold" />
       </div>
       <!-- 传递子节点给添加节点组件。会在子节点后面添加节点 -->
-      <node-handler v-if="currentNode" v-model:child-node="currentNode.childNode" />
+      <NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
     </div>
   </div>
 </template>
@@ -23,12 +23,20 @@ const props = defineProps({
     default: () => null
   }
 });
+// 定义事件,更新父组件。
+const emits = defineEmits<{
+  'update:modelValue': [node: SimpleFlowNode | undefined]
+}>()
+
 const currentNode = ref<SimpleFlowNode>(props.flowNode);
 
-watch(() => props.flowNode, (newValue) => {  
-  console.log('start node value changed=================>', newValue);
-  currentNode.value = newValue;  
-}); 
+watch(
+  () => props.flowNode,
+  (newValue) => {
+    currentNode.value = newValue
+  }
+)
+
 </script>
 <style lang="scss" scoped>
 

+ 8 - 9
src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue

@@ -8,7 +8,7 @@
             v-if="showInput"
             type="text"
             class="editable-title-input"
-            @blur="blurEvent($event)"
+            @blur="blurEvent()"
             v-mountedFocus
             v-model="currentNode.name"
             :placeholder="currentNode.name"
@@ -34,11 +34,11 @@
       <NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
     </div>
   </div>
+  <!-- 其实只需要一个全局审批节点配置就行, 不需要多个。点击配置的时候传值.  TODO 后面优化 -->
   <UserTaskNodeConfig
     v-if="currentNode"
     ref="nodeSetting"
     :flow-node="currentNode"
-    @update:model-value="handleModelValueUpdate"
   />
 </template>
 <script setup lang="ts">
@@ -63,8 +63,11 @@ const currentNode = ref<SimpleFlowNode>(props.flowNode)
 const nodeSetting = ref()
 // 打开节点配置
 const openNodeConfig = () => {
+  // 把当前节点传递给配置组件
+  nodeSetting.value.setCurrentNode(currentNode.value);
   nodeSetting.value.open()
 }
+// 监控节点变化
 watch(
   () => props.flowNode,
   (newValue) => {
@@ -74,7 +77,7 @@ watch(
 // 显示节点名称输入框
 const showInput = ref(false)
 // 节点名称输入框失去焦点
-const blurEvent = (event) => {
+const blurEvent = () => {
   showInput.value = false
   currentNode.value.name = currentNode.value.name || NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string
 }
@@ -82,16 +85,12 @@ const blurEvent = (event) => {
 const clickEvent = () => {
   showInput.value = true
 }
-const handleModelValueUpdate = (updateValue) => {
-  emits('update:modelValue', updateValue)
-  console.log('user task node handleModelValueUpdate', updateValue)
-}
+
 const deleteNode = () => {
-  console.log('the child node is ', currentNode.value.childNode)
   emits('update:modelValue', currentNode.value.childNode)
 }
+
 const copyNode = () => {
-  // const oldChildNode = currentNode.value.childNode
   const newCopyNode: SimpleFlowNode = {
     id: generateUUID(),
     name: currentNode.value.name,

+ 16 - 0
src/components/SimpleProcessDesignerV2/src/utils.ts

@@ -5,3 +5,19 @@ export const getDefaultConditionNodeName = (index:number, defaultFlow: boolean)
   }
   return '条件' + (index+1)
 }
+
+// 获得默认的表单字段权限.  
+export const getDefaultFieldsPermission = (formFields: string[] | undefined) =>{
+  const defaultFieldsPermission : any[] = [];
+  if(formFields){
+    formFields.forEach((fieldStr: string) => {
+      const { field, title } = JSON.parse(fieldStr)
+      defaultFieldsPermission.push({
+        field,
+        title,
+        permission: '2' // 只读
+      })
+    })
+  }
+  return  defaultFieldsPermission;
+}

+ 76 - 0
src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss

@@ -517,6 +517,7 @@
     display: flex;
     height: 24px;
     line-height: 24px;
+    font-size: 16px;
     cursor: pointer;
     align-items: center;
   }
@@ -545,7 +546,82 @@
   }
 }
 
+// 表单字段权限
+.field-setting-pane {
+  display: flex;
+  flex-direction: column;
+  padding: 16px 0;
+  font-size: 14px;
+
+  .field-setting-desc {
+    padding-right: 8px;
+    margin-bottom: 16px;
+    font-size: 16px;
+    font-weight: 700;
+  }
+
+  .field-permit-title {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    height: 45px;
+    padding-left: 12px;
+    line-height: 45px;
+    background-color: #f8fafc0a;
+    border: 1px solid #1f38581a;
+   
+    .first-title {
+      text-align: left !important;
+    }
+
+    .other-titles {
+      display: flex;
+      justify-content: space-between;
+    }
+
+    .setting-title-label {
+      display: inline-block;
+      width: 110px;
+      padding: 5px 0;
+      font-size: 13px;
+      font-weight: 700;
+      color: #000;
+      text-align: center;
+    }
+    
+  }
+  
+  .field-setting-item {
+    align-items: center;
+    display: flex;
+    justify-content: space-between;
+    height: 38px;
+    padding-left: 12px;
+    border: 1px solid #1f38581a;
+    border-top: 0;
+
+    .field-setting-item-label {
+      display: inline-block;
+      width: 110px;
+      min-height: 16px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      cursor: text;
+    }
+
+    .field-setting-item-group {
+      display: flex;
+      justify-content: space-between;
 
+      .item-radio-wrap {
+        display: inline-block;
+        width: 110px;
+        text-align: center;
+      }
+    }
+  }
+}
 
 // 节点连线气泡卡片样式
 .handler-item-wrapper {