Pārlūkot izejas kodu

仿钉钉流程设计器- 操作按钮设置

jason 1 gadu atpakaļ
vecāks
revīzija
538ad86b03

+ 0 - 22
src/components/SimpleProcessDesignerV2/src/NodeHandler.vue

@@ -81,8 +81,6 @@ const addNode = (type: number) => {
       type: NodeType.USER_TASK_NODE,
       approveMethod: ApproveMethodType.RRANDOM_SELECT_ONE_APPROVE,
       candidateStrategy: CandidateStrategy.USER,
-      // candidateParam: undefined,
-      // fieldsPermission: undefined,
       // 超时处理
       rejectHandler: {
         type: RejectHandlerType.FINISH_PROCESS
@@ -92,20 +90,6 @@ const addNode = (type: number) => {
       },
 
       childNode: props.childNode
-      // 审批节点配置
-      // attributes: {
-      //   approveMethod: ApproveMethodType.RRANDOM_SELECT_ONE_APPROVE,
-      //   candidateStrategy: CandidateStrategy.USER,
-      //   candidateParam: undefined,
-      //   fieldsPermission: undefined,
-      //    // 超时处理
-      //   timeoutHandler: {
-      //     enable: false
-      //   },
-      //   rejectHandler: {
-      //     type: RejectHandlerType.FINISH_PROCESS
-      //   }
-      // },
     }
     emits('update:childNode', data)
   }
@@ -118,12 +102,6 @@ const addNode = (type: number) => {
       candidateStrategy: CandidateStrategy.USER,
       candidateParam: undefined,
       fieldsPermission: undefined,
-      // 审批节点配置
-      // attributes: {
-      //   candidateStrategy: CandidateStrategy.USER,
-      //   candidateParam: undefined,
-      //   fieldsPermission: undefined
-      // },
       childNode: props.childNode
     }
     emits('update:childNode', data)

+ 54 - 0
src/components/SimpleProcessDesignerV2/src/consts.ts

@@ -182,6 +182,8 @@ export type SimpleFlowNode = {
   approveMethod?: ApproveMethodType
   //通过比例
   approveRatio?: number
+  // 审批按钮设置
+  buttonsSetting?: any[]
   // 表单权限
   fieldsPermission?: any[]
   // 审批任务超时处理
@@ -214,6 +216,40 @@ export type ConditionRule = {
   rightSide: string
 }
 
+// 审批操作按钮类型
+export enum OpsButtonType {
+  /**
+   * 通过
+   */
+  APPROVE = 1,
+  /**
+   * 拒绝
+   */
+  REJECT = 2,
+  /**
+   * 转办
+   */
+  TRANSFER = 3,
+  /**
+   * 委派
+   */
+  DELEGATE = 4,
+  /**
+   * 加签
+   */
+  ADD_SIGN = 5,
+  /**
+   * 回退
+   */
+  RETURN = 6
+}
+
+export type ButtonSetting = {
+  id: OpsButtonType
+  displayName: string
+  enable: boolean
+}
+
 export const NODE_DEFAULT_TEXT = new Map<number, string>()
 NODE_DEFAULT_TEXT.set(NodeType.USER_TASK_NODE, '请配置审批人')
 NODE_DEFAULT_TEXT.set(NodeType.COPY_TASK_NODE, '请配置抄送人')
@@ -281,3 +317,21 @@ export const COMPARISON_OPERATORS: DictDataVO = [
     label: '小于等于'
   }
 ]
+// 审批操作按钮名称
+export const OPERATION_BUTTON_NAME = new Map<number, string>()
+OPERATION_BUTTON_NAME.set(OpsButtonType.APPROVE, '通过')
+OPERATION_BUTTON_NAME.set(OpsButtonType.REJECT, '拒绝')
+OPERATION_BUTTON_NAME.set(OpsButtonType.TRANSFER, '转办')
+OPERATION_BUTTON_NAME.set(OpsButtonType.DELEGATE, '委派')
+OPERATION_BUTTON_NAME.set(OpsButtonType.ADD_SIGN, '加签')
+OPERATION_BUTTON_NAME.set(OpsButtonType.RETURN, '回退')
+
+// 默认的按钮权限设置
+export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [
+  { id: OpsButtonType.APPROVE, displayName: '通过', enable: true },
+  { id: OpsButtonType.REJECT, displayName: '拒绝', enable: true },
+  { id: OpsButtonType.TRANSFER, displayName: '转办', enable: false },
+  { id: OpsButtonType.DELEGATE, displayName: '委派', enable: false },
+  { id: OpsButtonType.ADD_SIGN, displayName: '加签', enable: false },
+  { id: OpsButtonType.RETURN, displayName: '回退', enable: false }
+]

+ 137 - 4
src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue

@@ -280,6 +280,40 @@
           </el-form>
         </div>
       </el-tab-pane>
+      <el-tab-pane label="操作按钮设置" name="buttons" v-if="formType === 10">
+        <div class="button-setting-pane">
+          <div class="button-setting-desc">操作按钮</div>
+          <div class="button-setting-title">
+            <div class="button-title-label">操作按钮</div>
+            <div class="pl-4 button-title-label">显示名称</div>
+            <div class="button-title-label">启用</div>
+          </div>
+          <div
+            class="button-setting-item"
+            v-for="(item, index) in configForm.buttonsSetting"
+            :key="index"
+          >
+            <div class="button-setting-item-label"> {{ OPERATION_BUTTON_NAME.get(item.id) }} </div>
+            <div class="button-setting-item-label">
+              <input
+                type="text"
+                class="editable-title-input"
+                @blur="btnDisplayNameBlurEvent(index)"
+                v-mountedFocus
+                v-model="item.displayName"
+                :placeholder="item.displayName"
+                v-if="btnDisplayNameEdit[index]"
+              />
+              <el-button v-else text @click="changeBtnDisplayName(index)"
+                >{{ item.displayName }} &nbsp;<Icon icon="ep:edit"
+              /></el-button>
+            </div>
+            <div class="button-setting-item-label">
+              <el-switch v-model="item.enable" />
+            </div>
+          </div>
+        </div>
+      </el-tab-pane>
       <el-tab-pane label="表单字段权限" name="fields" v-if="formType === 10">
         <div class="field-setting-pane">
           <div class="field-setting-desc">字段权限</div>
@@ -334,7 +368,9 @@ import {
   TIMEOUT_HANDLER_ACTION_TYPES,
   TIME_UNIT_TYPES,
   REJECT_HANDLER_TYPES,
-  NODE_DEFAULT_NAME
+  NODE_DEFAULT_NAME,
+  DEFAULT_BUTTON_SETTING,
+  OPERATION_BUTTON_NAME
 } from '../consts'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { getDefaultFieldsPermission } from '../utils'
@@ -345,7 +381,6 @@ import * as PostApi from '@/api/system/post'
 import * as UserApi from '@/api/system/user'
 import * as UserGroupApi from '@/api/bpm/userGroup'
 import { cloneDeep } from 'lodash-es'
-
 defineOptions({
   name: 'UserTaskNodeConfig'
 })
@@ -385,7 +420,8 @@ const configForm = ref<any>({
   timeoutHandlerAction: 1,
   timeDuration: 6, // 默认 6小时
   maxRemindCount: 1, // 默认 提醒 1次
-  fieldsPermission: []
+  fieldsPermission: [],
+  buttonsSetting: []
 })
 // 表单校验规则
 const formRules = reactive({
@@ -432,6 +468,8 @@ const saveConfig = async () => {
   }
   // 设置表单权限
   currentNode.value.fieldsPermission = configForm.value.fieldsPermission
+  // 设置按钮权限
+  currentNode.value.buttonsSetting = configForm.value.buttonsSetting
 
   currentNode.value.showText = getShowText()
   settingVisible.value = false
@@ -533,6 +571,7 @@ const setCurrentNode = (node: SimpleFlowNode) => {
   currentNode.value = node
   configForm.value.fieldsPermission =
     cloneDeep(node.fieldsPermission) || getDefaultFieldsPermission(formFields?.value)
+  configForm.value.buttonsSetting = cloneDeep(node.buttonsSetting) || DEFAULT_BUTTON_SETTING
   configForm.value.candidateStrategy = node.candidateStrategy
   const strCandidateParam = node?.candidateParam
   if (node.candidateStrategy === CandidateStrategy.EXPRESSION) {
@@ -614,6 +653,7 @@ const blurEvent = () => {
   currentNode.value.name =
     currentNode.value.name || (NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string)
 }
+
 const approveMethodChanged = () => {
   configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS
   if (configForm.value.approveMethod === ApproveMethodType.APPROVE_BY_RATIO) {
@@ -703,6 +743,99 @@ const convertTimeUnit = (strTimeUnit: string) => {
   }
   return TimeUnitType.HOUR
 }
+
+// 操作按钮显示名称可编辑
+const btnDisplayNameEdit = ref<boolean[]>([])
+const changeBtnDisplayName = (index: number) => {
+  btnDisplayNameEdit.value[index] = true
+}
+const btnDisplayNameBlurEvent = (index: number) => {
+  btnDisplayNameEdit.value[index] = false
+  const buttonItem = configForm.value.buttonPermission[index]
+  buttonItem.displayName = buttonItem.displayName || OPERATION_BUTTON_NAME.get(buttonItem.id)
+}
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.button-setting-pane {
+  display: flex;
+  flex-direction: column;
+  font-size: 14px;
+
+  .button-setting-desc {
+    padding-right: 8px;
+    margin-bottom: 16px;
+    font-size: 16px;
+    font-weight: 700;
+  }
+
+  .button-setting-title {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    height: 45px;
+    padding-left: 12px;
+    background-color: #f8fafc0a;
+    border: 1px solid #1f38581a;
+
+    & > :first-child {
+      width: 100px !important;
+      text-align: left !important;
+    }
+
+    & > :last-child {
+      text-align: center !important;
+    }
+
+    .button-title-label {
+      width: 150px;
+      font-size: 13px;
+      font-weight: 700;
+      color: #000;
+      text-align: left;
+    }
+  }
+
+  .button-setting-item {
+    align-items: center;
+    display: flex;
+    justify-content: space-between;
+    height: 38px;
+    padding-left: 12px;
+    border: 1px solid #1f38581a;
+    border-top: 0;
+
+    & > :first-child {
+      width: 100px !important;
+    }
+
+    & > :last-child {
+      text-align: center !important;
+    }
+
+    .button-setting-item-label {
+      width: 150px;
+      overflow: hidden;
+      text-align: left;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+
+    .editable-title-input {
+      height: 24px;
+      max-width: 130px;
+      margin-left: 4px;
+      line-height: 24px;
+      border: 1px solid #d9d9d9;
+      border-radius: 4px;
+      transition: all 0.3s;
+
+      &:focus {
+        border-color: #40a9ff;
+        outline: 0;
+        box-shadow: 0 0 0 2px rgb(24 144 255 / 20%);
+      }
+    }
+  }
+}
+</style>

+ 52 - 58
src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss

@@ -241,7 +241,7 @@
         .move-node-left {
           left: -2px;
           top: 0px;
-          background: rgba(126, 134, 142, .08);
+          background: rgba(126, 134, 142, 0.08);
           border-top-left-radius: 8px;
           border-bottom-left-radius: 8px;
         }
@@ -249,13 +249,12 @@
         .move-node-right {
           right: -2px;
           top: 0px;
-          background: rgba(126,134,142,.08);
+          background: rgba(126, 134, 142, 0.08);
           border-top-right-radius: 6px;
           border-bottom-right-radius: 6px;
         }
       }
 
-      
       .node-config-error {
         border-color: #ff5219 !important;
       }
@@ -306,12 +305,10 @@
             background-color: #0089ff;
             border-radius: 50%;
 
-            &:hover{
+            &:hover {
               transform: scale(1.1);
-              
             }
           }
-          
         }
 
         .node-handler-arrow {
@@ -323,7 +320,6 @@
         }
       }
 
-      
       // 条件节点包装
       .branch-node-wrapper {
         position: relative;
@@ -341,8 +337,8 @@
             position: absolute;
             height: 100%;
             width: 4px;
-            background-color:  #fafafa;
-            content: "";
+            background-color: #fafafa;
+            content: '';
             left: 50%;
             transform: translate(-50%);
           }
@@ -498,10 +494,10 @@
         border-radius: 4px;
         transition: all 0.3s;
 
-        &:focus   {
+        &:focus {
           border-color: #40a9ff;
           outline: 0;
-          box-shadow: 0 0 0 2px rgba(24, 144, 255, .2)
+          box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
         }
       }
     }
@@ -537,11 +533,11 @@
     border: 1px solid #d9d9d9;
     border-radius: 4px;
     transition: all 0.3s;
-  
-    &:focus   {
+
+    &:focus {
       border-color: #40a9ff;
       outline: 0;
-      box-shadow: 0 0 0 2px rgba(24, 144, 255, .2)
+      box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
     }
   }
 }
@@ -550,7 +546,6 @@
 .field-setting-pane {
   display: flex;
   flex-direction: column;
-  padding: 16px 0;
   font-size: 14px;
 
   .field-setting-desc {
@@ -569,7 +564,7 @@
     line-height: 45px;
     background-color: #f8fafc0a;
     border: 1px solid #1f38581a;
-   
+
     .first-title {
       text-align: left !important;
     }
@@ -588,9 +583,8 @@
       color: #000;
       text-align: center;
     }
-    
   }
-  
+
   .field-setting-item {
     align-items: center;
     display: flex;
@@ -632,56 +626,56 @@
     margin-right: 8px;
   }
 
- .handler-item-icon {
-   width: 80px;
-   height: 80px;
-   background: #fff;
-   border: 1px solid #e2e2e2;
-   border-radius: 50%;
-   transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
-   user-select: none;
-   text-align: center;
-
-   &:hover {
-    background: #e2e2e2;
-    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
-   }
-
-   .icon-size {
-     font-size: 35px;
-     line-height: 80px;
-   }
+  .handler-item-icon {
+    width: 80px;
+    height: 80px;
+    background: #fff;
+    border: 1px solid #e2e2e2;
+    border-radius: 50%;
+    transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+    user-select: none;
+    text-align: center;
+
+    &:hover {
+      background: #e2e2e2;
+      box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
+    }
+
+    .icon-size {
+      font-size: 35px;
+      line-height: 80px;
+    }
   }
 
- .approve {
-   color: #ff943e;
+  .approve {
+    color: #ff943e;
   }
   .copy {
     color: #3296fa;
   }
-  
+
   .condition {
     color: #15bc83;
   }
 
- .handler-item-text {
-   margin-top: 4px;
-   width: 80px;
-   text-align: center;
- }
-
+  .handler-item-text {
+    margin-top: 4px;
+    width: 80px;
+    text-align: center;
+  }
 }
 
 // iconfont 样式
 @font-face {
-  font-family: "iconfont"; /* Project id 4495938 */
-  src: url('iconfont.woff2?t=1712392083512') format('woff2'),
-      url('iconfont.woff?t=1712392083512') format('woff'),
-      url('iconfont.ttf?t=1712392083512') format('truetype');
+  font-family: 'iconfont'; /* Project id 4495938 */
+  src:
+    url('iconfont.woff2?t=1712392083512') format('woff2'),
+    url('iconfont.woff?t=1712392083512') format('woff'),
+    url('iconfont.ttf?t=1712392083512') format('truetype');
 }
 
 .iconfont {
-  font-family: "iconfont" !important;
+  font-family: 'iconfont' !important;
   font-size: 16px;
   font-style: normal;
   -webkit-font-smoothing: antialiased;
@@ -689,25 +683,25 @@
 }
 
 .icon-Inclusive:before {
-  content: "\e602";
+  content: '\e602';
 }
 
 .icon-copy:before {
-  content: "\e7eb";
+  content: '\e7eb';
 }
 
 .icon-handle:before {
-  content: "\e61c";
+  content: '\e61c';
 }
 
 .icon-exclusive:before {
-  content: "\e717";
+  content: '\e717';
 }
 
 .icon-approve:before {
-  content: "\e715";
+  content: '\e715';
 }
 
 .icon-parallel:before {
-  content: "\e688";
-}
+  content: '\e688';
+}

+ 68 - 18
src/views/bpm/processInstance/detail/index.vue

@@ -56,29 +56,71 @@
           </el-form-item>
         </el-form>
         <div style="margin-bottom: 20px; margin-left: 10%; font-size: 14px">
-          <el-button type="success" @click="handleAudit(item, true)">
+          <el-button
+            type="success"
+            v-if="!item.buttonsSetting || item.buttonsSetting[OpsButtonType.APPROVE]?.enable"
+            @click="handleAudit(item, true)"
+          >
             <Icon icon="ep:select" />
-            通过
+            {{
+              item.buttonsSetting?.[OpsButtonType.APPROVE]?.displayName ||
+              OPERATION_BUTTON_NAME.get(OpsButtonType.APPROVE)
+            }}
           </el-button>
-          <el-button type="danger" @click="handleAudit(item, false)">
+          <el-button
+            v-if="!item.buttonsSetting || item.buttonsSetting[OpsButtonType.REJECT]?.enable"
+            type="danger"
+            @click="handleAudit(item, false)"
+          >
             <Icon icon="ep:close" />
-            不通过
+            {{
+              item.buttonsSetting?.[OpsButtonType.REJECT].displayName ||
+              OPERATION_BUTTON_NAME.get(OpsButtonType.REJECT)
+            }}
           </el-button>
-          <el-button type="primary" @click="openTaskUpdateAssigneeForm(item.id)">
+          <el-button
+            v-if="!item.buttonsSetting || item.buttonsSetting[OpsButtonType.TRANSFER]?.enable"
+            type="primary"
+            @click="openTaskUpdateAssigneeForm(item.id)"
+          >
             <Icon icon="ep:edit" />
-            转办
+            {{
+              item.buttonsSetting?.[OpsButtonType.TRANSFER]?.displayName ||
+              OPERATION_BUTTON_NAME.get(OpsButtonType.TRANSFER)
+            }}
           </el-button>
-          <el-button type="primary" @click="handleDelegate(item)">
+          <el-button
+            v-if="!item.buttonsSetting || item.buttonsSetting[OpsButtonType.DELEGATE]?.enable"
+            type="primary"
+            @click="handleDelegate(item)"
+          >
             <Icon icon="ep:position" />
-            委派
+            {{
+              item.buttonsSetting?.[OpsButtonType.DELEGATE]?.displayName ||
+              OPERATION_BUTTON_NAME.get(OpsButtonType.DELEGATE)
+            }}
           </el-button>
-          <el-button type="primary" @click="handleSign(item)">
+          <el-button
+            v-if="!item.buttonsSetting || item.buttonsSetting[OpsButtonType.ADD_SIGN]?.enable"
+            type="primary"
+            @click="handleSign(item)"
+          >
             <Icon icon="ep:plus" />
-            加签
+            {{
+              item.buttonsSetting?.[OpsButtonType.ADD_SIGN]?.displayName ||
+              OPERATION_BUTTON_NAME.get(OpsButtonType.ADD_SIGN)
+            }}
           </el-button>
-          <el-button type="warning" @click="handleBack(item)">
+          <el-button
+            v-if="!item.buttonsSetting || item.buttonsSetting[OpsButtonType.RETURN]?.enable"
+            type="warning"
+            @click="handleBack(item)"
+          >
             <Icon icon="ep:back" />
-            回退
+            {{
+              item.buttonsSetting?.[OpsButtonType.RETURN]?.displayName ||
+              OPERATION_BUTTON_NAME.get(OpsButtonType.RETURN)
+            }}
           </el-button>
         </div>
       </el-col>
@@ -147,6 +189,10 @@ import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue'
 import { registerComponent } from '@/utils/routerHelper'
 import { isEmpty } from '@/utils/is'
 import * as UserApi from '@/api/system/user'
+import {
+  OpsButtonType,
+  OPERATION_BUTTON_NAME
+} from '@/components/SimpleProcessDesignerV2/src/consts'
 
 defineOptions({ name: 'BpmProcessInstanceDetail' })
 
@@ -280,12 +326,16 @@ const getProcessInstance = async () => {
     // 设置表单信息
     const processDefinition = data.processDefinition
     if (processDefinition.formType === 10) {
-      setConfAndFields2(
-        detailForm,
-        processDefinition.formConf,
-        processDefinition.formFields,
-        data.formVariables
-      )
+      if (detailForm.value.rule.length > 0) {
+        detailForm.value.value = data.formVariables
+      } else {
+        setConfAndFields2(
+          detailForm,
+          processDefinition.formConf,
+          processDefinition.formFields,
+          data.formVariables
+        )
+      }
       nextTick().then(() => {
         fApi.value?.btn.show(false)
         fApi.value?.resetBtn.show(false)