From 844bf54fc403cef99f901ee6a9a3c57d7c840036 Mon Sep 17 00:00:00 2001
From: lyh <liuyuanheng@xalxzn.com>
Date: 星期一, 24 二月 2025 10:06:52 +0800
Subject: [PATCH] 新增flowable

---
 src/views/flowable/components/HistoricDetail.vue       |  343 +++++++++
 src/views/flowable/components/ActHandleBtn.vue         |  235 ++++++
 src/views/flowable/test_demo/modules/TestDemoModal.vue |   60 +
 src/views/flowable/mixin/FlowableMixin.js              |   68 +
 src/views/flowable/api/todo.js                         |   65 +
 src/views/flowable/components/ActHistoricDetailBtn.vue |   53 +
 src/views/flowable/test_demo/TestDemoList.vue          |  311 ++++++++
 src/views/flowable/test_demo/modules/TestDemoForm.vue  |  172 ++++
 src/views/flowable/components/ActCancelBtn.vue         |   74 ++
 src/views/flowable/api/process.js                      |   34 
 src/views/flowable/api/definition.js                   |  134 +++
 src/views/flowable/components/ActApplyBtn.vue          |   73 ++
 src/main.js                                            |    3 
 package.json                                           |    2 
 src/views/flowable/api/finished.js                     |   37 +
 src/views/flowable/modeler/modelerDesign.vue           |  400 +++++++++++
 16 files changed, 2,063 insertions(+), 1 deletions(-)

diff --git a/package.json b/package.json
index e15155c..ee25293 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
     "ant-design-vue": "^1.7.2",
     "axios": "^0.18.0",
     "china-area-data": "^5.0.1",
+    "element-ui": "^2.15.14",
     "clipboard": "^2.0.4",
     "codemirror": "^5.46.0",
     "cron-parser": "^2.10.0",
@@ -51,6 +52,7 @@
     "vuex": "^3.1.0",
     "vxe-table": "2.9.13",
     "vxe-table-plugin-antd": "1.8.10",
+    "workflow-bpmn-modeler": "^0.2.8",
     "xe-utils": "2.4.8",
     "xss": "^1.0.13"
   },
diff --git a/src/main.js b/src/main.js
index 5ce1354..29a973e 100644
--- a/src/main.js
+++ b/src/main.js
@@ -50,7 +50,8 @@
 //琛ㄥ崟楠岃瘉
 import { rules } from '@/utils/rules'
 import * as echarts from 'echarts'
-
+import ElementUI from 'element-ui';
+Vue.use(ElementUI);
 // 灏嗚嚜鍔ㄦ敞鍐屾墍鏈夌粍浠朵负鍏ㄥ眬缁勪欢
 import dataV from '@jiaminghi/data-view'
 Vue.use(dataV)
diff --git a/src/views/flowable/api/definition.js b/src/views/flowable/api/definition.js
new file mode 100644
index 0000000..918fdb2
--- /dev/null
+++ b/src/views/flowable/api/definition.js
@@ -0,0 +1,134 @@
+import {axios as request} from '@/utils/request'
+
+// 鏌ヨ娴佺▼瀹氫箟鍒楄〃
+export function listDefinition(query) {
+  return request({
+    url: '/flowable/definition/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 閮ㄧ讲娴佺▼瀹炰緥
+export function definitionStartByDefId(procDefId,data) {
+  return request({
+    url: '/flowable/definition/startByProcDefId/' + procDefId,
+    method: 'post',
+    data: JSON.stringify(data)
+  })
+}
+// 閮ㄧ讲娴佺▼瀹炰緥
+export function definitionStartByDefKey(procDefKey,data) {
+  return request({
+    url: '/flowable/definition/startByProcDefKey/' + procDefKey,
+    method: 'post',
+    data: JSON.stringify(data)
+  })
+}
+// 閮ㄧ讲娴佺▼瀹炰緥
+export function definitionStartByDataId(dataId,data) {
+  return request({
+    url: '/flowable/definition/startByDataId/' + dataId,
+    method: 'post',
+    data: data
+  })
+}
+
+// 鑾峰彇娴佺▼鍙橀噺
+export function getProcessVariables(taskId) {
+  return request({
+    url: '/flowable/task/processVariables/' + taskId,
+    method: 'get'
+  })
+}
+
+// 婵�娲�/鎸傝捣娴佺▼
+export function updateState(params) {
+  return request({
+    url: '/flowable/definition/updateState',
+    method: 'put',
+    params: params
+  })
+}
+
+// 鎸囧畾娴佺▼鍔炵悊浜哄憳鍒楄〃
+export function userList(query) {
+  return request({
+    url: '/flowable/definition/userList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鎸囧畾娴佺▼鍔炵悊缁勫垪琛�
+export function roleList(query) {
+  return request({
+    url: '/flowable/definition/roleList',
+    method: 'get',
+    params: query
+  })
+}
+// 鎸囧畾娴佺▼鍒嗙被鍒楄〃
+export function categoryList(query) {
+  return request({
+    url: '/flowable/definition/categoryList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 璇诲彇xml鏂囦欢
+export function readXml(deployId) {
+  return request({
+    url: '/flowable/definition/readXml/' + deployId,
+    method: 'get'
+  })
+}
+// 璇诲彇xml鏂囦欢
+export function readXmlByDataId(dataId) {
+  return request({
+    url: '/flowable/definition/readXmlByDataId/' + dataId,
+    method: 'get'
+  })
+}
+// 璇诲彇image鏂囦欢
+export function readImage(deployId) {
+  return request({
+    url: '/flowable/definition/readImage/' + deployId,
+    method: 'get'
+  })
+}
+
+// 璇诲彇image鏂囦欢
+export function getFlowViewer(procInsId) {
+  return request({
+    url: '/flowable/task/flowViewer/' + procInsId,
+    method: 'get'
+  })
+}
+// 璇诲彇image鏂囦欢
+export function getFlowViewerByDataId(dataId) {
+  return request({
+    url: '/flowable/task/flowViewerByDataId/' + dataId,
+    method: 'get'
+  })
+}
+
+// 璇诲彇xml鏂囦欢
+export function saveXml(data) {
+  return request({
+    url: '/flowable/definition/save',
+    method: 'post',
+    data: data
+  })
+}
+
+// 鍒犻櫎娴佺▼瀹氫箟
+export function delDeployment(query) {
+  return request({
+    url: '/flowable/definition/delete/',
+    method: 'delete',
+    params: query
+  })
+}
+
diff --git a/src/views/flowable/api/finished.js b/src/views/flowable/api/finished.js
new file mode 100644
index 0000000..bbab6da
--- /dev/null
+++ b/src/views/flowable/api/finished.js
@@ -0,0 +1,37 @@
+import {axios as request} from '@/utils/request'
+
+// 鏌ヨ宸插姙浠诲姟鍒楄〃
+export function finishedList(query) {
+  return request({
+    url: '/flowable/task/finishedList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 浠诲姟娴佽浆璁板綍
+export function flowRecord(query) {
+  return request({
+    url: '/flowable/task/flowRecord',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鎾ゅ洖浠诲姟
+export function revokeProcess(data) {
+  return request({
+    url: '/flowable/task/revokeProcess',
+    method: 'post',
+    data: data
+  })
+}
+
+// 閮ㄧ讲娴佺▼瀹炰緥
+export function deployStart(deployId) {
+  return request({
+    url: '/flowable/process/startFlow/' + deployId,
+    method: 'get',
+  })
+}
+
diff --git a/src/views/flowable/api/process.js b/src/views/flowable/api/process.js
new file mode 100644
index 0000000..87a3346
--- /dev/null
+++ b/src/views/flowable/api/process.js
@@ -0,0 +1,34 @@
+import {axios as request} from '@/utils/request'
+
+// 鎴戠殑鍙戣捣鐨勬祦绋�
+export function myProcessList(query) {
+  return request({
+    url: '/flowable/task/myProcess',
+    method: 'get',
+    params: query
+  })
+}
+
+
+// 鍙栨秷鐢宠
+export function deleteByDataId(dataId,deleteReason) {
+  const data = {
+    dataId:dataId,
+    deleteReason:deleteReason
+  }
+  return request({
+    url: '/flowable/instance/deleteByDataId',
+    method: 'post',
+    params: data
+  })
+}
+
+
+// 閮ㄧ讲娴佺▼瀹炰緥
+export function deployStart(deployId) {
+  return request({
+    url: '/flowable/process/startFlow/' + deployId,
+    method: 'get',
+  })
+}
+
diff --git a/src/views/flowable/api/todo.js b/src/views/flowable/api/todo.js
new file mode 100644
index 0000000..107bb4a
--- /dev/null
+++ b/src/views/flowable/api/todo.js
@@ -0,0 +1,65 @@
+import {axios as request} from '@/utils/request'
+
+// 鏌ヨ寰呭姙浠诲姟鍒楄〃
+export function todoList(query) {
+  return request({
+    url: '/flowable/task/todoList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 瀹屾垚浠诲姟
+export function completeTask(data) {
+  return request({
+    url: '/flowable/task/completeByDateId',
+    method: 'post',
+    data: data
+  })
+}
+
+// 濮旀淳浠诲姟
+export function delegate(data) {
+  return request({
+    url: '/flowable/task/delegate',
+    method: 'post',
+    data: data
+  })
+}
+
+// 閫�鍥炰换鍔�
+export function returnTask(data) {
+  return request({
+    url: '/flowable/task/taskReturnByDataId',
+    method: 'post',
+    data: data
+  })
+}
+
+// 椹冲洖浠诲姟
+export function rejectTask(data) {
+  return request({
+    url: '/flowable/task/taskRejectByDataId',
+    method: 'post',
+    data: data
+  })
+}
+
+// 鍙��鍥炰换鍔″垪琛�
+export function returnList(data) {
+  return request({
+    url: '/flowable/task/findReturnTaskListByDataId',
+    method: 'post',
+    data: data
+  })
+}
+
+// 涓嬩竴鑺傜偣 todo 鐩墠鐩存帴鑷姩鍒嗛厤鍒板�欓�変汉锛屼笉鐢ㄤ富鍔ㄩ�変汉锛屽鏈夐渶瑕佸啀寮�鍙�
+export function getNextFlowNode(data) {
+  return request({
+    url: '/flowable/task/nextFlowNode',
+    method: 'post',
+    data: data
+  })
+}
+
diff --git a/src/views/flowable/components/ActApplyBtn.vue b/src/views/flowable/components/ActApplyBtn.vue
new file mode 100644
index 0000000..7e5a39c
--- /dev/null
+++ b/src/views/flowable/components/ActApplyBtn.vue
@@ -0,0 +1,73 @@
+<style lang="less">
+</style>
+<template>
+  <span>
+      <a-button :loading="submitLoading" :type="btnType" @click="applySubmit()" >{{text}}</a-button>
+  </span>
+</template>
+
+<script>
+  import {definitionStartByDataId} from "@views/flowable/api/definition";
+
+export default {
+    name: 'ActApplyBtn',
+    components: {},
+    props: {
+        btnType: { type: String, default: 'link', required: false },
+        /**/
+        dataId: {
+            type: String,
+            default: '',
+            required: true
+        },
+        variables:{
+          type: Object,
+          default: {},
+        },
+        text: {
+            type: String,
+            default: '鎻愪氦鐢宠',
+            required: false
+        }
+
+    },
+    data() {
+        return {
+            modalVisible: false,
+            submitLoading: false,
+            form: {
+            },
+        };
+    },
+    created() {
+    },
+    watch: {
+    },
+    methods: {
+        applySubmit() {
+            if (this.dataId && this.dataId.length < 1) {
+                this.error = '蹇呴』浼犲叆鍙傛暟dataId';
+                this.$message.error(this.error);
+                return;
+            } else {
+                this.error = '';
+            }
+            this.submitLoading = true;
+            var params = Object.assign({
+                dataId: this.dataId
+            }, this.variables);
+          definitionStartByDataId(this.dataId, params)
+                .then(res => {
+                    if (res.success) {
+                        this.$message.success('鎿嶄綔鎴愬姛');
+                        this.$emit('success');
+                    } else {
+                        this.$message.error(res.message);
+                    }
+                })
+                .finally(() => (this.submitLoading = false));
+        }
+    }
+
+};
+</script>
diff --git a/src/views/flowable/components/ActCancelBtn.vue b/src/views/flowable/components/ActCancelBtn.vue
new file mode 100644
index 0000000..02af66f
--- /dev/null
+++ b/src/views/flowable/components/ActCancelBtn.vue
@@ -0,0 +1,74 @@
+<style lang="less">
+</style>
+<template>
+  <span>
+      <a-button :type="btnType" @click="cancel()" >{{text}}</a-button>
+      <a-modal title="纭鎾ゅ洖" v-model="modalCancelVisible" :mask-closable="false" :width="500">
+            <a-form ref="delForm" v-model="cancelForm" :label-width="70" v-if="modalCancelVisible">
+                <a-form-item label="鎾ゅ洖鍘熷洜" prop="reason">
+                    <a-input type="textarea" v-model="cancelForm.reason" :rows="4" />
+                </a-form-item>
+            </a-form>
+            <div slot="footer">
+                <a-button type="text" @click="modalCancelVisible = false">鍙栨秷</a-button>
+                <a-button type="primary" :disabled="submitLoading" @click="handelSubmitCancel">鎻愪氦</a-button>
+            </div>
+        </a-modal>
+  </span>
+</template>
+
+<script>
+import {deleteByDataId} from "@views/flowable/api/process";
+
+export default {
+    name: 'ActCancelBtn',
+    components: {},
+    props: {
+        btnType: { type: String, default: 'link', required: false },
+        /**/
+        dataId: {
+            type: String,
+            default: '',
+            required: true
+        },
+        text: {
+            type: String,
+            default: '鎾ゅ洖',
+            required: false
+        }
+    },
+    data() {
+        return {
+            modalCancelVisible: false,
+            cancelForm: {
+                reason: ''
+            },
+            submitLoading: false,
+        };
+    },
+    created() {
+    },
+    watch: {
+    },
+    methods: {
+        cancel() {
+            this.modalCancelVisible = true;
+        },
+        handelSubmitCancel() {
+            this.submitLoading = true;
+          deleteByDataId(this.dataId, this.cancelForm.reason)
+                .then(res => {
+                    if (res.success) {
+                        this.$message.success('鎿嶄綔鎴愬姛');
+                        this.modalCancelVisible = false;
+                        this.$emit('success');
+                    } else {
+                        this.$message.error(res.message);
+                    }
+                })
+                .finally(() => (this.submitLoading = false));
+        }
+    }
+
+};
+</script>
diff --git a/src/views/flowable/components/ActHandleBtn.vue b/src/views/flowable/components/ActHandleBtn.vue
new file mode 100644
index 0000000..a820ea6
--- /dev/null
+++ b/src/views/flowable/components/ActHandleBtn.vue
@@ -0,0 +1,235 @@
+<style lang="less">
+</style>
+<template>
+  <span>
+      <a-button :type="btnType" @click="handle()" >{{text}}</a-button>
+      <a-modal :title="modalTaskTitle" v-model="modalTaskVisible" :mask-closable="false" :width="500">
+
+      <div  v-if="modalTaskVisible">
+        <div v-if="type==handleType.reApply">
+          纭鏃犺骞堕噸鏂版彁浜わ紵
+        </div>
+        <a-form ref="form" :model="form" :label-width="85" >
+          <a-form-item v-if="type!==handleType.reApply" label="澶勭悊鎰忚" prop="reason">
+            <a-input type="textarea" v-model="form.comment" :rows="4" />
+          </a-form-item>
+          <div v-show="type==2">
+            <a-form-item label="閫�鍥炶妭鐐�" prop="targetKey" v-if="returnTaskList.length">
+              <a-radio-group v-model="form.targetKey" @change="targetKeyChange">
+                <a-radio-button
+                  v-for="item in returnTaskList"
+                  :key="item.id"
+                  :value="item.id"
+                >{{item.name}}</a-radio-button>
+              </a-radio-group>
+            </a-form-item>
+            <span v-else>鏃犲彲閫�鍥炶妭鐐癸紒</span>
+          </div>
+            <div v-if="form.targetKey !== 'start' && candidateUsers.length">
+                <a-form-item label="涓嬩釜鑺傜偣瀹℃壒鍊欓�変汉">
+                    <a-select
+                        mode="multiple"
+                        v-model="candidateUsersSelecteds"
+                        style="width: 100%"
+                        placeholder="璇烽�夋嫨涓嬩釜鑺傜偣瀹℃壒鍊欓�変汉"
+                    >
+                    <a-select-option v-for="user in candidateUsers" :key="user.username" :value="user.username">
+                        {{user.realname}}
+                    </a-select-option>
+                  </a-select>
+                </a-form-item>
+            </div>
+        </a-form>
+      </div>
+      <div slot="footer">
+        <a-button type="text" @click="modalTaskVisible=false">鍙栨秷</a-button>
+        <a-button type="primary" :loading="submitLoading" @click="handelSubmit">鎻愪氦</a-button>
+      </div>
+    </a-modal>
+  </span>
+</template>
+
+<script>
+  import {completeTask, rejectTask, returnList, returnTask} from "@views/flowable/api/todo";
+
+export default {
+    name: 'ActHandleBtn',
+    components: {},
+    props: {
+        btnType: { type: String, default: 'link', required: false },
+        /* handleType 0閫氳繃 1椹冲洖 2閫�鍥�  */
+        type: {
+            type: String|Number,
+            default: '0',
+            required: true
+        },
+        dataId: {
+            type: String,
+            default: '',
+            required: true
+        },
+      /*娴佺▼鍙橀噺*/
+        variables:{
+          type: Object,
+          default: ()=>{},
+        },
+        candidateUsers:{
+          type: Array,
+          default: ()=>[],
+        },
+        text: {
+            type: String,
+            default: '澶勭悊',
+            required: false
+        }
+    },
+    data() {
+        return {
+          handleType:{
+            // 閫氳繃
+            pass: 0,
+            // 椹冲洖
+            back: 1,
+            // 閫�鍥�
+            return: 2,
+            // 閲嶆柊鎻愪氦
+            reApply: 3
+            },
+            returnTaskList: [],
+            candidateUsersSelecteds:[],
+            modalTaskVisible: false,
+            submitLoading: false,
+            form: {
+              comment:'',
+              targetKey:''
+            },
+            modalTaskTitle: '',
+
+        };
+    },
+    created() {
+    },
+    watch: {
+    },
+    methods: {
+        handle() {
+          this.form.comment = ''
+          this.candidateUsersSelecteds = []
+            if (this.type === this.handleType.delegate) {
+                // this.delegateTask();
+            } else if (this.type === this.handleType.pass) {
+                this.passTask();
+            } else if (this.type === this.handleType.back) {
+                this.backTask();
+            } else if(this.type === this.handleType.return){
+                this.returnTask();
+            } else if(this.type === this.handleType.reApply){
+                this.reApply();
+            }
+            else {
+                this.$message.warn('鏈煡绫诲瀷type锛屽弬瑙� handleType');
+            }
+        },
+        reApply() {
+            const v = this;
+            this.modalTaskTitle = '纭閲嶆柊鎻愪氦';
+            this.modalTaskVisible = true;
+        },
+        passTask() {
+            const v = this;
+            this.modalTaskTitle = '瀹℃壒閫氳繃';
+            this.modalTaskVisible = true;
+        },
+        backTask() {
+          const v = this;
+          this.modalTaskTitle = '瀹℃壒椹冲洖';
+          this.modalTaskVisible = true;
+        },
+        returnTask() {
+            const v = this;
+            this.modalTaskTitle = '瀹℃壒閫�鍥�';
+            this.modalTaskVisible = true;
+            returnList({dataId:this.dataId}).then(res => {
+              this.returnTaskList = res.result||[];
+              // console.log(this.returnTaskList)
+            })
+        },
+
+        handelSubmit() {
+            console.log('鎻愪氦');
+            this.submitLoading = true;
+            var formData = Object.assign({
+                dataId:this.dataId,
+                candidateUsers:this.candidateUsersSelecteds,
+                values:Object.assign({dataId:this.dataId},this.variables)
+            }, this.form);
+            if (this.type==this.handleType.reApply){
+              formData.comment = '閲嶆柊鎻愪氦'
+            }
+            if (!formData.comment){
+              this.$message.error('璇疯緭鍏ュ鎵规剰瑙侊紒');
+              this.submitLoading=false
+              return;
+            }
+            // 鏈変笅涓妭鐐瑰鎵逛汉閫夋嫨锛屼絾鏄湭閫�
+            if (this.candidateUsers.length &&
+              this.candidateUsersSelecteds.length==0 &&
+              this.form.targetKey !== 'start'
+            ){
+              this.$message.error('璇烽�夋嫨涓嬩釜鑺傜偣瀹℃壒浜猴紒');
+              this.submitLoading=false
+              return;
+            }
+            if (this.type == this.handleType.reApply || this.type == this.handleType.pass) {
+                // 閫氳繃
+              completeTask(formData).then(res => {
+                    this.submitLoading = false;
+                    if (res.success) {
+                        this.$message.success('鎿嶄綔鎴愬姛');
+                        this.modalTaskVisible = false;
+                        this.$emit('success');
+                    } else {
+                        this.$message.error('鎿嶄綔澶辫触');
+                    }
+                }).finally(()=>{this.submitLoading=false});
+            } else if (this.type == this.handleType.back) {
+                // 椹冲洖
+                  rejectTask(formData).then(res => {
+                        this.submitLoading = false;
+                        if (res.success) {
+                            this.$message.success('鎿嶄綔鎴愬姛');
+                            this.modalTaskVisible = false;
+                            this.$emit('success');
+                        } else {
+                            this.$message.error('鎿嶄綔澶辫触');
+                        }
+                    }).finally(()=>{this.submitLoading=false});
+
+            } else if (this.type == this.handleType.return){
+              if (!formData.targetKey){
+                this.$message.error('璇烽�夋嫨閫�鍥炶妭鐐癸紒');
+                this.submitLoading=false
+                return;
+              }
+              //閫�鍥�
+              returnTask(formData).then(res => {
+                this.submitLoading = false;
+                if (res.success) {
+                  this.$message.success('鎿嶄綔鎴愬姛');
+                  this.modalTaskVisible = false;
+                  this.$emit('success');
+                } else {
+                  this.$message.error('鎿嶄綔澶辫触');
+                }
+              }).finally(()=>{this.submitLoading=false});
+            }
+        },
+
+        targetKeyChange() {
+            this.candidateUsersSelecteds = []
+            this.$emit('targetKeyChange',this.form.targetKey)
+        }
+    }
+
+};
+</script>
diff --git a/src/views/flowable/components/ActHistoricDetailBtn.vue b/src/views/flowable/components/ActHistoricDetailBtn.vue
new file mode 100644
index 0000000..c4a69f5
--- /dev/null
+++ b/src/views/flowable/components/ActHistoricDetailBtn.vue
@@ -0,0 +1,53 @@
+<style lang="less">
+</style>
+<template>
+  <span>
+      <a-button :type="btnType"  @click="history()" >{{text}}</a-button>
+      <a-modal title="瀹℃壒鍘嗗彶" v-model="modalLsVisible" :mask-closable="true" :width="'80%'" :footer="null">
+          <div v-if="modalLsVisible">
+              <HistoricDetail ref="historicDetail" :data-id="dataId"></HistoricDetail>
+          </div>
+      </a-modal>
+  </span>
+</template>
+
+<script>
+import HistoricDetail from './HistoricDetail';
+export default {
+    name: 'ActHistoricDetailBtn',
+    components: { HistoricDetail },
+    props: {
+        btnType: { type: String, default: 'link', required: false },
+        /**/
+        dataId: {
+            type: String,
+            default: '',
+            required: true
+        },
+        text: {
+            type: String,
+            default: '瀹℃壒鍘嗗彶',
+            required: false
+        }
+    },
+    data() {
+        return {
+            modalLsVisible: false
+        };
+    },
+    created() {
+    },
+    watch: {
+    },
+    methods: {
+        history() {
+            if (!this.dataId) {
+                this.$message.error('娴佺▼瀹炰緥ID涓嶅瓨鍦�');
+                return;
+            }
+            this.modalLsVisible = true;
+        }
+    }
+
+};
+</script>
diff --git a/src/views/flowable/components/HistoricDetail.vue b/src/views/flowable/components/HistoricDetail.vue
new file mode 100644
index 0000000..be707cc
--- /dev/null
+++ b/src/views/flowable/components/HistoricDetail.vue
@@ -0,0 +1,343 @@
+<style lang="less">
+</style>
+<template>
+  <div class="search">
+    <a-card>
+      <p slot="title">
+        <span>娴佺▼鍥�</span>
+      </p>
+      <div :style="{height: svgHeight}" v-if="svgShow">
+        <bpmnModeler class="svg" ref="bpm" :xml="xmlData" :is-view="true"></bpmnModeler>
+      </div>
+    </a-card>
+    <a-card style="margin-top:10px;">
+      <p slot="title">
+        <span>娴佺▼瀹℃壒杩涘害鍘嗗彶</span>
+      </p>
+      <a-row style="position:relative">
+        <div class="block">
+          <a-timeline>
+            <a-timeline-item
+              v-for="(item,index ) in flowRecordList"
+              :key="index"
+              :color="setColor(item.finishTime)"
+            >
+              <p style="font-weight: 700;">{{item.taskName}}
+                <i v-if="!item.finishTime" style="color: orange">(寰呭姙涓�傘�傘��)</i>
+              </p>
+
+              <a-card :body-style="{ padding: '10px' }">
+                <label v-if="item.assigneeName&&item.finishTime" style="font-weight: normal;margin-right: 30px;">瀹為檯鍔炵悊浜猴細 {{item.assigneeName}} <a-tag type="info" size="mini">{{item.deptName}}</a-tag></label>
+                <label v-if="item.candidate" style="font-weight: normal;margin-right: 30px;">鍊欓�夊姙鐞嗕汉锛� {{item.candidate}}</label>
+                <label style="font-weight: normal">鎺ユ敹鏃堕棿锛� </label><label style="color:#8a909c;font-weight: normal">{{item.createTime}}</label>
+                <label v-if="item.finishTime" style="margin-left: 30px;font-weight: normal">鍔炵粨鏃堕棿锛� </label><label style="color:#8a909c;font-weight: normal">{{item.finishTime}}</label>
+                <label v-if="item.duration" style="margin-left: 30px;font-weight: normal">鑰楁椂锛� </label><label style="color:#8a909c;font-weight: normal">{{item.duration}}</label>
+
+                <p  v-if="item.comment">
+<!--  1 姝e父鎰忚  2 閫�鍥炴剰瑙� 3 椹冲洖鎰忚                -->
+                  <a-tag color="green" v-if="item.comment.type === '1'">
+                    <span v-if="item.comment.comment!='閲嶆柊鎻愪氦'">閫氳繃锛�</span>
+                    {{item.comment.comment}}
+                  </a-tag>
+                  <a-tag color="orange" v-if="item.comment.type === '2'">閫�鍥烇細  {{item.comment.comment}}</a-tag>
+                  <a-tag color="red" v-if="item.comment.type === '3'">椹冲洖锛�  {{item.comment.comment}}</a-tag>
+                </p>
+              </a-card>
+            </a-timeline-item>
+          </a-timeline>
+        </div>
+      </a-row>
+    </a-card>
+  </div>
+</template>
+
+<script>
+import {flowRecord} from "@views/flowable/api/finished";
+import {getFlowViewerByDataId, readXmlByDataId} from "@views/flowable/api/definition";
+import bpmnModeler from "workflow-bpmn-modeler";
+
+export default {
+    name: 'HistoricDetail',
+  components: {
+    bpmnModeler,
+  },
+    props: {
+    /**/
+        dataId: {
+            type: String,
+            default: '',
+            required: true
+        },
+
+    },
+    data() {
+        return {
+            taskList:[],
+            flowRecordList: [], // 娴佺▼娴佽浆鏁版嵁
+            formData:{},
+            xmlData:'',
+            type: 0,
+            loading: false, // 琛ㄥ崟鍔犺浇鐘舵��
+            loadingImg: false,
+            data: [],
+            id: '',
+            imgUrl: '',
+            backRoute: '',
+          svgHeight:'',
+          svgShow: true
+        };
+    },
+    created() {
+        this.init();
+    },
+    watch: {
+      dataId: function(newval, oldName) {
+            this.init();
+        }
+    },
+
+    methods: {
+        init() {
+          this.getFlowRecordList()
+          this.getModelDetail()
+        },
+      /** xml 鏂囦欢 */
+      getModelDetail() {
+        // 鍙戦�佽姹傦紝鑾峰彇xml
+        readXmlByDataId(this.dataId).then(res => {
+          this.xmlData = res.result
+          this.getFlowViewer()
+          setTimeout(()=>{
+            this.fitViewport()
+          })
+        })
+      },
+      // 娴佺▼杩涜鎯呭喌
+      getFlowViewer() {
+        getFlowViewerByDataId(this.dataId).then(res => {
+          this.taskList = res.result || []
+          this.fillColor();
+        })
+      },
+      /** 娴佺▼娴佽浆璁板綍 */
+      getFlowRecordList() {
+        const params = {dataId: this.dataId}
+        flowRecord(params).then(res => {
+          // console.log(res)
+          this.flowRecordList = res.result.flowList;
+          this.finishOrder()
+          // 娴佺▼杩囩▼涓笉瀛樺湪鍒濆鍖栬〃鍗� 鐩存帴璇诲彇鐨勬祦绋嬪彉閲忎腑瀛樺偍鐨勮〃鍗曞��
+          if (res.result.formData) {
+            this.formData = res.result.formData;
+          }
+        }).catch(res => {
+          console.log(res)
+        })
+      },
+        //鏁寸悊椤哄簭锛屾妸寰呭姙鏀炬渶涓婇潰锛屽苟涓斿彧鐣欎竴涓紙涓嶇劧浼氱鏃朵細涔憋級
+      finishOrder(){
+        const list = []
+        let noFinish = null
+        for (const flow of this.flowRecordList) {
+          if (flow.finishTime){
+            // 鍔炵粨鐨勮妭鐐瑰悓鏃跺彇鏈夊疄闄呭姙鐞嗕汉鐨勶紝鍥犱负浼氱浼氬皢鎵�鏈夌殑澶氬疄渚嬮兘杩斿洖锛岄渶瑕佽繃婊�
+            if (flow.assigneeId){
+              list.push(flow)
+            }
+          } else {
+            noFinish = flow
+          }
+        }
+        if (noFinish){
+          const find = list.find(obj=>obj.taskDefKey == noFinish.taskDefKey);
+          if (find){
+            noFinish.taskName = '銆愪細绛句腑銆�'+noFinish.taskName
+          }
+          this.flowRecordList = [noFinish,...list];
+        } else {
+          this.flowRecordList = list;
+        }
+
+      },
+        setColor(val) {
+          if (val) {
+            return "#2bc418";
+          } else {
+            return "#b3bdbb";
+          }
+        },
+      fillColor() {
+        const modeler = this.$refs.bpm.modeler;
+        const canvas = modeler.get('canvas')
+        modeler._definitions.rootElements[0].flowElements.forEach(n => {
+          const completeTask = this.taskList.find(m => m.key === n.id)
+          const todoTask = this.taskList.find(m => !m.completed)
+          const endTask = this.taskList[this.taskList.length - 1]
+          //鐢ㄦ埛浠诲姟
+          if (n.$type === 'bpmn:UserTask') {
+            if (completeTask) {
+              canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
+              canvas.addMarker(n.id, completeTask.back ? 'highlight-back' : 'highlight-noback')
+              n.outgoing.forEach(nn => {
+                const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
+                if (targetTask) {
+                  if (todoTask && completeTask.key === todoTask.key && !todoTask.completed){
+                    canvas.addMarker(nn.id, todoTask.completed ? 'highlight' : 'highlight-todo')
+                    canvas.addMarker(nn.targetRef.id, todoTask.completed ? 'highlight' : 'highlight-todo')
+                  }else {
+                    canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                    canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                  }
+                }
+              })
+            }
+          }
+          // 鎺掍粬缃戝叧
+          else if (n.$type === 'bpmn:ExclusiveGateway') {
+            if (completeTask) {
+              canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
+              n.outgoing.forEach(nn => {
+                const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
+                if (targetTask) {
+
+                  canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                  canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                }
+
+              })
+            }
+
+          }
+          // 骞惰缃戝叧
+          else if (n.$type === 'bpmn:ParallelGateway') {
+            if (completeTask) {
+              canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
+              n.outgoing.forEach(nn => {
+                debugger
+                const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
+                if (targetTask) {
+                  canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                  canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
+                }
+              })
+            }
+          }
+          else if (n.$type === 'bpmn:StartEvent') {
+            n.outgoing.forEach(nn => {
+              const completeTask = this.taskList.find(m => m.key === nn.targetRef.id)
+              if (completeTask) {
+                canvas.addMarker(nn.id, 'highlight')
+                canvas.addMarker(n.id, 'highlight')
+                return
+              }
+            })
+          }
+          else if (n.$type === 'bpmn:EndEvent') {
+            if (endTask.key === n.id && endTask.completed) {
+              canvas.addMarker(n.id, 'highlight')
+              return
+            }
+          }
+        })
+      },
+      // 璁╁浘鑳借嚜閫傚簲灞忓箷
+      fitViewport() {
+        const modeler = this.$refs.bpm.modeler;
+        const canvas = modeler.get('canvas')
+        // this.zoom = this.modeler.get('canvas').zoom('fit-viewport')
+        if (this.svgHeight){
+          document.querySelector('.canvas').style.height = this.svgHeight;
+        }
+        const bbox = document.querySelector('.flow-containers .viewport').getBBox()
+        const currentViewbox = modeler.get('canvas').viewbox()
+        if (!this.svgHeight){
+          this.svgHeight = currentViewbox.inner.height + 'px'
+          this.svgShow = false
+          this.$nextTick(()=>{
+            this.svgShow = true
+          })
+          // this.fitViewport()
+          setTimeout(()=>{
+            this.fitViewport()
+          })
+        }
+
+        const elementMid = {
+          x: bbox.x + bbox.width / 2 - 65,
+          y: bbox.y + bbox.height / 2
+        }
+        // 璋冭妭浣嶇疆
+        modeler.get('canvas').viewbox({
+          x: elementMid.x - currentViewbox.width / 2 + 70,
+          y: elementMid.y - currentViewbox.height/2,
+          width: currentViewbox.width,
+          height: currentViewbox.height
+        })
+        // 璋冭妭澶у皬缂╂斁
+        const zoom = currentViewbox.outer.width /(currentViewbox.inner.width+200)
+        console.log('********',zoom,elementMid,currentViewbox.inner,currentViewbox.outer)
+        // modeler.get('canvas').zoom(zoom)
+
+
+      },
+    }
+
+};
+</script>
+<style lang="less">
+   .highlight.djs-shape .djs-visual > :nth-child(1) {
+     fill: green !important;
+     stroke: green !important;
+     fill-opacity: 0.2 !important;
+   }
+   .highlight.djs-shape .djs-visual > :nth-child(2) {
+     fill: green !important;
+   }
+   .highlight.djs-shape .djs-visual > path {
+     fill: green !important;
+     fill-opacity: 0.2 !important;
+     stroke: green !important;
+   }
+   .highlight.djs-connection > .djs-visual > path {
+     stroke: green !important;
+   }
+   // .djs-connection > .djs-visual > path {
+   //   stroke: orange !important;
+   //   stroke-dasharray: 4px !important;
+   //   fill-opacity: 0.2 !important;
+   // }
+   // .djs-shape .djs-visual > :nth-child(1) {
+   //   fill: orange !important;
+   //   stroke: orange !important;
+   //   stroke-dasharray: 4px !important;
+   //   fill-opacity: 0.2 !important;
+   // }
+   .highlight-todo.djs-connection > .djs-visual > path {
+     stroke: orange !important;
+     stroke-dasharray: 4px !important;
+     fill-opacity: 0.2 !important;
+   }
+   .highlight-todo.djs-shape .djs-visual > :nth-child(1) {
+     fill: orange !important;
+     stroke: orange !important;
+     stroke-dasharray: 4px !important;
+     fill-opacity: 0.2 !important;
+   }
+   .highlight-back.djs-connection > .djs-visual > path {
+     stroke: red !important;
+     stroke-dasharray: 4px !important;
+     fill-opacity: 0.2 !important;
+   }
+   .highlight-back.djs-shape .djs-visual > :nth-child(1) {
+     fill: red !important;
+     stroke: red !important;
+     stroke-dasharray: 4px !important;
+     fill-opacity: 0.2 !important;
+   }
+   .overlays-div {
+     font-size: 10px;
+     color: red;
+     width: 100px;
+     top: -20px !important;
+   }
+</style>
\ No newline at end of file
diff --git a/src/views/flowable/mixin/FlowableMixin.js b/src/views/flowable/mixin/FlowableMixin.js
new file mode 100644
index 0000000..50a76a4
--- /dev/null
+++ b/src/views/flowable/mixin/FlowableMixin.js
@@ -0,0 +1,68 @@
+import Vue from "vue";
+import {USER_INFO} from "@/store/mutation-types";
+
+/**
+ *
+ */
+
+export const FlowableMixin = {
+  data(){
+    return {
+      loginUser:{}
+    }
+  },
+  created() {
+    this.loginUser = Vue.ls.get(USER_INFO);
+  },
+  methods:{
+    // 褰撳墠鏁版嵁鏄惁鍙彁浜�
+    isCanApply(row){
+      // 娌℃湁娴佺▼瀹炰緥鐨勫嵆鍙彁浜�
+      return !Boolean(row.processInstanceId);
+    },
+    // 褰撳墠鏁版嵁鏄惁鍙挙鍥�
+    isCanRecall(row){
+      // 杩涜涓殑娴佺▼&&褰撳墠鑺傜偣涓嶆槸寮�濮嬭妭鐐�&&鐘舵�佷笉鏄�氳繃
+      return Boolean(row.processInstanceId)&&row.taskNameId!=='start'&&row.actStatus!=='瀹℃壒閫氳繃';
+    },
+    // 閲嶆柊鎻愪氦鎸夐挳
+    isCanReApply(row){
+      return row.taskNameId=='start'&&this.isTodoUsers(row);
+    },
+    // 閫氳繃鎸夐挳
+    isCanPass(row){
+      return row.taskNameId!=='start'&& this.isTodoUsers(row);
+    },
+    // 椹冲洖閫�鍥炴寜閽�
+    isCanBacke(row){
+      // 涓嶆槸start鑺傜偣&&鍦ㄥ彲鎿嶄綔浜哄憳鍒楄〃
+      return row.taskNameId!=='start'&&this.isTodoUsers(row);
+    },
+    // 鏌ョ湅瀹℃壒鍘嗗彶鎸夐挳
+    isCanHistoric(row){
+      // 鏈夊疄渚媔d灏辫兘鏌ョ湅
+      return Boolean(row.processInstanceId);
+    },
+    // 褰撳墠鐧诲綍浜烘槸鍚﹀湪澶勭悊浜哄垪琛�
+    isTodoUsers(row){
+      const todoUsers = row.todoUsers;
+      if (todoUsers&&todoUsers.length){
+        const parse = JSON.parse(todoUsers)||[];
+        return parse.includes(this.loginUser.username);
+      }else {
+        return false;
+      }
+    },
+    // 褰撳墠鐧诲綍浜烘槸鍚︽槸澶勭悊杩囩殑浜哄垪琛�
+    isDoneUsers(row){
+      const doneUsers = row.doneUsers;
+      if (doneUsers&&doneUsers.length){
+        const parse = JSON.parse(doneUsers)||[];
+        return parse.includes(this.loginUser.username);
+      }else {
+        return false;
+      }
+    },
+  }
+
+}
\ No newline at end of file
diff --git a/src/views/flowable/modeler/modelerDesign.vue b/src/views/flowable/modeler/modelerDesign.vue
new file mode 100644
index 0000000..a3233e3
--- /dev/null
+++ b/src/views/flowable/modeler/modelerDesign.vue
@@ -0,0 +1,400 @@
+<template>
+  <div>
+<!--  ==================娴佺▼瀹氫箟鍒楄〃===============  -->
+    <a-card v-if="!xmlFrame.open||xmlView">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
+      <el-form-item label="娴佺▼鍚嶇О" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="璇疯緭鍏ュ悕绉�"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="娴佺▼鍒嗙被" prop="category">
+        <el-select @change="handleQuery" v-model="queryParams.category" placeholder="璇烽�夋嫨娴佺▼鍒嗙被" clearable prop="category">
+          <el-option label="璇烽�夋嫨" value="" />
+          <el-option v-for="category in categorys" :key="category.id" :label="category.name" :value="category.id" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="婵�娲�" prop="active">
+        <el-switch
+          v-model="queryParams.active"
+          active-color="#13ce66"
+          inactive-color="#ff4949"
+          @change="handleQuery"
+        >
+        </el-switch>
+      </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-item style="float:right">
+        <el-button
+          type="primary"
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleLoadXml"
+        >鏂板娴佺▼瀹氫箟</el-button>
+      </el-form-item>
+    </el-form>
+
+      <el-table
+        v-loading="loading" fit
+        :data="definitionList"
+        row-key="id"
+        border
+        lazy
+        :load="load"
+        :tree-props="{children: 'children', hasChildren: 'hasChildren'}">
+      <el-table-column label="娴佺▼瀹氫箟id" align="center" prop="id" />
+      <el-table-column label="娴佺▼鏍囪瘑Key" align="center" prop="key"  />
+      <el-table-column label="娴佺▼鍒嗙被" align="center" >
+        <template slot-scope="scope">
+            <span>{{ getCategoryName(scope.row.category) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="娴佺▼鍚嶇О" align="center" :show-overflow-tooltip="true">
+        <template slot-scope="scope">
+          <el-button type="text" @click="handleReadImage(scope.row.deploymentId)">
+            <span>{{ scope.row.name }}</span>
+          </el-button>
+        </template>
+      </el-table-column>
+      <el-table-column label="娴佺▼鐗堟湰" align="center">
+        <template slot-scope="scope">
+          <el-tag size="medium" >v{{ scope.row.version }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="鐘舵��" align="center">
+        <template slot-scope="scope">
+          <el-tag type="success" v-if="scope.row.suspensionState === 1">婵�娲�</el-tag>
+          <el-tag type="warning" v-if="scope.row.suspensionState === 2">鎸傝捣</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="閮ㄧ讲鏃堕棿" align="center" prop="deploymentTime" width="180"/>
+      <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-dropdown>
+            <span class="el-dropdown-link">
+              鏇村鎿嶄綔<i class="el-icon-arrow-down el-icon--right"></i>
+            </span>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item icon="el-icon-edit-outline" @click.native="handleLoadXml(scope.row)">
+                缂栬緫
+              </el-dropdown-item>
+<!--              <el-dropdown-item icon="el-icon-connection" @click.native="handleAddForm(scope.row)" v-if="scope.row.formId == null">
+                閰嶇疆琛ㄥ崟
+              </el-dropdown-item>-->
+              <el-dropdown-item icon="el-icon-video-pause" @click.native="handleUpdateSuspensionState(scope.row)" v-if="scope.row.suspensionState === 1">
+                鎸傝捣
+              </el-dropdown-item>
+              <el-dropdown-item icon="el-icon-video-play" @click.native="handleUpdateSuspensionState(scope.row)" v-if="scope.row.suspensionState === 2">
+                婵�娲�
+              </el-dropdown-item>
+              <el-dropdown-item icon="el-icon-delete" @click.native="handleDelete(scope.row)" >
+                鍒犻櫎
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-pagination
+      v-show="total>0"
+      :total="total"
+      :current-page.sync="queryParams.pageNum"
+      :page-size.sync="queryParams.pageSize"
+      @size-change="getList"
+      @current-change="getList"
+    />
+    </a-card>
+      <!-- 娴佺▼鍥� -->
+    <a-card v-if="xmlFrame.open&&!xmlView" :title="xmlFrame.title">
+      <a slot="extra" href="#" @click="()=>{xmlFrame.open=false}">杩斿洖</a>
+      <bpmn-modeler
+        v-if="xmlShow"
+        ref="refNode"
+        :xml="xmlData"
+        :users="users"
+        :groups="groups"
+        :categorys="categorys"
+        :is-view="xmlView"
+        @save="save"
+      />
+    </a-card>
+<!--  寮圭獥棰勮  -->
+      <a-modal :title="xmlFrame.title" :visible.sync="xmlView&&xmlFrame.open" :width="xmlFrame.width"
+        :footer="null" closable @cancel="()=>{xmlView=false,xmlFrame.open=false}"
+      >
+        <bpmn-modeler
+          v-if="xmlShow"
+          ref="refNode"
+          :xml="xmlData"
+          :users="users"
+          :groups="groups"
+          :categorys="categorys"
+          :is-view="xmlView"
+          @save="save"
+        />
+      </a-modal>
+
+  </div>
+</template>
+
+<script>
+import bpmnModeler from "workflow-bpmn-modeler";
+import {
+  categoryList,
+  delDeployment,
+  listDefinition,
+  readXml,
+  roleList,
+  saveXml,
+  updateState,
+  userList
+} from "@views/flowable/api/definition";
+
+export default {
+  components: {
+    bpmnModeler,
+  },
+  data() {
+    return {
+      /*===================璁捐鍣ㄥ睘鎬�======================*/
+      users: [],
+      groups: [],
+      categorys: [],
+      /*=================椤甸潰灞炴��===================*/
+      loading: true,
+      // 鎬绘潯鏁�
+      total: 0,
+      // 娴佺▼瀹氫箟琛ㄦ牸鏁版嵁
+      definitionList: [],
+      allDefinitionList: [],
+      // 鏌ヨ鍙傛暟
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        category: null,
+        key: null,
+        tenantId: null,
+        deployTime: null,
+        derivedFrom: null,
+        derivedFromRoot: null,
+        parentDeploymentId: null,
+        engineVersion: null
+      },
+      xmlFrame:{
+        width:'70%',
+        title:'娴佺▼鍥�',
+        open: false,
+        src: "",
+      },
+      // xml
+      xmlData:"",
+      xmlShow: true,
+      xmlView: false,
+
+    };
+  },
+  created() {
+    this.initUserAndRole();
+    this.getList();
+  },
+  methods: {
+    /*===============璁捐鍣�===============*/
+    initUserAndRole(){
+      userList({}).then(res=>{
+        this.users = res.result||[]
+        this.users.map(o=>{
+          o.id = o.username
+          o.name = o.realname
+        })
+      })
+      roleList({}).then(res=>{
+        this.groups = res.result||[]
+        this.groups.map(o=>{
+          o.name = o.roleName
+        })
+      })
+      categoryList({}).then(res=>{
+        this.categorys = res.result||[]
+      })
+    },
+    getModelDetail(deployId) {
+      // 鍙戦�佽姹傦紝鑾峰彇xml
+      readXml(deployId).then(res =>{
+        this.xmlData = res.result;
+      })
+    },
+    getCategoryName(category){
+      let find = this.categorys.find(o=>o.id==category);
+      if (find){
+        return find.name
+      }
+      return ''
+    },
+    /*淇濆瓨娴佺▼瀹氫箟*/
+    save(data) {
+      console.log(data);  // { process: {...}, xml: '...', svg: '...' }
+      const params = {
+        name: data.process.name,
+        category: data.process.category,
+        xml: data.xml
+      }
+      saveXml(params).then(res => {
+        this.$message.success(res.message)
+        // 鍏抽棴褰撳墠鏍囩椤靛苟杩斿洖涓婁釜椤甸潰
+        this.getList()
+        this.xmlFrame.open = false
+      })
+    },
+    /*================椤甸潰===============*/
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.queryParams.suspensionState = this.queryParams.active?1:0;
+      this.getList();
+    },
+    /** 鏌ヨ娴佺▼瀹氫箟鍒楄〃 */
+    getList() {
+      this.loading = true;
+      // 鏈�鏂扮増鏈�
+      const param1 = Object.assign({
+        isLastVersion:1,
+      },this.queryParams)
+      listDefinition(param1).then(response => {
+        this.definitionList = response.result.records;
+        this.total = response.result.total;
+        this.loading = false;
+        for (const definition of this.definitionList) {
+          definition.hasChildren = true
+        }
+      });
+      // 鎵�鏈�
+      const param2 = Object.assign({
+        isLastVersion:0
+      },this.queryParams,{
+        pageSize: 9999,
+        pageNum:1
+      })
+      listDefinition(param2).then(response => {
+        console.log(response)
+        this.allDefinitionList = response.result.records;
+      });
+    },
+    /** 閲嶇疆鎸夐挳鎿嶄綔 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    resetForm(formName) {
+      this.$refs[formName].resetFields()
+    },
+    /** 鎵撳紑娴佺▼璁捐寮圭獥椤甸潰 */
+    handleLoadXml(row){
+      if (row&&row.deploymentId){
+        console.log(row.deploymentId)
+        this.handleReadImage(row.deploymentId)
+        this.xmlView = false
+        this.xmlFrame.title = "缂栬緫娴佺▼鍥�";
+      } else {
+        //鏂板
+        this.xmlData = ''
+        this.xmlView = false
+        this.xmlFrame.open = true
+        this.xmlFrame.title = '鏂板娴佺▼'
+        this.xmlShow = false
+        this.$nextTick(()=>{
+          this.xmlShow = true
+        })
+      }
+      this.xmlFrame.width = '90%'
+    },
+    /** 娴佺▼鍥炬煡鐪� */
+    handleReadImage(deploymentId){
+      this.xmlFrame.title = "娴佺▼鍥�";
+      this.xmlFrame.open = true;
+      this.xmlFrame.width = '70%';
+      // this.xmlFrame.src = process.env.VUE_APP_BASE_API + "/flowable/definition/xmlFrame/" + deploymentId;
+      // 鍙戦�佽姹傦紝鑾峰彇xml
+      this.xmlView = true
+      readXml(deploymentId).then(res =>{
+        if (res.success){
+          this.xmlData = res.result
+          /*this.xmlShow = false
+          this.$nextTick(()=>{
+            this.xmlShow = true
+          })*/
+        } else {
+          this.$message.error("鑾峰彇娴佺▼鍥惧け璐ワ紒")
+        }
+      })
+    },
+    // 鎵撳紑涓氬姟琛ㄥ崟
+    handleForm() {
+    },
+    // 閰嶇疆涓氬姟琛ㄥ崟
+    handleAddForm(row) {
+    },
+    /** 鎸傝捣/婵�娲绘祦绋� */
+    handleUpdateSuspensionState(row){
+      let state = 1;
+      if (row.suspensionState === 1) {
+        state = 2
+      }
+      const params = {
+        deployId: row.deploymentId,
+        state: state
+      }
+      updateState(params).then(res => {
+        this.$message.success(res.message);
+        this.getList();
+      });
+    },
+    /** 鍒犻櫎鎸夐挳鎿嶄綔 */
+    handleDelete(row) {
+      // const ids = row.deploymentId || this.ids;
+      const params = {
+        deployId: row.deploymentId
+      }
+      this.$confirm({
+        title:"璀﹀憡",
+        content:'鏄惁纭鍒犻櫎娴佺▼瀹氫箟缂栧彿涓�"' + params.deployId + '"鐨勬暟鎹」?',
+        confirmButtonText: "纭畾",
+        cancelButtonText: "鍙栨秷",
+        type: "warning",
+        onOk:()=>{
+          delDeployment(params).then(res=>{
+            this.getList();
+            if (res.success){
+              this.$message.success('鍒犻櫎鎴愬姛');
+            } else {
+              this.$message.success('鍒犻櫎澶辫触');
+            }
+          })
+        }
+      })
+    },
+    load(tree, treeNode, resolve) {
+      const key = tree.key;
+      const childrens = []
+      for (const one of this.allDefinitionList) {
+        if (one.key==key&&one.id!=tree.id){
+          childrens.push(one)
+        }
+      }
+      console.log(tree, treeNode,this.allDefinitionList,childrens)
+      resolve(childrens)
+    }
+  },
+  computed: {
+    getContainer() {
+      return document.querySelector('#app')
+    }
+  }
+};
+</script>
\ No newline at end of file
diff --git a/src/views/flowable/test_demo/TestDemoList.vue b/src/views/flowable/test_demo/TestDemoList.vue
new file mode 100644
index 0000000..a53ace4
--- /dev/null
+++ b/src/views/flowable/test_demo/TestDemoList.vue
@@ -0,0 +1,311 @@
+<template>
+  <a-card :bordered="false">
+    <!-- 鏌ヨ鍖哄煙 -->
+    <div class="table-page-search-wrapper">
+      <a-form layout="inline" @keyup.enter.native="searchQuery">
+        <a-row :gutter="24">
+          <a-col :xl="6" :lg="7" :md="8" :sm="24">
+            <a-form-item label="鐢ㄦ埛鍚�">
+              <a-input placeholder="璇疯緭鍏ョ敤鎴峰悕" v-model="queryParam.name"></a-input>
+            </a-form-item>
+          </a-col>
+          <a-col :xl="6" :lg="7" :md="8" :sm="24">
+            <a-form-item label="鎬у埆">
+              <j-dict-select-tag placeholder="璇烽�夋嫨鎬у埆" v-model="queryParam.sex" dictCode="sex"/>
+            </a-form-item>
+          </a-col>
+          <template v-if="toggleSearchStatus">
+            <a-col :xl="6" :lg="7" :md="8" :sm="24">
+              <a-form-item label="鐢熸棩">
+                <j-date placeholder="璇烽�夋嫨鐢熸棩" v-model="queryParam.birthday"></j-date>
+              </a-form-item>
+            </a-col>
+            <a-col :xl="6" :lg="7" :md="8" :sm="24">
+              <a-form-item label="鐢ㄦ埛缂栫爜">
+                <a-input placeholder="璇疯緭鍏ョ敤鎴风紪鐮�" v-model="queryParam.userCode"></a-input>
+              </a-form-item>
+            </a-col>
+            <a-col :xl="6" :lg="7" :md="8" :sm="24">
+              <a-form-item label="鍩庡競">
+                <j-area-linkage type="cascader" v-model="queryParam.chegnshi" placeholder="璇烽�夋嫨鐪佸競鍖�"/>
+              </a-form-item>
+            </a-col>
+            <a-col :xl="6" :lg="7" :md="8" :sm="24">
+              <a-form-item label="checkbox">
+                <j-dict-select-tag placeholder="璇烽�夋嫨checkbox" v-model="queryParam.ceck" dictCode="sex"/>
+              </a-form-item>
+            </a-col>
+            <a-col :xl="6" :lg="7" :md="8" :sm="24">
+              <a-form-item label="涓嬫媺澶氶��">
+                <j-multi-select-tag placeholder="璇烽�夋嫨涓嬫媺澶氶��" dictCode="sex" v-model="queryParam.xiamuti"/>
+              </a-form-item>
+            </a-col>
+            <a-col :xl="6" :lg="7" :md="8" :sm="24">
+              <a-form-item label="鎼滅储涓嬫媺">
+                <j-search-select-tag placeholder="璇烽�夋嫨鎼滅储涓嬫媺" v-model="queryParam.searchSel" dict="sys_role,role_name,role_code"/>
+              </a-form-item>
+            </a-col>
+          </template>
+          <a-col :xl="6" :lg="7" :md="8" :sm="24">
+            <span style="float: left;overflow: hidden;" class="table-page-search-submitButtons">
+              <a-button type="primary" @click="searchQuery" icon="search">鏌ヨ</a-button>
+              <a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px">閲嶇疆</a-button>
+              <a @click="handleToggleSearch" style="margin-left: 8px">
+                {{ toggleSearchStatus ? '鏀惰捣' : '灞曞紑' }}
+                <a-icon :type="toggleSearchStatus ? 'up' : 'down'"/>
+              </a>
+            </span>
+          </a-col>
+        </a-row>
+      </a-form>
+    </div>
+    <!-- 鏌ヨ鍖哄煙-END -->
+
+    <!-- 鎿嶄綔鎸夐挳鍖哄煙 -->
+    <div class="table-operator">
+      <a-button @click="handleAdd" type="primary" icon="plus">鏂板</a-button>
+      <a-button type="primary" icon="download" @click="handleExportXls('娴嬭瘯鐢ㄦ埛琛�')">瀵煎嚭</a-button>
+      <a-upload name="file" :showUploadList="false" :multiple="false" :headers="tokenHeader" :action="importExcelUrl" @change="handleImportExcel">
+        <a-button type="primary" icon="import">瀵煎叆</a-button>
+      </a-upload>
+      <!-- 楂樼骇鏌ヨ鍖哄煙 -->
+      <j-super-query :fieldList="superFieldList" ref="superQueryModal" @handleSuperQuery="handleSuperQuery"></j-super-query>
+      <a-dropdown v-if="selectedRowKeys.length > 0">
+        <a-menu slot="overlay">
+          <a-menu-item key="1" @click="batchDel"><a-icon type="delete"/>鍒犻櫎</a-menu-item>
+        </a-menu>
+        <a-button style="margin-left: 8px"> 鎵归噺鎿嶄綔 <a-icon type="down" /></a-button>
+      </a-dropdown>
+    </div>
+
+    <!-- table鍖哄煙-begin -->
+    <div>
+      <div class="ant-alert ant-alert-info" style="margin-bottom: 16px;">
+        <i class="anticon anticon-info-circle ant-alert-icon"></i> 宸查�夋嫨 <a style="font-weight: 600">{{ selectedRowKeys.length }}</a>椤�
+        <a style="margin-left: 24px" @click="onClearSelected">娓呯┖</a>
+      </div>
+
+      <a-table
+        ref="table"
+        size="middle"
+        bordered
+        rowKey="id"
+        :columns="columns"
+        :dataSource="dataSource"
+        :pagination="ipagination"
+        :loading="loading"
+        :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
+        class="j-table-force-nowrap"
+        @change="handleTableChange">
+
+        <template slot="htmlSlot" slot-scope="text">
+          <div v-html="text"></div>
+        </template>
+        <template slot="imgSlot" slot-scope="text">
+          <span v-if="!text" style="font-size: 12px;font-style: italic;">鏃犲浘鐗�</span>
+          <img v-else :src="getImgView(text)" height="25px" alt="" style="max-width:80px;font-size: 12px;font-style: italic;"/>
+        </template>
+        <template slot="pcaSlot" slot-scope="text">
+          <div>{{ getPcaText(text) }}</div>
+        </template>
+        <template slot="fileSlot" slot-scope="text">
+          <span v-if="!text" style="font-size: 12px;font-style: italic;">鏃犳枃浠�</span>
+          <a-button
+            v-else
+            :ghost="true"
+            type="primary"
+            icon="download"
+            size="small"
+            @click="downloadFile(text)">
+            涓嬭浇
+          </a-button>
+        </template>
+
+        <span slot="action" slot-scope="text, record">
+          <a @click="handleEdit(record)">缂栬緫</a>
+          <a-divider type="vertical" ></a-divider>
+          <a @click="relationAct(record)">鍏宠仈娴佺▼</a>
+          <act-apply-btn @success="loadData" :data-id="record.id"
+                         :variables="{ assigneeList:[]}"></act-apply-btn>
+          {{isCanApply(record)}}
+          <act-handle-btn @success="loadData" :data-id="record.id" :type="3" text="閲嶆柊鎻愪氦"></act-handle-btn>
+          {{isCanReApply(record)}}
+          <act-handle-btn @success="loadData" :data-id="record.id" :type="0" text="閫氳繃"
+            :candidate-users="[{username:'admin',realname:'绠$悊鍛�'},{username:'jeecg',realname:'jeecg璐﹀彿'}]"
+          ></act-handle-btn>
+          {{isCanPass(record)}}
+          <act-handle-btn @success="loadData" :data-id="record.id" :type="1" text="椹冲洖"></act-handle-btn>
+          {{isCanBacke(record)}}
+          <act-handle-btn @success="loadData" :data-id="record.id" :type="2" text="閫�鍥�"
+                          @targetKeyChange="targetKeyChange"
+                          :candidate-users="[{username:'admin',realname:'绠$悊鍛�'},{username:'jeecg',realname:'jeecg璐﹀彿'}]"
+          ></act-handle-btn>
+           {{isCanBacke(record)}}
+          <act-cancel-btn @success="loadData" :data-id="record.id"></act-cancel-btn>
+         {{isCanRecall(record)}}
+          <act-historic-detail-btn :data-id="record.id"></act-historic-detail-btn>
+          {{isCanHistoric(record)}}
+          <a-divider type="vertical" />
+          <a-dropdown>
+            <a class="ant-dropdown-link">鏇村 <a-icon type="down" /></a>
+            <a-menu slot="overlay">
+              <a-menu-item>
+                <a @click="handleDetail(record)">璇︽儏</a>
+              </a-menu-item>
+              <a-menu-item>
+                <a-popconfirm title="纭畾鍒犻櫎鍚�?" @confirm="() => handleDelete(record.id)">
+                  <a>鍒犻櫎</a>
+                </a-popconfirm>
+              </a-menu-item>
+            </a-menu>
+          </a-dropdown>
+        </span>
+
+      </a-table>
+    </div>
+
+    <test-demo-modal ref="modalForm" @ok="modalFormOk"></test-demo-modal>
+  </a-card>
+</template>
+
+<script>
+
+  import '@/assets/less/TableExpand.less'
+  import { mixinDevice } from '@/utils/mixin'
+  import { JeecgListMixin } from '@/mixins/JeecgListMixin'
+  import TestDemoModal from './modules/TestDemoModal'
+  import {filterMultiDictText} from '@/components/dict/JDictSelectUtil'
+  import Area from '@/components/_util/Area'
+  import {getAction} from "@api/manage";
+  import ActApplyBtn from "@views/flowable/components/ActApplyBtn";
+  import ActCancelBtn from "@views/flowable/components/ActCancelBtn";
+  import ActHandleBtn from "@views/flowable/components/ActHandleBtn";
+  import ActHistoricDetailBtn from "@views/flowable/components/ActHistoricDetailBtn";
+  import {FlowableMixin} from "@views/flowable/mixin/FlowableMixin";
+
+  export default {
+    name: 'TestDemoList',
+    mixins:[JeecgListMixin, mixinDevice,FlowableMixin],
+    components: {
+      TestDemoModal,
+      ActApplyBtn,
+      ActCancelBtn,
+      ActHandleBtn,
+      ActHistoricDetailBtn
+    },
+    data () {
+      return {
+        description: '娴嬭瘯鐢ㄦ埛琛ㄧ鐞嗛〉闈�',
+        // 琛ㄥご
+        columns: [
+          {
+            title: '#',
+            dataIndex: '',
+            key:'rowIndex',
+            width:60,
+            align:"center",
+            customRender:function (t,r,index) {
+              return parseInt(index)+1;
+            }
+          },
+          {
+            title:'鐢ㄦ埛鍚�',
+            align:"center",
+            sorter: true,
+            dataIndex: 'name'
+          },
+          {
+            title:'鎬у埆',
+            align:"center",
+            sorter: true,
+            dataIndex: 'sex_dictText'
+          },
+          {
+            title:'骞撮緞',
+            align:"center",
+            dataIndex: 'age'
+          },
+          {
+            title:'娴佺▼鐘舵��',
+            align:"center",
+            dataIndex: 'actStatus'
+          },
+          {
+            title:'寰呭鐞嗚妭鐐�',
+            align:"center",
+            dataIndex: 'taskName'
+          },
+          {
+            title: '鎿嶄綔',
+            dataIndex: 'action',
+            align:"center",
+            scopedSlots: { customRender: 'action' }
+          }
+        ],
+        url: {
+          list: "/test_demo/testDemo/list",
+          delete: "/test_demo/testDemo/delete",
+          deleteBatch: "/test_demo/testDemo/deleteBatch",
+          exportXlsUrl: "/test_demo/testDemo/exportXls",
+          importExcelUrl: "test_demo/testDemo/importExcel",
+
+        },
+        dictOptions:{},
+        pcaData:'',
+        superFieldList:[],
+      }
+    },
+    created() {
+      this.pcaData = new Area()
+    this.getSuperFieldList();
+    },
+    computed: {
+      importExcelUrl: function(){
+        return `${window._CONFIG['domianURL']}/${this.url.importExcelUrl}`;
+      },
+    },
+    methods: {
+      getPcaText(code){
+        return this.pcaData.getText(code);
+      },
+      initDictConfig(){
+      },
+      getSuperFieldList(){
+        let fieldList=[];
+        fieldList.push({type:'string',value:'id',text:'涓婚敭',dictCode:''})
+        fieldList.push({type:'string',value:'name',text:'鐢ㄦ埛鍚�',dictCode:''})
+        fieldList.push({type:'string',value:'sex',text:'鎬у埆',dictCode:'sex'})
+        fieldList.push({type:'int',value:'age',text:'骞撮緞',dictCode:''})
+        fieldList.push({type:'string',value:'descc',text:'鎻忚堪',dictCode:''})
+        fieldList.push({type:'date',value:'birthday',text:'鐢熸棩'})
+        fieldList.push({type:'string',value:'userCode',text:'鐢ㄦ埛缂栫爜',dictCode:''})
+        fieldList.push({type:'string',value:'topPic',text:'澶村儚',dictCode:''})
+        fieldList.push({type:'string',value:'fileKk',text:'闄勪欢',dictCode:''})
+        fieldList.push({type:'pca',value:'chegnshi',text:'鍩庡競'})
+        fieldList.push({type:'string',value:'pop',text:'寮圭獥',dictCode:''})
+        fieldList.push({type:'string',value:'ceck',text:'checkbox',dictCode:'sex'})
+        fieldList.push({type:'list_multi',value:'xiamuti',text:'涓嬫媺澶氶��',dictTable:'', dictText:'', dictCode:'sex'})
+        fieldList.push({type:'sel_search',value:'searchSel',text:'鎼滅储涓嬫媺',dictTable:'sys_role', dictText:'role_name', dictCode:'role_code'})
+        fieldList.push({type:'sel_search',value:'selTable',text:'涓嬫媺瀛楀吀琛�',dictTable:'sys_user', dictText:'realname', dictCode:'username'})
+        this.superFieldList = fieldList
+      },
+      relationAct(r) {
+        getAction("/test_demo/testDemo/relationAct",{dataId:r.id}).then(res=>{
+          if (res.success){
+            this.$message.success("鎿嶄綔鎴愬姛")
+            this.loadData()
+          } else {
+            this.$message.error("鎿嶄綔澶辫触")
+          }
+        })
+      },
+      targetKeyChange(targetKey) {
+        // todo
+        console.log('targetKey鏀瑰彉锛屾敼鍙� :candidate-users 鍊欓�変汉',targetKey)
+      }
+    }
+  }
+</script>
+<style scoped>
+  @import '~@assets/less/common.less';
+</style>
\ No newline at end of file
diff --git a/src/views/flowable/test_demo/modules/TestDemoForm.vue b/src/views/flowable/test_demo/modules/TestDemoForm.vue
new file mode 100644
index 0000000..b7dde82
--- /dev/null
+++ b/src/views/flowable/test_demo/modules/TestDemoForm.vue
@@ -0,0 +1,172 @@
+<template>
+  <a-spin :spinning="confirmLoading">
+    <j-form-container :disabled="formDisabled">
+      <a-form-model ref="form" :model="model" :rules="validatorRules" slot="detail">
+        <a-row>
+          <a-col :span="12">
+            <a-form-model-item label="鐢ㄦ埛鍚�" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="name">
+              <a-input v-model="model.name" placeholder="璇疯緭鍏ョ敤鎴峰悕"  ></a-input>
+            </a-form-model-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-model-item label="鎬у埆" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="sex">
+              <j-dict-select-tag type="list" v-model="model.sex" dictCode="sex" placeholder="璇烽�夋嫨鎬у埆" />
+            </a-form-model-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-model-item label="骞撮緞" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="age">
+              <a-input-number v-model="model.age" placeholder="璇疯緭鍏ュ勾榫�" style="width: 100%" />
+            </a-form-model-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-model-item label="鎻忚堪" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="descc">
+              <j-editor v-model="model.descc" />
+            </a-form-model-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-model-item label="鐢熸棩" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="birthday">
+              <j-date placeholder="璇烽�夋嫨鐢熸棩" v-model="model.birthday"  style="width: 100%" />
+            </a-form-model-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-model-item label="鐢ㄦ埛缂栫爜" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="userCode">
+              <a-input v-model="model.userCode" placeholder="璇疯緭鍏ョ敤鎴风紪鐮�"  ></a-input>
+            </a-form-model-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-model-item label="澶村儚" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="topPic">
+              <j-image-upload isMultiple  v-model="model.topPic" ></j-image-upload>
+            </a-form-model-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-model-item label="闄勪欢" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="fileKk">
+              <j-upload v-model="model.fileKk"   ></j-upload>
+            </a-form-model-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-model-item label="鍩庡競" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="chegnshi">
+             <j-area-linkage type="cascader" v-model="model.chegnshi" placeholder="璇疯緭鍏ョ渷甯傚尯"  />
+            </a-form-model-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-model-item label="寮圭獥" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="pop">
+              <a-input v-model="model.pop" placeholder="璇疯緭鍏ュ脊绐�"  ></a-input>
+            </a-form-model-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-model-item label="checkbox" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="ceck">
+              <j-multi-select-tag type="checkbox" v-model="model.ceck" dictCode="sex" placeholder="璇烽�夋嫨checkbox" />
+            </a-form-model-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-model-item label="涓嬫媺澶氶��" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="xiamuti">
+              <j-multi-select-tag type="list_multi" v-model="model.xiamuti" dictCode="sex" placeholder="璇烽�夋嫨涓嬫媺澶氶��" />
+            </a-form-model-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-model-item label="鎼滅储涓嬫媺" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="searchSel">
+              <j-search-select-tag v-model="model.searchSel" dict="sys_role,role_name,role_code"  />
+            </a-form-model-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-model-item label="涓嬫媺瀛楀吀琛�" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="selTable">
+              <j-search-select-tag v-model="model.selTable" dict="sys_user,realname,username"  />
+            </a-form-model-item>
+          </a-col>
+        </a-row>
+      </a-form-model>
+    </j-form-container>
+  </a-spin>
+</template>
+
+<script>
+
+  import { httpAction, getAction } from '@/api/manage'
+  import { validateDuplicateValue } from '@/utils/util'
+
+  export default {
+    name: 'TestDemoForm',
+    components: {
+    },
+    props: {
+      //琛ㄥ崟绂佺敤
+      disabled: {
+        type: Boolean,
+        default: false,
+        required: false
+      }
+    },
+    data () {
+      return {
+        model:{
+         },
+        labelCol: {
+          xs: { span: 24 },
+          sm: { span: 5 },
+        },
+        wrapperCol: {
+          xs: { span: 24 },
+          sm: { span: 16 },
+        },
+        confirmLoading: false,
+        validatorRules: {
+           name: [
+              { required: true, message: '璇疯緭鍏ョ敤鎴峰悕!'},
+           ],
+        },
+        url: {
+          add: "/test_demo/testDemo/add",
+          edit: "/test_demo/testDemo/edit",
+          queryById: "/test_demo/testDemo/queryById"
+        }
+      }
+    },
+    computed: {
+      formDisabled(){
+        return this.disabled
+      },
+    },
+    created () {
+       //澶囦唤model鍘熷鍊�
+      this.modelDefault = JSON.parse(JSON.stringify(this.model));
+    },
+    methods: {
+      add () {
+        this.edit(this.modelDefault);
+      },
+      edit (record) {
+        this.model = Object.assign({}, record);
+        this.visible = true;
+      },
+      submitForm () {
+        const that = this;
+        // 瑙﹀彂琛ㄥ崟楠岃瘉
+        this.$refs.form.validate(valid => {
+          if (valid) {
+            that.confirmLoading = true;
+            let httpurl = '';
+            let method = '';
+            if(!this.model.id){
+              httpurl+=this.url.add;
+              method = 'post';
+            }else{
+              httpurl+=this.url.edit;
+               method = 'put';
+            }
+            httpAction(httpurl,this.model,method).then((res)=>{
+              if(res.success){
+                that.$message.success(res.message);
+                that.$emit('ok');
+              }else{
+                that.$message.warning(res.message);
+              }
+            }).finally(() => {
+              that.confirmLoading = false;
+            })
+          }
+         
+        })
+      },
+    }
+  }
+</script>
\ No newline at end of file
diff --git a/src/views/flowable/test_demo/modules/TestDemoModal.vue b/src/views/flowable/test_demo/modules/TestDemoModal.vue
new file mode 100644
index 0000000..9fd874f
--- /dev/null
+++ b/src/views/flowable/test_demo/modules/TestDemoModal.vue
@@ -0,0 +1,60 @@
+<template>
+  <j-modal
+    :title="title"
+    :width="width"
+    :visible="visible"
+    switchFullscreen
+    @ok="handleOk"
+    :okButtonProps="{ class:{'jee-hidden': disableSubmit} }"
+    @cancel="handleCancel"
+    cancelText="鍏抽棴">
+    <test-demo-form ref="realForm" @ok="submitCallback" :disabled="disableSubmit"></test-demo-form>
+  </j-modal>
+</template>
+
+<script>
+
+  import TestDemoForm from './TestDemoForm'
+  export default {
+    name: 'TestDemoModal',
+    components: {
+      TestDemoForm
+    },
+    data () {
+      return {
+        title:'',
+        width:896,
+        visible: false,
+        disableSubmit: false
+      }
+    },
+    methods: {
+      add () {
+        this.visible=true
+        this.$nextTick(()=>{
+          this.$refs.realForm.add();
+        })
+      },
+      edit (record) {
+        this.visible=true
+        this.$nextTick(()=>{
+          this.$refs.realForm.edit(record);
+        })
+      },
+      close () {
+        this.$emit('close');
+        this.visible = false;
+      },
+      handleOk () {
+        this.$refs.realForm.submitForm();
+      },
+      submitCallback(){
+        this.$emit('ok');
+        this.visible = false;
+      },
+      handleCancel () {
+        this.close()
+      }
+    }
+  }
+</script>
\ No newline at end of file

--
Gitblit v1.9.3