实现一个时间轴组件

本文讲述了一个时间轴实现的核心原理,搞懂这个后,可以在此基础上开发更多功能。

效果展示:

功能点:

  1. 分钟级高精度(支持"HH:MM"格式)
  2. 状态条基于百分比的精确定位
  3. 刻度线和时间标签自适应容器宽度

功能拆解:

时间轴本身就是一个白色背景的div,刻度线通过flex实现均分,五条刻度线为一个小时。核心点在于计算偏移量和宽度,先把时间转化为分钟如:"1:30" → 1 × 60 + 30 = 90 分钟,一天总分钟数:24 * 60 = 1440分钟。

偏移计算:开始时间在一天中的比例 × 100% = 左偏移百分比。 6:00:(360 ÷ 1440) × 100% = 25%

宽度计算:会议持续时间在一天中的比例 × 100% = 宽度百分比 6:00-7:30: (90 ÷ 1440) × 100% = 6.25%

具体实现:

基本结构:

html 复制代码
<!-- 时间轴 -->
<div class="meeting-time">
  <div class="time-container">
    <!-- 选中的时间端 -->
    <div class="time-line">
      <div v-for="(meeting, index) in meetings" :key="index" class="status-bar" :style="getStatusBarStyle(meeting)"
        :class="meeting.status"></div>
    </div>
    <div class="time-scale">
      <div v-for="hour in 24" :key="hour" class="hour-mark">
        <!-- 刻度线 -->
        <div class="tick-line first"></div>
        <div class="tick-line"></div>
        <div class="tick-line"></div>
        <div class="tick-line"></div>
        <div class="tick-line last"></div>
        <!-- 时间 -->
        <span class="time-text">{{ hour - 1 }}:00</span>
        <span v-if="hour === 24" class="time-text time-right">00:00</span>
      </div>
    </div>
  </div>
</div>

定位实现:

vue2 复制代码
methods: {
    // 将时间字符串转换为分钟数 (如 "14:30" -> 870分钟)
    timeToMinutes(timeStr) {
        const [hours, minutes] = timeStr.split(':').map(Number);
        return hours * 60 + minutes;
    },

    // 计算状态条的样式
    getStatusBarStyle(meeting) {
        const { startTime, endTime } = meeting;

        const startMinutes = this.timeToMinutes(startTime);
        const endMinutes = this.timeToMinutes(endTime);

        // 一天总共1440分钟
        const totalMinutesInDay = 24 * 60;

        // 计算左边距百分比:开始分钟数 / 总分钟数 * 100%
        const left = (startMinutes / totalMinutesInDay) * 100;
        // 计算宽度百分比:(结束分钟数 - 开始分钟数) / 总分钟数 * 100%
        const width = ((endMinutes - startMinutes) / totalMinutesInDay) * 100;

        return {
                left: `${left}%`,
                width: `${width}%`
        };
    }
},

样式部分:

scss 复制代码
.meeting-time {
  height: 156px;
  margin-top: 60px;
  position: relative;

  .time-container {
    height: 100%;
    padding: 0 30px;
    background: var(--base-bg-color);
    box-shadow: var(--bg-shadow);
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  .time-scale {
    width: 100%;
    display: flex;
    justify-content: space-between;
    margin-bottom: 10px;
    font-size: 18px;
    color: #666;

    .hour-mark {
      flex: 1;
      text-align: center;
      position: relative;
      display: flex;
      justify-content: space-between;

      .tick-line {
        width: 1px;
        height: 8px;
        background: #ccc;
        margin-bottom: 2px;

        &.first {
          height: 15px;
          width: 2px;
        }

        &.last {
          height: 15px;
        }

      }

      .time-text {
        position: absolute;
        left: -20px;
        top: 15px;
        font-size: 18px;
        font-weight: bold;

        &.time-right {
          left: auto;
          right: -20px;
        }
      }
    }
  }

  .time-line {
    width: 100%;
    height: 14px;
    background: #fff;
    position: relative;
    display: flex;
    overflow: hidden;

    .status-bar {
      position: absolute;
      top: 0;
      height: 100%;
      border-radius: 2px;

      // 不同状态的颜色
      &.finished {
        background: #999; // 已结束 - 灰色
      }

      &.ongoing {
        background: var(--primary-bg-color); // 进行中 - 蓝色
      }

      &.reserved {
        background: #ccc; // 已预约 - 浅灰色
      }

      &.idle {
        background: #fff; // 空闲中 - 白色
        border: 1px solid #ddd;
      }
    }
  }
}

数据部分:

vue2 复制代码
data() {
    return {
        meetings: [
            {
                startTime: '1:15',
                endTime: '2:30',
                status: 'ongoing'   // 进行中
            },
            {
                startTime: '9:15',
                endTime: '18:45',
                status: 'finished'  // 已结束
            },
            {
                startTime: '23:00',
                endTime: '23:59',
                status: 'reserved'  // 已预约
            }
        ]
    }
},

★感谢您看到最后★

相关推荐
rookie_fly2 小时前
基于Vue的数字输入框指令
前端·vue.js·设计模式
西部森林牧歌3 小时前
Arbess零基础学习,使用Arbess+GitLab实现Vue.js项目构建并主机部署
vue.js·gitlab·arbess·tiklab devops
我有一棵树3 小时前
Vue 项目中全局样式的正确写法:不要把字体和主题写在 #app 上
前端·javascript·vue.js
wangbing11256 小时前
开发指南139-VUE里的高级糖块
前端·javascript·vue.js
半桶水专家7 小时前
Vue 3 动态组件详解
前端·javascript·vue.js
我有一棵树7 小时前
避免 JS 报错阻塞 Vue 组件渲染:以 window.jsbridge 和 el-tooltip 为例
前端·javascript·vue.js
没有鸡汤吃不下饭7 小时前
解决前端项目中大数据复杂列表场景的完美方案
前端·javascript·vue.js
低保和光头哪个先来8 小时前
如何实现弹窗的 双击关闭 & 拖动 & 图层优先级
前端·javascript·css·vue.js
张雨zy9 小时前
使用nvm管理本地node版本
vue.js·node.js
小码编匠9 小时前
Three.js 遇上 Vue3 开发现代化 3D 可视化编辑系统
vue.js·typescript·three.js