You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
617 lines
20 KiB
617 lines
20 KiB
<template> |
|
<div :class="prefixCls"> |
|
<ALayoutContent class="m-10"> |
|
<h4 class="text-center text-lg pb-2">{{ state.formTitle }}</h4> |
|
<ATables v-model:activeKey="state.taskSelectedTab" |
|
class="bg-white" |
|
:tab-bar-style="{ 'padding-left': '13px' }" |
|
@tabClick="handleTabsClick" |
|
> |
|
<ATabPane key="formInfo"> |
|
<template #tab> |
|
<span> |
|
<Icon icon="fa6-solid:file-lines"/>表单信息 |
|
</span> |
|
</template> |
|
<!-- 动态表单:内置使用枇杷表单设计器(待更新为海豚表单设计器) --> |
|
<workflow-preview-form v-if="state.formType !== '2'" |
|
ref="formPreview" |
|
:taskFormData="state.taskFormData" |
|
/> |
|
<!-- 外置表单:内置使用用户自定义的vue页面,手动填写页面路径即可 --> |
|
<component :is="state.formPath" |
|
v-if="state.formType === '2'" |
|
ref="form" |
|
:formReadOnly="state.formReadOnly" |
|
:businessId="state.businessId" |
|
/> |
|
</ATabPane> |
|
<ATabPane key="processInfo"> |
|
<template #tab> |
|
<span> |
|
<Icon icon="fa6-solid:money-bill-wheat"/>流程信息 |
|
</span> |
|
</template> |
|
<WorkflowTimeLine/> |
|
</ATabPane> |
|
<ATabPane key="processChart"> |
|
<template #tab> |
|
<span> |
|
<Icon icon="fa-solid:image"/>流程图 |
|
</span> |
|
</template> |
|
<WorkflowChartModel ref="workflowChart"/> |
|
</ATabPane> |
|
<ATabPane key="flowRecord"> |
|
<template #tab> |
|
<span> |
|
<Icon icon="fa6-solid:bars-progress"/>流转记录 |
|
</span> |
|
</template> |
|
<WorkflowStep/> |
|
</ATabPane> |
|
</ATables> |
|
<a-card v-if="!state.processInsId || state.taskId || state.status === 'reStart'" style="margin-top:10px;margin-bottom:66px;"> |
|
<AForm |
|
ref="auditForm" |
|
:labelCol="{ style: { width: '90px' } }" |
|
:wrapperCol="{ style: { width: '100%' } }" |
|
:model="state.auditForm" |
|
:scrollToFirstError="true" |
|
> |
|
<ACol :span="16"> |
|
<AFormItem v-if="!state.processInsId || state.status === 'reStart'" label="流程标题" name="formTitle"> |
|
<a-input v-model:value="state.formTitle" placeholder="请输入流程标题"/> |
|
</AFormItem> |
|
</ACol> |
|
<ACol :span="16"> |
|
<AFormItem v-if="state.taskId" label="审批信息" name="message"> |
|
<ATextarea |
|
v-model:value="state.auditForm.message" |
|
placeholder="请输入审批意见" |
|
:rows="3" |
|
allowClear |
|
/> |
|
</AFormItem> |
|
</ACol> |
|
<ACol :span="16"> |
|
<AFormItem label=" " :colon="false" name="isCC"> |
|
<a-checkbox v-model:checked="state.isCC">是否抄送</a-checkbox> |
|
</AFormItem> |
|
</ACol> |
|
<ACol :span="16"> |
|
<AFormItem v-if="state.isCC" |
|
label="抄送给" |
|
name="userIds" |
|
:rules="[{required: true, message: '用户不能为空', validateTrigger: 'blur'}]" |
|
> |
|
<a-input-group compact> |
|
<a-input v-model:value="state.auditForm.userIds" style="width: calc(100% - 48px)"/> |
|
<a-button type="primary" |
|
style="width: 48px" |
|
@click="handleCCUserPicker" |
|
> |
|
<Icon icon="fa6-solid:users-viewfinder"/> |
|
</a-button> |
|
</a-input-group> |
|
</AFormItem> |
|
</ACol> |
|
<ACol :span="16"> |
|
<AFormItem label=" " :colon="false" name="isAssign"> |
|
<a-checkbox v-model:checked="state.isAssign">指定下一步处理者(不设置就使用默认处理人)</a-checkbox> |
|
</AFormItem> |
|
</ACol> |
|
<ACol :span="16"> |
|
<AFormItem v-if="state.isAssign" |
|
label="指定" |
|
name="assignee" |
|
:rules="[{required: true, message: '用户不能为空', validateTrigger: 'blur'}]" |
|
> |
|
<a-select v-model:value="state.auditForm.userIds" allowClear/> |
|
</AFormItem> |
|
</ACol> |
|
</AForm> |
|
</a-card> |
|
</ALayoutContent> |
|
<footer class="workflow-form__footer" :style="getWrapFormFooterStyle"> |
|
<a-button size="large" type="primary">审批</a-button> |
|
<a-button size="large" type="primary" danger>驳回</a-button> |
|
</footer> |
|
<UserPicker @register="registerModal" @success="handleTransferTask"/> |
|
</div> |
|
</template> |
|
<script lang="ts" setup> |
|
/** |
|
* 提供模板规范代码参考,请尽量保证编写代码风格跟模板规范代码一致 |
|
* 采用vben-动态表格表单封装组件编写,采用 setup 写法 |
|
* Copyright © 2023-2023 <a href="https://godolphinx.org">海豚生态开源社区</a> All rights reserved. |
|
* author wangxiang4 |
|
*/ |
|
import { reactive, onActivated, unref, nextTick, ref, computed, CSSProperties } from 'vue'; |
|
import { Input, LayoutContent, Tabs, Form, Col } from 'ant-design-vue'; |
|
import type { FormProperty } from '/@/api/platform/workflow/entity/formProperty'; |
|
import type { Workflow } from '/@/api/platform/workflow/entity/workflow'; |
|
import type { WorkflowButton } from '/@/api/platform/workflow/extension/entity/workflowButton'; |
|
import { useRouter } from 'vue-router'; |
|
import { saveWorkflowCopy } from '/@/api/platform/workflow/extension/controller/workflowCopy'; |
|
import { useMessage } from '/@/hooks/web/useMessage'; |
|
import { startProcessDefinition, stopProcessInstance, getFlowChart as getProcessDefFlowChart } from '/@/api/platform/workflow/controller/process'; |
|
import { useTabs } from '/@/hooks/web/useTabs'; |
|
import { auditTask, rollBackTaskList, rejectTask, transferTask, delegateTask, getFlowChart, listHistoryFlowChange } from '/@/api/platform/workflow/controller/task'; |
|
import { useModal } from '/@/components/Modal'; |
|
import type { KiccUser } from '/@/api/common/base/entity'; |
|
import WorkflowPreviewForm from './WorkflowPreviewForm.vue'; |
|
import WorkflowChartModel from './WorkflowChartModel.vue'; |
|
import WorkflowTimeLine from './WorkflowTimeLine.vue'; |
|
import WorkflowStep from './workflowStep/index.vue'; |
|
import { getProcessStartEventFormData, getTaskFormData } from '/@/api/platform/workflow/controller/form'; |
|
import { findByDefIdAndTaskId } from '/@/api/platform/workflow/extension/controller/activityExtensionData'; |
|
import { useDesign } from '/@/hooks/web/useDesign'; |
|
import { useAppInject } from '/@/hooks/web/useAppInject'; |
|
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
|
import UserPicker from './popups/UserPicker/index.vue'; |
|
|
|
/** 类型规范统一声明定义区域 */ |
|
interface WindowState { |
|
// 流程表单标题 |
|
formTitle: string; |
|
// 流程表单key |
|
formKey: string; |
|
// 表单类型(1:动态表单,2:外置表单) |
|
formType: string; |
|
// 外置表单路径 |
|
formPath: string; |
|
// 外置表单是否只读 |
|
formReadOnly: boolean, |
|
// 外置表单业务数据绑定ID |
|
businessId: string, |
|
// 动态表单字段配置数据 |
|
taskFormData: FormProperty[], |
|
|
|
// 设置选择任务选项卡 |
|
taskSelectedTab: string, |
|
// 流程任务ID |
|
taskId: string, |
|
// 流程任务定义key |
|
taskDefKey: string, |
|
|
|
// 流程定义ID |
|
processDefId: string, |
|
// 流程实例ID |
|
processInsId: string, |
|
// 流程定义Key |
|
processDefKey: string, |
|
|
|
// 当前任务表单操作状态 |
|
status: string, |
|
// 历史流转信息 |
|
historyFlowChangeList: Workflow[], |
|
// 当前操作按钮配置信息 |
|
buttons: Partial<WorkflowButton>[], |
|
// 是否抄送 |
|
isCC: boolean, |
|
// 是否指定代理人 |
|
isAssign: boolean, |
|
// 审批表单信息 |
|
auditForm: { |
|
message: string, |
|
mesCode: string, |
|
mesName: string, |
|
// 抄送用户ID |
|
userIds: string, |
|
// 任务代理人ID |
|
assignee: string, |
|
}, |
|
} |
|
|
|
/** 通用变量统一声明区域 */ |
|
const ALayoutContent = LayoutContent; |
|
const ATables = Tabs; |
|
const ATabPane = Tabs.TabPane; |
|
const AForm = Form; |
|
const AFormItem = Form.Item; |
|
const ACol = Col; |
|
const ATextarea = Input.TextArea; |
|
|
|
const { prefixCls } = useDesign('task-form'); |
|
const { getIsMobile } = useAppInject(); |
|
const { getCalcContentWidth } = useMenuSetting(); |
|
const state = reactive<WindowState>({ |
|
formTitle: '', |
|
formKey: '', |
|
formType: '', |
|
formPath: '', |
|
formReadOnly: false, |
|
businessId: '', |
|
taskFormData: [], |
|
|
|
taskSelectedTab: 'formInfo', |
|
taskId: '', |
|
taskDefKey: '', |
|
|
|
processDefId: '', |
|
processInsId: '', |
|
processDefKey: '', |
|
|
|
status: '', |
|
historyFlowChangeList: [], |
|
buttons: [], |
|
isCC: false, |
|
isAssign: false, |
|
|
|
auditForm: { |
|
message: '', |
|
mesCode: '', |
|
mesName: '', |
|
userIds: '', |
|
assignee: '' |
|
} |
|
}); |
|
const { currentRoute, push } = useRouter(); |
|
const { params, query } = unref(currentRoute); |
|
const { notification, createMessage, createConfirm } = useMessage(); |
|
const { close } = useTabs(); |
|
const formPreview = ref(); |
|
const workflowChart = ref(); |
|
const [registerModal, { openModal }] = useModal(); |
|
const [registerRollBackTask , { openModal: openRollBackTask }] = useModal(); |
|
onActivated(() => { |
|
handleInit(); |
|
nextTick(async () => { |
|
// 初始化外置表单 |
|
if (state.formType === '2') { |
|
if (state.formKey === '/404') { |
|
state.formPath = ''; |
|
createMessage.info('没有关联流程表单!'); |
|
} else { |
|
state.formPath = require('@/views' + state.formKey + '.vue').default; |
|
} |
|
// 初始化动态表单 |
|
} else { |
|
if (state.formKey === '/404') { |
|
formPreview.value.init(''); |
|
} else { |
|
formPreview.value.init(state.formKey); |
|
} |
|
// 获取启动事件表单数据 |
|
if (state.status === 'start' || state.status === 'reStart') { |
|
state.taskFormData = await getProcessStartEventFormData(state.processDefId); |
|
// 获取任务表单数据 |
|
} else { |
|
state.taskFormData = await getTaskFormData(state.taskId); |
|
} |
|
} |
|
// 设置启动按钮配置 |
|
if (state.status === 'start' || state.status === 'reStart') { |
|
state.buttons = [{ code: '_workflow_activity_start', name: '启动', isHide: '0' }]; |
|
// 获取bpmn设计器按钮配置 |
|
} else if (state.processDefKey && state.taskDefKey) { |
|
const data = await findByDefIdAndTaskId({ processDefId: state.processDefKey, activityDefId: state.taskDefKey }); |
|
state.buttons = data.workflowButtonList; |
|
} |
|
// 获取历史任务流转列表 |
|
if (state.processInsId) { |
|
state.historyFlowChangeList = await listHistoryFlowChange(state.processInsId); |
|
} |
|
}); |
|
}); |
|
|
|
function handleInit() { |
|
state.taskSelectedTab = 'formInfo'; |
|
state.processDefId = query.processDefId as string; |
|
state.processInsId = query.processInsId as string; |
|
state.processDefKey = query.processDefKey as string; |
|
state.formType = query.formType as string; |
|
state.formKey = query.formKey as string; |
|
state.taskId = query.taskId as string; |
|
state.taskDefKey = query.taskDefKey as string; |
|
state.status = query.status as string; |
|
state.formTitle = query.formTitle as string; |
|
state.businessId = query.businessId as string; |
|
state.formReadOnly = query.formReadOnly === 'true'; |
|
state.isCC = false; |
|
state.isAssign = false; |
|
state.auditForm.assignee = ''; |
|
state.auditForm.userIds = ''; |
|
state.auditForm.message = ''; |
|
} |
|
|
|
/** 流程抄送 */ |
|
function cc(data: Recordable) { |
|
if (state.isCC && state.auditForm.userIds) { |
|
//this.$refs['auditForm'].validate((valid) => { |
|
//if (valid) { |
|
saveWorkflowCopy(state.auditForm.userIds, { |
|
processDefId: state.processDefId, |
|
processInsId: data.processInsId, |
|
processDefName: '', |
|
processInsName: state.formTitle, |
|
taskName: '' |
|
}); |
|
//} |
|
//}); |
|
} |
|
} |
|
|
|
/** 暂存草稿 */ |
|
function save() { |
|
notification.warn({ |
|
message: '提示', |
|
description: '功能正在开发中...' |
|
}); |
|
} |
|
|
|
/** 启动流程 */ |
|
function start(vars: Recordable) { |
|
// 外置表单 |
|
if (state.formType === '2') { |
|
formPreview.value.startProcessDefinition(async (businessTable: string, businessId: string) => { |
|
const processInsId = await startProcessDefinition({ |
|
processDefKey: this.processDefKey, |
|
businessTable: businessTable, |
|
businessId: businessId, |
|
...vars |
|
}); |
|
await close(); |
|
await push({path: '/workflow/task/index'}); |
|
cc({ processInsId }); |
|
}); |
|
// 动态表单 |
|
} else { |
|
formPreview.value.startFormProcessDefinition({ processDefId: this.processDefId, ...vars }, (processInsId: string) => { |
|
close(); |
|
push({ path: '/workflow/task/index' }); |
|
cc({ processInsId }); |
|
}); |
|
} |
|
} |
|
|
|
/** 同意任务 */ |
|
function agree(vars?: Recordable) { |
|
commit(vars); |
|
} |
|
|
|
/** 驳回任务 */ |
|
function reject () { |
|
createConfirm({ |
|
iconType: 'warning', |
|
title: '警告', |
|
content: '确定驳回任务吗?', |
|
onOk: async () => { |
|
const nodes = await rollBackTaskList(state.taskId); |
|
if (nodes.length > 0) { |
|
const backTaskDefKey = nodes[nodes.length - 1].taskDefKey; |
|
await handleRollBackTask(backTaskDefKey); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
/** 抄送 */ |
|
function handleCCUserPicker() { |
|
openModal(true,{ _tag: 'transfer' }); |
|
} |
|
|
|
/** 转办 */ |
|
function handleTransferUserPicker() { |
|
openModal(true,{ _tag: 'transfer' }); |
|
} |
|
|
|
/** 委托 */ |
|
function handleDelegateUserPicker() { |
|
openModal(true,{ _tag: 'delegate' }); |
|
} |
|
|
|
/** 终止 */ |
|
function stop() { |
|
createConfirm({ |
|
iconType: 'warning', |
|
title: '警告', |
|
content: '确定终止流程吗?', |
|
onOk: async () => { |
|
await stopProcessInstance(this.processInsId, this.auditForm.message); |
|
await close(); |
|
await push({path: '/workflow/task/index'}); |
|
cc({processInsId: state.processInsId}); |
|
} |
|
}); |
|
} |
|
|
|
/** 打印 */ |
|
function print() { |
|
console.warn('---工作流表单打印成功,此处可以做一些打印回调逻辑处理!---'); |
|
} |
|
|
|
/** 驳回到任意节点 */ |
|
function rollBack() { |
|
openRollBackTask(true,{ taskId: this.taskId }); |
|
} |
|
|
|
/** 加签 */ |
|
function addMultiInstance() { |
|
// flowable开源版加签会导致模板数量急剧扩大,需要经过内部讨论是否有比较好的解决方案:http://www.pangubpm.com/doc58.html |
|
notification.warn({ |
|
message: '提示', |
|
description: '功能正在开发中...' |
|
}); |
|
} |
|
|
|
/** 减签 */ |
|
function delMultiInstance () { |
|
notification.warn({ |
|
message: '提示', |
|
description: '功能正在开发中...' |
|
}); |
|
} |
|
|
|
/** 自定义按钮提交 */ |
|
function commit(vars?: Recordable) { |
|
// 外置表单审批 |
|
if (state.formType === '2') { |
|
formPreview.value.auditTask(async () => { |
|
await auditTask({ |
|
taskId: this.taskId, |
|
taskDefKey: this.taskDefKey, |
|
processInsId: this.processInsId, |
|
processDefId: this.processDefId, |
|
activityCommentInfo: this.auditForm, |
|
assignee: this.auditForm.assignee, |
|
vars, |
|
}); |
|
await close(); |
|
await push({ path: '/workflow/task/index' }); |
|
cc({ processInsId: state.processInsId }); |
|
}); |
|
// 动态表单审批 |
|
} else { |
|
formPreview.value.auditFormTask({ |
|
taskId: this.taskId, |
|
processInsId: this.processInsId, |
|
activityCommentInfo: this.auditForm, |
|
assignee: this.auditForm.assignee, |
|
vars, |
|
}, () => { |
|
close(); |
|
push({ path: '/workflow/task/index' }); |
|
cc({ processInsId: state.processInsId }); |
|
}); |
|
} |
|
} |
|
|
|
/** 处理回退到任意节点 */ |
|
async function handleRollBackTask(backTaskDefKey: string) { |
|
await rejectTask({ |
|
currentTaskId: this.taskId, |
|
rollBackTaskDefKey: backTaskDefKey, |
|
comment: this.auditForm |
|
}); |
|
await close(); |
|
await push({ path: '/workflow/task/index' }); |
|
cc({ processInsId: state.processInsId }); |
|
} |
|
|
|
/** 处理转派任务 */ |
|
async function handleTransferTask(userList: KiccUser[]) { |
|
await transferTask( this.taskId, userList[0].id); |
|
await close(); |
|
await push({ path: '/workflow/task/index' }); |
|
cc({ processInsId: state.processInsId }); |
|
} |
|
|
|
/** 处理任务委派 */ |
|
async function handleDelegateTask(userList: KiccUser[]) { |
|
await delegateTask(this.taskId, userList[0].id); |
|
await close(); |
|
await push({ path: '/workflow/task/index' }); |
|
cc({ processInsId: state.processInsId }); |
|
} |
|
|
|
function submit(button: WorkflowButton) { |
|
// 设置流程变量 |
|
const vars: Recordable = {}; |
|
// 流程表单标题 |
|
vars.title = state.formTitle; |
|
// 指定的下一步骤处理人 |
|
vars.assignee = state.auditForm.assignee; |
|
// 操作类型 |
|
state.auditForm.mesCode = button.code; |
|
// 操作名称 |
|
state.auditForm.mesName = button.name; |
|
switch (button.code) { |
|
// 启动流程 |
|
case '_workflow_activity_start': |
|
start(vars); |
|
break; |
|
// 保存草稿 |
|
case '_workflow_activity_save': |
|
save(); |
|
break; |
|
// 同意 |
|
case '_workflow_activity_agree': |
|
agree(); |
|
break; |
|
// 驳回 |
|
case '_workflow_activity_reject': |
|
reject(); |
|
break; |
|
// 驳回到任意步骤 |
|
case '_workflow_activity_roll_back': |
|
rollBack(); |
|
break; |
|
// 加签 |
|
case '_workflow_activity_add_multi_instance': |
|
addMultiInstance(); |
|
break; |
|
// 减签 |
|
case '_workflow_activity_del_multi_instance': |
|
delMultiInstance(); |
|
break; |
|
// 转办 |
|
case '_workflow_activity_transfer': |
|
handleTransferUserPicker(); |
|
break; |
|
// 外派 |
|
case '_workflow_activity_delegate': |
|
handleDelegateUserPicker(); |
|
break; |
|
// 终止 |
|
case '_workflow_activity_stop': |
|
stop(); |
|
break; |
|
// 打印 |
|
case '_workflow_activity_print': |
|
print(); |
|
break; |
|
// 自定义按钮提交 |
|
default: |
|
commit(vars); |
|
} |
|
} |
|
|
|
const getWrapFormFooterStyle = computed((): CSSProperties | Recordable => { |
|
return { |
|
width: unref(getIsMobile) ? '100%' : unref(getCalcContentWidth) |
|
}; |
|
}); |
|
|
|
function handleTabsClick(activeKey: string, event: MouseEvent) { |
|
// fixme: tabs组件首次加载不能初始化未激活的tab中的组件 |
|
if (activeKey === 'processChart' && !workflowChart.value) { |
|
setTimeout(() => { |
|
// 获取流程图数据 |
|
workflowChart.value.init(state.processInsId, state.processDefId); |
|
}, 100); |
|
} |
|
} |
|
|
|
</script> |
|
<style lang="less" scoped> |
|
@prefix-cls: ~'@{namespace}-task-form'; |
|
|
|
.@{prefix-cls} { |
|
.workflow-form { |
|
&__footer{ |
|
height: 66px; |
|
background: #fff; |
|
display: flex; |
|
gap: 11px; |
|
flex-flow: row nowrap; |
|
justify-content: center; |
|
align-items: center; |
|
-webkit-box-shadow: 0 -3px 5px 0 rgba(0,0,0,.12); |
|
box-shadow: 0 -3px 5px 0 rgba(0,0,0,.12); |
|
-webkit-transition: inline-block 0.3s, left 0.3s, width 0.3s, margin-left 0.3s, font-size 0.3s; |
|
transition: inline-block 0.3s, left 0.3s, width 0.3s, margin-left 0.3s, font-size 0.3s; |
|
position: fixed; |
|
bottom: 0; |
|
z-index: @page-footer-z-index; |
|
} |
|
} |
|
} |
|
|
|
.ant-form { |
|
.ant-form-item { |
|
margin-bottom: 15px; |
|
} |
|
} |
|
</style>
|
|
|