最终效果图
直接看TheTimeLine.vue文件代码
<template> <div class="timeline-main"> <!--年月标题--> <div class="timeline-title"> {{ timeData.yearData }} <i :class="showCards ? 'el-icon-arrow-down' : 'el-icon-arrow-right'" @click="showCards = !showCards"></i> </div> <ul class="timeline-body"> <!--时间线顶部圆点--> <li class="timeline-item-head"> <div class="item-node"></div> <div class="item-tail"></div> </li> <!--时间线内容--> <template v-if="showCards"> <li v-for="(mouthItem, i) in timeData.mouthData" :key="'mm'+i" class="timeline-item"> <div class="item-left"> <div class="item-left-data">{{ mouthItem.dateData }}</div> <div class="item-left-total"> <div class="item-left-total-text"> 共{{mouthItem.dateArr.length}}条 </div> <div class="item-left-total-end"></div> </div> </div> <div class="item-tail"></div> <div class="item-node"></div> <div class="item-content"> <slot v-for="v in mouthItem.dateArr" :card="v"></slot> </div> </li> </template> <!--时间线尾部圆点--> <li class="timeline-item-foot"> <div class="item-node"></div> <div class="item-tail"></div> </li> </ul> </div> </template> <script> export default { name: 'TheTimeLine', props: { timeData: { type: Object, default: () => ({}) } }, data() { return { showCards: true }; }, }; </script> <style scoped lang="less"> .timeline-main { padding: 0 0 0 20px; .timeline-title { margin-bottom: 10px; font-weight: bold; i { cursor: pointer; } } .timeline-body { margin: 0; font-size: 14px; list-style: none; // 顶尾圆圈 .timeline-item-head, .timeline-item-foot { position: relative; height: 15px; .item-tail { position: absolute; left: 45px; height: 100%; border-left: 2px solid #e4e7ed; } .item-node { position: absolute; left: 42px; 8px; height: 8px; background-color: #e4e7ed; border-radius: 50%; display: flex; justify-content: center; align-items: center; } } .timeline-item-foot { .item-node { top: 14px; } } // 时间线主体内容 .timeline-item { position: relative; padding-bottom: 10px; .item-left { position: absolute; top: 13px; left: -9px; .item-left-data { font-weight: bold; line-height: 20px; } .item-left-total { display: flex; font-size: 12px; .item-left-total-text { padding: 0 3px; line-height: 20px; color: #ffffff; background: #409EFF; } .item-left-total-end { 0; height: 0; border-top: 10px solid transparent; border-left: 6px solid #409eff; border-bottom: 10px solid transparent; } } } .item-tail { position: absolute; left: 45px; height: 100%; border-left: 2px solid #e4e7ed; } .item-node { position: absolute; top: 38px; left: 41px; 6px; height: 6px; background-color: #ffffff; border: 2px solid #409EFF; border-radius: 50%; display: flex; justify-content: center; align-items: center; } .item-content { position: relative; padding-top: 15px; padding-left: 60px;; // top: -3px; } } } } </style>
数组格式需要这样的
{ yearData: '2021年2月', mouthData: [ { dateData: '20日', dateArr: [ { workNo: '2321321231233', time: '12:00:03', status: '1', name: '张三', type: '1' } ] }, { dateData: '1日', dateArr: [ { workNo: '2321321231233', time: '12:00:03', status: '1', name: '张三', type: '1' } ] }, ] }, { yearData: '2021年1月', mouthData: [ { dateData: '30日', dateArr: [ { workNo: '2321321231233', time: '12:00:03', status: '1', name: '张三', type: '1' } ] }, ] }
组件使用时
<div v-for="(v, i) in data" :key="'id'+i" class="year-item"> <the-time-line :time-data="v"> <template v-slot="soltData"> <!--这里采用作用域插槽-可以自己自定义卡片样式--> <card :card-data="soltData.card"></card> </template> </the-time-line> </div>
我自己写的卡片组件
这里删了一些逻辑代码,尽量保留了静态的样式
card.vue文件
<template> <div class="item-content-card" @click="goDetail()"> <div class="time-title" :class="statusStyle(cardData.status)"> <i class="el-icon-time"></i> {{ cardData.createTime.slice(-8) }} <div class="item-status"><b>{{ formatStatus(cardData.status) }}</b></div> </div> <div class="time-content"> <div class="time-content-no"> <div class="time-content-no-circle" :class="{'time-content-delete' : cardData.type!=1}"></div> {{ cardData.workNo }} </div> <div class="time-content-name"> <span>{{ cardData.name }}</span> 新增/导入/删除 <div v-if="showRevoke()" class="opera-icon-back" title="撤销" @click.stop="revokeWorkFlow"></div> <div v-if="showRevoke() && cardData.status == 4" class="opera-icon-edit" title="编辑" @click.stop="editWorkFlow"></div> </div> </div> </div> </template> <script> export default { name: 'TheWorkNoteCard', props: { cardData: { type: Object, default: () => ({}) } }, methods: { // 跳转详情页面 goDetail() { this.$router.push({}); }, // 显示撤销 showRevoke() { // 判断符合条件的逻辑 }, // 撤销 revokeWorkFlow() { }, // 编辑被驳回 editWorkFlow() { }, statusStyle(v) { if (String(v) === '2') { return 'time-status-unreview'; } else if (String(v) === '3') { return 'time-status-success'; } else if (String(v) === '4' || String(v) === '5') { return 'time-status-back'; } else { return 'time-status-unreview'; } }, formatStatus(v) { if (String(v) === '2') { // 待审核 return '待审核'; } else if (String(v) === '3') { // 通过 return '通过'; } else if (String(v) === '4') { // 驳回 return '驳回'; } else if (String(v) === '5') { // 撤销 return '撤销'; } else { return '--'; } }, } }; </script> <style scoped lang="less"> .item-content-card { cursor: pointer; border-radius: 4px; background-color: #f7f9fa; margin-bottom: 10px; .time-title { position: relative; box-sizing: border-box; height: 50px; padding: 15px; border-bottom: 1px solid #e4e4e4; i { font-size: 16px; } .item-status { position: absolute; font-size: 12px; top: 13px; right: 0; 40px; text-align: center; z-index: 1; transform: rotate(45deg) } &:after { content: ""; position: absolute; top: 0; right: 0; border-style: solid; border- 25px 25px 25px 25px; 0px; height: 0px; } } .time-status-unreview { .item-status { color: #409EFF; } &:after { border-color: #d3e7fb #d3e7fb transparent transparent; } } .time-status-success { .item-status { color: #67C23A; } &:after { border-color: #dbeed4 #dbeed4 transparent transparent; } } .time-status-back { .item-status { color: #E6A23C; } &:after { border-color: #f4e7d4 #f4e7d4 transparent transparent; } } .time-content { padding: 15px; .time-content-no { margin-bottom: 10px; .time-content-no-circle { 10px; height: 10px; display: inline-block; background-color: #8ec850; border-radius: 50%; margin-right: 6px; } .time-content-delete { background-color: #666666; } } .time-content-name { line-height: 20px; padding-left: 20px; span { margin-right: 20px; } .opera-icon-back { margin-left: 30px; display: inline-block; 18px; height: 18px; cursor: pointer; background: url('../images/rollback.png') no-repeat; background-size: 18px; &:hover { background: url('../images/rollback_h.png') no-repeat; background-size: 18px; } } .opera-icon-edit { margin-left: 5px; display: inline-block; 18px; height: 18px; cursor: pointer; background: url('../images/edit.png') no-repeat; background-size: 18px; &:hover { background: url('../images/edit_h.png') no-repeat; background-size: 18px; } } } } &:hover { box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.176470588235294) } } </style>