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 = []
相关推荐
嵌入式小能手2 小时前
飞凌嵌入式ElfBoard-环境变量之添加修改环境变量setenv
服务器·前端·javascript
郭泽斌之心2 小时前
Live2D工程对接Fay数字人框架
前端·经验分享·fay数字人
前端搬砖人沐兮2 小时前
被忽视的宝藏:深入解读 createRangeFromPoint 的前世今生与实战技巧
前端
kyriewen2 小时前
手写 Promise:从“我会用”到“我会造”
前端·javascript·面试
Synmbrf2 小时前
基于micro-app的微前端落地实践
javascript·vue.js
wuhen_n2 小时前
案例分析:大屏可视化项目的卡顿排查与解决
前端·javascript·vue.js
比尔盖茨的大脑2 小时前
为了学习 AI Agent,我做了一个 AI 阅读器(已开源)
前端·人工智能
始持2 小时前
第十九讲 深度布局原理与优化
前端·flutter
二十一_2 小时前
LangChain 教程 03|快速开始:10 分钟创建第一个 Agent
前端·面试·langchain