Skip to content

Time Picker 时间选择器

概述

Time Picker 时间选择器是一个用于选择时间的组件,支持多种时间选择模式,包括单时间、时间范围等选择方式。它提供了丰富的配置选项和自定义功能,能够满足各种时间选择需求。

学习目标

  • 掌握 Time Picker 的基本概念和使用场景
  • 学会基础时间选择功能的实现
  • 了解时间范围选择和时间限制
  • 掌握时间格式化和显示
  • 学会禁用时间和步长配置
  • 了解自定义时间选择器
  • 掌握时间选择器在实际项目中的应用
  • 掌握 API 的完整使用方法

基础用法

基本时间选择

最简单的时间选择器:

vue
<template>
  <div>
    <h4>基本时间选择</h4>
    <el-time-picker
      v-model="time1"
      placeholder="选择时间"
    />
    <p>选中的时间:{{ time1 }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const time1 = ref('')
</script>

限制时间范围

限制可选择的时间范围:

vue
<template>
  <div>
    <h4>限制时间范围</h4>
    <el-time-picker
      v-model="time2"
      :disabled-hours="disabledHours"
      :disabled-minutes="disabledMinutes"
      :disabled-seconds="disabledSeconds"
      placeholder="选择时间"
    />
    <p>只能选择 9:00-18:00 的时间</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const time2 = ref('')

const disabledHours = () => {
  const hours = []
  for (let i = 0; i < 9; i++) {
    hours.push(i)
  }
  for (let i = 18; i < 24; i++) {
    hours.push(i)
  }
  return hours
}

const disabledMinutes = (hour) => {
  // 在工作时间内,分钟不限制
  return []
}

const disabledSeconds = (hour, minute) => {
  // 秒数不限制
  return []
}
</script>

时间范围选择

选择一个时间范围:

vue
<template>
  <div>
    <h4>时间范围选择</h4>
    <el-time-picker
      v-model="timeRange"
      is-range
      range-separator="至"
      start-placeholder="开始时间"
      end-placeholder="结束时间"
    />
    <p>选中的时间范围:{{ timeRange }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const timeRange = ref('')
</script>

固定时间点

提供固定的时间点选择:

vue
<template>
  <div>
    <h4>固定时间点</h4>
    <el-time-select
      v-model="time3"
      start="08:30"
      step="00:15"
      end="18:30"
      placeholder="选择时间"
    />
    <p>选中的时间:{{ time3 }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const time3 = ref('')
</script>

时间格式化

自定义时间显示格式:

vue
<template>
  <div>
    <h4>时间格式化</h4>
    <el-time-picker
      v-model="time4"
      format="HH:mm:ss"
      value-format="HH:mm:ss"
      placeholder="选择时间"
    />
    <p>格式化后的时间:{{ time4 }}</p>
    
    <el-time-picker
      v-model="time5"
      format="hh:mm:ss A"
      value-format="HH:mm:ss"
      placeholder="12小时制"
    />
    <p>12小时制时间:{{ time5 }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const time4 = ref('')
const time5 = ref('')
</script>

高级功能

步长设置

设置时间选择的步长:

vue
<template>
  <div>
    <h4>步长设置</h4>
    <div class="step-demo">
      <div class="step-item">
        <label>5分钟步长:</label>
        <el-time-select
          v-model="stepTime1"
          start="00:00"
          step="00:05"
          end="23:59"
          placeholder="选择时间"
        />
      </div>
      
      <div class="step-item">
        <label>15分钟步长:</label>
        <el-time-select
          v-model="stepTime2"
          start="00:00"
          step="00:15"
          end="23:59"
          placeholder="选择时间"
        />
      </div>
      
      <div class="step-item">
        <label>30分钟步长:</label>
        <el-time-select
          v-model="stepTime3"
          start="00:00"
          step="00:30"
          end="23:59"
          placeholder="选择时间"
        />
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const stepTime1 = ref('')
const stepTime2 = ref('')
const stepTime3 = ref('')
</script>

<style scoped>
.step-demo {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.step-item {
  display: flex;
  align-items: center;
  gap: 12px;
}

.step-item label {
  width: 120px;
  font-weight: 500;
  color: #303133;
}
</style>

禁用状态

禁用时间选择器:

vue
<template>
  <div>
    <h4>禁用状态</h4>
    <div class="disabled-demo">
      <el-time-picker
        v-model="disabledTime"
        disabled
        placeholder="禁用状态"
      />
      
      <el-time-picker
        v-model="readonlyTime"
        readonly
        placeholder="只读状态"
      />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const disabledTime = ref('12:30:00')
const readonlyTime = ref('15:45:30')
</script>

<style scoped>
.disabled-demo {
  display: flex;
  gap: 16px;
  align-items: center;
}
</style>

不同尺寸

提供不同尺寸的时间选择器:

vue
<template>
  <div>
    <h4>不同尺寸</h4>
    <div class="size-demo">
      <el-time-picker
        v-model="time6"
        size="large"
        placeholder="大尺寸"
      />
      <el-time-picker
        v-model="time7"
        placeholder="默认尺寸"
      />
      <el-time-picker
        v-model="time8"
        size="small"
        placeholder="小尺寸"
      />
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const time6 = ref('')
const time7 = ref('')
const time8 = ref('')
</script>

<style scoped>
.size-demo {
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: flex-start;
}
</style>

可清空

添加清空功能:

vue
<template>
  <div>
    <h4>可清空</h4>
    <el-time-picker
      v-model="clearableTime"
      clearable
      placeholder="可清空的时间选择器"
    />
    <p>选中的时间:{{ clearableTime || '未选择' }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const clearableTime = ref('')
</script>

实际应用示例

会议室预约时间选择

创建一个会议室预约的时间选择组件:

vue
<template>
  <div class="meeting-room-booking">
    <h3>会议室预约</h3>
    
    <div class="booking-form">
      <div class="form-section">
        <h4>选择会议室</h4>
        <el-select v-model="selectedRoom" placeholder="请选择会议室">
          <el-option
            v-for="room in meetingRooms"
            :key="room.id"
            :label="room.name"
            :value="room.id"
          >
            <span>{{ room.name }}</span>
            <span class="room-capacity">({{ room.capacity }}人)</span>
          </el-option>
        </el-select>
      </div>
      
      <div class="form-section">
        <h4>选择时间段</h4>
        <div class="time-selection">
          <div class="time-item">
            <label>开始时间:</label>
            <el-time-select
              v-model="startTime"
              start="08:00"
              step="00:30"
              end="18:00"
              placeholder="选择开始时间"
              @change="handleStartTimeChange"
            />
          </div>
          
          <div class="time-item">
            <label>结束时间:</label>
            <el-time-select
              v-model="endTime"
              :start="minEndTime"
              step="00:30"
              end="18:30"
              placeholder="选择结束时间"
              :disabled="!startTime"
            />
          </div>
        </div>
        
        <div v-if="duration" class="duration-info">
          <span class="duration-label">会议时长:</span>
          <span class="duration-value">{{ duration }}</span>
        </div>
      </div>
      
      <div v-if="selectedRoom && startTime && endTime" class="booking-summary">
        <h4>预约信息</h4>
        <div class="summary-content">
          <div class="summary-item">
            <span class="label">会议室:</span>
            <span class="value">{{ getRoomName(selectedRoom) }}</span>
          </div>
          <div class="summary-item">
            <span class="label">时间:</span>
            <span class="value">{{ startTime }} - {{ endTime }}</span>
          </div>
          <div class="summary-item">
            <span class="label">时长:</span>
            <span class="value">{{ duration }}</span>
          </div>
          <div class="summary-item">
            <span class="label">费用:</span>
            <span class="value cost">¥{{ calculateCost() }}</span>
          </div>
        </div>
        
        <div class="booking-actions">
          <el-button type="primary" @click="confirmBooking">确认预约</el-button>
          <el-button @click="resetForm">重置</el-button>
        </div>
      </div>
    </div>
    
    <div v-if="bookings.length > 0" class="booking-history">
      <h4>预约记录</h4>
      <div class="history-list">
        <div
          v-for="booking in bookings"
          :key="booking.id"
          class="history-item"
        >
          <div class="booking-info">
            <div class="booking-room">{{ getRoomName(booking.roomId) }}</div>
            <div class="booking-time">{{ booking.startTime }} - {{ booking.endTime }}</div>
            <div class="booking-date">{{ formatDate(booking.date) }}</div>
          </div>
          <div class="booking-status">
            <el-tag
              :type="getStatusType(booking.status)"
              size="small"
            >
              {{ booking.status }}
            </el-tag>
            <el-button
              v-if="booking.status === '已预约'"
              size="small"
              type="danger"
              @click="cancelBooking(booking.id)"
            >
              取消
            </el-button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'

const selectedRoom = ref('')
const startTime = ref('')
const endTime = ref('')
const bookings = ref([
  {
    id: 1,
    roomId: 1,
    startTime: '09:00',
    endTime: '10:30',
    date: new Date(),
    status: '已预约'
  },
  {
    id: 2,
    roomId: 2,
    startTime: '14:00',
    endTime: '16:00',
    date: new Date(),
    status: '已完成'
  }
])

const meetingRooms = ref([
  { id: 1, name: '会议室A', capacity: 10, hourlyRate: 50 },
  { id: 2, name: '会议室B', capacity: 20, hourlyRate: 80 },
  { id: 3, name: '会议室C', capacity: 50, hourlyRate: 150 },
  { id: 4, name: '多功能厅', capacity: 100, hourlyRate: 300 }
])

const minEndTime = computed(() => {
  if (!startTime.value) return '08:30'
  
  const [hours, minutes] = startTime.value.split(':').map(Number)
  const startMinutes = hours * 60 + minutes + 30 // 最少30分钟
  const endHours = Math.floor(startMinutes / 60)
  const endMins = startMinutes % 60
  
  return `${endHours.toString().padStart(2, '0')}:${endMins.toString().padStart(2, '0')}`
})

const duration = computed(() => {
  if (!startTime.value || !endTime.value) return ''
  
  const [startHours, startMinutes] = startTime.value.split(':').map(Number)
  const [endHours, endMinutes] = endTime.value.split(':').map(Number)
  
  const startTotalMinutes = startHours * 60 + startMinutes
  const endTotalMinutes = endHours * 60 + endMinutes
  
  const durationMinutes = endTotalMinutes - startTotalMinutes
  
  if (durationMinutes <= 0) return ''
  
  const hours = Math.floor(durationMinutes / 60)
  const minutes = durationMinutes % 60
  
  if (hours === 0) {
    return `${minutes}分钟`
  } else if (minutes === 0) {
    return `${hours}小时`
  } else {
    return `${hours}小时${minutes}分钟`
  }
})

const handleStartTimeChange = () => {
  // 如果结束时间小于开始时间,清空结束时间
  if (endTime.value && startTime.value >= endTime.value) {
    endTime.value = ''
  }
}

const getRoomName = (roomId) => {
  const room = meetingRooms.value.find(r => r.id === roomId)
  return room ? room.name : '未知会议室'
}

const calculateCost = () => {
  if (!selectedRoom.value || !duration.value) return 0
  
  const room = meetingRooms.value.find(r => r.id === selectedRoom.value)
  if (!room) return 0
  
  const [startHours, startMinutes] = startTime.value.split(':').map(Number)
  const [endHours, endMinutes] = endTime.value.split(':').map(Number)
  
  const startTotalMinutes = startHours * 60 + startMinutes
  const endTotalMinutes = endHours * 60 + endMinutes
  const durationMinutes = endTotalMinutes - startTotalMinutes
  
  const hours = Math.ceil(durationMinutes / 60) // 按小时计费,不足一小时按一小时计算
  
  return hours * room.hourlyRate
}

const confirmBooking = () => {
  const newBooking = {
    id: Date.now(),
    roomId: selectedRoom.value,
    startTime: startTime.value,
    endTime: endTime.value,
    date: new Date(),
    status: '已预约'
  }
  
  bookings.value.unshift(newBooking)
  ElMessage.success('会议室预约成功!')
  resetForm()
}

const resetForm = () => {
  selectedRoom.value = ''
  startTime.value = ''
  endTime.value = ''
}

const cancelBooking = (bookingId) => {
  const booking = bookings.value.find(b => b.id === bookingId)
  if (booking) {
    booking.status = '已取消'
    ElMessage.success('预约已取消')
  }
}

const formatDate = (date) => {
  return new Date(date).toLocaleDateString('zh-CN')
}

const getStatusType = (status) => {
  const statusMap = {
    '已预约': 'warning',
    '已完成': 'success',
    '已取消': 'danger'
  }
  return statusMap[status] || 'info'
}
</script>

<style scoped>
.meeting-room-booking {
  max-width: 800px;
  padding: 20px;
}

.booking-form {
  background: #f5f7fa;
  padding: 20px;
  border-radius: 8px;
  margin-bottom: 24px;
}

.form-section {
  margin-bottom: 24px;
}

.form-section:last-child {
  margin-bottom: 0;
}

.form-section h4 {
  margin: 0 0 12px 0;
  color: #303133;
  font-size: 16px;
}

.room-capacity {
  color: #909399;
  font-size: 12px;
  margin-left: 8px;
}

.time-selection {
  display: flex;
  gap: 20px;
  margin-bottom: 16px;
}

.time-item {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.time-item label {
  font-weight: 500;
  color: #303133;
  font-size: 14px;
}

.duration-info {
  padding: 12px;
  background: #e1f3d8;
  border-radius: 8px;
  text-align: center;
}

.finished-message {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}

.finished-message h3 {
  margin: 0;
  color: #529b2e;
}

.finished-message p {
  margin: 0;
  color: #606266;
}
</style>

工作时间管理

创建一个工作时间管理组件:

vue
<template>
  <div class="work-schedule">
    <h3>工作时间管理</h3>
    
    <div class="schedule-form">
      <h4>设置工作时间</h4>
      
      <div class="weekdays-schedule">
        <div
          v-for="day in weekdays"
          :key="day.key"
          class="day-schedule"
        >
          <div class="day-header">
            <el-checkbox
              v-model="day.enabled"
              @change="handleDayToggle(day)"
            >
              {{ day.name }}
            </el-checkbox>
          </div>
          
          <div v-if="day.enabled" class="time-slots">
            <div
              v-for="(slot, index) in day.timeSlots"
              :key="index"
              class="time-slot"
            >
              <el-time-select
                v-model="slot.start"
                start="00:00"
                step="00:30"
                end="23:30"
                placeholder="开始时间"
                @change="validateTimeSlot(day, slot)"
              />
              <span class="time-separator">至</span>
              <el-time-select
                v-model="slot.end"
                :start="slot.start || '00:30'"
                step="00:30"
                end="23:59"
                placeholder="结束时间"
                :disabled="!slot.start"
              />
              <el-button
                v-if="day.timeSlots.length > 1"
                size="small"
                type="danger"
                @click="removeTimeSlot(day, index)"
              >
                删除
              </el-button>
            </div>
            
            <el-button
              size="small"
              type="primary"
              @click="addTimeSlot(day)"
            >
              添加时间段
            </el-button>
          </div>
        </div>
      </div>
      
      <div class="schedule-actions">
        <el-button type="primary" @click="saveSchedule">保存设置</el-button>
        <el-button @click="resetSchedule">重置</el-button>
        <el-button @click="applyTemplate">应用模板</el-button>
      </div>
    </div>
    
    <div class="schedule-preview">
      <h4>工作时间预览</h4>
      <div class="preview-content">
        <div
          v-for="day in enabledDays"
          :key="day.key"
          class="preview-day"
        >
          <div class="preview-day-name">{{ day.name }}</div>
          <div class="preview-time-slots">
            <span
              v-for="(slot, index) in day.timeSlots"
              :key="index"
              class="preview-slot"
            >
              {{ slot.start }} - {{ slot.end }}
            </span>
          </div>
        </div>
        
        <div v-if="enabledDays.length === 0" class="no-schedule">
          暂未设置工作时间
        </div>
      </div>
      
      <div class="schedule-stats">
        <div class="stat-item">
          <span class="stat-label">工作天数:</span>
          <span class="stat-value">{{ enabledDays.length }}天/周</span>
        </div>
        <div class="stat-item">
          <span class="stat-label">总工作时长:</span>
          <span class="stat-value">{{ totalWorkHours }}小时/周</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'

const weekdays = ref([
  {
    key: 'monday',
    name: '周一',
    enabled: true,
    timeSlots: [{ start: '09:00', end: '18:00' }]
  },
  {
    key: 'tuesday',
    name: '周二',
    enabled: true,
    timeSlots: [{ start: '09:00', end: '18:00' }]
  },
  {
    key: 'wednesday',
    name: '周三',
    enabled: true,
    timeSlots: [{ start: '09:00', end: '18:00' }]
  },
  {
    key: 'thursday',
    name: '周四',
    enabled: true,
    timeSlots: [{ start: '09:00', end: '18:00' }]
  },
  {
    key: 'friday',
    name: '周五',
    enabled: true,
    timeSlots: [{ start: '09:00', end: '18:00' }]
  },
  {
    key: 'saturday',
    name: '周六',
    enabled: false,
    timeSlots: [{ start: '', end: '' }]
  },
  {
    key: 'sunday',
    name: '周日',
    enabled: false,
    timeSlots: [{ start: '', end: '' }]
  }
])

const enabledDays = computed(() => {
  return weekdays.value.filter(day => 
    day.enabled && day.timeSlots.some(slot => slot.start && slot.end)
  )
})

const totalWorkHours = computed(() => {
  let total = 0
  
  enabledDays.value.forEach(day => {
    day.timeSlots.forEach(slot => {
      if (slot.start && slot.end) {
        const [startHours, startMinutes] = slot.start.split(':').map(Number)
        const [endHours, endMinutes] = slot.end.split(':').map(Number)
        
        const startTotalMinutes = startHours * 60 + startMinutes
        const endTotalMinutes = endHours * 60 + endMinutes
        
        const duration = (endTotalMinutes - startTotalMinutes) / 60
        total += duration
      }
    })
  })
  
  return total.toFixed(1)
})

const handleDayToggle = (day) => {
  if (!day.enabled) {
    // 禁用时清空时间段
    day.timeSlots = [{ start: '', end: '' }]
  } else {
    // 启用时设置默认时间段
    if (!day.timeSlots[0].start) {
      day.timeSlots[0] = { start: '09:00', end: '18:00' }
    }
  }
}

const addTimeSlot = (day) => {
  day.timeSlots.push({ start: '', end: '' })
}

const removeTimeSlot = (day, index) => {
  day.timeSlots.splice(index, 1)
}

const validateTimeSlot = (day, slot) => {
  if (slot.start && slot.end && slot.start >= slot.end) {
    ElMessage.warning('结束时间必须大于开始时间')
    slot.end = ''
  }
}

const saveSchedule = () => {
  // 验证时间段
  let isValid = true
  
  enabledDays.value.forEach(day => {
    day.timeSlots.forEach(slot => {
      if (!slot.start || !slot.end) {
        isValid = false
      }
    })
  })
  
  if (!isValid) {
    ElMessage.warning('请完善所有时间段设置')
    return
  }
  
  ElMessage.success('工作时间设置已保存')
}

const resetSchedule = () => {
  weekdays.value.forEach(day => {
    if (['monday', 'tuesday', 'wednesday', 'thursday', 'friday'].includes(day.key)) {
      day.enabled = true
      day.timeSlots = [{ start: '09:00', end: '18:00' }]
    } else {
      day.enabled = false
      day.timeSlots = [{ start: '', end: '' }]
    }
  })
  
  ElMessage.success('已重置为默认工作时间')
}

const applyTemplate = () => {
  // 应用弹性工作制模板
  weekdays.value.forEach(day => {
    if (['monday', 'tuesday', 'wednesday', 'thursday', 'friday'].includes(day.key)) {
      day.enabled = true
      day.timeSlots = [
        { start: '09:00', end: '12:00' },
        { start: '13:30', end: '18:30' }
      ]
    } else {
      day.enabled = false
      day.timeSlots = [{ start: '', end: '' }]
    }
  })
  
  ElMessage.success('已应用弹性工作制模板')
}
</script>

<style scoped>
.work-schedule {
  max-width: 800px;
  padding: 20px;
}

.schedule-form {
  background: #f5f7fa;
  padding: 20px;
  border-radius: 8px;
  margin-bottom: 24px;
}

.schedule-form h4 {
  margin: 0 0 20px 0;
  color: #303133;
}

.weekdays-schedule {
  margin-bottom: 24px;
}

.day-schedule {
  margin-bottom: 20px;
  padding: 16px;
  background: white;
  border-radius: 6px;
  border: 1px solid #e4e7ed;
}

.day-header {
  margin-bottom: 12px;
}

.time-slots {
  margin-left: 24px;
}

.time-slot {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}

.time-separator {
  color: #606266;
  font-weight: 500;
}

.schedule-actions {
  display: flex;
  gap: 12px;
}

.schedule-preview {
  background: white;
  padding: 20px;
  border-radius: 8px;
  border: 1px solid #e4e7ed;
}

.schedule-preview h4 {
  margin: 0 0 16px 0;
  color: #303133;
}

.preview-content {
  margin-bottom: 20px;
}

.preview-day {
  display: flex;
  align-items: center;
  margin-bottom: 12px;
  padding: 8px 0;
  border-bottom: 1px solid #f0f2f5;
}

.preview-day-name {
  width: 60px;
  font-weight: 500;
  color: #303133;
}

.preview-time-slots {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}

.preview-slot {
  background: #e1f3d8;
  color: #529b2e;
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 12px;
}

.no-schedule {
  text-align: center;
  color: #909399;
  padding: 40px;
  background: #f5f7fa;
  border-radius: 6px;
}

.schedule-stats {
  display: flex;
  gap: 24px;
  padding: 16px;
  background: #f8f9fa;
  border-radius: 6px;
}

.stat-item {
  display: flex;
  align-items: center;
  gap: 8px;
}

.stat-label {
  color: #606266;
  font-size: 14px;
}

.stat-value {
  color: #303133;
  font-weight: 500;
}
</style>

API 文档

TimePicker Attributes

名称说明类型默认值
model-value / v-model绑定值Date / string
readonly只读booleanfalse
disabled禁用booleanfalse
editable文本框可输入booleantrue
clearable是否显示清除按钮booleantrue
size输入框尺寸enumdefault
placeholder非范围选择时的占位内容string
start-placeholder范围选择时开始时间的占位内容string
end-placeholder范围选择时结束时间的占位内容string
is-range是否为时间范围选择booleanfalse
arrow-control是否使用箭头进行时间选择booleanfalse
format显示在输入框中的格式stringHH:mm:ss
value-format绑定值的格式string
default-value可选,选择器打开时默认显示的时间Date / string
name原生属性string
prefix-icon自定义前缀图标string / ComponentClock
clear-icon自定义清空图标string / ComponentCircleClose
disabled-hours禁止选择部分小时选项Function
disabled-minutes禁止选择部分分钟选项Function
disabled-seconds禁止选择部分秒选项Function
teleported是否将 picker 的下拉列表插入至 body 元素booleantrue

TimeSelect Attributes

名称说明类型默认值
model-value / v-model绑定值string
disabled禁用状态booleanfalse
editable文本框可输入booleantrue
clearable是否显示清除按钮booleantrue
size输入框尺寸enumdefault
placeholder占位内容string
name原生属性string
effectTooltip 主题enumlight
prefix-icon自定义前缀图标string / ComponentClock
clear-icon自定义清空图标string / ComponentCircleClose
start开始时间string09:00
end结束时间string18:00
step间隔时间string00:30
min-time最早时间点string
max-time最晚时间点string

TimePicker Events

名称说明类型
change用户确认选定的值时触发Function
blur当 input 失去焦点时触发Function
focus当 input 获得焦点时触发Function
visible-change当 TimePicker 的下拉列表出现/消失时触发Function

TimePicker Methods

名称说明类型
focus使 input 获取焦点Function
blur使 input 失去焦点Function

实践练习

练习1:课程表时间设置

创建一个课程表的时间设置系统:

vue
<template>
  <div class="course-schedule">
    <h3>课程表设置</h3>
    
    <div class="schedule-settings">
      <h4>设置上课时间</h4>
      
      <div class="time-periods">
        <div
          v-for="(period, index) in timePeriods"
          :key="index"
          class="period-item"
        >
          <div class="period-label">第{{ index + 1 }}节课</div>
          <el-time-select
            v-model="period.start"
            start="08:00"
            step="00:05"
            end="22:00"
            placeholder="开始时间"
          />
          <span class="time-separator">-</span>
          <el-time-select
            v-model="period.end"
            :start="period.start || '08:05'"
            step="00:05"
            end="22:00"
            placeholder="结束时间"
            :disabled="!period.start"
          />
          <el-button
            v-if="timePeriods.length > 1"
            size="small"
            type="danger"
            @click="removePeriod(index)"
          >
            删除
          </el-button>
        </div>
      </div>
      
      <div class="period-actions">
        <el-button size="small" @click="addPeriod">添加课时</el-button>
        <el-button size="small" @click="applyStandardSchedule">应用标准课表</el-button>
      </div>
    </div>
    
    <div class="schedule-preview">
      <h4>课程表预览</h4>
      <div class="preview-table">
        <div class="time-column">
          <div class="time-header">时间</div>
          <div
            v-for="(period, index) in validPeriods"
            :key="index"
            class="time-cell"
          >
            {{ period.start }} - {{ period.end }}
          </div>
        </div>
        
        <div
          v-for="day in weekDays"
          :key="day"
          class="day-column"
        >
          <div class="day-header">{{ day }}</div>
          <div
            v-for="(period, index) in validPeriods"
            :key="index"
            class="course-cell"
          >
            <!-- 这里可以添加课程内容 -->
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'

const timePeriods = ref([
  { start: '08:00', end: '08:45' },
  { start: '08:55', end: '09:40' },
  { start: '10:00', end: '10:45' },
  { start: '10:55', end: '11:40' },
  { start: '14:00', end: '14:45' },
  { start: '14:55', end: '15:40' },
  { start: '16:00', end: '16:45' },
  { start: '16:55', end: '17:40' }
])

const weekDays = ['周一', '周二', '周三', '周四', '周五']

const validPeriods = computed(() => {
  return timePeriods.value.filter(period => period.start && period.end)
})

const addPeriod = () => {
  timePeriods.value.push({ start: '', end: '' })
}

const removePeriod = (index) => {
  timePeriods.value.splice(index, 1)
}

const applyStandardSchedule = () => {
  timePeriods.value = [
    { start: '08:00', end: '08:45' },
    { start: '08:55', end: '09:40' },
    { start: '10:00', end: '10:45' },
    { start: '10:55', end: '11:40' },
    { start: '14:00', end: '14:45' },
    { start: '14:55', end: '15:40' },
    { start: '16:00', end: '16:45' },
    { start: '16:55', end: '17:40' }
  ]
  
  ElMessage.success('已应用标准课表时间')
}
</script>

<style scoped>
.course-schedule {
  max-width: 1000px;
  padding: 20px;
}

.schedule-settings {
  background: #f5f7fa;
  padding: 20px;
  border-radius: 8px;
  margin-bottom: 24px;
}

.schedule-settings h4 {
  margin: 0 0 16px 0;
  color: #303133;
}

.time-periods {
  margin-bottom: 16px;
}

.period-item {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
  padding: 12px;
  background: white;
  border-radius: 6px;
  border: 1px solid #e4e7ed;
}

.period-label {
  width: 80px;
  font-weight: 500;
  color: #303133;
}

.time-separator {
  color: #606266;
}

.period-actions {
  display: flex;
  gap: 12px;
}

.schedule-preview {
  background: white;
  padding: 20px;
  border-radius: 8px;
  border: 1px solid #e4e7ed;
}

.schedule-preview h4 {
  margin: 0 0 16px 0;
  color: #303133;
}

.preview-table {
  display: flex;
  border: 1px solid #e4e7ed;
  border-radius: 6px;
  overflow: hidden;
}

.time-column,
.day-column {
  flex: 1;
  border-right: 1px solid #e4e7ed;
}

.day-column:last-child {
  border-right: none;
}

.time-header,
.day-header {
  padding: 12px 8px;
  background: #f5f7fa;
  font-weight: 500;
  text-align: center;
  border-bottom: 1px solid #e4e7ed;
}

.time-cell,
.course-cell {
  padding: 8px;
  min-height: 40px;
  border-bottom: 1px solid #f0f2f5;
  font-size: 12px;
  text-align: center;
}

.time-cell {
  background: #fafafa;
  color: #606266;
}

.course-cell {
  background: white;
}
</style>

练习2:倒计时器设置

创建一个倒计时器的时间设置组件:

vue
<template>
  <div class="countdown-timer">
    <h3>倒计时器</h3>
    
    <div class="timer-settings">
      <h4>设置倒计时时间</h4>
      
      <div class="time-input">
        <div class="input-group">
          <label>小时:</label>
          <el-input-number
            v-model="hours"
            :min="0"
            :max="23"
            controls-position="right"
          />
        </div>
        
        <div class="input-group">
          <label>分钟:</label>
          <el-input-number
            v-model="minutes"
            :min="0"
            :max="59"
            controls-position="right"
          />
        </div>
        
        <div class="input-group">
          <label>秒数:</label>
          <el-input-number
            v-model="seconds"
            :min="0"
            :max="59"
            controls-position="right"
          />
        </div>
      </div>
      
      <div class="quick-settings">
        <h5>快速设置</h5>
        <div class="quick-buttons">
          <el-button size="small" @click="setTime(0, 1, 0)">1分钟</el-button>
          <el-button size="small" @click="setTime(0, 5, 0)">5分钟</el-button>
          <el-button size="small" @click="setTime(0, 10, 0)">10分钟</el-button>
          <el-button size="small" @click="setTime(0, 30, 0)">30分钟</el-button>
          <el-button size="small" @click="setTime(1, 0, 0)">1小时</el-button>
        </div>
      </div>
      
      <div class="timer-controls">
        <el-button
          type="primary"
          :disabled="totalSeconds === 0"
          @click="startTimer"
        >
          开始倒计时
        </el-button>
        <el-button @click="resetTimer">重置</el-button>
      </div>
    </div>
    
    <div v-if="isRunning || remainingTime > 0" class="timer-display">
      <div class="countdown-circle">
        <div class="time-display">
          {{ formatTime(remainingTime) }}
        </div>
        <div class="progress-ring">
          <svg width="200" height="200">
            <circle
              cx="100"
              cy="100"
              r="90"
              fill="none"
              stroke="#e4e7ed"
              stroke-width="8"
            />
            <circle
              cx="100"
              cy="100"
              r="90"
              fill="none"
              stroke="#409EFF"
              stroke-width="8"
              stroke-linecap="round"
              :stroke-dasharray="circumference"
              :stroke-dashoffset="strokeDashoffset"
              transform="rotate(-90 100 100)"
            />
          </svg>
        </div>
      </div>
      
      <div class="timer-controls">
        <el-button
          v-if="!isRunning"
          type="primary"
          @click="startTimer"
        >
          继续
        </el-button>
        <el-button
          v-if="isRunning"
          type="warning"
          @click="pauseTimer"
        >
          暂停
        </el-button>
        <el-button @click="stopTimer">停止</el-button>
      </div>
    </div>
    
    <div v-if="isFinished" class="timer-finished">
      <div class="finished-message">
        <el-icon size="48" color="#67c23a">
          <Check />
        </el-icon>
        <h3>倒计时结束!</h3>
        <p>设定的时间已到</p>
      </div>
      
      <el-button type="primary" @click="resetTimer">
        重新设置
      </el-button>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onUnmounted } from 'vue'
import { Check } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'

const hours = ref(0)
const minutes = ref(5)
const seconds = ref(0)
const remainingTime = ref(0)
const isRunning = ref(false)
const isFinished = ref(false)
const timer = ref(null)

const totalSeconds = computed(() => {
  return hours.value * 3600 + minutes.value * 60 + seconds.value
})

const circumference = computed(() => {
  return 2 * Math.PI * 90 // r = 90
})

const strokeDashoffset = computed(() => {
  if (totalSeconds.value === 0) return circumference.value
  const progress = remainingTime.value / totalSeconds.value
  return circumference.value * (1 - progress)
})

const setTime = (h, m, s) => {
  hours.value = h
  minutes.value = m
  seconds.value = s
}

const startTimer = () => {
  if (remainingTime.value === 0) {
    remainingTime.value = totalSeconds.value
  }
  
  if (remainingTime.value === 0) {
    ElMessage.warning('请设置倒计时时间')
    return
  }
  
  isRunning.value = true
  isFinished.value = false
  
  timer.value = setInterval(() => {
    remainingTime.value--
    
    if (remainingTime.value <= 0) {
      clearInterval(timer.value)
      isRunning.value = false
      isFinished.value = true
      remainingTime.value = 0
      
      // 播放提示音或显示通知
      ElMessage.success('倒计时结束!')
    }
  }, 1000)
}

const pauseTimer = () => {
  if (timer.value) {
    clearInterval(timer.value)
    timer.value = null
  }
  isRunning.value = false
}

const stopTimer = () => {
  if (timer.value) {
    clearInterval(timer.value)
    timer.value = null
  }
  isRunning.value = false
  remainingTime.value = 0
  isFinished.value = false
}

const resetTimer = () => {
  stopTimer()
  hours.value = 0
  minutes.value = 5
  seconds.value = 0
}

const formatTime = (totalSeconds) => {
  const h = Math.floor(totalSeconds / 3600)
  const m = Math.floor((totalSeconds % 3600) / 60)
  const s = totalSeconds % 60
  
  if (h > 0) {
    return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
  } else {
    return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
  }
}

onUnmounted(() => {
  if (timer.value) {
    clearInterval(timer.value)
  }
})
</script>

<style scoped>
.countdown-timer {
  max-width: 600px;
  padding: 20px;
  margin: 0 auto;
}

.timer-settings {
  background: #f5f7fa;
  padding: 20px;
  border-radius: 8px;
  margin-bottom: 24px;
}

.timer-settings h4 {
  margin: 0 0 16px 0;
  color: #303133;
}

.time-input {
  display: flex;
  gap: 20px;
  margin-bottom: 20px;
  justify-content: center;
}

.input-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.input-group label {
  font-weight: 500;
  color: #303133;
}

.quick-settings {
  margin-bottom: 20px;
}

.quick-settings h5 {
  margin: 0 0 12px 0;
  color: #303133;
  text-align: center;
}

.quick-buttons {
  display: flex;
  gap: 8px;
  justify-content: center;
  flex-wrap: wrap;
}

.timer-controls {
  display: flex;
  gap: 12px;
  justify-content: center;
}

.timer-display {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  padding: 40px;
  background: white;
  border-radius: 8px;
  border: 1px solid #e4e7ed;
  margin-bottom: 24px;
}

.countdown-circle {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.time-display {
  position: absolute;
  font-size: 24px;
  font-weight: bold;
  color: #303133;
  text-align: center;
}

.progress-ring {
  transform: rotate(-90deg);
}

.timer-finished {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;

Element Plus Study Guide