From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目

---
 src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue | 1140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 1,140 insertions(+), 0 deletions(-)

diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue b/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue
new file mode 100644
index 0000000..53b10bd
--- /dev/null
+++ b/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue
@@ -0,0 +1,1140 @@
+<template>
+  <div
+    class="h-50px bottom-10 text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
+  >
+    <!-- 銆愰�氳繃銆戞寜閽� -->
+    <el-popover
+      :visible="popOverVisible.approve"
+      placement="top-end"
+      :width="420"
+      trigger="click"
+      v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.APPROVE)"
+    >
+      <template #reference>
+        <el-button plain type="success" @click="openPopover('approve')">
+          <Icon icon="ep:select" />&nbsp; {{ getButtonDisplayName(OperationButtonType.APPROVE) }}
+        </el-button>
+      </template>
+      <!-- 瀹℃壒琛ㄥ崟 -->
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="approveFormRef"
+          :model="approveReasonForm"
+          :rules="approveReasonRule"
+          label-width="100px"
+        >
+          <el-card v-if="runningTask?.formId > 0" class="mb-15px !-mt-10px">
+            <template #header>
+              <span class="el-icon-picture-outline"> 濉啓琛ㄥ崟銆恵{ runningTask?.formName }}銆� </span>
+            </template>
+            <form-create
+              v-model="approveForm.value"
+              v-model:api="approveFormFApi"
+              :option="approveForm.option"
+              :rule="approveForm.rule"
+            />
+          </el-card>
+          <el-form-item :label="`${nodeTypeName}鎰忚`" prop="reason">
+            <el-input
+              v-model="approveReasonForm.reason"
+              :placeholder="`璇疯緭鍏�${nodeTypeName}鎰忚`"
+              type="textarea"
+              :rows="4"
+            />
+          </el-form-item>
+          <el-form-item
+            label="涓嬩竴涓妭鐐圭殑瀹℃壒浜�"
+            prop="nextAssignees"
+            v-if="nextAssigneesActivityNode.length > 0"
+          >
+            <div class="ml-10px -mt-15px -mb-35px">
+              <ProcessInstanceTimeline
+                ref="nextAssigneesTimelineRef"
+                :activity-nodes="nextAssigneesActivityNode"
+                :show-status-icon="false"
+                :enable-approve-user-select="true"
+                @select-user-confirm="selectNextAssigneesConfirm"
+              />
+            </div>
+          </el-form-item>
+          <el-form-item
+            v-if="runningTask.signEnable"
+            label="绛惧悕"
+            prop="signPicUrl"
+            ref="approveSignFormRef"
+          >
+            <el-button @click="signRef.open()">鐐瑰嚮绛惧悕</el-button>
+            <el-image
+              class="w-90px h-40px ml-5px"
+              v-if="approveReasonForm.signPicUrl"
+              :src="approveReasonForm.signPicUrl"
+              :preview-src-list="[approveReasonForm.signPicUrl]"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button
+              :disabled="formLoading"
+              type="success"
+              @click="handleAudit(true, approveFormRef)"
+            >
+              {{ getButtonDisplayName(OperationButtonType.APPROVE) }}
+            </el-button>
+            <el-button @click="closePopover('approve', approveFormRef)"> 鍙栨秷 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!-- 銆愭嫆缁濄�戞寜閽� -->
+    <el-popover
+      :visible="popOverVisible.reject"
+      placement="top-end"
+      :width="420"
+      trigger="click"
+      v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.REJECT)"
+    >
+      <template #reference>
+        <el-button class="mr-20px" plain type="danger" @click="openPopover('reject')">
+          <Icon icon="ep:close" />&nbsp; {{ getButtonDisplayName(OperationButtonType.REJECT) }}
+        </el-button>
+      </template>
+      <!-- 瀹℃壒琛ㄥ崟 -->
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="rejectFormRef"
+          :model="rejectReasonForm"
+          :rules="rejectReasonRule"
+          label-width="100px"
+        >
+          <el-form-item label="瀹℃壒鎰忚" prop="reason">
+            <el-input
+              v-model="rejectReasonForm.reason"
+              placeholder="璇疯緭鍏ュ鎵规剰瑙�"
+              type="textarea"
+              :rows="4"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button
+              :disabled="formLoading"
+              type="danger"
+              @click="handleAudit(false, rejectFormRef)"
+            >
+              {{ getButtonDisplayName(OperationButtonType.REJECT) }}
+            </el-button>
+            <el-button @click="closePopover('reject', rejectFormRef)"> 鍙栨秷 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!-- 銆愭妱閫併�戞寜閽� -->
+    <el-popover
+      :visible="popOverVisible.copy"
+      placement="top-start"
+      :width="420"
+      trigger="click"
+      v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.COPY)"
+    >
+      <template #reference>
+        <div @click="openPopover('copy')" class="hover-bg-gray-100 rounded-xl p-6px">
+          <Icon :size="14" icon="svg-icon:send" />&nbsp;
+          {{ getButtonDisplayName(OperationButtonType.COPY) }}
+        </div>
+      </template>
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="copyFormRef"
+          :model="copyForm"
+          :rules="copyFormRule"
+          label-width="100px"
+        >
+          <el-form-item label="鎶勯�佷汉" prop="copyUserIds">
+            <el-select
+              v-model="copyForm.copyUserIds"
+              clearable
+              style="width: 100%"
+              multiple
+              placeholder="璇烽�夋嫨鎶勯�佷汉"
+            >
+              <el-option
+                v-for="item in userOptions"
+                :key="item.id"
+                :label="item.nickname"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="鎶勯�佹剰瑙�" prop="copyReason">
+            <el-input
+              v-model="copyForm.copyReason"
+              clearable
+              placeholder="璇疯緭鍏ユ妱閫佹剰瑙�"
+              type="textarea"
+              :rows="3"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="primary" @click="handleCopy">
+              {{ getButtonDisplayName(OperationButtonType.COPY) }}
+            </el-button>
+            <el-button @click="closePopover('copy', copyFormRef)"> 鍙栨秷 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!-- 銆愯浆鍔炪�戞寜閽� -->
+    <el-popover
+      :visible="popOverVisible.transfer"
+      placement="top-start"
+      :width="420"
+      trigger="click"
+      v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.TRANSFER)"
+    >
+      <template #reference>
+        <div @click="openPopover('transfer')" class="hover-bg-gray-100 rounded-xl p-6px">
+          <Icon :size="14" icon="fa:share-square-o" />&nbsp;
+          {{ getButtonDisplayName(OperationButtonType.TRANSFER) }}
+        </div>
+      </template>
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="transferFormRef"
+          :model="transferForm"
+          :rules="transferFormRule"
+          label-width="100px"
+        >
+          <el-form-item label="鏂板鎵逛汉" prop="assigneeUserId">
+            <el-select v-model="transferForm.assigneeUserId" clearable 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 label="瀹℃壒鎰忚" prop="reason">
+            <el-input
+              v-model="transferForm.reason"
+              clearable
+              placeholder="璇疯緭鍏ュ鎵规剰瑙�"
+              type="textarea"
+              :rows="3"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="primary" @click="handleTransfer()">
+              {{ getButtonDisplayName(OperationButtonType.TRANSFER) }}
+            </el-button>
+            <el-button @click="closePopover('transfer', transferFormRef)"> 鍙栨秷 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!-- 銆愬娲俱�戞寜閽� -->
+    <el-popover
+      :visible="popOverVisible.delegate"
+      placement="top-start"
+      :width="420"
+      trigger="click"
+      v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.DELEGATE)"
+    >
+      <template #reference>
+        <div @click="openPopover('delegate')" class="hover-bg-gray-100 rounded-xl p-6px">
+          <Icon :size="14" icon="ep:position" />&nbsp;
+          {{ getButtonDisplayName(OperationButtonType.DELEGATE) }}
+        </div>
+      </template>
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="delegateFormRef"
+          :model="delegateForm"
+          :rules="delegateFormRule"
+          label-width="100px"
+        >
+          <el-form-item label="鎺ユ敹浜�" prop="delegateUserId">
+            <el-select v-model="delegateForm.delegateUserId" clearable 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 label="瀹℃壒鎰忚" prop="reason">
+            <el-input
+              v-model="delegateForm.reason"
+              clearable
+              placeholder="璇疯緭鍏ュ鎵规剰瑙�"
+              type="textarea"
+              :rows="3"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="primary" @click="handleDelegate()">
+              {{ getButtonDisplayName(OperationButtonType.DELEGATE) }}
+            </el-button>
+            <el-button @click="closePopover('delegate', delegateFormRef)"> 鍙栨秷 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!-- 銆愬姞绛俱�戞寜閽� 褰撳墠浠诲姟瀹℃壒浜轰负A锛屽悜鍓嶅姞绛鹃�変簡涓�涓狢锛屽垯闇�瑕丆鍏堝鎵癸紝鐒跺悗鍐嶆槸A瀹℃壒锛屽悜鍚庡姞绛綛锛孉瀹℃壒瀹岋紝闇�瑕丅鍐嶅鎵瑰畬锛屾墠绠楀畬鎴愯繖涓换鍔¤妭鐐� -->
+    <el-popover
+      :visible="popOverVisible.addSign"
+      placement="top-start"
+      :width="420"
+      trigger="click"
+      v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.ADD_SIGN)"
+    >
+      <template #reference>
+        <div @click="openPopover('addSign')" class="hover-bg-gray-100 rounded-xl p-6px">
+          <Icon :size="14" icon="ep:plus" />&nbsp;
+          {{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
+        </div>
+      </template>
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="addSignFormRef"
+          :model="addSignForm"
+          :rules="addSignFormRule"
+          label-width="100px"
+        >
+          <el-form-item label="鍔犵澶勭悊浜�" prop="addSignUserIds">
+            <el-select v-model="addSignForm.addSignUserIds" multiple clearable 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 label="瀹℃壒鎰忚" prop="reason">
+            <el-input
+              v-model="addSignForm.reason"
+              clearable
+              placeholder="璇疯緭鍏ュ鎵规剰瑙�"
+              type="textarea"
+              :rows="3"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="primary" @click="handlerAddSign('before')">
+              鍚戝墠{{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
+            </el-button>
+            <el-button :disabled="formLoading" type="primary" @click="handlerAddSign('after')">
+              鍚戝悗{{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }}
+            </el-button>
+            <el-button @click="closePopover('addSign', addSignFormRef)"> 鍙栨秷 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!-- 銆愬噺绛俱�戞寜閽� -->
+    <el-popover
+      :visible="popOverVisible.deleteSign"
+      placement="top-start"
+      :width="420"
+      trigger="click"
+      v-if="runningTask?.children.length > 0"
+    >
+      <template #reference>
+        <div @click="openPopover('deleteSign')" class="hover-bg-gray-100 rounded-xl p-6px">
+          <Icon :size="14" icon="ep:semi-select" />&nbsp; 鍑忕
+        </div>
+      </template>
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="deleteSignFormRef"
+          :model="deleteSignForm"
+          :rules="deleteSignFormRule"
+          label-width="100px"
+        >
+          <el-form-item label="鍑忕浜哄憳" prop="deleteSignTaskId">
+            <el-select v-model="deleteSignForm.deleteSignTaskId" clearable style="width: 100%">
+              <el-option
+                v-for="item in runningTask.children"
+                :key="item.id"
+                :label="getDeleteSignUserLabel(item)"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="瀹℃壒鎰忚" prop="reason">
+            <el-input
+              v-model="deleteSignForm.reason"
+              clearable
+              placeholder="璇疯緭鍏ュ鎵规剰瑙�"
+              type="textarea"
+              :rows="3"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="primary" @click="handlerDeleteSign()">
+              鍑忕
+            </el-button>
+            <el-button @click="closePopover('deleteSign', deleteSignFormRef)"> 鍙栨秷 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!-- 銆愰��鍥炪�戞寜閽� -->
+    <el-popover
+      :visible="popOverVisible.return"
+      placement="top-start"
+      :width="420"
+      trigger="click"
+      v-if="runningTask && isHandleTaskStatus() && isShowButton(OperationButtonType.RETURN)"
+    >
+      <template #reference>
+        <div @click="openPopover('return')" class="hover-bg-gray-100 rounded-xl p-6px">
+          <Icon :size="14" icon="ep:back" />&nbsp;
+          {{ getButtonDisplayName(OperationButtonType.RETURN) }}
+        </div>
+      </template>
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="returnFormRef"
+          :model="returnForm"
+          :rules="returnFormRule"
+          label-width="100px"
+        >
+          <el-form-item label="閫�鍥炶妭鐐�" prop="targetTaskDefinitionKey">
+            <el-select v-model="returnForm.targetTaskDefinitionKey" clearable style="width: 100%">
+              <el-option
+                v-for="item in returnList"
+                :key="item.taskDefinitionKey"
+                :label="item.name"
+                :value="item.taskDefinitionKey"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="閫�鍥炵悊鐢�" prop="returnReason">
+            <el-input
+              v-model="returnForm.returnReason"
+              clearable
+              placeholder="璇疯緭鍏ラ��鍥炵悊鐢�"
+              type="textarea"
+              :rows="3"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="primary" @click="handleReturn()">
+              {{ getButtonDisplayName(OperationButtonType.RETURN) }}
+            </el-button>
+            <el-button @click="closePopover('return', returnFormRef)"> 鍙栨秷 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+
+    <!--銆愬彇娑堛�戞寜閽� 杩欎釜瀵瑰簲鍙戣捣浜虹殑鍙栨秷, 鍙湁鍙戣捣浜哄彲浠ュ彇娑� -->
+    <el-popover
+      :visible="popOverVisible.cancel"
+      placement="top-start"
+      :width="420"
+      trigger="click"
+      v-if="
+        userId === processInstance?.startUser?.id && !isEndProcessStatus(processInstance?.status)
+      "
+    >
+      <template #reference>
+        <div @click="openPopover('cancel')" class="hover-bg-gray-100 rounded-xl p-6px">
+          <Icon :size="14" icon="fa:mail-reply" />&nbsp; 鍙栨秷
+        </div>
+      </template>
+      <div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
+        <el-form
+          label-position="top"
+          class="mb-auto"
+          ref="cancelFormRef"
+          :model="cancelForm"
+          :rules="cancelFormRule"
+          label-width="100px"
+        >
+          <el-form-item label="鍙栨秷鐞嗙敱" prop="cancelReason">
+            <span class="text-#878c93 text-12px">&nbsp; 鍙栨秷鍚庯紝璇ュ鎵规祦绋嬪皢鑷姩缁撴潫</span>
+            <el-input
+              v-model="cancelForm.cancelReason"
+              clearable
+              placeholder="璇疯緭鍏ュ彇娑堢悊鐢�"
+              type="textarea"
+              :rows="3"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button :disabled="formLoading" type="primary" @click="handleCancel()">
+              纭
+            </el-button>
+            <el-button @click="closePopover('cancel', cancelFormRef)"> 鍙栨秷 </el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-popover>
+    <!-- 銆愬啀娆℃彁浜ゃ�� 鎸夐挳-->
+    <div
+      @click="handleReCreate()"
+      class="hover-bg-gray-100 rounded-xl p-6px"
+      v-if="
+        userId === processInstance?.startUser?.id &&
+        isEndProcessStatus(processInstance?.status) &&
+        processDefinition?.formType === 10
+      "
+    >
+      <Icon :size="14" icon="ep:refresh" />&nbsp; 鍐嶆鎻愪氦
+    </div>
+  </div>
+
+  <!-- 绛惧悕寮圭獥 -->
+  <SignDialog ref="signRef" @success="handleSignFinish" />
+</template>
+<script lang="ts" setup>
+import { useUserStoreWithOut } from '@/store/modules/user'
+import { setConfAndFields2 } from '@/utils/formCreate'
+import * as TaskApi from '@/api/bpm/task'
+import * as ProcessInstanceApi from '@/api/bpm/processInstance'
+import * as UserApi from '@/api/system/user'
+import {
+  NodeType,
+  OPERATION_BUTTON_NAME,
+  OperationButtonType,
+  CandidateStrategy
+} from '@/components/SimpleProcessDesignerV2/src/consts'
+import { BpmModelFormType, BpmProcessInstanceStatus } from '@/utils/constants'
+import type { FormInstance, FormRules } from 'element-plus'
+import SignDialog from './SignDialog.vue'
+import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue'
+import { isEmpty } from '@/utils/is'
+
+defineOptions({ name: 'ProcessInstanceBtnContainer' })
+
+const router = useRouter() // 璺敱
+const message = useMessage() // 娑堟伅寮圭獥
+
+const userId = useUserStoreWithOut().getUser.id // 褰撳墠鐧诲綍鐨勭紪鍙�
+const emit = defineEmits(['success']) // 瀹氫箟 success 浜嬩欢锛岀敤浜庢搷浣滄垚鍔熷悗鐨勫洖璋�
+
+const props = defineProps<{
+  processInstance: any // 娴佺▼瀹炰緥淇℃伅
+  processDefinition: any // 娴佺▼瀹氫箟淇℃伅
+  userOptions: UserApi.UserVO[]
+  normalForm: any // 娴佺▼琛ㄥ崟 formCreate
+  normalFormApi: any // 娴佺▼琛ㄥ崟 formCreate Api
+  writableFields: string[] // 娴佺▼琛ㄥ崟鍙互缂栬緫鐨勫瓧娈�
+}>()
+
+const formLoading = ref(false) // 琛ㄥ崟鍔犺浇涓�
+const popOverVisible = ref({
+  approve: false,
+  reject: false,
+  transfer: false,
+  delegate: false,
+  addSign: false,
+  return: false,
+  copy: false,
+  cancel: false,
+  deleteSign: false
+}) // 姘旀场鍗℃槸鍚﹀睍绀�
+const returnList = ref([] as any) // 閫�鍥炶妭鐐�
+
+// ========== 瀹℃壒淇℃伅 ==========
+const runningTask = ref<any>() // 杩愯涓殑浠诲姟
+const approveForm = ref<any>({}) // 瀹℃壒閫氳繃鏃讹紝棰濆鐨勮ˉ鍏呬俊鎭�
+const approveFormFApi = ref<any>({}) // approveForms 鐨� fAPi
+const nodeTypeName = ref('瀹℃壒') // 鑺傜偣绫诲瀷鍚嶇О
+
+// 瀹℃壒閫氳繃鎰忚琛ㄥ崟
+const reasonRequire = ref()
+const approveFormRef = ref<FormInstance>()
+const signRef = ref()
+const approveSignFormRef = ref()
+const nextAssigneesActivityNode = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 涓嬩竴涓鎵硅妭鐐逛俊鎭�
+const nextAssigneesTimelineRef = ref() // 涓嬩竴涓妭鐐瑰鎵逛汉鏃堕棿绾跨粍浠剁殑寮曠敤
+const approveReasonForm = reactive({
+  reason: '',
+  signPicUrl: '',
+  nextAssignees: {}
+})
+const approveReasonRule = computed(() => {
+  return {
+    reason: [
+      { required: reasonRequire.value, message: nodeTypeName.value + '鎰忚涓嶈兘涓虹┖', trigger: 'blur' }
+    ],
+    signPicUrl: [{ required: true, message: '绛惧悕涓嶈兘涓虹┖', trigger: 'change' }],
+    nextAssignees: [{ required: true, message: '瀹℃壒浜轰笉鑳戒负绌�', trigger: 'blur' }]
+  }
+})
+
+// 鎷掔粷琛ㄥ崟
+const rejectFormRef = ref<FormInstance>()
+const rejectReasonForm = reactive({
+  reason: ''
+})
+const rejectReasonRule = computed(() => {
+  return {
+    reason: [{ required: reasonRequire.value, message: '瀹℃壒鎰忚涓嶈兘涓虹┖', trigger: 'blur' }]
+  }
+})
+
+// 鎶勯�佽〃鍗�
+const copyFormRef = ref<FormInstance>()
+const copyForm = reactive({
+  copyUserIds: [],
+  copyReason: ''
+})
+const copyFormRule = reactive<FormRules<typeof copyForm>>({
+  copyUserIds: [{ required: true, message: '鎶勯�佷汉涓嶈兘涓虹┖', trigger: 'change' }]
+})
+
+// 杞姙琛ㄥ崟
+const transferFormRef = ref<FormInstance>()
+const transferForm = reactive({
+  assigneeUserId: undefined,
+  reason: ''
+})
+const transferFormRule = reactive<FormRules<typeof transferForm>>({
+  assigneeUserId: [{ required: true, message: '鏂板鎵逛汉涓嶈兘涓虹┖', trigger: 'change' }],
+  reason: [{ required: true, message: '瀹℃壒鎰忚涓嶈兘涓虹┖', trigger: 'blur' }]
+})
+
+// 濮旀淳琛ㄥ崟
+const delegateFormRef = ref<FormInstance>()
+const delegateForm = reactive({
+  delegateUserId: undefined,
+  reason: ''
+})
+const delegateFormRule = reactive<FormRules<typeof delegateForm>>({
+  delegateUserId: [{ required: true, message: '鎺ユ敹浜轰笉鑳戒负绌�', trigger: 'change' }],
+  reason: [{ required: true, message: '瀹℃壒鎰忚涓嶈兘涓虹┖', trigger: 'blur' }]
+})
+
+// 鍔犵琛ㄥ崟
+const addSignFormRef = ref<FormInstance>()
+const addSignForm = reactive({
+  addSignUserIds: undefined,
+  reason: ''
+})
+const addSignFormRule = reactive<FormRules<typeof addSignForm>>({
+  addSignUserIds: [{ required: true, message: '鍔犵澶勭悊浜轰笉鑳戒负绌�', trigger: 'change' }],
+  reason: [{ required: true, message: '瀹℃壒鎰忚涓嶈兘涓虹┖', trigger: 'blur' }]
+})
+
+// 鍑忕琛ㄥ崟
+const deleteSignFormRef = ref<FormInstance>()
+const deleteSignForm = reactive({
+  deleteSignTaskId: undefined,
+  reason: ''
+})
+const deleteSignFormRule = reactive<FormRules<typeof deleteSignForm>>({
+  deleteSignTaskId: [{ required: true, message: '鍑忕浜哄憳涓嶈兘涓虹┖', trigger: 'change' }],
+  reason: [{ required: true, message: '瀹℃壒鎰忚涓嶈兘涓虹┖', trigger: 'blur' }]
+})
+
+// 閫�鍥炶〃鍗�
+const returnFormRef = ref<FormInstance>()
+const returnForm = reactive({
+  targetTaskDefinitionKey: undefined,
+  returnReason: ''
+})
+const returnFormRule = reactive<FormRules<typeof returnForm>>({
+  targetTaskDefinitionKey: [{ required: true, message: '閫�鍥炶妭鐐逛笉鑳戒负绌�', trigger: 'change' }],
+  returnReason: [{ required: true, message: '閫�鍥炵悊鐢变笉鑳戒负绌�', trigger: 'blur' }]
+})
+
+// 鍙栨秷琛ㄥ崟
+const cancelFormRef = ref<FormInstance>()
+const cancelForm = reactive({
+  cancelReason: ''
+})
+const cancelFormRule = reactive<FormRules<typeof cancelForm>>({
+  cancelReason: [{ required: true, message: '鍙栨秷鐞嗙敱涓嶈兘涓虹┖', trigger: 'blur' }]
+})
+
+/** 鐩戝惉 approveFormFApis锛屽疄鐜板畠瀵瑰簲鐨� form-create 鍒濆鍖栧悗锛岄殣钘忔帀瀵瑰簲鐨勮〃鍗曟彁浜ゆ寜閽� */
+watch(
+  () => approveFormFApi.value,
+  (val) => {
+    val?.btn?.show(false)
+    val?.resetBtn?.show(false)
+  },
+  {
+    deep: true
+  }
+)
+
+/** 寮瑰嚭姘旀场鍗� */
+const openPopover = async (type: string) => {
+  if (popOverVisible.value[type] === true) return
+  if (type === 'approve') {
+    // 鏍¢獙娴佺▼琛ㄥ崟
+    const valid = await validateNormalForm()
+    if (!valid) {
+      message.warning('琛ㄥ崟鏍¢獙涓嶉�氳繃锛岃鍏堝畬鍠勮〃鍗�!!')
+      return
+    }
+    initNextAssigneesFormField()
+  }
+  if (type === 'return') {
+    // 鑾峰彇閫�鍥炶妭鐐�
+    returnList.value = await TaskApi.getTaskListByReturn(runningTask.value.id)
+    if (returnList.value.length === 0) {
+      message.warning('褰撳墠娌℃湁鍙��鍥炵殑鑺傜偣')
+      return
+    }
+  }
+  Object.keys(popOverVisible.value).forEach((item) => {
+    popOverVisible.value[item] = item === type
+  })
+  // await nextTick()
+  // formRef.value.resetFields()
+}
+
+/** 鍏抽棴姘旀场鍗� */
+const closePopover = (type: string, formRef: FormInstance | undefined) => {
+  if (formRef) {
+    formRef.resetFields()
+  }
+  popOverVisible.value[type] = false
+  nextAssigneesActivityNode.value = []
+  // 娓呯悊 Timeline 缁勪欢涓殑鑷畾涔夊鎵逛汉鏁版嵁
+  if (nextAssigneesTimelineRef.value) {
+    nextAssigneesTimelineRef.value.batchSetCustomApproveUsers({})
+  }
+}
+
+/** 娴佺▼閫氳繃鏃讹紝鏍规嵁琛ㄥ崟鍙橀噺鏌ヨ鏂扮殑娴佺▼鑺傜偣锛屽垽鏂笅涓�涓妭鐐圭被鍨嬫槸鍚︿负鑷�夊鎵逛汉 */
+const initNextAssigneesFormField = async () => {
+  // 鑾峰彇淇敼鐨勬祦绋嬪彉閲�, 鏆傛椂鍙敮鎸佹祦绋嬭〃鍗�
+  const variables = getUpdatedProcessInstanceVariables()
+  const data = await ProcessInstanceApi.getNextApprovalNodes({
+    processInstanceId: props.processInstance.id,
+    taskId: runningTask.value.id,
+    processVariablesStr: JSON.stringify(variables)
+  })
+  if (data && data.length > 0) {
+    const customApproveUsersData: Record<string, any[]> = {} // 鐢ㄤ簬鏀堕泦闇�瑕佽缃埌 Timeline 缁勪欢鐨勮嚜瀹氫箟瀹℃壒浜烘暟鎹�
+    data.forEach((node: any) => {
+      if (
+        // 鎯呭喌涓�锛氬綋鍓嶈妭鐐规病鏈夊鎵逛汉锛屽苟涓旀槸鍙戣捣浜鸿嚜閫�
+        (isEmpty(node.tasks) &&
+          isEmpty(node.candidateUsers) &&
+          CandidateStrategy.START_USER_SELECT === node.candidateStrategy) ||
+        // 鎯呭喌浜岋細褰撳墠鑺傜偣鏄鎵逛汉鑷��
+        CandidateStrategy.APPROVE_USER_SELECT === node.candidateStrategy
+      ) {
+        nextAssigneesActivityNode.value.push(node)
+      }
+
+      // 濡傛灉鑺傜偣鏈� candidateUsers锛岃缃埌 customApproveUsers 涓�
+      if (node.candidateUsers && node.candidateUsers.length > 0) {
+        customApproveUsersData[node.id] = node.candidateUsers
+      }
+    })
+
+    // 灏� candidateUsers 璁剧疆鍒� Timeline 缁勪欢涓�
+    await nextTick() // 绛夊緟涓嬩竴涓� tick锛岀‘淇� Timeline 缁勪欢宸茬粡娓叉煋
+    if (nextAssigneesTimelineRef.value && Object.keys(customApproveUsersData).length > 0) {
+      nextAssigneesTimelineRef.value.batchSetCustomApproveUsers(customApproveUsersData)
+    }
+  }
+}
+
+/** 閫夋嫨涓嬩竴涓妭鐐圭殑瀹℃壒浜� */
+const selectNextAssigneesConfirm = (id: string, userList: any[]) => {
+  approveReasonForm.nextAssignees[id] = userList?.map((item: any) => item.id)
+}
+/** 瀹℃壒閫氳繃鏃讹紝鏍¢獙姣忎釜鑷�夊鎵逛汉鐨勮妭鐐规槸鍚﹂兘宸查厤缃簡瀹℃壒浜� */
+const validateNextAssignees = () => {
+  if (Object.keys(nextAssigneesActivityNode.value).length === 0) {
+    return true
+  }
+  // 濡傛灉闇�瑕佽嚜閫夊鎵逛汉锛屽垯鏍¢獙姣忎釜鑺傜偣鏄惁閮藉凡閰嶇疆瀹℃壒浜�
+  for (const item of nextAssigneesActivityNode.value) {
+    if (isEmpty(approveReasonForm.nextAssignees[item.id])) {
+      message.warning('涓嬩竴涓妭鐐圭殑瀹℃壒浜轰笉鑳戒负绌�!')
+      return false
+    }
+  }
+  return true
+}
+
+/** 澶勭悊瀹℃壒閫氳繃鍜屼笉閫氳繃鐨勬搷浣� */
+const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) => {
+  formLoading.value = true
+  try {
+    // 鏍¢獙琛ㄥ崟
+    if (!formRef) return
+    await formRef.validate()
+    // 鏍¢獙娴佺▼琛ㄥ崟蹇呭~瀛楁
+    const valid = await validateNormalForm()
+    if (!valid) {
+      message.warning('琛ㄥ崟鏍¢獙涓嶉�氳繃锛岃鍏堝畬鍠勮〃鍗�!!')
+      return
+    }
+
+    if (pass) {
+      const nextAssigneesValid = validateNextAssignees()
+      if (!nextAssigneesValid) return
+      const variables = getUpdatedProcessInstanceVariables()
+      // 瀹℃壒閫氳繃鏁版嵁
+      const data = {
+        id: runningTask.value.id,
+        reason: approveReasonForm.reason,
+        variables, // 瀹℃壒閫氳繃, 鎶婁慨鏀圭殑瀛楁鍊艰祴浜庢祦绋嬪疄渚嬪彉閲�
+        nextAssignees: approveReasonForm.nextAssignees // 涓嬩釜鑷�夎妭鐐归�夋嫨鐨勫鎵逛汉淇℃伅
+      } as any
+      // 绛惧悕
+      if (runningTask.value.signEnable) {
+        data.signPicUrl = approveReasonForm.signPicUrl
+      }
+      // 澶氳〃鍗曞鐞嗭紝骞朵笖鏈夐澶栫殑 approveForm 琛ㄥ崟锛岄渶瑕佹牎楠� + 鎷兼帴鍒� data 琛ㄥ崟閲屾彁浜�
+      // TODO 鑺嬭壙 浠诲姟鏈夊琛ㄥ崟杩欓噷瑕佸浣曞鐞嗭紝浼氬拰鍙紪杈戠殑瀛楁鍐茬獊
+      const formCreateApi = approveFormFApi.value
+      if (Object.keys(formCreateApi)?.length > 0) {
+        await formCreateApi.validate()
+        // @ts-ignore
+        data.variables = approveForm.value.value
+      }
+      await TaskApi.approveTask(data)
+      popOverVisible.value.approve = false
+      nextAssigneesActivityNode.value = []
+      // 娓呯悊 Timeline 缁勪欢涓殑鑷畾涔夊鎵逛汉鏁版嵁
+      if (nextAssigneesTimelineRef.value) {
+        nextAssigneesTimelineRef.value.batchSetCustomApproveUsers({})
+      }
+      message.success('瀹℃壒閫氳繃鎴愬姛')
+    } else {
+      // 瀹℃壒涓嶉�氳繃鏁版嵁
+      const data = {
+        id: runningTask.value.id,
+        reason: rejectReasonForm.reason
+      }
+      await TaskApi.rejectTask(data)
+      popOverVisible.value.reject = false
+      message.success('瀹℃壒涓嶉�氳繃鎴愬姛')
+    }
+    // 閲嶇疆琛ㄥ崟
+    formRef.resetFields()
+    // 鍔犺浇鏈�鏂版暟鎹�
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 澶勭悊鎶勯�� */
+const handleCopy = async () => {
+  formLoading.value = true
+  try {
+    // 1. 鏍¢獙琛ㄥ崟
+    if (!copyFormRef.value) return
+    await copyFormRef.value.validate()
+    // 2. 鎻愪氦鎶勯��
+    const data = {
+      id: runningTask.value.id,
+      reason: copyForm.copyReason,
+      copyUserIds: copyForm.copyUserIds
+    }
+    await TaskApi.copyTask(data)
+    copyFormRef.value.resetFields()
+    popOverVisible.value.copy = false
+    message.success('鎿嶄綔鎴愬姛')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 澶勭悊杞氦 */
+const handleTransfer = async () => {
+  formLoading.value = true
+  try {
+    // 1.1 鏍¢獙琛ㄥ崟
+    if (!transferFormRef.value) return
+    await transferFormRef.value.validate()
+    // 1.2 鎻愪氦杞氦
+    const data = {
+      id: runningTask.value.id,
+      reason: transferForm.reason,
+      assigneeUserId: transferForm.assigneeUserId
+    }
+    await TaskApi.transferTask(data)
+    transferFormRef.value.resetFields()
+    popOverVisible.value.transfer = false
+    message.success('鎿嶄綔鎴愬姛')
+    // 2. 鍔犺浇鏈�鏂版暟鎹�
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 澶勭悊濮旀淳 */
+const handleDelegate = async () => {
+  formLoading.value = true
+  try {
+    // 1.1 鏍¢獙琛ㄥ崟
+    if (!delegateFormRef.value) return
+    await delegateFormRef.value.validate()
+    // 1.2 澶勭悊濮旀淳
+    const data = {
+      id: runningTask.value.id,
+      reason: delegateForm.reason,
+      delegateUserId: delegateForm.delegateUserId
+    }
+
+    await TaskApi.delegateTask(data)
+    popOverVisible.value.delegate = false
+    delegateFormRef.value.resetFields()
+    message.success('鎿嶄綔鎴愬姛')
+    // 2. 鍔犺浇鏈�鏂版暟鎹�
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 澶勭悊鍔犵 */
+const handlerAddSign = async (type: string) => {
+  formLoading.value = true
+  try {
+    // 1.1 鏍¢獙琛ㄥ崟
+    if (!addSignFormRef.value) return
+    await addSignFormRef.value.validate()
+    // 1.2 鎻愪氦鍔犵
+    const data = {
+      id: runningTask.value.id,
+      type,
+      reason: addSignForm.reason,
+      userIds: addSignForm.addSignUserIds
+    }
+    await TaskApi.signCreateTask(data)
+    message.success('鎿嶄綔鎴愬姛')
+    addSignFormRef.value.resetFields()
+    popOverVisible.value.addSign = false
+    // 2 鍔犺浇鏈�鏂版暟鎹�
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 澶勭悊閫�鍥� */
+const handleReturn = async () => {
+  formLoading.value = true
+  try {
+    // 1.1 鏍¢獙琛ㄥ崟
+    if (!returnFormRef.value) return
+    await returnFormRef.value.validate()
+    // 1.2 鎻愪氦閫�鍥�
+    const data = {
+      id: runningTask.value.id,
+      reason: returnForm.returnReason,
+      targetTaskDefinitionKey: returnForm.targetTaskDefinitionKey
+    }
+
+    await TaskApi.returnTask(data)
+    popOverVisible.value.return = false
+    returnFormRef.value.resetFields()
+    message.success('鎿嶄綔鎴愬姛')
+    // 2 閲嶆柊鍔犺浇鏁版嵁
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 澶勭悊鍙栨秷 */
+const handleCancel = async () => {
+  formLoading.value = true
+  try {
+    // 1.1 鏍¢獙琛ㄥ崟
+    if (!cancelFormRef.value) return
+    await cancelFormRef.value.validate()
+    // 1.2 鎻愪氦鍙栨秷
+    await ProcessInstanceApi.cancelProcessInstanceByStartUser(
+      props.processInstance.id,
+      cancelForm.cancelReason
+    )
+    popOverVisible.value.return = false
+    message.success('鎿嶄綔鎴愬姛')
+    cancelFormRef.value.resetFields()
+    // 2 閲嶆柊鍔犺浇鏁版嵁
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 澶勭悊鍐嶆鎻愪氦 */
+const handleReCreate = async () => {
+  // 璺宠浆鍙戣捣娴佺▼鐣岄潰
+  await router.push({
+    name: 'BpmProcessInstanceCreate',
+    query: { processInstanceId: props.processInstance?.id }
+  })
+}
+
+/** 鑾峰彇鍑忕浜哄憳鏍囩 */
+const getDeleteSignUserLabel = (task: any): string => {
+  const deptName = task?.assigneeUser?.deptName || task?.ownerUser?.deptName
+  const nickname = task?.assigneeUser?.nickname || task?.ownerUser?.nickname
+  return `${nickname} ( 鎵�灞為儴闂細${deptName} )`
+}
+/** 澶勭悊鍑忕 */
+const handlerDeleteSign = async () => {
+  formLoading.value = true
+  try {
+    // 1.1 鏍¢獙琛ㄥ崟
+    if (!deleteSignFormRef.value) return
+    await deleteSignFormRef.value.validate()
+    // 1.2 鎻愪氦鍑忕
+    const data = {
+      id: deleteSignForm.deleteSignTaskId,
+      reason: deleteSignForm.reason
+    }
+    await TaskApi.signDeleteTask(data)
+    message.success('鍑忕鎴愬姛')
+    deleteSignFormRef.value.resetFields()
+    popOverVisible.value.deleteSign = false
+    // 2 鍔犺浇鏈�鏂版暟鎹�
+    reload()
+  } finally {
+    formLoading.value = false
+  }
+}
+/** 閲嶆柊鍔犺浇鏁版嵁 */
+const reload = () => {
+  emit('success')
+}
+
+/** 浠诲姟鏄惁涓哄鐞嗕腑鐘舵�� */
+const isHandleTaskStatus = () => {
+  let canHandle = false
+  if (TaskApi.TaskStatusEnum.RUNNING === runningTask.value?.status) {
+    canHandle = true
+  }
+  return canHandle
+}
+
+/** 娴佺▼鐘舵�佹槸鍚︿负缁撴潫鐘舵�� */
+const isEndProcessStatus = (status: number) => {
+  let isEndStatus = false
+  if (
+    BpmProcessInstanceStatus.APPROVE === status ||
+    BpmProcessInstanceStatus.REJECT === status ||
+    BpmProcessInstanceStatus.CANCEL === status
+  ) {
+    isEndStatus = true
+  }
+  return isEndStatus
+}
+
+/** 鏄惁鏄剧ず鎸夐挳 */
+const isShowButton = (btnType: OperationButtonType): boolean => {
+  let isShow = true
+  if (runningTask.value?.buttonsSetting && runningTask.value?.buttonsSetting[btnType]) {
+    isShow = runningTask.value.buttonsSetting[btnType].enable
+  }
+  return isShow
+}
+
+/** 鑾峰彇鎸夐挳鐨勬樉绀哄悕绉� */
+const getButtonDisplayName = (btnType: OperationButtonType) => {
+  let displayName = OPERATION_BUTTON_NAME.get(btnType)
+  if (runningTask.value?.buttonsSetting && runningTask.value?.buttonsSetting[btnType]) {
+    displayName = runningTask.value.buttonsSetting[btnType].displayName
+  }
+  return displayName
+}
+
+const loadTodoTask = (task: any) => {
+  approveForm.value = {}
+  runningTask.value = task
+  approveFormFApi.value = {}
+  reasonRequire.value = task?.reasonRequire ?? false
+  nodeTypeName.value = task?.nodeType === NodeType.TRANSACTOR_NODE ? '鍔炵悊' : '瀹℃壒'
+  // 澶勭悊 approve 琛ㄥ崟.
+  if (task && task.formId && task.formConf) {
+    const tempApproveForm = {}
+    setConfAndFields2(tempApproveForm, task.formConf, task.formFields, task.formVariables)
+    approveForm.value = tempApproveForm
+  } else {
+    approveForm.value = {} // 鍗犱綅锛岄伩鍏嶄负绌�
+  }
+}
+
+/** 鏍¢獙娴佺▼琛ㄥ崟 */
+const validateNormalForm = async () => {
+  if (props.processDefinition?.formType === BpmModelFormType.NORMAL) {
+    let valid = true
+    try {
+      await props.normalFormApi?.validate()
+    } catch {
+      valid = false
+    }
+    return valid
+  } else {
+    return true
+  }
+}
+
+/** 浠庡彲浠ョ紪杈戠殑娴佺▼琛ㄥ崟瀛楁锛岃幏鍙栭渶瑕佷慨鏀圭殑娴佺▼瀹炰緥鐨勫彉閲� */
+const getUpdatedProcessInstanceVariables = () => {
+  const variables = {}
+  props.writableFields.forEach((field) => {
+    variables[field] = props.normalFormApi.getValue(field)
+  })
+  return variables
+}
+
+/** 澶勭悊绛惧悕瀹屾垚 */
+const handleSignFinish = (url: string) => {
+  approveReasonForm.signPicUrl = url
+  approveSignFormRef.value.validate('change')
+}
+
+defineExpose({ loadTodoTask })
+</script>
+
+<style lang="scss" scoped>
+:deep(.el-affix--fixed) {
+  background-color: var(--el-bg-color);
+}
+
+.btn-container {
+  > div {
+    display: flex;
+    margin: 0 8px;
+    cursor: pointer;
+    align-items: center;
+
+    &:hover {
+      color: #6db5ff;
+    }
+  }
+}
+</style>

--
Gitblit v1.8.0