lyh
2025-02-24 844bf54fc403cef99f901ee6a9a3c57d7c840036
新增flowable
已添加14个文件
已修改2个文件
2064 ■■■■■ 文件已修改
package.json 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/flowable/api/definition.js 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/flowable/api/finished.js 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/flowable/api/process.js 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/flowable/api/todo.js 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/flowable/components/ActApplyBtn.vue 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/flowable/components/ActCancelBtn.vue 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/flowable/components/ActHandleBtn.vue 235 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/flowable/components/ActHistoricDetailBtn.vue 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/flowable/components/HistoricDetail.vue 343 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/flowable/mixin/FlowableMixin.js 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/flowable/modeler/modelerDesign.vue 400 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/flowable/test_demo/TestDemoList.vue 311 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/flowable/test_demo/modules/TestDemoForm.vue 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/flowable/test_demo/modules/TestDemoModal.vue 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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"
  },
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)
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
  })
}
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',
  })
}
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',
  })
}
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
  })
}
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>
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>
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>
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>
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 æ­£å¸¸æ„è§  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>
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){
      // æœ‰å®žä¾‹id就能查看
      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;
      }
    },
  }
}
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>
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>
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>
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>