vue组件二次封装

1.96点选择框

TypeScript 复制代码
<!-- TimeRangePicker.vue -->
<template>
    <div class="time-range-picker">
     <span>{{title}}&nbsp;</span>
        <!-- 输入框 -->
        <el-input ref="inputRef" v-model="displayValue" placeholder="请选择时间范围" readonly @click="togglePicker" clearable @clear="clearSelection" style="margin-right:16px" :style="{'width':width?width:'200px'}"/>
        <Teleport to="body">

               <!-- 弹出面板 -->
        <div v-if="showPicker" class="picker-panel" @click.stop :style="panelStyle">
            <!-- 时间网格 -->
            <div class="time-grid">
                <div v-for="(item, index) in currentOption" :key="index" class="time-cell" :class="{
            'start':item.start,
            'end':item.end,
            'in-range':item.inRange,
          }" @click="handleCellClick(item)" @mouseenter="handleCellEnter(item)">
                    {{ item.label }}
                </div>
            </div>
        </div>
        </Teleport> 
     

        <div v-if="showPicker" class="picker-mask" @click="closePicker">
        </div>
    </div>
</template>

<script setup name='TimeRangePicker'>
import { ref, computed, watch, onMounted } from 'vue';
import { point96_0 } from '@bdss-ui/bdss-core/utils/common'
const emit = defineEmits(['update:modelValue']);
const props = defineProps({
    modelValue: {
        type:Array,
    default:()=>[]
    },
    title:{
        type:String, 
        default:''
    },
    width:{
        type:[String,Number], 
    }
})
const inputRef=ref()
const showPicker = ref(false);
const startTime = ref(null);
const endTime = ref(null);
const hoverTime = ref(null);
const startIndex = ref(null)
const endIndex = ref(null)
const curEndIndex = ref(null)
const currentOption = ref(point96_0.map((i, inx) => {
    return {
        value: inx + 1,
        label: i,
        start: false,
        end: false,
        inRange: false
    }
}))
const panelStyle=ref({})
const togglePicker = () => {
    showPicker.value = !showPicker.value;
    if (showPicker.value) {
      
    const inputEl = inputRef.value.$el;
    inputRef.value.focus()
    const rect = inputEl.getBoundingClientRect();
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
    const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
    panelStyle.value = {
      position: 'fixed',
      top: `${rect.bottom + scrollTop + 10}px`,   
      left: `${rect.left + scrollLeft}px`,
      zIndex: 3000,
    };
        currentOption.value = point96_0.map((i, inx) => {
            return {
                value: inx + 1,
                label: i,
                start: false,
                end: false,
                inRange: false
            }
        })
        if (props.modelValue.length > 0) {
            startIndex.value = props.modelValue[0]
            endIndex.value = props.modelValue[1]
            currentOption.value[props.modelValue[0] - 1].start = true
            currentOption.value[props.modelValue[1] - 1].end = true
            for (let i = props.modelValue[0]; i < props.modelValue[1] - 1; i++) {
                currentOption.value[i].inRange = true
            }
        } else {
            startIndex.value = null
            endIndex.value = null
        }
    }
};

const displayValue = computed(() => {
    if (props.modelValue.length == 0) return '';
    let startT = currentOption.value.find(i => props.modelValue[0] == i.value).label
    let startE = currentOption.value.find(i => props.modelValue[1] == i.value).label
    return `${startT}~${startE}`;
})

const closePicker = () => {
    showPicker.value = false;
};

const handleCellClick = (item) => {
    if ((startIndex.value && endIndex.value) || !startIndex.value) {
        inputRef.value.focus()
        currentOption.value = point96_0.map((i, inx) => {
            return {
                value: inx + 1,
                label: i,
                start: false,
                end: false,
                inRange: false
            }
        })
        startIndex.value = item.value
        endIndex.value = null;
        currentOption.value[item.value - 1].start = true
    } else if (!endIndex.value) {
        endIndex.value = item.value
        currentOption.value[item.value - 1].end = true
        // props.modelValue = [startIndex.value, endIndex.value].slice().sort((a, b) => a - b)
        emit('update:modelValue',[startIndex.value, endIndex.value].slice().sort((a, b) => a - b))
        showPicker.value = false;
    }

};

const handleCellEnter = (item) => {
    if (startIndex.value && !endIndex.value) {
        currentOption.value = point96_0.map((i, inx) => {
            return {
                value: inx + 1,
                label: i,
                start: false,
                end: false,
                inRange: false
            }

        })
        currentOption.value[startIndex.value - 1].start = true
        currentOption.value[item.value - 1].end = true
        let arr = [startIndex.value, item.value].slice().sort((a, b) => a - b)
        for (let i = arr[0]; i < arr[1] - 1; i++) {
            currentOption.value[i].inRange = true
        }

    }
};

const clearSelection = () => {
    startTime.value = null;
    endTime.value = null;
};

onMounted(() => {

});

</script>

<style scoped>
.time-range-picker {
    position: relative;
    display:flex;
    align-items: center;
    width: auto;
}

.picker-panel {
    /* position: absolute;
    top: calc(100% + 10px);
    left: 0; */
    width: 680px;
    background: #fff;
    border: 1px solid #dcdfe6;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    padding: 12px;
    /* z-index: 3000; */
    font-size: 14px;
    pointer-events: none; 
}

.picker-mask {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    height: 100vh;
    width: 100vw;
    z-index: 1999;
    pointer-events: auto; /* 关键!允许事件穿透 */
}

.picker-mask-content {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: transparent;
    pointer-events: all; /* 允许点击 */
    z-index: 1999;
}

/* .picker-mask::before {
  content: '';
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.1);
  pointer-events: all;
} */

.month-row {
    display: flex;
    gap: 8px;
    margin-bottom: 12px;
    flex-wrap: wrap;
}

.month-item {
    padding: 6px 10px;
    border: 1px solid #ebeef5;
    border-radius: 4px;
    font-size: 12px;
    color: #606266;
    cursor: pointer;
    transition: all 0.2s;
}

.month-item:hover {
    background-color: #f5f7fa;
}

.month-item.active {
    background-color: #409eff;
    color: white;
    border-color: #409eff;
}

.time-grid {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    grid-template-rows: repeat(8, 1fr);
}

.time-cell {
    padding: 8px 0;
    margin: 5px 0;
    text-align: center;
    border-radius: 4px;
    font-size: 12px;
    cursor: pointer;
    pointer-events: auto; 
}

.time-cell:hover {
    color: var(--qctc-primary-color);
}

.time-cell.start,
.time-cell.end {
    background-color: #409eff;
    color: white;
}

.time-cell.in-range {
    /* background-color: #f2f6fc; */
    background-color: #edf5fa;
}

.time-cell.hovered {
    background-color: #f0f7ff;
    border-color: #409eff;
}
</style>
TypeScript 复制代码
 <QctcTimeRangePicker v-model:modelValue="dataForm.timepoints" width="254px" />

dataForm.timepoints = []
相关推荐
To_OC8 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
To_OC8 小时前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
vipbic8 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy8 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志9 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble10 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长10 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯11 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜11 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司12 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能