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/index.vue |  363 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 363 insertions(+), 0 deletions(-)

diff --git a/src/views/bpm/processInstance/detail/index.vue b/src/views/bpm/processInstance/detail/index.vue
new file mode 100644
index 0000000..aec1473
--- /dev/null
+++ b/src/views/bpm/processInstance/detail/index.vue
@@ -0,0 +1,363 @@
+<template>
+  <ContentWrap :bodyStyle="{ padding: '10px 20px 0' }" class="position-relative">
+    <div class="processInstance-wrap-main">
+      <el-scrollbar>
+        <img
+          class="position-absolute right-20px"
+          width="150"
+          :src="auditIconsMap[processInstance.status]"
+          alt=""
+        />
+        <div class="flex">
+          <div class="text-#878c93 h-15px">缂栧彿锛歿{ id }}</div>
+          <Icon icon="ep:printer" class="ml-15px cursor-pointer" @click="handlePrint" />
+        </div>
+        <el-divider class="!my-8px" />
+        <div class="flex items-center gap-5 mb-10px h-40px">
+          <div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
+          <dict-tag
+            v-if="processInstance.status"
+            :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
+            :value="processInstance.status"
+          />
+        </div>
+
+        <div class="flex items-center gap-5 mb-10px text-13px h-35px">
+          <div
+            class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600"
+          >
+            <el-avatar
+              :size="28"
+              v-if="processInstance?.startUser?.avatar"
+              :src="processInstance?.startUser?.avatar"
+            />
+            <el-avatar :size="28" v-else-if="processInstance?.startUser?.nickname">
+              {{ processInstance?.startUser?.nickname.substring(0, 1) }}
+            </el-avatar>
+            {{ processInstance?.startUser?.nickname }}
+          </div>
+          <div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 鎻愪氦 </div>
+        </div>
+
+        <el-tabs v-model="activeTab">
+          <!-- 琛ㄥ崟淇℃伅 -->
+          <el-tab-pane label="瀹℃壒璇︽儏" name="form">
+            <div class="form-scroll-area">
+              <el-scrollbar>
+                <el-row>
+                  <el-col :span="17" class="!flex !flex-col formCol">
+                    <!-- 琛ㄥ崟淇℃伅 -->
+                    <div
+                      v-loading="processInstanceLoading"
+                      class="form-box flex flex-col mb-30px flex-1"
+                    >
+                      <!-- 鎯呭喌涓�锛氭祦绋嬭〃鍗� -->
+                      <el-col v-if="processDefinition?.formType === BpmModelFormType.NORMAL">
+                        <form-create
+                          v-model="detailForm.value"
+                          v-model:api="fApi"
+                          :option="detailForm.option"
+                          :rule="detailForm.rule"
+                        />
+                      </el-col>
+                      <!-- 鎯呭喌浜岋細涓氬姟琛ㄥ崟 -->
+                      <div v-if="processDefinition?.formType === BpmModelFormType.CUSTOM">
+                        <BusinessFormComponent :id="processInstance.businessKey" />
+                      </div>
+                    </div>
+                  </el-col>
+                  <el-col :span="7">
+                    <!-- 瀹℃壒璁板綍鏃堕棿绾� -->
+                    <ProcessInstanceTimeline :activity-nodes="activityNodes" />
+                  </el-col>
+                </el-row>
+              </el-scrollbar>
+            </div>
+          </el-tab-pane>
+
+          <!-- 娴佺▼鍥� -->
+          <el-tab-pane label="娴佺▼鍥�" name="diagram">
+            <div class="form-scroll-area">
+              <ProcessInstanceSimpleViewer
+                v-show="
+                  processDefinition.modelType && processDefinition.modelType === BpmModelType.SIMPLE
+                "
+                :loading="processInstanceLoading"
+                :model-view="processModelView"
+              />
+              <ProcessInstanceBpmnViewer
+                v-show="
+                  processDefinition.modelType && processDefinition.modelType === BpmModelType.BPMN
+                "
+                :loading="processInstanceLoading"
+                :model-view="processModelView"
+              />
+            </div>
+          </el-tab-pane>
+
+          <!-- 娴佽浆璁板綍 -->
+          <el-tab-pane label="娴佽浆璁板綍" name="record">
+            <div class="form-scroll-area">
+              <el-scrollbar>
+                <ProcessInstanceTaskList :loading="processInstanceLoading" :id="id" />
+              </el-scrollbar>
+            </div>
+          </el-tab-pane>
+
+          <!-- 娴佽浆璇勮 TODO 寰呭紑鍙� -->
+          <el-tab-pane label="娴佽浆璇勮" name="comment" v-if="false">
+            <div class="form-scroll-area">
+              <el-scrollbar> 娴佽浆璇勮 </el-scrollbar>
+            </div>
+          </el-tab-pane>
+        </el-tabs>
+
+        <div class="b-t-solid border-t-1px border-[var(--el-border-color)]">
+          <!-- 鎿嶄綔鏍忔寜閽� -->
+          <ProcessInstanceOperationButton
+            ref="operationButtonRef"
+            :process-instance="processInstance"
+            :process-definition="processDefinition"
+            :userOptions="userOptions"
+            :normal-form="detailForm"
+            :normal-form-api="fApi"
+            :writable-fields="writableFields"
+            @success="refresh"
+          />
+        </div>
+      </el-scrollbar>
+    </div>
+  </ContentWrap>
+
+  <!-- 鎵撳嵃棰勮寮圭獥 -->
+  <PrintDialog ref="printRef" />
+</template>
+<script lang="ts" setup>
+import { formatDate } from '@/utils/formatTime'
+import { DICT_TYPE } from '@/utils/dict'
+import { BpmModelType, BpmModelFormType } from '@/utils/constants'
+import { setConfAndFields2 } from '@/utils/formCreate'
+import { registerComponent } from '@/utils/routerHelper'
+import type { ApiAttrs } from '@form-create/element-ui/types/config'
+import * as ProcessInstanceApi from '@/api/bpm/processInstance'
+import * as UserApi from '@/api/system/user'
+import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
+import ProcessInstanceSimpleViewer from './ProcessInstanceSimpleViewer.vue'
+import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
+import ProcessInstanceOperationButton from './ProcessInstanceOperationButton.vue'
+import ProcessInstanceTimeline from './ProcessInstanceTimeline.vue'
+import { FieldPermissionType } from '@/components/SimpleProcessDesignerV2/src/consts'
+import { TaskStatusEnum } from '@/api/bpm/task'
+import runningSvg from '@/assets/svgs/bpm/running.svg'
+import approveSvg from '@/assets/svgs/bpm/approve.svg'
+import rejectSvg from '@/assets/svgs/bpm/reject.svg'
+import cancelSvg from '@/assets/svgs/bpm/cancel.svg'
+import PrintDialog from './PrintDialog.vue'
+
+defineOptions({ name: 'BpmProcessInstanceDetail' })
+const props = defineProps<{
+  id: string // 娴佺▼瀹炰緥鐨勭紪鍙�
+  taskId?: string // 浠诲姟缂栧彿
+  activityId?: string //娴佺▼娲诲姩缂栧彿锛岀敤浜庢妱閫佹煡鐪�
+}>()
+const message = useMessage() // 娑堟伅寮圭獥
+const processInstanceLoading = ref(false) // 娴佺▼瀹炰緥鐨勫姞杞戒腑
+const processInstance = ref<any>({}) // 娴佺▼瀹炰緥
+const processDefinition = ref<any>({}) // 娴佺▼瀹氫箟
+const processModelView = ref<any>({}) // 娴佺▼妯″瀷瑙嗗浘
+const operationButtonRef = ref() // 鎿嶄綔鎸夐挳缁勪欢 ref
+const auditIconsMap = {
+  [TaskStatusEnum.RUNNING]: runningSvg,
+  [TaskStatusEnum.APPROVE]: approveSvg,
+  [TaskStatusEnum.REJECT]: rejectSvg,
+  [TaskStatusEnum.CANCEL]: cancelSvg
+}
+
+// ========== 鐢宠淇℃伅 ==========
+const fApi = ref<ApiAttrs>() //
+const detailForm = ref({
+  rule: [],
+  option: {},
+  value: {}
+}) // 娴佺▼瀹炰緥鐨勮〃鍗曡鎯�
+
+const writableFields: Array<string> = [] // 琛ㄥ崟鍙互缂栬緫鐨勫瓧娈�
+
+/** 鑾峰緱璇︽儏 */
+const getDetail = () => {
+  // 鑾峰緱瀹℃壒璇︽儏
+  getApprovalDetail()
+  // 鑾峰緱娴佺▼妯″瀷瑙嗗浘
+  getProcessModelView()
+}
+
+/** 鍔犺浇娴佺▼瀹炰緥 */
+const BusinessFormComponent = ref<any>(null) // 寮傛缁勪欢
+/** 鑾峰彇瀹℃壒璇︽儏 */
+const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 瀹℃壒鑺傜偣淇℃伅
+const getApprovalDetail = async () => {
+  processInstanceLoading.value = true
+  try {
+    const param = {
+      processInstanceId: props.id,
+      activityId: props.activityId,
+      taskId: props.taskId
+    }
+    const data = await ProcessInstanceApi.getApprovalDetail(param)
+    if (!data) {
+      message.error('鏌ヨ涓嶅埌瀹℃壒璇︽儏淇℃伅锛�')
+      return
+    }
+    if (!data.processDefinition || !data.processInstance) {
+      message.error('鏌ヨ涓嶅埌娴佺▼淇℃伅锛�')
+      return
+    }
+    processInstance.value = data.processInstance
+    processDefinition.value = data.processDefinition
+
+    // 璁剧疆琛ㄥ崟淇℃伅
+    if (processDefinition.value.formType === BpmModelFormType.NORMAL) {
+      // 鑾峰彇琛ㄥ崟瀛楁鏉冮檺
+      const formFieldsPermission = data.formFieldsPermission
+      // 娓呯┖鍙紪杈戝瓧娈典负绌�
+      writableFields.splice(0)
+      if (detailForm.value.rule?.length > 0) {
+        // 閬垮厤鍒锋柊 form-create 鏄剧ず涓嶄簡
+        detailForm.value.value = processInstance.value.formVariables
+      } else {
+        setConfAndFields2(
+          detailForm,
+          processDefinition.value.formConf,
+          processDefinition.value.formFields,
+          processInstance.value.formVariables
+        )
+      }
+      nextTick().then(() => {
+        fApi.value?.btn.show(false)
+        fApi.value?.resetBtn.show(false)
+        //@ts-ignore
+        fApi.value?.disabled(true)
+        // 璁剧疆琛ㄥ崟瀛楁鏉冮檺
+        if (formFieldsPermission) {
+          Object.keys(data.formFieldsPermission).forEach((item) => {
+            setFieldPermission(item, formFieldsPermission[item])
+          })
+        }
+      })
+    } else {
+      // 娉ㄦ剰锛歞ata.processDefinition.formCustomViewPath 鏄粍浠剁殑鍏ㄨ矾寰勶紝渚嬪璇达細/crm/contract/detail/index.vue
+      BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
+    }
+
+    // 鑾峰彇瀹℃壒鑺傜偣锛屾樉绀� Timeline 鐨勬暟鎹�
+    activityNodes.value = data.activityNodes
+
+    // 鑾峰彇寰呭姙浠诲姟鏄剧ず鎿嶄綔鎸夐挳
+    operationButtonRef.value?.loadTodoTask(data.todoTask)
+  } finally {
+    processInstanceLoading.value = false
+  }
+}
+
+/** 鑾峰彇娴佺▼妯″瀷瑙嗗浘*/
+const getProcessModelView = async () => {
+  if (BpmModelType.BPMN === processDefinition.value?.modelType) {
+    // 閲嶇疆锛岃В鍐� BPMN 娴佺▼鍥惧埛鏂颁笉浼氶噸鏂版覆鏌撻棶棰�
+    processModelView.value = {
+      bpmnXml: ''
+    }
+  }
+  const data = await ProcessInstanceApi.getProcessInstanceBpmnModelView(props.id)
+  if (data) {
+    processModelView.value = data
+  }
+}
+
+/** 璁剧疆琛ㄥ崟鏉冮檺 */
+const setFieldPermission = (field: string, permission: string) => {
+  if (permission === FieldPermissionType.READ) {
+    //@ts-ignore
+    fApi.value?.disabled(true, field)
+  }
+  if (permission === FieldPermissionType.WRITE) {
+    //@ts-ignore
+    fApi.value?.disabled(false, field)
+    // 鍔犲叆鍙互缂栬緫鐨勫瓧娈�
+    writableFields.push(field)
+  }
+  if (permission === FieldPermissionType.NONE) {
+    //@ts-ignore
+    fApi.value?.hidden(true, field)
+  }
+}
+
+/** 鎿嶄綔鎴愬姛鍚庡埛鏂� */
+const refresh = () => {
+  // 閲嶆柊鑾峰彇璇︽儏
+  getDetail()
+}
+
+/** 澶勭悊鎵撳嵃 */
+const printRef = ref()
+const handlePrint = async () => {
+  printRef.value.open(props.id)
+}
+
+/** 褰撳墠鐨� Tab */
+const activeTab = ref('form')
+
+/** 鍒濆鍖� */
+const userOptions = ref<UserApi.UserVO[]>([]) // 鐢ㄦ埛鍒楄〃
+onMounted(async () => {
+  getDetail()
+  // 鑾峰緱鐢ㄦ埛鍒楄〃
+  userOptions.value = await UserApi.getSimpleUserList()
+})
+</script>
+
+<style lang="scss" scoped>
+$wrap-padding-height: 20px;
+$wrap-margin-height: 15px;
+$button-height: 51px;
+$process-header-height: 194px;
+
+.processInstance-wrap-main {
+  height: calc(
+    100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
+  );
+  max-height: calc(
+    100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
+  );
+  overflow: auto;
+
+  .form-scroll-area {
+    display: flex;
+    height: calc(
+      100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
+        $process-header-height - 40px
+    );
+    max-height: calc(
+      100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
+        $process-header-height - 40px
+    );
+    overflow: auto;
+    flex-direction: column;
+
+    :deep(.box-card) {
+      height: 100%;
+      flex: 1;
+
+      .el-card__body {
+        height: 100%;
+        padding: 0;
+      }
+    }
+  }
+}
+
+.form-box {
+  :deep(.el-card) {
+    border: none;
+  }
+}
+</style>

--
Gitblit v1.8.0