CaiYuanYiTiHua/src/views/demo/workflow/task/process/audit.vue

744 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<PageWrapper :class="prefixCls">
<div class="btn-box">
<a-button type="primary" @click="confimReading" class="ml-2" v-if="props.isRead == 1"
>确认阅读
</a-button>
<a-button type="primary" @click="closePage" class="ml-2" danger>关闭 </a-button>
</div>
<a-layout>
<a-layout>
<a-layout-content>
<a-tabs v-model:activeKey="activeName" @change="changeActive">
<a-tab-pane key="form" tab="表单信息" v-if="formVisble">
<FormViewer
ref="formBoxRef"
:formConfig="formConfig"
:processId="designerData.process.id"
:formVerison="designerData.formCurrentNode.formVerison"
:formRelationId="designerData.formCurrentNode.formRelationId"
:instanceInfo="designerData.process.instanceInfo"
v-if="formVisble"
/>
</a-tab-pane>
<a-tab-pane key="form" tab="系统表单信息" v-if="formUrlVisble">
<AsyncComponent ref="pcForm" />
</a-tab-pane>
<a-tab-pane key="flow" tab="流程信息" force-render>
<div class="process-design" :style="'display: flex; height:' + designerData.height">
<process-viewer
v-if="processVisble"
:key="`designer-${id}`"
:events="['element.click']"
@element-click="elementClick"
:xml="flowContent"
:flowViewer="flowViewer"
/>
</div>
</a-tab-pane>
<a-tab-pane key="record" tab="流转记录" force-render>
<a-timeline>
<a-timeline-item
v-for="(item, index) in designerData.logs"
:key="index"
:color="item.type"
>
<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"
:key="index2"
>{{ userName }}</span
>
{{ item.des }}
</div>
</a-card>
</a-timeline-item>
</a-timeline>
</a-tab-pane>
</a-tabs>
</a-layout-content>
<!-- <a-divider type="vertical" /> -->
<a-layout-sider v-if="props.isRead == 0">
<a-tabs v-model:activeKey="auditName">
<a-tab-pane key="audit" tab="审批栏">
<div class="approval-column">
<a-form
ref="formRef"
:rules="rules"
:model="formData"
labelAlign="left"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<a-form-item
:label="designerData.isCreateAgain ? '备注' : '审批意见'"
name="des"
/>
<a-form-item label="">
<a-textarea
v-model:value="formData.des"
placeholder="请输入"
:auto-size="{ minRows: 5, maxRows: 8 }"
/>
</a-form-item>
<a-form-item label="" class="l-task-btns">
<a-button
v-for="(btn, index) in designerData.taskBtns"
:key="index"
:color="btn.type"
:type="btn.type ? btn.type : 'primary'"
@click="handleBtnClick(btn)"
>{{ btn.name }}</a-button
>
</a-form-item>
</a-form>
<auditInfo :data="designerData.userLogs" />
</div>
</a-tab-pane>
</a-tabs>
</a-layout-sider>
</a-layout>
</a-layout>
<!-- 节点记录信息 -->
<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"
:color="item.type"
>
<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" :key="index2">{{
userName
}}</span>
{{ item.des }}
</div>
</a-card>
</a-timeline-item>
</a-timeline>
</a-drawer>
</div>
</PageWrapper>
</template>
<script lang="ts" setup>
import { ref, reactive, onBeforeMount, defineAsyncComponent } from 'vue';
import { ProcessViewer } from '@/components/ProcessViewer';
import { PageWrapper } from '@/components/Page';
import { createAgain, signAudit, audit, ReadFlow } from '@/api/sys/WFProcess';
import { getBPMNTask } from '@/api/sys/WFTask';
import { dateFormat } from '@/utils/base';
import { flowStore } from '@/store/modules/flow';
import { functionsaveForm, LoadFormScheme } from '@/api/demo/formScheme';
import { FormViewer } from '@/components/FormViewer';
import {
designerDataType,
logsType,
taskBtnsType,
currentNodeObject,
nodeUsersType,
} from './processModel';
import { auditInfo } from './page';
import { IFormConfig } from '@/views/demo/form-design/typings/v-form-component';
import { useMessage } from '@/hooks/web/useMessage';
import { buildGUID } from '@/utils/uuid';
const { createMessage } = useMessage();
const formBoxRef = ref<any>();
const emit = defineEmits(['closeModel']);
const keyValue = ref('');
const flowWfDataStore = flowStore();
const prefixCls = 'preview-box';
const flowContent = ref('');
const flowViewer = ref({});
const formRef = ref();
const labelCol = { span: 7 };
const wrapperCol = { span: 24 };
const infoOpen = ref(true);
const formVisble = ref(false);
const formUrlVisble = ref(false);
const processVisble = ref(false);
const AsyncComponent = ref();
const pcForm = ref();
const props = defineProps({
processId: String,
taskId: String,
type: String,
isRead: String,
});
const formData = ref({
des: '',
});
const rules: any = ref({
des: [{ required: true, message: '请填写审批意见', trigger: 'blur' }],
});
if (props.type == 4) {
rules.value = {};
}
// 表单数据
const formConfig = ref<IFormConfig>({
// 表单配置
schemas: [],
layout: 'horizontal',
labelLayout: 'flex',
labelWidth: 100,
labelCol: {},
wrapperCol: {},
currentItem: {
component: '',
componentProps: {},
},
activeKey: 1,
});
const designerData: designerDataType = reactive({
loading: false,
xmlString: '',
height: document.documentElement.clientHeight - 200.5 + 'px;',
midVisible: false,
isCustmerTitle: false,
nodeUsers: [],
selectUsersVisible: false,
selectTUserVisible: false,
tUserType: 1, // 1 转移 2 加签,
isDraft: false,
delegateUsers: [],
task: {},
process: {},
logs: [], // 流程日志信息
nodeMap: {}, // 需要处理的任务
userLogs: [], // 人员日志信息
nodeLogs: [],
taskBtns: [],
currentNode: {},
stampList: [],
currentBtn: {
code: '',
name: '',
isNextAuditor: false,
},
wfData: [],
isCreateAgain: props.type == 4 ? true : false,
selectRejectNodeVisible: false,
selectSignVisible: false,
formCurrentNode: {},
});
const activeName = ref('form');
const auditName = ref('audit');
function changeActive(activeKey) {
if (activeKey == 'flow') {
processVisble.value = true;
}
}
function elementClick(element: { id: string | number }) {
if (element) {
designerData.nodeLogs = designerData.nodeMap[element.id] || [];
infoOpen.value = true;
} else {
designerData.nodeLogs = [];
}
}
async function getTaskInfo() {
let query: any = {
id: props.taskId,
};
let data = await getBPMNTask(query);
flowContent.value = data.flowContent;
flowViewer.value = data.flowViewer;
designerData.process = data.process;
designerData.task = data.task;
let content = JSON.parse(data.scheme.content);
let wfData = content.wfData;
console.log(wfData);
const auditNode = wfData.find((t) => t.id == data.task.unitId);
console.log(auditNode);
let currentNode;
if (auditNode.isInherit) {
currentNode = wfData.find((t) => t.type == 'bpmn:StartEvent');
} else {
currentNode = auditNode;
}
console.log(currentNode);
if (currentNode.authFields.length > 0) {
formVisble.value = true;
} else if (currentNode.formUrl) {
// 有PC系统表单时
formUrlVisble.value = true;
let url = '../../../' + currentNode.formUrl + '.vue';
console.log(url);
// AsyncComponent.value = defineAsyncComponent(() => import(url));
AsyncComponent.value = defineAsyncComponent({
// 加载函数
loader: () => import(url),
// 展示加载组件前的延迟时间默认为200ms,注:这里如果设置的时间过短,会有闪烁的效果
delay: 200,
// 如果提供了一个timeout时间限制并且超时了也会展示这里的报错组件
// 设置加载超时时间为3000毫秒
timeout: 3000,
onError: function () {
createMessage.error('无法加载系统表单,请查看流程配置是否正确!');
},
});
} else {
activeName.value = 'flow';
processVisble.value = true;
}
designerData.formCurrentNode = currentNode;
formConfig.value = currentNode.authFields;
if (props.isRead == 0) {
setLogsAndTasks(data.logs, data.tasks);
getBtns();
}
}
function setLogsAndTasks(logs: any[], tasks: any[]) {
const res: logsType[] = [];
const taskMap = {};
const nodeMap = {};
const userLogs: any[] = [];
tasks.forEach(
(task: {
unitId: string | number;
unitName: any;
createDate: any;
type: number;
userId: any;
userName: any;
}) => {
nodeMap[task.unitId] = nodeMap[task.unitId] || [
{
unitId: task.unitId,
name: task.unitName,
userIds: [],
userNames: [],
des: '正在审核',
time: `当前-创建时间:${dateFormat(task.createDate)}`,
type: 'blue',
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: 'blue',
};
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: 'blue',
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: 'blue',
};
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: { userIds: string | any[] }) => t.userIds.length > 0);
}
logs.forEach(
(log: {
unitId: string;
unitName: any;
userId: string;
userName: string;
des: string;
operationName: string;
createDate: any;
taskType: number;
operationCode: string;
stampImg: any;
}) => {
res.push({
unitId: log.unitId,
name: log.unitName,
userIds: [log.userId],
userNames: [log.userName],
des: log.des ? log.des : log.operationName,
time: dateFormat(log.createDate),
type: 'gray',
});
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.operationName,
type: 'gray',
isFinish: true,
});
if (log.taskType == 1 && !['sign'].includes(log.operationCode)) {
// 右侧显示审核记录
// const userLogIndex = userLogs.findIndex((t) => t.id == log.unitId);
// if (userLogIndex == -1) {
userLogs.push({
id: log.unitId,
name: log.unitName,
user: log.userName,
time: dateFormat(log.createDate),
des: log.des,
img: log.stampImg,
});
// }
}
},
);
designerData.logs = res;
designerData.nodeMap = nodeMap;
designerData.userLogs = userLogs.sort(function (a, b) {
return b.time < a.time ? -1 : 1;
});
}
async function validateForm() {
let res = await formRef.value
.validate()
.then(() => {
return true;
})
.catch(() => {
return false;
});
return res;
}
function getBtns() {
const wfData = flowWfDataStore.getWfData;
designerData.wfData = wfData;
const currentNode: currentNodeObject =
wfData.find((t) => t.id == designerData.task.unitId) || {};
designerData.currentNode = currentNode;
// 设置审核按钮
const btns: taskBtnsType[] = [];
if (currentNode.type == 'bpmn:StartEvent') {
btns.push({
code: 'learun_create',
name: '提交',
type: '',
});
} else {
currentNode.btnlist.forEach((btn: taskBtnsType) => {
if (btn.code == 'agree') {
btn.type = '';
} else if (btn.code == 'disagree') {
btn.type = 'error';
}
btns.push(btn);
});
if (currentNode.isAddSign) {
btns.push({
code: 'learun_sign',
name: '加签',
type: 'success',
});
}
if (currentNode.isTransfer) {
btns.push({
code: 'learun_transfer',
name: '转移',
type: 'success',
});
}
}
designerData.taskBtns = btns;
}
async function handleBtnClick(btn) {
const instanceInfo = JSON.parse(designerData.process.instanceInfo);
// 验证审批栏必填项
const data = await validateForm();
if (!data) {
return;
}
// 有表单先提交表单数据
if (formVisble.value) {
var querys = {
schemeId: designerData.formCurrentNode.formVerison,
isUpdate: true,
pkey: instanceInfo.pkey,
pkeyValue: instanceInfo.pkeyValue,
};
formBoxRef.value
.getForm()
.then(async (res) => {
res[designerData.formCurrentNode.formRelationId] = instanceInfo.pkeyValue;
for (var item in res) {
if (!res[item]) {
res[item] = '';
if (item.search('_input_guid') != -1) {
res[item] = buildGUID();
}
}
}
querys.data = JSON.stringify(res);
const formValue = await functionsaveForm(querys);
if (formValue) {
handleSubmit(btn);
}
})
.catch((error) => {
console.log(error);
activeName.value = 'form';
return;
});
} else if (formUrlVisble.value) {
//有系统表单,先存系统表单
const funName = btn.code;
const res = await pcForm.value[funName]();
console.log(res);
// handleCreateFlow(processId);
} else {
handleSubmit(btn);
}
}
async function handleSubmit(btn) {
const currentBtn = btn;
designerData.currentBtn = currentBtn;
let res: any;
switch (btn.code) {
case 'learun_create':
res = await createAgain({
processId: props.processId,
des: `重新提交${formData.value.des ? '-' : ''}${formData.value.des}`,
});
break;
case 'learun_sign':
designerData.tUserType = 2;
designerData.selectTUserVisible = true;
break;
case 'learun_transfer':
designerData.tUserType = 1;
designerData.selectTUserVisible = true;
break;
default:
if (designerData.task.type == 6) {
// 加签审核
res = await signAudit(props.taskId, {
code: btn.code,
name: btn.name,
des: formData.value.des,
});
} else {
if (designerData.currentNode.rejectType == '2' && btn.code == 'disagree') {
designerData.selectRejectNodeVisible = true;
return;
}
if (btn.isSign) {
// 加载签章数据
// const stampList = await this.$awaitWraper(apiStamp.getList(this.loginInfo.f_UserId));
// if (stampList && stampList.length > 0) {
// designerData.stampList = stampList;
// designerData.selectSignVisible = true;
// return;
// }
}
res = await auditFun();
}
break;
}
if (res) {
closePage();
return createMessage.success('成功');
} else {
return createMessage.error('失败');
}
}
async function auditFun() {
// 获取接下来节点审核人
if (designerData.currentBtn.isNextAuditor) {
const res = await getLoadNextAuditors({
processId: props.processId,
nodeId: designerData.currentNode.id,
});
const nodeUserMap = res.data.data;
const nodeUsers: nodeUsersType[] = [];
for (let key in nodeUserMap) {
const nodeUserItem = nodeUserMap[key];
if (nodeUserItem.length > 1) {
nodeUsers.push({
name: designerData.wfData.find((t) => t.id == key).name,
id: key,
options: nodeUserItem.map((t: { id: any; name: any }) => {
return { value: t.id, label: t.name };
}),
});
}
}
designerData.nodeUsers = nodeUsers;
if (designerData.nodeUsers.length > 0) {
designerData.selectUsersVisible = true;
return;
}
}
const data = await audit(props.taskId, {
code: designerData.currentBtn.code,
name: designerData.currentBtn.name,
des: formData.value.des,
});
if (data) {
closePage();
return createMessage.success('成功');
} else {
return createMessage.error('失败');
}
}
function closePage() {
emit('closeModel');
// tabStore.closeTabByKey(
// '/dashboard/task_audit_preview/detail?processId=' +
// processId +
// '&taskId=' +
// taskId +
// '&type=' +
// type +
// '&isRead=' +
// isRead,
// router,
// );
}
async function confimReading() {
const data = await ReadFlow(props.taskId);
if (data) {
closePage();
return createMessage.success('成功');
} else {
return createMessage.error('失败');
}
}
onBeforeMount(() => {
getTaskInfo();
});
</script>
<style lang="less" scoped>
::v-deep .ant-tabs-nav-wrap {
padding-left: 10px;
}
::v-deep .ant-layout {
height: 100%;
background-color: @component-background;
}
::v-deep .ant-layout-sider-children {
border-left: 1px solid rgba(5, 5, 5, 0.06);
}
::v-deep .ant-divider-vertical {
height: 100%;
}
.info-box {
display: inline-block;
width: 300px;
position: absolute;
right: 0;
margin-top: 40px;
}
.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 {
height: 100%;
.btn-box {
padding: 10px;
justify-content: flex-end;
display: flex;
}
}
::v-deep .vben-page-wrapper-content {
height: 100%;
// margin: 0 0 0 16px;
}
.form-box {
width: 480px;
}
.approval-column {
padding: 10px;
}
::v-deep .ant-layout .ant-layout-sider {
padding-left: 10px;
background-color: @component-background;
flex: 0 0 360px !important;
max-width: 360px !important;
min-width: 360px !important;
width: 360px !important;
}
::v-deep .vben-page-wrapper .vben-page-wrapper-content {
height: 98%;
}
.l-task-btns {
.ant-btn {
margin: 0;
margin-top: 8px;
margin-right: 8px;
}
}
</style>