日历组件封装 —— (Vue 和 JS两种方式实现)

该文章展示了如何使用Vue.js编写一个日期选择器组件,包括获取每月第一天的星期和最后一天,以及处理前后月份的额外日期。同时,文章还介绍了原生JavaScript版本的实现,分为日期、年份和月份三个模块,通过事件委托处理点击事件,动态创建和更新面板。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

效果如下

Vue实现

工具类util:

export const getFirstWeekDay = (year, month) => {
    const date = new Date(year, month - 1, 1)
    return date.getDay()
}

export const getMonthLastDay = (year, month) => {
    const date = new Date(year, month, 0)
    return date.getDate()
}

export const prevMonthRestDay = (year, month) => { // [28, 29, 30]
    const firstDayWeek = getFirstWeekDay(year, month)
    let prevMonthLastDay = getMonthLastDay(year, month - 1)
    const lastDayArr = []
    for (let i = 0; i < firstDayWeek; i++) {
        lastDayArr.unshift(prevMonthLastDay)
        prevMonthLastDay--
    }
    return lastDayArr
}

export const nextMonthRestDay = (year, month) => { // [1, 2, 3]
    const firstDayWeek = getFirstWeekDay(year, month)
    const thisMonthLastDay = getMonthLastDay(year, month)
    const nextDayCount = 42 - (firstDayWeek + thisMonthLastDay)
    const nextDayArr = []
    for (let i = 1; i <= nextDayCount; i++) {
        nextDayArr.push(i)
    }
    return nextDayArr
}

export const getDateInfo = (timeStamp) => {
    const date = new Date(timeStamp)
    return {
        year: date.getFullYear(),
        month: date.getMonth() + 1,
        date: date.getDate()
    }

}

组件index:

<template>
    <div class="container">
        <div class="input-box">
            <input type="text" @click="openCalendar" v-model="throwDate" />
            <div v-if="isShow" class="calendar" @click="events">
                <div class="control-area">
                    <span class="control-btn btn-year-lt">&lt;&lt;</span>
                    <span class="control-btn btn-month-lt">&lt;</span>
                    <span class="control-show">
                        <span class="control-title">
                            <span class="title-year">{{ dateInfo.year }}</span>
                        </span>
                        &nbsp;
                        <span>
                            <span class="title-month">{{ dateInfo.month }}</span>
                        </span>
                    </span>
                    <span class="control-btn btn-month-gt">&gt;</span>
                    <span class="control-btn btn-year-gt">&gt;&gt;</span>
                </div>
                <table>
                    <thead v-if="timeType == 'DATE'">
                        <tr>
                            <th>日</th>
                            <th>一</th>
                            <th>二</th>
                            <th>三</th>
                            <th>四</th>
                            <th>五</th>
                            <th>六</th>
                        </tr>
                    </thead>
                    <tbody>
                        <template>
                            <tr v-for="(tr, index) in dateTrs" :key="index">
                                <td v-for="(td, dex) in tr" :key="dex"
                                    :class="{ 'rest-day': td.type !== 'this', 'current-day': td.type === 'this', 'current': td.current, 'seleted': td.seleted }">
                                    {{
    td.name
                                    }}</td>
                            </tr>
                        </template>
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</template>

<script>
import { getDateInfo, prevMonthRestDay, nextMonthRestDay, getMonthLastDay } from './util'
export default defineComponent({
    name: 'index',
    emits: ['throwDate'],
    props: {
        timeStemp: {
            type: Number,
            default: +new Date()
        }
    },
    setup(props, SetupContext) {
        const TIME_TYPE = {
            'DATE': 'DATE',
            'YEAR': 'YEAR',
            'MONTH': 'MONTH'
        }
        const dateInfo = ref(getDateInfo(props.timeStemp))
        const data = reactive({
            isShow: false,
            throwDate: '',
            dateTrs: [],
            timeType: 'DATE'
        })

        onMounted(() => {
            data.throwDate = dateInfo.value.year + '-' + (dateInfo.value.month < 10 ? '0' + dateInfo.value.month : dateInfo.value.month) + '-' + (dateInfo.value.date < 10 ? '0' + dateInfo.value.date : dateInfo.value.date)
        })

        const createDateTrs = () => {
            const date = new Date()
            data.dateTrs = []
            const prevMonthRest = prevMonthRestDay(dateInfo.value.year, dateInfo.value.month)
            const currentMonthDay = getMonthLastDay(dateInfo.value.year, dateInfo.value.month)
            const nextMonthRest = nextMonthRestDay(dateInfo.value.year, dateInfo.value.month)
            const prevMonthRestTd = []
            const currentMonthDayTd = []
            const nextMonthRestTd = []
            prevMonthRest.forEach(i => prevMonthRestTd.push({ name: i, type: 'prev' }))
            for (let i = 1; i <= currentMonthDay; i++) {
                currentMonthDayTd.push({ name: i, type: 'this' })
                if (i == date.getDate() && dateInfo.value.year == date.getFullYear() && dateInfo.value.month == date.getMonth() + 1) {
                    currentMonthDayTd[i - 1]['current'] = true
                } else if (i == dateInfo.value.date && data.throwDate.split('-')[0] == dateInfo.value.year && data.throwDate.split('-')[1] == dateInfo.value.month) {
                    currentMonthDayTd[i - 1]['seleted'] = true
                }
            }
            nextMonthRest.forEach(i => nextMonthRestTd.push({ name: i, type: 'next' }))
            integTrs(prevMonthRestTd, currentMonthDayTd, nextMonthRestTd)
        }

        const integTrs = (...args) => {
            const dateTds = [...(args.flat())]
            let index = 0
            for (let i = 0; i < 6; i++) {
                if (!data.dateTrs[i]) data.dateTrs[i] = []
                for (let j = 0; j < 7; j++) {
                    data.dateTrs[i].push(dateTds[index])
                    index++
                }
            }
        }

        const events = (e) => {
            console.log(e.target.className);
            switch (data.timeType) {
                case (TIME_TYPE.DATE):
                    dateEvent(e.target)
                    break
                case (TIME_TYPE.YEAR):

                    break
                case (TIME_TYPE.MONTH):

                    break

            }
        }

        const dateEvent = (target) => {
            switch (target.className) {
                case 'control-btn btn-year-gt':
                    dateInfo.value.year += 1
                    break
                case 'control-btn btn-year-lt':
                    dateInfo.value.year -= 1
                    break
                case 'control-btn btn-month-gt':
                    if (dateInfo.value.month === 12) {
                        dateInfo.value.month = 1
                        dateInfo.value.year += 1
                    } else {
                        dateInfo.value.month += 1
                    }
                    break
                case 'control-btn btn-month-lt':
                    if (dateInfo.value.month === 1) {
                        dateInfo.value.month = 12
                        dateInfo.value.year -= 1
                    } else {
                        dateInfo.value.month -= 1
                    }
                    break
            }
            if (target.className.includes('current-day')) {
                document.querySelectorAll('.current-day').forEach(td => {
                    td.className = td.className.replace(' seleted', '')
                })
                target.className += ' seleted'

                dateInfo.value.date = Number(target.innerHTML)
                data.throwDate = dateInfo.value.year + '-' + (dateInfo.value.month < 10 ? '0' + dateInfo.value.month : dateInfo.value.month) + '-' + (dateInfo.value.date < 10 ? '0' + dateInfo.value.date : dateInfo.value.date)
                SetupContext.emit('throwDate', data.throwDate)
                data.isShow = false
            }
            createDateTrs()
        }

        const openCalendar = () => {
            data.isShow = true
            createDateTrs()
        }

        return {
            ...toRefs(data),
            dateInfo,
            openCalendar,
            events
        }
    }
})
</script>
<style lang="scss" scoped>
.input-box {
    position: relative;
    width: 200px;
    margin-right: 100px;

    @keyframes identifier {
        0% {
            height: 0;
        }

        100% {
            height: 300px;
        }
    }

    .calendar {
        position: absolute;
        top: 26px;
        left: 0;
        width: 324px;
        background-color: aliceblue;
        transform: translateX(-25%);
        animation: identifier .3s ease-out 1 normal forwards;
        z-index: 999;
        overflow: hidden;
    }

    table {
        width: 100%;
        font-size: 12px;

        thead {
            border-bottom: 1px solid #ddd;
        }

        th {
            font-weight: 400;
        }

        tr {
            height: 38px;
        }

        td {
            text-align: center;

            &.rest-day {
                color: #ccc;
            }

            &.current-day {
                cursor: pointer;
            }

            &.current {
                color: orange;
                font-weight: 700;
            }

            &.seleted {
                color: #fff !important;
                background: orange !important;
            }
        }

        border-collapse: collapse;

    }

    .control-area {
        height: 30px;
        border-bottom: 1px solid #ddd;

        .control-btn {
            float: left;
            display: block;
            width: 30px;
            height: 30px;
            color: orange;
            text-align: center;
            line-height: 30px;
            cursor: pointer;
        }

        .control-show {
            float: left;
            display: block;
            width: 204px;
            height: 30px;
            text-align: center;
            line-height: 30px;
            cursor: pointer;
            color: orange;
            font-weight: 700;
        }
    }
}

input {
    outline: none;
    border: 1px solid pink;
    width: 100%;
    height: 25px;
    box-sizing: border-box;
    padding-left: 10px;
    cursor: pointer;
}
</style>

原生js实现

分三个模块,日期面板、年份面板、月份面板

项目结构:

入口文件:

import MyCalendar from './calendar/index';

(() => {
    MyCalendar('#app', (date) => {
        console.log(date);
    })
})()

首页加载index

import event from './event'
import { reactive } from './store'
import { render } from './date/render'
import { getDateInfo } from './util'
import './index.scss'

export default (...args) => {
    if (args.length === 2 && typeof args[1] === 'function') {
        const [year, month] = getDateInfo()
        const [el, hander] = args
        init(el, [year, month], hander)
    } else {
        const [el, [year, month], hander] = args
        init(el, [year, month], hander)
    }
}

const init = (el, [year, month], handler) => {
    const oApp = document.querySelector(el)
    const oContainer = document.createElement('div')
    oContainer.className = 'my-calendar'
    const dateInfo = reactive({ year, month })
    event(oContainer, handler, dateInfo) // 注册事件监听
    render(oContainer, year, month)

    oApp.appendChild(oContainer)
}

公共样式部分:

.my-calendar {
    width: 324px;
    border: 1px solid #ddd;
}

公共utils:

/*
    公共utils
*/
// 创建几个tr
export const createTrs = (count) => {
    const trArr = []
    for (var i = 0; i < count; i++) {
        const oTr = document.createElement('tr')
        trArr.push(oTr)
    }

    return trArr
}

export const getDateInfo = (timeStamp) => { // 传入时间戳,返回当天日期
    const date = timeStamp ? new Date(timeStamp) : new Date()
    return [
        date.getFullYear(),
        date.getMonth() + 1,
        date.getDate()
    ]
}

公共store,用于响应数据的变化,及时去创建或更新面板

/*
     @description: 响应式文件,监听年、月的修改,及时去更新update
*/
import { update as dateUpdate, render as dateRender } from "./date/render";
import { update as yearUpdate, render as yearRender } from "./year/render";
import { update as monthUpdate, render as monthRender } from "./month/render";

export const ALLOWED_FLAGS = {
    'YEAR': 'YEAR',
    'MONTH': 'MONTH',
    'DATE': 'DATE'
}

let currentFlag = ALLOWED_FLAGS.DATE

export function getFlag() {
    return currentFlag
}

export function setFlag(value, container, { year, month }) {
    if (ALLOWED_FLAGS[value]) {
        currentFlag = ALLOWED_FLAGS[value]
    }
    switch (currentFlag) {
        case ALLOWED_FLAGS.YEAR:
            yearRender(container, year)
            break
        case ALLOWED_FLAGS.MONTH:
            monthRender(container, year, month)
            break
        case ALLOWED_FLAGS.DATE:
            dateRender(container, year, month)
            break
    }
}

export function reactive({ year, month }) {
    const dateInfo = {}
    const _dateInfo = [year, month]

    Object.defineProperties(dateInfo, {
        year: {
            get() {
                return _dateInfo[0]
            },
            set(year) {
                _dateInfo[0] = year
                update(..._dateInfo)
            }
        },
        month: {
            get() {
                return _dateInfo[1]
            },
            set(month) {
                _dateInfo[1] = month
                update(..._dateInfo)
            }
        }
    })
    return dateInfo
}

function update(year, month) {
    switch (currentFlag) {
        case ALLOWED_FLAGS.YEAR:
            yearUpdate(year)
            break
        case ALLOWED_FLAGS.MONTH:
            monthUpdate(year)
            break
        case ALLOWED_FLAGS.DATE:
            dateUpdate(year, month)
    }
}

所有事件,利用事件代理方式,只绑定根元素点击事件,最后触发子节点通过冒泡触发事件

import { ALLOWED_FLAGS, getFlag, setFlag } from "./store"

let target = null

export default (container, handler, dateInfo) => { // hander: 切换日期时执行的回调
    container.addEventListener('click', handlerClick.bind(null, container, handler, dateInfo), false)
}

const handlerClick = (...args) => {
    const [container, handler, dateInfo, e] = args // 事件对象永远在最后一位
    const tar = e.target
    const className = tar.className
    const flag = getFlag()
    // console.log(className);
    if (className.includes('current-day')) { // 日期面板点击日期
        dateClick(tar, handler)
        return
    }

    if (className.includes('decade-year')) { // 年份面板点击年份
        yearClick(container, tar, dateInfo)
    }

    if (className.includes('static-month')) { // 月份面板点击月份
        monthClick(container, tar, dateInfo)
    }

    if (className == 'title-year') { // 日期面板点击顶部年份
        titleYearClick(container, dateInfo)
        return
    }

    if (className == 'title-month') { // 日期面板点击顶部月份
        titleMonthClick(container, dateInfo)
        return
    }

    // 不论哪个面板点击控制栏时
    switch (flag) {
        case ALLOWED_FLAGS.YEAR:
            yearControlClick(className, dateInfo) // 年份面板点击控制栏箭头
            break
        case ALLOWED_FLAGS.MONTH:
            monthControlClick(className, dateInfo) // 月份面板点击控制栏箭头
            break
        case ALLOWED_FLAGS.DATE:
            dateControlClick(className, dateInfo) // 日期面板点击控制栏箭头

    }
}

// 日期面板点击日期
const dateClick = (tar, handler) => {
    if (target) {
        target.className = target.className.replace(' seleted', '')
    }
    target = tar
    tar.className += ' seleted'
    handler && handler(tar.dataset.date)
}

// 日期面板控制栏箭头
const dateControlClick = (className, dateInfo) => {
    switch (className) {
        case 'control-btn btn-year-lt':
            dateInfo.year -= 1
            break
        case 'control-btn btn-month-lt':
            dateInfo.month > 1 && (dateInfo.month -= 1)
            break
        case 'control-btn btn-year-gt':
            dateInfo.year = dateInfo.year + 1
            break
        case 'control-btn btn-month-gt':
            dateInfo.month < 12 && (dateInfo.month += 1)
            break
    }
}

console.log('------------------------------');

// 日期面板点击顶部年份
const titleYearClick = (container, dateInfo) => {
    setFlag(ALLOWED_FLAGS.YEAR, container, dateInfo)
}

// 年份面板点击年份
const yearClick = (container, tar, dateInfo) => {
    dateInfo.year = Number(tar.dataset.year)
    setFlag(ALLOWED_FLAGS.DATE, container, dateInfo)
}

// 年份面板顶部控制栏箭头
const yearControlClick = (className, dateInfo) => {
    switch (className) {
        case 'year-control-btn btn-year-lt':
            dateInfo.year -= 10
            break
        case 'year-control-btn btn-year-gt':
            dateInfo.year += 10
            break
    }
}

console.log('------------------------------');

// 日期面板点击顶部月份
const titleMonthClick = (container, dateInfo) => {
    setFlag(ALLOWED_FLAGS.MONTH, container, dateInfo)
}

// 年份面板点击年份
const monthClick = (container, tar, dateInfo) => {
    dateInfo.month = Number(tar.dataset.month)
    setFlag(ALLOWED_FLAGS.DATE, container, dateInfo)
}

// 月份面板点击控制栏左右箭头
const monthControlClick = (className, dateInfo) => {
    switch (className) {
        case 'month-control-btn btn-year-lt':
            dateInfo.year -= 1
            break
        case 'month-control-btn btn-year-gt':
            dateInfo.year += 1
            break
    }
}

日期模块

工具util:

export const getFirstWeekDay = (year, month) => { // 当月1号是周几    2
    const date = new Date(year, month - 1, 1)
    return date.getDay()
}

export const getMonthDayCount = (year, month) => { // 当月有多少天(最后一天的日期)
    const date = new Date(year, month, 0) // 下个月的第0天也就是当月最后一天
    return date.getDate()
}

export const getLastMonthRestDays = (year, month) => { // 当月1号前有几天(29,30,31,1,2,3,4)
    const days = getFirstWeekDay(year, month) // 当月1号周几
    let lastDate = getMonthDayCount(year, month - 1) // 上月最后一天
    const restDays = []

    while (restDays.length < days) {
        restDays.unshift(lastDate--)
    }
    return restDays
}

export const getNextMonthRestDays = (year, month) => { // 当月最后一天后下月的天数 (26,27,28,1,2,3,4)
    const lastMonthRestDayCount = getFirstWeekDay(year, month) // 1号前还有多少天
    const currentMonthDayCount = getMonthDayCount(year, month) // 当月最后一天
    const nextMonthRestDayCount = 42 - (lastMonthRestDayCount + currentMonthDayCount)
    let restDays = []

    for (let i = 1; i <= nextMonthRestDayCount; i++) {
        restDays.push(i)
    }
    return restDays
}

export const getFormatDate = (year, month, date) => { // 点击展示当前日期
    const dateArr = [year, month, date]

    for (let i = 1; i < dateArr.length; i++) {
        dateArr[i] < 10 && (dateArr[i] = '0' + dateArr[i])
    }
    return dateArr.join('-')
}

用于创建日期面板的节点 creator

import { createTrs } from "../util"
import { WEEK_DAYS } from "./config"
import { getFormatDate, getLastMonthRestDays, getMonthDayCount, getNextMonthRestDays } from "./util"
import { getDateInfo } from "../util"

// 生成表头(星期)
const domPool = { // keep-alive操作,固定不变的dom不必每次重新生成
    weekDays: null,
    controlArea: null
}

export const createWeekDayNode = () => {
    if (!domPool.weekDays) {
        domPool.weekDays = document.createElement('tr')
        domPool.weekDays.className = 'week-day'

        domPool.weekDays.innerHTML = WEEK_DAYS.map(item => {
            return `<th>${item}</th>`
        }).join('')
    }
    return domPool.weekDays
}

// 生成所有身体tr含td
export const createDateNode = (year, month) => {
    const lastMonthRestDays = getLastMonthRestDays(year, month) // [29, 30, 31]
    const currentMonthDayCount = getMonthDayCount(year, month) // 28
    const nextMonthRestDays = getNextMonthRestDays(year, month) // [1, 2, 3, 4, 5]
    const dateTrArr = createTrs(6)
    const lastMonthRestDaysTd = createRestDaysTD(lastMonthRestDays) // [node(30), ...] 上月
    const currentMonthDayCountTd = createCurrentDaysTD(currentMonthDayCount, year, month) // [node(1) ~ node(30)]
    const nextMonthRestDaysTd = createRestDaysTD(nextMonthRestDays) // [node(1), ...] 下月

    const dateArr = [
        ...lastMonthRestDaysTd,
        ...currentMonthDayCountTd,
        ...nextMonthRestDaysTd
    ]
    let index = 0
    dateTrArr.forEach(tr => { // 将6行tr依次循环,添加子节点td
        for (let i = 0; i < 7; i++) {
            tr.appendChild(dateArr[index])
            index++
        }
    })

    return dateTrArr
}

// 创建上月、下月目标日期
const createRestDaysTD = (restDays) => {
    return restDays.map(item => {
        const oTd = document.createElement('td')
        oTd.className = 'day rest-day'
        oTd.innerText = item
        return oTd
    })
}

// 生成当月td
const createCurrentDaysTD = (currentDayCount, year, month) => {
    let tdArr = []

    const [
        currentYear,
        currentMonth,
        currentDate
    ] = getDateInfo()

    for (let i = 1; i <= currentDayCount; i++) {
        const oTd = document.createElement('td')
        if (currentYear === year && currentMonth === month && currentDate === i) {
            oTd.className = 'day current-day current'
        } else {
            oTd.className = 'day current-day'
        }
        oTd.innerText = i
        oTd.setAttribute('data-date', getFormatDate(year, month, i))
        tdArr.push(oTd)
    }
    return tdArr
}

// 生成顶部控制栏
export const createControlArea = (year, month) => {
    if (!domPool.controlArea) {
        domPool.controlArea = document.createElement('div')
        domPool.controlArea.className = 'control-area'
        domPool.controlArea.innerHTML = `
        <span class="control-btn btn-year-lt">&lt;&lt;</span>
        <span class="control-btn btn-month-lt">&lt;</span>
        <span class="control-show">
            <span class="control-title">
                <span class="title-year">${year}</span>
            </span>
            <span>
                <span class="title-month">${month}</span>
            </span>
        </span>
        <span class="control-btn btn-month-gt">&gt;</span>
        <span class="control-btn btn-year-gt">&gt;&gt;</span>
    `
    } else { // 每次更新只需要改值即可,不用每次生成dom
        domPool.controlArea.querySelector('.title-year').innerText = year
        domPool.controlArea.querySelector('.title-month').innerText = month
    }

    return domPool.controlArea
}

用于渲染节点的render:

import { createWeekDayNode, createDateNode, createControlArea } from "./creator"
import './index.scss'

export function render(oContainer, year, month) {
    oContainer.innerHTML = ''
    const oTable = document.createElement('table')
    oTable.className = 'my-calendar-table'
    const oThead = document.createElement('thead')
    const oTbody = document.createElement('tbody')
    const weekDayNode = createWeekDayNode() // 表头星期行

    const dateTrs = createDateNode(year, month) // 所有身体tr含td
    dateTrs.forEach(tr => oTbody.appendChild(tr))
    oTbody.className = 'my-calendar-body'
    oThead.appendChild(weekDayNode)

    oTable.appendChild(oThead)
    oTable.appendChild(oTbody)
    const controlArea = createControlArea(year, month) // 控制栏
    oContainer.appendChild(controlArea)
    oContainer.appendChild(oTable)
}

export function update(year, month) { // 左右切换时的变化
    // 表格数据的变化
    const oTbody = document.querySelector('.my-calendar-body')
    oTbody.innerHTML = ''
    const dateTrs = createDateNode(year, month)
    dateTrs.forEach(tr => oTbody.appendChild(tr))

    // 控制台的年月文字变化
    const oTitleYear = document.querySelector('.title-year')
    const oTitleMonth = document.querySelector('.title-month')
    oTitleYear.innerText = year
    oTitleMonth.innerText = month
}

config:

export const WEEK_DAYS = [
    '日',
    '一',
    '二',
    '三',
    '四',
    '五',
    '六'
]

样式文件:

.my-calendar-table {
    width: 100%;
    font-size: 12px;
    color: #666;
    border-collapse: collapse;

    tHead {
        border-bottom: 1px solid #ddd;
    }

    th {
        font-weight: 400;
    }

    tr {
        height: 38px;
    }

    td {
        text-align: center;
    }

    .day {
        &.rest-day {
            color: #ccc;
        }

        &.current-day {
            cursor: pointer;
        }

        &.current {
            color: orange;
            font-weight: 700;
        }

        &.seleted {
            color: #fff;
            background: orange;
        }
    }
}

.control-area {
    height: 30px;
    border-bottom: 1px solid #ddd;

    .control-btn {
        float: left;
        display: block;
        width: 30px;
        height: 30px;
        color: orange;
        text-align: center;
        line-height: 30px;
        cursor: pointer;
    }

    .control-show {
        float: left;
        display: block;
        width: 204px;
        height: 30px;
        text-align: center;
        line-height: 30px;
        cursor: pointer;
        color: orange;
        font-weight: 700;
    }
}

年份模块

工具:

export function createDecadeYears(year) {
    const str = year.toString().slice(0, 3)
    const yearArr = []
    for (let i = 0; i < 10; i++) {
        yearArr.push(Number(str + i))
    }
    return yearArr
}

export function getStartAndEndYear(year) {
    const str = year.toString().slice(0, 3)
    return [Number(str + '0'), Number(str + '9')]
}

生成节点:

import { createTrs, getDateInfo } from "../util";
import { createDecadeYears, getStartAndEndYear } from "./util";

const domPool = {
    controlArea: null
}

// 生成年份面板的控制区
export function createYearControlArea(year) {
    const [startYear, endYear] = getStartAndEndYear(year) // [2020, 2029]
    if (!domPool.controlArea) {
        domPool.controlArea = document.createElement('div')
        domPool.className = 'year-control-area'
        domPool.controlArea.innerHTML = `
        <span class="year-control-btn btn-year-lt">&lt;&lt;</span>
        <span class="control-show">
            <span class="control-title">
                <span class="start-year">${startYear}</span>
                -
                <span class="end-year">${endYear}</span>
            </span>
        </span>
        <span class="year-control-btn btn-year-gt">&gt;&gt;</span>
        `
    } else {
        domPool.controlArea.querySelector('.start-year').innerText = startYear
        domPool.controlArea.querySelector('.end-year').innerText = endYear
    }
    return domPool.controlArea
}

// 生成年份面板的td
export function createYearTD(year) {
    const decadeYearArr = createDecadeYears(year) // [2020, 2021, ..., 2029]
    const [currentYear] = getDateInfo()
    const tdArr = []
    decadeYearArr.forEach(year => {
        const oTd = document.createElement('td')
        oTd.className = 'year decade-year'
        if (currentYear === year) {
            oTd.className += ' current-year'
        }
        oTd.innerText = year
        oTd.setAttribute('data-year', year)
        tdArr.push(oTd)
    })
    return tdArr
}

// 生成年份面板的tr含td
export function createYearNode(year) {
    const yearTrArr = createTrs(3) // 3个tr
    const yearTds = createYearTD(year)
    let index = 0
    yearTrArr.forEach(tr => {
        for (let i = 0; i < 4 && yearTds[index]; i++) {
            tr.appendChild(yearTds[index])
            index++
        }
    })
    return yearTrArr
}

渲染:

import { createYearControlArea, createYearNode } from "./creator";
import './index.scss'
import { getStartAndEndYear } from "./util";

export function render(container, year) {
    container.innerHTML = ''
    const oTable = document.createElement('table')
    oTable.className = 'my-year-calendar-table'

    const controlArea = createYearControlArea(year) // 控制器
    const yearNode = createYearNode(year) // tr
    yearNode.forEach(tr => {
        oTable.appendChild(tr)
    })
    container.appendChild(controlArea)
    container.appendChild(oTable)
}

export function update(year) { // 年份左右切花时的变化
    const oTable = document.querySelector('.my-year-calendar-table')
    const oStartYear = document.querySelector('.start-year')
    const oEndStart = document.querySelector('.end-year')

    const yearNode = createYearNode(year) // tr
    const [startYear, endYear] = getStartAndEndYear(year)
    oStartYear.innerText = startYear
    oEndStart.innerText = endYear
    oTable.innerHTML = ''
    yearNode.forEach(tr => {
        oTable.appendChild(tr)
    })
}

样式:

.my-year-calendar-table {
    width: 100%;
    font-size: 12px;
    color: #666;

    tr {
        height: 38px;
    }

    td {
        text-align: center;
    }

    .year {
        cursor: pointer;

        &.current-year {
            color: orange;
        }

        &.current {
            color: orange;
            font-weight: 700;
        }

    }
}

.year-control-area {
    height: 30px;
    border-bottom: 1px solid #ddd;
}

.year-control-btn {
    float: left;
    display: block;
    width: 30px;
    height: 30px;
    color: orange;
    text-align: center;
    line-height: 30px;
    cursor: pointer;
}

.control-show {
    float: left;
    display: block;
    width: 264px;
    height: 30px;
    text-align: center;
    line-height: 30px;
    font-weight: 700;

    &:hover {
        cursor: pointer;
        color: orange;
    }
}

月份模块

生成节点:

import { createTrs } from "../util"
import { MONTHS } from "./config"

const domPool = {
    controlArea: null,
    monthTrs: null
}
// 月份面板生成控制器
export function createMonthControlArea(year) {
    if (!domPool.controlArea) {
        domPool.controlArea = document.createElement('div')
        domPool.controlArea.className = 'month-control-area'
        domPool.controlArea.innerHTML = `
        <span class="month-control-btn btn-year-lt">&lt;&lt;</span>
        <span class="control-show">
            <span class="control-title">
                <span class="title-year">${year}</span>
            </span>
        </span>
        <span class="month-control-btn btn-year-gt">&gt;&gt;</span>
        `
    } else {
        domPool.controlArea.querySelector('.title-year').innerText = year
    }
    return domPool.controlArea
}

// 月份面板生成行tr含td
export function createMonthNode(month) {
    if (!domPool.monthTrs) {
        domPool.monthTrs = createTrs(4) // [tr, tr, tr, tr]

        let index = 0
        domPool.monthTrs.forEach(tr => {
            for (let i = 0; i < 3; i++) {
                const oTd = document.createElement('td')
                oTd.className = 'static-month'
                oTd.setAttribute('data-month', index)
                oTd.innerText = MONTHS[index]
                if (index === month) {
                    oTd.className += ' current'
                }
                tr.appendChild(oTd)
                index++
            }
        })
    }
    return domPool.monthTrs
}

渲染节点:

import { createMonthNode, createMonthControlArea } from './creator'
import './index.scss'
export const render = (container, year, month) => {
    container.innerHTML = ''
    const oTable = document.createElement('table')
    oTable.className = 'my-month-calendar-table'
    const controlArea = createMonthControlArea(year) // 控制器
    const monthNode = createMonthNode(month) // tr

    monthNode.forEach(tr => {
        oTable.appendChild(tr)
    })
    container.appendChild(controlArea)
    container.appendChild(oTable)
}

export const update = (year) => {
    const oYear = document.querySelector('.title-year')
    oYear.innerText = year
} 

样式:

.my-month-calendar-table {
    width: 100%;
    font-size: 12px;
    color: #666;

    tr {
        height: 38px;
    }

    td {
        text-align: center;
    }

    .static-month {
        cursor: pointer;

        &.current {
            color: orange;
        }
    }
}

.month-control-area {
    height: 30px;
    border-bottom: 1px solid #ddd;

    .month-control-btn {
        float: left;
        display: block;
        width: 30px;
        height: 30px;
        color: orange;
        text-align: center;
        line-height: 30px;
        cursor: pointer;
    }

    .control-show {
        float: left;
        display: block;
        width: 264px;
        height: 30px;
        text-align: center;
        line-height: 30px;
        font-weight: 700;

        &:hover {
            cursor: pointer;
            color: orange;
        }
    }


}

config配置:

.my-month-calendar-table {
    width: 100%;
    font-size: 12px;
    color: #666;

    tr {
        height: 38px;
    }

    td {
        text-align: center;
    }

    .static-month {
        cursor: pointer;

        &.current {
            color: orange;
        }
    }
}

.month-control-area {
    height: 30px;
    border-bottom: 1px solid #ddd;

    .month-control-btn {
        float: left;
        display: block;
        width: 30px;
        height: 30px;
        color: orange;
        text-align: center;
        line-height: 30px;
        cursor: pointer;
    }

    .control-show {
        float: left;
        display: block;
        width: 264px;
        height: 30px;
        text-align: center;
        line-height: 30px;
        font-weight: 700;

        &:hover {
            cursor: pointer;
            color: orange;
        }
    }


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山楂树の

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值