1.前言
此文章用于记录二次开发的若依Flowable更新过程,具体可以查看我的文章《以若依Flowable工作流版本(RuoYi-Vue-Flowable)为基础,进行二次开发》和《以若依移动端版为基础,实现uniapp的flowable流程管理》,分别对应PC端和uniapp端。2025年3月4日之前的更新内容,只会介绍更新内容,不附带代码,之后更新的会附带代码。并且,如果不表明某一端,就代表双端同步修改。
2.更新记录
1.2025年2月13日
1.解决流程申请人无法审批问题。
2.解决PC端新增流程时,名称输入框回车导致页面强制刷新问题。
2.2025年2月17日
1.解决上次更新时,代码不规范问题。
2.解决执行监听器的事件类型等属性是英文的问题。
3.解决新增或删除执行监听器或任务监听器导致数据丢失的问题。
3.2025年3月4日
1.解决流程当中节点无法填写表单或者上传文件问题。
2.解决uniapp端待办任务处理后,数据更新不及时问题。
1.后端
1.打开com.ruoyi.flowable.controller.FlowTaskController类,找到complete方法,删除itemList参数。
2.打开com.ruoyi.flowable.service.impl.FlowTaskServiceImpl类,找到complete方法,用以下代码替换。
public AjaxResult complete(FlowTaskVo taskVo, Map<String, Object> variables) {
Task task = taskService.createTaskQuery().taskId(taskVo.getTaskId()).singleResult();
if (Objects.isNull(task)) {
return AjaxResult.error("任务不存在");
}
if (DelegationState.PENDING.equals(task.getDelegationState())) {
taskService.addComment(taskVo.getTaskId(), taskVo.getInstanceId(), FlowComment.DELEGATE.getType(), taskVo.getComment());
taskService.resolveTask(taskVo.getTaskId(), taskVo.getVariables());
} else {
taskService.addComment(taskVo.getTaskId(), taskVo.getInstanceId(), FlowComment.NORMAL.getType(), taskVo.getComment());
Long userId = getLoginUser().getUser().getUserId();
taskService.setAssignee(taskVo.getTaskId(), userId.toString());
taskService.complete(taskVo.getTaskId(), taskVo.getVariables());
}
if (variables != null) {
Object itemList = variables.get("itemList");
Object nodeItemList = variables.get("nodeItemList");
String procInsId = taskVo.getInstanceId();
// 普通表单直接修改
if (com.ruoyi.common.utils.StringUtils.isNotNull(itemList)) {
// 修改变量列表
sysFlowableFormVariableService.updateSysFlowableFormVariableList(variables.get("itemList"), procInsId);
} else if (com.ruoyi.common.utils.StringUtils.isNotNull(nodeItemList)) {
// 节点表单需要判断是否保存,如果保存就修改,如果没有保存就新增
ArrayList<SysFlowableFormVariable> formVariableList = sysFlowableFormVariableService.getFormVariableList(nodeItemList, procInsId);
if (formVariableList.size() > 0) {
SysFlowableFormVariable sysFlowableFormVariable = formVariableList.get(0);
Long varId = sysFlowableFormVariable.getVarId();
// 如果存在id,直接更新;否则,新增数据
if (com.ruoyi.common.utils.StringUtils.isNotNull(varId)) {
sysFlowableFormVariableService.updateSysFlowableFormVariableList(formVariableList);
} else {
sysFlowableFormVariableService.insertSysFlowableFormVariableList(formVariableList);
}
}
}
}
return AjaxResult.success();
}
2.PC端
1.打开src\views\flowable\task\todo\detail\index.vue文件,用以下代码替换。
<template>
<div class="app-container">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span class="el-icon-document">待办任务</span>
<el-tag style="margin-left:10px">发起人:{
{ startUser }}</el-tag>
<el-tag>任务节点:{
{ taskName }}</el-tag>
<el-button style="float: right;" size="mini" type="danger" @click="goBack">关闭</el-button>
</div>
<el-tabs tab-position="top" v-model="activeName" @tab-click="handleClick">
<!--表单信息-->
<el-tab-pane label="表单信息" name="1">
<el-col :span="16" :offset="4">
<component :is="currentComponent" :fill="hasFillNode" :formData="componentForm"
:currentNode="currentFlowRecord" ref="currentComponent" @saveForm="saveForm" class="notFill"
@openComplete="handleCompleteOpenFromNodeForm"></component>
<div style="margin-left:10%;margin-bottom: 20px;font-size: 14px;">
<div v-if="loginUserId == startUserId && hasFillNode">
<el-button type="primary" @click="submitCurrentForm(false)">保 存</el-button>
<el-button type="success" @click="submitCurrentForm(true)">启 动</el-button>
</div>
<div v-else>
<el-button type="primary" @click="handleComplete">审 批</el-button>
<el-button type="warning" @click="handleReturn">选 择 退 回</el-button>
<el-button type="danger" @click="handleReject">退 回 上 级</el-button>
<el-button type="danger" @click="handleReturnFirst">退 回 初 始</el-button>
</div>
</div>
</el-col>
</el-tab-pane>
<!--流程流转记录-->
<el-tab-pane label="流转记录" name="2">
<!--flowRecordList-->
<el-col :span="16" :offset="4">
<div class="block">
<el-timeline>
<el-timeline-item v-for="(item,index ) in flowRecordList" :key="index" :icon="setIcon(item.finishTime)"
:color="setColor(item.finishTime)">
<p style="font-weight: 700">{
{ item.taskName }}</p>
<el-card :body-style="{ padding: '10px' }">
<el-descriptions class="margin-top" :column="1" size="small" border>
<el-descriptions-item v-if="item.assigneeName" label-class-name="my-label">
<template slot="label"><i class="el-icon-user"></i>办理人</template>
{
{ item.assigneeName }}
<el-tag type="info" size="mini">{
{ item.deptName }}</el-tag>
</el-descriptions-item>
<el-descriptions-item v-if="item.candidate && item.taskDefKey != lastFlowRecord.taskDefKey"
label-class-name="my-label">
<template slot="label"><i class="el-icon-user"></i>候选办理</template>
{
{item.candidate}}
</el-descriptions-item>
<el-descriptions-item
v-else-if="item.candidate && item.taskDefKey == lastFlowRecord.taskDefKey && lastFlowRecord.comment && index == 0"
label-class-name="my-label">
<template slot="label"><i class="el-icon-user"></i>办理人</template>
{
{lastFlowRecord.comment.comment ? lastFlowRecord.comment.comment.substring(0,
lastFlowRecord.comment.comment.indexOf("发起流程申请")) : ""}}
<el-tag type="info" size="mini">流程发起人</el-tag>
</el-descriptions-item>
<el-descriptions-item label-class-name="my-label">
<template slot="label"><i class="el-icon-date"></i>接收时间</template>
{
{ item.createTime }}
</el-descriptions-item>
<el-descriptions-item v-if="item.finishTime" label-class-name="my-label">
<template slot="label"><i class="el-icon-date"></i>处理时间</template>
{
{ item.finishTime }}
</el-descriptions-item>
<el-descriptions-item v-if="item.duration" label-class-name="my-label">
<template slot="label"><i class="el-icon-time"></i>耗时</template>
{
{ item.duration }}
</el-descriptions-item>
<el-descriptions-item v-if="item.comment" label-class-name="my-label">
<template slot="label"><i class="el-icon-tickets"></i>处理意见</template>
{
{ item.comment.comment }}
</el-descriptions-item>
</el-descriptions>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
</el-col>
</el-tab-pane>
<!--流程图-->
<el-tab-pane label="流程图" name="3">
<bpmn-viewer :flowData="flowData" :procInsId="taskForm.procInsId" />
</el-tab-pane>
</el-tabs>
<!--审批任务-->
<el-dialog :title="completeTitle" :visible.sync="completeOpen" width="60%" append-to-body>
<el-form ref="taskForm" :model="taskForm">
<el-form-item prop="targetKey">
<flow-user v-if="checkSendUser" :checkType="checkType" @handleUserSelect="handleUserSelect"></flow-user>
<flow-role v-if="checkSendRole" @handleRoleSelect="handleRoleSelect"></flow-role>
</el-form-item>
<el-form-item label="处理意见" label-width="80px" prop="comment"
:rules="[{ required: true, message: '请输入处理意见', trigger: 'blur' }]">
<el-input type="textarea" v-model="taskForm.comment" placeholder="请输入处理意见" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="completeOpen = false">取 消</el-button>
<el-button type="primary" @click="handleTaskComplete">确 定</el-button>
</span>
</el-dialog>
<!--退回流程-->
<el-dialog :title="returnTitle" :visible.sync="returnOpen" width="40%" append-to-body>
<el-form ref="taskForm" :model="taskForm" label-width="80px">
<el-form-item label="退回节点" prop="targetKey">
<el-radio-group v-model="taskForm.targetKey">
<el-radio-button v-for="item in returnTaskList" :key="item.id" :label="item.id">{
{ item.name }}
</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="退回意见" prop="comment" :rules="[{ required: true, message: '请输入意见', trigger: 'blur' }]">
<el-input style="width: 50%" type="textarea" v-model="taskForm.comment" placeholder="请输入意见" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="returnOpen = false">取 消</el-button>
<el-button type="primary" @click="taskReturn">确 定</el-button>
</span>
</el-dialog>
<!--驳回流程-->
<el-dialog :title="rejectTitle" :visible.sync="rejectOpen" width="40%" append-to-body>
<el-form ref="taskForm" :model="taskForm" label-width="80px">
<el-form-item label="驳回意见" prop="comment" :rules="[{ required: true, message: '请输入意见', trigger: 'blur' }]">
<el-input style="width: 50%" type="textarea" v-model="taskForm.comment" placeholder="请输入意见" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="rejectOpen = false">取 消</el-button>
<el-button type="primary" @click="taskReject">确 定</el-button>
</span>
</el-dialog>
<!--驳回流程-->
<el-dialog :title="returnFirstTitle" :visible.sync="returnFirstOpen" width="40%" append-to-body>
<el-form ref="taskForm" :model="taskForm" label-width="80px">
<el-form-item label="退回意见" prop="comment" :rules="[{ required: true, message: '请输入意见', trigger: 'blur' }]">
<el-input style="width: 50%" type="textarea" v-model="taskForm.comment" placeholder="请输入意见" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="returnFirstOpen = false">取 消</el-button>
<el-button type="primary" @click="taskFirstReturn">确 定</el-button>
</span>
</el-dialog>
</el-card>
</div>
</template>
<script>
import { flowRecord } from "@/api/flowable/finished";
import FlowUser from '@/components/flow/User'
import FlowRole from '@/components/flow/Role'
import { flowXmlAndNode } from "@/api/flowable/definition";
import {
complete,
rejectTask,
returnList,
returnTask,
getNextFlowNode,
delegate,
flowTaskForm
} from "@/api/flowable/todo";
import store from "@/store";
import settings from '@/store/modules/settings';
import { getFlowableFormByDeployId } from "@/api/flowable/flowableForm";
import { getFormVariableList, updateFormVariableList } from "@/api/flowable/flowableFormVariable";
import { getBpmnStartId, getVarType, getFormData } from "@/api/flowable/my";
import BpmnViewer from '@/components/Process/viewer';
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
name: "Record",
components: {
BpmnViewer,
FlowUser,
FlowRole,
},
props: {},
data() {
return {
eventName: "click",
// 流程数据
flowData: {},
activeName: '1',
// 遮罩层
loading: true,
flowRecordList: [], // 流程流转数据
rules: {}, // 表单校验
taskForm: {
returnTaskShow: false, // 是否展示回退表单
delegateTaskShow: false, // 是否展示回退表单
defaultTaskShow: true, // 默认处理
comment: "", // 意见内容
procInsId: "", // 流程实例编号
instanceId: "", // 流程实例编号
deployId: "", // 流程定义编号
taskId: "",// 流程任务编号
procDefId: "", // 流程编号
targetKey: "",
variables: {},
},
returnTaskList: [], // 回退列表数据
completeTitle: null,
completeOpen: false,
returnTitle: null,
returnOpen: false,
rejectOpen: false,
rejectTitle: null,
checkSendUser: false, // 是否展示人员选择模块
checkSendRole: false,// 是否展示角色选择模块
checkType: 'single', // 选择类型
taskName: null, // 任务节点
startUser: null, // 发起人信息,
multiInstanceVars: '', // 会签节点
formJson: {},
// 当前组件
currentComponent: undefined,
// 组件表单
componentForm: undefined,
// 发起人id
startUserId: undefined,
// 登录账号id
loginUserId: undefined,
// 流程图对应的id
procDefId: undefined,
// 退回初始标题
returnFirstTitle: null,
// 退回初始对话框
returnFirstOpen: false,
// 是否填写节点
hasFillNode: false,
// 是否启动流程
hasStart: false,
// 表单id对象
formIdObj: {},
// 最后一个流程记录,也就是流程开始节点信息
lastFlowRecord: undefined,
// 流程当前节点
currentFlowRecord: undefined,
// 审批时,节点表单
completeNodeForm: undefined
};
},
created() {
if (this.$route.query) {
this.taskName = this.$route.query.taskName;
this.startUser = this.$route.query.startUser;
this.taskForm.deployId = this.$route.query.deployId;
this.taskForm.taskId = this.$route.query.taskId;
this.taskForm.procInsId = this.$route.query.procInsId;
this.taskForm.executionId = this.$route.query.executionId;
this.taskForm.instanceId = this.$route.query.procInsId;
this.startUserId = this.$route.query.startUserId;
this.procDefId = this.$route.query.procDefId;
let taskDefKey = this.$route.query.taskDefKey;
// 获取开始节点信息,并且判断当前是否为填写节点
getBpmnStartId(this.procDefId).then(response => {
let currentKey = response.msg;
if (taskDefKey == currentKey) {
this.hasFillNode = true;
} else {
this.hasFillNode = false;
// 设置高亮颜色为主题颜色
document.documentElement.style.setProperty("--theme-color", settings.state.theme);
}
})
// 获取获取表单变量信息
this.getFormVariable(this.taskForm.procInsId).then(() => {
// 获取流程表单信息并且导入
this.getFormPathAndImport(this.taskForm.deployId);
});
this.getFlowRecordList(this.taskForm.procInsId, this.taskForm.deployId);
}
this.loginUserId = store.state.user.id;
},
methods: {
// 获取表单信息并且导入
getFormPathAndImport(deployId) {
getFlowableFormByDeployId(deployId).then(response => {
let data = response.data;
this.importComponent(data.formPath);
})
},
// 导入组件
importComponent(componentPath) {
if (!componentPath) {
return;
}
let prefixIndex = componentPath.indexOf("@/");
let suffixIndex = componentPath.lastIndexOf(".vue");
if (prefixIndex == -1) {
prefixIndex = 0;
} else {
prefixIndex = 2;
}
if (suffixIndex == -1) {
suffixIndex = componentPath.length;
}
let path = componentPath.substring(prefixIndex, suffixIndex);
this.currentComponent = resolve => require([`@/${path}.vue`], resolve);
},
// 获取表单变量信息
getFormVariable(procInsId) {
return new Promise((resolve, reject) => {
this.$modal.loading("正在获取数据...")
getFormVariableList({ procInsId }).then(response => {
let data = response.data;
let formData = getFormData(data);
this.saveformIdObj(data);
this.componentForm = formData;
this.$modal.closeLoading();
resolve();
})
})
},
// 将表表单id按照名称保存在对象中
saveformIdObj(data) {
let formIdObj = this.formIdObj;
data.forEach(element => {
formIdObj[element.varName] = element.varId;
});
},
handleClick(tab, event) {
if (tab.name === '3') {
flowXmlAndNode({ procInsId: this.taskForm.procInsId, deployId: this.taskForm.deployId }).then(res => {
this.flowData = res.data;
})
}
},
setIcon(val) {
if (val) {
return "el-icon-check";
} else {
return "el-icon-time";
}
},
setColor(val) {
if (val) {
return "#2bc418";
} else {
return "#b3bdbb";
}
},
// 用户信息选中数据
handleUserSelect(selection) {
if (selection) {
if (selection instanceof Array) {
const selectVal = selection.map(item => item.userId.toString());
if (this.multiInstanceVars) {
this.$set(this.taskForm.variables, this.multiInstanceVars, selectVal);
} else {
this.$set(this.taskForm.variables, "approval", selectVal.join(','));
}
} else {
this.$set(this.taskForm.variables, "approval", selection.userId.toString());
}
}
},
// 角色信息选中数据
handleRoleSelect(selection, roleName) {
if (selection) {
if (selection instanceof Array) {
const selectVal = selection.map(item => item.roleId.toString());
this.$set(this.taskForm.variables, "approval", selectVal.join(','));
} else {
this.$set(this.taskForm.variables, "approval", selection);
}
}
},
/** 流程流转记录 */
getFlowRecordList(procInsId, deployId) {
const that = this
const params = { procInsId: procInsId, deployId: deployId }
flowRecord(params).then(res => {
let flowRecordList = res.data.flowList;
that.flowRecordList = flowRecordList;
that.lastFlowRecord = flowRecordList[flowRecordList.length - 1];
that.currentFlowRecord = flowRecordList[0];
}).catch(res => {
this.goBack();
})
},
/** 流程节点表单 */
getFlowTaskForm(taskId) {
if (taskId) {
// 提交流程申请时填写的表单存入了流程变量中后续任务处理时需要展示
flowTaskForm({ taskId: taskId }).then(res => {
// 回显表单
this.$refs.vFormRef.setFormJson(res.data.formJson);
this.formJson = res.data.formJson;
this.$nextTick(() => {
// 加载表单填写的数据
this.$refs.vFormRef.setFormData(res.data);
// this.$nextTick(() => {
// // 表单禁用
// this.$refs.vFormRef.disableForm();
// })
})
});
}
},
/** 委派任务 */
handleDelegate() {
this.taskForm.delegateTaskShow = true;
this.taskForm.defaultTaskShow = false;
},
/** 返回页面 */
goBack() {
// 关闭当前标签页并返回上个页面
const obj = { path: "/task/todo", query: { t: Date.now() } };
this.$tab.closeOpenPage(obj);
},
// 获取表单想列表
getFormItemList(form) {
let formIdObj = this.formIdObj;
let itemList = [];
for (const key in form) {
if (Object.hasOwnProperty.call(form, key)) {
let eachObj = {};
const element = form[key];
eachObj["name"] = key;
eachObj["type"] = getVarType(element);
if (eachObj["type"] == "array") {
eachObj["value"] = JSON.stringify(element);
} else {
eachObj["value"] = element;
}
// 如果有id进行保存
if (Object.hasOwnProperty.call(formIdObj, key)) {
eachObj["id"] = formIdObj[key];
}
itemList.push(eachObj);
}
}
return itemList;
},
// 提交当前表单
submitCurrentForm(hasStart) {
this.hasStart = hasStart;
this.$refs.currentComponent.submitForm();
},
// 保存表单,并且启动程序
saveForm(form) {
let itemList = this.getFormItemList(form);
// 如果只是保存,不需要启动
if (!this.hasStart) {
let currentData = {
itemList,
procInsId: this.taskForm.procInsId
}
updateFormVariableList(currentData).then(response => {
this.$modal.msgSuccess("保存成功");
this.goBack();
})
return;
}
const params = { taskId: this.taskForm.taskId };
getNextFlowNode(params).then(res => {
const data = res.data;
if (data) {
if (data.dataType === 'dynamic') {
if (data.type === 'assignee') { // 指定人员
this.checkSendUser = true;
this.checkType = "single";
} else if (data.type === 'candidateUsers') { // 候选人员(多个)
this.checkSendUser = true;
this.checkType = "multiple";
} else if (data.type === 'candidateGroups') { // 指定组(所属角色接收任务)
this.checkSendRole = true;
} else { // 会签
// 流程设计指定的 elementVariable 作为会签人员列表
this.multiInstanceVars = data.vars;
this.checkSendUser = true;
this.checkType = "multiple";
}
} else {
// 如果启动,设置意见
if (this.hasStart) {
this.taskForm.comment = "启动流程";
}
// 设置表单信息
this.taskForm.itemList = itemList;
this.taskComplete();
}
}
})
},
// 启动程序
handleStart() {
const params = { taskId: this.taskForm.taskId }
getNextFlowNode(params).then(res => {
const data = res.data;
if (data) {
if (data.dataType === 'dynamic') {
if (data.type === 'assignee') { // 指定人员
this.checkSendUser = true;
this.checkType = "single";
} else if (data.type === 'candidateUsers') { // 候选人员(多个)
this.checkSendUser = true;
this.checkType = "multiple";
} else if (data.type === 'candidateGroups') { // 指定组(所属角色接收任务)
this.checkSendRole = true;
} else { // 会签
// 流程设计指定的 elementVariable 作为会签人员列表
this.multiInstanceVars = data.vars;
this.checkSendUser = true;
this.checkType = "multiple";
}
} else {
this.taskForm.comment = "启动程序";
this.taskComplete();
}
}
})
},
/** 驳回任务 */
handleReject() {
this.rejectOpen = true;
this.rejectTitle = "驳回流程";
},
/** 驳回任务 */
taskReject() {
this.$refs["taskForm"].validate(valid => {
if (valid) {
rejectTask(this.taskForm).then(res => {
this.$modal.msgSuccess(res.msg);
this.goBack();
});
}
});
},
/** 可退回任务列表 */
handleReturn() {
this.returnOpen = true;
this.returnTitle = "退回流程";
returnList(this.taskForm).then(res => {
this.returnTaskList = res.data;
})
},
/** 提交退回任务 */
taskReturn() {
this.$refs["taskForm"].validate(valid => {
if (valid) {
returnTask(this.taskForm).then(res => {
this.$modal.msgSuccess(res.msg);
this.goBack()
});
}
});
},
/** 取消回退任务按钮 */
cancelTask() {
this.taskForm.returnTaskShow = false;
this.taskForm.defaultTaskShow = true;
this.returnTaskList = [];
},
// 处理点击退回到初始节点操作
handleReturnFirst() {
this.returnFirstOpen = true;
this.returnFirstTitle = "退回初始流程";
},
// 提交退回初始任务
taskFirstReturn() {
this.$refs["taskForm"].validate(valid => {
if (valid) {
let obj = {
targetKey: "start_event",
taskId: this.taskForm.taskId,
comment: this.taskForm.comment
}
returnTask(obj).then(res => {
this.$modal.msgSuccess(res.msg);
this.goBack();
});
}
});
},
/** 委派任务 */
submitDeleteTask() {
this.$refs["taskForm"].validate(valid => {
if (valid) {
delegate(this.taskForm).then(response => {
this.$modal.msgSuccess(response.msg);
this.goBack();
});
}
});
},
/** 取消回退任务按钮 */
cancelDelegateTask() {
this.taskForm.delegateTaskShow = false;
this.taskForm.defaultTaskShow = true;
this.returnTaskList = [];
},
/** 加载审批任务弹框 */
handleComplete() {
// 判断是否存在节点保存方法
if (this.$refs.currentComponent && typeof this.$refs.currentComponent.submitNodeForm == 'function') {
this.$refs.currentComponent.submitNodeForm();
} else {
this.completeOpen = true;
this.completeTitle = "流程审批";
this.submitForm();
}
},
// 从节点表单规则验证通过时,打开审批对话框
handleCompleteOpenFromNodeForm(form) {
this.completeNodeForm = form;
this.completeOpen = true;
this.completeTitle = "流程审批";
this.submitForm();
},
// 处理任务审批操作
handleTaskComplete() {
let completeNodeForm = this.completeNodeForm;
if (completeNodeForm) {
let nodeItemList = this.getFormItemList(completeNodeForm);
this.taskForm["nodeItemList"] = nodeItemList;
}
this.taskComplete();
},
/** 用户审批任务 */
taskComplete() {
if (!this.taskForm.variables && this.checkSendUser) {
this.$modal.msgError("请选择流程接收人员!");
return;
}
if (!this.taskForm.variables && this.checkSendRole) {
this.$modal.msgError("请选择流程接收角色组!");
return;
}
if (!this.taskForm.comment) {
this.$modal.msgError("请输入审批意见!");
return;
}
if (this.taskForm) {
complete(this.taskForm).then(response => {
this.$modal.msgSuccess(response.msg);
this.goBack();
});
}
},
/** 申请流程表单数据提交 */
submitForm() {
// 根据当前任务或者流程设计配置的下一步节点 todo 暂时未涉及到考虑网关、表达式和多节点情况
const params = { taskId: this.taskForm.taskId }
getNextFlowNode(params).then(res => {
const data = res.data;
if (data) {
if (data.dataType === 'dynamic') {
if (data.type === 'assignee') { // 指定人员
this.checkSendUser = true;
this.checkType = "single";
} else if (data.type === 'candidateUsers') { // 候选人员(多个)
this.checkSendUser = true;
this.checkType = "multiple";
} else if (data.type === 'candidateGroups') { // 指定组(所属角色接收任务)
this.checkSendRole = true;
} else { // 会签
// 流程设计指定的 elementVariable 作为会签人员列表
this.multiInstanceVars = data.vars;
this.checkSendUser = true;
this.checkType = "multiple";
}
}
}
})
},
// 动态绑定操作按钮的点击事件
handleButtonClick(method) {
this[method]();
}
},
};
</script>
<style lang="scss" scoped>
.test-form {
margin: 15px auto;
width: 800px;
padding: 15px;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
.box-card {
width: 100%;
margin-bottom: 20px;
}
.el-tag+.el-tag {
margin-left: 10px;
}
.my-label {
background: #E1F3D8;
}
</style>
<style lang="css" scoped>
/* 设置一般输入框禁用样式 */
.notFill /deep/ .el-input.is-disabled .el-input__inner {
background-color: #FFF;
border-color: #DCDFE6;
color: #606266;
}
/* 设置一般多选框禁用样式 */
.notFill /deep/ .el-checkbox__input.is-disabled+span.el-checkbox__label {
color: #606266;
}
.notFill /deep/ .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner {
background: none;
}
.notFill /deep/ .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner::after {
border-color: #FFF;
}
.notFill /deep/ .el-checkbox__input.is-checked+.el-checkbox__label {
color: var(--theme-color) !important;
}
.notFill /deep/ .el-checkbox__input.is-checked .el-checkbox__inner,
.notFill /deep/ .el-checkbox__input.is-indeterminate .el-checkbox__inner {
background-color: var(--theme-color) !important;
border-color: var(--theme-color) !important;
}
/* 设置单选框样式*/
.notFill /deep/ .el-radio__input.is-disabled+span.el-radio__label {
color: #606266;
}
.notFill /deep/ .el-radio__input.is-checked .el-radio__inner {
border-color: var(--theme-color) !important;
background: var(--theme-color) !important;
}
.notFill /deep/ .el-radio__input.is-checked+.el-radio__label {
color: var(--theme-color) !important;
}
.notFill /deep/ .el-radio__input.is-disabled.is-checked .el-radio__inner::after {
background-color: #FFF;
}
.notFill /deep/ .el-slider__button {
border: 2px solid var(--theme-color) !important;
background-color: #FFF;
}
/* 设置滑块样式 */
.notFill /deep/ .el-slider__bar {
background-color: var(--theme-color) !important;
}
/* 设置文本域样式 */
.notFill /deep/ .el-textarea.is-disabled .el-textarea__inner {
background-color: #FFF;
border-color: #DCDFE6;
color: #606266;
}
</style>
2.打开src\views\py\test.vue文件,用以下代码替换。
<template>
<div>
<el-form ref="form" :model="form" :disabled="!fill" label-width="80px" :rules="rules">
<el-form-item label="活动名称" prop="name">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="活动区域" prop="region">
<el-select v-model="form.region" placeholder="请选择活动区域">
<el-option label="区域一" value="shanghai"></el-option>
<el-option label="区域二" value="beijing"></el-option>
</el-select>
</el-form-item>
<el-form-item label="活动时间">
<el-col :span="11">
<el-form-item prop="date1">
<el-date-picker type="date" placeholder="选择日期" v-model="form.date1"
style="width: 100%;"></el-date-picker>
</el-form-item>
</el-col>
<el-col style="text-align: center;" :span="2">-</el-col>
<el-col :span="11">
<el-form-item prop="date2">
<el-time-picker placeholder="选择时间" v-model="form.date2" style="width: 100%;"></el-time-picker>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="即时配送" prop="delivery">
<el-switch v-model="form.delivery"></el-switch>
</el-form-item>
<el-form-item label="测试数字" prop="testNumber">
<el-input-number v-model="form.testNumber" :max="10"></el-input-number>
</el-form-item>
<el-form-item label="测试选择器" prop="testCascader">
<el-cascader v-model="form.testCascader" :options="cascaderOptions"></el-cascader>
</el-form-item>
<el-form-item label="测试滑块" prop="testSlider">
<el-slider v-model="form.testSlider"></el-slider>
</el-form-item>
<el-form-item label="测试时间日期选择器" prop="testDatePicker">
<el-date-picker v-model="form.testDatePicker" type="datetime" placeholder="选择日期时间">
</el-date-picker>
</el-form-item>
<el-form-item label="测试文件" prop="testFile">
<el-upload ref="upload" accept=".jpg, .png" :action="upload.url" :headers="upload.headers" multiple
:file-list="upload.fileList" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess"
:auto-upload="true" :on-preview="handleFileUploadPreview" :on-remove="handleRemove">
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<div v-if="!fill">
<transition-group tag="ul" class="el-upload-list" name="el-list">
<li v-for="(file, index) in form.fileList" class="el-upload-list__item" tabindex="0"
:key="file">
<a class="el-upload-list__item-name" @click="handleFilePreview(file)">
<i class="el-icon-document"></i>{
{file.substring(file.lastIndexOf("/") + 1)}}
</a>
</li>
</transition-group>
</div>
<div v-else slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
</el-form-item>
<el-form-item label="测试评分" prop="testRate">
<el-rate v-model="form.testRate" :colors="colors"></el-rate>
</el-form-item>
<el-form-item label="测试颜色选择器" prop="testColorPicker">
<el-color-picker v-model="form.testColorPicker"></el-color-picker>
</el-form-item>
<el-form-item label="测试穿梭框" prop="testTransfer">
<el-transfer v-model="form.testTransfer" :data="transferData"></el-transfer>
</el-form-item>
<el-form-item label="活动性质" prop="type">
<el-checkbox-group v-model="form.type">
<el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
<el-checkbox label="地推活动" name="type"></el-checkbox>
<el-checkbox label="线下主题活动" name="type"></el-checkbox>
<el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="特殊资源" prop="resource">
<el-radio-group v-model="form.resource">
<el-radio label="线上品牌商赞助"></el-radio>
<el-radio label="线下场地免费"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="活动形式" prop="desc">
<el-input type="textarea" v-model="form.desc"></el-input>
</el-form-item>
</el-form>
<div style="margin: 50px 0 20px 0;">
------------填报节点开始------------
</div>
<!-- 非开始节点需要填报的信息,表单属性名称不要和上面的表单名称一样,否则,会出现冲突 -->
<!-- 下面是以节点名称为“一次审批”为例,需要根据实际修改 -->
<el-form ref="nodeForm" :model="nodeForm" :disabled="!nodeFill" label-width="80px" :rules="nodeRules">
<el-form-item label="活动名称" prop="nodeName">
<el-input v-model="nodeForm.nodeName"></el-input>
</el-form-item>
<el-form-item label="活动区域" prop="nodeRegion">
<el-select v-model="nodeForm.nodeRegion" placeholder="请选择活动区域">
<el-option label="区域一" value="shanghai"></el-option>
<el-option label="区域二" value="beijing"></el-option>
</el-select>
</el-form-item>
<el-form-item label="活动时间">
<el-col :span="11">
<el-form-item prop="nodeDate1">
<el-date-picker type="date" placeholder="选择日期" v-model="nodeForm.nodeDate1"
style="width: 100%;"></el-date-picker>
</el-form-item>
</el-col>
<el-col style="text-align: center;" :span="2">-</el-col>
<el-col :span="11">
<el-form-item prop="nodeDate2">
<el-time-picker placeholder="选择时间" v-model="nodeForm.nodeDate2"
style="width: 100%;"></el-time-picker>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="即时配送" prop="nodeDelivery">
<el-switch v-model="nodeForm.nodeDelivery"></el-switch>
</el-form-item>
<el-form-item label="测试数字" prop="nodeTestNumber">
<el-input-number v-model="nodeForm.nodeTestNumber" :max="10"></el-input-number>
</el-form-item>
<el-form-item label="测试选择器" prop="nodeTestCascader">
<el-cascader v-model="nodeForm.nodeTestCascader" :options="cascaderOptions"></el-cascader>
</el-form-item>
<el-form-item label="测试滑块" prop="nodeTestSlider">
<el-slider v-model="nodeForm.nodeTestSlider"></el-slider>
</el-form-item>
<el-form-item label="测试时间日期选择器" prop="nodeTestDatePicker">
<el-date-picker v-model="nodeForm.nodeTestDatePicker" type="datetime" placeholder="选择日期时间">
</el-date-picker>
</el-form-item>
<el-form-item label="测试文件" prop="nodeTestFile">
<el-upload ref="nodeUpload" accept=".jpg, .png" :action="nodeUpload.url" :headers="nodeUpload.headers"
multiple :file-list="nodeUpload.fileList" :on-progress="handleNodeFormFileUploadProgress"
:on-success="handleNodeFormFileSuccess" :auto-upload="true" :on-preview="handleFileUploadPreview"
:on-remove="handleNodeFormRemove">
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<div v-if="!nodeFill">
<transition-group tag="ul" class="el-upload-list" name="el-list">
<li v-for="(file, index) in nodeForm.nodeFileList" class="el-upload-list__item" tabindex="0"
:key="file">
<a class="el-upload-list__item-name" @click="handleFilePreview(file)">
<i class="el-icon-document"></i>{
{file.substring(file.lastIndexOf("/") + 1)}}
</a>
</li>
</transition-group>
</div>
<div v-else slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
</el-form-item>
<el-form-item label="测试评分" prop="nodeTestRate">
<el-rate v-model="nodeForm.nodeTestRate" :colors="colors"></el-rate>
</el-form-item>
<el-form-item label="测试颜色选择器" prop="nodeTestColorPicker">
<el-color-picker v-model="nodeForm.nodeTestColorPicker"></el-color-picker>
</el-form-item>
<el-form-item label="测试穿梭框" prop="nodeTestTransfer">
<el-transfer v-model="nodeForm.nodeTestTransfer" :data="transferData"></el-transfer>
</el-form-item>
<el-form-item label="活动性质" prop="nodeType">
<el-checkbox-group v-model="nodeForm.nodeType">
<el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
<el-checkbox label="地推活动" name="type"></el-checkbox>
<el-checkbox label="线下主题活动" name="type"></el-checkbox>
<el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="特殊资源" prop="nodeResource">
<el-radio-group v-model="nodeForm.nodeResource">
<el-radio label="线上品牌商赞助"></el-radio>
<el-radio label="线下场地免费"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="活动形式" prop="nodeDesc">
<el-input type="textarea" v-model="nodeForm.nodeDesc"></el-input>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { getToken } from "@/utils/auth";
export default {
name: "PyTest",
data() {
const generateData = _ => {
const data = [];
for (let i = 1; i <= 15; i++) {
data.push({
key: i,
label: `备选项 ${i}`,
disabled: i % 4 === 0
});
}
return data;
};
return {
form: {
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
testNumber: 0,
testCascader: undefined,
testSlider: 0,
testDatePicker: undefined,
testRate: undefined,
testColorPicker: undefined,
testTransfer: undefined,
resource: '',
desc: '',
fileList: []
},
rules: {
// name: [
// { required: true, message: '请输入活动名称', trigger: 'blur' },
// { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
// ],
// region: [
// { required: true, message: '请选择活动区域', trigger: 'change' }
// ],
// date1: [
// { type: 'date', required: true, message: '请选择日期', trigger: 'change' }
// ],
// date2: [
// { type: 'date', required: true, message: '请选择时间', trigger: 'change' }
// ],
// type: [
// { type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' }
// ],
// resource: [
// { required: true, message: '请选择活动资源', trigger: 'change' }
// ],
// desc: [
// { required: true, message: '请填写活动形式', trigger: 'blur' }
// ]
},
cascaderOptions: [{
value: 'zhinan',
label: '指南',
children: [{
value: 'shejiyuanze',
label: '设计原则',
children: [{
value: 'yizhi',
label: '一致'
}, {
value: 'fankui',
label: '反馈'
}, {
value: 'xiaolv',
label: '效率'
}, {
value: 'kekong',
label: '可控'
}]
}, {
value: 'daohang',
label: '导航',
children: [{
value: 'cexiangdaohang',
label: '侧向导航'
}, {
value: 'dingbudaohang',
label: '顶部导航'
}]
}]
}, {
value: 'zujian',
label: '组件',
children: [{
value: 'basic',
label: 'Basic',
children: [{
value: 'layout',
label: 'Layout 布局'
}, {
value: 'color',
label: 'Color 色彩'
}, {
value: 'typography',
label: 'Typography 字体'
}, {
value: 'icon',
label: 'Icon 图标'
}, {
value: 'button',
label: 'Button 按钮'
}]
}, {
value: 'form',
label: 'Form',
children: [{
value: 'radio',
label: 'Radio 单选框'
}, {
value: 'checkbox',
label: 'Checkbox 多选框'
}, {
value: 'input',
label: 'Input 输入框'
}, {
value: 'input-number',
label: 'InputNumber 计数器'
}, {
value: 'select',
label: 'Select 选择器'
}, {
value: 'cascader',
label: 'Cascader 级联选择器'
}, {
value: 'switch',
label: 'Switch 开关'
}, {
value: 'slider',
label: 'Slider 滑块'
}, {
value: 'time-picker',
label: 'TimePicker 时间选择器'
}, {
value: 'date-picker',
label: 'DatePicker 日期选择器'
}, {
value: 'datetime-picker',
label: 'DateTimePicker 日期时间选择器'
}, {
value: 'upload',
label: 'Upload 上传'
}, {
value: 'rate',
label: 'Rate 评分'
}, {
value: 'form',
label: 'Form 表单'
}]
}, {
value: 'data',
label: 'Data',
children: [{
value: 'table',
label: 'Table 表格'
}, {
value: 'tag',
label: 'Tag 标签'
}, {
value: 'progress',
label: 'Progress 进度条'
}, {
value: 'tree',
label: 'Tree 树形控件'
}, {
value: 'pagination',
label: 'Pagination 分页'
}, {
value: 'badge',
label: 'Badge 标记'
}]
}, {
value: 'notice',
label: 'Notice',
children: [{
value: 'alert',
label: 'Alert 警告'
}, {
value: 'loading',
label: 'Loading 加载'
}, {
value: 'message',
label: 'Message 消息提示'
}, {
value: 'message-box',
label: 'MessageBox 弹框'
}, {
value: 'notification',
label: 'Notification 通知'
}]
}, {
value: 'navigation',
label: 'Navigation',
children: [{
value: 'menu',
label: 'NavMenu 导航菜单'
}, {
value: 'tabs',
label: 'Tabs 标签页'
}, {
value: 'breadcrumb',
label: 'Breadcrumb 面包屑'
}, {
value: 'dropdown',
label: 'Dropdown 下拉菜单'
}, {
value: 'steps',
label: 'Steps 步骤条'
}]
}, {
value: 'others',
label: 'Others',
children: [{
value: 'dialog',
label: 'Dialog 对话框'
}, {
value: 'tooltip',
label: 'Tooltip 文字提示'
}, {
value: 'popover',
label: 'Popover 弹出框'
}, {
value: 'card',
label: 'Card 卡片'
}, {
value: 'carousel',
label: 'Carousel 走马灯'
}, {
value: 'collapse',
label: 'Collapse 折叠面板'
}]
}]
}, {
value: 'ziyuan',
label: '资源',
children: [{
value: 'axure',
label: 'Axure Components'
}, {
value: 'sketch',
label: 'Sketch Templates'
}, {
value: 'jiaohu',
label: '组件交互文档'
}]
}],
// 上传参数
upload: {
// 是否禁用上传
isUploading: false,
// 设置上传的请求头部
headers: { Authorization: "Bearer " + getToken() },
// 上传的地址
url: process.env.VUE_APP_BASE_API + "/common/upload",
// 上传的文件列表
fileList: []
},
colors: ['#99A9BF', '#F7BA2A', '#FF9900'],
transferData: generateData(),
// 节点表单信息
nodeForm: {
nodeName: '',
nodeRegion: '',
nodeDate1: '',
nodeDate2: '',
nodeDelivery: false,
nodeType: [],
nodeTestNumber: 0,
nodeTestCascader: undefined,
nodeTestSlider: 0,
nodeTestDatePicker: undefined,
nodeTestRate: undefined,
nodeTestColorPicker: undefined,
nodeTestTransfer: undefined,
nodeResource: '',
nodeDesc: '',
nodeFileList: []
},
nodeRules: {
// nodeName: [
// { required: true, message: '请输入活动名称', trigger: 'blur' },
// { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
// ],
// nodeRegion: [
// { required: true, message: '请选择活动区域', trigger: 'change' }
// ],
// nodeDate1: [
// { type: 'date', required: true, message: '请选择日期', trigger: 'change' }
// ],
// nodeDate2: [
// { type: 'date', required: true, message: '请选择时间', trigger: 'change' }
// ],
// nodeType: [
// { type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' }
// ],
// nodeResource: [
// { required: true, message: '请选择活动资源', trigger: 'change' }
// ],
// nodeDesc: [
// { required: true, message: '请填写活动形式', trigger: 'blur' }
// ]
},
// 需要填报的节点名称
nodeTaskName: "一次审批",
// 节点是否允许填报
nodeFill: false,
nodeUpload: {
// 是否禁用上传
isUploading: false,
// 设置上传的请求头部
headers: { Authorization: "Bearer " + getToken() },
// 上传的地址
url: process.env.VUE_APP_BASE_API + "/common/upload",
// 上传的文件列表
fileList: []
},
}
},
props: {
// 是否允许填写
fill: {
type: Boolean,
default: true
},
// 表单信息
formData: {
type: Object,
default: undefined
},
// 当前节点信息
currentNode: {
type: Object,
default: undefined
}
},
created() {
// 如果没有节点表单,可以直接赋值
// if (this.formData) {
// this.form = this.formData;
// }
// 有节点表单时设置方式
// 如果有节点表单,将数据分成普通表单和节点表单两部分
if (this.formData) {
this.splitFormData();
}
// 设置节点表单是否能填写
this.setNodeFill();
// 普通表单如果可以修改,并且文件列表数量大于0,将路径转换为文件
if (this.fill && this.form && this.form.fileList && this.form.fileList.length > 0) {
this.upload.fileList = this.form.fileList.map((url, index) => ({
name: url.substring(url.lastIndexOf("/") + 1),
url: url,
status: 'success'
}));
}
// 节点表单如果可以修改,并且文件列表数量大于0,将路径转换为文件
if (this.nodeFill && this.nodeForm && this.nodeForm.nodeFileList && this.nodeForm.nodeFileList.length > 0) {
this.nodeUpload.fileList = this.nodeForm.nodeFileList.map((url, index) => ({
name: url.substring(url.lastIndexOf("/") + 1),
url: url,
status: 'success'
}));
}
},
methods: {
// 处理文件预览
handleFilePreview(fileUrl) {
console.log("文件预览点击:" + fileUrl);
},
// 处理文件移除
handleRemove(file, fileList) {
this.form.fileList = [];
fileList.forEach(currentFile => {
this.form.fileList.push(currentFile.url);
});
},
// 文件上传列表中文件预览
handleFileUploadPreview(file) {
let fileUrl = "";
if (file.url) {
fileUrl = file.url;
} else {
fileUrl = file.response && file.response.fileName;
}
console.log(fileUrl);
},
// 文件上传中处理
handleFileUploadProgress(event, file, fileList) {
this.upload.isUploading = true;
},
// 文件上传成功处理
handleFileSuccess(response, file, fileList) {
this.upload.isUploading = false;
this.form.fileList.push(response.fileName);
this.$modal.msgSuccess(response.msg);
},
// 表单重置
reset() {
this.form = {
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
testNumber: 0,
testCascader: undefined,
testSlider: 0,
testDatePicker: undefined,
testRate: undefined,
testColorPicker: undefined,
testTransfer: undefined,
resource: '',
desc: '',
fileList: []
};
this.upload.fileList = [];
this.resetForm("form");
},
// 表单提交
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
this.$emit("saveForm", this.form);
}
});
},
// 节点表单内容开始
// 将formData中的数据拆分为普通表单和节点表单两部分
splitFormData() {
// 遍历 form 对象,提取 formData 中的对应属性
Object.keys(this.form).forEach(key => {
if (this.formData.hasOwnProperty(key)) {
this.form[key] = this.formData[key];
}
});
// 遍历 nodeForm 对象,提取 formData 中的对应属性
Object.keys(this.nodeForm).forEach(key => {
if (this.formData.hasOwnProperty(key)) {
this.nodeForm[key] = this.formData[key];
}
});
},
// 设置节点是否能填写
setNodeFill() {
let currentNode = this.currentNode;
let nodeTaskName = this.nodeTaskName;
this.nodeFill = currentNode && currentNode.taskName == nodeTaskName;
},
// 处理文件移除
handleNodeFormRemove(file, fileList) {
this.nodeForm.nodeFileList = [];
fileList.forEach(currentFile => {
this.nodeForm.nodeFileList.push(currentFile.url);
});
},
// 文件上传中处理
handleNodeFormFileUploadProgress(event, file, fileList) {
this.nodeUpload.isUploading = true;
},
// 文件上传成功处理
handleNodeFormFileSuccess(response, file, fileList) {
this.nodeUpload.isUploading = false;
this.nodeForm.nodeFileList.push(response.fileName);
this.$modal.msgSuccess(response.msg);
},
// 节点表单提交
submitNodeForm() {
this.$refs["nodeForm"].validate(valid => {
if (valid) {
this.$emit("openComplete", this.nodeForm);
}
});
}
// 节点表单内容结束
}
}
</script>
3.uniapp端
1.打开pages\flowable\todo\index.vue文件,增加以下代码。
onShow: function() {
// 获取列表信息
this.getList();
},
2.打开pages\flowable\todo\detail\index.vue文件,用以下代码替换。
<template>
<view class="py-outer-content">
<view class="py-list-content">
<u-tabs :list="tabsList" @click="handleTabsClick"></u-tabs>
</view>
<!-- 表单信息 -->
<view class="py-list-content py-flow-content" v-if="formShow"
:style="{height: windowHeight ? 'calc('+ windowHeight + ' - 10rpx - 10rpx - 44px - 5px)' : ''}">
<view class="py-content" style="margin-bottom: 10rpx;">
<!-- #ifdef H5 -->
<component :is="currentComponent" ref="currentComponent" :formData="componentForm"
:currentNode="currentFlowRecord" :readOnly="!hasFillNode"></component>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN || APP-PLUS -->
<!-- 使用APP需要在main.js进行配置,配置位置有注释标注 -->
<!-- 由于微信不支持动态组件,需要在pages.json中两处进行配置,配置位置有注释标注 -->
<!-- 新增组件,只需要修改组件名称和最后条件判断中实际路径即可,并且将v-if替换成v-else-if -->
<!-- ref、formData、currentNode和readOnly的内容均不能修改 -->
<!-- 条件判断中要写详细文件路径,包含前缀@和后缀.vue -->
<!-- v-if="'@/pages/py/test.vue' == currentComponentPath" -->
<test ref="currentComponent" v-if="'@/pages/py/test.vue' == currentComponentPath"
:formData="componentForm" :currentNode="currentFlowRecord" :readOnly="!hasFillNode"></test>
<!-- #endif -->
<!-- 操作 -->
<u-line></u-line>
<view style="margin: 5rpx 0 5rpx 0;">
<u-row justify="center" :gutter="4" v-if="loginUserId == startUserId && hasFillNode">
<u-col :span="4">
<u-button type="primary" text="保存" @click="submitCurrentForm(false)"></u-button>
</u-col>
<u-col :span="4">
<u-button type="success" text="发起" @click="submitCurrentForm(true)"></u-button>
</u-col>
</u-row>
<u-row justify="center" :gutter="3" v-else>
<!-- u-button在H5页面会自动调用第四个按钮的click,很奇怪的问题 -->
<!-- 开始以为是自己设置问题,把按钮位置调换后,发现第四个位置的按钮,会在页面加载时自动调用第四个按钮 -->
<!-- 但是,在微信小程序和APP中使用没有任何问题 -->
<!-- 由于上述问题,统一使用py-button,其实和u-button一样,只是初始化时不会调用@click -->
<!-- 只有按钮过多时才使用py-button,其他情况均使用u-button -->
<u-col :span="3">
<py-button type="primary" text="审批" @click="handleComplete"></py-button>
</u-col>
<u-col :span="3">
<py-button type="warning" text="选择退回" @click="handleReturn"></py-button>
</u-col>
<u-col :span="3">
<py-button type="error" text="退回上级" @click="handleReject"></py-button>
</u-col>
<u-col :span="3">
<py-button type="error" text="退回初始" @click="handleReturnFirst"></py-button>
</u-col>
</u-row>
</view>
</view>
</view>
<!-- 流转记录 -->
<view class="py-list-content py-flow-content" v-else-if="flowRecordShow"
:style="{height: windowHeight ? 'calc('+ windowHeight + ' - 10rpx - 10rpx - 44px - 5px)' : ''}">
<view class="py-content">
<!-- 非初始节点 -->
<view v-for="(item, index) in flowRecordList" :key="index">
<py-flow-record-item :flow="item" fillNode :firstAssigneeName="lastFlowRecord.assigneeName"
:firstDeptName="lastFlowRecord.deptName"
v-if="item.taskDefKey == lastFlowRecord.taskDefKey"></py-flow-record-item>
<py-flow-record-item :flow="item" v-else></py-flow-record-item>
</view>
<!-- 初始节点设置 -->
<py-flow-record-item :flow="lastFlowRecord" start></py-flow-record-item>
</view>
</view>
<!-- 流程图 -->
<view class="py-flow-content" v-else-if="flowableShow"
:style="{height: windowHeight ? 'calc('+ windowHeight + ' - 10rpx - 44px - 5px)' : ''}">
<py-bpmn-viewer :flowData="flowData"></py-bpmn-viewer>
</view>
<!-- 审批弹框 -->
<py-popup :show="completeOpen" mode="center" @close="completeOpen = false;" title="流程审批">
<template slot="form">
<view>
<py-section title="处理意见">
<u--textarea v-model="comment" placeholder="请填写处理意见" autoHeight></u--textarea>
</py-section>
<view class="py-popup-button-group">
<u-button type="primary" text="审批" @click="handleTaskComplete"></u-button>
<u-button type="warning" text="取消" @click="completeOpen = false;"></u-button>
</view>
</view>
</template>
</py-popup>
<!-- 选择退回弹框 -->
<py-popup :show="returnOpen" mode="center" @close="returnOpen = false;" title="退回流程">
<template slot="form">
<view>
<py-section title="退回节点">
<view class="py-return-tag-group">
<py-tag :text="item.name" :plain="!(returnTaskActiveId == item.id)"
:borderColor="returnTaskActiveId == item.id ? '#5ac725' : '#c8c9cc'"
:bgColor="returnTaskActiveId == item.id ? '#5ac725' : ''"
:color="returnTaskActiveId == item.id ? 'white' : '#606266'" :name="item.id"
@click="handleReturnTagClick" v-for="(item, index) in returnTaskList" :key="item.id">
</py-tag>
</view>
</py-section>
<py-section title="处理意见">
<u--textarea v-model="comment" placeholder="请填写处理意见" autoHeight></u--textarea>
</py-section>
<view class="py-popup-button-group">
<u-button type="primary" text="退回" @click="taskReturn"></u-button>
<u-button type="warning" text="取消" @click="returnOpen = false;"></u-button>
</view>
</view>
</template>
</py-popup>
<!-- 退回上级弹框 -->
<py-popup :show="rejectOpen" mode="center" @close="rejectOpen = false;" title="驳回流程">
<template slot="form">
<view>
<py-section title="驳回意见">
<u--textarea v-model="comment" placeholder="请填写驳回意见" autoHeight></u--textarea>
</py-section>
<view class="py-popup-button-group">
<u-button type="primary" text="驳回" @click="taskReject"></u-button>
<u-button type="warning" text="取消" @click="rejectOpen = false;"></u-button>
</view>
</view>
</template>
</py-popup>
<!-- 退回初始 -->
<py-popup :show="returnFirstOpen" mode="center" @close="returnFirstOpen = false;" title="退回初始流程">
<template slot="form">
<view>
<py-section title="退回意见">
<u--textarea v-model="comment" placeholder="请填写退回意见" autoHeight></u--textarea>
</py-section>
<view class="py-popup-button-group">
<u-button type="primary" text="退回" @click="taskFirstReturn"></u-button>
<u-button type="warning" text="取消" @click="returnFirstOpen = false;"></u-button>
</view>
</view>
</template>
</py-popup>
<!-- 消息提醒框 -->
<!-- 由于py-popup的z-index值太大,会隐藏uni.showToast的显示,所以用u-toast进行消息提醒 -->
<u-toast ref="uToast"></u-toast>
</view>
</template>
<script>
import { getBpmnStartId } from "@/api/flowable/py";
import store from "@/store";
import { flowRecord } from "@/api/flowable/finished";
import {
getFormVariableList,
updateFormVariableList
} from "@/api/flowable/flowableFormVariable";
import {
getFlowableFormByDeployId
} from "@/api/flowable/flowableForm";
import {
complete,
getNextFlowNode,
returnList,
returnTask,
rejectTask
} from "@/api/flowable/todo";
import {
flowXmlAndNode
} from "@/api/flowable/definition";
import {
getWindowHeight,
getVarType,
getFormData
} from "@/utils/py";
export default {
data() {
return {
// 标签列表信息
tabsList: [
{
name: "表单信息"
},
{
name: "流转记录"
},
{
name: "流程图"
}
],
// 是否展示表单信息
formShow: true,
// 是否展示流转记录
flowRecordShow: false,
// 是否展示流程图
flowableShow: false,
// 模型xml数据
flowData: {},
// 查询参数
queryParams: {
deptId: undefined
},
deployId: "", // 流程定义编号
procInsId: "", // 流程实例编号
taskId: "", // 任务编号
flowRecordList: [], // 流程流转数据
// 流程图对应的id
procDefId: undefined,
// 是否填写节点
hasFillNode: false,
// 发起人id
startUserId: undefined,
// 登录账号id
loginUserId: undefined,
// 最后一个流程记录,也就是流程开始节点信息
lastFlowRecord: undefined,
// 当前组件
// #ifdef APP-PLUS || H5
currentComponent: undefined,
// #endif
// #ifdef APP-PLUS
// App组件路径
currentAppComponentPath: "",
// #endif
// 表单Id
formId: undefined,
// 是否启动流程
hasStart: false,
// 窗口高度(去掉导航栏)
windowHeight: undefined,
// 当前组件路径
// #ifdef MP-WEIXIN || APP-PLUS
currentComponentPath: "",
// #endif
// 是否启动流程
hasStart: false,
// 组件表单
componentForm: undefined,
// 任务信息
taskForm: {
returnTaskShow: false, // 是否展示回退表单
delegateTaskShow: false, // 是否展示回退表单
defaultTaskShow: true, // 默认处理
comment: "", // 意见内容
variables: {},
// 表单数据
itemList: []
},
// 表单id对象
formIdObj: {},
// 审批弹窗是否打开
completeOpen: false,
// 选择退回弹框是否打开
returnOpen: false,
returnTaskList: [], // 回退列表数据
// 退回任务标签当前活跃id
returnTaskActiveId: "",
// 退回上级弹窗是否打开
rejectOpen: false,
// 退回初始弹框是否打开
returnFirstOpen: false,
// 处理意见
comment: "",
// 流程当前节点
currentFlowRecord: undefined,
// 审批时,节点表单
completeNodeForm: undefined
}
},
onLoad: function (options) {
let taskDefKey = undefined;
if (options) {
this.deployId = options.deployId;
this.procInsId = options.procInsId;
this.taskId = options.taskId;
this.procDefId = options.procDefId;
this.startUserId = options.startUserId;
taskDefKey = options.taskDefKey;
}
// 获取登录id
this.loginUserId = store.state.user.id;
// 获取开始节点信息,并且判断当前是否为填写节点
getBpmnStartId(this.procDefId).then(response => {
let currentKey = response.msg;
if (taskDefKey == currentKey) {
this.hasFillNode = true;
} else {
this.hasFillNode = false;
}
})
// 获取获取表单变量信息
this.getFormVariable(this.procInsId).then(() => {
// 获取流程表单信息并且导入
this.getFormPathAndImport(this.deployId);
});
// 获取流转记录
this.getFlowRecordList(this.procInsId, this.deployId);
// 获取窗口高度
this.getWindowHeight();
// 监听保存表单
uni.$once('saveForm', form => {
this.saveForm(form);
});
// 监听审批对话框打开
uni.$on('openComplete', form => {
this.handleCompleteOpenFromNodeForm(form);
});
},
methods: {
// 获取窗口高度
getWindowHeight() {
getWindowHeight().then(response => {
this.windowHeight = response;
})
},
/** 流程流转记录 */
getFlowRecordList(procInsId, deployId) {
const params = {
procInsId: procInsId,
deployId: deployId
};
flowRecord(params).then(response => {
let flowRecordList = response.data.flowList;
this.lastFlowRecord = flowRecordList.splice(flowRecordList.length - 1, 1)[0];
this.currentFlowRecord = flowRecordList[0];
this.flowRecordList = flowRecordList;
})
},
// 处理标签点击操作
handleTabsClick(item) {
let index = item && item.index;
switch (index) {
case 0:
this.flowRecordShow = false;
this.flowableShow = false;
this.formShow = true;
break;
case 1:
this.flowableShow = false;
this.formShow = false;
this.flowRecordShow = true;
break;
case 2:
// 获取流程图xml
this.getFlowableXml();
break;
}
},
// 获取表单变量信息
getFormVariable(procInsId) {
return new Promise((resolve, reject) => {
this.$modal.loading("正在获取数据...")
getFormVariableList({ procInsId }).then(response => {
let data = response.data;
let formData = getFormData(data);
this.saveformIdObj(data);
this.componentForm = formData;
this.$modal.closeLoading();
resolve();
})
})
},
// 将表表单id按照名称保存在对象中
saveformIdObj(data) {
let formIdObj = this.formIdObj;
data.forEach(element => {
formIdObj[element.varName] = element.varId;
});
},
// 获取流程图Xml
getFlowableXml() {
flowXmlAndNode({
procInsId: this.procInsId, deployId: this.deployId
}).then(res => {
this.formShow = false;
this.flowRecordShow = false;
this.flowableShow = true;
this.flowData = res.data;
})
},
// 导入组件
importComponent(componentPath) {
if (!componentPath) {
return;
}
let prefixIndex = componentPath.indexOf("@/");
let suffixIndex = componentPath.lastIndexOf(".vue");
if (prefixIndex == -1) {
prefixIndex = 0;
} else {
prefixIndex = 2;
}
if (suffixIndex == -1) {
suffixIndex = componentPath.length;
}
let path = componentPath.substring(prefixIndex, suffixIndex);
let appPath = path.replace("views/", "pages/");
// #ifdef H5
this.currentComponent = resolve => require([`@/${appPath}.vue`], resolve);
// #endif
// #ifdef MP-WEIXIN || APP-PLUS
this.currentComponentPath = `@/${appPath}.vue`;
// #endif
},
// 获取表单信息并且导入
getFormPathAndImport(deployId) {
getFlowableFormByDeployId(deployId).then(response => {
let data = response.data;
this.importComponent(data.formPath);
})
},
/** 申请流程表单数据提交 */
submitCurrentForm(hasStart) {
this.hasStart = hasStart;
this.$refs.currentComponent.submitForm();
},
// 获取表单想列表
getFormItemList(form) {
let formIdObj = this.formIdObj;
let itemList = [];
for (const key in form) {
if (Object.hasOwnProperty.call(form, key)) {
let eachObj = {};
const element = form[key];
eachObj["name"] = key;
eachObj["type"] = getVarType(element);
if (eachObj["type"] == "array") {
eachObj["value"] = JSON.stringify(element);
} else {
eachObj["value"] = element;
}
// 如果有id进行保存
if (Object.hasOwnProperty.call(formIdObj, key)) {
eachObj["id"] = formIdObj[key];
}
itemList.push(eachObj);
}
}
return itemList;
},
// 保存表单
saveForm(form) {
let itemList = this.getFormItemList(form);
// 如果只是保存,不需要启动
if (!this.hasStart) {
let currentData = {
itemList,
procInsId: this.procInsId
}
updateFormVariableList(currentData).then(response => {
// 显示成功信息,并且返回页面
this.showSuccessMessageAndGoBack(response.msg);
})
return;
}
const params = { taskId: this.taskId };
getNextFlowNode(params).then(res => {
const data = res.data;
if (data) {
// 如果启动,设置意见
this.comment = "启动流程";
// 设置表单信息
this.taskComplete(itemList);
}
})
},
// 显示错误显示提示
showErrorToast(message) {
this.$refs.uToast.show({
type: "error",
message,
duration: 1500,
position: "top"
});
},
// 处理任务审批操作
handleTaskComplete() {
let completeNodeForm = this.completeNodeForm;
if (completeNodeForm) {
let nodeItemList = this.getFormItemList(completeNodeForm);
this.taskComplete(nodeItemList, true);
return;
}
this.taskComplete();
},
/** 用户审批任务 */
taskComplete(itemList, hasNodeForm = false) {
let comment = this.comment;
if (!comment || comment == "") {
// 提示信息
this.showErrorToast("请填写处理意见");
return;
}
let instanceId = this.procInsId;
let taskId = this.taskId;
let taskForm = {
instanceId,
taskId,
comment
}
// 如果是节点表单
if (hasNodeForm) {
taskForm["nodeItemList"] = itemList;
} else if (itemList) {
taskForm["itemList"] = itemList;
}
complete(taskForm).then(response => {
// 关闭弹窗
this.completeOpen = false;
// 显示成功信息,并且返回页面
this.showSuccessMessageAndGoBack(response.msg);
});
},
/** 加载审批任务弹框 */
handleComplete() {
// 判断是否存在节点保存方法
if (this.$refs.currentComponent && typeof this.$refs.currentComponent.submitNodeForm == 'function') {
this.$refs.currentComponent.submitNodeForm();
} else {
this.comment = "";
this.completeOpen = true;
}
},
// 从节点表单规则验证通过时,打开审批对话框
handleCompleteOpenFromNodeForm(form) {
this.completeNodeForm = form;
this.comment = "";
this.completeOpen = true;
},
/** 可退回任务列表 */
handleReturn() {
// 重置
this.comment = "";
this.returnTaskActiveId = "";
let taskId = this.taskId;
let taskForm = {
taskId
}
returnList(taskForm).then(res => {
this.returnTaskList = res.data;
this.returnOpen = true;
})
},
// 处理退回标签点击
handleReturnTagClick(name) {
this.returnTaskActiveId = name;
},
/** 提交退回任务 */
taskReturn() {
// 不选择退回节点提示错误信息
let returnTaskActiveId = this.returnTaskActiveId;
if (!returnTaskActiveId && returnTaskActiveId == "") {
this.showErrorToast("请选择退回节点");
return;
}
// 不填写处理意见提示错误信息
let comment = this.comment;
if (!comment || comment == "") {
this.showErrorToast("请填写处理意见");
return;
}
let taskId = this.taskId;
let targetKey = this.returnTaskActiveId;
let taskForm = {
taskId,
targetKey,
comment
};
returnTask(taskForm).then(response => {
// 关闭弹窗
this.returnOpen = false;
// 显示成功信息,并且返回页面
this.showSuccessMessageAndGoBack(response.msg);
});
},
// 显示成功信息,并且返回页面
showSuccessMessageAndGoBack(message) {
let delayTime = 1000;
// 返回上层页面
this.$modal.msgSuccess(message);
// #ifdef MP-WEIXIN || APP-PLUS
delayTime = 500;
// #endif
// 设置延迟,防止提示信息不显示
setTimeout(() => {
// 返回上一页
this.$tab.navigateBack();
}, delayTime)
},
/** 驳回任务 */
handleReject() {
this.comment = "";
this.rejectOpen = true;
},
/** 驳回任务 */
taskReject() {
// 不填写驳回意见提示错误信息
let comment = this.comment;
if (!comment || comment == "") {
this.showErrorToast("请填写驳回意见");
return;
}
let taskId = this.taskId;
let taskForm = {
taskId,
comment
}
rejectTask(taskForm).then(response => {
// 关闭弹窗
this.rejectOpen = false;
// 显示成功信息,并且返回页面
this.showSuccessMessageAndGoBack(response.msg);
});
},
// 处理点击退回到初始节点操作
handleReturnFirst() {
this.comment = "";
this.returnFirstOpen = true;
},
// 提交退回初始任务
taskFirstReturn() {
let comment = this.comment;
if (!comment || comment == "") {
this.showErrorToast("请填写退回意见");
return;
}
let taskId = this.taskId;
let taskForm = {
targetKey: "start_event",
taskId,
comment
}
returnTask(taskForm).then(response => {
// 关闭弹窗
this.returnFirstOpen = false;
// 显示成功信息,并且返回页面
this.showSuccessMessageAndGoBack(response.msg);
});
},
},
}
</script>
<style lang="css" scoped>
.py-flow-content {
overflow: auto;
}
/* 弹窗按钮样式 */
.py-popup-button-group {
margin-top: 5px;
display: flex;
justify-content: space-between;
}
.py-popup-button-group uni-button {
margin: 0 5px;
}
.py-return-tag-group {
display: flex;
/* 允许换行 */
flex-wrap: wrap;
}
.py-return-tag-group .py-tag {
margin: 5px 5px 0 0;
}
</style>
3.打开pages\py\test.vue文件,用以下代码替换。
<template>
<view>
<!-- 只读状态禁用所有事件,对于需要事件的部分,加上类clickable-item,例如:文件上传 -->
<view :class="readOnly ? 'py-form-disabled' : ''">
<u--form labelPosition="left" labelWidth="auto" :rules="rules" :model="form" ref="form">
<u-form-item label="活动名称" prop="name" borderBottom>
<u--input v-model="form.name" border="none" placeholder="请填写活动名称"></u--input>
</u-form-item>
<u-form-item label="活动区域" prop="region" borderBottom borderBottom>
<py-select :data="regionData" v-model="form.region" title="请选择活动区域"></py-select>
</u-form-item>
<u-form-item label="活动时间">
<view style="display: flex;">
<py-datetime-picker title="选择日期" mode="date" v-model="form.date1"></py-datetime-picker>
<view style="margin: 0 5px; line-height: 24px;">
<u--text text="-"></u--text>
</view>
<py-datetime-picker title="选择时间" mode="time" v-model="form.date2"></py-datetime-picker>
</view>
</u-form-item>
<u-form-item label="即时配送" prop="delivery" borderBottom>
<u-switch v-model="form.delivery" size="18"></u-switch>
</u-form-item>
<u-form-item label="测试数字" prop="testNumber" borderBottom>
<u-number-box v-model="form.testNumber" :max="10"></u-number-box>
</u-form-item>
<u-form-item label="测试选择器" prop="testCascader" borderBottom>
<py-cascader v-model="form.testCascader" title="测试选择器" :options="cascaderOptions"></py-cascader>
</u-form-item>
<u-form-item label="测试滑块" prop="testSlider" borderBottom>
<u-slider v-model="form.testSlider" showValue style="width: 100%;"></u-slider>
</u-form-item>
<u-form-item label="测试时间日期选择器" prop="testDatePicker" borderBottom>
<py-datetime-picker title="选择日期时间" mode="datetime"
v-model="form.testDatePicker"></py-datetime-picker>
</u-form-item>
<!-- 由于只读状态也需要点击图片进行放大预览,对于需要点击效果的图片加上clickable-item类 -->
<!-- 注意:将clickable-item的class加在u-form-item上,在微信小程序上生效 -->
<!-- 因此将类clickable-item加在py-upload-image组件上 -->
<u-form-item label="测试文件" prop="fileList" borderBottom>
<!-- py-upload-image组件设置readOnly属性为true,表示只读状态,会自动隐藏删除和上传按钮 -->
<!-- #ifdef H5 -->
<py-upload-image multiple :fileList="form.fileList" :maxCount="10" :readOnly="readOnly"
class="clickable-item"></py-upload-image>
<!-- #endif -->
<!-- #ifdef APP-PLUS || MP-WEIXIN -->
<!-- 真机和微信小程序双向绑定失效,通过change方法手动修改fileList值 -->
<py-upload-image multiple :fileList="form.fileList" :maxCount="10" :readOnly="readOnly"
@change="handleUploadImageChange" class="clickable-item"></py-upload-image>
<!-- #endif -->
</u-form-item>
<u-form-item label="测试评分" prop="testRate" borderBottom>
<!-- u-rate在模拟器演示时,初次点击导致星星置空,之后无法点击 -->
<!-- 切换到流程图,再切换到表单,也就是控制v-if隐藏与显示后,功能正常 -->
<!-- 出于上述原因,使用uni-rate -->
<!-- <u-rate v-model="form.testRate"></u-rate> -->
<uni-rate v-model="form.testRate" />
</u-form-item>
<!-- py-color-picker组件设置readOnly属性为true,表示只读状态,会自动隐藏头部 -->
<u-form-item label="测试颜色选择器" prop="testColorPicker" borderBottom>
<py-color-picker v-model="form.testColorPicker" :readOnly="readOnly"
class="clickable-item"></py-color-picker>
</u-form-item>
<u-form-item label="测试穿梭框" prop="testTransfer" borderBottom labelPosition="top">
<py-transfer :data="transferData" v-model="form.testTransfer"></py-transfer>
</u-form-item>
<u-form-item label="活动性质" prop="type" borderBottom labelPosition="top">
<view class="py-checkbox-group">
<u-checkbox-group v-model="form.type" placement="column">
<u-checkbox label="美食/餐厅线上活动" name="美食/餐厅线上活动"></u-checkbox>
<u-checkbox label="地推活动" name="地推活动"></u-checkbox>
<u-checkbox label="线下主题活动" name="线下主题活动"></u-checkbox>
<u-checkbox label="单纯品牌曝光" name="单纯品牌曝光"></u-checkbox>
</u-checkbox-group>
</view>
</u-form-item>
<u-form-item label="特殊资源" prop="resource" borderBottom labelPosition="top">
<view class="py-radio-group">
<u-radio-group v-model="form.resource" placement="column">
<u-radio shape="circle" label="线上品牌商赞助" name="线上品牌商赞助"></u-radio>
<u-radio shape="circle" label="线下场地免费" name="线下场地免费"></u-radio>
</u-radio-group>
</view>
</u-form-item>
<u-form-item label="活动形式" prop="desc">
<u-textarea v-model="form.desc" confirmType="send" autoHeight></u-textarea>
</u-form-item>
</u--form>
</view>
<view style="margin: 50px 0 20px 0;">
------------填报节点开始------------
</view>
<view :class="nodeReadOnly ? 'py-form-disabled' : ''">
<u--form labelPosition="left" labelWidth="auto" :rules="nodeRules" :model="nodeForm" ref="nodeForm">
<u-form-item label="活动名称" prop="nodeName" borderBottom>
<u--input v-model="nodeForm.nodeName" border="none" placeholder="请填写活动名称"></u--input>
</u-form-item>
<u-form-item label="活动区域" prop="nodeRegion" borderBottom borderBottom>
<py-select :data="regionData" v-model="nodeForm.nodeRegion" title="请选择活动区域"></py-select>
</u-form-item>
<u-form-item label="活动时间">
<view style="display: flex;">
<py-datetime-picker title="选择日期" mode="date" v-model="nodeForm.nodeDate1"></py-datetime-picker>
<view style="margin: 0 5px; line-height: 24px;">
<u--text text="-"></u--text>
</view>
<py-datetime-picker title="选择时间" mode="time" v-model="nodeForm.nodeDate2"></py-datetime-picker>
</view>
</u-form-item>
<u-form-item label="即时配送" prop="nodeDelivery" borderBottom>
<u-switch v-model="nodeForm.nodeDelivery" size="18"></u-switch>
</u-form-item>
<u-form-item label="测试数字" prop="nodeTestNumber" borderBottom>
<u-number-box v-model="nodeForm.nodeTestNumber" :max="10"></u-number-box>
</u-form-item>
<u-form-item label="测试选择器" prop="nodeTestCascader" borderBottom>
<py-cascader v-model="nodeForm.nodeTestCascader" title="测试选择器"
:options="cascaderOptions"></py-cascader>
</u-form-item>
<u-form-item label="测试滑块" prop="nodeTestSlider" borderBottom>
<u-slider v-model="nodeForm.nodeTestSlider" showValue style="width: 100%;"></u-slider>
</u-form-item>
<u-form-item label="测试时间日期选择器" prop="nodeTestDatePicker" borderBottom>
<py-datetime-picker title="选择日期时间" mode="datetime"
v-model="nodeForm.nodeTestDatePicker"></py-datetime-picker>
</u-form-item>
<!-- 由于只读状态也需要点击图片进行放大预览,对于需要点击效果的图片加上clickable-item类 -->
<!-- 注意:将clickable-item的class加在u-form-item上,在微信小程序上生效 -->
<!-- 因此将类clickable-item加在py-upload-image组件上 -->
<u-form-item label="测试文件" prop="nodeFileList" borderBottom>
<!-- py-upload-image组件设置readOnly属性为true,表示只读状态,会自动隐藏删除和上传按钮 -->
<!-- #ifdef H5 -->
<py-upload-image multiple :fileList="nodeForm.nodeFileList" :maxCount="10" :readOnly="nodeReadOnly"
class="clickable-item"></py-upload-image>
<!-- #endif -->
<!-- #ifdef APP-PLUS || MP-WEIXIN -->
<!-- 真机和微信小程序双向绑定失效,通过change方法手动修改fileList值 -->
<py-upload-image multiple :fileList="nodeForm.nodeFileList" :maxCount="10" :readOnly="nodeReadOnly"
@change="handleNodeFormUploadImageChange" class="clickable-item"></py-upload-image>
<!-- #endif -->
</u-form-item>
<u-form-item label="测试评分" prop="nodeTestRate" borderBottom>
<!-- u-rate在模拟器演示时,初次点击导致星星置空,之后无法点击 -->
<!-- 切换到流程图,再切换到表单,也就是控制v-if隐藏与显示后,功能正常 -->
<!-- 出于上述原因,使用uni-rate -->
<!-- <u-rate v-model="nodeForm.testRate"></u-rate> -->
<uni-rate v-model="nodeForm.nodeTestRate" />
</u-form-item>
<!-- py-color-picker组件设置readOnly属性为true,表示只读状态,会自动隐藏头部 -->
<u-form-item label="测试颜色选择器" prop="nodeTestColorPicker" borderBottom>
<py-color-picker v-model="nodeForm.nodeTestColorPicker" :readOnly="nodeReadOnly"
class="clickable-item"></py-color-picker>
</u-form-item>
<u-form-item label="测试穿梭框" prop="nodeTestTransfer" borderBottom labelPosition="top">
<py-transfer :data="transferData" v-model="nodeForm.nodeTestTransfer"></py-transfer>
</u-form-item>
<u-form-item label="活动性质" prop="nodeType" borderBottom labelPosition="top">
<view class="py-checkbox-group">
<u-checkbox-group v-model="nodeForm.nodeType" placement="column">
<u-checkbox label="美食/餐厅线上活动" name="美食/餐厅线上活动"></u-checkbox>
<u-checkbox label="地推活动" name="地推活动"></u-checkbox>
<u-checkbox label="线下主题活动" name="线下主题活动"></u-checkbox>
<u-checkbox label="单纯品牌曝光" name="单纯品牌曝光"></u-checkbox>
</u-checkbox-group>
</view>
</u-form-item>
<u-form-item label="特殊资源" prop="nodeResource" borderBottom labelPosition="top">
<view class="py-radio-group">
<u-radio-group v-model="nodeForm.nodeResource" placement="column">
<u-radio shape="circle" label="线上品牌商赞助" name="线上品牌商赞助"></u-radio>
<u-radio shape="circle" label="线下场地免费" name="线下场地免费"></u-radio>
</u-radio-group>
</view>
</u-form-item>
<u-form-item label="活动形式" prop="nodeDesc">
<u-textarea v-model="nodeForm.nodeDesc" confirmType="send" autoHeight></u-textarea>
</u-form-item>
</u--form>
</view>
</view>
</template>
<script>
export default {
// 微信小程序解除样式隔离
// #ifdef MP-WEIXIN
options: {
styleIsolation: "shared"
},
// #endif
data() {
const generateData = _ => {
const data = [];
for (let i = 1; i <= 15; i++) {
data.push({
key: i,
label: `备选项 ${i}`,
disabled: i % 4 === 0
});
}
return data;
};
return {
form: {
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
testNumber: 0,
testCascader: undefined,
testSlider: 0,
testDatePicker: undefined,
testRate: undefined,
testColorPicker: undefined,
testTransfer: undefined,
resource: '',
desc: '',
fileList: []
},
rules: {
// name: [
// { required: true, message: '请输入活动名称', trigger: 'blur' },
// { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
// ],
// region: [
// { required: true, message: '请选择活动区域', trigger: 'change' }
// ],
// date1: [
// { type: 'date', required: true, message: '请选择日期', trigger: 'change' }
// ],
// date2: [
// { type: 'date', required: true, message: '请选择时间', trigger: 'change' }
// ],
// type: [
// { type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' }
// ],
// resource: [
// { required: true, message: '请选择活动资源', trigger: 'change' }
// ],
// desc: [
// { required: true, message: '请填写活动形式', trigger: 'blur' }
// ]
},
// 区域数据数组
regionData: [
{
name: "区域一",
value: "shanghai"
},
{
name: "区域二",
value: "beijing"
}
],
cascaderOptions: [{
value: 'zhinan',
label: '指南',
children: [{
value: 'shejiyuanze',
label: '设计原则',
children: [{
value: 'yizhi',
label: '一致'
}, {
value: 'fankui',
label: '反馈'
}, {
value: 'xiaolv',
label: '效率'
}, {
value: 'kekong',
label: '可控'
}]
}, {
value: 'daohang',
label: '导航',
children: [{
value: 'cexiangdaohang',
label: '侧向导航'
}, {
value: 'dingbudaohang',
label: '顶部导航'
}]
}]
}, {
value: 'zujian',
label: '组件',
children: [{
value: 'basic',
label: 'Basic',
children: [{
value: 'layout',
label: 'Layout 布局'
}, {
value: 'color',
label: 'Color 色彩'
}, {
value: 'typography',
label: 'Typography 字体'
}, {
value: 'icon',
label: 'Icon 图标'
}, {
value: 'button',
label: 'Button 按钮'
}]
}, {
value: 'form',
label: 'Form',
children: [{
value: 'radio',
label: 'Radio 单选框'
}, {
value: 'checkbox',
label: 'Checkbox 多选框'
}, {
value: 'input',
label: 'Input 输入框'
}, {
value: 'input-number',
label: 'InputNumber 计数器'
}, {
value: 'select',
label: 'Select 选择器'
}, {
value: 'cascader',
label: 'Cascader 级联选择器'
}, {
value: 'switch',
label: 'Switch 开关'
}, {
value: 'slider',
label: 'Slider 滑块'
}, {
value: 'time-picker',
label: 'TimePicker 时间选择器'
}, {
value: 'date-picker',
label: 'DatePicker 日期选择器'
}, {
value: 'datetime-picker',
label: 'DateTimePicker 日期时间选择器'
}, {
value: 'upload',
label: 'Upload 上传'
}, {
value: 'rate',
label: 'Rate 评分'
}, {
value: 'form',
label: 'Form 表单'
}]
}, {
value: 'data',
label: 'Data',
children: [{
value: 'table',
label: 'Table 表格'
}, {
value: 'tag',
label: 'Tag 标签'
}, {
value: 'progress',
label: 'Progress 进度条'
}, {
value: 'tree',
label: 'Tree 树形控件'
}, {
value: 'pagination',
label: 'Pagination 分页'
}, {
value: 'badge',
label: 'Badge 标记'
}]
}, {
value: 'notice',
label: 'Notice',
children: [{
value: 'alert',
label: 'Alert 警告'
}, {
value: 'loading',
label: 'Loading 加载'
}, {
value: 'message',
label: 'Message 消息提示'
}, {
value: 'message-box',
label: 'MessageBox 弹框'
}, {
value: 'notification',
label: 'Notification 通知'
}]
}, {
value: 'navigation',
label: 'Navigation',
children: [{
value: 'menu',
label: 'NavMenu 导航菜单'
}, {
value: 'tabs',
label: 'Tabs 标签页'
}, {
value: 'breadcrumb',
label: 'Breadcrumb 面包屑'
}, {
value: 'dropdown',
label: 'Dropdown 下拉菜单'
}, {
value: 'steps',
label: 'Steps 步骤条'
}]
}, {
value: 'others',
label: 'Others',
children: [{
value: 'dialog',
label: 'Dialog 对话框'
}, {
value: 'tooltip',
label: 'Tooltip 文字提示'
}, {
value: 'popover',
label: 'Popover 弹出框'
}, {
value: 'card',
label: 'Card 卡片'
}, {
value: 'carousel',
label: 'Carousel 走马灯'
}, {
value: 'collapse',
label: 'Collapse 折叠面板'
}]
}]
}, {
value: 'ziyuan',
label: '资源',
children: [{
value: 'axure',
label: 'Axure Components'
}, {
value: 'sketch',
label: 'Sketch Templates'
}, {
value: 'jiaohu',
label: '组件交互文档'
}]
}],
colors: ['#99A9BF', '#F7BA2A', '#FF9900'],
transferData: generateData(),
// 节点表单信息
nodeForm: {
nodeName: '',
nodeRegion: '',
nodeDate1: '',
nodeDate2: '',
nodeDelivery: false,
nodeType: [],
nodeTestNumber: 0,
nodeTestCascader: undefined,
nodeTestSlider: 0,
nodeTestDatePicker: undefined,
nodeTestRate: undefined,
nodeTestColorPicker: undefined,
nodeTestTransfer: undefined,
nodeResource: '',
nodeDesc: '',
nodeFileList: []
},
nodeRules: {
// nodeName: [
// { required: true, message: '请输入活动名称', trigger: 'blur' },
// { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
// ],
// nodeRegion: [
// { required: true, message: '请选择活动区域', trigger: 'change' }
// ],
// nodeDate1: [
// { type: 'date', required: true, message: '请选择日期', trigger: 'change' }
// ],
// nodeDate2: [
// { type: 'date', required: true, message: '请选择时间', trigger: 'change' }
// ],
// nodeType: [
// { type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' }
// ],
// nodeResource: [
// { required: true, message: '请选择活动资源', trigger: 'change' }
// ],
// nodeDesc: [
// { required: true, message: '请填写活动形式', trigger: 'blur' }
// ]
},
// 需要填报的节点名称
nodeTaskName: "一次审批",
// 节点是否只读
nodeReadOnly: true,
}
},
props: {
// 是否为只读状态
readOnly: {
type: Boolean,
default: false
},
// 表单数据,
formData: {
type: Object,
default: undefined
},
// 当前节点信息
currentNode: {
type: Object,
default: undefined
}
},
created() {
// 如果没有节点表单,可以直接赋值
// if (this.formData) {
// this.form = this.formData;
// }
// 有节点表单时设置方式
// 如果有节点表单,将数据分成普通表单和节点表单两部分
if (this.formData) {
this.splitFormData();
}
// 设置节点表单是否能填写
this.setNodeReadOnly();
},
onReady() {
// 设置规则
this.$refs.form.setRules(this.rules);
// 设置节点表单规则
this.$refs.nodeForm.setRules(this.nodeRules);
},
methods: {
// #ifdef APP-PLUS || MP-WEIXIN
// 真机和微信小程序双向绑定失效,手动设置
handleUploadImageChange(fileList) {
this.form.fileList = fileList;
},
// 节点表单
handleNodeFormUploadImageChange(fileList) {
this.nodeForm.nodeFileList = fileList;
},
// #endif
// 表单重置
reset() {
// 重置显示标签
this.selectDateTagShow = true;
this.dateTagText = "";
this.selectTimeTagShow = true;
this.timeTagText = "";
this.selectDateTimeTagShow = true;
this.dateTimeTagText = "";
this.selectColorTagShow = true;
this.form = {
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
testNumber: 0,
testCascader: undefined,
testSlider: 0,
testDatePicker: undefined,
testRate: undefined,
testColorPicker: undefined,
testTransfer: undefined,
resource: '',
desc: '',
fileList: []
};
this.resetForm("form");
},
// 表单提交
submitForm() {
// 由于不设置规则时,调用validate函数报错,而进行如下判断。
// 此方式不论是否设置规则,都不报错,可以根据实际情况进行修改。
let currentForm = this.$refs["form"];
let currentRules = currentForm.rules;
let rulesKeyList = Object.keys(currentRules);
// 如果设置规则,验证规则成功后保存。如果没有设置规则,直接保存。
if (rulesKeyList.length) {
currentForm.validate().then(valid => {
if (valid) {
uni.$emit("saveForm", this.form);
}
});
} else {
uni.$emit("saveForm", this.form);
}
},
// 节点表单内容开始
// 将formData中的数据拆分为普通表单和节点表单两部分
splitFormData() {
// 遍历 form 对象,提取 formData 中的对应属性
Object.keys(this.form).forEach(key => {
if (this.formData.hasOwnProperty(key)) {
this.form[key] = this.formData[key];
}
});
// 遍历 nodeForm 对象,提取 formData 中的对应属性
Object.keys(this.nodeForm).forEach(key => {
if (this.formData.hasOwnProperty(key)) {
this.nodeForm[key] = this.formData[key];
}
});
},
// 设置节点是否能填写
setNodeReadOnly() {
let currentNode = this.currentNode;
let nodeTaskName = this.nodeTaskName;
this.nodeReadOnly = !(currentNode && currentNode.taskName == nodeTaskName);
},
// 节点表单提交
submitNodeForm() {
// 由于不设置规则时,调用validate函数报错,而进行如下判断。
// 此方式不论是否设置规则,都不报错,可以根据实际情况进行修改。
let currentForm = this.$refs["nodeForm"];
let currentRules = currentForm.rules;
let rulesKeyList = Object.keys(currentRules);
// 如果设置规则,验证规则成功后保存。如果没有设置规则,直接保存。
if (rulesKeyList.length) {
currentForm.validate().then(valid => {
if (valid) {
uni.$emit("openComplete", this.nodeForm);
}
});
} else {
uni.$emit("openComplete", this.nodeForm);
}
}
//节点表单内容结束
},
}
</script>
<style lang="css" scoped>
/* 标签居中显示 */
/deep/ .u-tag {
justify-content: center;
}
/* 多选框间距设置 */
.py-checkbox-group /deep/ .u-checkbox {
margin-top: 10rpx;
}
/* 单选框间距设置 */
.py-radio-group /deep/ .u-radio {
margin-top: 10rpx;
}
</style>
4.使用说明
1.表单组件增加currentNode属性,保存当前节点信息,只有处理待办任务时才传值。一般使用其taskName属性(流程节点名称)判断是允许节点表单填报,因此,需要填写表单的流程节点,节点名称一定唯一,有利于区分。
2.普通表单和节点表单要通过两个表单控制,不会出现同时填写或者同时保存的情况,可以完全当做两个表单来处理。但是,表单回显时,所有数据都保存在formData中,因为数据库中是没有区分的,可以通过splitFormData方法将表单数据拆分。能够这样使用的前提是,两个表单不能有重复的属性名,不然保存数据库会混乱。
3.节点表单和普通表单使用时类似的,可以将其当做一个普通表单处理。节点表单也是支持表单规则验证的,使用节点表单必须有个名称为的submitNodeForm方法。因为,待办任务点击“审批”按钮时,会调用submitNodeForm,当规则通过后,必须调用父组件名称为openComplete方法,会自动打开审批对话框。
4.节点表单和普通表单使用方式类似,注意保证两个表单属性的唯一性,固定提交方法名称的区别和规则通过后调用父方法名称的区别即可。uniapp端和PC端使用方式类似,可以根据两端特点和参考测试表单完成功能,这里就不介绍了。