赞
踩
#vue3 Calendar组件 vue3日历组件 vite + vue3
https://v3.cn.vuejs.org/guide/introduction.html
https://youzan.github.io/vant/#/zh-CN/home
https://vitejs.cn/
import { createApp } from 'vue'
import { Button, Icon, Overlay, Picker } from 'vant'
createApp(App)
.use(Button)
.use(Icon)
.use(Overlay)
.use(Picker)
.mount('#app')
// vite.config.js
import { defineConfig } from 'vite'
import styleImport, { VantResolve } from 'vite-plugin-style-import'
export default defineConfig({
plugins: [
styleImport({ resolves: [VantResolve()] })
]
})
/** "vant": "^3.4.7", "vue": "^3.2.25", "sass": "^1.49.9", "vite": "^2.8.0", "vite-plugin-style-import": "1.4.1", "autoprefixer": "^10.4.4", "postcss-pxtorem": "^6.0.0" */ // 这里用了 postcss-pxtorem 对px进行rem转换,如果您没有用 postcss-pxtorem,还得需要您自己修改样式 /* * postcss.config.js 'postcss-pxtorem': { rootValue: 37.5, propList: ['*'], unitPrecision: 5 } */
<template> <section id="Calendar" v-cloak> <van-overlay :show="arise" @click="handleClose"> <div class="calendar-view"> <div class="container" @click.stop> <div class="display-flex align-items-center justify-content-space-between"> <div class="calendar-top-l display-flex align-items-center" @click.stop="handlePreNextY(-1)"> <van-icon class="calendar-top-left mr10" name="arrow-left" /> <div class="m">{{ MonthChineseAndEnglish[month - 1].en_ab }}</div> <div class="y">{{ year }}</div> <van-icon class="calendar-top-right ml10" name="arrow" @click.stop="handlePreNextY(1)"></van-icon> </div> <div class="calendar-top-r"> <van-icon class="calendar-top-left mr20" name="arrow-left" @click.stop="handlePreNextM(-1)" /> <van-icon class="calendar-top-right" name="arrow" @click.stop="handlePreNextM(1)" /> </div> </div> <div> <ul class="week"><li v-for="i of Week" :key="i" class="week-op">{{ i }}</li></ul> </div> <div class="date-g-v"> <ul class="date-grid"> <li v-for="(i, index) of currentCalendarFullDays" :key="i.key" class="date-grid-list" @click.stop="handleSelymd(i, index)" :class="{ 'selBg': i.active }" > <p class="date-view" :class="{ 'date-day-current': (i.where === 'current' && year === nDate.getFullYear() && month === nDate.getMonth() + 1 && Number(i.day) === nDate.getDate()) || i.active, 'preDay': i.where === 'pre', 'nextDay': i.where === 'next' }"> {{ i.zh_ch }} <span class="date-view-ab" :class="{ 'date-day-current': (i.where === 'current' && year === nDate.getFullYear() && month === nDate.getMonth() + 1 && Number(i.day) === nDate.getDate()) || i.active, 'preDay': i.where === 'pre', 'nextDay': i.where === 'next' }">{{ i.en_day_ab }}</span></p> </li> </ul> </div> <div class="time"> <div><p class="time-title">Time</p></div> <div class="w-time-picker"> <van-picker @change="handlePicker" :show-toolbar="false" :item-height="wWidth ? '50px' : '26px' " :columns="columns" /> </div> </div> <div class="time-confirm" @click.stop="handleConfirm"> <van-button type="warning" block>Confirm</van-button> </div> </div> </div> </van-overlay> </section> </template>
<script setup> import { defineEmits, defineProps, nextTick, reactive, ref, toRefs, watch } from 'vue' const props = defineProps({ arise: { type: Boolean, default: false }, ymdhms: { type: Object, default: { // ymd: 2022/04/17 // hms: 00:00:00 } } }) const emits = defineEmits(['update:arise', 'giveMe']) const date = props.ymdhms.ymd ? ref(new Date(props.ymdhms.ymd)) : ref(new Date()) // 传入时间 || 当前时间 const nDate = new Date() const ymd = reactive({ year: date.value.getFullYear(), // 年 month: date.value.getMonth() + 1, // 月 day: date.value.getDate() // 日 }) const wWidth = window.screen.width >= 768 const Week = reactive(['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT']) // 星期 (周日 - 周六) const MonthChineseAndEnglish = ref([ // 中英文月份 { zh_ch: '一月', en_us: 'January', en_ab: 'Jan.', str_num: '01' }, { zh_ch: '二月', en_us: 'February', en_ab: 'Feb.', str_num: '02' }, { zh_ch: '三月', en_us: 'March', en_ab: 'Mar.', str_num: '03' }, { zh_ch: '四月', en_us: 'April', en_ab: 'Apr.', str_num: '04' }, { zh_ch: '五月', en_us: 'May', en_ab: 'May.', str_num: '05' }, { zh_ch: '六月', en_us: 'June', en_ab: 'Jun.', str_num: '06' }, { zh_ch: '七月', en_us: 'July', en_ab: 'Jul.', str_num: '07' }, { zh_ch: '八月', en_us: 'August', en_ab: 'Aug.', str_num: '08' }, { zh_ch: '九月', en_us: 'September', en_ab: 'Sept.', str_num: '09' }, { zh_ch: '十月', en_us: 'October', en_ab: 'Oct.', str_num: '10' }, { zh_ch: '十一月', en_us: 'November', en_ab: 'Nov.', str_num: '11' }, { zh_ch: '十二月', en_us: 'December', en_ab: 'Dec.', str_num: '12' } ]) const monthList = ref([31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]) // 1 3 5 7 8 10 12 (31) 4 6 9 11 (30) 2 (28, 29) const monthSize = ref(0) // 天数 const firstDay = ref(null) // 当月第一天是星期几 const lastDay = ref(null) // 当月最后一天是星期几 const preDay = ref(0) // 当月一号前面剩余天数 const nextDay = ref(0) // 当月一号前面剩余天数 const columns = reactive([ // 选择时间 { values: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'], defaultIndex: 9 }, { values: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59'], defaultIndex: 9 }, { values: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59'], defaultIndex: 9 } ]) const currentCalendarFullDays = ref([]) // 日期list const throwToYou = reactive({ date: { yr: '', m: '', d: '', hr: '', min: '', s: '' } }) // 传递给父组件 /** * oopsIsALeapYear * 公历闰年判定遵循的规律为:四年一闰、百年不闰、400年再闰。 * 公历闰年的精确计算方法:普通年能被四整除且不能被100整除的为闰年。 * 世纪年能被400整除的是闰年,如2000年是闰年,1900年不是闰年。 * 对于数值很大的年份,如果这年能整除3200并且能整除172800则是闰年。 * return @type { Boolean } true 是闰年 */ function oopsIsALeapYear(y) { return (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0 } if (oopsIsALeapYear(ymd.year)) { // 是闰年 monthList.value[1] = 29 // 二月是29天 } function handleLastMonthSize () { monthSize.value = monthList.value[ymd.month - 1] // 当月天数 firstDay.value = new Date(`${ ymd.year }/${ ymd.month }/01`).getDay() // 当月第一天是星期几 preDay.value = firstDay.value === 7 ? 0 : firstDay.value // 当月1号前面上月剩余天数 return ymd.month - 1 === 0 ? 31 : monthList.value[ymd.month - 2] // 上月天数大小(这个月是1月的话上个月就是12月共有31天) } function handleNextMonthSize() { monthSize.value = monthList.value[ymd.month - 1] // 当月天数 lastDay.value = new Date(`${ ymd.year }/${ ymd.month }/${ monthSize.value }`).getDay() // 当月最后一天是星期几 nextDay.value = lastDay === 7 ? 6 : 6 - lastDay.value // 下月天数, 如果最后一天是星期天则下月天数为6 } /** * 英文日期缩写 * 1st 21st 31st * 2nd 22nd * 3rd 23rd * 4th 5th 6th 7th 8th 9th 10th 11th * 12th 13th 14th 15th 16th 17th 18th 19th 20th * 24th 25th 26th 27th 28th 29th 30th */ function handleDayAb(_day) { let day = String(_day), rDay = '' if (day === '01' || day === '21' || day === '31') { rDay = `st` } else if (day === '02' || day === '22') { rDay = `nd` } else if (day === '03' || day === '23') { rDay = `rd` } else { rDay = `th` } return rDay } function funcIsBA(where) { let yr = Number(ymd.year), m = Number(ymd.month) if (where === 'pre') { m -= 1 if (m <= 0) { m = 12 yr -= 1 } } else if (where === 'next') { m += 1 if (m > 12) { m = 1 yr += 1 } } return { yr, m } } function funcOrganizeDates(_Day, key, where) { // 处理日期 let arr = [] const pymd = throwToYou.date.yr ? `${ throwToYou.date.yr }/${ throwToYou.date.m }/${ throwToYou.date.d }` : props.ymdhms.ymd const isYmd = !!pymd const pymdArr = isYmd ? pymd.split('/') : [] for (let i = 0; i < Number(_Day); i++) { let day let day_len let act if (where === 'pre') { day = (Number(handleLastMonthSize()) - Number(_Day) + Number(i + 1)) } else { day = i + 1 } if (isYmd) { // 判断是否有传入的年月日设置选中状态 const { yr, m } = funcIsBA(where) if (Number(pymdArr[0]) === Number(yr) && Number(pymdArr[1]) === Number(m) && where === 'pre' && Number(pymdArr[2]) === Number(day)) { act = true } else if (Number(pymdArr[0]) === Number(yr) && Number(pymdArr[1]) === Number(m) && where === 'next' && Number(pymdArr[2]) === Number(day)) { act = true } else { act = Number(pymdArr[0]) === Number(ymd.year) && Number(pymdArr[1]) === Number(ymd.month) && Number(pymdArr[2]) === Number(day) && where === 'current' } } else { act = false } day_len = day.toString().length >= 2 ? day : `0${day}` arr.push({ zh_ch: day_len, en_us: day_len + handleDayAb(day_len), en_day_ab: handleDayAb(day_len), key: day + key, where, day, active: act }) } return arr } function funcDays() { // 整合 所有日期 currentCalendarFullDays.value = [] currentCalendarFullDays.value = [ ...currentCalendarFullDays.value, ...funcOrganizeDates(preDay.value, 'pre_day', 'pre'), // preDay 当月前面日期 ...funcOrganizeDates(monthSize.value, 'current_day', 'current'), // monthSize 当月日期 ...funcOrganizeDates(nextDay.value, 'next_day', 'next') // nextDay 当月之后日期 ] } function init() { // 初始化 传递给父组件的值 if (!throwToYou.date.yr) { const MapYmd = new Map().set(0, 'yr').set(1, 'm').set(2, 'd') const MapHms = new Map().set(0, 'hr').set(1, 'min').set(2, 's') if (props.ymdhms.ymd) { // 是否传递了 ymdhms ymd let arrPYmd = props.ymdhms.ymd.split('/') for(let [key, value] of MapYmd.entries()){ throwToYou.date[value] = arrPYmd[key] } } if (props.ymdhms.hms) { // 是否传递了 ymdhms hms let arrPHms = props.ymdhms.hms.split(':') for (var i = 0; i < 3; i++) { // 初始化时间展示 columns[i]['defaultIndex'] = columns[i].values.findIndex(i1 => i1 === arrPHms[i]) } for(let [key, value] of MapHms.entries()){ // 初始化返回传入的时间 throwToYou.date[value] = arrPHms[key] } } else { for(let [key, value] of MapHms.entries()){ // 没有传递 设置成 09:09:09 throwToYou.date[value] = columns[key]['values'][9] } } } handleLastMonthSize() handleNextMonthSize() nextTick(() => { funcDays() }) } init() function handleClose() { // 弹框关闭 emits('update:arise', false) } function handleConfirm() { // 确认选择 // console.log(throwToYou.date) emits('giveMe', throwToYou.date) handleClose() } function handlePreNextY(num) { // 年切换 ymd.year += num nextTick(() => { date.value = new Date(`${ymd.year}/${ymd.month}/${ymd.day}`) }) } function handlePreNextM(num) { // 月份切换 let m = ymd.month += num if (m <= 0) { ymd.month = 12 ymd.year += num } if (m > 12) { ymd.month = 1 ymd.year += num } nextTick(() => { date.value = new Date(`${ymd.year}/${ymd.month}/${ymd.day}`) }) } function handleSelymd(i, index) { let arr = currentCalendarFullDays.value currentCalendarFullDays.value = arr.map(item => { item.active = false return item }) currentCalendarFullDays.value[index].active = true // date: { // yr: '', // m: '', // d: '', // hr: '', // min: '', // s: '' // } let d = i.zh_ch const { yr, m } = funcIsBA(i.where) const m1 = m.toString().length >= 2 ? m : `0${m}` throwToYou.date = { ...throwToYou.date, ...{ yr: String(yr), m: String(m1), d: String(d) } } } function handlePicker(val, index) { const MapHms = new Map().set(0, 'hr').set(1, 'min').set(2, 's') for(let [key, value] of MapHms.entries()){ throwToYou.date[value] = val[key] } } watch([() => ymd.year, () => ymd.month], (val) => { init() }) const { year, month, day } = toRefs(ymd) </script>
<style lang="scss" scoped> #Calendar { $mainColor: rgba(255, 0, 0, 1); $mainLightColor: rgba(255, 0, 0, 0.1); $mainTextColor: #000000; $subtextColor: #C0C0C0; $btnTextColor1: #A9A9A9; $DefFFFFFF: #ffffff; [v-cloak] { display: none; } :deep(.van-overlay) { z-index: 2015; background: rgba(0, 0, 0, .5); } .calendar-view { width: 100%; height: 100%; display: flex; } .container { margin: auto; width: 343px; /* Background/Primary */ background: $DefFFFFFF; border-radius: 12px; padding: 12px; box-sizing: border-box; .calendar-top-l { .ml10 { margin-left: 10px; } .mr10 { margin-right: 10px; } .calendar-top-left { color: $mainColor; font-size: 20px; } .calendar-top-right { @extend .calendar-top-left; } .m { line-height: 22px; font-weight: 600; font-size: 18px; } .y { @extend .m; margin-left: 10px; } } .calendar-top-r { @extend .calendar-top-l; .mr20 { margin-right: 20px; } } .week { display: flex; align-items: center; margin-top: 16px; .week-op { width: calc(100% / 7); font-weight: 600; font-size: 12px; line-height: 16px; color: #878787; text-align: center; } } .date-g-v { margin-top: 12px; } .date-grid { display: flex; align-items: center; flex-wrap: wrap; width: 100%; height: 273.38px; .date-grid-list { width: calc(100% / 7); text-align: center; position: relative; padding-bottom: 14.285%; border-radius: 100%; overflow: hidden; .date-view { width: 100%; position: absolute; left: 0; top: calc(50% - 11px); font-weight: 400; font-size: 18px; line-height: 22px; color: $mainTextColor; .date-view-ab { font-size: 10px; color: $subtextColor; position: absolute; top: -3px; right: 0; } } .preDay { color: $btnTextColor1 !important; } .nextDay { @extend .preDay; } .date-day-current { color: $mainColor !important; } } .selBg { background: $mainLightColor; } } .time { margin-top: 12px; .time-title { font-weight: 600; font-size: 18px; line-height: 22px; color: $mainTextColor; } } .w-time-picker { margin-top: 12px; } .time-confirm { @extend .w-time-picker; } } .display-flex { display: flex; } .align-items-center { align-items: center; } .justify-content-center { justify-content: center; } .justify-content-space-between { justify-content: space-between; } .justify-content-space-around { justify-content: space-around; } .justify-content-space-evenly { justify-content: space-evenly; } .flex-wrap { flex-wrap: wrap; } .flex-direction-row { flex-direction: row; } .flex-direction-row-reverse { flex-direction: row-reverse; } .flex-direction-column { flex-direction: column; } .flex-direction-column-reverse { flex-direction: column-reverse; } } </style>
<template> <div v-if="calendarFlag"> <!-- ymdhms 可以不传 --> <Calendar v-model:arise="calendarFlag" :ymdhms="{ ymd: '2022/04/17', hms: '12:30:30' }" @giveMe="handleGiveMe" /> </div> </template> // 使用 <script setup> import { ref } from 'vue' import Calendar from '@/components/Calendar/index' // 组件路径 const calendarFlag = ref(false) function handleGiveMe(e) { console.log(e) } </script>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。