🎉 1.1.3.RELEASE

master 1.1.x
ssc 4 years ago
parent 6de6dcdfa9
commit 0165c01d75
  1. 6
      public/cdn/wf-design/index.umd.min.js
  2. 139
      src/api/plugin/workflow/ops.js
  3. 14
      src/views/plugin/workflow/mixins/ex-form.js
  4. 129
      src/views/plugin/workflow/ops/detail.vue
  5. 558
      src/views/plugin/workflow/ops/index.vue
  6. 4
      src/views/plugin/workflow/process/components/examForm.vue
  7. 21
      src/views/plugin/workflow/process/components/flow.vue

File diff suppressed because one or more lines are too long

@ -0,0 +1,139 @@
import request from '@/router/axios'
const prefix = '/api/blade-workflow/ops'
/**
* 所有待办任务
*/
export const getList = (current, size, params) => {
return request({
url: `${prefix}/list`,
method: 'get',
params: {
...params,
current,
size,
}
})
}
/**
* 完成任务
*/
export const completeTask = (data) => {
return request({
url: `${prefix}/completeTask`,
method: 'post',
data
})
}
/**
* 变更任务状态
*/
export const changeTaskStatus = (data) => {
return request({
url: `${prefix}/changeTaskStatus`,
method: 'post',
data
})
}
/**
* 变更任务审核人
*/
export const changeTaskAssignee = (data) => {
return request({
url: `${prefix}/changeTaskAssignee`,
method: 'post',
data
})
}
/**
* 转办任务
*/
export const transferTask = (data) => {
return request({
url: `${prefix}/transferTask`,
method: 'post',
data
})
}
/**
* 委托任务
*/
export const delegateTask = (data) => {
return request({
url: `${prefix}/delegateTask`,
method: 'post',
data
})
}
/**
* 抄送任务
*/
export const copyTask = (data) => {
return request({
url: `${prefix}/copyTask`,
method: 'post',
data
})
}
/**
* 催办任务
*/
export const urgeTask = (data) => {
return request({
url: `${prefix}/urgeTask`,
method: 'post',
data
})
}
/**
* 终止流程
*/
export const terminateProcess = (data) => {
return request({
url: `${prefix}/terminateProcess`,
method: 'post',
data
})
}
/**
* 流程节点
*/
export const processNodes = (params) => {
return request({
url: `${prefix}/processNodes`,
method: 'get',
params
})
}
/**
* 指定回退
*/
export const rollbackTask = (data) => {
return request({
url: `${prefix}/rollbackTask`,
method: 'post',
data
})
}
/**
* 调度任务
*/
export const dispatchTask = (data) => {
return request({
url: `${prefix}/dispatchTask`,
method: 'post',
data
})
}

@ -83,6 +83,11 @@ export default {
if (col.type == 'dynamic') { // 处理子表单
col.children.column = this.filterAvueColumn(col.children.column, taskForm).column
}
if (col.rules && col.pattern) {
col.rules.forEach(c => {
if (c.pattern) c.pattern = new RegExp(col.pattern)
})
}
values.push(col)
}
})
@ -271,6 +276,15 @@ export default {
if (comments && comments.length > 0) {
let comment
let { type, fullMessage } = comments[0]
if (type == 'assigneeComment') {
comment = '变更审核人:' + fullMessage
ff.class = 'nodeWarn'
}
if (type == 'dispatchComment') {
comment = '调度:' + fullMessage
ff.class = 'nodeWarn'
}
if (type == 'transferComment') {
comment = '转办:' + fullMessage
ff.class = 'nodeWarn'

@ -0,0 +1,129 @@
<template>
<el-tabs v-model="activeName">
<el-tab-pane label="申请信息"
name="first">
<el-card shadow="never">
<div ref="printBody">
<avue-form v-if="option && ((option.column && option.column.length > 0) || (option.group && option.group.length > 0))"
v-model="form"
ref="form"
:option="option">
</avue-form>
</div>
</el-card>
</el-tab-pane>
<el-tab-pane label="流转信息"
name="second">
<el-card shadow="never"
style="margin-top: 5px;">
<wf-flow :flow="flow"></wf-flow>
</el-card>
</el-tab-pane>
<el-tab-pane label="流程跟踪"
name="third">
<template v-if="activeName == 'third'">
<el-card shadow="never"
style="margin-top: 5px;">
<wf-design ref="bpmn"
style="height: 500px;"
:options="bpmnOption"></wf-design>
</el-card>
</template>
</el-tab-pane>
</el-tabs>
</template>
<script>
import WfFlow from '../process/components/flow.vue'
import exForm from '../mixins/ex-form'
export default {
mixins: [exForm],
components: { WfFlow },
props: {
processInstanceId: String,
taskId: String
},
watch: {
processInstanceId: {
handler() {
const { taskId, processInstanceId } = this
if (taskId && processInstanceId) this.getDetail(taskId, processInstanceId)
},
immediate: true
}
},
data() {
return {
activeName: 'first',
form: {},
option: {},
vars: [], //
submitLoading: false, // loading
}
},
methods: {
//
getDetail(taskId, processInsId) {
this.getTaskDetail(taskId, processInsId).then(res => {
const { process, form } = res
const { variables } = process
let { allForm } = form
if (allForm) {
const option = eval('(' + allForm + ')')
option.menuBtn = false
const { column, group } = option
option.detail = true
if (column && column.length > 0) { // column
column.forEach(col => {
if (col.type == 'dynamic') col.children.column.forEach(cc => delete cc.value)
else delete col.value
})
}
if (group && group.length > 0) { // group
group.forEach(gro => {
if (gro.column && gro.column.length > 0) {
gro.column.forEach(col => {
if (col.type == 'dynamic') col.children.column.forEach(cc => delete cc.value)
else delete col.value
})
}
})
}
for (let key in variables) {
if (!variables[key]) delete variables[key]
}
if (process.variables && process.variables.serialNumber) {
column.unshift({
label: '流水号',
prop: 'serialNumber',
span: 24,
detail: true,
})
}
option.column = column
option.group = group
this.option = option
this.form = variables
}
})
},
}
}
</script>
<style lang="scss" scoped>
.header {
width: 100%;
height: 50px;
background: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px 10px 0;
}
</style>

@ -0,0 +1,558 @@
<template>
<basic-container>
<avue-crud ref="crud"
:option="option"
:table-loading="loading"
:data="data"
:page.sync="page"
v-model="form"
@search-change="searchChange"
@search-reset="searchReset"
@selection-change="selectionChange"
@current-change="currentChange"
@size-change="sizeChange"
@refresh-change="onLoad(page, query)"
@on-load="onLoad">
<template #menuLeft>
<el-button v-if="permission.wf_ops_pass"
:disabled="loading"
size="mini"
type="success"
icon="el-icon-check"
@click="handleCompleteTask(null, true)">通过</el-button>
<el-button v-if="permission.wf_ops_reject"
:disabled="loading"
size="mini"
type="danger"
icon="el-icon-close"
@click="handleCompleteTask(null, false)">驳回</el-button>
<el-button v-if="permission.wf_ops_change_assignee"
:disabled="loading"
size="mini"
type="warning"
icon="el-icon-user"
@click="handleUserSelect({type: 'assignee', checkType: 'radio'})">变更审核人</el-button>
<el-button v-if="permission.wf_ops_transfer"
:disabled="loading"
size="mini"
type="primary"
icon="el-icon-user"
@click="handleUserSelect({type: 'transfer', checkType: 'radio'})">转办</el-button>
<el-button v-if="permission.wf_ops_delegate"
:disabled="loading"
size="mini"
type="success"
icon="el-icon-user"
@click="handleUserSelect({type: 'delegate', checkType: 'radio'})">委托</el-button>
<el-button v-if="permission.wf_ops_copy"
:disabled="loading"
size="mini"
type="info"
icon="el-icon-s-promotion"
@click="handleUserSelect({type: 'copy', checkType: 'checkbox'})">抄送</el-button>
<el-button v-if="permission.wf_ops_urge"
:disabled="loading"
size="mini"
type="warning"
icon="el-icon-warning"
@click="handleUrgeTask(null)">催办</el-button>
<el-button v-if="permission.wf_ops_terminate"
:disabled="loading"
size="mini"
type="danger"
icon="el-icon-s-opportunity"
@click="handleTerminateProcess(null)">终止</el-button>
</template>
<template #processDefinitionName="{row}">
<el-link v-if="permission.wf_ops_detail"
style="font-size: 12px;"
type="primary"
@click="handleDetail(row)">{{row.processDefinitionName}}</el-link>
<span v-else>{{row.processDefinitionName}}</span>
</template>
<template #isSuspended="{row}">
<el-tag v-if="row.isSuspended"
size="mini"
type="danger">挂起</el-tag>
<el-tag v-else
size="mini">激活</el-tag>
</template>
<template #menu="{row}">
<el-button v-if="permission.wf_ops_follow"
type="text"
size="small"
icon="el-icon-search"
@click="handleFlow(row)">流程图</el-button>
<el-dropdown style="margin-left: 5px;">
<el-button size="mini"
type="text">更多操作<i class="el-icon-arrow-down el-icon--right"></i></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="permission.wf_ops_pass"
@click.native="handleCompleteTask(row.taskId, true)">通过</el-dropdown-item>
<el-dropdown-item v-if="permission.wf_ops_reject"
@click.native="handleCompleteTask(row.taskId, false)">驳回</el-dropdown-item>
<el-dropdown-item v-if="permission.wf_ops_transfer"
@click.native="handleUserSelect({type: 'transfer', checkType: 'radio'}, row)">转办</el-dropdown-item>
<el-dropdown-item v-if="permission.wf_ops_delegate"
@click.native="handleUserSelect({type: 'delegate', checkType: 'radio'}, row)">委托</el-dropdown-item>
<el-dropdown-item v-if="permission.wf_ops_dispatch"
@click.native="getProcessNodes(row.taskId, row.processInstanceId, 'dispatch')">调度</el-dropdown-item>
<el-dropdown-item v-if="permission.wf_ops_rollback"
@click.native="getProcessNodes(row.taskId, row.processInstanceId, 'rollback')">指定回退</el-dropdown-item>
<el-dropdown-item v-if="permission.wf_ops_terminate"
@click.native="handleTerminateProcess(row.taskId)">终止</el-dropdown-item>
<el-dropdown-item v-if="permission.wf_ops_copy"
@click.native="handleUserSelect({type: 'copy', checkType: 'checkbox'}, row)">抄送</el-dropdown-item>
<el-dropdown-item v-if="permission.wf_ops_urge"
@click.native="handleUrgeTask(row.taskId)">催办</el-dropdown-item>
<el-dropdown-item v-if="permission.wf_ops_active && row.isSuspended"
@click.native="handleChangeStatus(row, false)">激活</el-dropdown-item>
<el-dropdown-item v-if="permission.wf_ops_suspend && !row.isSuspended"
@click.native="handleChangeStatus(row, true)">挂起</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</avue-crud>
<el-dialog :visible.sync="bpmnVisible"
append-to-body
destroy-on-close
title="流程图">
<wf-design ref="bpmn"
style="height: 500px;"
:options="bpmnOption"></wf-design>
</el-dialog>
<!-- 人员选择弹窗 -->
<user-select ref="user-select"
:check-type="checkType"
@onConfirm="handleUserSelectConfirm"></user-select>
<!-- 选择节点弹窗 -->
<el-dialog :visible.sync="nodeVisible"
append-to-body
title="选择节点">
<avue-form v-if="nodeVisible"
v-model="nodeForm"
:option="nodeOption"
@submit="handleNodeSubmit"></avue-form>
</el-dialog>
<el-drawer :visible.sync="detailVisible"
:title="form.processDefinitionName"
custom-class="wf-drawer"
size="60%"
append-to-body>
<task-detail v-if="detailVisible"
:taskId="form.taskId"
:processInstanceId="form.processInstanceId"></task-detail>
</el-drawer>
</basic-container>
</template>
<script>
import { detail } from '@/api/plugin/workflow/process'
import {
getList, completeTask, changeTaskStatus, changeTaskAssignee, transferTask, delegateTask, copyTask, urgeTask, terminateProcess, processNodes, rollbackTask, dispatchTask
} from "@/api/plugin/workflow/ops";
import { userList } from "@/api/plugin/workflow/process";
import { mapGetters } from "vuex";
import UserSelect from '../process/components/user-select.vue'
import TaskDetail from './detail.vue'
import exForm from '../mixins/ex-form'
export default {
mixins: [exForm],
components: {
UserSelect, TaskDetail
},
data() {
return {
checkType: 'radio',
form: {},
query: {},
loading: true,
page: {
pageSize: 10,
currentPage: 1,
total: 0
},
selectionList: [],
option: {
size: 'mini',
height: 'auto',
calcHeight: 30,
tip: false,
border: true,
selection: true,
dialogType: 'drawer',
addBtn: false,
editBtn: false,
delBtn: false,
align: 'center',
searchSize: 'mini',
searchMenuSpan: 6,
// searchIndex: 3,
// searchIcon: true,
column: [
{
label: '流程名称',
prop: 'processDefinitionName',
search: true,
overHidden: true
},
{
label: '流程标识',
prop: 'processDefinitionKey',
search: true,
overHidden: true
},
{
label: '流水号',
prop: 'serialNumber',
search: true,
overHidden: true
},
{
label: "流程分类",
row: true,
type: 'tree',
dicUrl: '/api/blade-workflow/design/category/tree',
props: {
label: 'name',
value: 'id'
},
prop: "category",
search: true,
},
{
label: '当前节点',
prop: 'taskName',
search: true
},
{
label: '审核人',
prop: 'assignee',
search: true
},
{
label: '申请人',
prop: 'applyUserName',
search: true
},
{
label: '创建时间',
prop: 'createTime',
type: 'datetime',
format: 'yyyy-MM-dd HH:mm',
width: 165,
},
{
label: '状态',
prop: 'isSuspended',
type: 'select',
dicData: [{
label: '挂起',
value: true
}, {
label: '激活',
value: false
}],
search: true
},
{
label: '时间范围',
prop: 'date',
type: 'date',
dataType: 'string',
format: 'yyyy-MM-dd',
valueFormat: 'yyyy-MM-dd',
hide: true,
search: true,
searchRange: true,
}
]
},
data: [],
bpmnVisible: false,
bpmnOption: {},
nodeVisible: false,
nodeForm: {},
nodeOption: {
column: [{
label: '节点',
prop: 'nodeId',
type: 'select',
props: {
label: 'nodeName',
value: 'nodeId'
},
span: 24,
rules: [{ required: true, message: '请选择节点', trigger: 'change' }]
}]
},
nodeType: '',
detailVisible: false
};
},
computed: {
...mapGetters(["permission"]),
ids() {
let ids = [];
this.selectionList.forEach(ele => {
ids.push(ele.taskId);
});
return ids.join(",");
}
},
methods: {
handleDetail(row) {
this.form = { ...row }
this.detailVisible = true
},
rollbackTask,
dispatchTask,
handleNodeSubmit(form, done) {
const { nodeId, taskId } = form
const param = { nodeId, taskId }
this.loading = true
this[`${this.nodeType}Task`](param).then(() => {
this.$message.success('操作成功')
this.form = {}
done()
this.nodeVisible = false
this.onLoad(this.page, this.query)
}).catch(() => {
done()
this.loading = false
})
},
getProcessNodes(taskId, processInstanceId, type) {
this.nodeType = type
const param = { processInstanceId }
if (type == 'rollback') param.taskId = taskId
processNodes(param).then(res => {
this.findObject(this.nodeOption.column, 'nodeId').dicData = res.data.data
this.nodeVisible = true
this.nodeForm = {
...this.nodeForm,
taskId, processInstanceId
}
})
},
handleCompleteTask(taskId, pass) {
if (!taskId) {
if (this.selectionList.length === 0) {
this.$message.warning("请选择至少一条数据")
return
}
taskId = this.ids
}
this.$confirm(`确定要将选中的任务全部<span style='color: red;'> ${pass ? '通过' : '驳回'} </span>吗?`, '警告', {
type: 'warning',
dangerouslyUseHTMLString: true,
}).then(() => {
this.loading = true
completeTask({ taskId, pass }).then(() => {
this.$message.success('操作成功')
this.onLoad(this.page, this.query)
}).catch(() => {
this.loading = false
})
}).catch(() => { })
},
handleUrgeTask(taskId) {
if (!taskId) {
if (this.selectionList.length === 0) {
this.$message.warning("请选择至少一条数据")
return
}
taskId = this.ids
}
this.$confirm(`确定要将催办选中的任务吗?若任务没有审核人此操作无效`, '警告', {
type: 'warning',
}).then(() => {
this.loading = true
urgeTask({ taskId }).then(() => {
this.$message.success('催办成功')
this.onLoad(this.page, this.query)
}).catch(() => {
this.loading = false
})
}).catch(() => { })
},
handleTerminateProcess(taskId) {
if (!taskId) {
if (this.selectionList.length === 0) {
this.$message.warning("请选择至少一条数据")
return
}
taskId = this.ids
}
this.$confirm(`确定要将选中的任务流程终止吗?`, '警告', {
type: 'warning',
}).then(() => {
this.loading = true
terminateProcess({ taskId }).then(() => {
this.$message.success('终止成功')
this.onLoad(this.page, this.query)
}).catch(() => {
this.loading = false
})
}).catch(() => { })
},
handleChangeStatus(row, isSuspended) {
this.loading = true
changeTaskStatus({ taskId: row.taskId, isSuspended }).then(() => {
this.$message.success('操作成功')
this.onLoad(this.page, this.query)
}).catch(() => {
this.loading = false
})
},
//
handleUserSelect({ type, checkType }, row) {
if (row) this.selectionList = [row]
if (this.selectionList.length === 0) {
this.$message.warning("请选择至少一条数据")
return
}
this.$refs['user-select'].visible = true
this.userSelectType = type
this.checkType = checkType
},
handleUserSelectConfirm(id, name) {
switch (this.userSelectType) {
case 'assignee':
this.$confirm(`确定要将选中的任务审核人变更为<span style='color: red;'> ${name} </span>吗?`, '提示', {
type: 'warning',
dangerouslyUseHTMLString: true,
}).then(() => {
this.$refs['user-select'].visible = false
this.loading = true
changeTaskAssignee({ taskId: this.ids, assignee: id }).then(() => {
this.$message.success('操作成功')
this.onLoad(this.page, this.query)
this.selectionList = []
}).catch(() => {
this.loading = false
})
}).catch(() => { })
break;
case 'transfer':
this.$confirm(`确定要将选中的任务转办给<span style='color: red;'> ${name} </span>吗?`, '提示', {
type: 'warning',
dangerouslyUseHTMLString: true,
}).then(() => {
this.$refs['user-select'].visible = false
this.loading = true
transferTask({ taskId: this.ids, assignee: id }).then(() => {
this.$message.success('转办成功')
this.onLoad(this.page, this.query)
this.selectionList = []
}).catch(() => {
this.loading = false
})
}).catch(() => { })
break;
case 'delegate':
this.$confirm(`确定要将选中的任务委托给<span style='color: red;'> ${name} </span>吗?`, '提示', {
type: 'warning',
dangerouslyUseHTMLString: true,
}).then(() => {
this.$refs['user-select'].visible = false
this.loading = true
delegateTask({ taskId: this.ids, assignee: id }).then(() => {
this.$message.success('委托成功')
this.onLoad(this.page, this.query)
this.selectionList = []
}).catch(() => {
this.loading = false
})
}).catch(() => { })
break;
case 'copy':
this.$confirm(`确定要将选中的任务抄送给<span style='color: red;'> ${name} </span>吗?`, '提示', {
type: 'warning',
dangerouslyUseHTMLString: true,
}).then(() => {
this.$refs['user-select'].visible = false
copyTask({ taskId: this.ids, assignee: id }).then(() => {
this.$refs.crud.toggleSelection()
this.$message.success('抄送成功')
}).catch(() => {
this.loading = false
})
}).catch(() => { })
break;
}
},
handleFlow(row) {
const { taskId, processInstanceId } = row
detail({ taskId, processInsId: processInstanceId }).then(res => {
const { process, flow } = res.data.data
this.bpmnOption = {
mode: 'view',
xml: process.xml,
flows: this.handleResolveFlows(flow)
}
this.bpmnVisible = true
})
},
searchReset() {
this.query = {};
this.onLoad(this.page);
},
searchChange(params, done) {
this.query = params;
this.page.currentPage = 1;
this.onLoad(this.page, params);
done();
},
selectionChange(list) {
this.selectionList = list;
},
currentChange(currentPage) {
this.page.currentPage = currentPage;
},
sizeChange(pageSize) {
this.page.pageSize = pageSize;
},
async onLoad(page, params = {}) {
this.loading = true;
const param = Object.assign(params, this.query)
const { assignee } = param
if (assignee) {
const res = await userList(1, -1, { name: assignee })
if (res.data.data.records && res.data.data.records.length > 0) {
param.assignee = res.data.data.records.map(d => d.id).join(',')
} else {
this.page.total = 0
this.data = []
this.loading = false
}
}
getList(page.currentPage, page.pageSize, param).then(res => {
const data = res.data.data
this.page.total = data.total
this.data = data.records
this.loading = false
}).catch(() => {
this.loading = false
})
}
}
};
</script>
<style lang="scss">
.wf-drawer {
.el-drawer__body {
padding: 0 20px;
overflow: auto;
}
}
</style>

@ -65,13 +65,13 @@ export default {
}, {
label: '指定审批人',
prop: '$assignee',
placeholder: '请选择 下一级审批人,如不选择则使用默认处理人,驳回时无效',
placeholder: '请选择 下一级审批人,如不选择则使用默认处理人,驳回时无效。多选时若下一节点为多实例则按选择顺序赋值,若不是择只有第一个生效。',
readonly: true,
append: '+',
span: 24,
event: {
click: () => {
this.$emit('user-select', { type: 'assignee', checkType: 'radio' })
this.$emit('user-select', { type: 'assignee', checkType: 'checkbox' })
}
},
display: true

@ -13,11 +13,7 @@
<p v-for="(comment, index) in item.comments"
:key="index">
<template v-if="index < 1">
<span v-if="comment.type == 'transferComment'">转办: [{{comment.fullMessage}}]</span>
<span v-if="comment.type == 'delegateComment'">委托: [{{comment.fullMessage}}]</span>
<span v-if="comment.type == 'rollbackComment'">驳回意见: [{{comment.fullMessage}}]</span>
<span v-if="comment.type == 'terminateComment'">终止意见: [{{comment.fullMessage}}]</span>
<span v-if="comment.type == 'comment'">审批意见: [{{comment.fullMessage}}]</span>
<span v-if="commentMap[comment.type]">{{commentMap[comment.type]}}: [{{comment.fullMessage}}]</span>
<span style="color: #1989fa; float: right;"
v-if="item.comments.length > 1"
@click="handleClick">{{toggleText}} <i :class="[isFlag ? 'el-icon-arrow-up' : 'el-icon-arrow-down']"></i> </span>
@ -25,11 +21,7 @@
v-if="comment.time">{{comment.time}}</p>
</template>
<template v-if="index > 0 && isFlag">
<span v-if="comment.type == 'transferComment'">转办: [{{comment.fullMessage}}]</span>
<span v-if="comment.type == 'delegateComment'">委托: [{{comment.fullMessage}}]</span>
<span v-if="comment.type == 'rollbackComment'">驳回意见: [{{comment.fullMessage}}]</span>
<span v-if="comment.type == 'terminateComment'">终止意见: [{{comment.fullMessage}}]</span>
<span v-if="comment.type == 'comment'">审批意见: [{{comment.fullMessage}}]</span>
<span v-if="commentMap[comment.type]">{{commentMap[comment.type]}}: [{{comment.fullMessage}}]</span>
<p style="color: gray; font-size: 12px;"
v-if="comment.time">{{comment.time}}</p>
</template>
@ -58,6 +50,15 @@ export default {
return {
isFlag: false,
toggleText: '展开',
commentMap: {
assigneeComment: '变更审核人',
dispatchComment: '调度',
transferComment: '转办',
delegateComment: '委托',
rollbackComment: '驳回意见',
terminateComment: '终止意见',
comment: '审批意见'
}
}
},
methods: {

Loading…
Cancel
Save