chen.cheng 4 сар өмнө
parent
commit
a17d7d0878

+ 5 - 0
.env.development

@@ -22,3 +22,8 @@ VUE_APP_3D_SWITCH = false
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true
+
+
+# 工作流地址
+VUE_APP_FLOW_API = '/pkb-api'
+#VUE_APP_FLOW_API = 'http://localhost:28080'

+ 5 - 0
.env.production

@@ -20,3 +20,8 @@ VUE_APP_DEF_LANGUAGE = 'en'
 
 # 3D图层开关
 VUE_APP_3D_SWITCH = true
+
+
+# 工作流地址
+VUE_APP_FLOW_API = '/prod-api'
+#VUE_APP_FLOW_API = 'http://localhost:81'

+ 3 - 0
package.json

@@ -38,6 +38,8 @@
   },
   "dependencies": {
     "@fingerprintjs/fingerprintjs": "^4.5.1",
+    "@logicflow/core": "^1.2.15",
+    "@logicflow/extension": "^1.2.16",
     "@riophae/vue-treeselect": "0.4.0",
     "axios": "0.28.1",
     "clipboard": "2.0.8",
@@ -51,6 +53,7 @@
     "js-beautify": "1.13.0",
     "js-cookie": "3.0.1",
     "jsencrypt": "3.0.0-rc.1",
+    "json-bigint": "^1.0.0",
     "nprogress": "0.2.0",
     "quill": "1.3.7",
     "screenfull": "5.0.2",

+ 133 - 0
src/api/flow/definition.js

@@ -0,0 +1,133 @@
+import request from '@/utils/request'
+
+// 查询流程定义列表
+export function listDefinition(query) {
+  return request({
+    url: '/flow/definition/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询流程定义详细
+export function getDefinition(id) {
+  return request({
+    url: '/flow/definition/' + id,
+    method: 'get'
+  })
+}
+
+// 获取流程定义xml字符串
+export function saveXml(data) {
+  return request({
+    url: '/flow/definition/saveXml',
+    method: 'post',
+    data: data
+  })
+}
+
+// 导出流程定义详细
+export function exportDefinition(id) {
+  return request({
+    url: '/flow/definition/exportDefinition/' + id,
+    method: 'get'
+  })
+}
+
+// 获取流程定义xml字符串
+export function xmlString(id) {
+  return request({
+    url: '/flow/definition/xmlString/' + id,
+    method: 'get'
+  })
+}
+
+// 新增流程定义
+export function addDefinition(data) {
+  return request({
+    url: '/flow/definition',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改流程定义
+export function updateDefinition(data) {
+  return request({
+    url: '/flow/definition',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除流程定义
+export function delDefinition(id) {
+  return request({
+    url: '/flow/definition/' + id,
+    method: 'delete'
+  })
+}
+
+// 发布流程定义
+export function publish(id) {
+  return request({
+    url: '/flow/definition/publish/' + id,
+    method: 'get'
+  })
+}
+
+// 取消发布流程定义
+export function unPublish(id) {
+  return request({
+    url: '/flow/definition/unPublish/' + id,
+    method: 'get'
+  })
+}
+
+// 复制流程定义
+export function copyDef(id) {
+  return request({
+    url: '/flow/definition/copyDef/' + id,
+    method: 'get'
+  })
+}
+
+// 查看流程图
+export function chartDef(definitionId) {
+  return request({
+    url: '/flow/definition/chartDef/' + definitionId,
+    method: 'get'
+  })
+}
+
+// 查看流程图
+export function flowImage(instanceId) {
+  return request({
+    url: '/flow/definition/flowChart/' + instanceId,
+    method: 'get'
+  })
+}
+
+// 激活流程
+export function active(definitionId) {
+  return request({
+    url: '/flow/definition/active/' + definitionId,
+    method: 'get'
+  })
+}
+
+// 挂起流程
+export function unActive(definitionId) {
+  return request({
+    url: '/flow/definition/unActive/' + definitionId,
+    method: 'get'
+  })
+}
+
+// 查询已发布表单定义列表
+export function publishedList() {
+  return request({
+    url: '/warm-flow/published-form',
+    method: 'get'
+  })
+}

+ 90 - 0
src/api/flow/execute.js

@@ -0,0 +1,90 @@
+import request from '@/utils/request'
+
+// 查询待办任务列表
+export function toDoPage(query) {
+  return request({
+    url: '/flow/execute/toDoPage',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询已办任务列表
+export function donePage(query) {
+  return request({
+    url: '/flow/execute/donePage',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询抄送任务列表
+export function copyPage(query) {
+  return request({
+    url: '/flow/execute/copyPage',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询已办任务列表
+export function doneList(instanceId) {
+  return request({
+    url: '/flow/execute/doneList/' + instanceId,
+    method: 'get'
+  })
+}
+
+// 查询跳转任意节点列表
+export function anyNodeList(instanceId) {
+  return request({
+    url: '/flow/execute/anyNodeList/' + instanceId,
+    method: 'get'
+  })
+}
+
+// 转办|加签|委派|减签
+export function interactiveType(taskId, assigneePermission, operatorType) {
+  return request({
+    url: '/flow/execute/interactiveType',
+    method: 'post',
+    params: {
+              taskId: taskId,
+              addHandlers: assigneePermission,
+              operatorType: operatorType
+            }
+  })
+}
+
+// 查询跳转任意节点列表
+export function getTaskById(taskId) {
+  return request({
+    url: '/flow/execute/getTaskById/' + taskId,
+    method: 'get'
+  })
+}
+
+// 激活流程
+export function active(instanceId) {
+  return request({
+    url: '/flow/execute/active/' + instanceId,
+    method: 'get'
+  })
+}
+
+// 挂起流程
+export function unActive(instanceId) {
+  return request({
+    url: '/flow/execute/unActive/' + instanceId,
+    method: 'get'
+  })
+}
+
+// 查询用户列表-转办|加签|委派|减签
+export function interactiveTypeSysUser(query) {
+  return request({
+    url: '/flow/execute/interactiveTypeSysUser',
+    method: 'get',
+    params: query
+  })
+}

+ 77 - 0
src/api/form/definition.js

@@ -0,0 +1,77 @@
+import request from '@/utils/request'
+
+// 查询表单定义列表
+export function listDefinition(query) {
+  return request({
+    url: '/flow/form/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询表单定义详细
+export function getDefinition(id) {
+  return request({
+    url: '/flow/form/' + id,
+    method: 'get'
+  })
+}
+
+// 新增表单定义
+export function addDefinition(data) {
+  return request({
+    url: '/flow/form/add',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改表单定义
+export function updateDefinition(data) {
+  return request({
+    url: '/flow/form/edit',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除表单定义
+export function delDefinition(id) {
+  return request({
+    url: '/flow/form/' + id,
+    method: 'delete'
+  })
+}
+
+// 发布表单定义
+export function publish(id) {
+  return request({
+    url: '/flow/form/publish/' + id,
+    method: 'get'
+  })
+}
+
+// 取消发布表单定义
+export function unPublish(id) {
+  return request({
+    url: '/flow/form/unPublish/' + id,
+    method: 'get'
+  })
+}
+
+// 复制表单定义
+export function copyDef(id) {
+  return request({
+    url: '/flow/form/copyForm/' + id,
+    method: 'post'
+  })
+}
+
+// 保存表单设计
+export function saveFormContent(data) {
+  return request({
+    url: '/flow/form/saveFormContent',
+    method: 'post',
+    data: data
+  })
+}

+ 71 - 0
src/api/system/leave.js

@@ -0,0 +1,71 @@
+import request from '@/utils/request'
+
+// 查询OA 请假申请列表
+export function listLeave(query) {
+  return request({
+    url: '/system/leave/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询OA 请假申请详细
+export function getLeave(id) {
+  return request({
+    url: '/system/leave/' + id,
+    method: 'get'
+  })
+}
+
+// 新增OA 请假申请
+export function addLeave(data, flowStatus) {
+  return request({
+    url: '/system/leave?flowStatus=' + flowStatus,
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改OA 请假申请
+export function updateLeave(data) {
+  return request({
+    url: '/system/leave',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除OA 请假申请
+export function delLeave(id) {
+  return request({
+    url: '/system/leave/' + id,
+    method: 'delete'
+  })
+}
+
+// 提交审批OA 请假申请
+export function submit(id, flowStatus) {
+  return request({
+    url: '/system/leave/submit?id=' + id + '&flowStatus=' + flowStatus,
+    method: 'get'
+  })
+}
+
+// 办理OA 请假申请
+export function handle(data, taskId, skipType, message, nodeCode, flowStatus) {
+  return request({
+    url: '/system/leave/handle?taskId=' + taskId + '&skipType=' + skipType + '&message=' + message
+      + '&nodeCode=' + nodeCode + '&flowStatus=' + flowStatus,
+    data: data,
+    method: 'post'
+  })
+}
+
+// 终止流程
+export function termination(data) {
+  return request({
+    url: '/system/leave/termination',
+    method: 'post',
+    data: data
+  })
+}

+ 42 - 0
src/router/index.js

@@ -171,6 +171,48 @@ export const dynamicRoutes = [
         meta: {title: '修改生成配置', activeMenu: '/tool/gen'}
       }
     ]
+  },
+  {
+    path: '/flow/flow-design',
+    component: Layout,
+    hidden: true,
+    permissions: ['flow:definition:design'],
+    children: [
+      {
+        path: 'index/:id(\\d+)',
+        component: () => import('@/views/flow/definition/warm-flow'),
+        name: 'Design',
+        meta: { title: '流程设计', activeMenu: '/flow/definition' }
+      }
+    ]
+  },
+  {
+    path: '/form/form-design',
+    component: Layout,
+    hidden: true,
+    permissions: ['form:definition:design'],
+    children: [
+      {
+        path: 'index/:id(\\d+)',
+        component: () => import('@/views/form/definition/warm-form'),
+        name: 'formDesign',
+        meta: { title: '表单设计', activeMenu: '/form/formDefinition' }
+      }
+    ]
+  },
+  {
+    path: '/done/doneList',
+    component: Layout,
+    hidden: true,
+    permissions: ['flow:execute:doneList'],
+    children: [
+      {
+        path: 'index/:instanceId(\\d+)',
+        component: () => import('@/views/flow/task/done/doneList'),
+        name: 'DoneList',
+        meta: { title: '流程历史记录', activeMenu: '/flow/task/done' }
+      }
+    ]
   }
 ]
 

+ 16 - 1
src/utils/request.js

@@ -7,6 +7,7 @@ import {blobValidate, tansParams} from "@/utils/ruoyi";
 import cache from '@/plugins/cache'
 import {saveAs} from 'file-saver'
 import i18n from '@/i18n'
+import JSONBIG from 'json-bigint'
 import {sysLanguage} from "@/utils/index";
 
 let downloadLoadingInstance;
@@ -19,7 +20,21 @@ const service = axios.create({
   // axios中请求配置有baseURL选项,表示请求URL公共部分
   baseURL: process.env.VUE_APP_BASE_API,
   // 超时
-  timeout: 10000
+  timeout: 10000,
+  transformResponse: [function (data) {
+    // transformResponse这个配置项可以拦截接口返回的内容进行处理
+    if (data instanceof Blob || data instanceof ArrayBuffer) {
+      return data
+    }
+    try {
+      // 如果大数字类型转换成功则返回转换的数据结果
+      return JSONBIG.parse(data);
+    } catch (err) {
+      // 如果转换失败,代表没有长数字可转,正常解析并返回
+      return JSON.parse(data)
+    }
+  }]
+
 })
 
 console.log(i18n.t("session.outTime"))

+ 344 - 0
src/views/components/selectUser.vue

@@ -0,0 +1,344 @@
+<template>
+  <!-- 选择用户 -->
+  <div class="selectUser">
+    <el-row :gutter="20">
+      <!--部门数据-->
+      <el-col :span="4" :xs="24">
+        <div class="head-container">
+          <el-input
+            v-model="deptName"
+            placeholder="请输入部门名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+          />
+        </div>
+        <div class="head-container">
+          <el-tree
+            :data="deptOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            ref="tree"
+            node-key="id"
+            default-expand-all
+            highlight-current
+            @node-click="handleNodeClick"
+          />
+        </div>
+      </el-col>
+      <!--用户数据-->
+      <el-col :span="20" :xs="24">
+        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+          <el-form-item label="用户名称" prop="userName">
+            <el-input
+              v-model="queryParams.userName"
+              placeholder="请输入用户名称"
+              clearable
+              style="width: 240px"
+              @keyup.enter.native="handleQuery"
+            />
+          </el-form-item>
+          <el-form-item label="手机号码" prop="phonenumber">
+            <el-input
+              v-model="queryParams.phonenumber"
+              placeholder="请输入手机号码"
+              clearable
+              style="width: 240px"
+              @keyup.enter.native="handleQuery"
+            />
+          </el-form-item>
+          <el-form-item label="创建时间">
+            <el-date-picker
+              v-model="dateRange"
+              style="width: 240px"
+              value-format="yyyy-MM-dd"
+              type="daterange"
+              range-separator="-"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+            ></el-date-picker>
+          </el-form-item>
+          <div style="text-align: right">
+            <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+            <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+            <el-button type="primary" size="mini" :disabled="checkedItemList.length === 0" @click="submitForm">确 定</el-button>
+          </div>
+        </el-form>
+
+        <el-row class="mb8">
+          <el-tag style="margin-right: 10px" v-for="tag in checkedItemList" :key="tag.userId" closable @close="handleClose(tag.userId)">{{tag.userId}}</el-tag>
+        </el-row>
+
+        <el-table ref="table" v-loading="loading" :data="userList" @row-click="handleCheck">
+          <el-table-column width="50" align="center">
+            <template #header v-if="!['转办', '委派'].includes(type)">
+                <el-checkbox
+                    :indeterminate="checkAllInfo.isIndeterminate"
+                    v-model="checkAllInfo.isChecked"
+                    @change="handleCheckAll"
+                ></el-checkbox>
+            </template>
+            <template slot-scope="{ row }">
+              <el-checkbox v-model="row.isChecked" @change.capture="handleCheck(row)"></el-checkbox>
+            </template>
+          </el-table-column>
+          <el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns[0].visible" />
+          <el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns[1].visible" :show-overflow-tooltip="true" />
+          <el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" :show-overflow-tooltip="true" />
+          <el-table-column label="部门" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible" :show-overflow-tooltip="true" />
+          <el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" v-if="columns[4].visible" width="120" />
+          <el-table-column label="状态" align="center" key="status" v-if="columns[5].visible">
+            <template slot-scope="scope">
+              <el-tag :type="scope.row.status === '0' ? '' : 'warning'">{{ scope.row.status === '0' ? '正常' : '停用' }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[6].visible" width="160">
+            <template slot-scope="scope">
+              <span>{{ parseTime(scope.row.createTime) }}</span>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <pagination
+          v-show="total>0"
+          style="margin-bottom: 35px;"
+          :total="total"
+          :page.sync="queryParams.pageNum"
+          :limit.sync="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import {interactiveTypeSysUser} from "@/api/flow/execute";
+import {deptTreeSelect} from "@/api/system/user";
+
+export default {
+  name: "User",
+  dicts: ['sys_normal_disable', 'sys_user_sex'],
+  components: { Treeselect },
+  props: ["userVisible", "selectUser", "postParams", "type"],
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 用户表格数据
+      userList: null,
+      // 弹出层标题
+      title: "",
+      // 部门树选项
+      deptOptions: undefined,
+      // 是否显示弹出层
+      open: false,
+      // 部门名称
+      deptName: undefined,
+      // 默认密码
+      initPassword: undefined,
+      // 日期范围
+      dateRange: [],
+      // 岗位选项
+      postOptions: [],
+      // 角色选项
+      roleOptions: [],
+      // 表单参数
+      form: {},
+      defaultProps: {
+        children: "children",
+        label: "label"
+      },
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userName: undefined,
+        phonenumber: undefined,
+        status: "0",
+        deptId: undefined
+      },
+      // 列信息
+      columns: [
+        { key: 0, label: `用户编号`, visible: true },
+        { key: 1, label: `用户名称`, visible: true },
+        { key: 2, label: `用户昵称`, visible: true },
+        { key: 3, label: `部门`, visible: true },
+        { key: 4, label: `手机号码`, visible: true },
+        { key: 5, label: `状态`, visible: true },
+        { key: 6, label: `创建时间`, visible: true }
+      ],
+      checkedItemList: [], // 已选的itemList
+      checkAllInfo: {
+        isIndeterminate: false,
+        isChecked: false,
+      }
+    };
+  },
+  watch: {
+    // 根据名称筛选部门树
+    deptName(val) {
+      this.$refs.tree.filter(val);
+    },
+    selectUser: {
+      handler(val, oldVal) {
+        if (oldVal) {
+          this.$nextTick(() => {
+            this.checkedItemList = this.checkedItemList.filter(e => {
+              let index = val ? val.findIndex(v => v === e.userId) : -1;
+              this.userList.forEach(u => {
+                if (u.userId === e.userId) u.isChecked = index !== -1;
+              });
+              return index !== -1;
+            });
+          });
+        } else {
+          this.checkedItemList = val.map(e => { return { userId: e } });
+        }
+      },
+      deep: true,
+      immediate: true
+    }
+  },
+  created() {
+    this.getList();
+    this.getDeptTree();
+  },
+  methods: {
+    /** 查询用户列表 */
+    getList() {
+      this.loading = true;
+      let params = this.addDateRange(this.queryParams, this.dateRange);
+      if (this.type) {
+        let postParams = JSON.parse(JSON.stringify(this.postParams));
+        delete postParams.url;
+        params = { ...postParams, deptId: this.queryParams.deptId };
+      }
+      interactiveTypeSysUser(params).then(response => {
+          this.total = response.total;
+          this.loading = false;
+          response.rows.forEach(item => {
+            item.isChecked = this.checkedItemList.findIndex(e => e.userId === item.userId) !== -1;
+          })
+          this.userList = response.rows;
+          this.isCheckedAll();
+        }
+      );
+    },
+    /** 查询部门下拉树结构 */
+    getDeptTree() {
+      deptTreeSelect().then(response => {
+        this.deptOptions = response.data;
+      });
+    },
+    // 筛选节点
+    filterNode(value, data) {
+      if (!value) return true;
+      return data.label.indexOf(value) !== -1;
+    },
+    // 节点单击事件
+    handleNodeClick(data) {
+      this.queryParams.deptId = data.id;
+      this.handleQuery();
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.dateRange = [];
+      this.resetForm("queryForm");
+      this.queryParams.deptId = undefined;
+      this.$refs.tree.setCurrentKey(null);
+      this.handleQuery();
+    },
+    // 是否全选中
+    isCheckedAll() {
+      const len = this.userList.length;
+      let count = 0;
+      this.userList.map(item => {
+        if (item.isChecked) count += 1;
+      });
+      this.checkAllInfo.isChecked = len === count && len > 0;
+      this.checkAllInfo.isIndeterminate = count > 0 && count < len;
+    },
+    // 全选
+    handleCheckAll() {
+      const checkedItemList = this.checkedItemList;
+      this.checkAllInfo.isIndeterminate = false;
+      if (this.checkAllInfo.isChecked) {
+        this.userList = this.userList.map(item => {
+          item.isChecked = true;
+          if (this.checkedItemList.findIndex(e => e.userId === item.userId) === -1) {
+            checkedItemList.push({ userId: item.userId });
+          }
+          return item;
+        });
+      } else {
+        this.userList = this.userList.map(item => {
+          item.isChecked = false;
+          let index = checkedItemList.findIndex(e => e.userId === item.userId);
+          if (index !== -1) checkedItemList.splice(index, 1);
+          return item;
+        });
+      }
+      this.checkedItemList = checkedItemList
+    },
+    // 单选
+    handleCheck(row) {
+      // 转办|委派仅支持单选
+      if (['转办', '委派'].includes(this.type)) {
+        this.userList.forEach(e => {
+          if (e.userId === row.userId) e.isChecked = true;
+          else e.isChecked = false;
+        });
+        this.checkedItemList = [{ userId: row.userId }];
+      } else {
+        this.userList.forEach(e => {
+          if (e.userId === row.userId) e.isChecked = !e.isChecked;
+        });
+
+        const checkedItemList = this.checkedItemList;
+        if (row.isChecked) {
+          checkedItemList.push({ userId: row.userId });
+        } else {
+          const index = checkedItemList.findIndex(n => n.userId === row.userId);
+          if (index !== -1) checkedItemList.splice(index, 1);
+        }
+        this.checkedItemList = checkedItemList;
+        this.isCheckedAll();
+      }
+    },
+    // 删除标签
+    handleClose(userId) {
+      this.userList.forEach(e => {
+        if (e.userId === userId) e.isChecked = !e.isChecked;
+      });
+      const checkedItemList = this.checkedItemList
+      const index = checkedItemList.findIndex(n => n.userId === userId);
+      if (index !== -1) checkedItemList.splice(index, 1);
+      this.checkedItemList = checkedItemList;
+      this.isCheckedAll();
+    },
+    // 取消按钮
+    cancel() {
+      this.$emit("update:userVisible", false);
+    },
+    // 提交按钮
+    submitForm() {
+      this.$emit("handleUserSelect", this.checkedItemList);
+      this.cancel();
+    }
+  }
+};
+</script>

+ 258 - 0
src/views/flow/definition/dialog.vue

@@ -0,0 +1,258 @@
+
+<template>
+  <div class="app-container">
+    <!-- 添加或修改流程定义对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="800px" v-if="open" append-to-body>
+      <el-tabs type="border-card" class="Tabs" v-model="tabsValue">
+        <el-tab-pane label="基础设置" name="1"></el-tab-pane>
+        <el-tab-pane label="监听器" name="2"></el-tab-pane>
+      </el-tabs>
+      <el-form ref="form" :model="form" class="dialogForm" :rules="rules" label-width="150px" :disabled="disabled">
+        <div v-show="tabsValue === '1'">
+          <el-form-item label="流程编码" prop="flowCode">
+            <el-input v-model="form.flowCode" placeholder="请输入流程编码" maxlength="40" show-word-limit/>
+          </el-form-item>
+          <el-form-item label="流程名称" prop="flowName">
+            <el-input v-model="form.flowName" placeholder="请输入流程名称" maxlength="100" show-word-limit/>
+          </el-form-item>
+          <el-form-item label="流程类别" prop="category">
+            <el-input v-model="form.category" placeholder="请输入流程类别" maxlength="20" show-word-limit/>
+          </el-form-item>
+          <el-form-item label="是否发布" prop="isPublish" v-if="disabled">
+            <el-select v-model="form.isPublish" placeholder="请选择是否开启流程">
+              <el-option
+                v-for="dict in dict.type.is_publish"
+                :key="dict.value"
+                :label="dict.label"
+                :value="parseInt(dict.value)"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="审批表单是否自定义" prop="formCustom">
+            <el-select v-model="form.formCustom" clearable>
+              <el-option label="表单路径" value="N"></el-option>
+              <!--TODO form 开发中-->
+<!--              <el-option label="动态表单" value="Y"></el-option>-->
+            </el-select>
+          </el-form-item>
+          <el-form-item label="审批表单路径" prop="formPath" v-if="form.formCustom === 'N'">
+            <el-input v-model="form.formPath" placeholder="请输入审批表单路径" maxlength="100" show-word-limit/>
+          </el-form-item>
+          <el-form-item label="审批流程表单" v-else-if="form.formCustom === 'Y'">
+            <el-select v-model="form.formPath">
+              <el-option v-for="item in definitionList" :key="item.id" :label="`${item.formName} - v${item.version}`" :value="item.id"></el-option>
+            </el-select>
+          </el-form-item>
+        </div>
+        <div v-show="tabsValue === '2'">
+          <el-form-item prop="listenerRows" class="listenerItem">
+            <el-table :data="form.listenerRows" style="width: 100%">
+            <el-table-column prop="listenerType" width="150" label="类型">
+                <template slot-scope="scope">
+                  <el-form-item :prop="'listenerRows.' + scope.$index + '.listenerType'" :rules="rules.listenerType">
+                    <el-select v-model="scope.row.listenerType" placeholder="请选择类型">
+                      <el-option label="开始" value="start"></el-option>
+                      <el-option label="分派" value="assignment"></el-option>
+                      <el-option label="完成" value="finish"></el-option>
+                      <el-option label="创建" value="create"></el-option>
+                    </el-select>
+                  </el-form-item>
+                </template>
+              </el-table-column>
+              <el-table-column prop="listenerPath" label="路径">
+                <template slot-scope="scope">
+                  <el-form-item :prop="'listenerRows.' + scope.$index + '.listenerPath'" :rules="rules.listenerPath">
+                    <el-input v-model="scope.row.listenerPath" placeholder="请输入路径"></el-input>
+                  </el-form-item>
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" width="65" v-if="!disabled">
+                <template slot-scope="scope">
+                  <el-button size="mini" type="danger" icon="el-icon-delete" @click="handleDeleteRow(scope.$index)"/>
+                </template>
+              </el-table-column>
+            </el-table>
+            <el-button v-if="!disabled" style="margin-top: 10px;" type="primary" @click="handleAddRow">增加行</el-button>
+          </el-form-item>
+        </div>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" v-if="!disabled" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel" v-if="!disabled">取 消</el-button>
+        <el-button @click="cancel" v-if="disabled">关 闭</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getDefinition, addDefinition, updateDefinition, publishedList } from "@/api/flow/definition";
+
+export default {
+  name: "Dialog",
+  dicts: ['sys_yes_no', 'is_publish'],
+  data() {
+    return {
+      // 是否禁用表单
+      disabled: false,
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      tabsValue: "1",
+      // 表单参数
+      form: {
+        listenerRows: [] // 新增的表格数据
+      },
+      definitionList: [],
+      // 表单校验
+      rules: {
+        flowCode: [
+          { required: true, message: "流程编码不能为空", trigger: "blur" }
+        ],
+        flowName: [
+          { required: true, message: "流程名称不能为空", trigger: "blur" }
+        ],
+        isPublish: [
+          { required: true, message: "是否开启流程不能为空", trigger: "change" }
+        ],
+        formCustom: [
+          { required: true, message: "请选择审批表单是否自定义", trigger: "change" }
+        ],
+        listenerType: [{ required: true, message: '监听器不能为空', trigger: ['change', 'blur'] }],
+        listenerPath: [{ required: true, message: '监听器不能为空', trigger: ['change', 'blur'] }]
+      }
+    };
+  },
+  methods: {
+    /** 打开流程定义弹框 */
+    async show(id, disabled) {
+      this.reset();
+      this.disabled = disabled
+      // TODO form 开发中
+      // this.getDefinition();
+
+      if (id) {
+        await getDefinition(id).then(response => {
+          this.form = response.data;
+          if (this.form.listenerType) {
+            const listenerTypes = this.form.listenerType.split(",");
+            const listenerPaths = this.form.listenerPath.split("@@");
+            this.form.listenerRows = listenerTypes.map((type, index) => ({
+              listenerType: type,
+              listenerPath: listenerPaths[index]
+            }));
+          } else {
+            this.form.listenerRows = [];
+          }
+        });
+      }
+      this.open = true
+      if (this.disabled) {
+        this.title = "详情"
+      } else if (id) {
+        this.title = "修改"
+      } else {
+        this.title = "新增"
+      }
+    },
+    /** 查询表单定义列表 */
+    getDefinition() {
+      publishedList().then(response => {
+        this.definitionList = response.data;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        flowCode: null,
+        flowName: null,
+        version: null,
+        isPublish: null,
+        formCustom: null,
+        formPath: null,
+        createTime: null,
+        updateTime: null,
+        delFlag: null,
+        listenerRows: [] // 初始化表格数据
+      };
+      this.resetForm("form");
+      this.tabsValue = "1";
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate((valid, err) => {
+        if (valid) {
+          this.form.listenerType = this.form.listenerRows.map(row => row.listenerType).join(",")
+          this.form.listenerPath = this.form.listenerRows.map(row => row.listenerPath).join("@@")
+          if (this.form.id != null) {
+            updateDefinition(this.form).then(response => {
+              this.$modal.msgSuccess("修改成功");
+              this.open = false;
+              this.$emit('refresh');
+            });
+          } else {
+            addDefinition(this.form).then(response => {
+              this.$modal.msgSuccess("新增成功");
+              this.open = false;
+              this.$emit('refresh');
+            });
+          }
+        } else {
+          let errItems = Object.keys(err);
+          if (errItems.length > 0) {
+            this.tabsValue = "1";
+            if (errItems.every(e => e.includes("listenerRows"))) this.tabsValue = "2";
+          }
+        }
+      });
+    },
+    // 增加行
+    handleAddRow() {
+      this.form.listenerRows.push({ listenerType: '', listenerPath: '' });
+      this.$refs.form.clearValidate("listenerRows");
+    },
+    // 删除行
+    handleDeleteRow(index) {
+      this.form.listenerRows.splice(index, 1);
+    }
+  }
+};
+</script>
+
+<style scoped lang="scss">
+::v-deep.Tabs {
+  margin-top: -20px;
+  box-shadow: none;
+  border-bottom: 0;
+  .el-tabs__content {
+    display: none;
+  }
+  .el-tabs__item.is-active {
+    margin-left: 0;
+    border-top: 1px solid var(--el-border-color);
+    margin-top: 0;
+  }
+}
+.dialogForm {
+  border: 1px solid #e4e7ed;
+  border-top: 0;
+  padding: 15px;
+}
+::v-deep.listenerItem {
+  .el-form-item__label {
+    float: none;
+    display: inline-block;
+    text-align: left;
+  }
+  .el-form-item__content {
+    margin-left: 0 !important;
+  }
+}
+</style>

+ 424 - 0
src/views/flow/definition/index.vue

@@ -0,0 +1,424 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="mini" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="流程编码" prop="flowCode">
+        <el-input
+          v-model="queryParams.flowCode"
+          placeholder="请输入流程编码"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="流程名称" prop="flowName">
+        <el-input
+          v-model="queryParams.flowName"
+          placeholder="请输入流程名称"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="流程类别" prop="category">
+        <el-input
+          v-model="queryParams.category"
+          placeholder="请输入流程类别"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="流程版本" prop="version">
+        <el-input
+          v-model="queryParams.version"
+          placeholder="请输入流程版本"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['flow:definition:add']"
+        >新增</el-button>
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleImport"
+          v-hasPermi="['flow:definition:importDefinition']"
+        >导入流程定义</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="definitionList" @selection-change="handleSelectionChange"  @sort-change="handleSortChange">
+      <el-table-column type="selection" width="55" align="center" fixed />
+      <el-table-column label="序号" width="50" align="center" key="id" prop="id">
+        <template slot-scope="scope">
+          {{ scope.$index + 1 }}
+        </template>
+      </el-table-column>
+      <el-table-column label="流程编码" align="center" prop="flowCode" width="150" sortable="custom"  :show-overflow-tooltip="true"/>
+      <el-table-column label="流程名称" align="center" prop="flowName" sortable="custom" :show-overflow-tooltip="true"/>
+      <el-table-column label="流程版本" align="center" prop="version" width="100" sortable="custom" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <el-tag>{{scope.row.version}}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="流程类别" align="center" prop="category" sortable="custom" :show-overflow-tooltip="true"/>
+      <el-table-column label="是否发布" align="center" prop="isPublish" width="140" sortable="custom" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <dict-tag :options="dict.type.is_publish" :value="scope.row.isPublish"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="激活状态" align="center" prop="activityStatus" width="140" sortable="custom" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <dict-tag :options="dict.type.activity_status" :value="scope.row.activityStatus"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="160" sortable="custom" :sort-orders="['descending', 'ascending']" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" width="200" fixed="right" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            @click="handleDesign(scope.row.id, scope.row.isPublish)"
+            v-hasPermi="['flow:definition:queryDesign']"
+          >流程设计</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="toFlowImage(scope.row.id)"
+          >流程图</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.isPublish === 0"
+            @click="handlePublish(scope.row.id)"
+            v-hasPermi="['flow:definition:publish']"
+          >发布</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.isPublish === 1"
+            @click="handleUpPublish(scope.row.id)"
+            v-hasPermi="['flow:definition:upPublish']"
+          >取消发布</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.activityStatus === 0"
+            @click="toActive(scope.row.id)"
+          >激活</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.activityStatus === 1"
+            @click="toUnActive(scope.row.id)"
+          >挂起</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="handleCopyDef(scope.row.id)"
+            v-hasPermi="['flow:definition:upPublish']"
+          >复制流程</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.isPublish === 0"
+            @click="handleUpdate(scope.row.id)"
+            v-hasPermi="['flow:definition:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="handleExport(scope.row)"
+            v-hasPermi="['flow:definition:exportDefinition']"
+          >导出流程</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.isPublish === 0"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['flow:definition:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+    <Dialog ref="dialog" @refresh="getList"></Dialog>
+
+    <!-- 用户导入对话框 -->
+    <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
+      <el-upload
+        ref="upload"
+        multiple
+        :limit="20"
+        accept=".json"
+        :headers="upload.headers"
+        :action="upload.url"
+        :disabled="upload.isUploading"
+        :on-progress="handleFileUploadProgress"
+        :on-success="handleFileSuccess"
+        :auto-upload="false"
+        drag
+      >
+        <i class="el-icon-upload"></i>
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+        <div class="el-upload__tip text-center" slot="tip">
+          <span>仅允许导入json格式文件。</span>
+        </div>
+      </el-upload>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitFileForm">确 定</el-button>
+        <el-button @click="upload.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog title="流程图" :visible.sync="flowChart" width="80%">
+      <img :src="imgUrl" width="100%" style="margin:0 auto"/>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  listDefinition,
+  delDefinition,
+  publish,
+  unPublish,
+  copyDef,
+  chartDef, active, unActive
+} from '@/api/flow/definition'
+import Dialog from "@/views/flow/definition/dialog";
+import { getToken } from '@/utils/auth'
+
+export default {
+  name: "Definition",
+  dicts: ['is_publish', 'activity_status'],
+  components: {
+    Dialog
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      imgUrl: "",
+      flowChart: false,
+      // 唯一标识符
+      uniqueId: "",
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 流程定义表格数据
+      definitionList: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        flowCode: null,
+        flowName: null,
+        version: null,
+      },
+      // 用户导入参数
+      upload: {
+        // 是否显示弹出层(用户导入)
+        open: false,
+        // 弹出层标题(用户导入)
+        title: "",
+        // 是否禁用上传
+        isUploading: false,
+        // 设置上传的请求头部
+        headers: { Authorization: "Bearer " + getToken() },
+        // 上传的地址
+        url: process.env.VUE_APP_BASE_API + "/flow/definition/importDefinition"
+      },
+    };
+  },
+  created() {
+    this.getList();
+  },
+  activated() {
+    const time = this.$route.query.t;
+    if (time != null && time != this.uniqueId) {
+      this.uniqueId = time;
+      this.queryParams.pageNum = Number(this.$route.query.pageNum);
+      this.getList();
+    }
+  },
+  methods: {
+    active,
+    /** 查询流程定义列表 */
+    getList() {
+      this.loading = true;
+      listDefinition(this.queryParams).then(response => {
+        this.definitionList = response.rows;
+        debugger
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.$refs.dialog.show();
+    },
+
+    /** 流程设计按钮操作 */
+    handleDesign(id, isPublish) {
+      const params = { disabled: isPublish === 1, pageNum: this.queryParams.pageNum };
+      debugger
+      this.$tab.openPage("流程设计", '/flow/flow-design/index/' + id, params);
+    },
+
+    /** 发布按钮操作 */
+    handlePublish(id) {
+      this.$modal.confirm('是否确认发布流程定义编号为"' + id + '"的数据项?').then(function() {
+        return publish(id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("发布成功");
+      }).catch(() => {});
+    },
+
+    /** 取消发布按钮操作 */
+    handleUpPublish(id) {
+      this.$modal.confirm('是否确认取消发布流程定义编号为"' + id + '"的数据项?').then(function() {
+        return unPublish(id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("取消成功");
+      }).catch(() => {});
+    },
+
+    /** 修改按钮操作 */
+    handleUpdate(id) {
+      this.$refs.dialog.show(id);
+    },
+
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$modal.confirm('是否确认删除流程定义编号为"' + ids + '"的数据项?').then(function() {
+        return delDefinition(ids);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+
+    /** 复制流程按钮操作 */
+    handleCopyDef(id) {
+      this.$modal.confirm('是否确认复制流程定义编号为"' + id + '"的数据项?').then(function() {
+        return copyDef(id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("复制成功");
+      }).catch(() => {});
+    },
+
+    /** 导入按钮操作 */
+    handleImport() {
+      this.upload.title = "用户导入";
+      this.upload.open = true;
+    },
+
+    handleExport(row) {
+      this.download('/flow/definition/exportDefinition/' + row.id, {
+        ...this.queryParams
+      }, row.flowCode + '_' + row.version + '.json')
+    },
+
+    // 文件上传中处理
+    handleFileUploadProgress(event, file, fileList) {
+      this.upload.isUploading = true;
+    },
+    // 文件上传成功处理
+    handleFileSuccess(response, file, fileList) {
+      this.upload.open = false;
+      this.upload.isUploading = false;
+      this.$refs.upload.clearFiles();
+      this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true });
+      this.getList();
+    },
+    // 提交上传文件
+    submitFileForm() {
+      this.$refs.upload.submit();
+    },
+    /** 排序触发事件 */
+    handleSortChange(column, prop, order) {
+      this.queryParams.orderByColumn = column.prop;
+      this.queryParams.isAsc = column.order;
+      this.getList();
+    },
+
+    toFlowImage(id) {
+      chartDef(id).then(response => {
+        this.flowChart = true
+        this.imgUrl = "data:image/gif;base64," + response.data;
+      });
+    },
+
+    toActive(id) {
+      this.$modal.confirm('是否确认激活流程定义编号为"' + id + '"的数据项?').then(function() {
+        return active(id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("激活成功");
+      }).catch(() => {});
+    },
+
+    toUnActive(id) {
+      this.$modal.confirm('是否确认挂起流程定义编号为"' + id + '"的数据项?').then(function() {
+        return unActive(id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("挂起成功");
+      }).catch(() => {});
+    },
+  }
+};
+</script>

+ 39 - 0
src/views/flow/definition/warm-flow.vue

@@ -0,0 +1,39 @@
+<template>
+  <div :style="'height:' + height">
+    <iframe :src="url" style="width: 100%; height: 100%"/>
+  </div>
+</template>
+<script>
+import {getToken} from "@/utils/auth";
+
+export default {
+  name: "WarmFlow",
+  data() {
+    return {
+      height: document.documentElement.clientHeight - 94.5 + "px;",
+      url: ""
+    };
+  },
+  mounted() {
+    this.url = `${process.env.VUE_APP_FLOW_API}/warm-flow-ui/index.html?id=${this.$route.params.id}&disabled=${this.$route.query.disabled}&Authorization=Bearer ` + getToken();
+    window.addEventListener("message", this.handleMessage);
+  },
+  beforeDestroy() {
+    window.removeEventListener("message", this.handleMessage);
+  },
+  methods: {
+    handleMessage(event) {
+      console.log(event.data.method, event);
+      switch (event.data.method) {
+        case "close":
+          this.close();
+          break;
+      }
+    },
+    close() {
+      const obj = { path: "/flow/definition" };
+      this.$tab.closeOpenPage(obj);
+    }
+  }
+};
+</script>

+ 169 - 0
src/views/flow/notice/index.vue

@@ -0,0 +1,169 @@
+<template>
+    <div class="app-container">
+      <el-form :model="queryParams" ref="queryForm" size="mini" :inline="true" v-show="showSearch" label-width="100px">
+        <el-form-item label="任务名称" prop="nodeName">
+          <el-input
+            v-model="queryParams.nodeName"
+            placeholder="请输入任务名称"
+            clearable
+            @keyup.enter.native="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="流程状态" prop="flowStatus">
+          <el-select v-model="queryParams.nodeType" placeholder="请选择流程状态" clearable>
+            <el-option
+              v-for="dict in dict.type.flow_status"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="抄送人" prop="flowName">
+          <el-input
+            v-model="queryParams.flowName"
+            placeholder="请输入任抄送人"
+            @keyup.enter.native="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+      <el-table v-loading="loading" :data="instanceList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" fixed />
+        <el-table-column label="序号" width="55" align="center" key="id" prop="id">
+          <template slot-scope="scope">
+            {{ scope.$index + 1 }}
+          </template>
+        </el-table-column>
+        <el-table-column label="流程实例id" width="100" align="center" prop="id" :show-overflow-tooltip="true"/>
+        <el-table-column label="流程名称" align="center" prop="flowName" :show-overflow-tooltip="true"/>
+        <el-table-column label="任务名称" align="center" prop="nodeName" :show-overflow-tooltip="true"/>
+        <el-table-column label="抄送人" align="center" prop="approver" :show-overflow-tooltip="true"/>
+        <el-table-column label="流程状态" align="center" prop="flowStatus" :show-overflow-tooltip="true">
+          <template slot-scope="scope">
+            <dict-tag :options="dict.type.flow_status" :value="scope.row.flowStatus"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="创建时间" align="center" prop="createTime" width="160" :show-overflow-tooltip="true">
+          <template slot-scope="scope">
+            <span>{{ parseTime(scope.row.createTime) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="240" fixed="right" class-name="small-padding fixed-width">
+          <template slot-scope="scope">
+            <el-button
+              size="mini"
+              type="text"
+              @click="toFlowImage(scope.row.id)"
+            >流程图</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="total>0"
+        :total="total"
+        :page.sync="queryParams.pageNum"
+        :limit.sync="queryParams.pageSize"
+        @pagination="getList"
+      />
+
+      <component v-bind:is="approve" v-model="businessId" :taskId="taskId" @refresh="getList"></component>
+      <el-dialog title="流程图" :visible.sync="flowChart" width="80%">
+        <img :src="imgUrl" width="100%" style="margin:0 auto"/>
+      </el-dialog>
+
+    </div>
+  </template>
+
+  <script>
+  import * as api from "@/api/flow/execute";
+  import { flowImage } from "@/api/flow/definition";
+
+  export default {
+    name: "Todo",
+    dicts: ['flow_status'],
+    data() {
+      return {
+        // 遮罩层
+        loading: true,
+        imgUrl: "",
+        flowChart: false,
+        // 选中数组
+        ids: [],
+        // 非单个禁用
+        single: true,
+        // 非多个禁用
+        multiple: true,
+        // 显示搜索条件
+        showSearch: true,
+        // 总条数
+        total: 0,
+        // 流程实例表格数据
+        instanceList: [],
+        // 业务审批页面
+        approve: null,
+        taskId: "",
+        businessId: "",
+        // 查询参数
+        queryParams: {
+          pageNum: 1,
+          pageSize: 10,
+          nodeName: null,
+          flowStatus: null,
+          approver: null,
+          flowName: null,
+          createTime: null,
+        },
+      };
+    },
+    created() {
+      this.getList();
+    },
+    methods: {
+      /** 查询流程实例列表 */
+      getList() {
+        this.loading = true;
+        api.copyPage(this.queryParams).then(response => {
+          this.instanceList = response.rows;
+          this.total = response.total;
+          this.loading = false;
+        });
+      },
+      /** 搜索按钮操作 */
+      handleQuery() {
+        this.queryParams.pageNum = 1;
+        this.getList();
+      },
+      /** 重置按钮操作 */
+      resetQuery() {
+        this.resetForm("queryForm");
+        this.handleQuery();
+      },
+      // 多选框选中数据
+      handleSelectionChange(selection) {
+        this.ids = selection.map(item => item.id)
+        this.single = selection.length!==1
+        this.multiple = !selection.length
+      },
+      /** 办理按钮操作 */
+      handle(row) {
+        this.taskId = row.id
+        this.businessId = row.businessId
+        if (row.formCustom == 'N' && row.formPath) {
+          // 实际情况是,不同条件对应不同的页面,所以用动态组件
+          this.approve = (resolve) => require([`@/views/${row.formPath}`], resolve)
+        }
+      },
+      toFlowImage(instanceId) {
+        flowImage(instanceId).then(response => {
+          this.flowChart = true
+          this.imgUrl = "data:image/gif;base64," + response.data;
+        });
+      },
+    }
+  };
+  </script>

+ 125 - 0
src/views/flow/task/done/doneList.vue

@@ -0,0 +1,125 @@
+<template>
+  <div class="app-container">
+    <el-table :data="taskList">
+      <el-table-column label="序号" width="50" align="center" key="id" prop="id">
+        <template slot-scope="scope">
+          {{ scope.$index + 1 }}
+        </template>
+      </el-table-column>
+      <el-table-column label="开始节点名称" align="center" prop="nodeName" :show-overflow-tooltip="true"/>
+      <el-table-column label="结束节点名称" align="center" prop="targetNodeName" :show-overflow-tooltip="true"/>
+      <el-table-column label="审批人" align="center" prop="approver" :show-overflow-tooltip="true"/>
+      <el-table-column label="协作类型" align="center" prop="cooperateType" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <dict-tag :options="dict.type.cooperate_type" :value="scope.row.cooperateType"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="协作人" align="center" prop="collaborator" :show-overflow-tooltip="true"/>
+      <!--       <el-table-column label="流转类型" align="center" prop="skipType" :show-overflow-tooltip="true">
+              <template slot-scope="scope">
+                <el-tag v-if="scope.row.skipType==='PASS'">通过</el-tag>
+                <el-tag v-if="scope.row.skipType==='REJECT'" type="danger">退回</el-tag>
+                <el-tag v-if="scope.row.skipType==='NONE'" type="info">无动作</el-tag>
+              </template>
+            </el-table-column> -->
+      <el-table-column label="流程状态" align="center" prop="flowStatus" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <dict-tag :options="dict.type.flow_status" :value="scope.row.flowStatus"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="审批意见" align="center" prop="message" :show-overflow-tooltip="true"/>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="160" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="业务详情" align="center" width="100">
+        <template slot-scope="scope">
+          <el-button size="mini" v-if="scope.row.ext" @click="handle(scope.row)">查看</el-button>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" width="110" fixed="right" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.nodeType === 1 && scope.row.formCustom === 'Y'"
+            @click="handleForm(scope.row.id)"
+          >查看</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <component v-if="approve" v-bind:is="approve" :businessId="businessId" :taskId="taskId" :testLeaveVo="testLeaveVo" @refresh="fetchTaskList"></component>
+    <formDialog v-if="showFormDialog" :disabled="true" :formName="'查看'" :visible.sync="showFormDialog" :showApproval="false" :taskId="taskId" :type="'1'"></formDialog>
+  </div>
+</template>
+
+<script>
+import {doneList} from '@/api/flow/execute';
+import formDialog from "../../../form/definition/formDialog";
+
+export default {
+  name: "DoneList",
+  dicts: ['flow_status', 'cooperate_type'],
+  components: {
+    formDialog
+  },
+  data() {
+    return {
+      instanceId: "",
+      formPath: "",
+      // 历史任务记录表格数据
+      taskList: [],
+      taskId: "",
+      businessId: "",
+      approve: null,
+      testLeaveVo: null,
+      showFormDialog: false
+    };
+  },
+  activated() {
+    this.instanceId = this.$route.params.instanceId;
+    this.formPath = this.$route.query.formPath;
+    this.testLeaveVo = this.$route.query.testLeaveVo; // 接收传递过来的testLeaveVo
+    this.fetchTaskList();
+  },
+  methods: {
+    fetchTaskList() {
+      doneList(this.instanceId).then(response => {
+        this.taskList = response.data;
+      });
+    },
+    handle(row) {
+      // 设置任务ID和业务ID
+      this.taskId = row.id;
+      this.businessId = row.businessId;
+
+      // 获取或设置testLeaveVo数据
+      // 解析ext字段的JSON数据
+      if (row.ext) {
+        try {
+          const parsedExt = JSON.parse(row.ext);
+          this.testLeaveVo = {...parsedExt};
+        } catch (e) {
+          console.error('Invalid ext JSON format:', row.ext);
+        }
+      }
+      // 获取或设置formPath路径
+      const formPath = row.formPath || this.formPath;
+
+      // 如果存在formPath,则动态加载组件
+      if (formPath) {
+        // 通过require动态加载组件
+        this.approve = (resolve) => require([`@/views/${formPath}`], resolve);
+      } else {
+        this.approve = null;
+      }
+    },
+    // 查看表单详情
+    handleForm(id) {
+      this.taskId = id;
+      this.showFormDialog = true;
+    }
+  }
+};
+</script>

+ 228 - 0
src/views/flow/task/done/index.vue

@@ -0,0 +1,228 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索表单 -->
+    <el-form :model="queryParams" ref="queryForm" size="mini" :inline="true" v-show="showSearch" label-width="100px">
+      <!-- 任务名称输入框 -->
+      <el-form-item label="任务名称" prop="nodeName">
+        <el-input
+          v-model="queryParams.nodeName"
+          placeholder="请输入任务名称"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <!-- 流程状态选择框 -->
+      <el-form-item label="流程状态" prop="flowStatus">
+        <el-select v-model="queryParams.flowStatus" placeholder="请选择流程状态" clearable>
+          <el-option
+            v-for="dict in dict.type.flow_status"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <!-- 审批时间选择器 -->
+      <el-form-item label="审批时间" prop="createTime">
+        <el-date-picker
+          clearable size="small"
+          v-model="queryParams.createTime"
+          type="datetime"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          placeholder="选择创建时间">
+        </el-date-picker>
+      </el-form-item>
+      <!-- 搜索和重置按钮 -->
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+    <!-- 流程实例表格 -->
+    <el-table :data="instanceList">
+      <!-- 表格列定义 -->
+      <el-table-column type="selection" width="55" align="center" fixed />
+      <el-table-column label="序号" width="55" align="center" key="id" prop="id">
+        <template slot-scope="scope">
+          {{ scope.$index + 1 }}
+        </template>
+      </el-table-column>
+      <el-table-column label="流程实例id" align="center" prop="instanceId" :show-overflow-tooltip="true"/>
+      <el-table-column label="流程名称" align="center" prop="flowName" :show-overflow-tooltip="true"/>
+      <el-table-column label="任务名称" align="center" prop="targetNodeName" :show-overflow-tooltip="true"/>
+      <el-table-column label="审批人" align="center" prop="approver" :show-overflow-tooltip="true"/>
+      <el-table-column label="协作类型" align="center" prop="cooperateType" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <dict-tag :options="dict.type.cooperate_type" :value="scope.row.cooperateType"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="协作人" align="center" prop="collaborator" :show-overflow-tooltip="true"/>
+      <el-table-column label="流程状态" align="center" prop="flowStatus" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <dict-tag :options="dict.type.flow_status" :value="scope.row.flowStatus"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="审批时间" align="center" prop="createTime" width="160" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" width="110" fixed="right" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            @click="showDoneList(scope.row.instanceId, scope.row.formPath, scope.row.testLeaveVo)"
+            v-hasPermi="['flow:execute:doneList']"
+          >审批记录</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="toFlowImage(scope.row.instanceId)"
+          >流程图</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.ext"
+            @click="handle(scope.row)"
+          >查看</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="handleComponent"
+          >多组件加载</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+    <el-dialog
+      title="流程图"
+      :visible.sync="flowChart"
+      width="80%">
+      <img
+        :src="imgUrl"
+        width="100%"
+        style="margin:0 auto"/>
+    </el-dialog>
+    <component v-if="approve" v-bind:is="approve" :businessId="businessId" :taskId="taskId" :testLeaveVo="testLeaveVo" @refresh="getList"></component>
+    <!-- 动态加载的组件 -->
+    <el-dialog title="审批详情" :visible.sync="approveDialogVisible" width="80%">
+      <component :is="dynamicComponent" v-if="dynamicComponent" :testLeaveVo="testLeaveVo" :taskId="taskId" :disabled="false"/>
+    </el-dialog>
+    <!-- 多组件加载 -->
+    <el-dialog title="多组件加载" :visible.sync="componentVisible" width="80%">
+      <component v-for="(item, index) in componentList" :key="index" v-bind:is="item.approve"></component>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { donePage } from "@/api/flow/execute";
+import { flowImage } from "@/api/flow/definition";
+
+export default {
+  name: "Done",
+  dicts: ['flow_status', 'cooperate_type'],
+  data() {
+    return {
+      // 选中数组
+      ids: [],
+      // 显示搜索条件
+      showSearch: true,
+      flowChart: false,
+      imgUrl: "",
+      // 总条数
+      total: 0,
+      // 流程实例表格数据
+      instanceList: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        nodeName: null,
+        flowStatus: null,
+        createBy: null,
+        createTime: null,
+      },
+      // 动态加载的组件相关
+      approve: null,
+      dynamicComponent: null,
+      approveDialogVisible: false,
+      testLeaveVo: null,
+      taskId: null,
+      componentVisible: false,
+      componentList: []
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询流程实例列表 */
+    getList() {
+      donePage(this.queryParams).then(response => {
+        this.instanceList = response.rows;
+        this.total = response.total;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 办理按钮操作 */
+    showDoneList(instanceId, formPath, testLeaveVo) {
+      const params = { disabled: false, pageNum: this.queryParams.pageNum, formPath, testLeaveVo };
+      this.$router.push({ path: `/done/doneList/index/${instanceId}`, query: params });
+    },
+    toFlowImage(instanceId) {
+      flowImage(instanceId).then(response => {
+        this.flowChart = true;
+        this.imgUrl = "data:image/gif;base64," + response.data;
+      });
+    },
+    handle(row) {
+      // 设置任务ID和业务ID
+      this.taskId = row.id;
+      this.businessId = row.businessId;
+
+      // 解析ext字段的JSON数据
+      if (row.ext) {
+        try {
+          const parsedExt = JSON.parse(row.ext);
+          this.testLeaveVo = { ...parsedExt };
+        } catch (e) {
+          console.error('Invalid ext JSON format:', row.ext);
+        }
+      }
+      // 获取或设置formPath路径
+      const formPath = row.formPath || this.formPath;
+
+      // 如果存在formPath,则动态加载组件
+      if (formPath) {
+        // 通过require动态加载组件
+        this.approve = (resolve) => require([`@/views/${formPath}`], resolve);
+      } else {
+        this.approve = null;
+      }
+    },
+    handleComponent() {
+      this.componentList.push({ approve: (resolve) => require([`@/views/system/role`], resolve) });
+      this.componentList.push({ approve: (resolve) => require([`@/views/system/user`], resolve) });
+      this.componentVisible = true;
+    }
+  }
+};
+</script>

+ 312 - 0
src/views/flow/task/todo/index.vue

@@ -0,0 +1,312 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="mini" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="任务名称" prop="nodeName">
+        <el-input
+          v-model="queryParams.nodeName"
+          placeholder="请输入任务名称"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="流程状态" prop="flowStatus">
+        <el-select v-model="queryParams.flowStatus" placeholder="请选择流程状态" clearable>
+          <el-option
+            v-for="dict in dict.type.flow_status"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          clearable size="small"
+          v-model="queryParams.createTime"
+          type="datetime"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          placeholder="选择创建时间">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+    <el-table v-loading="loading" :data="instanceList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" fixed />
+      <el-table-column label="序号" width="55" align="center" key="id" prop="id">
+        <template slot-scope="scope">
+          {{ scope.$index + 1 }}
+        </template>
+      </el-table-column>
+      <el-table-column label="id" width="100" align="center" prop="id" :show-overflow-tooltip="true"/>
+      <el-table-column label="流程名称" align="center" prop="flowName" :show-overflow-tooltip="true"/>
+      <el-table-column label="任务名称" width="200" align="center" prop="nodeName" :show-overflow-tooltip="true"/>
+      <el-table-column label="审批人" align="center" prop="approver" :show-overflow-tooltip="true"/>
+      <el-table-column label="转办人" align="center" prop="transferredBy" :show-overflow-tooltip="true"/>
+      <el-table-column label="委派人" align="center" prop="delegate" :show-overflow-tooltip="true"/>
+      <el-table-column label="流程状态" align="center" prop="flowStatus" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <dict-tag :options="dict.type.flow_status" :value="scope.row.flowStatus"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="激活状态" align="center" prop="activityStatus" width="140" sortable="custom" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <dict-tag :options="dict.type.activity_status" :value="scope.row.activityStatus"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="160" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" width="240" fixed="right" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            @click="handle(scope.row, false)"
+            v-hasPermi="['flow:execute:handle']"
+          >办理</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="handle(scope.row, true)"
+            v-hasPermi="['flow:execute:handle']"
+          >任意跳转</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="transferShow(scope.row, '转办')"
+            v-hasPermi="['flow:execute:handle']"
+          >转办</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="transferShow(scope.row, '加签')"
+            v-hasPermi="['flow:execute:handle']"
+          >加签</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="transferShow(scope.row, '委派')"
+            v-hasPermi="['flow:execute:handle']"
+          >委派</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="transferShow(scope.row, '减签')"
+            v-hasPermi="['flow:execute:handle']"
+          >减签</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.activityStatus === 0"
+            @click="toActive(scope.row.instanceId)"
+          >激活</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.activityStatus === 1"
+            @click="toUnActive(scope.row.instanceId)"
+          >挂起</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="toFlowImage(scope.row.instanceId)"
+          >流程图</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <component v-bind:is="approve" v-model="businessId" :type="'0'" :taskId="taskId" :showAnyNode="showAnyNode" :formId="formPath" @refresh="getList"></component>
+    <el-dialog title="流程图" :visible.sync="flowChart" width="80%">
+      <img :src="imgUrl" width="100%" style="margin:0 auto"/>
+    </el-dialog>
+
+    <!-- 权限标识:选择用户 -->
+    <el-dialog :title="`${dialogTitle}用户选择`" v-if="userVisible" :visible.sync="userVisible" width="80%" append-to-body>
+      <selectUser :postParams="postParams" :type="dialogTitle" :selectUser.sync="form.assigneePermission" :userVisible.sync="userVisible" @handleUserSelect="handleUserSelect"></selectUser>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import * as api from "@/api/flow/execute";
+import { flowImage } from "@/api/flow/definition";
+import {active, unActive} from "@/api/flow/execute";
+import selectUser from "@/views/components/selectUser";
+
+export default {
+  name: "Todo",
+  dicts: ['flow_status', 'activity_status'],
+  components: {
+    selectUser
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      imgUrl: "",
+      flowChart: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 流程实例表格数据
+      instanceList: [],
+      // 业务审批页面
+      approve: null,
+      taskId: "",
+      businessId: "",
+      formPath: "",
+      showAnyNode: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        nodeName: null,
+        flowStatus: null,
+        approver: null,
+        createTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        assigneePermission: [
+          { required: true, message: "请选择", trigger: "change" }
+        ],
+      },
+      dialogTitle: "",
+      postParams: {},
+      userVisible: false
+    };
+  },
+  activated() {
+    this.getList();
+  },
+  methods: {
+    /** 查询流程实例列表 */
+    getList() {
+      this.loading = true;
+      api.toDoPage(this.queryParams).then(response => {
+        this.instanceList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 办理按钮操作 */
+    handle(row, showAnyNode) {
+      this.taskId = row.id
+      this.businessId = row.businessId
+      this.showAnyNode = showAnyNode
+      if(!row.formPath){
+        row.formPath='system/leave/approve.vue'
+      }
+
+      if (row.formCustom == 'N') {
+        // 实际情况是,不同条件对应不同的页面,所以用动态组件
+        this.approve = (resolve) => require([`@/views/${row.formPath}`], resolve)
+      } else if (row.formCustom === "Y") {
+        this.formPath = row.formPath;
+        this.approve = (resolve) => require([`@/views/form/definition/formDialog.vue`], resolve)
+      }
+    },
+
+    toFlowImage(instanceId) {
+      flowImage(instanceId).then(response => {
+        this.flowChart = true
+        this.imgUrl = "data:image/gif;base64," + response.data;
+      });
+    },
+    /** 转办|加签|委派|减签弹框显示按钮操作 */
+    transferShow(row, title) {
+      this.form.assigneePermission = null;
+      this.taskId = row.id;
+      this.dialogTitle = title;
+      api.getTaskById(this.taskId).then(res => {
+        this.form.assigneePermission = res.data.assigneePermission ? res.data.assigneePermission.split(",") : [];
+      });
+      this.userVisible = true;
+      let operatorTypeObj = {
+        "转办": "1",
+        "加签": "6",
+        "委派": "3",
+        "减签": "7"
+      };
+      this.postParams = {
+        taskId: row.id,
+        operatorType: operatorTypeObj[title]
+      };
+    },
+    // 获取选中用户数据
+    handleUserSelect(checkedItemList) {
+      this.form.assigneePermission = checkedItemList.map(e => {
+        return e.userId;
+      });
+      let assigneePermission = JSON.parse(JSON.stringify(this.form.assigneePermission));
+      let operatorTypeObj = {
+        "转办": "2",
+        "加签": "6",
+        "委派": "3",
+        "减签": "7"
+      };
+      api.interactiveType(this.taskId, Array.isArray(assigneePermission) ? assigneePermission.join(',') : assigneePermission,  operatorTypeObj[this.dialogTitle])
+        .then(() => {
+          this.$modal.msgSuccess(`${this.dialogTitle}成功`);
+          this.getList();
+        });
+    },
+
+    toActive(instanceId) {
+      this.$modal.confirm('是否确认激活流程编号为"' + instanceId + '"的数据项?').then(function() {
+        return active(instanceId);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("激活成功");
+      }).catch(() => {});
+    },
+
+    toUnActive(instanceId) {
+      this.$modal.confirm('是否确认挂起流程编号为"' + instanceId + '"的数据项?').then(function() {
+        return unActive(instanceId);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("挂起成功");
+      }).catch(() => {});
+    },
+  }
+};
+</script>

+ 119 - 0
src/views/form/definition/dialog.vue

@@ -0,0 +1,119 @@
+<template>
+  <div class="app-container">
+    <!-- 添加或修改表单定义对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="400px" v-if="open" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="77.45px" :disabled="disabled">
+        <el-form-item label="表单编码" prop="formCode">
+          <el-input v-model="form.formCode" placeholder="请输入表单编码" maxlength="40" show-word-limit/>
+        </el-form-item>
+        <el-form-item label="表单名称" prop="formName">
+          <el-input v-model="form.formName" placeholder="请输入表单名称" maxlength="100" show-word-limit/>
+        </el-form-item>
+        <el-form-item label="表单版本" prop="version">
+          <el-input v-model="form.version" placeholder="请输入表单版本" maxlength="20" show-word-limit/>
+        </el-form-item>
+        <el-form-item label="表单类型" prop="formType">
+          <el-select v-model="form.formType" placeholder="请选择表单类型">
+            <el-option label="内置表单" :value="0"></el-option>
+            <el-option label="外挂表单" :value="1"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="表单路径" v-if="form.formType === 1" prop="formPath">
+          <el-input v-model="form.formPath" placeholder="请输入表单路径" maxlength="100" show-word-limit/>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" v-if="!disabled" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel" v-if="!disabled">取 消</el-button>
+        <el-button @click="cancel" v-if="disabled">关 闭</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getDefinition, addDefinition, updateDefinition } from "@/api/form/definition";
+
+export default {
+  name: "Dialog",
+  data() {
+    return {
+      // 是否禁用表单
+      disabled: false,
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        formCode: [{ required: true, message: "表单编码不能为空", trigger: "blur" }],
+        formName: [{ required: true, message: "表单名称不能为空", trigger: "blur" }],
+        version: [{ required: true, message: "表单版本不能为空", trigger: "blur" }],
+        formType: [{ required: true, message: "表单类型不能为空", trigger: "blur" }],
+        formPath: [{ required: true, message: "表单路径不能为空", trigger: "blur" }]
+      }
+    };
+  },
+  methods: {
+    /** 打开表单定义弹框 */
+    async show(id, disabled) {
+      this.reset();
+      this.disabled = disabled
+      if (id) {
+        await getDefinition(id).then(response => {
+          this.form = response.data;
+        });
+      }
+      this.open = true
+      if (this.disabled) {
+        this.title = "详情"
+      } else if (id) {
+        this.title = "修改"
+      } else {
+        this.title = "新增"
+      }
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        formCode: null,
+        formName: null,
+        version: null,
+        formType: null,
+        formPath: null
+      };
+      this.resetForm("form");
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.formType === 0) this.form.formPath = "";
+          if (this.form.id != null) {
+            updateDefinition(this.form).then(response => {
+              this.$modal.msgSuccess("修改成功");
+              this.open = false;
+              this.$emit('refresh');
+            });
+          } else {
+            delete this.form.id;
+            addDefinition(this.form).then(response => {
+              this.$modal.msgSuccess("新增成功");
+              this.open = false;
+              this.$emit('refresh');
+            });
+          }
+        }
+      });
+    }
+  }
+};
+</script>

+ 96 - 0
src/views/form/definition/formDialog.vue

@@ -0,0 +1,96 @@
+<template>
+  <div class="app-container">
+    <!-- 添加或修改OA 自定义流程表单对话框 -->
+    <el-dialog :title="formName" :visible.sync="showFormDialog" width="500px" v-if="showFormDialog" append-to-body @close="cancel">
+      <iframe ref="FormCreate" :src="url" :style="`width: 100%; max-height: 60vh; height: ${offsetHeight}px; border: none;`"/>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getToken } from "@/utils/auth";
+export default {
+  name: "formDialog",
+  props: {
+    // 来源:0待办-办理 1已办-流程历史记录 2已发布的表单设计
+    type: {
+      type: String,
+      default: "0"
+    },
+    /* 业务id */
+    value: {
+      type: String,
+      default: "",
+    },
+    /* 实例id */
+    taskId: {
+      type: String,
+      default: "",
+    },
+    /* 是否可以标编辑 */
+    disabled: {
+      type: Boolean,
+      default: false,
+    },
+    // 是否显示弹出层
+    visible: {
+      type: Boolean,
+      default: true
+    },
+    // 表单名称
+    formName: {
+      type: String,
+      default: "办理"
+    },
+    // 表单id,查找表单设计内容
+    formId: {
+      type: String,
+      default: ""
+    }
+  },
+  data() {
+    return {
+      url: "",
+      offsetHeight: null, // iframe高度
+      showFormDialog: true
+    };
+  },
+  mounted() {
+    this.url = `${process.env.VUE_APP_FLOW_API}/warm-flow-ui/index.html?type=formCreate&Authorization=Bearer ` + getToken();
+    window.addEventListener("message", this.handleMessage);
+  },
+  beforeDestroy() {
+    window.removeEventListener("message", this.handleMessage);
+  },
+  methods: {
+    handleMessage(event) {
+      switch (event.data.method) {
+        case "formInit":
+          let data = {
+            type: this.type,
+            formId: this.formId,
+            taskId: this.taskId,
+            disabled: this.disabled
+          };
+          this.$refs.FormCreate.contentWindow.postMessage({ method: "formInit", data }, '*');
+          break;
+        case "getOffsetHeight":
+          // 获取子页面内容高度
+          this.offsetHeight = event.data.offsetHeight;
+          break;
+        case "submitSuccess":
+          this.$modal.msgSuccess("办理成功");
+          this.cancel();
+          break;
+      }
+    },
+    // 取消按钮
+    cancel() {
+      this.showFormDialog = false;
+      this.$emit("update:visible", false);
+      this.$refs.FormCreate.contentWindow.postMessage({ method: "reset" }, '*');
+      this.$emit('refresh');
+    }
+  }
+};
+</script>

+ 283 - 0
src/views/form/definition/index.vue

@@ -0,0 +1,283 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="mini" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="表单编码" prop="formCode">
+        <el-input
+          v-model="queryParams.formCode"
+          placeholder="请输入表单编码"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="表单名称" prop="formName">
+        <el-input
+          v-model="queryParams.formName"
+          placeholder="请输入表单名称"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="表单版本" prop="version">
+        <el-input
+          v-model="queryParams.version"
+          placeholder="请输入表单版本"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['form:definition:add']"
+        >新增</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="definitionList" @selection-change="handleSelectionChange"  @sort-change="handleSortChange">
+      <el-table-column type="selection" width="55" align="center" fixed />
+      <el-table-column label="序号" width="50" align="center" key="id" prop="id">
+        <template slot-scope="scope">
+          {{ scope.$index + 1 }}
+        </template>
+      </el-table-column>
+      <el-table-column label="表单编码" align="center" prop="formCode" sortable="custom"  :show-overflow-tooltip="true"/>
+      <el-table-column label="表单名称" align="center" prop="formName" sortable="custom" :show-overflow-tooltip="true"/>
+      <el-table-column label="表单版本" align="center" prop="version" sortable="custom" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <el-tag>{{scope.row.version}}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="是否发布" align="center" prop="isPublish" sortable="custom" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <dict-tag :options="dict.type.is_publish" :value="scope.row.isPublish"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" sortable="custom" :sort-orders="['descending', 'ascending']" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" fixed="right" width="230" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            v-if="scope.row.formType === 0"
+            size="mini"
+            type="text"
+            @click="handleDesign(scope.row)"
+            v-hasPermi="['form:design:queryDesign']"
+          >表单设计</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.isPublish === 0"
+            @click="handlePublish(scope.row.id)"
+            v-hasPermi="['form:definition:publish']"
+          >发布</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.isPublish === 1"
+            @click="handleUpPublish(scope.row.id)"
+            v-hasPermi="['form:definition:upPublish']"
+          >取消发布</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="handleCopyDef(scope.row.id)"
+            v-hasPermi="['form:definition:upPublish']"
+          >复制表单</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.isPublish === 0"
+            @click="handleUpdate(scope.row.id)"
+            v-hasPermi="['form:definition:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.isPublish === 0"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['form:definition:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+    <Dialog ref="dialog" @refresh="getList"></Dialog>
+    <formDialog v-if="showFormDialog" :visible.sync="showFormDialog" :type="'2'" :formName="formName" :formId="formId"></formDialog>
+  </div>
+</template>
+
+<script>
+import {
+  listDefinition,
+  delDefinition,
+  publish,
+  unPublish,
+  copyDef
+} from "@/api/form/definition";
+import Dialog from "./dialog";
+import formDialog from "./formDialog";
+
+export default {
+  name: "formDefinition",
+  dicts: ['is_publish', 'activity_status'],
+  components: {
+    Dialog,
+    formDialog
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      imgUrl: "",
+      flowChart: false,
+      // 唯一标识符
+      uniqueId: "",
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 表单定义表格数据
+      definitionList: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        formCode: null,
+        formName: null,
+        version: null,
+      },
+      formName: "",
+      formId: "",
+      showFormDialog: false
+    };
+  },
+  created() {
+    this.getList();
+  },
+  activated() {
+    const time = this.$route.query.t;
+    if (time != null && time != this.uniqueId) {
+      this.uniqueId = time;
+      this.queryParams.pageNum = Number(this.$route.query.pageNum);
+      this.getList();
+    }
+  },
+  methods: {
+    /** 查询表单定义列表 */
+    getList() {
+      this.loading = true;
+      listDefinition(this.queryParams).then(response => {
+        this.definitionList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.$refs.dialog.show();
+    },
+    /** 表单设计按钮操作 */
+    handleDesign(row) {
+      if (row.isPublish === 1) {
+        this.formName = row.formName;
+        this.formId = row.id;
+        this.showFormDialog = true;
+      } else {
+        const params = { pageNum: this.queryParams.pageNum };
+        this.$tab.openPage("表单设计", '/form/form-design/index/' + row.id, params);
+      }
+    },
+    /** 发布按钮操作 */
+    handlePublish(id) {
+      this.$modal.confirm('是否确认发布表单定义编号为"' + id + '"的数据项?').then(function() {
+        return publish(id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("发布成功");
+      }).catch(() => {});
+    },
+    /** 取消发布按钮操作 */
+    handleUpPublish(id) {
+      this.$modal.confirm('是否确认取消发布表单定义编号为"' + id + '"的数据项?').then(function() {
+        return unPublish(id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("取消成功");
+      }).catch(() => {});
+    },
+    /** 修改按钮操作 */
+    handleUpdate(id) {
+      this.$refs.dialog.show(id);
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$modal.confirm('是否确认删除表单定义编号为"' + ids + '"的数据项?').then(function() {
+        return delDefinition(ids);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+    /** 复制表单按钮操作 */
+    handleCopyDef(id) {
+      this.$modal.confirm('是否确认复制表单定义编号为"' + id + '"的数据项?').then(function() {
+        return copyDef(id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("复制成功");
+      }).catch(() => {});
+    },
+    /** 排序触发事件 */
+    handleSortChange(column, prop, order) {
+      this.queryParams.orderByColumn = column.prop;
+      this.queryParams.isAsc = column.order;
+      this.getList();
+    }
+  }
+};
+</script>

+ 38 - 0
src/views/form/definition/warm-form.vue

@@ -0,0 +1,38 @@
+<template>
+  <div :style="'height:' + height">
+    <iframe :src="url" style="width: 100%; height: 100%"/>
+  </div>
+</template>
+<script>
+import {getToken} from "@/utils/auth";
+
+export default {
+  name: "WarmForm",
+  data() {
+    return {
+      height: document.documentElement.clientHeight - 94.5 + "px;",
+      url: ""
+    };
+  },
+  mounted() {
+    this.url = `${process.env.VUE_APP_FLOW_API}/warm-flow-ui/index.html?type=form&id=${this.$route.params.id}&disabled=${this.$route.query.disabled}&Authorization=Bearer ` + getToken();
+    window.addEventListener("message", this.handleMessage);
+  },
+  beforeDestroy() {
+    window.removeEventListener("message", this.handleMessage);
+  },
+  methods: {
+    handleMessage(event) {
+      switch (event.data.method) {
+        case "close":
+          this.close();
+          break;
+      }
+    },
+    close() {
+      const obj = { path: "/flow/form" };
+      this.$tab.closeOpenPage(obj);
+    }
+  }
+};
+</script>