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 = []
相关推荐
kyriewen5 分钟前
我开发的 Chrome 扒图浏览器插件又更新了❗
前端·chrome·浏览器
程序员祥云16 分钟前
Prompt项目说明文档
前端
一勺菠萝丶17 分钟前
如何在 Linux 服务器上使用 Speedtest 官方 CLI 测试带宽(小白教程)
java·服务器·前端
DianSan_ERP20 分钟前
京东订单接口集成中如何处理消费者敏感信息的安全与合规问题?
前端·数据库·后端·团队开发·运维开发
TEC_INO23 分钟前
Linux50:ROCKX+RV1126视频流检测人脸
开发语言·前端·javascript
下载居33 分钟前
Node.js(Javascript运行环境) 26.1
开发语言·javascript·node.js
Dragon Wu42 分钟前
Taro v4.2.0 scss使用“@/xxx“的配置方法
前端·小程序·taro·scss
wordbaby1 小时前
如何封装一个生产级的 React Native 分页列表 Hook
前端·react native·react.js
小帅不太帅1 小时前
我做了两个工具,一个 7MB 的壳,一个会记住的壳
前端·app·产品
不瘦80斤不改名1 小时前
HTML基础(一)
开发语言·前端·html