ysy 2 jaren geleden
bovenliggende
commit
a9d0b21073
6 gewijzigde bestanden met toevoegingen van 481 en 7 verwijderingen
  1. 19 2
      package.json
  2. 59 0
      src/api/bpm/index.js
  3. 13 0
      src/main.js
  4. 75 0
      src/utils/dateUtils.js
  5. 282 0
      src/views/outsourcing/components/detail.vue
  6. 33 5
      src/views/outsourcing/index.vue

+ 19 - 2
package.json

@@ -16,7 +16,14 @@
     "@amap/amap-jsapi-loader": "^1.0.1",
     "@ant-design/colors": "^6.0.0",
     "@babel/core": "^7.18.13",
+    "@bytemd/plugin-gfm": "^1.17.2",
+    "@bytemd/vue": "^1.17.2",
+    "@riophae/vue-treeselect": "0.4.0",
     "axios": "^0.27.2",
+    "bpmn-js": "8.9.0",
+    "bpmn-js-properties-panel": "0.46.0",
+    "bpmn-js-token-simulation": "0.10.0",
+    "bytemd": "^1.17.2",
     "core-js": "^3.25.0",
     "countup.js": "^2.3.2",
     "cropperjs": "^1.5.12",
@@ -44,7 +51,8 @@
     "vuex": "^3.6.2",
     "vuex-persistedstate": "^4.1.0",
     "xgplayer-vue": "^1.1.5",
-    "xlsx": "^0.18.5"
+    "xlsx": "^0.18.5",
+    "xml-js": "1.6.11"
   },
   "devDependencies": {
     "@vue/cli-plugin-babel": "^5.0.8",
@@ -65,5 +73,14 @@
     "vue-eslint-parser": "^9.0.3",
     "vue-template-compiler": "^2.7.10",
     "webpack": "^5.74.0"
-  }
+  },
+  "main": ".eslintrc.js",
+  "repository": {
+    "type": "git",
+    "url": "http://110.41.163.243:9980/kd-aiot/kd-aiot-frontend-mes.git"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "description": ""
 }

+ 59 - 0
src/api/bpm/index.js

@@ -0,0 +1,59 @@
+import request from '@/utils/request'
+
+export async function getProcessDefinitionBpmnXML(id) {
+    const res = await request({
+      url: '/bpm/process-definition/get-bpmn-xml?id=' + id,
+      method: 'get'
+    })
+    if (res.data.code == 0) {
+      return res.data.data;
+    }
+    return Promise.reject(new Error(res.data.message));
+  }
+  
+
+  export async function getProcessInstance(id) {
+
+    const res= await request({
+      url: '/bpm/process-instance/get?id=' + id,
+      method: 'get',
+    })
+  
+    if (res.data.code == 0) {
+      return res.data.data;
+    }
+    return Promise.reject(new Error(res.data.message));
+  }
+ 
+  
+
+  export async function getTaskListByProcessInstanceId(processInstanceId) {
+    const res = await request({
+      url:
+        '/bpm/task/list-by-process-instance-id?processInstanceId=' +
+        processInstanceId,
+      method: 'get'
+    });
+  
+    if (res.data.code == 0) {
+      return res.data.data;
+    }
+    return Promise.reject(new Error(res.data.message));
+  }
+  
+
+  
+export async function getActivityList(query) {
+
+    const res= await request({
+      url: '/bpm/activity/list',
+      method: 'get',
+      params: query
+    })
+  
+    if (res.data.code == 0) {
+      return res.data.data;
+    }
+    return Promise.reject(new Error(res.data.message));
+  }
+  

+ 13 - 0
src/main.js

@@ -14,6 +14,19 @@ import DictSelection from '@/components/Dict/DictSelection';
 import HeaderTitle from '@/components/header-title';
 Vue.component('HeaderTitle', HeaderTitle);
 
+
+
+// bpmnProcessDesigner 需要引入
+import MyPD from '@/components/bpmnProcessDesigner/package/index.js';
+Vue.use(MyPD);
+import '@/components/bpmnProcessDesigner/package/theme/index.scss';
+import 'bpmn-js/dist/assets/diagram-js.css';
+import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
+import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
+import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
+
+
+
 // // register globally
 import '@/icons';
 

+ 75 - 0
src/utils/dateUtils.js

@@ -0,0 +1,75 @@
+/**
+ * 将毫秒,转换成时间字符串。例如说,xx 分钟
+ *
+ * @param ms 毫秒
+ * @returns {string} 字符串
+ */
+export function getDate(ms) {
+  const day = Math.floor(ms / (24 * 60 * 60 * 1000));
+  const hour =  Math.floor((ms / (60 * 60 * 1000) - day * 24));
+  const minute =  Math.floor(((ms / (60 * 1000)) - day * 24 * 60 - hour * 60));
+  const second =  Math.floor((ms / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60));
+  if (day > 0) {
+    return day + "天" + hour + "小时" + minute + "分钟";
+  }
+  if (hour > 0) {
+    return hour + "小时" + minute + "分钟";
+  }
+  if (minute > 0) {
+    return minute + "分钟";
+  }
+  if (second > 0) {
+    return second + "秒";
+  } else {
+    return 0 + "秒";
+  }
+}
+
+export function beginOfDay(date) {
+  return new Date(date.getFullYear(), date.getMonth(), date.getDate());
+}
+
+export function endOfDay(date) {
+  return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999);
+}
+
+export function betweenDay(date1, date2) {
+  date1 = convertDate(date1);
+  date2 = convertDate(date2);
+  // 计算差值
+  return Math.floor((date2.getTime() - date1.getTime()) / (24 * 3600 * 1000));
+}
+
+export function formatDate(date, fmt) {
+  date = convertDate(date);
+  const o = {
+    "M+": date.getMonth() + 1, //月份
+    "d+": date.getDate(), //日
+    "H+": date.getHours(), //小时
+    "m+": date.getMinutes(), //分
+    "s+": date.getSeconds(), //秒
+    "q+": Math.floor((date.getMonth() + 3) / 3), //季度
+    "S": date.getMilliseconds() //毫秒
+  };
+  if (/(y+)/.test(fmt)) { // 年份
+    fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
+  }
+  for (const k in o) {
+    if (new RegExp("(" + k + ")").test(fmt)) {
+      fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
+    }
+  }
+  return fmt;
+}
+
+export function addTime(date, time) {
+  date = convertDate(date);
+  return new Date(date.getTime() + time);
+}
+
+export function convertDate(date) {
+  if (typeof date === 'string') {
+    return new Date(date);
+  }
+  return date;
+}

+ 282 - 0
src/views/outsourcing/components/detail.vue

@@ -0,0 +1,282 @@
+<template>
+  <el-dialog
+    title="详情"
+    :before-close="handleClose"
+    :visible.sync="dialogVisible"
+    :close-on-click-modal="false"
+    :append-to-body="true"
+    width="80%"
+  >
+    <div class="app-container" style="padding: 15px">
+      <!-- 审批记录 -->
+      <el-card class="box-card" v-loading="tasksLoad">
+        <div slot="header" class="clearfix">
+          <span class="el-icon-picture-outline">审批记录</span>
+        </div>
+        <el-col :span="16" :offset="4">
+          <div class="block">
+            <el-timeline>
+              <el-timeline-item
+                v-for="(item, index) in tasks"
+                :key="index"
+                :icon="getTimelineItemIcon(item)"
+                :type="getTimelineItemType(item)"
+              >
+                <p style="font-weight: 700">任务:{{ item.name }}</p>
+                <el-card :body-style="{ padding: '10px' }">
+                  <label
+                    v-if="item.assigneeUser"
+                    style="font-weight: normal; margin-right: 30px"
+                  >
+                    审批人:{{ item.assigneeUser.nickname }}
+                    <el-tag type="info" size="mini">{{
+                      item.assigneeUser.deptName
+                    }}</el-tag>
+                  </label>
+                  <label style="font-weight: normal" v-if="item.createTime"
+                    >创建时间:</label
+                  >
+                  <label style="color: #8a909c; font-weight: normal">{{
+                    item.createTime
+                  }}</label>
+                  <label
+                    v-if="item.endTime"
+                    style="margin-left: 30px; font-weight: normal"
+                    >审批时间:</label
+                  >
+                  <label
+                    v-if="item.endTime"
+                    style="color: #8a909c; font-weight: normal"
+                  >
+                    {{ item.endTime }}</label
+                  >
+                  <label
+                    v-if="item.durationInMillis"
+                    style="margin-left: 30px; font-weight: normal"
+                    >耗时:</label
+                  >
+                  <label
+                    v-if="item.durationInMillis"
+                    style="color: #8a909c; font-weight: normal"
+                  >
+                    {{ getDateStar(item.durationInMillis) }}
+                  </label>
+                  <p v-if="item.reason">
+                    <el-tag :type="getTimelineItemType(item)">{{
+                      item.reason
+                    }}</el-tag>
+                  </p>
+                </el-card>
+              </el-timeline-item>
+            </el-timeline>
+          </div>
+        </el-col>
+      </el-card>
+      <!-- 高亮流程图 -->
+      <el-card class="box-card" v-loading="processInstanceLoading">
+        <div slot="header" class="clearfix">
+          <span class="el-icon-picture-outline">流程图1</span>
+        </div>
+
+        <my-process-viewer
+          key="designer"
+          v-model="bpmnXML"
+          v-bind="bpmnControlForm"
+          :activityData="activityList"
+          :processInstanceData="processInstance"
+          :taskData="tasks"
+        />
+      </el-card>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+  import { getProcessDefinitionBpmnXML } from '@/api/bpm/index';
+  import store from '@/store';
+  import { getProcessInstance } from '@/api/bpm/index';
+  import { getDate } from '@/utils/dateUtils';
+  import dictMixins from '@/mixins/dictMixins';
+  import { getTaskListByProcessInstanceId } from '@/api/bpm/index';
+  import { getActivityList } from '@/api/bpm/index';
+  // import Vue from 'vue';
+
+  // 流程实例的详情页,可用于审批
+  export default {
+    name: 'outsourcing',
+    mixins: [dictMixins],
+
+    components: {},
+    data() {
+      return {
+        // 遮罩层
+        processInstanceLoading: true,
+        dialogVisible: false,
+
+        // 流程实例
+        id: undefined, // 流程实例的编号
+        processInstance: {},
+
+        // BPMN 数据
+        bpmnXML: null,
+        bpmnControlForm: {
+          prefix: 'flowable'
+        },
+        activityList: [],
+
+        // 审批记录
+        tasksLoad: true,
+        tasks: []
+      };
+    },
+    created() {
+      // this.id = this.$route.query.id;
+      // if (!this.id) {
+      //   this.$message.error('未传递 id 参数,无法查看流程信息');
+      //   return;
+      // }
+      // this.getDetail();
+    },
+    methods: {
+      open(id) {
+        this.id = id;
+        this.dialogVisible = true;
+        this.getDetail();
+      },
+      /** 获得流程实例 */
+      getDetail() {
+        // 获得流程实例相关
+        this.processInstanceLoading = true;
+        getProcessInstance(this.id).then((response) => {
+          if (!response) {
+            this.$message.error('查询不到流程信息!');
+            return;
+          }
+          // 设置流程信息
+          this.processInstance = response;
+
+          // //将业务表单,注册为动态组件
+          // const path = this.processInstance.processDefinition.formCustomViewPath;
+          // Vue.component("async-biz-form-component", function (resolve) {
+          //   require([`@/views${path}`], resolve);
+          // });
+          // 加载流程图
+          getProcessDefinitionBpmnXML(
+            this.processInstance.processDefinition.id
+          ).then((response) => {
+            this.bpmnXML = response;
+          });
+          // 加载活动列表
+          getActivityList({
+            processInstanceId: this.processInstance.id
+          }).then((response) => {
+            console.log(response, 'response');
+            this.activityList = response;
+          });
+
+          // 取消加载中
+          this.processInstanceLoading = false;
+        });
+
+        // 获得流程任务列表(审批记录)
+        this.tasksLoad = true;
+        getTaskListByProcessInstanceId(this.id).then((response) => {
+          // 审批记录
+          this.tasks = [];
+          // 移除已取消的审批
+          response.forEach((task) => {
+            if (task.result !== 4) {
+              this.tasks.push(task);
+            }
+          });
+          // 排序,将未完成的排在前面,已完成的排在后面;
+          this.tasks.sort((a, b) => {
+            // 有已完成的情况,按照完成时间倒序
+            if (a.endTime && b.endTime) {
+              return b.endTime - a.endTime;
+            } else if (a.endTime) {
+              return 1;
+            } else if (b.endTime) {
+              return -1;
+              // 都是未完成,按照创建时间倒序
+            } else {
+              return b.createTime - a.createTime;
+            }
+          });
+
+          // 需要审核的记录
+          const userId = store.getters.userId;
+          this.tasks.forEach((task) => {
+            if (task.result !== 1 && task.result !== 6) {
+              // 只有待处理才需要
+              return;
+            }
+            if (!task.assigneeUser || task.assigneeUser.id !== userId) {
+              // 自己不是处理人
+              return;
+            }
+          });
+
+          // 取消加载中
+          this.tasksLoad = false;
+        });
+      },
+      getDateStar(ms) {
+        return getDate(ms);
+      },
+      getTimelineItemIcon(item) {
+        if (item.result === 1) {
+          return 'el-icon-time';
+        }
+        if (item.result === 2) {
+          return 'el-icon-check';
+        }
+        if (item.result === 3) {
+          return 'el-icon-close';
+        }
+        if (item.result === 4) {
+          return 'el-icon-remove-outline';
+        }
+        if (item.result === 5) {
+          return 'el-icon-back';
+        }
+        return '';
+      },
+      getTimelineItemType(item) {
+        if (item.result === 1) {
+          return 'primary';
+        }
+        if (item.result === 2) {
+          return 'success';
+        }
+        if (item.result === 3) {
+          return 'danger';
+        }
+        if (item.result === 4) {
+          return 'info';
+        }
+        if (item.result === 5) {
+          return 'warning';
+        }
+        if (item.result === 6) {
+          return 'default';
+        }
+        return '';
+      },
+      handleClose() {
+        this.dialogVisible = false;
+      }
+    }
+  };
+</script>
+
+<style lang="scss">
+  .my-process-designer {
+    height: calc(100vh - 200px);
+  }
+
+  .box-card {
+    width: 100%;
+    margin-bottom: 20px;
+  }
+</style>

+ 33 - 5
src/views/outsourcing/index.vue

@@ -7,21 +7,29 @@
             <ele-pro-table ref="table" :columns="columns" :datasource="datasource" cache-key="workOrderTable">
 
 
+                <template v-slot:action="{ row }">
+                    <el-link type="primary" :underline="false" @click="handleDetails(row)">
+                        详情
+                    </el-link>
+                </template>
+
+
 
             </ele-pro-table>
         </el-card>
-
+        <detail ref="detailRef"></detail>
 
     </div>
 </template>
   
 <script>
 import { getList } from '@/api/outsourcing/index.js';
-
+import detail from './components/detail.vue'
 import search from './components/search.vue';
 export default {
     components: {
-        search
+        search,
+        detail
     },
     data() {
         return {
@@ -68,7 +76,7 @@ export default {
                     align: 'center'
                 },
 
-            
+
                 {
                     prop: 'workOrderCode',
                     label: '工单编码',
@@ -115,7 +123,21 @@ export default {
                     showOverflowTooltip: true,
                     minWidth: 110
                 },
-             
+
+
+                
+                {
+                    columnKey: 'action',
+                    label: '操作',
+                    width: 120,
+                    align: 'center',
+                    resizable: false,
+                    fixed: 'right',
+                    slot: 'action',
+                    showOverflowTooltip: true
+                }
+
+
 
 
 
@@ -141,6 +163,12 @@ export default {
         },
 
 
+        handleDetails(row) {
+            this.$refs.detailRef.open(row.processInstanceId);
+
+        },
+
+
 
         /* 刷新表格 */
         reload(where) {