vite + vue3日历时间
vue3 Calendar组件 vue3日历组件 vite + vue3
vue3
vant
vite
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>