vue3实现时间分段选择

1. test.vue文件:

<template>
  <div>
    <el-button
      size="default"
      type="primary"
      icon="Plus"
      @click.prevent="timeClick"
    >
      时间段选择
    </el-button>
    <el-button
      size="default"
      type="primary"
      icon="Edit"
      @click.prevent="editClick"
    >
      时间段编辑
    </el-button>
    <!-- dialog弹窗加参数判断 v-if="dialog.visible", 是为解决弹窗加载子组件时只加载一次的问题,这样就是每次弹窗时重新渲染 -->
    <el-dialog
      v-model="dialog.visible"
      :title="dialog.title"
      @close="cancel"
      width="720px"
      v-if="dialog.visible"
    >
      <el-form
        ref="dataForm"
        :model="formData"
        :rules="rules"
        label-width="100px"
      >
         <el-form-item label="任务名称" prop="taskName">
           <el-input v-model="formData.taskName" placeholder="请输入任务名称" />
        </el-form-item>
         <el-form-item label="时间分段" prop="times">
          <div>
            <time-plan
              ref="PlanTime"
              :week-list="weekList"
              :width="formData.width"
              :clickbtn="formData.clickbtn"
              :period-num="formData.periodNum"
              @change="selectBack"
              @reserved="clickHandler"
            />
          </div>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="cancel">{{ $t('messageBox.cancel') }}</el-button>
          <el-button v-has-perm="['sys:dictItem:update','sys:dictItem:add']" type="primary" @click="submitForm">{{ $t('messageBox.determine') }}</el-button>
        </div>
      </template>
    </el-dialog>
    <el-dialog
      v-model="dialog.taskVisible"
      :title="dialog.taskTitle"
      v-if="dialog.taskVisible"
    >
      <el-form
        ref="dataFormRef"
        :model="formData"
        :rules="taskRules"
      >
        <el-form-item label="备注" prop="remark">
           <el-input v-model="formData.remark" placeholder="请输入内容" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="taskCancel">{{ $t('messageBox.cancel') }}</el-button>
          <el-button type="primary" @click="taskSubmit">{{ $t('messageBox.determine') }}</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>

<script lang="ts" setup>
import { reactive, toRefs, ref } from 'vue'
import { ElForm, ElMessage, ElMessageBox } from 'element-plus'
import TimePlan from '@/components/TimePlan/index.vue'

const dataForm = ref(ElForm)
const dataFormRef = ref(ElForm)
const PlanTime = ref(ElForm)
const state = reactive({
  loading: true,
  // 选中ID数组
  ids: [],
  // 非单个禁用
  // single: true,
  // 非多个禁用
  multiple: true,
  dialog: {
    title: '',
    visible: false,
    taskTitle: '',
    taskVisible: false
  },
  formData: {
    taskName: '',
    remark: '',
    width: '454px',
    clickbtn: {
      visible: true,
      title: '配置算法'
    },
    time: undefined,
    index: undefined,
    periodNum: 3
  },
  rules: {
    taskName: [
      { required: true, message: '任务名称不能为空', trigger: 'blur' }
    ]
  },
  taskRules: {
    remark: [
       { required: true, message: '备注不能为空', trigger: 'blur' }
    ]
  },
  weekList: [
      {
        label: '周一',
        active: false,
        timeList: [],
        popovisible: false
      },
      {
        label: '周二',
        active: false,
        timeList: [],
        popovisible: false
      },
      {
        label: '周三',
        active: false,
        timeList: [],
        popovisible: false
      },
      {
        label: '周四',
        active: false,
        timeList: [],
        popovisible: false
      },
      {
        label: '周五',
        active: false,
        timeList: [],
        popovisible: false
      },
      {
        label: '周六',
        active: false,
        timeList: [],
        popovisible: false
      },
      {
        label: '周日',
        active: false,
        timeList: [],
        popovisible: false
      }
    ]
})
const { loading, ids, formData, rules, dialog, weekList, taskRules } = toRefs(state)

/**
 * 时间分段组件回调函数
 */
const selectBack = (list) => {
  // console.log(list)
  state.weekList = list
}

const clickHandler = (data) => {
  // 预留按钮触发函数
  console.log(data)
  state.formData.time = data.time
  state.formData.index = data.index
  state.dialog.taskTitle = '算法配置'
  state.dialog.taskVisible = true
}

const taskCancel = () => {
  state.dialog.taskVisible = false
  state.formData.time = undefined
  state.formData.index = undefined
  dataFormRef.value.resetFields()
}

/**
 * 时间段选择
 */
const timeClick = () => {
  state.dialog.visible = true
  state.dialog.title = '时间段选择'
}
/**
 * 时间段编辑
 */
const editClick = () => {
  state.formData.taskName = '任务1'
  state.dialog.visible = true
  state.dialog.title = '时间段编辑'
  let timeList = [{
    "startTime": "09:00",
    "endTime": "18:00"
  }]
  state.weekList.forEach((value,index) => {
    // 星期二的时间段
    if (index == 1) {
      value.timeList = timeList
    }
  })
}
const taskSubmit = () => {
  dataFormRef.value.validate((valid: any) => {
    if (valid) {
      console.log('保存备注数据操作。。。')
      state.weekList.forEach((value) => {
        value.timeList.forEach((tm, ind) => {
          if (ind == state.formData.index) {
            tm.data = state.formData.remark
          }
        })
      })
      taskCancel()
    }
  })
}
const submitForm = () => {
  dataForm.value.validate((valid: any) => {
    if (valid) {
      console.log('保存时间段数据操作。。。')
      console.log(JSON.stringify(state.weekList))
      cancel()
    }
  })
}

const cancel = () => {
  state.dialog.visible = false
  dataForm.value.resetFields()
  // 关闭弹窗时清除timeList的数据
  state.weekList.forEach((value) => {
    value.timeList = []
  })
}
</script>
<style  lang="scss" scoped>
</style>

2. 时间分段组件index.vue:

<template>
  <div class="week-plan-time-wrap">
    <div class="tool-tips clearfix" :style="{maxWidth: boxwidths}">
      <el-button
        size="default"
        type="danger"
        class="fr"
        plain
        icon="Delete"
        @click.stop="resetForm"
      >清空</el-button>
    </div>
    <div class="scroll-box">
      <div class="week-plan-time" :style="{width: boxwidths}">
        <div class="az-time-list">
          <ul ref="timeContainer">
            <li v-for="index in 24" :key="index">{{ index-1 }}</li>
          </ul>
        </div>
        <div :ref="weekId" class="az-week-list">
          <div v-for="(item,index) in weekList" :key="index" class="week-item">
            <span class="label">{{ item[props.label] }}</span>
            <div
              class="time-swiper"
              @mousedown.stop="event => mousedownStart(event,[index,item[props.timeList].length-1])"
            >
              <s v-for="ind in 24" :key="ind" class="chunk"></s>
              <el-popover
                placement="top"
                v-for="(time,ind) in item[props.timeList]"
                :key="ind+'_'"
                v-model:visible="time.popovisible"
                trigger="click"
                width="240"
              >
                <template #reference>
                  <div
                    class="slider-time"
                    :class="{'active': (activeTime[0] == index && activeTime[1] == ind)}"
                    :style="{width: time.width,left: time.pointX}"
                  >
                    <div
                      class="content"
                      :style="{background: time.style}"
                      @mousedown.stop="event => arrowsStart(event,time,'content',[index,ind])"
                    ></div>
                    <div
                      v-if="(activeTime[0] == index && activeTime[1] == ind)"
                      class="left-arrows arrows"
                      @mousedown.stop="event => arrowsStart(event,time,'start')"
                    >
                      <i class="el-icon-caret-bottom">
                        <el-icon><CaretBottom /></el-icon>
                      </i>
                      <i class="el-icon-caret-top">
                        <el-icon><CaretTop /></el-icon>
                      </i>
                    </div>
                    <div
                      v-if="(activeTime[0] == index && activeTime[1] == ind)"
                      class="right-arrows arrows"
                      @mousedown.stop="event => arrowsStart(event,time,'end')"
                    >
                     <i class="el-icon-caret-bottom">
                        <el-icon><CaretBottom /></el-icon>
                      </i>
                      <i class="el-icon-caret-top">
                        <el-icon><CaretTop /></el-icon>
                      </i>
                    </div>
                  </div>
                </template>
                <!-- 加上该参数设置 :teleported="false",解决选中时间段后el-popover弹窗会自动关闭的问题 -->
                <div class="detail-box">
                  <el-time-picker
                    v-if="timeLevel=='mm'"
                    v-model="time.RangeTime"
                    :clearable="false"
                    style="width: 100%"
                    is-range
                    range-separator="-"
                    format="HH:mm"
                    value-format="HH:mm"
                    :teleported="false"
                    @change="value => timeChange(value,time)"
                  />
                  <div class="btn-box" style="text-align: right;margin-top: 10px;">
                    <el-button
                      v-if="clickbtn.visible"
                      type="success"
                      @click.stop="reservedfun(time,ind)"
                    >{{ clickbtn.title }}</el-button>
                    <el-button
                      type="danger"
                      @click.stop="deleteTime(index,ind)"
                    >删除</el-button>
                    <el-button
                      type="primary"
                      @click.stop="saveTime(time)"
                    >确定</el-button>
                  </div>
                </div>
              </el-popover>
            </div>
            <el-popover
              placement="left"
              width="350"
              trigger="click"
              v-model:visible="item.popovisible"
              append-to-body="false"
            >
              <template #reference>
                <span class="copy-btn" @click.stop="actionCopy(index)">
                  <el-icon><DocumentCopy /></el-icon>
                </span>
              </template>
              <p>复制到</p>
              <p>
                <el-checkbox
                  v-model="checkAll"
                  :indeterminate="isIndeterminate"
                  @change="handleCheckAllChange"
                >全选</el-checkbox>
                <el-checkbox-group v-model="copyList" @change="handleCheckedCitiesChange">
                  <el-checkbox
                    v-for="(val,i) in weekList"
                    :key="i+'__'"
                    :disabled="val.disabled"
                    :label="i"
                  >{{ val[props.label] }}</el-checkbox>
                </el-checkbox-group>
              </p>
              <div class="text-right">
                <el-button type="danger" @click="closePopover(index)">取消</el-button>
                <el-button type="primary" @click="confirmCopy(index)">确认</el-button>
              </div>
            </el-popover>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { reactive, toRefs, defineProps, onMounted, watch, onUpdated, ref } from 'vue'
import { ElForm, ElMessage, ElMessageBox } from 'element-plus'
import { deepClone } from '@/utils/index'
const emit = defineEmits(['change','reserved'])
const prop = defineProps({
  weekList: {
    type: Array,
    default() {
      return []
    }
  },
  width: {
    type: String,
    default: '454px'
  },
  clickbtn: {
    type: Object,
    default() {
      return {
        visible: false,
        title: '预留按钮'
      }
    }
  },
  periodNum: {
    type: Number,
    default: 5
  }
})

const state = reactive({
  timeLevel: 'mm',
  width: prop.width,
  weekId: 'week' + new Date().getTime(),
  boxwidths: '',
  weekList: prop.weekList,
  props: {
    label: 'label',
    timeList: 'timeList',
    startTime: 'startTime',
    endTime: 'endTime'
  },
  // 是否重复选择(即多个时间段有重叠部分)
  repeat: false,
  copyList: [],
  checkAll: false,
  isIndeterminate: false,
  // 当前激活对象
  activeTime: [],
  // 组件操作对象
  temp: undefined,
  // 拖拽节点
  dageDom: '',
  secondWidth: '', // 1秒对应的容器总宽度
  // 判断是否鼠标点下
  ifDrag: false,
  // 预留按钮
  clickbtn: prop.clickbtn,
  // 数据是否有所改变
  dataChange: false,
  // 一天允许选择时间段个数
  periodNum: prop.periodNum
})
const resetForm = () => {
  ElMessageBox.confirm('此操作将清空当前所有已选时段, 是否继续?', '警告', {
    confirmButtonText: '确 定',
    cancelButtonText: '取 消',
    type: 'warning'
  }).then(() => {
    state.weekList.forEach(value => {
        value[state.props.timeList] = []
      })
      state.temp = undefined
      state.dageDom = ''
      state.ifDrag = false
      state.activeTime = []
      state.copyList = []
      state.checkAll = false
      state.isIndeterminate = false
      listenerChange()
  }).catch(() =>
    ElMessage.info('已取消清空')
  )
}
const { 
  timeLevel,
  weekId,
  weekList,
  props,
  repeat,
  copyList,
  checkAll,
  isIndeterminate,
  activeTime,
  temp,
  dageDom,
  secondWidth,
  ifDrag,
  boxwidths,
  clickbtn
} = toRefs(state)

const initFormatData = () => {
  // 重置参数
  state.temp = undefined
  state.dageDom = ''
  state.ifDrag = false
  state.activeTime = []
  state.copyList = []
  state.checkAll = false
  state.isIndeterminate = false
  // 格式化数据  转化成组件可操作格式
  state.weekList.forEach(value => {
    if (value[state.props.timeList] && value[state.props.timeList].length) {
      value[state.props.timeList].forEach(val => {
        val.pointX = ''
        val.r_width = ''
        val.r_pointX = ''
        val.width = ''
        val.startX = ''
        val.endX = ''
        val.id = ''
        val.temporary = false
        val.popovisible = false
        val.style = randomRgb()
        val.RangeTime = [
          val[state.props.startTime],
          val[state.props.endTime]
        ]
        // 计算时段宽度
        let st = formatSecond(val[state.props.startTime])
        let et = formatSecond(val[state.props.endTime])
        val.width = (et - st) * state.secondWidth + 'px'
        // 计算时段定位
        val.pointX = st * state.secondWidth + 'px'
      })
      // 按起始时间重新排序
      value[state.props.timeList].sort((a, b) => {
        return (a.pointX.replace('px', '') * 1) - (b.pointX.replace('px', '') * 1)
      })
    }
  })
}
const formatSecond = (time) => {
if (!time) return
    let t = time.split(':')
    if (state.timeLevel === 'mm') {
      if (t.length !== 2) return
      // 去除前缀0
      let ht = t[0].replace(/^0/, '')
      let mt = t[1].replace(/^0/, '')
      return (ht * 60 * 60) + mt * 60
    }
}

const mousedownStart = (event, action) => {
  if (action[1] > (state.periodNum - 2)) {
    ElMessage.warning('只允许选择' + state.periodNum + '个时间段!')
    return false
  }
  state.dageDom = 'addTime'
  let temp = {
    pointX: event.layerX + 'px', // 时段定位
    r_width: '', // 备份
    r_pointX: '', // 备份
    width: `${state.secondWidth}px`, // 时段宽度
    startX: '', // 操作时鼠标点击起始点位置
    endX: '', // 操作时鼠标最终位置
    [state.props.startTime]: '', // 计算出的起始时间
    [state.props.endTime]: '', // 计算出的结束时间
    RangeTime: [],
    popovisible: false, // 时间选择弹窗显示/隐藏
    isPre: true,
    style: randomRgb(),
    temporary: true // 标记当前操作项 操作结束后为false
  }
  recalcTime(temp)
  state.weekList[action[0]][state.props.timeList].push(temp)
  // 按起始时间重新排序
  state.weekList[action[0]][state.props.timeList].sort((a, b) => {
    return (a.pointX.replace('px', '') * 1) - (b.pointX.replace('px', '') * 1)
  })
  state.activeTime = [
    action[0],
    state.weekList[action[0]][state.props.timeList].length - 1
  ]
  state.ifDrag = true
  state.dageDom = 'end'
  state.temp = temp
  // 备份记录
  state.temp.r_width = temp.width.replace('px', '') * 1
  state.temp.r_pointX = temp.pointX.replace('px', '') * 1
  state.temp.startX = event.clientX
}

// 点击结束
const mouseupEnd = (event) => {
  try {
    // 鼠标点击其他位置取消选中的操作对象
    let item_index = state.weekList[state.activeTime[0]][state.props.timeList].findIndex((d) => {
      return d.temporary
    })
    if (!state.temp) {
      state.activeTime = []
    } else {
      // 数据有变化时才往上提交
      if (state.dataChange) {
        listenerChange()
      }
    }
    // 并非真正想新增
    if (state.temp && state.temp.isPre) {
      state.weekList[state.activeTime[0]][state.props.timeList].splice(item_index, 1)
    }
    state.temp.temporary = false
    state.dageDom = ''
    state.ifDrag = false
  } catch (error) {
  }
}

// 时段点击拖拽
const arrowsStart = (event, temp, dom, action) => {
  state.activeTime = action ? action : state.activeTime
  state.ifDrag = true
  state.dageDom = dom
  state.temp = temp
  state.temp.temporary = true
  // 备份记录
  state.temp.r_width = temp.width.replace('px', '') * 1
  state.temp.r_pointX = temp.pointX.replace('px', '') * 1
  state.temp.startX = event.clientX
}

// 鼠标移动
const mousemoveNow = () => {
  if (state.ifDrag) {
    state.temp.endX = event.clientX
    state.temp.isPre = false
    recalcAttr(state.temp)
    state.dataChange = true
  }
}

// 重新计算宽度和定位
const recalcAttr = (temp) => {
  let point = temp.r_pointX
  let minX = 0
  let maxW = state.width.replace('px', '') * 1
  // 复选模式
  if (!state.repeat) {
    let item_index = state.weekList[state.activeTime[0]][state.props.timeList].findIndex((d) => {
      return d.temporary === true
    })
    // 最小不能小于前一个已有时段的最大值
    if (item_index > 0) {
      let last_item = state.weekList[state.activeTime[0]][state.props.timeList][item_index - 1]
      minX = (last_item.pointX.replace('px', '') * 1) + (last_item.width.replace('px', '') * 1)
    }
    // 最大不能大于后一个已有时段的最小值
    if (item_index < (state.weekList[state.activeTime[0]][state.props.timeList].length - 1)) {
      let next_item = state.weekList[state.activeTime[0]][state.props.timeList][item_index + 1]
      maxW = (next_item.pointX.replace('px', '') * 1)
    }
  }
  // 拖拽左滑块
  if (state.dageDom === 'start') {
    let width = temp.r_width * 1 + (temp.startX - temp.endX)
    point = point * 1 - (temp.startX - temp.endX)
    if (point < minX) {
      point = minX
      width = temp.width.replace('px', '')
    }
    if (temp.startX - temp.endX + temp.r_width * 1 < 0) {
      width *= -1
    } else {
      temp.pointX = point + 'px'
    }
    temp.width = width + 'px'
    // 拖拽右滑块
  } else if (state.dageDom === 'end') {
    let width = temp.r_width * 1 + (temp.endX - temp.startX)
    if (width < 0) {
      point = point * 1 + width
      if (point < minX) {
        point = minX
        width = temp.width.replace('px', '')
      }
      temp.pointX = point + 'px'
      temp.width = width * -1 + 'px'
    } else {
      if (point + width > maxW) {
        width = maxW - point
      }
      temp.width = width + 'px'
    }
    // 拖拽时段主体
  } else if (state.dageDom === 'content') {
    let width = temp.r_width
    point = point + (temp.endX - temp.startX)
    if (point < minX) {
      point = minX
    }
    if (point + width > maxW) {
      point = maxW - width
    }
    temp.pointX = point + 'px'
  }
  // 计算新时间
  recalcTime(state.temp)
}

// 重新计算时间
const recalcTime = (temp) => {
  let pointX = temp.pointX.replace('px', '') * 1
  let width = temp.width.replace('px', '') * 1
  let s_second = pointX / state.secondWidth
  let e_second = (pointX + width) / state.secondWidth
  temp[state.props.startTime] = formatMinute(s_second)
  temp[state.props.endTime] = formatMinute(e_second)
  temp.RangeTime = [temp[state.props.startTime], temp[state.props.endTime]]
}

// 秒数转
const formatMinute = (scNumber) => {
  if (state.timeLevel === 'mm') {
    let hour = Math.floor(scNumber / (60 * 60))
    let minute = Math.floor(scNumber % (60 * 60) / 60)
     // 判断是否是第24个小时
    if (hour == 24) {
      hour = 23
    }
    if (hour == 23 && minute == 0) {
      minute = 59
    }
    hour = hour >= 10 ? hour : '0' + hour
    minute = minute >= 10 ? minute : '0' + minute
    return hour + ':' + minute
  } else {
    let hour = Math.floor(scNumber / (60 * 60))
    let minute = Math.floor(scNumber % (60 * 60) / 60)
    let second = Math.floor(scNumber % (60 * 60) % 60)
    hour = hour >= 10 ? hour : '0' + hour
    minute = minute >= 10 ? minute : '0' + minute
    second = second >= 10 ? second : '0' + second
    return hour + ':' + minute + ':' + second
  }
}

// 关闭时间弹窗
const closeTimePopover = (temp) => {
  temp.popovisible = false
}

// 编辑保存操作
const saveTime = (temp) => {
  closeTimePopover(temp)
  // 计算时段宽度
  let st = formatSecond(temp[state.props.startTime])
  let et = formatSecond(temp[state.props.endTime])
  temp.width = (et - st) * state.secondWidth + 'px'
  // 计算时段定位
  temp.pointX = st * state.secondWidth + 'px'
  listenerChange()
}

// 关闭复制弹窗popover
const closePopover = (index) => {
  state.weekList.forEach((value, ind) => {
    if (ind === index) {
      value.popovisible = false
    }
  })
}

// 复制全选
const handleCheckAllChange = (val) => {
  state.copyList = val ? state.weekList.map((d, i) => { return i }): []
  state.isIndeterminate = false
}

// 复制选择回调
const handleCheckedCitiesChange = (value) => {
  let checkedCount = value.length
  state.checkAll = checkedCount === state.weekList.length
  state.isIndeterminate = checkedCount > 0 && checkedCount < state.weekList.length
}

// 确认复制
const confirmCopy = (index) => {
  // 复制时段
  state.copyList.forEach(ind => {
    state.weekList[ind][state.props.timeList] = deepClone(
      state.weekList[index][state.props.timeList]
    )
  })
  closePopover(index)
  listenerChange()
}

// 通过输入框改变时间
const timeChange = (value, time) => {
  time.RangeTime = value
  time[state.props.startTime] = value[0]
  time[state.props.endTime] = value[1]
}

// 复制
const actionCopy = (index) => {
  // 清空选中
  state.copyList = []
  state.weekList.forEach((value, ind) => {
    state.weekList[ind].disabled = false
    if (ind === index) {
      value.disabled = true
    }
  })
}

// 删除操作
const deleteTime = (index, ind) => {
  state.weekList[index][state.props.timeList].splice(ind, 1)
  listenerChange()
}

// 监听数据变动
const listenerChange = () => {
   emit('change', state.weekList)
   state.dataChange = false
}

// 预留按钮触发
const reservedfun = (time, ind) => {
  emit('reserved', {time: time, index: ind})
}

// 随机获取颜色
const randomRgb = () => {
  let R = Math.floor(Math.random() * 255)
  let G = Math.floor(Math.random() * 255)
  let B = Math.floor(Math.random() * 255)
  return 'rgb(' + R + ',' + G + ',' + B + ')'
}

onUpdated(() => {

})
const boxWidth = () => {
  return state.width.replace('px', '') * 1 + 122 + 'px'
}
onMounted(() => {
  state.boxwidths = boxWidth()
  window.addEventListener('mousemove', mousemoveNow)
  window.addEventListener('mouseup', mouseupEnd)
  // 获取1秒时间容器宽度
  state.secondWidth = state.width.replace('px', '') / (24 * 60 * 60)
  if (state.weekList) {
    initFormatData()
  }
})
</script>
<style  lang="scss" scoped>
.fr {
  float: right;
}
.week-plan-time-wrap {
  width: 100%;
  box-sizing: border-box;
  .tool-tips {
    padding: 10px 0;
    box-sizing: border-box;
    .circular{
      display: inline-block;
      width: 12px;
      height: 12px;
      vertical-align: middle;
      border-radius: 50%;
      background: #13C2C2;
      margin-top: -2px;
      margin-right: 8px;
    }
  }
  .scroll-box {
    width: 100%;
    overflow: auto;
    .week-plan-time {
      height: 280px;
      border: 1px solid #ddd;
      overflow: auto;
      .az-time-list {
        width: 100%;
        padding: 0 40px 0 80px;
        box-sizing: border-box;
        height: 28px;
        background: #eee;
        ul {
          display: flex;
          padding: 0;
          margin: 0;
          li {
            list-style-type: none;
            flex: 1;
            width: calc(100% / 24);
            height: 24px;
            font-size: 12px;
            line-height: 28px;
          }
        }
      }
      .az-week-list {
        width: 100%;
        .week-item {
          width: 100%;
          height: 35px;
          padding: 0 40px 0 80px;
          box-sizing: border-box;
          position: relative;
          &:hover {
            background: rgba(0, 0, 0, 0.05);
          }
          .label {
            box-sizing: border-box;
            position: absolute;
            left: 0;
            top: 0;
            width: 80px;
            height: 35px;
            line-height: 35px;
            text-align: center;
            border-right: 1px solid #ddd;
            user-select: none;
          }
          .time-swiper {
            width: 100%;
            height: 100%;
            box-sizing: border-box;
            display: flex;
            position: relative;
            cursor: pointer;
            .chunk {
              display: block;
              height: 100%;
              flex: 1;
              border-left: 1px solid #ddd;
              &:first-child {
                border: none;
              }
            }
            .slider-time {
              position: absolute;
              height: 100%;
              padding: 10px 0;
              box-sizing: border-box;
              z-index: 30;
              .content {
                width: 100%;
                height: 100%;
                background: #13c2c2;
                opacity: .8;
              }
              .arrows {
                position: absolute;
                height: 100%;
                size: 10px;
                color: #11a983;
                width: 14px;
                cursor: e-resize;
                .el-icon-postcard {
                  position: absolute;
                  top: 0;
                  left: 0;
                }
                .el-icon-caret-bottom {
                  position: absolute;
                  top: -2px;
                  left: 0;
                }
                .el-icon-caret-top {
                  position: absolute;
                  bottom: -5px;
                  left: 0;
                }
                &.left-arrows {
                  left: -7px;
                  top: 0;
                }
                &.right-arrows {
                  right: -7px;
                  top: 0;
                }
              }
            }
          }
          .copy-btn {
            position: absolute;
            top: 0;
            right: 0;
            width: 40px;
            height: 40px;
            line-height: 40px;
            background: #eee;
            text-align: center;
            font-size: 16px;
            cursor: pointer;
            &:hover {
              color: #13c2c2;
            }
          }
        }
      }
    }
    .footer-chunk {
      padding: 15px 0;
    }
  }
}
</style>

3. 工具文件index.ts:

/**
 * Check if an element has a class
 * @param {HTMLElement} elm
 * @param {string} cls
 * @returns {boolean}
 */
export function hasClass(ele: HTMLElement, cls: string) {
  return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
}

/**
* Add class to element
* @param {HTMLElement} elm
* @param {string} cls
*/
export function addClass(ele: HTMLElement, cls: string) {
  if (!hasClass(ele, cls)) ele.className += ' ' + cls
}

/**
* Remove class from element
* @param {HTMLElement} elm
* @param {string} cls
*/
export function removeClass(ele: HTMLElement, cls: string) {
  if (hasClass(ele, cls)) {
    const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
    ele.className = ele.className.replace(reg, ' ')
  }
}

export function mix(color1: string, color2: string, weight: number) {
  weight = Math.max(Math.min(Number(weight), 1), 0)
  let r1 = parseInt(color1.substring(1, 3), 16)
  let g1 = parseInt(color1.substring(3, 5), 16)
  let b1 = parseInt(color1.substring(5, 7), 16)
  let r2 = parseInt(color2.substring(1, 3), 16)
  let g2 = parseInt(color2.substring(3, 5), 16)
  let b2 = parseInt(color2.substring(5, 7), 16)
  let r = Math.round(r1 * (1 - weight) + r2 * weight)
  let g = Math.round(g1 * (1 - weight) + g2 * weight)
  let b = Math.round(b1 * (1 - weight) + b2 * weight)
  const rStr = ('0' + (r || 0).toString(16)).slice(-2)
  const gStr = ('0' + (g || 0).toString(16)).slice(-2)
  const bStr = ('0' + (b || 0).toString(16)).slice(-2)
  return '#' + rStr + gStr + bStr
}
export function deepClone(source: any) {
  let result
  let targetType = Object.prototype.toString.call(source).slice(8, -1)
  if (targetType === 'Object') {
    result = {}
  } else if (targetType === 'Array') {
    result = []
  } else {
    return source
  }
  for (let i in source) {
    let value = source[i]
    let valueType = Object.prototype.toString.call(value).slice(8, -1)
    if (valueType === 'Array' || valueType === 'Object') {
      result[i] = deepClone(value)
    } else {
      result[i] = value
    }
  }
  return result
}

4. 最终效果如图:

在这里插入图片描述
备注说明:
根据网上找到的资源是根据 vue2通过element-ui实现的,现在改成vue3+element-plus实现的。觉得不错的功能就分享出来。完!!!

Logo

前往低代码交流专区

更多推荐