vue实现水平时间线与蛇形时间线

7 篇文章 0 订阅
6 篇文章 1 订阅

一、水平时间线

效果图

 

<template>
  <div class="m-timeline-wrap">
    <div
      class="m-time-dot"
      :style="
        timelineData.length > 5
          ? 'justify-content: space-between'
          : 'justify-content: space-around'
      "
      v-if="timelineData.length > 0"
    >
      <div
        :class="['m-dot-box', { active: active === item.reportDate }]"
        v-for="(item, index) in timelineData"
        :key="index"
      >
        <div>
          <p class="u-year">{{ item.reportDate }}</p>
          <div class="m-dot">
            <div class="u-dot"></div>
          </div>
          <div class="reports">
            <div
              class="u-reports"
              v-for="term in item.reports"
              @click="onClickDate(item.reportDate, term)"
              :key="term.id"
              :style="term.id == activeMenu.id ? 'color:#1890ff' : ''"
            >
              <el-tooltip effect="dark" :content="term.title" placement="right">
                <p>
                  <img
                    v-if="term.hasReportFile"
                    class="btns-img"
                    :src="pdfImg"
                    alt
                  />{{ term.title }}
                </p>
              </el-tooltip>
            </div>
          </div>
        </div>
      </div>
      <div class="timeLine"></div>
    </div>
    <div v-else class="empty">暂无数据</div>
    <div class="calender">
      <div class="times">
        <div class="beginText" @click="$refs['beginVisible'].focus()">
          <i class="el-icon-d-arrow-left"></i>
          {{ queryList.beginTime.split(" ")[0] }}
          <div class="dates">
            <el-date-picker
              style="display: table-column"
              ref="beginVisible"
              v-model="dateRange"
              type="daterange"
              value-format="yyyy-MM-dd"
              range-separator="至"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :picker-options="pickerOptions"
              @change="handleDateRange"
            >
            </el-date-picker>
          </div>
        </div>
        <div class="endText" @click="$refs['endVisible'].focus()">
          {{ queryList.endTime.split(" ")[0] }}
          <i class="el-icon-d-arrow-right"></i>
          <div class="endates">
            <el-date-picker
              style="display: table-column"
              ref="endVisible"
              v-model="dateRange"
              type="daterange"
              value-format="yyyy-MM-dd"
              range-separator="至"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :picker-options="pickerOptions"
              @change="handleDateRange"
            >
            </el-date-picker>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { getTime, formatDate } from "@/utils";
import { handleConfirm, handleMessage } from "@/utils/popup";
import { pickerOptions } from "@/utils";
export default {
  name: "HorizonTimeLine",
  props: {
    timelineData: {
      // 时间轴数据
      type: Array,
      default: () => {
        return [];
      },
    },
    reportDate: {
      // 初始选中年份
      type: String,
      default: "",
    },
    activeMenu: {
      type: Object,
      default: () => {},
    },
  },
  data() {
    return {
      active: this.reportDate,
      queryList: {
        beginTime: formatDate(getTime(2, "end")),
        endTime: formatDate(new Date()),
      },
      dateRange: [],
      pdfImg: require("../../assets/images/pdfReported.png"),
      pickerBeginVisible: false,
      pickerOptions: {
        shortcuts: pickerOptions,
      },
    };
  },
  watch: {
    reportDate: {
      handler(val) {
        this.active = val;
      },
    },
  },
  methods: {
    onClickDate(date, term) {
      //选择报告
      this.active = date;
      this.$emit("onClickDate", term);
    },
    handleDateRange() {
      //时间范围
      if (this.dateRange) {
        this.queryList.beginTime = this.dateRange[0];
        this.queryList.endTime = this.dateRange[1];
        this.$emit("onDateRange", this.queryList);
      } else {
        this.queryList.beginTime = formatDate(getTime(2, "end"));
        this.queryList.endTime = formatDate(new Date());
        this.$emit("onDateRange", this.queryList);
      }
    },
  },
};
</script>
<style lang="scss" scoped>
$bgColor: #1890ff;

.m-timeline-wrap {
  width: 100%;
  margin: 0 auto;
  height: 100%;
  //background: #e6f7ff;
  padding: 5px 0;
  position: relative;
  .m-time-dot {
    display: flex;
    overflow-x: auto;
    overflow-y: hidden;
    &::-webkit-scrollbar {
      width: 4px;
      height: 4px;
    }

    &::-webkit-scrollbar-thumb {
      border-radius: 5px;
      -webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
      background-color: #99a9bf;
    }

    &::-webkit-scrollbar-track {
      -webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
      border-radius: 5px;
      background-color: #d3dce6;
    }
    .m-dot-box {
      text-align: center;
      z-index: 9999;
      min-width: 110px;
      //transform: translateY(-100% + 14px);
      .u-year {
        font-size: 18px;
        font-weight: 500;
        color: #333;
        transition: all 0.3s ease-in-out;
      }
      .m-dot {
        margin: 0 auto;
        margin-top: 5px;
        width: 12px;
        height: 12px;
        background: #8dc6f5;
        border-radius: 50%;
        transition: all 0.3s ease-in-out;
        .u-dot {
          width: 12px;
          height: 12px;
          background: #8dc6f5;
          border-radius: 50%;
          transition: all 0.3s ease-in-out;
        }
      }
      .reports {
        height: 60px;
        overflow-y: auto;
        overflow-x: hidden;
        &::-webkit-scrollbar {
          width: 4px;
          height: 4px;
        }

        &::-webkit-scrollbar-thumb {
          border-radius: 5px;
          -webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
          background-color: #99a9bf;
        }

        &::-webkit-scrollbar-track {
          -webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
          border-radius: 5px;
          background-color: #d3dce6;
        }
      }
      .u-reports {
        margin-top: 5px;
        display: flex;
        justify-content: center;
        align-items: center;
        width: 95px;
        overflow: hidden;
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 1;
        cursor: pointer;
      }
      .btns-img {
        height: 16px;
        width: 16px;
      }
    }

    .m-dot-box:hover {
      .u-year {
        color: $bgColor;
      }
      .m-dot {
        .u-dot {
          background: $bgColor;
        }
      }
    }
    .active {
      .u-year {
        transform: scale(1) translateY(0px); // 同时设置多个transform属性只需用空格间隔,执行时从后往前执行!
        font-weight: bold;
        color: $bgColor;
      }
      .m-dot {
        transform: scale(1.6);
        .u-dot {
          transform: scale(0.67);
          background: $bgColor;
        }
      }
      .u-reports {
        //transform: scale(1.2);
        //color: #1890ff;
      }
    }
  }
  .empty {
    width: 100%;
    text-align: center;
  }
  .timeLine::before {
    content: "";
    position: absolute;
    top: -4px;
    left: -2px;
    width: 1px;
    height: 1px;
    border-radius: 50%;
    background: #cccccc;
    border: 5px solid #cccccc;
    position: absolute;
    right: 35px;
    top: 50%;
    transform: translate(0, -50%);
  }
  .timeLine {
    width: 100%;
    border-bottom: 1px solid #ccc;
    position: absolute;
    bottom: 80px;
    z-index: 99;
  }
  .timeLine::after {
    content: "";
    position: absolute;
    top: -4px;
    right: -2px;
    border-top: 5px solid transparent;
    border-left: 12px solid #cccccc;
    border-bottom: 5px solid transparent;
  }
  .calender {
    display: flex;
    font-size: 12px;
    position: absolute;
    bottom: 0px;
    width: 100%;
    .times {
      justify-content: space-between;
      display: flex;
      width: 100%;
      position: relative;
      z-index: 999;
      .beginText {
        margin: 5px;
        color: $bgColor;
        text-decoration: underline;
        cursor: pointer;
        position: absolute;
        left: 5px;
        bottom: -12px;
        font-size: 14px;
      }
      .endText {
        margin: 5px;
        color: $bgColor;
        text-decoration: underline;
        cursor: pointer;
        position: absolute;
        right: 5px;
        bottom: -12px;
        font-size: 14px;
      }
    }
    .dates {
      z-index: 9999;
      position: absolute;
      left: 0px;
      /deep/ {
        .el-picker-panel el-date-range-picker el-popper {
          top: 40% !important;
          left: 50% !important;
          transform: translate(-50%, -50%) !important;
        }
      }
    }
    .endates {
      z-index: 9999;
      position: absolute;
      right: 0px;
    }
    .search-left {
      margin: 0 5px;
    }
    /deep/ {
      .el-date-editor .el-range-input {
        width: 50%;
      }
      .el-date-editor.el-input__inner {
        width: 220px;
        height: 30px;
      }
      .el-date-editor .el-range__close-icon {
        line-height: 22px;
      }
      .el-date-editor .el-range__icon {
        line-height: 22px;
      }
      .el-date-editor .el-range-separator {
        line-height: 22px;
        width: 12%;
      }
    }
  }
}
</style>

二、蛇形时间线

效果图

<template>
  <div>
    <div
      style="width: 100%; display: flex; position: relative; margin-top: 20px"
    >
      <div style="width: 10%; margin-left: 30px">
        <div>
          <!-- 开头 -->
          <span
            data-v-jzl20210826=""
            style="margin-top: 49.5px"
            class="headerRadio"
          ></span>
          <!-- 第二行 -->
          <!-- <span
            class="hingelisHeard"
            v-if="experienceData.length > Index * 2"
            style="margin-top: 98.9px"
          ></span> -->
          <div v-if="experienceData.length > Index">
            <span
              v-for="(num, index) in leftRows"
              :key="index"
              class="hingelisHeard"
              style="margin-top: 98.9px"
            ></span>
          </div>
          <span
            data-v-jzl20210826=""
            v-if="leftShow"
            style="margin-top: 98.5px"
            class="hingeorgerHeard"
          ></span>
          <!-- 第三行 -->
          <!-- <span
            data-v-jzl20210826=""
            v-if="experienceData.length > Index * 3"
            style="margin-top: 99.5px"
            class="hingeorgerHeard"
          ></span> -->
        </div>
      </div>
      <div style="width: 75%">
        <div
          style="display: flex"
          v-for="(item, index) in experienceData"
          :key="index"
        >
          <div
            class="timeline"
            v-for="(v, i) in DisplayProcessing(experienceData, index + 1)"
            :key="i"
          >
            <div class="border"></div>
            <div class="Nodes"></div>
            <div class="timeNodes">
              <div class="nodeTimes">
                <span>{{ v.reportDate }}</span>
              </div>
              <div class="timeContent">
                <div v-for="term in v.reports" :key="term.id">
                  <el-tooltip
                    class="item"
                    effect="dark"
                    :content="term.title"
                    placement="right"
                  >
                    <p
                      class="nodeTimelis"
                      @click="onClickDate(v.reportDate, term)"
                    >
                      <img
                        v-if="term.hasReportFile"
                        class="btns-img"
                        :src="pdfImg"
                        alt
                      />
                      <span>{{ term.title }}</span>
                    </p>
                  </el-tooltip>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <div style="width: 10%; margin-right: 30px">
        <div>
          <!-- 第一行 -->
          <span class="hingelis" v-if="experienceData.length > Index"></span>
          <span
            data-v-jzl20210826=""
            v-if="experienceData.length <= Index"
            class="hingeorger"
          ></span>
          <div v-if="experienceData.length > Index * 2">
            <span
              class="hingelis"
              v-for="(num, index) in rightRows"
              :key="index"
              style="margin-top: 100.3px"
            ></span>
          </div>
          <!-- 第二行 -->
          <!-- <span
            class="hingelis"
            v-if="experienceData.length > Index * 3"
            style="margin-top: 100.3px"
          ></span> -->
          <span
            data-v-jzl20210826=""
            v-if="rightShow"
            style="margin-top: 100.3px"
            class="hingeorger"
          ></span>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    data: [],
    Index: 0, //第一行展示X条数据
  },
  data() {
    return {
      experienceData: this.data,
      leftRows: 0,
      rightRows: 0,
      leftShow: false,
      rightShow: false,
      pdfImg: require("../../assets/images/pdfReported.png"),
    };
  },
  watch: {
    data: {
      handler(newVal, oldVal) {
        //时间线数据
        this.experienceData = newVal;
        let rows = Math.ceil(newVal.length / this.Index);
        this.leftRows =
          rows == 2
            ? 0
            : rows % 2 == 0
            ? parseInt(rows / 2) - 1
            : parseInt(rows / 2);
        this.rightRows =
          rows == 4
            ? 1
            : rows % 2 == 0
            ? parseInt(rows / 2) % 2 == 0
              ? parseInt(rows / 2) >= 4
                ? parseInt(rows / 2) - 1
                : parseInt(rows / 2)
              : parseInt(rows / 2) - 1
            : parseInt(rows / 2) - 1;
        this.leftShow = rows % 2 == 0 ? true : false;
        this.rightShow = rows == 1 ? false : rows % 2 == 1 ? true : false;
      },
      immediate: true,
    },
  },
  created() {},
  methods: {
    DisplayProcessing(Arg, Num) {
      //数据循环处理
      let arr = Arg.slice(this.Index * (Num - 1), this.Index * Num);
      arr = Num % 2 == 0 ? arr.reverse() : arr;
      return arr;
    },
    onClickDate(date, term) {
      //选择报告
      this.$emit("onClickDate", term);
    },
  },
};
</script>
<style scoped>
.timeline {
  width: 100%;
  height: 100px;
  position: relative;
}
.border {
  width: 100%;
  border-bottom: 1px solid #cccccc;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

.Nodes {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: white;
  border: 5px solid #1e9bff;
  position: absolute;
  right: 35px;
  top: 50%;
  transform: translate(0, -50%);
}

.timeNodes {
  position: absolute;
  text-align: center;
  right: -12px;
  width: 105px;
  top: 60%;
  transform: translate(0, -50%);
}
.timeContent {
  height: 40px;
  overflow-y: auto;
  overflow-x: hidden;
  margin-top: 30px;
}

.nodeTimelis {
  width: 70px;
  /* margin-bottom: 20px; */
  margin-left: 20px;
  overflow: hidden;
  word-break: keep-all;
  white-space: nowrap;
  text-overflow: ellipsis;
  cursor: pointer;
}
.nodeTimelis:active {
  color: #1e9bff;
}
.nodeTimes {
  margin-bottom: 0;
}

.btns-img {
  height: 16px;
  width: 16px;
}

.hingelis {
  content: "";
  display: block;
  width: 100%;
  height: 100.1px;
  border: 1px solid #cccccc;
  border-radius: 0 50px 50px 0;
  border-left: 0px;
  margin-top: 49.5px;
}

.hingelisHeard {
  content: "";
  display: block;
  width: 100%;
  height: 101.1px;
  border: 1px solid #cccccc;
  border-radius: 50px 0 0 50px;
  border-right: 0px;
  margin-top: 50px;
}

.hingeorger {
  display: block;
  width: 100%;
  border-bottom: 1px solid #cccccc;
  margin: 49.5px 0;
  position: relative;
}
.hingeorgerHeard {
  display: block;
  width: 100%;
  border-bottom: 1px solid #cccccc;
  position: relative;
}

.hingeorgerHeard[data-v-jzl20210826]:after {
  content: "";
  position: absolute;
  top: -4px;
  left: -2px;
  border-top: 5px solid transparent;
  border-right: 12px solid #cccccc;
  border-bottom: 5px solid transparent;
}

.hingeorger[data-v-jzl20210826]:after {
  content: "";
  position: absolute;
  top: -4px;
  right: -2px;
  border-top: 5px solid transparent;
  border-left: 12px solid #cccccc;
  border-bottom: 5px solid transparent;
}

.headerRadio {
  display: block;
  width: 100%;
  border-bottom: 1px solid #cccccc;
  position: relative;
}

.headerRadio[data-v-jzl20210826]:after {
  content: "";
  position: absolute;
  top: -4px;
  left: -2px;
  width: 1px;
  height: 1px;
  border-radius: 50%;
  background: #cccccc;
  border: 5px solid #cccccc;
  position: absolute;
  right: 35px;
  top: 50%;
  transform: translate(0, -50%);
}
::-webkit-scrollbar {
  width: 3px;
  height: 3px;
}

::-webkit-scrollbar-thumb {
  border-radius: 5px;
  -webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
  background-color: #99a9bf;
}

::-webkit-scrollbar-track {
  -webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
  border-radius: 5px;
  background-color: #d3dce6;
}
</style>

  • 1
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值