ant 的时间节点组件支持自定义节点内容, 以及节点状态。下面封装了几个业务组件,供应不同业务场景。流程审批节点的设置,查看历史节点信息,代办已办审批流程的状态查看。
如下图所示:
从ant官方不难找到如下代码
<template>
<a-timeline>
<a-timeline-item>Create a services site 2015-09-01</a-timeline-item>
<a-timeline-item>Solve initial network problems 2015-09-01</a-timeline-item>
<a-timeline-item>Technical testing 2015-09-01</a-timeline-item>
<a-timeline-item>Network problems being solved 2015-09-01</a-timeline-item>
</a-timeline>
</template>
a-timeline-item 就是每个节点的内容, 使用slot dot可以自定义节点 圆点图标。
实际中我们的代码框架是这样的
<a-timeline class="eg-time-line-todo">
<a-timeline-item class="line-item" v-for="(line, index) in flowListArr" :key="index">
<div>content</div>
<div slot="dot">
</div>
</a-timeline-item>
</a-timeline>
以上组件部分实现
EgTimelineParallelTodo:
<template>
<div>
<a-timeline class="eg-timeline-parallel-todo">
<a-timeline-item class="line-item" v-for="line in flowListArr" :key="line.nodeId">
<div class="line-right">
<parallel-node
:ref="'route' + line.nodeId"
v-if="line.type == 'route'"
:line="line"
:curNodeIDs="curNodeIDs"
:source="'todo'"
></parallel-node>
<p class="list-node" v-else>
{{ line.name }}
</p>
<p class="list-node-name" v-if="line.type == 'start'">{{ line.properties.contentLabel }}</p>
<!-- 单人 显示名称 -->
<p class="list-node-name" v-if="line.type !== 'start' && getApprovalList(line).length == 1">
{{ getApprovalList(line)[0].RY_JBXX__NAME }}
<span
>{{ getstatus(line) }}
<eg-button
v-if="line.status == 1 && curNodeIDs.includes(line.nodeId)"
:attrObject="attrObject"
@clickButton="cbClick"
></eg-button
></span>
</p>
<!-- 时间 -->
<p class="node-handle-time" v-if="line.status != '1'">{{ line.handleTime }}</p>
<span v-if="getApproveNode(line)">
<span v-if="getApprovalList(line).length == 0 && line.noneActionerAction == 'autoPass'">{{
getNoneActionerAction(line) == "autoPass" ? "(自动通过)" : "(转交管理员)"
}}</span>
</span>
<span v-if="line.status == '7'">(转审批)</span>
<span v-if="line.type == 1"
>{{ getApprovalList(line).length + getActType(line, "or") ? "人或签" : "人会签" }}
<eg-button
v-if="line.status == 1 && curNodeIDs.includes(line.nodeId)"
:attrObject="attrObject"
@clickButton="cbClick"
></eg-button
></span>
<span v-if="getAppproveOriNode(line)">(发起人自己)</span>
<!-- 多人头像 -->
<eg-head-group
v-if="getApprovalList(line).length > 1"
:photoList="getApprovalList(line)"
:isName="true"
:hasMaxLength="false"
:isphotoDel="getRuleType(line) == 'target_select'"
:isAdd="false"
:ellipsis="false"
:linerGrident="false"
></eg-head-group>
<!-- 审批意见 -->
<p
class="node-opinion"
v-if="
(line.handleOpinion && sourceName == 'todo') ||
(line.handleOpinion && opinionVisible == '0' && sourceName == 'application')
"
>
{{ line.handleOpinion }}
</p>
</div>
<template slot="dot">
<!-- 发起人头像 -->
<eg-photo type="after" :width="32" :height="32" v-if="line.type == 'start'" :photo="startPhoto"></eg-photo>
<!-- 并行分支头像 -->
<span class="circle" v-else-if="line.type == 'route'">
<eg-svg
:svgId="'bingxingfenzhi'"
:width="20"
:height="20"
:color="'#ffffff'"
:backgroundColor="'#3F8FFF'"
:verticalAlign="'-2px'"
:mright="0"
/>
</span>
<!-- 抄送头像 -->
<span class="circle" v-else-if="line.type == 'notifier'">
<eg-svg
:svgId="'icon_notifier'"
:width="32"
:height="32"
:color="'#ffffff'"
:backgroundColor="'#3F8FFF'"
:verticalAlign="'-2px'"
:mright="0"
/>
</span>
<!-- 单人头像 -->
<template v-else>
<template v-if="getApprovalList(line).length <= 1">
<eg-photo :width="32" :height="32" type="after" :photo="getApprovalList(line)[0]"></eg-photo>
</template>
<!-- 多人头像 -->
<template v-else>
<span class="circle">
<eg-svg
:svgId="'icon_manypeople'"
:width="32"
:height="32"
:color="'#ffffff'"
:backgroundColor="'#3F8FFF'"
:verticalAlign="'-2px'"
:mright="0"
/>
</span>
</template>
</template>
<!-- 状态图标 -->
<eg-svg
:svgId="getIconName(line)"
:backgroundColor="'transparent'"
:width="14"
:height="14"
class="status-icon"
/>
</template>
</a-timeline-item>
</a-timeline>
</div>
</template>
<script>
import Vue from "vue";
import { Timeline } from "ant-design-vue";
import "ant-design-vue/lib/timeline/style/css";
import ParallelNode from "./ParallelNode.vue";
Vue.use(Timeline);
/**
* @module EgTimelineParallelTodo
* @author 何志强
* @description 流程平行分支节点申请单,配置审批流程节点
*/
export default {
components: { ParallelNode },
name: "EgTimelineParallelTodo",
data() {
return {
attrObject: {
type: "link",
size: "small",
text: "催办"
},
token: sessionStorage.getItem("Authorization"),
defaultImg: 'this.src="' + require("../../commonImg/loading-failed-img.png") + '"', //默认图地址
treeType: "orgTree",
addIndex: "",
selctmodel: "",
orgIds: [],
multiple: false,
flowListArr: [],
handlerNode: null,
cbfunck: null,
startPhoto: {}
};
},
/**
* Props 接受父组件的传值 重要参数
* @prop {Array} flowList 流程节点数据源
*/
props: {
flowList: {
type: Object,
default: function() {
return {
nextId: "7954f7a941704b65a029ce250c8adb28,c6eeb77f3dc242aebc6ec96852b28ee3",
auth: [
{
name: "金额",
value: "1",
key: "7ba099ea42604ce7a0666e8f447ec38d"
},
{
name: "多行文本",
value: "1",
key: "b3a8b3894cf042baa744d7efa189592a"
}
],
name: "发起人",
type: "start",
nodeId: "4c1e626646e8487687411d044d0b21f2",
properties: {
contentLabel: "所有人",
type: "starter",
category: "0",
content: ""
},
childNode: {
nextId:
"139df798003444bcacee7e56a0707adc,bf1021ab34024051a4994635d1c832bf,b29755d09fd543468bc949cfdc7f9ef3",
name: "分支1",
prevId: "4c1e626646e8487687411d044d0b21f2",
type: "route",
nodeId: "c6eeb77f3dc242aebc6ec96852b28ee3",
childNode: {
nextId: "",
auth: [
{
name: "金额",
value: "2",
key: "7ba099ea42604ce7a0666e8f447ec38d"
},
{
name: "多行文本",
value: "2",
key: "b3a8b3894cf042baa744d7efa189592a"
}
],
name: "审批人",
prevId: "c6eeb77f3dc242aebc6ec96852b28ee3",
type: "approver",
nodeId: "b29755d09fd543468bc949cfdc7f9ef3",
properties: {
activateType: "ONE_BY_ONE",
actionerRules: [
{
approvals: [],
actType: "or",
subType: "3",
type: "target_select"
}
],
type: "approver",
agreeAll: false
},
childNode: ""
},
parallelismNodes: [
{
nextId: "fd5c709ac465469bbe59cb433d454199",
name: "",
prevId: "c6eeb77f3dc242aebc6ec96852b28ee3",
type: "parallelism",
priority: "1.0",
nodeId: "139df798003444bcacee7e56a0707adc",
childNode: {
nextId: "",
auth: [
{
name: "金额",
value: "2",
key: "7ba099ea42604ce7a0666e8f447ec38d"
},
{
name: "多行文本",
value: "2",
key: "b3a8b3894cf042baa744d7efa189592a"
}
],
name: "审批人1_1",
prevId: "139df798003444bcacee7e56a0707adc",
type: "approver",
nodeId: "fd5c709ac465469bbe59cb433d454199",
properties: {
activateType: "ONE_BY_ONE",
actionerRules: [
{
approvals: [],
actType: "or",
subType: "3",
type: "target_select"
}
],
type: "approver",
agreeAll: false
},
childNode: {
nextId: "",
auth: [
{
name: "金额",
value: "2",
key: "7ba099ea42604ce7a0666e8f447ec38d"
},
{
name: "多行文本",
value: "2",
key: "b3a8b3894cf042baa744d7efa189592a"
}
],
name: "审批人1_2",
prevId: "4c1e626646e8487687411d044d0b21f2",
type: "approver",
nodeId: "7954f7a941704b65a029ce250c8adb28",
properties: {
activateType: "ONE_BY_ONE",
actionerRules: [
{
approvals: [],
actType: "or",
subType: "3",
type: "target_select"
}
],
type: "approver",
agreeAll: false
},
childNode: ""
}
}
},
{
nextId: "8dad356f6e0a479d890326c647dc77cd,450149b8ced943f3849c96caf8bff90d",
name: "",
prevId: "c6eeb77f3dc242aebc6ec96852b28ee3",
type: "parallelism",
nodeId: "bf1021ab34024051a4994635d1c832bf",
childNode: {
nextId:
"e4e6fe160d7d4ed2afc08bd9f07fa3cf,c7203875b9a84d54b955800e311c3e56,0b0acd8add74465eab2d69c088c57ad5",
name: "分支2",
prevId: "bf1021ab34024051a4994635d1c832bf",
type: "route",
nodeId: "450149b8ced943f3849c96caf8bff90d",
childNode: "",
parallelismNodes: [
{
nextId: "f321be68edbd4880b9e1688c74df7424",
name: "",
prevId: "450149b8ced943f3849c96caf8bff90d",
type: "parallelism",
priority: "1.0",
nodeId: "e4e6fe160d7d4ed2afc08bd9f07fa3cf",
childNode: {
nextId: "",
auth: [
{
name: "金额",
value: "2",
key: "7ba099ea42604ce7a0666e8f447ec38d"
},
{
name: "多行文本",
value: "2",
key: "b3a8b3894cf042baa744d7efa189592a"
}
],
name: "审批人2_1",
prevId: "e4e6fe160d7d4ed2afc08bd9f07fa3cf",
type: "approver",
nodeId: "f321be68edbd4880b9e1688c74df7424",
properties: {
activateType: "ONE_BY_ONE",
actionerRules: [
{
approvals: [],
actType: "or",
subType: "3",
type: "target_select"
}
],
type: "approver",
agreeAll: false
},
childNode: {
nextId: "",
auth: [
{
name: "金额",
value: "2",
key: "7ba099ea42604ce7a0666e8f447ec38d"
},
{
name: "多行文本",
value: "2",
key: "b3a8b3894cf042baa744d7efa189592a"
}
],
name: "审批人2_2",
prevId: "bf1021ab34024051a4994635d1c832bf",
type: "approver",
nodeId: "8dad356f6e0a479d890326c647dc77cd",
properties: {
activateType: "ONE_BY_ONE",
actionerRules: [
{
approvals: [],
actType: "or",
subType: "3",
type: "target_select"
}
],
type: "approver",
agreeAll: false
},
childNode: ""
}
}
},
{
nextId: "3e5d85f5fdc2430abffc68b4b86eeaa8",
name: "",
prevId: "450149b8ced943f3849c96caf8bff90d",
type: "parallelism",
nodeId: "c7203875b9a84d54b955800e311c3e56",
childNode: {
nextId: "",
auth: [
{
name: "金额",
value: "2",
key: "7ba099ea42604ce7a0666e8f447ec38d"
},
{
name: "多行文本",
value: "2",
key: "b3a8b3894cf042baa744d7efa189592a"
}
],
name: "审批人3_1",
prevId: "c7203875b9a84d54b955800e311c3e56",
type: "approver",
nodeId: "3e5d85f5fdc2430abffc68b4b86eeaa8",
properties: {
activateType: "ONE_BY_ONE",
actionerRules: [
{
approvals: [],
actType: "or",
subType: "3",
type: "target_select"
}
],
type: "approver",
agreeAll: false
},
childNode: ""
}
},
{
nextId: "56b8ea405c6943ca86e79edb904c9a5a",
name: "分支3",
prevId: "450149b8ced943f3849c96caf8bff90d",
type: "parallelism",
nodeId: "0b0acd8add74465eab2d69c088c57ad5",
childNode: {
nextId: "",
auth: [
{
name: "金额",
value: "2",
key: "7ba099ea42604ce7a0666e8f447ec38d"
},
{
name: "多行文本",
value: "2",
key: "b3a8b3894cf042baa744d7efa189592a"
}
],
name: "审批人4_1",
prevId: "0b0acd8add74465eab2d69c088c57ad5",
type: "approver",
nodeId: "56b8ea405c6943ca86e79edb904c9a5a",
properties: {
activateType: "ONE_BY_ONE",
actionerRules: [
{
approvals: [],
actType: "or",
subType: "3",
type: "target_select"
}
],
type: "approver",
agreeAll: false
},
childNode: ""
}
}
]
}
}
]
}
};
}
},
nodeId: {
default: ""
},
applyUser: {
type: String
},
creator: {
type: String
},
instanceId: {
type: String
},
sourceName: {
default: "todo"
},
opinionVisible: {
type: String,
default: "0"
}
},
computed: {
curNodeIDs() {
if (this.nodeId) {
return this.nodeId.split(",");
} else return [];
}
},
mounted() {
this.getListArr(this.flowList);
},
methods: {
getRuleType(line) {
if (line.properties && line.properties.actionerRules) return line.properties.actionerRules[0].type;
else return "";
},
getApprovalList(line) {
if (line.properties && line.properties.actionerRules) return line.properties.actionerRules[0].approvals;
else return [];
},
getNoneActionerAction(line) {
return line.properties.actionerRules[0].noneActionerAction;
},
getAutoPass(line, text) {
if (line.type == "approver") {
let approveList = line.properties.actionerRules[0].approvals.length;
let noneActionerAction = this.getNoneActionerAction(line);
if (approveList == 0 && noneActionerAction == text) return true;
else return false;
} else return false;
},
getActType(line, text) {
if (line.type == "approver") {
let actT = line.properties.actionerRules[0].actType;
let approveList = line.properties.actionerRules[0].approvals.length;
if (actT == text && approveList > 1) return true;
else return false;
} else return false;
},
getAppproveOriNode(line) {
if (line.type == "approver") {
let ruleT = line.properties.actionerRules[0].type;
if (ruleT == "target_originator") return true;
else return false;
} else return false;
},
getApproveNode(line) {
if (line.type == "approver") {
let ruleT = line.properties.actionerRules[0].type;
if (ruleT == "target_post" || ruleT == "target_pos" || ruleT == "target_charge") return true;
else return false;
} else return false;
},
getListArr(flow) {
this.flowListArr.push({
nextId: flow.nextId,
name: flow.name,
type: flow.type,
nodeId: flow.nodeId,
prevId: flow.prevId,
properties: flow.properties,
parallelismNodes: flow.parallelismNodes
});
if (flow.childNode) this.getListArr(flow.childNode);
},
/**
* @function cbClick
* @description: 催办接口
* @param {*}
*/
cbClick() {
let par = {
serviceName: "plugin",
servicePath: "grid",
instanceId: this.instanceId
};
this.$request(par).then(result => {
if (result.status == "1") {
this.$message.success(result.msgInfo);
} else {
this.$message.error(result.msgInfo);
}
});
},
/**
* @function getIconName
* @description: 根据节点状态设置回显图标
* @param {*}
*/
getIconName(line) {
if (line.type === "start") return "icon-full-flow-agree";
if (line.type === "route") return this.getRouteApproveStatus(line);
let item = this.getApprovalList(line)[0];
return item.status === "1" && item.nodeId == this.nodeId
? "icon-full-flow-doing"
: item.status === "2"
? "icon-full-flow-agree"
: item.status === "3"
? "icon-full-flow-disagree"
: item.status == "4"
? "icon-full-flow-back"
: item.status == "5"
? "icon-full-flow-agree"
: item.status == "6"
? "icon-full-flow-agree"
: item.status == "7"
? "icon-full-flow-share"
: "";
},
// 判断平行分支是否全部审批完成
getRouteApproveStatus(line) {
let { poeList, routeNodeId } = this.getRoutePeople(line);
let status = poeList.map(p => p.status);
let uniA = Array.from(new Set(status));
if (uniA.length == 1 && uniA[0] == "2") return "icon-full-flow-agree";
let svgID = "";
this.curNodeIDs.forEach(id => {
if (routeNodeId.indexOf(id) > 0) {
svgID = "icon-full-flow-doing";
}
});
return svgID;
},
/**
* @function getstatus
* @description: 回显状态名称, type: 0发起人, 1审批人, 2抄送人
* @param {*}
*/
getstatus(line) {
let item = this.getApprovalList(line)[0];
// 1: 有效 2: 同意 3. 不同意 4: 退回 5:自动同意 6:自动通过
return item.status == "1" && line.nodeId == this.nodeId
? "(审批中)"
: item.status == "2"
? "(已同意)"
: item.status == "3"
? "(不同意)"
: item.status == "4"
? "(退回)"
: item.status == "5"
? "(自动同意)"
: item.status == "6"
? "(自动通过)"
: item.status == "7"
? "(转审批)"
: "";
},
// 获取平行分支节点的 审批人列表
getRoutePeople(line) {
let _this = this;
let poeList = [];
let routeNodeId = [];
line.parallelismNodes.forEach(parall => {
_this.getChildNodePeo(parall.childNode, poeList, routeNodeId);
});
return { poeList, routeNodeId };
},
// 对平行分支的一个递归操作
getChildNodePeo(childNode, poeList, routeNodeId) {
let _this = this;
if (childNode.type === "route" && childNode.parallelismNodes) {
childNode.parallelismNodes.forEach(parall => {
_this.getChildNodePeo(parall.childNode, poeList, routeNodeId);
});
} else {
let pl = childNode.properties.actionerRules[0].approvals;
routeNodeId.push(childNode.nodeId);
pl.forEach(p => {
poeList.push(p);
});
}
if (childNode.childNode) {
_this.getChildNodePeo(childNode.childNode, poeList, routeNodeId);
}
}
}
};
</script>
<style lang="scss">
.eg-timeline-parallel-todo {
.list-node {
color: rgba(0, 0, 0, 0.9);
font-size: 12px;
margin: 0;
}
.line-item .ant-timeline-item-head {
padding: 0 !important;
.status-icon {
position: absolute;
bottom: 0px;
right: 0px;
}
.circle {
border-radius: 50%;
border: 1px solid #e8e8e8;
display: inline-block;
overflow: hidden;
width: 32px;
height: 32px;
background-color: rgb(63, 143, 255);
.svg-icon {
margin-top: 5px;
}
}
.eg-photo {
border-radius: 50%;
overflow: hidden;
display: inline-block;
border: 1px solid #e8e8e8;
img {
width: 100%;
height: 100%;
-o-object-fit: cover;
object-fit: cover;
border-radius: 50%;
border: 1px solid #e8e8e8;
}
}
}
.ant-timeline-item {
padding-bottom: $spacing-xxl !important;
}
& > .line-item {
& > .ant-timeline-item-content {
left: 8px;
margin: 0 0 0 22px;
top: -15px;
.line-right {
width: 100%;
display: inline-block;
.eg-head-group {
margin-top: 13px;
}
.parallel-node {
background: #f5f5f5;
border-radius: 4px;
}
.ant-btn {
vertical-align: top;
margin-left: 12px;
margin-right: 12px;
}
}
}
}
}
</style>
ParallelNode:
<template>
<div class="parallel-node">
<!-- 面包屑 -->
<eg-breadcrumb
:breadcrumbDatas="breadcrumbDatas"
:attrObject="attrObject"
@breadcrumbItemClick="breadcrumbItemClick"
>
<span slot="separator">></span>
</eg-breadcrumb>
<!-- 平行分支几点展示 -->
<div style="display: flex; margin-top: 4px;">
<div class="parallel-line" v-for="parallel in renderLine.parallelismNodes" :key="parallel.nodeId">
<ParallelTimeLine
:source="'todo'"
:curNodeIDs="curNodeIDs"
:parallelNode="parallel.childNode"
@route-node="routeNode"
@select-person="selectPerson"
></ParallelTimeLine>
</div>
</div>
</div>
</template>
<script>
/**
* @module ParallelNode
* @author 何志强
* @description 流程平行分支节点
*/
import ParallelTimeLine from "./ParallelTimeLine.vue";
export default {
components: { ParallelTimeLine },
name: "ParallelNode",
data() {
return {
attrObject: {
separatorSlot: true,
itemSlot: false
},
breadcrumbDatas: [],
renderLine: null,
poeList: []
};
},
/**
* Props 接受父组件的传值 重要参数
* @prop {Object} line 流程节点数据源
*/
props: {
line: {
type: Object
},
source: {
type: String,
default: "applicate"
},
curNodeIDs: Array
},
watch: {
line: {
deep: true,
immediate: true,
handler(va) {
if (va) {
if (this.breadcrumbDatas.length == 0) {
this.renderLine = va;
this.breadcrumbDatas = [];
this.breadcrumbDatas.push({
name: `${this.line.name}(${this.line.parallelismNodes.length}条)`,
nodeId: this.line.nodeId,
routeNode: va
});
}
}
}
}
},
computed: {},
mounted() {},
methods: {
getRoutePeople() {
this.poeList = [];
let _this = this;
this.line.parallelismNodes.forEach(parall => {
_this.getChildNodePeo(parall.childNode);
});
return this.poeList;
},
getChildNodePeo(childNode) {
let _this = this;
if (childNode.type === "route" && childNode.parallelismNodes) {
childNode.parallelismNodes.forEach(parall => {
_this.getChildNodePeo(parall.childNode);
});
} else {
_this.poeList = [
..._this.poeList,
{ nodeId: childNode.nodeId, type: _this.getRuleType(childNode), approvals: _this.getApprovalList(childNode) }
];
}
if (childNode.childNode) {
_this.getChildNodePeo(childNode.childNode);
}
},
// 自身后面的面包屑 清除
breadcrumbItemClick(bread, index) {
this.breadcrumbDatas = this.breadcrumbDatas.filter((item, findex) => findex <= index);
this.renderLine = bread.routeNode;
},
getRuleType(line) {
if (line.properties && line.properties.actionerRules) return line.properties.actionerRules[0].type;
else return "";
},
getApprovalList(line) {
if (line.properties && line.properties.actionerRules) return line.properties.actionerRules[0].approvals;
else return [];
},
getAutoPass(line, text) {
if (line.type == "approver") {
line.approvalList.length == 0 && line.noneActionerAction == "admin";
let approveList = line.properties.actionerRules[0].approvals.length;
if (approveList == 0 && line.noneActionerAction == text) return true;
else return false;
} else return false;
},
getActType(line) {
let act = line.properties.actionerRules[0].actType;
if (act == "or") return "或签";
else return "会签";
},
getAppproveOriNode(line) {
if (line.type == "approver") {
let ruleT = line.properties.actionerRules[0].type;
if (ruleT == "target_originator") {
return true;
} else return false;
} else return false;
},
getApproveNode(line) {
if (line.type == "approver") {
let ruleT = line.properties.actionerRules[0].type;
if (ruleT == "target_post" || ruleT == "target_pos" || ruleT == "target_charge") {
return true;
} else return false;
} else return false;
},
routeNode(routeNode) {
this.renderLine = routeNode;
this.breadcrumbDatas.push({
name: `${routeNode.name}(${routeNode.parallelismNodes.length}条)`,
nodeId: routeNode.nodeId,
routeNode: routeNode
});
},
selectPerson(approveNode, cb) {
this.$emit("select-person", approveNode, cb);
}
}
};
</script>
<style lang="scss">
.parallel-node {
padding: 4px 0 8px 8px;
.parallel-line {
width: 174px;
background: #ffffff;
border-radius: 4px;
margin-right: 8px;
// padding: 8px;
padding: 12px 8px 0 8px;
.ant-timeline-item {
padding-bottom: 8px !important;
}
.ant-timeline-item-last {
padding-bottom: 0px !important;
}
.ant-timeline-item-content {
min-height: 24px;
}
}
.eg-breadcrumb .ant-breadcrumb > span:last-child {
font-size: 12px !important;
color: rgba(0, 0, 0, 0.9);
font-weight: 600;
line-height: 20px !important;
}
.eg-breadcrumb {
.ant-breadcrumb {
.ant-breadcrumb-link {
font-size: 12px;
font-weight: 400;
line-height: 20px;
}
}
.ant-breadcrumb-separator {
font-size: 14px;
line-height: 20px;
}
}
}
</style>
EgTimelineInner:
<template>
<div class="eg-timeline-inner">
<a-timeline class="eg-time-line-inner">
<a-timeline-item class="line-item" v-for="(line, index) in manyPeoFlowList" :key="index">
<div class="line-right">
<!-- 节点名称 -->
<p class="list-node">
{{ line.name || "" }}
</p>
<!-- 单人 显示名称 -->
<p class="list-node-name" v-if="line.approvals.length == 1">
{{ line.approvals[0].RY_JBXX__NAME }}
<span>{{ getstatus(line) }} </span>
</p>
<!-- 时间 -->
<p class="node-handle-time" v-if="line.status != '1'">{{ line.handleTime }}</p>
<span v-if="line.type == 'target_post' || line.type == 'target_pos' || line.type == 'target_charge'">
<span v-if="line.approvals.length == 0 && line.noneActionerAction == 'autoPass'">(自动通过)</span>
<span v-if="line.approvals.length == 0 && line.noneActionerAction == 'admin'">(转交管理员)</span>
</span>
<!-- <span v-if="line.status == '7'">(转审批)</span> -->
<span v-if="line.activateType == 'or' && line.approvals.length > 1"
>{{ line.approvals.length + "人或签" }}
</span>
<span v-if="line.activateType == 'and' && line.approvals.length > 1"
>{{ line.approvals.length + "人会签" }}
</span>
<span v-if="line.type == 'target_originator'">(发起人自己)</span>
<!-- // 多人的时候, 显示头像 -->
<p v-if="line.approvals.length > 1">
<span class="list-persin" v-for="ava in line.approvals" :key="ava.RY_JBXX__PERSONID">
<span class="circle" v-if="ava.RY_JBXX__PHOTO">
<img :src="'api/thumbnail/' + ava.RY_JBXX__PHOTO + '?' + token" :onerror="defaultImg" alt />
</span>
<span class="circle" v-else>
<img v-if="ava.RY_JBXX__GENDER !== '1'" src="../../commonImg/female.png" alt />
<img v-else-if="ava.RY_JBXX__GENDER !== '2'" src="../../commonImg/male.png" alt />
<img v-else src="../../commonImg/default-emp.png" />
</span>
<p class="list-name">{{ ava.RY_JBXX__NAME }}</p>
</span>
</p>
<!-- 审批意见 -->
<p class="node-opinion" v-if="line.handleOpinion && sourceName == 'todo'">
{{ line.handleOpinion }}
</p>
</div>
<div slot="dot">
<!-- 单人头像 -->
<template v-if="line.approvals.length <= 1">
<span class="circle" v-if="line.approvals[0] && line.approvals[0].RY_JBXX__PHOTO">
<img :src="'api/thumbnail/' + line.approvals[0].RY_JBXX__PHOTO + '?' + token" :onerror="defaultImg" alt />
</span>
<span class="circle" v-else>
<img
v-if="line.approvals[0] && line.approvals[0].RY_JBXX__GENDER !== '1'"
src="../../commonImg/female.png"
alt
/>
<img
v-else-if="line.approvals[0] && line.approvals[0].RY_JBXX__GENDER !== '2'"
src="../../commonImg/male.png"
alt
/>
<img v-else src="../../commonImg/default-emp.png" />
</span>
</template>
<!-- 多人头像 -->
<template v-else>
<span class="circle">
<eg-svg
:svgId="'icon_manypeople'"
:width="32"
:height="32"
:color="'#ffffff'"
:backgroundColor="'#3F8FFF'"
:verticalAlign="'-2px'"
:mright="0"
/>
</span>
</template>
<!-- 状态图标 -->
<eg-svg
:svgId="getIconName(line)"
:backgroundColor="'transparent'"
:width="14"
:height="14"
class="status-icon"
/>
</div>
</a-timeline-item>
</a-timeline>
</div>
</template>
<script>
/**
* @module EgTimelineInner
* @author 何志强
* @description 代办流程节点展示, 自带业务功能催办
*/
export default {
name: "EgTimelineInner",
data() {
return {
attrObject: {
type: "link",
size: "small",
text: "催办"
},
token: sessionStorage.getItem("Authorization"),
userId: "83133C71402F45D7A64DE68D3D1064CD",
defaultImg: 'this.src="' + require("../../commonImg/loading-failed-img.png") + '"' //默认图地址
};
},
computed: {
manyPeoFlowList() {
let arr = new Array();
let obj = {
name: this.flowNode.name,
nodeId: this.flowNode.nodeId,
// type: this.flowNode.type,
activateType: this.flowNode.properties.actionerRules[0].actType,
agreeAll: this.flowNode.properties.agreeAll
};
let approved = this.flowNode.properties.actionerRules[0].approvals.filter(item => item.status !== "1");
let noneApprove = this.flowNode.properties.actionerRules[0].approvals.filter(item => item.status == "1");
approved.forEach(element => {
let O = {
// 代办有审批意见, 需要按照时间排序
status: element.status,
handleOpinion: element.handleOpinion,
handleTime: element.handleTime,
type: this.flowNode.properties.actionerRules[0].type,
approvals: [element],
...obj
};
arr.push(O);
});
arr = this.handleTimeSort(arr, "handleTime");
if (noneApprove.length > 0) {
arr.push({
// 代办有审批意见
status: "1",
handleOpinion: "",
handleTime: "",
type: this.flowNode.properties.actionerRules[0].type,
approvals: noneApprove,
...obj
});
}
return arr;
}
},
/**
* Props 接受父组件的传值 重要参数
* @prop {String} curNodeIDs 当前审批中节点id
* @prop {String} applyUser 申请人, 原始发起人
* @prop {String} creator 代理的发起人
* @prop {Object} flowNode 流程节点数据源
* @prop {String} instanceId 催办接口使用
* @prop {String} sourceName 组件引用来源 可为 todo(代办)、application(申请) 默认 todo
* @prop {String} opinionVisible 审批意见是否显示 (为0 且当 sourceName == application) 时 或者 (sourceName == todo)时 显示
*/
props: {
applyUser: {
type: String
},
creator: {
type: String
},
flowNode: {
type: Object
},
instanceId: {
type: String
},
sourceName: {
default: "todo"
},
opinionVisible: {
type: String,
default: "0"
},
curNodeIDs: Array
},
methods: {
// 根据时间排序
handleTimeSort(arr, name) {
arr.sort((a, b) => {
let t1 = new Date(a[name]);
let t2 = new Date(b[name]);
return t2.getTime() - t1.getTime();
});
return arr;
},
/**
* @function getIconBgColor
* @description: 设置icon 颜色 弃用
* @param {*}
*/
getIconBgColor(item) {
return item.status === "1" && this.curNodeIDs.includes(item.nodeId)
? "#F5A60A"
: item.status === "2"
? "#1FC48D"
: item.status === "3"
? "#F53F2E"
: item.status == "4"
? "#7C00F0"
: item.status == "5"
? "#1FC48D"
: item.status == "6"
? "#1FC48D"
: "";
},
/**
* @function getIconName
* @description: 根据节点状态设置回显图标
* @param {*}
*/
getIconName(item) {
return item.status === "1" && this.curNodeIDs.includes(item.nodeId)
? "icon-full-flow-doing"
: item.status === "2"
? "icon-full-flow-agree"
: item.status === "3"
? "icon-full-flow-disagree"
: item.status == "4"
? "icon-full-flow-back"
: item.status == "5"
? "icon-full-flow-agree"
: item.status == "6"
? "icon-full-flow-agree"
: item.status == "7"
? "icon-full-flow-share"
: "";
},
/**
* @function getstatus
* @description: 回显状态名称, type: start发起人, approver审批人, notifier抄送人
* @param {*}
*/
getstatus(item) {
// 1: 有效 2: 同意 3. 不同意 4: 退回 5:自动同意 6:自动通过
return item.status == "1" && this.curNodeIDs.includes(item.nodeId)
? "(审批中)"
: item.status == "2" && item.type != "0"
? "(已同意)"
: item.status == "3"
? "(不同意)"
: item.status == "4"
? "(退回)"
: item.status == "5"
? "(自动同意)"
: item.status == "6"
? "(自动通过)"
: item.status == "7"
? "(转审批)"
: "";
}
}
};
</script>
<style lang="scss">
.eg-timeline-inner {
.ant-timeline-item-last {
.ant-timeline-item-content {
padding-bottom: 0px !important;
margin-left: 24px !important;
}
}
.line-item {
padding: 0px;
.circle {
width: 32px;
height: 32px;
border-radius: 50%;
overflow: hidden;
display: inline-block;
border: 1px solid #e8e8e8;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.ant-timeline-item-content {
left: $spacing-m;
margin: 0 0 0 22px;
padding-bottom: 36px;
top: -15px;
.line-right {
.circle {
margin-left: 12px;
margin-right: 12px;
}
.list-node {
margin-bottom: 0;
}
.list-node-name {
margin-bottom: $spacing-m;
}
.list-node {
@include text-02;
}
.list-node-name {
@include explain-01;
}
.node-handle-time {
top: 0;
position: absolute;
right: 0;
@include explain-03;
}
.node-opinion {
@include text-02;
padding: $spacing-m $spacing-l;
background-color: $bg-light;
margin: 0;
}
.list-persin {
display: inline-block;
height: 32px;
.list-name {
text-align: center;
}
}
}
}
.ant-timeline-item-head {
padding: 0 !important;
.status-icon {
position: absolute;
bottom: 0px;
right: 0px;
}
}
}
}
</style>
ParallelTimeLine:
<template>
<a-timeline>
<a-timeline-item v-for="(node, index) in childNodes" :key="index" :color="colorText(node)">
<!-- 分支节点 -->
<div class="line-content-flex" v-if="node.type == 'route'">
<div>{{ node.name }}</div>
<div class="node-people-num-text" @click="getNestNode(node)">
{{ node.parallelismNodes.length + "条" }} <span class="arrow-icon">></span>
</div>
</div>
<div class="line-content-flex" v-else>
<div>{{ node.name }}</div>
<!-- 多人 -->
<!-- // 1: 有效 2: 同意 3. 不同意 4: 退回 5:自动同意 6:自动通过 -->
<div class="line-content-right" v-if="getApprovalList(node).length > 1">
<!-- 不在当前审批节点范围 -->
<div class="line-content-right many-people-underline" v-if="!curNodeIDs.includes(node.nodeId)">
<!-- 显示会签或签 -->
{{ `${getApprovalList(node).length}人${getActType(node)}` }}
</div>
<!-- 多人 会签, 要判断每一个人的审批状态 -->
<eg-popover overlayClassName="time-line-simple" v-else>
<template slot="popoverContent">
<div style="margin-bottom: 24px;">
{{ `${node.name}(${getApprovalList(node).length}人${getActType(node)})` }}
</div>
<EgTimelineInner :flowNode="node" :sourceName="'todo'" :curNodeIDs="curNodeIDs" />
</template>
<div class="line-content-right many-people-underline" slot="main">
<!-- 显示会签或签 -->
{{ `${getApprovalList(node).length}人${getActType(node)}` }}
</div>
</eg-popover>
</div>
<!-- 单人 -->
<template v-if="getApprovalList(node).length == 1">
<eg-popover v-if="getApprovalList(node)[0].status !== '1'" overlayClassName="time-line-simple">
<template slot="popoverContent">
<div class="popover-left-name">
审批状态 <span class="popover-right-text">{{ getstatus(node) }}</span>
</div>
<div class="popover-left-name">
审批意见 <span class="popover-right-text">{{ getApprovalList(node)[0].handleOpinion }}</span>
</div>
<div class="popover-left-name">
审批时间 <span class="popover-right-text">{{ getApprovalList(node)[0].handleTime }}</span>
</div>
</template>
<div class="line-content-right" slot="main">
{{ getApprovalList(node)[0].RY_JBXX__NAME }}
</div>
</eg-popover>
<div v-else class="line-content-right">
{{ getApprovalList(node)[0].RY_JBXX__NAME }}
</div>
</template>
</div>
</a-timeline-item>
</a-timeline>
</template>
<script>
import EgTimelineInner from "./EgTimelineInner.vue";
/**
* @module ParallelTimeLine
* @author 何志强
* @description 流程平行分支节点
*/
export default {
name: "ParallelTimeLine",
components: { EgTimelineInner },
data() {
return {
childNodes: []
};
},
/**
* Props 接受父组件的传值 重要参数
* @prop {Object} line 流程节点数据源
*/
props: {
parallelNode: {
type: Object
},
source: {
type: String,
default: "applicate"
},
curNodeIDs: {
type: Array
}
},
watch: {
parallelNode: {
deep: true,
immediate: true,
handler(va) {
if (va) {
this.childNodes = [];
this.getListArr(va);
}
}
}
},
computed: {},
mounted() {},
methods: {
// <!-- // 1: 有效 2: 同意 3. 不同意 4: 退回 5:自动同意 6:自动通过 -->
// 节点圆圈的 颜色, 根据 status 是否等于 1 未审批, 或者2 审批通过, 当前节点, 会签是否全部完成等判断审批节点颜色
// #EAA83E 当前审批节点 黄色
// "#1890ff" 默认节点颜色 蓝色
// #5CC190 审批通过颜色 绿色
colorText(node) {
let approvals = this.getApprovalList(node);
if (node.type == "route" && approvals.length == 0) {
let colorArr = [];
let filteredArr = [];
node.parallelismNodes.forEach(item => {
colorArr.push(this.handlerApprovals(item.childNode, this.getApprovalList(item.childNode)));
});
filteredArr = [...new Set(colorArr)];
return filteredArr.length == 1 ? filteredArr[0] : "#EAA83E";
} else {
return this.handlerApprovals(node, approvals);
}
},
handlerApprovals(node, approvals) {
if (approvals && approvals.length == 1) {
if (approvals[0].status == 1) {
if (this.curNodeIDs.includes(node.nodeId)) return "#EAA83E";
else return "#1890ff";
} else if (approvals[0].status == 2) return "#5CC190";
else return "#1890ff";
} else if (approvals.length > 1) {
if (this.getActType(node) == "会签") {
let arr = approvals.map(item => item.status);
// 存在未审批
if (arr.includes("1")) {
if (this.curNodeIDs.includes(node.nodeId)) return "#EAA83E";
else return "#1890ff";
} else {
let narr = [...new Set(arr)];
if (narr[0] == "2" && narr.length == 1) return "#5CC190";
else return "#1890ff";
}
} else {
// 或签
if (this.curNodeIDs.includes(node.nodeId)) return "#EAA83E";
else return "#1890ff";
}
}
return "#1890ff";
},
// 判断会签是否全部通过
checkAnd(node) {
let approvals = this.getApprovalList(node);
if (this.getActType(node) == "会签") {
let arr = approvals.map(item => item.status);
// 存在未审批
if (arr.includes("1")) {
return false;
} else {
// 有过审批, 可以是拒绝 通过等
return true;
}
}
},
// 自身后面的面包屑 清除
breadcrumbItemClick(bread) {
console.log(bread);
},
getRuleType(line) {
if (line.properties && line.properties.actionerRules) return line.properties.actionerRules[0].type;
else return "";
},
getApprovalList(line) {
if (line.properties && line.properties.actionerRules) return line.properties.actionerRules[0].approvals;
else return [];
},
getAutoPass(line, text) {
if (line.type == "approver") {
line.approvalList.length == 0 && line.noneActionerAction == "admin";
let approveList = line.properties.actionerRules[0].approvals.length;
if (approveList == 0 && line.noneActionerAction == text) return true;
else return false;
} else return false;
},
getActType(line) {
let act = line.properties.actionerRules[0].actType;
if (act == "or") return "或签";
else return "会签";
},
getAppproveOriNode(line) {
if (line.type == "approver") {
let ruleT = line.properties.actionerRules[0].type;
if (ruleT == "target_originator") {
return true;
} else return false;
} else return false;
},
getApproveNode(line) {
if (line.type == "approver") {
let ruleT = line.properties.actionerRules[0].type;
if (ruleT == "target_post" || ruleT == "target_pos" || ruleT == "target_charge") {
return true;
} else return false;
} else return false;
},
getListArr(flow) {
this.childNodes.push({
nextId: flow.nextId,
name: flow.name,
type: flow.type,
nodeId: flow.nodeId,
prevId: flow.prevId,
properties: flow.properties,
parallelismNodes: flow.parallelismNodes
});
if (flow.childNode) {
this.getListArr(flow.childNode);
}
},
getNestNode(routeNode) {
this.$emit("route-node", routeNode);
},
selectPerson(approveNode) {
if (this.source == "todo") return false;
this.$emit("select-person", approveNode, approvals => {
approveNode.properties.actionerRules[0].approvals = approvals;
});
},
getstatus(line) {
let item = this.getApprovalList(line)[0];
// 1: 有效 2: 同意 3. 不同意 4: 退回 5:自动同意 6:自动通过
return item.status == "1" && line.nodeId == this.nodeId
? "审批中"
: item.status == "2" && item.type != "0"
? "已同意"
: item.status == "3"
? "不同意"
: item.status == "4"
? "退回"
: item.status == "5"
? "自动同意"
: item.status == "6"
? "自动通过"
: item.status == "7"
? "转审批"
: "";
}
}
};
</script>
<style lang="scss">
.time-line-simple {
min-width: 240px;
.popover-left-name {
color: #191919;
font-size: 14px;
line-height: 22px;
margin-bottom: 8px;
}
.popover-right-text {
display: inline-block;
width: 207px;
margin-left: 24px;
color: #4c4c4c;
}
}
.line-content-flex {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 12px;
line-height: 24px;
.line-content-right {
cursor: pointer;
&:hover {
color: #3f8fff;
}
}
.select-people-tips {
color: #eaa83e;
}
.node-people-num-text {
font-size: 12px;
color: #3f8fff;
line-height: 20px;
cursor: pointer;
.arrow-icon {
color: #7f7f7f;
}
}
}
</style>