Przeglądaj źródła

feat: 作业票提交后支持查看审批流程

1. 复用 bpm/processInstance/detail.vue 组件,展示审批记录时间线与高亮流程图

2. 作业票管理列表新增流程入口,提交成功后自动弹出审批流程

3. 作业票查看弹窗新增作业票信息/审批流程页签

4. constants.js 新增 getProcessInstanceId,兼容流程实例 id 的多种字段命名
xieyong 6 dni temu
rodzic
commit
ef830be3f3

+ 58 - 4
src/views/DangerousWorkList/add.vue

@@ -7,8 +7,28 @@
     :maxable="true"
     @close="cancel"
   >
+    <!-- 查看态:作业票信息 / 审批流程 切换 -->
+    <div class="switch" v-if="isView">
+      <div class="switch_left">
+        <ul>
+          <li
+            :class="{ active: activeComp === 'main' }"
+            @click="activeComp = 'main'"
+          >
+            作业票信息
+          </li>
+          <li
+            :class="{ active: activeComp === 'bpm' }"
+            @click="activeComp = 'bpm'"
+          >
+            审批流程
+          </li>
+        </ul>
+      </div>
+    </div>
     <el-form
       ref="form"
+      v-show="activeComp === 'main'"
       v-loading="detailLoading"
       element-loading-text="正在加载作业票详情..."
       :model="form"
@@ -173,6 +193,12 @@
       </el-form-item>
     </el-form>
 
+    <!-- 审批流程(审批记录 + 高亮流程图) -->
+    <div v-if="activeComp === 'bpm'" class="flow-box">
+      <bpmDetail v-if="form.processInstanceId" :id="form.processInstanceId" />
+      <div v-else class="flow-empty">该作业票暂无审批流程</div>
+    </div>
+
     <template v-slot:footer>
       <el-button
         v-if="!isView"
@@ -208,7 +234,12 @@
     getWorkTicketTemplate,
     getUnfilledRequiredFields
   } from './templates';
-  import { DRAFT_STATUS, getProcessDefinitionKey } from './constants';
+  import {
+    DRAFT_STATUS,
+    getProcessDefinitionKey,
+    getProcessInstanceId
+  } from './constants';
+  import bpmDetail from '@/views/bpm/processInstance/detail.vue';
   import {
     ticketSave,
     ticketUpdate,
@@ -245,7 +276,9 @@
     endTime: '',
     remark: '',
     // 作业票状态:1-待提交、2-审批中、3-已通过、4-已驳回
-    status: ''
+    status: '',
+    // 审批流程实例 id,用于查看审批流程
+    processInstanceId: ''
   };
 
   export default {
@@ -255,7 +288,8 @@
       personSelect,
       releaseRules,
       experimentationProcessNew,
-      workTicketForm
+      workTicketForm,
+      bpmDetail
     },
     mixins: [areaMixin, dictMixins],
     data() {
@@ -269,6 +303,8 @@
         // 详情请求序号:用于丢弃过期响应(快速切换作业票时)
         detailSeq: 0,
         dialogType: 'add',
+        // 弹窗内容页签:main-作业票信息、bpm-审批流程
+        activeComp: 'main',
         form: { ...defForm },
         personList: [],
         // 匹配到的前端作业票模板
@@ -490,6 +526,7 @@
       async open(work) {
         this.visible = true;
         this.dialogType = 'add';
+        this.activeComp = 'main';
         const detail = work || {};
         this.resetTicketForm();
         this.form = {
@@ -527,6 +564,7 @@
       async openTicket(ticket, work, mode) {
         this.visible = true;
         this.dialogType = mode || 'view';
+        this.activeComp = 'main';
         this.detailLoading = true;
         const seq = ++this.detailSeq;
         // 先清空上一次的数据,避免详情返回前展示旧内容
@@ -584,7 +622,10 @@
           startTime: detail.startTime || '',
           endTime: detail.endTime || '',
           workType: detail.workType || '',
-          status: detail.status ?? row?.status ?? ''
+          status: detail.status ?? row?.status ?? '',
+          // 流程实例 id:详情没有时用列表行数据兜底
+          processInstanceId:
+            getProcessInstanceId(detail) || getProcessInstanceId(row)
         };
         // 解析已保存的作业票内容
         const extension = this.parseExtension(detail.extensionJson);
@@ -841,4 +882,17 @@
     flex: 1;
     color: #303133;
   }
+
+  /* 审批流程 */
+  .flow-box {
+    max-height: calc(100vh - 300px);
+    overflow-y: auto;
+  }
+
+  .flow-empty {
+    padding: 40px 0;
+    font-size: 14px;
+    color: #909399;
+    text-align: center;
+  }
 </style>

+ 41 - 0
src/views/DangerousWorkList/constants.js

@@ -74,3 +74,44 @@ export function getProcessDefinitionKey(workType, workTypeName) {
   }
   return '';
 }
+
+/**
+ * 作业票上流程实例 id 的字段名
+ * 后端发起审批流后会把流程实例 id 回写到作业票上,字段名以接口实际返回为准,
+ * 这里兼容本项目其它业务模块(拣货申请、事故事件、设备处置)使用的命名
+ */
+export const PROCESS_INSTANCE_ID_KEYS = [
+  'processInstanceId',
+  'processInstId',
+  'processId',
+  'flowInstanceId'
+];
+
+// flowable 的流程实例 id 为 uuid,用于识别「后端直接返回流程实例 id」的响应
+const UUID_REG =
+  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
+
+/**
+ * 从作业票数据或提交响应中取流程实例 id
+ * @param source 作业票对象、作业票详情或提交接口的响应数据
+ * @returns 流程实例 id,没有则返回空字符串
+ */
+export function getProcessInstanceId(source) {
+  if (!source) {
+    return '';
+  }
+  if (typeof source === 'string') {
+    // 兼容后端直接返回流程实例 id
+    return UUID_REG.test(source) ? source : '';
+  }
+  if (typeof source !== 'object' || Array.isArray(source)) {
+    return '';
+  }
+  for (const key of PROCESS_INSTANCE_ID_KEYS) {
+    const value = source[key];
+    if (value !== undefined && value !== null && value !== '') {
+      return String(value);
+    }
+  }
+  return '';
+}

+ 172 - 104
src/views/DangerousWorkList/workTicketDialog.vue

@@ -1,105 +1,126 @@
 <template>
-  <ele-modal
-    :visible.sync="visible"
-    title="作业票管理"
-    width="80vw"
-    append-to-body
-    :close-on-click-modal="false"
-    @close="handleClose"
-  >
-    <div>
-      <!-- 搜索区域 -->
-      <div class="search-area">
-        <seek-page
-          ref="seekPage"
-          :seekList="seekList"
-          @search="handleSearch"
-          :formLength="4"
-        />
-      </div>
+  <div>
+    <ele-modal
+      :visible.sync="visible"
+      title="作业票管理"
+      width="80vw"
+      append-to-body
+      :close-on-click-modal="false"
+      @close="handleClose"
+    >
+      <div>
+        <!-- 搜索区域 -->
+        <div class="search-area">
+          <seek-page
+            ref="seekPage"
+            :seekList="seekList"
+            @search="handleSearch"
+            :formLength="4"
+          />
+        </div>
 
-      <!-- 列表视图 -->
-      <div class="list-view">
-        <!--
-          v-if="work.id":el-dialog 首次打开才会渲染弹窗内容,表格也是此时才挂载。
-          表格挂载后由自身 mounted 触发首次加载,避免首次打开时 $refs.table 还不存在而不发请求。
-        -->
-        <ele-pro-table
-          v-if="work.id"
-          ref="table"
-          row-key="id"
-          :columns="columns"
-          :datasource="datasource"
-          :cache-key="cacheKeyUrl"
-          max-height="500px"
-          @columns-change="handleColumnChange"
-          @selection-change="handleSelectionChange"
-        >
-          <template v-slot:toolbar>
-            <div class="toolbar-left">
-              <el-button
-                type="primary"
-                size="small"
-                icon="el-icon-plus"
-                @click="handleAdd"
-                >申请作业票</el-button
-              >
-              <el-button
-                type="danger"
-                size="small"
-                icon="el-icon-delete"
-                :disabled="!selectedRows.length"
-                @click="handleBatchDelete"
-                >批量删除</el-button
-              >
-              <span class="toolbar-tip"
-                >仅草稿(待提交)状态的作业票可编辑、提交、删除</span
-              >
-            </div>
-          </template>
-          <template v-slot:workType="{ row }">
-            <span>{{ getWorkTypeName(row.workType) }}</span>
-          </template>
-          <template v-slot:status="{ row }">
-            <el-tag :type="getStatusType(row.status)" size="small">
-              {{ row.statusName || getStatusName(row.status) }}
-            </el-tag>
-          </template>
-          <template v-slot:action="{ row }">
-            <div class="action-links">
-              <el-link
-                type="primary"
-                :underline="false"
-                @click="handleView(row)"
-                >查看</el-link
-              >
-              <el-link
-                type="primary"
-                :underline="false"
-                @click="handleEdit(row)"
-                >编辑</el-link
-              >
-              <el-link
-                v-if="isDraft(row)"
-                type="success"
-                :underline="false"
-                :disabled="submittingId === row.id"
-                @click="handleSubmit(row)"
-                >提交</el-link
-              >
-              <el-link
-                type="danger"
-                :underline="false"
-                @click="handleDelete(row)"
-                >删除</el-link
-              >
-            </div>
-          </template>
-        </ele-pro-table>
+        <!-- 列表视图 -->
+        <div class="list-view">
+          <!--
+            v-if="work.id":el-dialog 首次打开才会渲染弹窗内容,表格也是此时才挂载。
+            表格挂载后由自身 mounted 触发首次加载,避免首次打开时 $refs.table 还不存在而不发请求。
+          -->
+          <ele-pro-table
+            v-if="work.id"
+            ref="table"
+            row-key="id"
+            :columns="columns"
+            :datasource="datasource"
+            :cache-key="cacheKeyUrl"
+            max-height="500px"
+            @columns-change="handleColumnChange"
+            @selection-change="handleSelectionChange"
+          >
+            <template v-slot:toolbar>
+              <div class="toolbar-left">
+                <el-button
+                  type="primary"
+                  size="small"
+                  icon="el-icon-plus"
+                  @click="handleAdd"
+                  >申请作业票</el-button
+                >
+                <el-button
+                  type="danger"
+                  size="small"
+                  icon="el-icon-delete"
+                  :disabled="!selectedRows.length"
+                  @click="handleBatchDelete"
+                  >批量删除</el-button
+                >
+                <span class="toolbar-tip"
+                  >仅草稿(待提交)状态的作业票可编辑、提交、删除</span
+                >
+              </div>
+            </template>
+            <template v-slot:workType="{ row }">
+              <span>{{ getWorkTypeName(row.workType) }}</span>
+            </template>
+            <template v-slot:status="{ row }">
+              <el-tag :type="getStatusType(row.status)" size="small">
+                {{ row.statusName || getStatusName(row.status) }}
+              </el-tag>
+            </template>
+            <template v-slot:action="{ row }">
+              <div class="action-links">
+                <el-link
+                  type="primary"
+                  :underline="false"
+                  @click="handleView(row)"
+                  >查看</el-link
+                >
+                <el-link
+                  type="primary"
+                  :underline="false"
+                  @click="handleEdit(row)"
+                  >编辑</el-link
+                >
+                <el-link
+                  v-if="isDraft(row)"
+                  type="success"
+                  :underline="false"
+                  :disabled="submittingId === row.id"
+                  @click="handleSubmit(row)"
+                  >提交</el-link
+                >
+                <el-link
+                  v-if="!isDraft(row)"
+                  type="primary"
+                  :underline="false"
+                  @click="handleFlow(row)"
+                  >流程</el-link
+                >
+                <el-link
+                  type="danger"
+                  :underline="false"
+                  @click="handleDelete(row)"
+                  >删除</el-link
+                >
+              </div>
+            </template>
+          </ele-pro-table>
+        </div>
       </div>
-    </div>
-    <add ref="addRef" @reload="reloadTable"> </add>
-  </ele-modal>
+      <add ref="addRef" @reload="reloadTable"> </add>
+    </ele-modal>
+
+    <!-- 审批流程(审批记录 + 高亮流程图) -->
+    <ele-modal
+      title="审批流程"
+      :visible.sync="flowVisible"
+      width="90%"
+      append-to-body
+      :close-on-click-modal="false"
+      :maxable="true"
+    >
+      <flow v-if="flowVisible" :id="currentProcessInstanceId" />
+    </ele-modal>
+  </div>
 </template>
 
 <script>
@@ -107,11 +128,18 @@
   import dictMixins from '@/mixins/dictMixins';
   import dictEnum from '@/enum/dict';
   import add from './add.vue';
-  import { ticketPage, ticketRemove, ticketSubmit } from '@/api/dangerousWork';
+  import flow from '@/views/bpm/processInstance/detail.vue';
+  import {
+    ticketPage,
+    ticketRemove,
+    ticketSubmit,
+    ticketGetById
+  } from '@/api/dangerousWork';
   import {
     TICKET_STATUS,
     DRAFT_STATUS,
-    getProcessDefinitionKey
+    getProcessDefinitionKey,
+    getProcessInstanceId
   } from './constants';
 
   // 仅草稿(待提交)状态的作业票可以删除
@@ -119,7 +147,7 @@
 
   export default {
     mixins: [tabMixins, dictMixins],
-    components: { add },
+    components: { add, flow },
     data() {
       return {
         visible: false,
@@ -129,6 +157,10 @@
         selectedRows: [],
         // 正在提交审批的作业票 id
         submittingId: null,
+        // 审批流程弹窗
+        flowVisible: false,
+        // 当前查看的流程实例 id
+        currentProcessInstanceId: '',
         columns: [
           {
             columnKey: 'selection',
@@ -201,7 +233,7 @@
           {
             columnKey: 'action',
             label: '操作',
-            width: 200,
+            width: 220,
             align: 'center',
             slot: 'action'
           }
@@ -319,9 +351,16 @@
           .then(() => {
             this.submittingId = row.id;
             return ticketSubmit({ id: row.id, processDefinitionKey })
-              .then(() => {
+              .then(async (data) => {
                 this.$message.success('提交成功,已发起审批');
                 this.reloadTable();
+                // 提交后自动打开审批流程:优先用响应里的流程实例 id,没有则回查作业票详情
+                const processInstanceId =
+                  getProcessInstanceId(data) ||
+                  (await this.resolveProcessInstanceId(row));
+                if (processInstanceId) {
+                  this.openFlow(processInstanceId);
+                }
               })
               .finally(() => {
                 this.submittingId = null;
@@ -329,6 +368,35 @@
           })
           .catch(() => {});
       },
+      // 查看审批流程
+      async handleFlow(row) {
+        const processInstanceId = await this.resolveProcessInstanceId(row);
+        if (!processInstanceId) {
+          this.$message.warning('该作业票暂无审批流程');
+          return;
+        }
+        this.openFlow(processInstanceId);
+      },
+      // 打开审批流程弹窗
+      openFlow(processInstanceId) {
+        this.currentProcessInstanceId = processInstanceId;
+        this.flowVisible = true;
+      },
+      // 取作业票的流程实例 id:优先用列表行数据,没有再查作业票详情
+      async resolveProcessInstanceId(row) {
+        const fromRow = getProcessInstanceId(row);
+        if (fromRow) {
+          return fromRow;
+        }
+        try {
+          const detail = await ticketGetById(row.id, {
+            showErrorToast: false
+          });
+          return getProcessInstanceId(detail);
+        } catch (e) {
+          return '';
+        }
+      },
       handleClose() {
         this.work = {};
         this.visible = false;