Compare commits

...

2 Commits

18 changed files with 623 additions and 842 deletions

View File

@ -1,7 +1,7 @@
// WFTask
import { defHttp } from '@/utils/http/axios';
import {MyUncompletedParams} from './model/WFTaskModel'
import {MyUncompletedParams,TaskDetailParam} from './model/WFTaskModel'
enum Api {
// 我的待办
@ -12,6 +12,7 @@ import {MyUncompletedParams} from './model/WFTaskModel'
LoadMyReadPage='/api/WFTask/LoadMyReadPage',
// 我的委托
LoadMyDelegatePage='/api/WFTask/LoadMyDelegatePage',
GetTaskDetail='/api/WFTask/Get',
}
/**
@ -37,4 +38,10 @@ import {MyUncompletedParams} from './model/WFTaskModel'
*/
export function getLoadMyDelegatePage(params?: MyUncompletedParams) {
return defHttp.get({ url: Api.LoadMyDelegatePage, params });
}
}
/**
* @description: GetTaskDetail
*/
export function getTaskDetail(params?: TaskDetailParam) {
return defHttp.get({ url: Api.GetTaskDetail, params });
}

View File

@ -7,4 +7,7 @@
limit: number;
startDate: string;
endDate: string;
}
export interface TaskDetailParam {
id: string;
}

View File

@ -21,7 +21,7 @@
<a-tab-pane key="1" tab="自定义表单">
<a-space direction="vertical" size="middle" class="site-space-compact-wrapper">
<a-space-compact block>
<a-input v-model:value="formData.formCode" placeholder="请选择表单" readonly />
<a-input v-model:value="node.formCode" placeholder="请选择表单" readonly />
<a-button >选择</a-button>
</a-space-compact>
<a-space-compact block>

View File

@ -14,6 +14,10 @@
<a-button :disabled="data.defaultZoom >= 3.9" :icon="h(ZoomInOutlined)" @click="processZoomIn()">
</a-button>
</a-tooltip>
<div class="ml-2 tag-box">
<a-tag color="processing">正在审核</a-tag>
<a-tag color="success">已审核</a-tag>
</div>
</a-space>
</div>
@ -22,6 +26,7 @@
</div>
</div>
<div class="my-process-designer__container">
<div class="my-process-designer__canvas" ref="bpmn-canvas" id="view"></div>
</div>
</div>
@ -33,6 +38,8 @@
import { SaveOutlined, ZoomOutOutlined, ZoomInOutlined, RotateLeftOutlined, RotateRightOutlined, ClearOutlined } from '@ant-design/icons-vue';
import BpmnViewer from 'bpmn-js/lib/Viewer'
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
const emit = defineEmits(['event', 'element-click']);
const data = reactive({
bpmnModeler: null,
defaultZoom: 1,
@ -40,7 +47,7 @@
const props = defineProps({
xml: String,
flowViewer:{
flowViewer: {
type: Object,
default: () => ({
finishedTaskSet: [],
@ -48,7 +55,11 @@
unfinishedTaskSet: [],
rejectedTaskSet: [],
})
}
},
events: {
type: Array,
default: () => ["element.click"]
},
})
watch(
() => props.xml,
@ -71,6 +82,19 @@
MoveCanvasModule
],
})
initModelListeners()
}
function initModelListeners() {
const EventBus = data.bpmnModerler.get("eventBus");
// , . - ,
props.events.forEach(event => {
EventBus.on(event, function (eventObj) {
let eventName = event.replace('.', "-");
let element = eventObj ? eventObj.element : null;
emit(eventName, element, eventObj);
emit('event', eventName, element, eventObj);
});
});
}
async function createDiagram(xml) {
const viewer = data.bpmnModerler
@ -103,6 +127,7 @@
//
function setNodeColor() {
const elementRegistry = data.bpmnModerler.get('elementRegistry')
console.log(elementRegistry)
let { finishedTaskSet, rejectedTaskSet, unfinishedTaskSet, finishedSequenceFlowSet } = props.flowViewer
if (Array.isArray(finishedSequenceFlowSet)) {
finishedSequenceFlowSet.forEach(item => {
@ -120,6 +145,7 @@
}
})
}
console.log(unfinishedTaskSet)
if (Array.isArray(unfinishedTaskSet)) {
unfinishedTaskSet.forEach(item => {
if (elementRegistry._elements[item]) {
@ -161,13 +187,22 @@
function getOperationTagType(type) {
return 'success';
}
</script>
<style lang="less" scoped>
.my-process-designer {
width: 100%;
}
.tag-box{
float: right;
}
::v-deep .ant-tag-success{
padding: 5px 11px;
}
::v-deep .ant-tag-processing{
padding: 5px 11px;
}
::v-deep .bjs-container a {
visibility: hidden;
}

View File

@ -180,6 +180,7 @@
"workflow":{
"scheme_preview":"流程模板设计",
"create_preview":"流程发起",
"task_preview":"流程详情"
"task_audit_preview":"审核节点",
"task_look_preview":"查看流程"
}
}

View File

@ -48,17 +48,29 @@ const dashboard: AppRouteModule = {
component: () => import('@/views/demo/workflow/create/preview.vue'),
},
{
path: 'task_preview/:id',
name: 'TaskPreview',
path: 'task_audit_preview/:id',
name: 'TaskAuditPreview',
meta: {
hideMenu: true,
title: t('routes.demo.workflow.task_preview'),
title: t('routes.demo.workflow.task_audit_preview'),
ignoreKeepAlive: true,
showMenu: false,
currentActiveMenu: '/workflow/create',
currentActiveMenu: '/workflow/task',
},
component: () => import('@/views/demo/workflow/task/detail.vue'),
}
component: () => import('@/views/demo/workflow/task/process/audit.vue'),
},
{
path: 'task_look_preview/:id',
name: 'TaskLookPreview',
meta: {
hideMenu: true,
title: t('routes.demo.workflow.task_look_preview'),
ignoreKeepAlive: true,
showMenu: false,
currentActiveMenu: '/workflow/task',
},
component: () => import('@/views/demo/workflow/task/process/look.vue'),
},
],
};

38
src/utils/base.ts Normal file
View File

@ -0,0 +1,38 @@
/**
*
*/
export function formatDate(v, format) {
if (!v) return "";
var d = v;
if (typeof v === 'string') {
if (v.indexOf("/Date(") > -1)
d = new Date(parseInt(v.replace("/Date(", "").replace(")/", ""), 10));
else
d = new Date(Date.parse(v.replace(/-/g, "/").replace("T", " ").split(".")[0]));//.split(".")[0] 用来处理出现毫秒的情况,截取掉.xxx否则会出错
}
var o = {
"M+": d.getMonth() + 1, //month
"d+": d.getDate(), //day
"h+": d.getHours(), //hour
"H+": d.getHours(), //hour
"m+": d.getMinutes(), //minute
"s+": d.getSeconds(), //second
"q+": Math.floor((d.getMonth() + 3) / 3), //quarter
"S": d.getMilliseconds() //millisecond
};
if (/(y+)/.test(format)) {
format = format.replace(RegExp.$1, (d.getFullYear() + "").substr(4 - RegExp.$1.length));
}
for (var k in o) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
}
}
return format;
}
export function dateFormat(date,format = 'yyyy-MM-dd hh:mm:ss'){
return formatDate(date,format);
}

View File

@ -15,7 +15,7 @@
</BasicTable>
</BasicDrawer>
<a-modal width="90%" height="80%" v-model:open="postOpen" title="流程模板预览" @ok="postHandleOk">
<a-modal width="70%" height="80%" v-model:open="postOpen" title="流程模板预览" @ok="postHandleOk">
<process-designer :key="designerOpen" style="border:1px solid rgba(0, 0, 0, 0.1);" ref="modelDesigner"
v-loading="designerData.loading" :pageFlow="flowContent" :pageView="content" :pageType="'detail'"
@save="onSaveDesigner" />

View File

@ -111,7 +111,7 @@
},
});
function handleDetail(record){
go('/dashboard/task_preview/detail?id=' + record.id);
go('/dashboard/task_look_preview/detail?processId=' + record.id);
}
function handleRevocation(record){
console.log(record)

View File

@ -107,7 +107,7 @@
},
});
function handleDetail(record){
go('/dashboard/task_preview/detail?id=' + record.id);
go('/dashboard/task_look_preview/detail?processId=' + record.id);
}
function handleRevocation(record){
console.log(record)

View File

@ -1,109 +0,0 @@
<template>
<PageWrapper :class="prefixCls">
<a-tabs v-model:activeKey="activeName">
<a-tab-pane key="form" tab="表单信息">
</a-tab-pane>
<a-tab-pane key="flow" tab="流程信息" force-render>
<div class="process-design" :style="'display: flex; height:' + designerData.height">
<process-viewer :key="`designer-${id}`" :xml="flowContent" :flowViewer="flowViewer"/>
</div>
</a-tab-pane>
</a-tabs>
</PageWrapper>
</template>
<script lang="ts" setup>
import { h, ref, provide, reactive, onMounted, defineProps, computed, defineEmits, onBeforeMount } from 'vue';
import ProcessViewer from '@/components/ProcessViewer/index.vue';
import { PageWrapper } from '@/components/Page';
import { SendOutlined, SaveOutlined, CloseCircleOutlined, ZoomInOutlined, RotateLeftOutlined, RotateRightOutlined, ClearOutlined } from '@ant-design/icons-vue';
import { getBPMN } from '@/api/sys/WFProcess'
import { getLoadMyUserList } from '@/api/sys/WFDelegate'
import { useRoute } from 'vue-router'
import { useMultipleTabStore } from '@/store/modules/multipleTab';
import { useRouter } from 'vue-router';
import { buildGUID } from '@/utils/uuid';
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm, createMessage } = useMessage();
const prefixCls = 'preview-box'
const tabStore = useMultipleTabStore();
const router = useRouter();
const content = ref('')
const flowContent = ref('')
const flowViewer = ref({})
const route = useRoute()
const id = route.query.id
const designerOpen = ref(false)
const formRef = ref();
const labelCol = { span: 7 };
const wrapperCol = { span: 13 };
const designerData = reactive({
loading: false,
xmlString: '',
controlForm: {
prefix: 'flowable',
},
height: document.documentElement.clientHeight - 230.5 + "px;",
midVisible: false,
isCustmerTitle: false,
nodeUsers: [],
selectUsersVisible: false,
isDraft: false,
delegateUsers: []
})
const activeName = ref('flow')
async function getDetailInfo() {
let data = await getBPMN({ id: id })
flowContent.value = data.flowContent
flowViewer.value = data.flowViewer
// var result = {
// // xml
// flowContent: "",
// // id
// flowViewer: {
// // id
// finishedTaskSet: [],
// // 线id
// finishedSequenceFlowSet: [],
// // id
// unfinishedTaskSet: [],
// // id
// rejectedTaskSet: [],
// }
// //
// }
}
function closePreview() {
if (!id) {
tabStore.closeTabByKey('/dashboard/task_preview/detail', router);
} else {
// /dashboard/create_preview/add?id=1
tabStore.closeTabByKey('/dashboard/task_preview/detail?id=' + id, router);
}
}
onBeforeMount(() => {
getDetailInfo()
})
</script>
<style lang="less" scoped>
.preview-box {
background-color: @component-background;
.btn-box {
padding: 10px;
justify-content: flex-end;
display: flex;
}
}
.form-box {
width: 480px;
}
</style>

View File

@ -110,7 +110,7 @@
}
}
function handleDetail(record) {
go('/dashboard/task_preview/detail?id=' + record.id);
go('/dashboard/task_look_preview/detail?processId=' + record.id);
}
</script>

View File

@ -122,7 +122,7 @@
},
});
function handleDetail(record) {
go('/dashboard/task_preview/detail?id=' + record.id);
go('/dashboard/task_look_preview/detail?processId=' + record.id);
}
async function handleUrge(record) {
var query = {

View File

@ -1,575 +1,293 @@
<template>
<l-fullscreen-dialog
:title="`${title}`"
:visible.sync="midVisible"
:showOk="false"
<PageWrapper :class="prefixCls">
<a-tabs v-model:activeKey="activeName">
<a-tab-pane key="form" tab="表单信息">
@closed="handleClosed"
@opened="handleOpened"
</a-tab-pane>
<a-tab-pane key="flow" tab="流程信息(审核)" force-render>
<div class="process-design" :style="'display: flex; height:' + designerData.height">
<process-viewer :key="`designer-${id}`" :events="[
'element.click',
]" @element-click="elementClick" :xml="flowContent" :flowViewer="flowViewer" />
<div class="info-box" v-if="designerData.nodeLogs.length>0" >
<a-drawer v-model:open="infoOpen" class="custom-class" title="记录信息" placement="right">
<a-timeline>
<a-timeline-item v-for="(item,index) in designerData.nodeLogs" :key="index">
<div class="title">{{item.time}}</div>
<a-card hoverable size="small">
<div class="type-title">{{item.name}}</div>
<div class="content">
<span class="link" v-for="(userName,index2) in item.userNames">{{userName}}</span>
<!-- <l-user v-for="(userId,index2) in item.userIds" :key="index2" :value="userId" ></l-user> -->
{{item.des}}
</div>
</a-card>
</a-timeline-item>
</a-timeline>
</a-drawer>
ref="formDialog"
>
</div>
</div>
</a-tab-pane>
</a-tabs>
<template #headerRight v-if="isRead && task && task.f_State == 1" >
<el-button size="mini" type="primary" @click="handleRead" >{{$t('确认阅读')}}</el-button>
</template>
</PageWrapper>
<l-layout class="l-tab-page" :right="400">
<l-panel :style="{'padding-right':isRead?'':0 }" >
<div class="l-auto-window" style="padding: 0 8px;" >
<el-tabs v-model="activeName" @tab-click="handleTabClick" >
<el-tab-pane v-if="hasWfForm" :label="$t('表单信息')" name="form">
<div class="l-rblock" v-loading="formSchemeLoding">
<template v-if="showForm" >
<l-form-viewer
v-if="formType == '1'"
:formInfo="formInfo"
:isWfForm="true"
:authFieldsMap="formAuthFieldsMap"
ref="wfForm"
></l-form-viewer>
<component ref="wfForm" v-else :requiredMap="formRequiredMap" :authFieldsMap="formAuthFieldsMap" :isWfForm="true" :is="sysFormComponent"></component>
</template>
</div>
</el-tab-pane>
<el-tab-pane :label="$t('流程信息')" name="wfinfo">
<l-layout style="background: #f1f2f5;" :right="320">
<l-panel class="flow-panel" style="padding:0;padding-top:0;" >
<template #title>
<el-button-group>
<el-tooltip effect="dark" :content="$t('复原')" placement="bottom">
<el-button size="mini" icon="el-icon-aim" @click="resetZoom"></el-button>
</el-tooltip>
<el-tooltip effect="dark" :content="$t('放大')" placement="bottom">
<el-button size="mini" icon="el-icon-zoom-in" @click="handlerZoom(0.1)"></el-button>
</el-tooltip>
<el-tooltip effect="dark" :content="$t('缩小')" placement="bottom">
<el-button size="mini" icon="el-icon-zoom-out" @click="handlerZoom(-0.1)"></el-button>
</el-tooltip>
</el-button-group>
<div style="float:right;" >
<el-tag size="small" effect="plain" style="margin-right: 8px;">正在审核</el-tag>
<el-tag size="small" effect="plain" style="margin-right: 8px;" type="success">已审核</el-tag>
</div>
</template>
<b-wflow-viewer
ref="bflow"
@elementClick="elementClick"
>
</b-wflow-viewer>
</l-panel>
<template #right >
<l-panel v-if="nodeLogs.length>0" class="flow-panel" style="padding:0;padding-top:0;" >
<template #title >
记录信息
</template>
<div class="l-rblock l-time-line-wraper" style="padding:8px;overflow:hidden auto;" >
<el-timeline>
<el-timeline-item :type="item.type" v-for="(item,index) in nodeLogs" :key="index" :timestamp="item.time" placement="top">
<el-card shadow="hover">
<div class="title" >{{item.name}}</div>
<div class="content">
<l-user v-for="(userId,index2) in item.userIds" :key="index2" :value="userId" ></l-user>
{{item.des}}
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
</l-panel>
</template>
</l-layout>
</el-tab-pane>
<el-tab-pane :label="$t('流转记录')" name="wflogs">
<div class="l-rblock l-time-line-wraper" style="padding:8px;overflow:hidden auto;" >
<el-timeline>
<el-timeline-item :type="item.type" v-for="(item,index) in logs" :key="index" :timestamp="item.time" placement="top">
<el-card shadow="hover">
<div class="title" >{{item.name}}</div>
<div class="content">
<l-user v-for="(userId,index2) in item.userIds" :key="index2" :value="userId" ></l-user>
{{item.des}}
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
</el-tab-pane>
</el-tabs>
</div>
</l-panel>
<template #right v-if="!isRead">
<l-panel style="padding-left:0;" title="审批栏" >
<div class="l-rblock" style="padding:8px;" >
<el-form :model="formData" :rules="myRules" size="mini" ref="form" >
<el-form-item :label="isCreateAgain?'备注':'审批意见'" prop="des">
<el-input
type="textarea"
v-model="formData.des"
placeholder="请输入"
rows="3"
>
</el-input >
</el-form-item>
<el-form-item class="l-task-btns" >
<el-button v-for="(btn,index) in taskBtns" :key="index" :type="btn.type" @click="handleBtnClick(btn)" >{{btn.name}}</el-button>
</el-form-item>
</el-form>
<l-wf-audit-info :data="userLogs" ></l-wf-audit-info>
</div>
</l-panel>
</template>
</l-layout>
<l-dialog
:width="500"
:height="nodeUsers.length * 48 + 88"
title="选择下一审核节点人员"
:visible.sync="selectUsersVisible"
@ok="handleSelectUsersSave"
@close="handleSelectUsersCloseForm"
:showClose="false"
>
<select-users ref="selectUsers" :nodeList="nodeUsers"></select-users>
</l-dialog>
<l-dialog
:title="$t(tUserType == 1?`选择转移人员`:`选择加签人员`)"
:visible.sync="selectTUserVisible"
:height="480"
width="1024px"
:hasBtns="false"
>
<l-user-select-panel @change="handleChange" :multiple="false" ref="userSelectPanel" ></l-user-select-panel>
</l-dialog>
<l-dialog
:title="$t('驳回节点选择')"
:visible.sync="selectRejectNodeVisible"
:height="136"
:width="500"
@ok="handleSelectRejectNodeSave"
@close="handleRejectNodeCloseForm"
:showClose="false"
>
<select-reject-node ref="selectRejectNode" :nodeList="completedNodes"></select-reject-node>
</l-dialog>
<!--选择签章-->
<l-dialog
:title="$t('选择签章')"
:visible.sync="selectSignVisible"
:height="480"
:width="508"
@ok="handleSelectSignSave"
@closed="handleSelectSignCloseForm"
@opened="handleSelectSignOpenForm"
>
<select-sign ref="SelectSign" ></select-sign>
</l-dialog>
</l-fullscreen-dialog>
</template>
<script>
import mixin from '../../../mixins/wf'
import SelectUsers from './selectAuditUsers.vue'
import SelectRejectNode from './selectRejectNode.vue'
import SelectSign from './selectSign.vue'
<script lang="ts" setup>
import { h, ref, provide, reactive, onMounted, defineProps, computed, defineEmits, onBeforeMount } from 'vue';
import ProcessViewer from '@/components/ProcessViewer/index.vue';
import { PageWrapper } from '@/components/Page';
import { SendOutlined, SaveOutlined, CloseCircleOutlined, ZoomInOutlined, RotateLeftOutlined, RotateRightOutlined, ClearOutlined } from '@ant-design/icons-vue';
import { getBPMN } from '@/api/sys/WFProcess'
import { getLoadMyUserList } from '@/api/sys/WFDelegate'
import { getTaskDetail } from '@/api/sys/WFTask'
import { useRoute } from 'vue-router'
import { useMultipleTabStore } from '@/store/modules/multipleTab';
import { useRouter } from 'vue-router';
import { buildGUID } from '@/utils/uuid';
import { useMessage } from '/@/hooks/web/useMessage';
import { dateFormat } from '@/utils/base'
const { createConfirm, createMessage } = useMessage();
const api = window.$api.workflow.process
const apiStamp = window.$api.workflow.stamp
export default {
mixins:[mixin()],
components:{
SelectUsers,
SelectRejectNode,
SelectSign
const prefixCls = 'preview-box'
const tabStore = useMultipleTabStore();
const router = useRouter();
const content = ref('')
const flowContent = ref('')
const flowViewer = ref({})
const route = useRoute()
const processId = route.query.processId
const taskId = route.query.taskId
const designerOpen = ref(false)
const formRef = ref();
const labelCol = { span: 7 };
const wrapperCol = { span: 13 };
const infoOpen = ref(true)
const designerData = reactive({
loading: false,
xmlString: '',
controlForm: {
prefix: 'flowable',
},
props:{
isCreateAgain:Boolean,
isRead:Boolean
},
data () {
return {
midVisible:false,
formData:{
des:''
},
rules:{
des:[
{ required: true, message: '请填写审批意见',trigger: 'blur' }
],
},
height: document.documentElement.clientHeight - 200.5 + "px;",
midVisible: false,
isCustmerTitle: false,
nodeUsers: [],
selectUsersVisible: false,
currentBtn:null,
nodeUsers:[],
selectUsersVisible:false,
selectTUserVisible:false,
tUserType:1, // 1 2 ,
selectRejectNodeVisible:false, //
selectSignVisible:false, //
stampList:[]
}
},
computed:{
myRules(){
if(this.isCreateAgain || this.isRead){
return {}
}
else{
return this.rules
}
},
completedNodes(){
const nodes = this.$deepClone(this.wfData)
return nodes.filter(t=>t.hasFinish && t.id != this.currentNode.id)
},
},
watch:{
visible: {
handler (n) {
this.midVisible = n
}
},
},
methods:{
resetZoom(){
this.$refs.bflow.reset()
},
handlerZoom(r){
this.$refs.bflow.handlerZoom(r)
},
elementClick(node){
if(node){
this.nodeLogs = this.nodeMap[node.id] || []
}
else{
this.nodeLogs = []
}
},
resetFormToMe(){
this.$refs.form && this.$refs.form.resetFields()
this.taskBtns = []
},
setForm(){
this.$refs.form && this.$refs.form.clearValidate()
this.currentNode = this.wfData.find(t=>t.id == this.task.f_UnitId)
//
const btns = []
if(this.currentNode.type =='startEvent'){
btns.push({
code:'learun_create',
name:'提交',
type:'primary'
})
}
else{
this.currentNode.btnlist.forEach(btn => {
if(btn.code == 'agree'){
btn.type = 'primary'
}
else if(btn.code == 'disagree'){
btn.type = 'danger'
}
btns.push(btn)
})
if(this.currentNode.isAddSign){
btns.push({
code:'learun_sign',
name:'加签',
type:'success'
})
}
if(this.currentNode.isTransfer){
btns.push({
code:'learun_transfer',
name:'转移',
type:'success'
})
}
}
this.taskBtns = btns
},
validateForm(){
return this.$formValidateWraper(this.$refs.form)
},
handleBtnClick(btn){
this.$refs.formDialog.showLoading('流程处理中...')
this.$nextTick(async ()=>{
this.currentBtn = btn
if(!(await this.validateForm())){
this.$refs.formDialog.hideLoading()
return
}
if(!(await this.validateWfForm())){
this.$refs.formDialog.hideLoading()
return
}
if(!(await this.saveWfForm(btn.code))){
this.$refs.formDialog.hideLoading()
return
}
let res
switch(btn.code){
case 'learun_create':
res = await this.$awaitWraper(api.createAgain({
processId:this.processId,
des:`重新提交${this.formData.des?'-':''}${this.formData.des}`
}))
break
case 'learun_sign':
this.tUserType = 2
this.selectTUserVisible = true
break
case 'learun_transfer':
this.tUserType = 1
this.selectTUserVisible = true
break
default:
if(this.task.f_Type == 6){//
res = await this.$awaitWraper(api.signAudit(this.taskId,{
code:btn.code,
name:btn.name,
des:this.formData.des
}))
}
else {
if(this.currentNode.rejectType == '2' && btn.code == 'disagree'){
this.selectRejectNodeVisible = true
this.$refs.formDialog.hideLoading()
return
}
if(btn.isSign){
//
const stampList = await this.$awaitWraper(apiStamp.getList(this.loginInfo.f_UserId))
if(stampList && stampList.length >0){
this.stampList = stampList
this.selectSignVisible = true
this.$refs.formDialog.hideLoading()
return
}
}
res = await this.audit()
}
break
}
this.$refs.formDialog.hideLoading()
if(res){
this.midVisible = false
this.$emit('refresh')
}
})
},
async audit(){
//
if(this.currentBtn.isNextAuditor){
const res = await api.getNextUsers({processId:this.processId,nodeId:this.currentNode.id})
const nodeUserMap = res.data.data
const nodeUsers = []
for(let key in nodeUserMap){
const nodeUserItem = nodeUserMap[key]
if(nodeUserItem.length > 1){
nodeUsers.push({
name:this.wfData.find(t=>t.id == key).name,
id:key,
options:nodeUserItem.map(t=>{return{value:t.id,label:t.name} })
})
}
}
this.nodeUsers = nodeUsers
if(this.nodeUsers.length > 0){
this.selectUsersVisible = true
return
}
}
return await this.$awaitWraper(api.audit(this.taskId,{
code:this.currentBtn.code,
name:this.currentBtn.name,
des:this.formData.des
}))
},
handleSelectUsersSave(){
this.selectUsersVisible = false
this.$refs.formDialog.showLoading('流程处理中...')
const nextUsers = this.$refs.selectUsers.getForm()
this.$nextTick(async ()=>{
const res = await this.$awaitWraper(api.audit(this.taskId,{
code:this.currentBtn.code,
name:this.currentBtn.name,
nextUsers: nextUsers,
des:this.formData.des
}))
if(res){
this.midVisible = false
}
this.$refs.formDialog.hideLoading()
this.$emit('refresh')
})
},
handleSelectUsersCloseForm(){
this.$refs.selectUsers.resetForm()
},
handleChange(userInfo){
if(userInfo){
const title = this.tUserType == 1?'转移':'加签'
if(this.loginInfo.f_UserId == userInfo.f_UserId){
this.$message({
type: 'warning',
message: `${title}不能给自己本人`
})
return
}
this.$confirm(`是否确定${title}${userInfo.f_RealName}?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.selectTUserVisible = false
this.$refs.formDialog.showLoading('流程处理中...')
this.$nextTick(async ()=>{
let res
if(this.tUserType == 1){
res = await this.$awaitWraper(api.transferUser(this.taskId,{
toUserId:userInfo.f_UserId,
des:`转移给${userInfo.f_RealName}${this.formData.des?'-':''}${this.formData.des}`
}))
}
else{
res = await this.$awaitWraper(api.sign(this.taskId,{
toUserId:userInfo.f_UserId,
des:`加签给${userInfo.f_RealName}${this.formData.des?'-':''}${this.formData.des}`
}))
}
this.$refs.formDialog.hideLoading()
if(res){
this.midVisible = false
this.$emit('refresh')
}
})
}).catch(() => {
this.$message({
type: 'info',
message: `已取消${title}`
})
})
}
},
async handleSelectRejectNodeSave(){
if(await this.$refs.selectRejectNode.validateForm()){
this.$refs.formDialog.showLoading('流程处理中...')
const nextId = this.$refs.selectRejectNode.getForm()
this.selectRejectNodeVisible = false
this.$nextTick(async ()=>{
const res = await this.$awaitWraper(api.audit(this.taskId,{
code:this.currentBtn.code,
name:this.currentBtn.name,
nextId: nextId,
des:this.formData.des
}))
if(res){
this.midVisible = false
}
this.$refs.formDialog.hideLoading()
this.$emit('refresh')
})
}
},
handleRejectNodeCloseForm(){
this.$refs.selectRejectNode.resetForm()
},
async handleRead(){
this.$refs.formDialog.showLoading('流程处理中...')
const res = await this.$awaitWraper(api.readAudit(this.taskId))
if(res){
this.midVisible = false
}
this.$refs.formDialog.hideLoading()
this.$emit('refresh')
},
//
async handleSelectSignSave(){
if(await this.$refs.SelectSign.validateForm()){
this.$refs.formDialog.showLoading('流程处理中...')
const {f_StampId,f_Password} = this.$refs.SelectSign.getForm()
this.selectSignVisible = false
this.$nextTick(async ()=>{
const res = await this.$awaitWraper(api.audit(this.taskId,{
code:this.currentBtn.code,
name:this.currentBtn.name,
stampImg:f_StampId,
stampPassWord:f_Password,
des:this.formData.des
}))
if(res){
this.midVisible = false
}
this.$refs.formDialog.hideLoading()
this.$emit('refresh')
})
}
},
handleSelectSignCloseForm(){
this.$refs.SelectSign.resetForm()
},
handleSelectSignOpenForm(){
this.$refs.SelectSign.setForm(this.stampList)
}
isDraft: false,
delegateUsers: [],
task: null,
process: null,
logs: [],//
nodeMap: {},//
userLogs: [],//
nodeLogs: [],
taskBtns: [],
})
const activeName = ref('flow')
function elementClick(element) {
console.log('elementClick')
console.log(element)
if (element) {
designerData.nodeLogs = designerData.nodeMap[element.id] || []
infoOpen.value = true
}
}
else {
designerData.nodeLogs = []
}
}
async function getDetailInfo() {
let data = await getBPMN({ id: processId })
flowContent.value = data.flowContent
flowViewer.value = data.flowViewer
}
async function getTaskInfo() {
let data = await getTaskDetail({ id: taskId })
designerData.process = data.process
designerData.task = data.task
setLogsAndTasks(data.logs, data.tasks)
}
function setLogsAndTasks(logs, tasks) {
const res = []
const taskMap = {}
const nodeMap = {}
const userLogs = []
tasks.forEach(task => {
nodeMap[task.unitId] = nodeMap[task.unitId] || [{
unitId: task.unitId,
name: task.unitName,
userIds: [],
userNames: [],
des: '正在审核',
time: `当前-创建时间:${dateFormat(task.createDate)}`,
type: 'primary',
isFinish: false
}]
if (task.type == 2) {
taskMap[task.unitId + task.type] = taskMap[task.unitId + task.type] || {
unitId: task.unitId,
name: task.unitName,
userIds: [],
userNames: [],
des: '正在查阅',
time: `当前-创建时间:${dateFormat(task.createDate)}`,
type: 'primary'
}
taskMap[task.unitId + task.type].userIds.push(task.userId)
taskMap[task.unitId + task.type].userNames.push(task.userName)
if (nodeMap[task.unitId].length == 1) {
nodeMap[task.unitId].push({
unitId: task.unitId,
name: task.unitName,
userIds: [],
userNames: [],
des: '正在查阅',
time: `当前-创建时间:${dateFormat(task.createDate)}`,
type: 'primary',
isFinish: true
})
}
nodeMap[task.unitId][1].userIds.push(task.userId)
nodeMap[task.unitId][1].userNames.push(task.userName)
}
else {
taskMap[task.unitId] = taskMap[task.unitId] || {
unitId: task.unitId,
name: task.unitName,
userIds: [],
userNames: [],
des: '正在审核',
time: `当前-创建时间:${dateFormat(task.createDate)}`,
type: 'primary'
}
taskMap[task.unitId].userIds.push(task.userId)
nodeMap[task.unitId][0].userIds.push(task.userId)
taskMap[task.unitId].userNames.push(task.userName)
nodeMap[task.unitId][0].userNames.push(task.userName)
}
})
for (let key in taskMap) {
res.push(taskMap[key])
}
for (let key in nodeMap) {
nodeMap[key] = nodeMap[key].filter(t => t.userIds.length > 0)
}
logs.forEach(log => {
res.push({
unitId: log.unitId,
name: log.unitName,
userIds: [log.userId],
userNames: [log.userName],
des: log.des ? log.des : log.f_OperationName,
time: dateFormat(log.createDate),
type: 'info'
})
nodeMap[log.unitId] = nodeMap[log.unitId] || []
nodeMap[log.unitId].push({
unitId: log.unitId,
name: log.unitName,
userIds: [log.userId],
userNames: [log.userName],
time: dateFormat(log.createDate),
des: log.des ? log.des : log.f_OperationName,
type: 'info',
isFinish: true
})
if (log.f_TaskType == 1 && !['sign'].includes(log.f_OperationCode)) { //
const userLogIndex = userLogs.findIndex(t => t.id == log.unitId)
if (userLogIndex == -1) {
userLogs.push({
id: log.unitId,
name: log.unitName,
user: log.f_UserName,
time: dateFormat(log.createDate),
des: log.des,
img: log.f_StampImg
})
}
}
})
designerData.logs = res
designerData.nodeMap = nodeMap
designerData.userLogs = userLogs.sort(function (a, b) {
return b.time < a.time ? -1 : 1
})
//console.log(logs,'logs')
}
function closePreview() {
if (!id) {
tabStore.closeTabByKey('/dashboard/task_preview/detail', router);
} else {
// /dashboard/create_preview/add?id=1
tabStore.closeTabByKey('/dashboard/task_preview/detail?id=' + id, router);
}
}
onBeforeMount(() => {
getDetailInfo()
getTaskInfo()
})
</script>
<style lang="less" scoped>
.info-box {
display: inline-block;
width: 300px;
position: absolute;
right: 0;
margin-top: 40px;
<style lang="scss">
.l-task-btns{
.el-button{
margin: 0;
margin-top: 8px;
margin-right: 8px;
}
}
.ant-timeline-item-content {
.title {
color: #909399;
line-height: 1;
margin-bottom: 8px;
font-size: 13px;
}
.type-title {
font-size: 12px;
font-weight: bold;
margin-bottom: 8px;
}
.link{
color:#409EFF;
}
}
.preview-box {
background-color: @component-background;
height: 100%;
.btn-box {
padding: 10px;
justify-content: flex-end;
display: flex;
}
}
.form-box {
width: 480px;
}
</style>

View File

@ -1,153 +1,229 @@
<template>
<l-fullscreen-dialog
:title="`${$t('查看流程')}【${title}】`"
:visible.sync="midVisible"
:showOk="false"
@closed="handleClosed"
@opened="handleOpened"
ref="formDialog"
>
<l-layout class="l-tab-page" :right="400">
<l-panel :style="{ 'padding-right':userLogs.length >0?0:''}" >
<div class="l-auto-window" style="padding: 0 8px;" >
<el-tabs v-model="activeName" @tab-click="handleTabClick" >
<el-tab-pane v-if="hasWfForm" :label="$t('表单信息')" name="form">
<div class="l-rblock" v-loading="formSchemeLoding">
<template v-if="showForm" >
<l-form-viewer
v-if="formType == '1'"
:formInfo="formInfo"
:isWfForm="true"
:authFieldsMap="formAuthFieldsMap"
ref="wfForm"
></l-form-viewer>
<component ref="wfForm" v-else :requiredMap="formRequiredMap" :authFieldsMap="formAuthFieldsMap" :isWfForm="true" :is="sysFormComponent"></component>
</template>
</div>
</el-tab-pane>
<el-tab-pane :label="$t('流程信息')" name="wfinfo">
<l-layout style="background: #f1f2f5;" :right="320">
<l-panel class="flow-panel" style="padding:0;padding-top:0;" >
<template #title>
<el-button-group>
<el-tooltip effect="dark" :content="$t('复原')" placement="bottom">
<el-button size="mini" icon="el-icon-aim" @click="resetZoom"></el-button>
</el-tooltip>
<el-tooltip effect="dark" :content="$t('放大')" placement="bottom">
<el-button size="mini" icon="el-icon-zoom-in" @click="handlerZoom(0.1)"></el-button>
</el-tooltip>
<el-tooltip effect="dark" :content="$t('缩小')" placement="bottom">
<el-button size="mini" icon="el-icon-zoom-out" @click="handlerZoom(-0.1)"></el-button>
</el-tooltip>
</el-button-group>
<div style="float:right;" >
<el-tag size="small" effect="plain" style="margin-right: 8px;">正在审核</el-tag>
<el-tag size="small" effect="plain" style="margin-right: 8px;" type="success">已审核</el-tag>
</div>
</template>
<b-wflow-viewer
ref="bflow"
@elementClick="elementClick"
>
</b-wflow-viewer>
</l-panel>
<template #right >
<l-panel v-if="nodeLogs.length>0" class="flow-panel" style="padding:0;padding-top:0;" >
<template #title >
记录信息
</template>
<div class="l-rblock l-time-line-wraper" style="padding:8px;overflow:hidden auto;" >
<el-timeline>
<el-timeline-item :type="item.type" v-for="(item,index) in nodeLogs" :key="index" :timestamp="item.time" placement="top">
<el-card shadow="hover">
<div class="title" >{{item.name}}</div>
<div class="content">
<l-user v-for="(userId,index2) in item.userIds" :key="index2" :value="userId" ></l-user>
{{item.des}}
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
</l-panel>
</template>
</l-layout>
</el-tab-pane>
<el-tab-pane :label="$t('流转记录')" name="wflogs">
<div class="l-rblock l-time-line-wraper" style="padding:8px;overflow:hidden auto;" >
<el-timeline>
<el-timeline-item :type="item.type" v-for="(item,index) in logs" :key="index" :timestamp="item.time" placement="top">
<el-card shadow="hover">
<div class="title" >{{item.name}}</div>
<div class="content">
<l-user v-for="(userId,index2) in item.userIds" :key="index2" :value="userId" ></l-user>
{{item.des}}
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
</el-tab-pane>
</el-tabs>
</div>
</l-panel>
<template #right >
<l-panel v-if="userLogs.length >0" style="padding-left:0;" title="审批信息" >
<div class="l-rblock" style="padding:8px;" >
<l-wf-audit-info :data="userLogs" ></l-wf-audit-info>
</div>
</l-panel>
</template>
</l-layout>
</l-fullscreen-dialog>
</template>
<script>
import mixin from '../../../mixins/wf'
export default {
mixins:[mixin()],
data () {
return {
midVisible:false,
}
},
watch:{
visible: {
handler (n) {
this.midVisible = n
}
},
},
methods:{
resetZoom(){
this.$refs.bflow.reset()
},
handlerZoom(r){
this.$refs.bflow.handlerZoom(r)
},
elementClick(node){
if(node){
this.nodeLogs = this.nodeMap[node.id] || []
}
else{
this.nodeLogs = []
}
},
setForm(){
if(this.type == 'look' && this.task)
{
this.currentNode = this.wfData.find(t=>t.id == this.task.f_UnitId)
}
else{
this.currentNode = this.wfData.find(t=>t.type == 'startEvent')
}
}
<PageWrapper :class="prefixCls">
<a-tabs v-model:activeKey="activeName">
<a-tab-pane key="form" tab="表单信息">
</a-tab-pane>
<a-tab-pane key="flow" tab="流程信息" force-render>
<div class="process-design" :style="'display: flex; height:' + designerData.height">
<process-viewer :key="`designer-${id}`" :events="[
'element.click',
]" @element-click="elementClick" :xml="flowContent" :flowViewer="flowViewer" />
</div>
</a-tab-pane>
</a-tabs>
</PageWrapper>
</template>
<script lang="ts" setup>
import { h, ref, provide, reactive, onMounted, defineProps, computed, defineEmits, onBeforeMount } from 'vue';
import ProcessViewer from '@/components/ProcessViewer/index.vue';
import { PageWrapper } from '@/components/Page';
import { SendOutlined, SaveOutlined, CloseCircleOutlined, ZoomInOutlined, RotateLeftOutlined, RotateRightOutlined, ClearOutlined } from '@ant-design/icons-vue';
import { getBPMN } from '@/api/sys/WFProcess'
import { getLoadMyUserList } from '@/api/sys/WFDelegate'
import { getTaskDetail } from '@/api/sys/WFTask'
import { useRoute } from 'vue-router'
import { useMultipleTabStore } from '@/store/modules/multipleTab';
import { useRouter } from 'vue-router';
import { buildGUID } from '@/utils/uuid';
import { useMessage } from '/@/hooks/web/useMessage';
import {dateFormat} from '@/utils/base'
const { createConfirm, createMessage } = useMessage();
const prefixCls = 'preview-box'
const tabStore = useMultipleTabStore();
const router = useRouter();
const content = ref('')
const flowContent = ref('')
const flowViewer = ref({})
const route = useRoute()
const processId = route.query.processId
const taskId = route.query.taskId
const designerOpen = ref(false)
const formRef = ref();
const labelCol = { span: 7 };
const wrapperCol = { span: 13 };
const designerData = reactive({
loading: false,
xmlString: '',
controlForm: {
prefix: 'flowable',
},
height: document.documentElement.clientHeight - 230.5 + "px;",
midVisible: false,
isCustmerTitle: false,
nodeUsers: [],
selectUsersVisible: false,
isDraft: false,
delegateUsers: [],
task: null,
process: null,
logs: [],//
nodeMap: {},//
userLogs: [],//
nodeLogs: [],
taskBtns: [],
})
const activeName = ref('flow')
function elementClick(element) {
console.log('elementClick')
console.log(element)
}
}
</script>
async function getDetailInfo() {
let data = await getBPMN({ id: processId })
flowContent.value = data.flowContent
flowViewer.value = data.flowViewer
}
async function getTaskInfo() {
let data = await getTaskDetail({ id: taskId })
designerData.process = data.process
designerData.task = data.task
setLogsAndTasks(data.logs, data.tasks)
}
function setLogsAndTasks(logs, tasks) {
const res = []
const taskMap = {}
const nodeMap = {}
const userLogs = []
tasks.forEach(task => {
nodeMap[task.unitId] = nodeMap[task.unitId] || [{
unitId: task.unitId,
name: task.unitName,
userIds: [],
des: '正在审核',
time: `当前-创建时间:${dateFormat(task.createDate)}`,
type: 'primary',
isFinish: false
}]
if (task.type == 2) {
taskMap[task.unitId + task.type] = taskMap[task.unitId + task.type] || {
unitId: task.unitId,
name: task.unitName,
userIds: [],
des: '正在查阅',
time: `当前-创建时间:${dateFormat(task.createDate)}`,
type: 'primary'
}
taskMap[task.unitId + task.type].userIds.push(task.userId)
if (nodeMap[task.unitId].length == 1) {
nodeMap[task.unitId].push({
unitId: task.unitId,
name: task.unitName,
userIds: [],
des: '正在查阅',
time: `当前-创建时间:${dateFormat(task.createDate)}`,
type: 'primary',
isFinish: true
})
}
nodeMap[task.unitId][1].userIds.push(task.userId)
}
else {
taskMap[task.unitId] = taskMap[task.unitId] || {
unitId: task.unitId,
name: task.unitName,
userIds: [],
des: '正在审核',
time: `当前-创建时间:${dateFormat(task.createDate)}`,
type: 'primary'
}
taskMap[task.unitId].userIds.push(task.userId)
nodeMap[task.unitId][0].userIds.push(task.userId)
}
})
for (let key in taskMap) {
res.push(taskMap[key])
}
for (let key in nodeMap) {
nodeMap[key] = nodeMap[key].filter(t => t.userIds.length > 0)
}
logs.forEach(log => {
res.push({
unitId: log.unitId,
name: log.unitName,
userIds: [log.userId],
des: log.des ? log.des : log.f_OperationName,
time: dateFormat(log.createDate),
type: 'info'
})
nodeMap[log.unitId] = nodeMap[log.unitId] || []
nodeMap[log.unitId].push({
unitId: log.unitId,
name: log.unitName,
userIds: [log.userId],
time: dateFormat(log.createDate),
des: log.des ? log.des : log.f_OperationName,
type: 'info',
isFinish: true
})
if (log.f_TaskType == 1 && !['sign'].includes(log.f_OperationCode)) { //
const userLogIndex = userLogs.findIndex(t => t.id == log.unitId)
if (userLogIndex == -1) {
userLogs.push({
id: log.unitId,
name: log.unitName,
user: log.f_UserName,
time: dateFormat(log.createDate),
des: log.des,
img: log.f_StampImg
})
}
}
})
designerData.logs = res
designerData.nodeMap = nodeMap
designerData.userLogs = userLogs.sort(function (a, b) {
return b.time < a.time ? -1 : 1
})
//console.log(logs,'logs')
}
function closePreview() {
if (!id) {
tabStore.closeTabByKey('/dashboard/task_preview/detail', router);
} else {
// /dashboard/create_preview/add?id=1
tabStore.closeTabByKey('/dashboard/task_preview/detail?id=' + id, router);
}
}
onBeforeMount(() => {
getDetailInfo()
getTaskInfo()
})
</script>
<style lang="less" scoped>
.preview-box {
background-color: @component-background;
.btn-box {
padding: 10px;
justify-content: flex-end;
display: flex;
}
}
.form-box {
width: 480px;
}
</style>

View File

@ -107,7 +107,7 @@
},
});
function handleDetail(record){
go('/dashboard/task_preview/detail?id=' + record.id);
go('/dashboard/task_look_preview/detail?processId=' + record.id);
}
function handleRevocation(record){
console.log(record)

View File

@ -20,7 +20,7 @@
</template>
<script lang="ts" setup>
import { reactive } from 'vue'
import { reactive ,h} from 'vue'
import { BasicTable, useTable, TableAction } from '@/components/Table';
import { PageWrapper } from '@/components/Page';
import { getLoadMyUncompletedPage } from '@/api/sys/WFTask'
@ -103,7 +103,7 @@
},
});
function handleDetail(record){
go('/dashboard/task_preview/detail?id=' + record.id);
go('/dashboard/task_audit_preview/detail?processId=' + record.processId+"&taskId="+record.id);
}

View File

@ -129,7 +129,7 @@
const userInfo = await userStore.login({
password: data.password,
account: data.account,
mode: 'modal', //
mode: 'none', //
});
console.log(userInfo)
localStorage.setItem('fireUserLoginName',userInfo.name)