mobile-workflow/pages/instance/instancePreview.vue

594 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view>
<view class="fixedheader">
<headers :showBack="true" :themeType="'white'">
<view class="headerName">
流程详情
</view>
</headers>
</view>
<view v-if="instanceData">
<scroll-view class="w-instace" scroll-y show-scrollbar @scroll="scroll" :style="{height: contentHeight + 'px'}">
<view class="w-instace-header" v-if="instanceData.staterUser">
<avatar :name="instanceData.staterUser.name" :size="30" :src="getRes(instanceData.staterUser.avatar)" showY></avatar>
<view>
<view style="display: flex; align-items: center;">
<text style="margin-right: 20rpx;">{{instanceData.processDefName}}</text>
<uni-tag circle="true" :text="instanceData.status" :type="getProcTag(instanceData.result).type"
inverted></uni-tag>
</view>
<view>编号{{instanceData.instanceId}}</view>
</view>
<image v-if="instanceData.statusImg" :src="instanceData.statusImg" mode="aspectFill"></image>
</view>
<view class="w-instace-form">
<form-render :jsonConf="instanceData.formItems" :config="instanceData.formConfig" v-model="instanceData.formData"></form-render>
</view>
<view class="w-instace-process">
<process-progress :progress="instanceData.progress" :status="instanceData.status"
:result="instanceData.result"></process-progress>
</view>
</scroll-view>
<view class="w-instace-action" v-if="instanceData.operationPerm || !instanceData.finishTime">
<view class="w-action">
<view @click="doAction('comment')">
<uni-icons type="chat" size="34"></uni-icons>
<view>评论</view>
</view>
<!-- <view class="w-action-dis" @click="urging" v-if="!instanceData.finishTime">
<uni-icons type="notification" size="34"></uni-icons>
<view>催办</view>
</view> -->
<view @click="showMore">
<uni-icons type="more-filled" size="34"></uni-icons>
<view>更多</view>
</view>
<view class="w-action-main">
<button v-if="instanceData.operationPerm?.refuse?.show && rejectionBtn" @click="doAction('recall')">
退回
</button>
<button v-if="instanceData.operationPerm?.refuse?.show && !rejectionBtn" @click="doAction('refuse')">
{{instanceData.operationPerm.refuse.alisa}}
</button>
<button type="primary" v-if="instanceData.operationPerm?.agree?.show" @click="doAction('agree')">
{{instanceData.operationPerm.agree.alisa}}
</button>
</view>
</view>
</view>
<uni-popup ref="actionPopup" type="bottom" background-color="#fff">
<view class="w-more-title">选择您的审批操作</view>
<view class="w-action-mores">
<view v-if="instanceData.operationPerm?.transfer?.show" @click="doAction('transfer')">
<uni-icons type="staff" size="34"></uni-icons>
<view>转交</view>
</view>
<view v-if="instanceData.operationPerm?.recall?.show" @click="doAction('recall')">
<uni-icons type="undo" size="34"></uni-icons>
<view>退回</view>
</view>
<view v-if="instanceData.operationPerm?.afterAdd?.show" @click="doAction('afterAdd')">
<uni-icons type="personadd" size="34"></uni-icons>
<view>后加签</view>
</view>
<view v-if="enableCancel" @click="doAction('cancel')">
<uni-icons type="refreshempty" size="34"></uni-icons>
<view>撤销</view>
</view>
</view>
<button class="w-more-close" type="default" @click="actionPopup.close()"> </button>
</uni-popup>
<uni-popup ref="doActionPopup" type="bottom" background-color="#fff">
<view class="w-more-title">{{action.title}}</view>
<view class="w-action-content">
<view style="display: flex; align-items: center;"
v-if="action.type === 'afterAdd' || action.type === 'transfer'">
<text>目标人员</text>
<view style="flex: 1;">
<user-picker v-model="selectedUser" position="bottom"></user-picker>
</view>
</view>
<view style="display: flex; align-items: center;" v-else-if="action.type === 'recall'">
<text>回退节点</text>
<view style="flex: 1;">
<select-picker labelKey="nodeName" valueKey="nodeId" v-model="handerParams.targetNode"
:options="recallNodes" />
</view>
</view>
<view style="display: flex; align-items: center;" v-if="activeTasks.length > 1">
<text>处理节点</text>
<view style="flex: 1;">
<select-picker placeholder="要处理哪个任务" valueKey="taskId" labelKey="name" v-model="handerParams.taskId"
:options="activeTasks" />
</view>
</view>
<uni-easyinput type="textarea" v-model="handerParams.comment.text" :placeholder="action.tip" />
<view style="margin: 32rpx 0;">
<file-upload ref="attachFile" :formProps="{maxSize: 10, placeholder:'审批附件'}" v-model="attachment.files" />
<image-upload :formProps="{maxSize: 10, placeholder:'审批附图'}" v-model="attachment.images" />
<view style="margin-top: 32rpx;" v-if="showSignCt">
<sign-panel position="bottom" v-model="handerParams.signature" />
</view>
</view>
</view>
<view class="w-action-confirm">
<button type="default" @click="doActionPopup.close()">取消</button>
<button type="primary" @click="submitAction"> </button>
</view>
</uni-popup>
</view>
</view>
</template>
<script setup>
import { computed, nextTick, onBeforeMount, reactive, ref, watch } from "vue";
import { getFormAndProcessProgress, approvalTask, getEnableRecallNodes, getTaskNodeSettings } from '@/api/task'
import { onLoad } from "@dcloudio/uni-app";
import { $nEmpty, debounce } from '@/utils/tool.js'
import Avatar from '@/components/Avatar.vue'
import FormRender from '@/components/FormRender.vue'
import processProgress from './processProgress.vue'
import { getUserSign } from "@/api/org";
import { getProcTag } from '@/utils/ProcessUtil.js'
import OrgPicker from "@/components/OrgPicker.vue";
import ImageUpload from "@/components/form/ImageUpload.vue";
import FileUpload from "@/components/form/FileUpload.vue";
import SignPanel from "@/components/form/SignPanel.vue";
import UserPicker from "@/components/form/UserPicker.vue";
import SelectPicker from '@/components/common/SelectPicker.vue'
import { getRes } from '@/utils/tool.js'
const nodeId = ref(null)
const instanceData = ref(null)
const selectedUser = ref([])
const recallNodes = ref([])
const actionPopup = ref();
const doActionPopup = ref();
const showSign = ref(false);
const attachFile = ref()
const action = ref({
type: '',
title: null,
tip: null
})
//审批参数
const attachment = reactive({
files: [],
images: []
});
const handerParams = reactive({
instanceId: null,
taskId: null,
comment: {
text: null,
attachments: []
},
formData: {},
signature: null,
action: null,
updateSign: false,
targetNode: null,
targetUser: null,
})
onLoad((v) => {
nodeId.value = v.nodeId
getInstanceData(v.instanceId, v.nodeId)
})
//当前登录的用户
const loginUser = JSON.parse(uni.getStorageSync('loginUser'))
const rejectionBtn = computed(() => {
if(["文明施工","安全检查","质量问题","质量监督检查"].includes(instanceData.value.processDefName)) {
return instanceData.value.progress.length == 2 ? false : true;
}
return false;
})
const showSignCt = computed(() => {
return showSign.value && action.value.type === 'agree'
})
const enableCancel = computed(() => {
try {
return instanceData.value?.externSetting?.enableCancel
} catch (e) {
return false
}
})
//过滤本人下待处理的节点
const activeTasks = computed(() => {
let tasks = [];
(instanceData.value.progress || []).forEach(task => {
if (task.isFuture) return
if (task.users) {
task.users.forEach(tk => {
if (tk.user && tk.user.id === loginUser.userId && !$nEmpty(tk.finishTime)) {
tasks.push(tk)
}
})
} else {
if (task.user && task.user.id === loginUser.userId && !$nEmpty(task.finishTime)) {
tasks.push(task)
}
}
})
return tasks;
})
const contentHeight = computed(() => {
const h = uni.getSystemInfoSync().windowHeight
if(window.plus) {
return instanceData.value.finishTime ? h : h - uni.upx2px(88) - uni.upx2px(44) - uni.upx2px(60) - uni.upx2px(88);
}
return instanceData.value.finishTime ? h : h - uni.upx2px(88) - uni.upx2px(60) - uni.upx2px(88);
})
//滚动延时去抖动
const scDebounce = debounce(() => uni.$emit('showFp'), 500)
watch(() => handerParams.taskId, () => {
getTaskSet()
})
function getTaskSet(){
getTaskNodeSettings(handerParams.taskId).then(rsp => {
showSign.value = rsp.data.enableSign || false
}).catch(e => {})
}
function getInstanceData(instId, nodeId) {
getFormAndProcessProgress(instId, nodeId).then(rsp => {
instanceData.value = rsp.data
//设置状态图标
switch (instanceData.value.result) {
case 'PASS':
instanceData.value.statusImg = '/static/image/agree.png'
break
case 'CANCEL':
instanceData.value.statusImg = '/static/image/recall.png'
break
case 'REFUSE':
instanceData.value.statusImg = '/static/image/refuse.png'
break
}
})
}
function showMore() {
actionPopup.value.open()
}
function urging() {
uni.showModal({
title: '催办确认',
content: '您确认要提醒流程中人员尽快处理任务?',
success: function(res) {
if (res.confirm) {
uni.showToast({
icon: 'none',
title: '该功能暂未实现哈😘'
})
} else if (res.cancel) {
}
}
});
}
function doAction(type) {
handerParams.taskId = getTask()
selectedUser.value = []
handerParams.targetUser = null
handerParams.comment.text = ''
handerParams.comment.attachments = []
switch (type) {
case 'comment':
action.value = {
type: type,
title: '添加评论',
tip: '评论内容'
}
break;
case 'transfer':
action.value = {
type: type,
title: '转交流程',
tip: '转交意见',
}
break;
case 'afterAdd':
action.value = {
type: type,
title: '流程节点加签',
tip: '加签意见'
}
break;
case 'recall':
action.value = {
type: type,
title: '退回流程',
tip: '退回意见'
}
getEnableRecallNode()
break;
case 'cancel':
action.value = {
type: type,
title: '撤销流程',
tip: '撤销原因'
}
break;
case 'agree':
action.value = {
type: type,
title: '审批同意',
tip: '审批意见'
}
break;
case 'refuse':
action.value = {
type: type,
title: '审批拒绝',
tip: '审批意见'
}
break;
}
doActionPopup.value.open()
//使文件选择生效
nextTick(() => attachFile.value.show())
}
function loadBeforeSign() {
getUserSign().then(rsp => {
if (rsp.data === '') {
uni.showToast({
icon: 'none',
title: '您还没有保存过的签名'
})
} else {
handerParams.signature = rsp.data
}
})
}
//获取要处理的任务id
function getTask() {
if ($nEmpty(handerParams.taskId)) {
return handerParams.taskId
} else if (activeTasks.value.length > 0) {
return activeTasks.value[0].taskId
} else if (action.value.type === 'cancel') {
const pg = instanceData.value.progress || [];
for (let i = 0; i < pg.length; i++) {
if (pg[i].users) {
for (let j = 0; j < pg[i].users.length; j++) {
if (!$nEmpty(pg[i].users[j].finishTime)) {
return pg[i].users[j].taskId
}
}
} else {
if (!$nEmpty(pg[i].finishTime)) {
return pg[i].taskId
}
}
}
}
return null
}
function getEnableRecallNode() {
getEnableRecallNodes(instanceData.value.instanceId, getTask()).then(rsp => {
recallNodes.value = rsp.data
}).catch(err => {
uni.showToast({
icon: 'none',
title: '获取可回退节点失败'
})
})
}
function submitAction() {
handerParams.instanceId = instanceData.value.instanceId
handerParams.action = action.value.type
handerParams.taskId = getTask()
handerParams.formData = instanceData.value.formData
handerParams.comment.attachments = [...attachment.files, ...attachment.images]
if ((action.value.type === 'afterAdd' || action.value.type === 'transfer') && !selectedUser.value.length > 0) {
uni.showToast({
icon: 'none',
title: '请设置人员'
})
} else if (action.value.type === 'recall' && !$nEmpty(handerParams.targetNode)) {
uni.showToast({
icon: 'none',
title: '请选择回退节点'
})
} else if (showSignCt.value && !$nEmpty(handerParams.signature)) {
uni.showToast({
icon: 'none',
title: '请签字后再提交'
})
} else {
handerParams.targetUser = selectedUser.value?.[0]?.id
approvalTask(handerParams).then(rsp => {
uni.showToast({
icon: 'success',
title: '处理成功'
})
actionPopup.value.close()
doActionPopup.value.close()
if (action.value.type === 'comment') {
getInstanceData(instanceData.value.instanceId, nodeId.value)
} else {
uni.reLaunch({
url: '/pages/workspace/workspace'
})
}
}).catch(err => {
uni.showToast({
icon: 'none',
title: err.msg
})
})
return
}
}
function scroll() {
// #ifdef APP-VUE
uni.$emit('wv:scorll');
// #endif
}
</script>
<style lang="less" scoped>
:deep(.headerBox) {
background-color: #2F8FF3;
}
page {
background-color: #F4F5F7;
}
:deep(.w-instace-form) {
.w-form-item {
margin-bottom: 0;
padding-bottom: 16rpx;
}
.w-form-item-r {
margin-bottom: 16rpx;
}
}
.w-instace {
font-size: 32rpx;
.w-instace-header,
.w-instace-form,
.w-instace-process {
margin: 32rpx 0;
padding: 32rpx 16rpx;
background-color: white;
}
.w-instace-header {
margin-top: 0;
position: relative;
image {
position: absolute;
z-index: 2;
width: 160rpx;
height: 160rpx;
right: 32rpx;
bottom: -96rpx;
}
}
:deep(.w-instace-form) {
padding: 32rpx 16rpx;
.w-form-item-r {
padding-bottom: 0;
font-size: 26rpx;
.w-form-title :last-child {
font-size: 29rpx !important;
color: #848484;
}
}
}
.w-instace-header {
display: flex;
align-items: center;
font-size: 26rpx;
.uni-tag {
font-weight: 400;
}
}
}
.w-instace-action {
width: 100%;
height: 144rpx;
background-color: #EFEFEF;
display: flex;
justify-content: space-between;
.w-action {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 25rpx;
text-align: center;
padding: 0 32rpx;
.w-action-main {
width: 40%;
display: flex;
right: 32rpx;
&>button {
width: 128rpx;
font-size: 32rpx;
line-height: 64rpx;
}
}
}
}
.w-action-mores {
display: flex;
justify-content: space-between;
padding: 64rpx;
font-size: 32rpx;
&>view {
text-align: center;
}
}
.w-more-close {
background-color: #E8E8E8 !important;
border: none !important;
margin: 0 32rpx 32rpx 32rpx;
border-radius: 48rpx;
line-height: 64rpx;
color: #848484;
}
.w-more-title {
font-size: 29rpx;
text-align: center;
margin-top: 16rpx;
color: #848484;
}
.w-action-content {
padding: 32rpx;
}
.w-action-confirm {
display: flex;
padding-bottom: 32rpx;
button {
width: 40%;
font-size: 32rpx;
line-height: 64rpx;
}
}
</style>