vue3+原生javascript 手写日期时间选择框 滚动选择可输入

需求:

web端 想要跟手机端一样选择年月日时分,通过滚动选择

实现效果图:

理念:

1.年月日时分 分别为单个输入框,用来做输入修改

2.div把输入框拼接起来,显示出一个日期框的样子

3.年月日时分 下拉给默认list数据,

  • 年:给100个选项    20 - 现在年 - 80
  • 月:12个选项
  • 日:根据所选中年月来判断出多少个日期
  • 时:24
  • 分:60

4.打开日期框时,使用 appendChild(div) 动态插入年月日时分

5.获取到默认日期,给对应下拉数据 add('active') 加选中class名

6.注意一点:日期格式转换 月 日 时 分  小于10补0

7.当输入修改时,下面的年月日会滚动到修改后的日期上面,输入框 加 @input 事件

 8.当输入的值不在下拉列表中 默认第一个月 01,日01,时01,分01

9.添加滚动 事件 handleScroll

10.添加选中事件 handleClick

11.添加打开,关闭(取消/确定) 日期框事件

示例:默认值为当时间 (可使用组件传参方式给与默认值)

完整代码:

<template><div class="main-container"><div class="datetime-input" @click="togglePicker(true)"><span class="el-input__prefix"><span class="el-input__prefix-inner"><i class="el-icon el-input__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="currentColor"d="M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768m0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896"></path><path fill="currentColor"d="M480 256a32 32 0 0 1 32 32v256a32 32 0 0 1-64 0V288a32 32 0 0 1 32-32"></path><path fill="currentColor" d="M480 512h256q32 0 32 32t-32 32H480q-32 0-32-32t32-32"></path></svg></i></span></span><input class="no" style="min-width: 56px;width: 56px;" type="text" maxlength="4" placeholder="YYYY"v-model="selectedDate.year"@input="updateFromInputFields('year')"/><span class="separator">-</span><input class="no" style="min-width: 28px;width: 28px;" type="text" maxlength="2" placeholder="MM"v-model="selectedDate.month"@input="updateFromInputFields('month')"/><span class="separator">-</span><input class="no" style="min-width: 28px;width: 28px;" type="text" maxlength="2" placeholder="DD"v-model="selectedDate.day"@input="updateFromInputFields('day')"/><span style="width: 3px;"></span><input class="no" style="min-width: 28px;width: 28px;" type="text" maxlength="2" placeholder="HH"v-model="selectedDate.hour"@input="updateFromInputFields('hour')"/><span class="separator">:</span><input class="no" style="min-width: 28px;width: 28px;" type="text" maxlength="2" placeholder="MM"v-model="selectedDate.minute"@input="updateFromInputFields('minute')"/></div><div class="picker-container" ref="pickerContainerRef"><div class="wheel-container"><div class="wheel-group"><div class="wheel-label">年</div><div class="wheel" ref="yearsWheel" @scroll="handleScroll(yearsWheel,'year')" @click="handleClick(yearsWheel)"@wheel="handleWheel(yearsWheel)"></div></div><div class="wheel-group"><div class="wheel-label">月</div><div class="wheel" ref="monthWheel" @scroll="handleScroll(monthWheel,'month')"@click="handleClick(monthWheel)" @wheel="handleWheel(monthWheel)"></div></div><div class="wheel-group"><div class="wheel-label">日</div><div class="wheel" ref="dayWheel" @scroll="handleScroll(dayWheel,'')" @click="handleClick(dayWheel)"@wheel="handleWheel(dayWheel)"></div></div><div class="wheel-group" style="margin-left: 8px;"><div class="wheel-label">时</div><div class="wheel" ref="hourWheel" @scroll="handleScroll(hourWheel,'')" @click="handleClick(hourWheel)"@wheel="handleWheel(hourWheel)"></div></div><div class="wheel-group"><div class="wheel-label">分</div><div class="wheel" ref="minuteWheel" @scroll="handleScroll(minuteWheel,'')" @click="handleClick(minuteWheel)"@wheel="handleWheel(minuteWheel)"></div></div></div><div class="buttons"><button class="now-btn" @click="nowSelection">此刻</button><button class="cancel-btn" @click="cancelSelection">取消</button><button class="confirm-btn" @click="confirmSelection">确定</button></div></div></div>
</template>
<script setup lang="ts" name="index">
import {ref, onMounted, onActivated} from 'vue';const props = defineProps({localDateTimeNow: {type: String,default: ''}
});const emit = defineEmits(['timeUpdateClick']);const selectedDate = ref({day: new Date().getDate(),month: new Date().getMonth() + 1,year: new Date().getFullYear(),hour: new Date().getHours(),minute: new Date().getMinutes(),
});const years = Array.from({length: 100}, (_, i) => new Date().getFullYear() - 20 + i);
const months = Array.from({length: 12}, (_, i) => i + 1);
const hours = Array.from({length: 24}, (_, i) => i);
const minutes = Array.from({length: 60}, (_, i) => i);const yearsWheel = ref(null)
const monthWheel = ref(null)
const dayWheel = ref(null)
const hourWheel = ref(null)
const minuteWheel = ref(null)
const pickerContainerRef = ref(null)
const dayFather = ref(0)
const dayInputNum = ref(0)const getDaysInMonth = (year: number, month: number) => {const days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];if ((year % 4 === 0) && (year % 100 !== 0 || year % 400 === 0)) {days[1] = 29;}return days[month - 1];
};const generateDaysArray = (year: number, month: number) => {const daysInMonth = getDaysInMonth(year, month);return Array.from({length: daysInMonth}, (_, i) => i + 1);
};const initWheel = (wheelElement, data, initialValue, addPlaceholders = false) => {wheelElement.innerHTML = '';data.forEach(item => {const div = document.createElement('div');div.textContent = item < 10 ? '0' + item : item;div.className = 'divItem';div.dataset.value = item.toString();wheelElement.appendChild(div);});if (addPlaceholders) {for (let i = 0; i < 4; i++) {const div = document.createElement('div');div.textContent = '';div.classList.add('placeholder');wheelElement.appendChild(div);}}const itemHeight = 21;const initialIndex = data.indexOf(Number(initialValue));nextTick(() => {wheelElement.scrollTop = initialIndex * itemHeightupdateActiveItem(wheelElement);wheelElement.addEventListener('scroll', () => {updateActiveItem(wheelElement);updateSelectedDate();updateInputFields();});wheelElement.addEventListener('click', (e: MouseEvent) => {if (e.target instanceof HTMLElement && e.target.tagName === 'DIV' && !e.target.classList.contains('placeholder')) {const index = Array.from(wheelElement.children).indexOf(e.target);wheelElement.scrollTop = index * itemHeight;}});wheelElement.addEventListener('wheel', (e: WheelEvent) => {e.preventDefault();const delta = Math.sign(e.deltaY) * 3;const currentScroll = wheelElement.scrollTop;const newScroll = Math.max(0, Math.min(currentScroll + delta * itemHeight, (data.length - 1) * itemHeight));wheelElement.scrollTop = newScroll;});})};const updateActiveItem = (wheelElement: HTMLElement) => {const itemHeight = 21;const selectedIndex = Math.round(wheelElement.scrollTop / itemHeight);const items = wheelElement.querySelectorAll('div');items.forEach(item => item.classList.remove('active'));let activeIndex = selectedIndex;while (activeIndex >= 0 && items[activeIndex] && items[activeIndex].classList.contains('placeholder')) {activeIndex--;}if (activeIndex >= 0 && items[activeIndex]) {items[activeIndex].classList.add('active');}
};const updateSelectedDate = () => {const activeYear = yearsWheel.value.querySelector('.active')const activeMonth = monthWheel.value.querySelector('.active')const activeDay = dayWheel.value.querySelector('.active')const activeHour = hourWheel.value.querySelector('.active')const activeMinute = minuteWheel.value.querySelector('.active')if (activeDay && activeMonth && activeYear && activeHour && activeMinute) {selectedDate.value = {year: parseInt(activeYear.dataset.value),month: parseInt(activeMonth.dataset.value),day: parseInt(activeDay.dataset.value),hour: parseInt(activeHour.dataset.value),minute: parseInt(activeMinute.dataset.value),};validateDate();}
};const validateDate = () => {if (dayInputNum.value == 1) {selectedDate.value.day = dayFather.value}const lastDayOfMonth = getDaysInMonth(Number(selectedDate.value.year), selectedDate.value.month);if (selectedDate.value.day > lastDayOfMonth) {selectedDate.value.day = lastDayOfMonth;const days = generateDaysArray(Number(selectedDate.value.year), selectedDate.value.month);initWheel(dayWheel.value, days, selectedDate.value.day, true);}
};const updateInputFields = () => {selectedDate.value.year = selectedDate.value.yearselectedDate.value.month = Number(selectedDate.value.month) < 10 ? '0' + Number(selectedDate.value.month) : selectedDate.value.monthselectedDate.value.day = Number(selectedDate.value.day) < 10 ? '0' + Number(selectedDate.value.day) : selectedDate.value.dayselectedDate.value.hour = Number(selectedDate.value.hour) < 10 ? '0' + Number(selectedDate.value.hour) : selectedDate.value.hourselectedDate.value.minute = Number(selectedDate.value.minute) < 10 ? '0' + Number(selectedDate.value.minute) : selectedDate.value.minute
};const updateFromInputFields = (type) => {if (type == 'year' && selectedDate.value[type].length == 4 || selectedDate.value[type].length == 2) {const year = selectedDate.value.year;const month = selectedDate.value.month;const day = selectedDate.value.day;const hour = selectedDate.value.hour;const minute = selectedDate.value.minute;const lastDayOfMonth = getDaysInMonth(year, month) || 1;const validDay = Math.min(day, lastDayOfMonth);selectedDate.value = {year,month: Math.max(1, Math.min(12, month)),day: Math.max(1, Math.min(lastDayOfMonth, validDay)),hour: Math.max(0, Math.min(23, hour)),minute: Math.max(0, Math.min(59, minute)),};initAllWheels();updateInputFields();}
};const togglePicker = (show) => {if (pickerContainerRef.value) {pickerContainerRef.value.style.display = show ? 'block' : 'none';if (show) {initAllWheels();} else {emit('timeUpdateClick', selectedDate.value)}if (show) {dayInputNum.value++}}
};const initAllWheels = () => {initWheel(yearsWheel.value, years, selectedDate.value.year)initWheel(monthWheel.value, months, selectedDate.value.month, true);const days = generateDaysArray(selectedDate.value.year, selectedDate.value.month);const lastDayOfMonth = getDaysInMonth(selectedDate.value.year, selectedDate.value.month);if (selectedDate.value.day > lastDayOfMonth) {selectedDate.value.day = lastDayOfMonth;}initWheel(dayWheel.value, days, selectedDate.value.day, true);initWheel(hourWheel.value, hours, selectedDate.value.hour, true);initWheel(minuteWheel.value, minutes, selectedDate.value.minute, true);updateInputFields();
};const updateDayWheelOnMonthOrYearChange = () => {const activeYear = yearsWheel.value.querySelector('.active')const activeMonth = monthWheel.value.querySelector('.active')if (activeYear && activeMonth) {const year = parseInt(activeYear.dataset.value);const month = parseInt(activeMonth.dataset.value);const days = generateDaysArray(year, month);if (Number(selectedDate.value.day) > days[days.length - 1]) {selectedDate.value.day = days[days.length - 1];}initWheel(dayWheel.value, days, selectedDate.value.day, true);updateInputFields();}
};const handleScroll = (wheelElement, type: string) => {updateActiveItem(wheelElement);updateSelectedDate();if (type === 'year' || type === 'month') {updateDayWheelOnMonthOrYearChange();}
};const handleClick = (wheelElement) => {dayInputNum.value++const index = Array.from(wheelElement.children).findIndex(item => item.className == 'active');wheelElement.scrollTop = index * 21;
};const handleWheel = (wheelElement) => {const delta = Math.sign(event.deltaY) * 3;const currentScroll = wheelElement.scrollTop;const itemHeight = 21;const newScroll = Math.max(0, Math.min(currentScroll + delta * itemHeight, (wheelElement.children.length - 1) * itemHeight));wheelElement.scrollTop = newScroll;
};const confirmSelection = () => {togglePicker(false);
};const nowSelection = () => {const now = new Date();selectedDate.value = {day: now.getDate(),month: now.getMonth() + 1,year: now.getFullYear(),hour: now.getHours(),minute: now.getMinutes(),};initAllWheels();
};const cancelSelection = () => {const now = new Date();selectedDate.value = {day: now.getDate(),month: now.getMonth() + 1,year: now.getFullYear(),hour: now.getHours(),minute: now.getMinutes(),};initAllWheels();togglePicker(false);
};
// 点击页面其他地方关闭选择器document.addEventListener('click', (e) => {if (e.target && e.target.className != 'no' && e.target.className != 'datetime-input' && e.target.className != 'separator' && e.target.className != 'cancel-btn' && e.target.className != 'picker-container' && e.target.className != 'wheel-container' && e.target.className != 'wheel-group' && e.target.className != 'wheel' && e.target.className != 'buttons' && e.target.className != 'confirm-btn' && e.target.className != 'cancel-btn' && e.target.className != 'now-btn' && e.target.className != 'wheel-label' && e.target.className != 'active' && e.target.className != 'divItem') {togglePicker(false);}
})
// 暴露变量
defineExpose({timeupdate
});
onMounted(() => {initAllWheels();
});
</script>
<style scoped>
.main-container {width: 300px;position: relative;border: 1px solid #dcdfe6;margin-top: 1px;margin-bottom: 1px;
}.datetime-input {height: 30px;line-height: 30px;display: flex;align-items: center;margin: 0 10px;
}.datetime-input input {/*width: 100%;*//*text-align: center;*/
}.datetime-input .separator {margin: 0 0 2px 0;color: #999;
}.picker-container {display: none;position: absolute;top: calc(100% + 2px);left: 0;width: 100%;background: white;border-radius: 0 0 5px 5px;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);padding: 10px 0 5px;z-index: 1000;border: 1px solid #ddd;border-top: none;}.wheel-container {display: flex;overflow-x: auto;padding: 5px 0;scrollbar-width: none;justify-content: center;}.wheel-container::-webkit-scrollbar {display: none;
}.wheel-group {display: flex;flex-direction: column;align-items: center;margin: 0 2px;min-width: 40px;
}.wheel-label {font-size: 11px;color: #666;margin-bottom: 3px;height: 15px;line-height: 15px;
}.wheel {height: 110px;width: 40px;overflow: hidden;text-align: center;border: 1px solid #eee;border-radius: 3px;background: #fff;scroll-behavior: smooth;
}.wheel div {height: 25px;line-height: 25px;cursor: pointer;transition: all 0.2s;font-size: 13px;
}.wheel div:hover {background: #f5f5f5;
}:deep(.wheel div.active) {background: #4285f4;color: white;font-weight: bold;
}:deep(.placeholder) {height: 21px;line-height: 21px;
}:deep(.divItem) {font-size: 16px;height: 21px;line-height: 21px;
}:deep(.wheel div.placeholder) {color: transparent;pointer-events: none;
}.buttons {display: flex;justify-content: flex-end;
}button {height: 30px;line-height: 30px;width: 50px;border: none;border-radius: 3px;cursor: pointer;font-size: 12px;margin-left: 6px;
}.cancel-btn {background: #f0f0f0;color: #333;
}.confirm-btn {background: #4285f4;color: white;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/89632.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/89632.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类

最近由于业务需求&#xff0c;接触到了Jetson边缘AI计算主板&#xff0c;博主使用的是Jetson Orin NX 16GB这个版本&#xff0c;可以看到其算力达到了100TOPS&#xff0c;这是一个非常恐怖的算力了&#xff0c;接下来便是博主对其的环境配置过程&#xff0c;博主要在该主板上运…

CLIP模型实现中的其他细节

之前已经完整的拆解了CLIP中所用到的ResNet、ViT和Transformer三个模型&#xff08;CLIP拆解-CSDN博客&#xff09;&#xff0c;这篇将讲解model.py实现中的其他细节。 1.关于ResNet模型中vision_head的设置 ResNet: vision_heads vision_width * 32 // 64 ViT: vision_h…

国科大深度学习作业1-手写数字识别实验

背景介绍&#xff1a;单位实习&#xff0c;趁机摸鱼&#xff0c;由于电脑只安装了VSCode&#xff0c;所以算是从环境搭建写起。 目录 一、环境搭建 1. 安装Anaconda 2. 创建Python环境 3. 安装PyTorch 4. 安装其他必要库 二、在 VSCode 中配置环境 1. 安装Pytho…

基于Spring Boot的绿园社区团购系统的设计与实现

第1章 摘 要 本设计与实现的基于Spring Boot的绿园社区团购系统&#xff0c;旨在为社区居民提供一套高效、便捷的团购购物解决方案。随着电子商务的发展和社区居民对便捷购物需求的增加&#xff0c;传统的团购模式已无法满足用户的个性化需求。本系统通过整合现代化技术&…

【51单片机四位数码管从0循环显示到99,每0.5秒增加一个数字,打击键计数】2022-6-11

缘由 #include "REG52.h" unsigned char code smgduan[]{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0,64,15,56}; //共阴0~F消隐减号 unsigned char Js0, miao0;//中断计时 秒 分 时 毫秒 unsigned int shu0; //bit Mb0;//…

如何通过python脚本向redis和mongoDB传点位数据

向MongoDB传数据 from pymongo import MongoClient #导入库对应的库localhost "172.16.0.203" #数据库IP地址 baseName "GreenNagoya" client MongoClient(localhost, 27017, username"admin", password"zdiai123") #数…

昆仑通泰触摸屏Modbus TCP服务器工程 || TCP客户端工程

目录 一、Modbus TCP服务端 1.设备地址 2.实操及数据 二、Modbus TCP客户端 1.结果及协议解析 一、Modbus TCP服务端 1.设备地址 --单元标识符 DI输入/4个离散输入 DO输出/单个线圈输出 输入寄存器 读输入寄存器操作&#xff0c;写输入寄存器操作 保持寄存器 …

PyTorch 安装使用教程

一、PyTorch 简介 PyTorch 是由 Facebook AI Research 团队开发的开源深度学习框架。它以动态图机制、灵活性强、易于调试而著称&#xff0c;广泛应用于自然语言处理、计算机视觉和学术研究。 二、安装 PyTorch 2.1 通过官网选择安装命令&#xff08;推荐&#xff09; 访问官…

开源功能开关(feature flags) 和管理平台之unleash

文章目录 背景Flagsmith 和 Unleash什么是unleash架构Unleash Edge 安装和使用Unleash SDKs开放API Tokens访问**Server-side SDK (CLIENT)****查询所有 Feature Toggles****查询特定 Toggle** API token typesClient tokensFrontend tokensPersonal access tokensService acco…

细胞建模“图灵测试”:解析学习虚拟细胞挑战赛

一、AI能否预测细胞的未来&#xff1f; 想象一下&#xff0c;有一天我们不必一管管地做实验&#xff0c;就能在计算机中模拟细胞对基因敲除、药物处理乃至微环境变化的反应。这不再是科幻&#xff0c;而是“虚拟细胞”&#xff08;Virtual Cell&#xff09;研究的宏大目标。然…

centos9安装docker Dify

CentOS | Docker Docs yum -y install gcc gcc-c yum-utils Docker 官方的 YUM 软件仓库配置文件到系统,设置存储库 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 也可以从阿里云下(我选择上面的) yum-config-manager --add-re…

基于Jenkins和Kubernetes构建DevOps自动化运维管理平台

目录 引言 基础概念 DevOps概述 Jenkins简介 Kubernetes简介 Jenkins与Kubernetes的关系 Jenkins与Kubernetes的集成 集成架构 安装和配置 安装Jenkins 安装Kubernetes插件 配置Kubernetes连接 配置Jenkins Agent Jenkins Pipeline与Kubernetes集成 Pipeline定义…

MySQL 8.0 OCP 1Z0-908 题目解析(18)

题目69 Choose three. A MySQL server is monitored using MySQL Enterprise Monitor’s agentless installation. Which three features are available with this installation method? □ A) MySQL Replication monitoring □ B) security-related advisor warnings □ …

【mongodb】安装和使用mongod

文章目录 前言一、如何安装&#xff1f;二、使用步骤1. 开启mongod服务2. 客户端连接数据库3. 数据库指令 总结 前言 Mongodb的安装可以直接安装系统默认的版本&#xff0c;也可以安装官网维护的版本&#xff0c;相对而言更推荐安装官网维护的版本&#xff0c;版本也相当更新。…

云效DevOps vs Gitee vs 自建GitLab的技术选型

针对「云效DevOps vs Gitee vs 自建GitLab」的技术选型&#xff0c;我们从核心需求、成本、运维、扩展性四个维度进行深度对比&#xff0c;并给出场景化决策建议&#xff1a; 一、核心能力对比表 能力维度云效DevOpsGitee自建GitLab&#xff08;社区版/企业版&#xff09;代码…

CentOS 7 安装RabbitMQ详细教程

前言&#xff1a;在分布式系统架构中&#xff0c;消息队列作为数据流转的 “高速公路”&#xff0c;是微服务架构不可或缺的核心组件。RabbitMQ 凭借其稳定的性能、灵活的路由机制和强大的生态支持&#xff0c;成为企业级消息中间件的首选之一。不过&#xff0c;当我们聚焦 Cen…

Python爬虫用途和介绍

目录 什么是Python爬虫 Python爬虫用途 Python爬虫可以获得那些数据 Python爬虫的用途 反爬是什么 常见的反爬措施 Python爬虫技术模块总结 获取网站的原始响应数据 获取到响应数据对响应数据进行过滤 对收集好的数据进行存储 抵御反爬机制 Python爬虫框架 Python…

uni-app开发app保持登录状态

在 uni-app 中实现用户登录一次后在 token 过期前一直免登录的功能&#xff0c;可以通过以下几个关键步骤实现&#xff1a;本地持久化存储 Token、使用请求与响应拦截器自动处理 Token 刷新、以及在 App.vue 中结合 pages.json 设置登录状态跳转逻辑。 ✅ 一、pages.json 配置说…

21、MQ常见问题梳理

目录 ⼀ 、MQ如何保证消息不丢失 1 、哪些环节可能会丢消息 2 、⽣产者发送消息如何保证不丢失 2.1、⽣产者发送消息确认机制 2.2、Rocket MQ的事务消息机制 2.3 、Broker写⼊数据如何保证不丢失 2.3.1** ⾸先需要理解操作系统是如何把消息写⼊到磁盘的**。 2.3.2然后来…

MySQL数据库--SQL DDL语句

SQL--DDL语句 1&#xff0c;DDL-数据库操作2&#xff0c;DDL-表操作-查询3&#xff0c;DDL-表操作-创建4&#xff0c;DDL-表操作-数据类型4.1&#xff0c;DDL-表操作-数值类型4.2&#xff0c;DDL-表操作-字符串类型4.3&#xff0c;DDL-表操作-日期时间类型4.4&#xff0c;实例 …