有奖:语音产品征文挑战赛火热进行中> HOT
本文将介绍在审批详情界面构建完之后的数据交互逻辑,也就是审批相关的操作。审批操作主要分为两大类:提交阶段审批阶段。
说明:
这一环节是在审批详情页面数据初始化和审批详情页面 UI 构建完毕的基础上进行。如果不了解上述这两个过程,可参见审批详情模板介绍下的 审批详情页 UI 构建 以及 审批详情页面数据初始化

提交阶段

这个阶段操作区域会设置这几个操作: 抄送保存草稿提交申请取消

提交申请

提交申请的入口在流程操作中的提交申请中。这个交互的操作逻辑是:
执行绑定的 onSubmitApply 方法,传递 buttonAction 和 operationName 这两个参数。
buttonAction 是请求 CompleteTask 接口的入参之一,告知后端当前操作是什么,即起到一个操作编码的作用。
operationName 是当前的操作标识。因为 onSubmitApply 这个方法是和保存草稿共用的,且 onSubmitApply 中会针对提交申请进行特殊的逻辑,故需要传此参数。
({
buttonAction: 1,
operationName: "apply",
});
onSubmitApply 内触发 completeTask 方法。
completeTask 中 approvalInfo 这个参数中会设置 CcUserIds 属性,用 applyCcUserIds 这个变量赋值。
提交申请后的回调。
onSubmitApply 针对提交申请操作设置了一个回调,重新进行页面初始化的,并且会将当前的页面类型变为 CREATE。
// 如果当前操作类型是apply即提交申请,在请求成功后,重新初始化审批页面,并将页面内部页面类型变量设置为CREATE,配合相应操作按钮渲染
if (data.target.operationName === "apply") {
$page.handler.initApprovalPage(
$page.dataset.state.internalFlowTaskId,
"CREATE"
);
}

涉及到的变量

变量
描述
applyCcUserIds
提交申请阶段抄送人员变量。

涉及到的方法

方法
描述
initApprovalPage
初始化审批页面数据。
onSubmitApply
提交申请点击事件。

保存草稿

此操作逻辑与提交审批的逻辑相同,区别是此操作没有完成审批操作后的回调操作。

抄送

单击抄送实际上是呼出成员选择组件弹窗,在选择好成员之后会触发成员选择组建的值改变事件,这个事件绑定了 onCcUserIdsChange 这个方法。此方法中主要就是针对 applyCcUserIds 这个变量进行一个赋值操作。
当然这里也可以通过编辑器自带的变量赋值操作也可以实现

涉及到的变量

applyCcUserIds:提交申请阶段抄送人员变量。
变量
描述
applyCcUserIds
提交申请阶段抄送人员变量。

涉及到的方法

方法
描述
onCcUserIdsChange
提交阶段给抄送用户变量赋值。

取消

取消按钮这里绑定了一个编辑器自带的返回上一页的事件,这里可以根据具体的业务需要进行调整。

审批阶段

这个阶段操作区域会设置这几个操作: 同意拒绝处理加签转办回退撤销

同意

同意按钮会绑定 onOpenApprovalModal 方法,并且传递入参。
({ buttonAction: 2, operationName: "agree" });
表单校验没问题的话会打开审批表单弹窗,填写完弹窗内审批表单之后,单击确定会执行 onSubmitApprovalForm,成功后关闭弹窗。

选择下一节点审批人

1. completeTask 这个方法中有一个逻辑是当 CompleteTask 请求结果中 ToBeSubmittedFlowTaskFlag 的值为 true 时会触发选择下一节点审批人弹窗前提是当前审批任务对应的审批节点配置了上一节点指定处理人且当前操作为同意这个条件成立后,在同意结束后会弹出下一节点审批人弹窗。
2. 在上一章提到下一节点审批人弹窗的构成。是一组审批人任务列表构成的多选列表。多选列表绑定的值改变事件为 onFlowTaskCheckboxChange。具体逻辑请参见 onFlowTaskCheckboxChange 方法中的注释。
3. 在选择完之后,会进行一个 onSubmitFlowTaskList 提交审批任务点击事件的操作。这个事件绑定在弹出的确认和取消按钮上。绑定在取消按钮上时,提交审批任务会将所有下一节点审批人进行提交。避免流程因下一节点没选审批人导致整个流程无法进行下去的情况。

涉及到的方法

方法
描述
onOpenApprovalModal
打开审批表单弹窗。
onSubmitApprovalForm
提交审批表单。
onFlowTaskCheckboxChange
下一节点审批人多选组件值变更事件。
onSubmitFlowTaskList
提交审批任务点击事件。

拒绝

拒绝的操作逻辑整体与同意的相同,区别是入参的传递。
({ buttonAction: 3, operationName: "reject" });

处理

处理的操作逻辑整体与同意的相同,区别是入参的传递。
({ buttonAction: 2, operationName: "handle" });

加签

单击加签,会呼出成员选择弹窗,单击弹窗中的确定会触发成员选择组建的值改变事件,该事件绑定了 onAssigneeUserChange 方法。这个方法主要接收成员选择组件传递的参数,请求 AddCounterSignAssignee APIs 接口。 具体逻辑请参见 onAssigneeUserChange 方法内的注释。

涉及到的方法

方法
描述
onAssigneeUserChange
执行加签请求。

转办

单击转办,会呼出成员选择弹窗,单击弹窗中的确定会触发成员选择组建的值改变事件,该事件绑定了 onTurnToUserChange 方法。onTurnToUserChange 这个方法接收成员选择组件传递的参数,回去执行 completeTask 这个方法。其中要关注 completeTask 方法中 approvalInfo 入参中的 TurnToUser 参数传递。 TurnToUser中的 Type 属性值 依赖已选转办用户中的 type 属性。具体取值见下方代码:
approvalInfo: {
TurnToUser: {
// selectUser 是已选转办用户
Type: selectUser?.type < 2 ? 4 : 1,
UserId: selectUser?.userId,
},
},

涉及到的方法

方法
描述
onTurnToUserChange
转办选人组件值变更事件。

回退

回退操作包含两块逻辑,一个是打开回退弹窗的一系列逻辑,然后就是请求回退 APIs 的过程。
打开弹窗。
回退按钮绑定了 onOpenRollbackModal 这个方法,方法内的逻辑概括如下:
弹出回退弹窗。
获取可回退的节点列表请求。
将请求结果进行处理然后复制给 preElementList 这个变量。
回退弹窗中的内容为一个单选组件,这个组件选项绑定了 preElementList 这个变量。请求回退列表并完成赋值之后,弹窗中就会有可回退节点选项了。
选择节点。
可回退单选组件的值改变事件也绑定了一个方法 onRollbackRadioChange。将组件传递的参数进行一个转换,整理出 TargetNodeId 和 TargetActTaskId,组装成一个对象,给 selectedRollbackNode 这个变量赋值,用于后续的回退请求。
export default function ({ event, data }) {
const selectedNodeId = event.detail.value;
// 将所选值处理后赋值给已选回退节点变量
$page.dataset.state.selectedRollbackNode = {
TargetNodeId: event.detail.value,
TargetActTaskId: $page.dataset.state.preElementList.find(
(item) => item.value === selectedNodeId
)?.ActTaskId,
};
}
回退请求。
选择完回退节点之后,单击确定,确定按钮点击事件绑定了一个 onConfirmRollBack 方法,利用前一步设置好的 selectedRollbackNode 变量进行请求。
针对 hasRollback 变量赋值。
// 回退之后当前任务被销毁 故回退之后不重新请求流程页面数据,页面仅保留一份当前数据缓存
// 【注意】这里由于任务已销毁,用户自己手动刷新页面的话,也不会请求到当前任务数据,建议此处设置一个跳转链接,亦或是关闭当前页面
$page.dataset.state.hasRollback = true;

涉及到的方法

方法
描述
onOpenRollbackModal
打开回退弹窗事件。
onConfirmRollBack
确认回退。

涉及到的变量

变量
描述
rollbackModalVisible
回退弹窗是否可见。
preElementList
可回退节点列表。
selectedRollbackNode
已选回退节点。
hasRollback
是否已经回退。

撤销

撤销按钮点击事件绑定了 onOpenRevokeConfirm 这个方法,这里的交互过程主要是:单击撤销后弹出一个 modal 确认框,单击确认,就执行撤销请求。 这里用到了微搭提供的 $w.utils.showModal 显示模态弹框这个交互方法,请参见 交互方法
/** onOpenRevokeConfirm */
export default function ({ event, data }) {
$w.utils.showModal({
title: "撤销流程提醒",
content: "流程正在进行中,确认撤销此流程?",
success(res) {
if (res.confirm) {
revoke();
}
},
});
}
async function revoke() {
// ...执行撤销逻辑
}

涉及到的方法

方法
描述
onOpenRevokeConfirm
打开回退弹窗事件。

通用方法介绍

completeTask

模板中预置的 completeTask 方法被以下操作调用:
保存草稿。
提交审批。
同意。
拒绝。
转办。
处理。
这一方法的核心逻辑就是请求工作流 APIs 中 CompleteTask 操作审批任务这个接口,然后就是请求成功后的数据刷新以及可能会进行的触发下一节点审批人操作。

参数解构

方法开始部分是对参数的解构。参数有 approvalInfo 审批信息和 config 配置。
// 从审批弹窗表格信息中解构完成审批请求所需的参数
const { Comment, TurnToUser, CcUserIds } = approvalInfo || {};
// 从审批配置中解构buttonAction 和成功后的回调successCallback
const { buttonAction, successCallback } = config || {};
对于 approvalInfo 参数解构出来的变量。
Comment:审批意见,在同意、拒绝、处理 这三个操作中会传入。
TurnToUser:主要来源于转办操作。
CcUserIds:抄送人员 ID,在同意、处理操作下传入,当然也会在提交审批时传入特此针对代码中的注释进行纠正
以上这些结构出来的变量后续都会作为请求 CompleteTask 接口的入参。 config 则会解构出:
buttonAction:操作编码,每个调用这个方法的操作必传。 这个变量将会作为请求 CompleteTask 接口的入参之一。
successCallback:成功后的回调,可选,后续接口请求成功后执行。

获取表单信息

CompleteTask 接口的入参还有一项是 PageParamsJsonStr,其实就是审批页面表单的值,对应流程中的输出变量。模板中封装了一个 getPageParams 方法获取这个参数。
这个方法最终会返回一个以下对象类型的字符串。
{
"{{VariableKey}}": {
Type: "object",
Value: {{ FormValue }}
}
}
为了获取这种格式的值,首先要获取当前表单的值。
// 获取当前页面表单值
const currentFormData = await $page.handler.getFormValue();
通过模板中预置的 getFormValue 这个方法,我们可以得到当前页面中所有表单的值,数据格式示例如下,其中对象的每个属性名就是数据源标识,属性值为表单值。
{
sjsl1_7gsct06: {
a: '1',
b: 2
},
sjsl2_0bksafz: {
x: 'test',
y: 20
},
}
接口要的是输出变量,有可能当前页面中的表单对应的不是输出变量,这个时候可以依靠审批页面详情变量信息获取到当前审批页面的输出变量有哪些。
const outEntityCodes =
$page.dataset.state.approvalPageDetail?.OutEntityCodes || [];
outEntityCodes 数据格式实例如下,其中 EntityCode 的值就对应数据数据源的标识。
[
{
EntityCode: "sjsl1_7gsct06",
// 其余属性在这个方法中不关注,暂不进行展示
// ...
},
];
这样通过遍历 outEntityCodes 输出变量,通过 EntityCode,即可得到对应的表单的值。为了确保我们最终得到的数据完整性,我们还需要获取变量原始的值。这个值存在审批页面详情变量中,通过以下路径可以获得。
const originObjectMap =
$page.dataset.state.approvalPageDetail?.VariableInfos?.ObjectMap || [];
然后在遍历 outEntityCodes 的过程中,就是取值、合并的过程。
取变量原始值 FieldValueMap。
// 先找到当前输出变量对应的业务数据信息
const originVariableInfo = originObjectMap?.find(
(info) => info?.EntityCode === item?.EntityCode
);
const { FieldValueMap } = originVariableInfo || {};
取变量对应的表单的值 currentFieldValueMap。
const currentFieldValueMap = currentFormData?.[item?.EntityCode] || {};
合并 FieldValueMap 和 currentFieldValueMap 并移除关联字段。移除关联字段是为了通过后端对数据源的校验
// 声明一个方法处理最终的Value
const getEfficientValue = () => {
// 合并表单中的数据和原变量中的数据
const assignParams = Object.assign(
{},
{
...(FieldValueMap || {}),
...currentFieldValueMap,
}
);
// 申明一个新值作为新变量
const newValue = {};
// 移除掉关联关系字段
Object.keys(assignParams).forEach((key) => {
if (key.startsWith("@") || key.startsWith("#")) {
return;
}
newValue[key] = assignParams[key];
});
// 返回移除关联字段之后的新变量
return newValue;
};
最终完整的逻辑如下。这里要注意的是,得到最终的对象之后,要进行两次 JSON.stringify 操作。
async function getPageParams() {
// 获取当前页面表单值
const currentFormData = await $page.handler.getFormValue();
// 获取当前页面输出变量信息
const outEntityCodes =
$page.dataset.state.approvalPageDetail?.OutEntityCodes || [];
// 获取当前流程的业务数据结果(页面输入变量信息)
const originObjectMap =
$page.dataset.state.approvalPageDetail?.VariableInfos?.ObjectMap || [];
// 声明pageParams
const pageParams = {};
// 遍历页面输出变量信息,
outEntityCodes?.forEach((item) => {
// 先找到当前输出变量对应的业务数据信息
const originVariableInfo = originObjectMap?.find(
(info) => info?.EntityCode === item?.EntityCode
);
const { FieldValueMap } = originVariableInfo || {};
// 找到变量kv映射
const currentFieldValueMap = currentFormData?.[item?.EntityCode] || {};

// 声明一个方法处理最终的Value
const getEfficientValue = () => {
// 合并表单中的数据和原变量中的数据
const assignParams = Object.assign(
{},
{
...(FieldValueMap || {}),
...currentFieldValueMap,
}
);
// 申明一个新值作为新变量
const newValue = {};
// 移除掉关联关系字段
Object.keys(assignParams).forEach((key) => {
if (key.startsWith("@") || key.startsWith("#")) {
return;
}
newValue[key] = assignParams[key];
});
// 返回移除关联字段之后的新变量
return newValue;
};
// 拼装pageParams要求的格式数据
pageParams[item.VariableKey] = {
Type: item?.Type,
Value: getEfficientValue(),
};
});
// 转换成字符串用于APIs作为参数进行请求(前端需要对内容进行两次转义)
return JSON.stringify(JSON.stringify(pageParams));
}

请求 APIs

到这一步已经可以拿到所有 CompleteTask 请求所需要的参数了。
const result = await app.cloud.callConnector({
name: "workflow",
methodName: "CompleteTask",
params: {
/** TaskId - 任务ID。取 processInstance 变量中的TaskId*/
TaskId: $page.dataset.state.processInstance.TaskId,
/** buttonAction - 操作编码。来源于`completeTask`入参解构 */
ButtonAction: buttonAction,
/** Comment - 审批意见信息。来源于`completeTask`入参解构 */
Comment,
/** 页面参数字符串,来源于 getPageParams方法的返回*/
PageParamsJsonStr: pageParams,
/** TurnToUser - 转办人。来源于`completeTask`入参解构 */
TurnToUser,
/** CcUserIds - 抄送人。来源于`completeTask`入参解构 */
CcUserIds,
/**NeedUserExtraInfo - 是否返回筛选审批人详细信息, 这个请求中传false即可*/
NeedUserExtraInfo: false,
/** OpinionUserMapJsonStr 通知人 map字符串*/
OpinionUserMapJsonStr: getOpinionUserMap(),
},
});
说明:
关于 OpinionUserMapJsonStr 参数 ,是通过 getOpinionUserMap 这个方法计算获取。逻辑也比较单纯,就是根据 opinionUserList 这个变量进行一个转换。
当前已生成的页面模板如存在 OpinionUserMap: getOpinionUserMap() 的情况可以在 completeTask 方法中将 OpinionUserMap 更改为 OpinionUserMapJsonStr。

请求成功后的处理

请求成功后主要有以下几个逻辑执行。
成功后执行回调
// 成功后执行回调
successCallback?.();
刷新页面数据,更新当前页面类型。
// 如果buttonAction不是保存草稿,则需要重新初始化审批页面数据,将当前内置的页面类型更新为DONE
if (buttonAction !== 17) {
$page.handler.initApprovalPage(
$page.dataset.state.internalFlowTaskId,
"DONE"
);
}
触发下一节点审批人提交。
// 如果 是否弹出筛选审批人列表页面 为true 则触发下一节点审批人弹窗
if (result.Response.Data.ToBeSubmittedFlowTaskFlag) {
triggerFlowTaskSelect(result.Response.Data.ToBeSubmittedFlowTaskInfo);
}
这里 triggerFlowTaskSelect 主要是针对下一节点审批人的任务列表进行处理、以及触发下一节点审批人弹窗的展示。具体逻辑参见 completeTask 内的 triggerFlowTaskSelect 注释。

涉及到的变量

变量
描述
processInstance
流程实例信息。
internalFlowTaskId
当前流程任务 ID。
approvalPageDetail
审批页面详情。
opinionUserList
通知用户列表。
flowTaskInfoList
下一节点审批人列表。
selectedFlowTaskIds
已选流程任务 ID。

涉及到的方法

方法
描述
initApprovalPage
初始化审批页面数据。
getFormValue
获取所有表单的值。

onOpenApprovalModal

这个方法会被以下几个操作绑定:
同意。
拒绝。
处理。
主要会进行以下几个逻辑:

记录操作信息

将操作传入的参数传递给 currentOperation 这个变量。
// 记录当前操作信息
$page.dataset.state.currentOperation = data.target;

表单校验

进行表单验证表单操作,没有问题再往下进行。
// 表单验证
const validateResult = await $page.handler.validateFormValue();
// 验证不通过给出提示
if (validateResult) {
$w.utils.showToast({
title: "表单填写错误 请检查",
icon: "error",
duration: 2000, // 2秒
});
return;
}

触发表单弹窗显示

// 验证无误后弹出表单弹窗
$page.dataset.state.approvalModalVisible = true;

涉及到的变量

变量
描述
approvalModalVisible
审批弹窗是否显示。
currentOperation
当前操作信息。

涉及到的方法

方法
描述
validateFormValue
验证审批表单。

onSubmitApprovalForm

这个方法是审批弹窗中单击底部默认按钮绑定的方法。
涉及到的操作:
同意
拒绝
处理
这里的逻辑也比较清晰,主要有以下几个过程:
验证当前审批表单。
验证成功后获取表单信息。
调用 completeTask 方法执行完成审批任务请求,请求成功后关闭弹窗。
export default async function ({ event, data }) {
// 验证当前审批表单
const validateResult = await $w._internalApprovalForm.validate();
if (Object.keys(validateResult).length) return;
// 验证成功后获取表单信息
const approvalFormValue = await $w._internalApprovalForm.submit();
// 调用completeTask方法执行完成审批任务请求
$page.handler.completeTask({
approvalInfo: approvalFormValue,
config: {
buttonAction: $page.dataset.state.currentOperation.buttonAction,
successCallback: () => {
$page.handler.onCloseApprovalModal({});
},
},
});
}

涉及到的变量

变量
描述
currentOperation
前操作信息。

涉及到的方法

方法
描述
onCloseApprovalModal
关闭审批弹窗。