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实现的。觉得不错的功能就分享出来。完!!!