单分支:
二分支:
1、需求
在项目中,我们除了会用到单分支,还会用到二分支,而现在没有二分支的组件,所以我们只能动手写了。
2、分叉步骤条设计
1、单个节点设计
1、将单个节点分离出来,用一个组件复用,分为三部分(节点序号、节点名称、节点的连接线),宽度auto,flex布局。
3、节点序号部分:
未开始的节点用灰色表示,显示节点序号;
选中节点用橙色表示,显示节点序号;
已完成的节点用橙色表示,显示勾选图标。
4、节点名称部分:
未开始的节点用灰色表示;
选中节点和已完成的节点用黑色表示。
5、节点的连接线部分:
最后一个节点没有连接线;
未开始的节点、选中节点用灰色表示;
已完成的节点用橙色表示。
2、四个角的连接线用盒子,只要某些边框即可
事实上右下角和右上角,用span标签包裹的线比较好
3、四个角的颜色设计
分子左边的颜色:子分支status有大于等于1的,则为完成色;
分支右边的颜色:子分支最后一个的status等于2,则为完成色。
4、分支流程内部用flex布局
5、分支流程长度不一致的处理情况
计算两个分支的宽度,相减就是span线的宽度
3、数据结构设计
1、以上图为例,如果节点的children长度为2,则表示存在分支,children[0]为上分支,children[1]为下分支。
2、在上分支中,可使用递归算法获取所有的节点,放在一维数组中。
3、status代表节点状态:0为未开始,1为选中,2为已完成。
branchWorkFlowList: [
{
step_no: 1,
flow_name: "步骤1",
status: 2,
children: [
{
step_no: 1,
flow_name: "步骤1",
status: 2,
children: [
{
step_no: 2,
flow_name: "步骤2",
status: 2,
children: [
{
step_no: 3,
flow_name: "步骤3",
status: 2,
children: [],
},
],
},
],
},
{
step_no: 1,
flow_name: "步骤1",
status: 2,
children: [],
},
],
},
],
6、上代码
<template>
<div>
//单分支
<DuiBranchStepBarNew :workFlowList="workFlowList" />
//二分支
<DuiBranchStepBarNew :workFlowList="originalWorkFlowThreeList" />
</div>
</template>
import { getWorkFlowList } from './DuiBranchStepBar.js'
export default {
name: 'EgDuiBranchStepBarNew',
components: {
MdView,
DuiTitle,
DuiBranchStepBarNew,
},
data() {
return {
workFlowList: [
{
step_no: 1,
flow_name: this.$t('步骤1'),
status: 2, //0未完成,1当前,2已完成
children: [],
},
{
step_no: 2,
flow_name: this.$t('步骤2'),
status: 2,
children: [],
},
{
step_no: 3,
flow_name: this.$t('步骤3'),
status: 1,
children: [],
},
{
step_no: 4,
flow_name: this.$t('步骤4'),
status: 0,
children: [],
},
{
step_no: 5,
flow_name: this.$t('步骤5'),
status: 0,
children: [],
},
],
originalWorkFlowList: [
{
ID: 78,
flow_fun_id: '0202',
flow_fun_name: this.$t('订单列表'),
line_no: 1,
flow_name: this.$t('步骤1'),
fun_id: '0202',
fun_name: this.$t('订单列表'),
operation: 'Submit',
operation_name: this.$t('提交'),
user_id: '3393192402',
user_name: this.$t('招海芬'),
create_time: '2023-02-22 05:50:11',
remark: 'N',
nextFlowList: [
{
ID: 88,
flow_fun_id: '0202',
flow_fun_name: this.$t('订单列表'),
line_no: 7,
flow_name: this.$t('经理审批'),
fun_id: '0202',
fun_name: this.$t('订单列表'),
operation: 'Audit',
operation_name: this.$t('审核'),
user_id: '3393192402',
user_name: this.$t('招海芬'),
create_time: '2023-02-25 11:07:07',
remark: '',
if_power: 'Y',
filter_power: '',
if_tip: '',
parent_flow_line_no: 3,
if_flow: 'N',
if_parallel: 'N',
},
{
ID: 85,
flow_fun_id: '0202',
flow_fun_name: this.$t('订单列表'),
line_no: 4,
flow_name: this.$t('技术评审'),
fun_id: '0202',
fun_name: this.$t('订单列表'),
operation: 'Audit',
operation_name: this.$t('审核'),
user_id: '3393192402',
user_name: this.$t('招海芬'),
create_time: '2023-02-25 11:07:07',
remark: '',
if_power: 'Y',
filter_power: '',
if_tip: '',
parent_flow_line_no: 2,
if_flow: 'N',
if_parallel: 'N',
},
],
if_power: 'Y',
filter_power: '',
if_tip: '',
},
{
ID: 79,
flow_fun_id: '0202',
flow_fun_name: this.$t('订单列表'),
line_no: 2,
flow_name: this.$t('审批流程'),
fun_id: '0202',
fun_name: this.$t('订单列表'),
operation: 'Appr',
operation_name: this.$t('审批'),
user_id: '3393192402',
user_name: this.$t('招海芬'),
create_time: '2023-02-25 11:07:07',
remark: 'N',
if_power: 'Y',
filter_power: '',
if_tip: '',
if_flow: 'Y',
if_parallel: 'Y',
childFlowList: [
{
ID: 84,
flow_fun_id: '0202',
flow_fun_name: this.$t('订单列表'),
line_no: 3,
flow_name: this.$t('优惠券申请流程'),
fun_id: '0202',
fun_name: this.$t('订单列表'),
operation: 'CouponApply',
operation_name: this.$t('优惠券申请'),
user_id: '3393192402',
user_name: this.$t('招海芬'),
create_time: '2023-02-25 11:07:07',
remark: 'N',
if_power: 'Y',
filter_power: '',
if_tip: '',
parent_flow_line_no: 2,
if_flow: 'Y',
if_parallel: 'N',
childFlowList: [
{
ID: 86,
flow_fun_id: '0202',
flow_fun_name: this.$t('订单列表'),
line_no: 5,
flow_name: this.$t('步骤上分支1'),
fun_id: '0202',
fun_name: this.$t('订单列表'),
operation: 'CouponApply',
operation_name: this.$t('优惠券申请'),
user_id: '3393192402',
user_name: this.$t('招海芬'),
create_time: '2023-02-25 11:07:07',
remark: 'Y',
nextFlowList: [
{
ID: 87,
flow_fun_id: '0202',
flow_fun_name: this.$t('订单列表'),
line_no: 6,
flow_name: this.$t('主管审批'),
fun_id: '0202',
fun_name: this.$t('订单列表'),
operation: 'Audit',
operation_name: this.$t('审核'),
user_id: '3393192402',
user_name: this.$t('招海芬'),
create_time: '2023-02-25 11:07:07',
remark: '',
if_power: 'Y',
filter_power: '',
if_tip: '',
parent_flow_line_no: 3,
if_flow: 'N',
if_parallel: 'N',
},
],
if_power: 'Y',
filter_power: '',
if_tip: '',
parent_flow_line_no: 3,
if_flow: 'N',
if_parallel: 'N',
},
{
ID: 87,
flow_fun_id: '0202',
flow_fun_name: this.$t('订单列表'),
line_no: 6,
flow_name: this.$t('步骤上分支2'),
fun_id: '0202',
fun_name: this.$t('订单列表'),
operation: 'Audit',
operation_name: this.$t('审核'),
user_id: '3393192402',
user_name: this.$t('招海芬'),
create_time: '2023-02-25 11:07:07',
remark: 'Y',
nextFlowList: [
{
ID: 88,
flow_fun_id: '0202',
flow_fun_name: this.$t('订单列表'),
line_no: 7,
flow_name: this.$t('经理审批'),
fun_id: '0202',
fun_name: this.$t('订单列表'),
operation: 'Audit',
operation_name: this.$t('审核'),
user_id: '3393192402',
user_name: this.$t('招海芬'),
create_time: '2023-02-25 11:07:07',
remark: '',
if_power: 'Y',
filter_power: '',
if_tip: '',
parent_flow_line_no: 3,
if_flow: 'N',
if_parallel: 'N',
},
],
if_power: 'Y',
filter_power: '',
if_tip: '',
parent_flow_line_no: 3,
if_flow: 'N',
if_parallel: 'N',
},
{
ID: 88,
flow_fun_id: '0202',
flow_fun_name: this.$t('订单列表'),
line_no: 7,
flow_name: this.$t('步骤上分支3'),
fun_id: '0202',
fun_name: this.$t('订单列表'),
operation: 'Audit',
operation_name: this.$t('审核'),
user_id: '3393192402',
user_name: this.$t('招海芬'),
create_time: '2023-02-25 11:07:07',
remark: 'N',
if_power: 'Y',
filter_power: '',
if_tip: '',
parent_flow_line_no: 3,
if_flow: 'N',
if_parallel: 'N',
},
],
},
{
ID: 85,
flow_fun_id: '0202',
flow_fun_name: this.$t('订单列表'),
line_no: 4,
flow_name: this.$t('步骤下分支1'),
fun_id: '0202',
fun_name: this.$t('订单列表'),
operation: 'Audit',
operation_name: this.$t('审核'),
user_id: '3393192402',
user_name: this.$t('招海芬'),
create_time: '2023-02-25 11:07:07',
remark: 'N',
if_power: 'Y',
filter_power: '',
if_tip: '',
parent_flow_line_no: 2,
if_flow: 'N',
if_parallel: 'N',
},
],
},
],
originalWorkFlowThreeList: [],
}
methods: {},
watch: {
originalWorkFlowList: {
immediate: true,
deep: true,
handler(val) {
this.originalWorkFlowThreeList = getWorkFlowList(val)
},
},
},
}
}
DuiBranchStepBar.js 文件代码:
export function getWorkFlowList(dataList) {
// 先组装最外层数据
dataList.forEach((item, index) => {
if (item.if_parallel == "Y" && dataList[index - 1]) {
dataList[index - 1].children = item.childFlowList;
}
});
dataList = dataList.filter((item) => item.if_parallel != "Y");
// 再获取children
let tempDataList = getChildrenList(dataList);
return getStatusByNextItem(tempDataList);
}
// 再获取childrenList
export function getChildrenList(dataList) {
dataList.forEach((dataListItem) => {
if (dataListItem.children) {
var tempList = dataListItem.children;
tempList = tempList.map((chilrenItem) => {
if (chilrenItem.childFlowList) {
chilrenItem = getChildren(chilrenItem.childFlowList);
return chilrenItem;
} else {
return getChilrenItem(chilrenItem);
}
});
dataListItem.children = tempList;
} else {
dataListItem.children = [];
}
});
return dataList;
}
// 获取多层children
export function getChildren(childFlowList) {
let tagIndex = 0;
// 先获取状态和step_no
childFlowList.forEach((item, index) => {
if (item.remark == "Y") {
item.status = 2;
item.step_no = index + 1;
tagIndex = index;
}
});
childFlowList.forEach((item, index) => {
if (index < tagIndex) {
item.status = 2;
item.step_no = index + 1;
} else if (index > tagIndex) {
item.status = 0;
item.step_no = index + 1;
}
});
// 最后一个children为[]
childFlowList[childFlowList.length - 1].children = [];
for (var index = childFlowList.length - 1; index > 0; index--) {
childFlowList[index - 1].children = [childFlowList[index]];
}
return childFlowList[0];
}
// 获取单个状态和step_no
export function getChilrenItem(chilrenItem) {
chilrenItem.step_no = 1;
chilrenItem.status = chilrenItem.remark == "Y" ? 2 : 0;
chilrenItem.children = [];
return chilrenItem;
}
// 获取分支节点前一个节点的status
export function getStatusByNextItem(dataList) {
dataList.forEach((item) => {
if (item.children.length == 2) {
item.status = getStatusByChildrenList(item.children) ? 2 : 0;
}
if (item.remark == "Y") {
item.status = 1;
}
});
return dataList;
}
// 根据childrenList
export function getStatusByChildrenList(data) {
let dataList = [];
data.forEach((item) => {
dataList.push(getStatusByChildrenItem(item));
});
return dataList.includes(true);
}
// 递归children
export function getStatusByChildrenItem(data) {
let dataList = [];
function circulate(elm) {
dataList.push(elm.status >= 1 ? true : false);
if (elm.children != []) {
elm.children.forEach(circulate);
}
}
circulate(data);
return dataList.includes(true);
}
DuiBranchStepBarNew.vue 文件代码:
<template>
<div class="step">
<template v-for="item in workFlowList">
<!-- 普通节点 -->
<DuiNormalStepNode
v-if="item.children.length <= 2"
:key="item.flow_name"
:item-node="item"
:is-last-node="
item == workFlowList[workFlowList.length - 1] &&
item.children.length < 2
"
></DuiNormalStepNode>
<!-- 分支节点 -->
<div
v-if="item.children.length == 2"
:key="item.flow_name + 1"
class="branch_node_box"
>
<!-- 左上角、左下角 -->
<div class="branch_node_left_box">
<div
class="branch_node_top_left"
:id="getBorderColorBychildrenList(item.children[0])"
></div>
<div
class="branch_node_bottom_left"
:id="getBorderColorBychildrenList(item.children[1])"
></div>
</div>
<div
v-for="branchNodeItem in item.children"
:key="branchNodeItem.flow_name"
:class="getBranchNodeClass(branchNodeItem, item.children)"
>
<DuiNormalStepNode
v-for="branNodeItemItem in getBranchNodeTop(branchNodeItem)"
:key="branNodeItemItem.flow_name"
:item-node="branNodeItemItem"
:is-last-node="
branNodeItemItem ==
getBranchNodeTop(branchNodeItem)[
getBranchNodeTop(branchNodeItem).length - 1
] && branNodeItemItem.children.length < 2
"
></DuiNormalStepNode>
<div
class="branch_node_top_bottom_line_box"
:id="getBackgroundColorBychildrenList(branchNodeItem)"
></div>
</div>
<!-- 右上角、右下角 -->
<div class="branch_node_right_box">
<div
class="branch_node_top_right"
:id="getBorderColorBychildrenList(item.children[0], true)"
></div>
<div
class="branch_node_bottom_right"
:id="getBorderColorBychildrenList(item.children[1], true)"
></div>
</div>
</div>
</template>
</div>
</template>
<script>
import DuiNormalStepNode from "./DuiNormalStepNode.vue";
export default {
props: {
// 流程数据
workFlowList: {
type: Array,
required: true,
},
},
computed: {},
components: { DuiNormalStepNode },
data() {
return {};
},
mounted() {},
methods: {
// 获取分支节点的分支class
getBranchNodeClass(item, list) {
if (item == list[0]) {
return "branch_node_top_box";
}
return "branch_node_bottom_box";
},
// 使用递归算法获取所有的节点,放在一维数组中
getBranchNodeTop(data) {
let dataList = [];
function circulate(elm) {
dataList.push(elm);
if (elm.children != []) {
elm.children.forEach(circulate);
}
}
circulate(data);
return dataList;
},
/* 根据分支的状态获取边框颜色,
分子左边的颜色:子分支status有大于等于1的,则为完成色;
分支右边的颜色:子分支最后一个的status等于2,则为完成色。
*/
getBorderColorBychildrenList(data, isCheckLast) {
let statusList = [];
function circulate(elm) {
if (isCheckLast) {
statusList.push(elm.status == 2 ? true : false);
} else {
statusList.push(elm.status >= 1 ? true : false);
}
if (elm.children != []) {
elm.children.forEach(circulate);
}
}
circulate(data);
// 是否只检查最后一个
if (isCheckLast) {
if (statusList[statusList.length - 1]) {
return "action_border_color";
}
return "un_action_border_color";
} else {
if (statusList.includes(true)) {
return "action_border_color";
}
return "un_action_border_color";
}
},
/* 根据分支的状态获取背景颜色,
子分支最后一个的status等于2,则为完成色。
*/
getBackgroundColorBychildrenList(data) {
let statusList = [];
function circulate(elm) {
statusList.push(elm.status == 2 ? true : false);
if (elm.children != []) {
elm.children.forEach(circulate);
}
}
circulate(data);
if (statusList[statusList.length - 1]) {
return "action_background_color";
}
return "un_action_background_color";
},
},
};
</script>
<style lang="less" scoped>
@import url("./DuiBranchStepBar.less");
</style>
DuiNormalStepNode.vue 文件代码:
、
<!-- 普通节点 -->
<template>
<div class="node_box">
<div :class="getNodeIconClass(itemNode.status)">
<img
v-if="itemNode.status == 2"
style="width: 16px"
src="@/assets/icon/完成.png"
/>
<span v-else class="node_icon_text">
{{ itemNode.step_no }}
</span>
</div>
<div :class="getNodeTextClass(itemNode.status)">
{{ itemNode.flow_name }}
</div>
<div v-show="!isLastNode" :class="getNodeLineClass(itemNode.status)"></div>
<!-- <div class="step_time">''</div>
<div class="step_time">''</div> -->
</div>
</template>
<script>
export default {
props: {
// 流程节点
itemNode: {
type: Object,
required: true,
},
// 是否最后一个节点
isLastNode: {
type: Boolean,
required: false,
default: false,
},
},
computed: {},
components: {},
data() {
return {}
},
mounted() {},
methods: {
// 圆框状态
getNodeIconClass(status) {
switch (status) {
case 0:
return 'node_icon_mo'
case 1:
return 'node_icon_cur'
case 2:
return 'node_icon_active'
default:
return ''
}
},
getNodeTextClass(status) {
switch (status) {
case 0:
return 'node_text_mo'
case 1:
return 'node_text_cur'
case 2:
return 'node_text_active'
default:
return ''
}
},
// 边框
getNodeLineClass(status) {
// 已完成
if (status == 2) {
return 'action_line'
} else {
return 'un_action_line'
}
},
},
}
</script>
<style lang="less" scoped>
@import url('./DuiBranchStepBar.less');
</style>
DuiBranchStepBar.less 文件代码
.step {
// width: 1000px;
width: 100%;
height: auto;
// padding: 100px 50px 20px 100px;
padding: 10px 50px;
display: flex;
// position: relative;
justify-content: center;
overflow: scroll;
.step_time {
font-size: 12px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
// color: #999999;
color: transparent;
margin-top: 7px;
}
// 普通节点
.node_box {
display: flex;
align-items: center;
// 节点序号部分-边框-未选中
.node_icon_mo {
width: 36px;
height: 36px;
margin-right: 16px;
border-radius: 50%;
border: 1px solid #bfbfbf;
font-size: 16px;
font-family: Helvetica Neue-Regular, Helvetica Neue;
font-weight: 400;
color: #7e7a7a;
text-align: center;
line-height: 36px;
opacity: 1;
}
// 选中
.node_icon_cur {
width: 36px;
height: 36px;
margin-right: 16px;
border-radius: 50%;
border: 2px solid rgba(244, 153, 16, 0.5);
font-size: 16px;
font-family: Helvetica Neue-Regular, Helvetica Neue;
font-weight: 400;
color: rgba(244, 153, 16, 0.5);
text-align: center;
line-height: 36px;
}
// 已完成
.node_icon_active {
width: 36px;
height: 36px;
margin-right: 16px;
border-radius: 50%;
border: 2px solid rgba(244, 153, 16, 0.5);
font-size: 16px;
font-family: Helvetica Neue-Regular, Helvetica Neue;
font-weight: 400;
color: rgba(244, 153, 16, 0.5);
text-align: center;
line-height: 30px;
padding-left: 2px;
}
// 节点序号部分-文字
.node_icon_text {
width: 36px;
height: 36px;
align-items: center;
}
// 节点文字部分-已完成
.node_text_active {
font-size: 16px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #333333;
white-space: nowrap;
}
// 选中
.node_text_cur {
font-size: 16px;
font-family: Microsoft YaHei-Bold, Microsoft YaHei;
font-weight: bold;
color: #333333;
white-space: nowrap;
}
// 未完成
.node_text_mo {
font-size: 16px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #999999;
position: relative;
white-space: nowrap;
}
// 节点连接线部分-已完成的border
.action_line {
width: 60px;
height: 1px;
background-color: rgba(244, 153, 16, 0.5);
}
// 当前和未完成的border
.un_action_line {
width: 60px;
height: 1px;
background-color: #cdcdcd;
}
}
// 分支节点
.branch_node_box {
// border: 1px solid #000;
display: flex;
position: relative;
// 左上角、左下角
.branch_node_left_box {
margin: 20px 0;
.branch_node_top_left {
width: 30px;
height: 50px;
border-top: 1px solid rgba(244, 153, 16, 0.5);
border-left: 1px solid rgba(244, 153, 16, 0.5);
}
.branch_node_bottom_left {
width: 30px;
height: 50px;
border-bottom: 1px solid rgba(244, 153, 16, 0.5);
border-left: 1px solid rgba(244, 153, 16, 0.5);
}
}
#action_border_color {
border-color: rgba(244, 153, 16, 0.5);
}
#un_action_border_color {
border-color: #cdcdcd;
}
#action_background_color {
background-color: rgba(244, 153, 16, 0.5);
}
#un_action_background_color {
background-color: #cdcdcd;
}
.branch_node_top_box {
height: 41px;
display: flex;
align-items: center;
width: calc(100% - 60px);
}
.branch_node_bottom_box {
position: absolute;
left: 30px;
bottom: 0;
height: 41px;
width: calc(100% - 60px);
display: flex;
align-items: center;
}
.branch_node_top_bottom_line_box {
width: 100%;
height: 1px;
background-color: rgba(244, 153, 16, 0.5);
}
// 右上角、右下角
.branch_node_right_box {
margin: 20px 0;
.branch_node_top_right {
width: 30px;
height: 50px;
border-top: 1px solid rgba(244, 153, 16, 0.5);
border-right: 1px solid rgba(244, 153, 16, 0.5);
}
.branch_node_bottom_right {
width: 30px;
height: 50px;
border-bottom: 1px solid rgba(244, 153, 16, 0.5);
border-right: 1px solid rgba(244, 153, 16, 0.5);
}
}
}
}