如何使用 CSS Grid、Vue 和 moment/date-fns 制作简单的日历

How to make a simple calendar using CSS Grid, Vue and moment/date-fns

我使用 Vuejs、momentjs 和 css-grids 布局制作了一个简单的日历。但是一周从星期天开始(美式)。如何更改代码以使其从星期一开始(欧式日历)?我尝试更改 column 方法,将其递减为 2,但仅正确显示八月,其他月份以错误的工作日开始。另外我想我需要以某种方式更改 css 选项 grid-template-columns 以支持星期一作为一周的第一天。

这是代码笔:https://codepen.io/moogeek/pen/oNWyWvM

const calendar = new Vue({
  el: '#app',
  data() {
    return {
      date:moment(),
      days: [],
      monthName: '',
    }
  },
  methods: {
    column(index) {
      if (index == 0) {
        return this.days[0].day() + 1
      }
    },
    isToday(day) {
      return moment().isSame(day, 'day')
    },
    updateMonth(){
      this.monthName = this.date.format("MMMM YYYY")
      let monthDate = this.date.startOf('month');
    
      this.days = [...Array(monthDate.daysInMonth())].map((_, i) => monthDate.clone().add(i, 'day'))
    },
    nextMonth(){
      this.date.add(1,'month')
      this.updateMonth()
    },
    previousMonth(){
      this.date.subtract(1,'month')
      this.updateMonth()
    },
  },
  mounted() {
    this.updateMonth()
  }
})
html {
  overflow:hidden;
}

#app {
    width:100%;
    height:100%;
    font-size: 100%;
    user-select: none;
    overflow:hidden;
}

.page-content {
  overflow: hidden;
}

.calendar-wrap {
  padding-top:4vh;
}

.calendar-header {
    width: 100vw;
    font-size: 170%;
    text-align:center;
    background-color: transparent;
}
.calendar-header .month-name, .calendar-header button{
  display: inline-block;
}

.calendar-header .month-name{
  color:#000;
  font-size:150%;
}
.calendar-header .current-month-value {
  color:#000;
}

.calendar-header .link, .calendar-header .link:hover,.calendar-header .link:active {text-decoration:none;}

.calendar-header .calendar-prev-month-button {
  margin-right: 1vh;
}

.calendar-header .calendar-next-month-button {
  margin-left: 1vh;
}

.calendar-wrapper {
    align-items: center;
    box-sizing: border-box;
    display: flex;
    justify-content: center;
    padding: 0 2em 0 2em;
  }  
  #calendar{
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    max-width: 1024px;
    width: 100%;
    transition: transform .3s ease 0s;
  }

  #calendar > *{
    align-items: center;
    display: flex;
    justify-content: center;
  }
  
  #calendar > *::before {
    content: "";
    display: inline-block;
    height: 0;
    padding-bottom: 100%;
    width: 1px;
  }
  
  #calendar > *.today{
    color: black;
    border: 0.1em solid black;
    border-radius: 100%;
  }

  #calendar > .day {
    border-radius: 100%;
    margin:3px;
  }
<html>
  <head>
      <meta charset="utf-8">
      <title>Calendar</title>


  </head>
 <body>
   <div id="app" class="page-content">
      <div class="calendar-wrap">      
              <section class="calendar-header">
                  <a @click="previousMonth()"class="link icon-only calendar-prev-month-button"><i class="icon icon-prev"></i></a>
                  <a class="current-month-value link">{{ monthName }}</a>
                  <a @click="nextMonth()" class="link icon-only calendar-next-month-button"><i class="icon icon-next"></i></a>
              </section>

              <section id="calendar-wrapper" class="calendar-wrapper skeletons">
                <main id="calendar">
                  
                    <div class="weekday">S</div>
                    <div class="weekday">M</div>
                    <div class="weekday">T</div>
                    <div class="weekday">W</div>
                    <div class="weekday">T</div>
                    <div class="weekday">F</div>
                    <div class="weekday">S</div>
                  
                  <div v-for="(day, index) in days"
                      :data-date="day.format('DD.MM.YYYY')"
                      :style="{ gridColumn: column(index) }" 
                      :class="{ day, today: isToday(day) }">
                    <span>{{ day.format('D') }}</span>
                  </div>
                </main> 
              </section>
    </div>
</div>
</body>
</html>

更新 好吧,也许我找到了一个更好的解决方案,将工作日和日子包装在单独的容器中,并将 grid-column:7 属性 添加到第一天 child。还更改了列函数,因此它不会添加额外的偏移量:

https://codepen.io/moogeek/pen/ExmRzgb

现在 8 月份的 onmount 显示正确,但如果我单击 prev/next 月份,它会以某种方式克隆出 9 月份的天数...请告诉我我做错了什么以及如何做我更正了代码,以便它可以正确显示所有月份:

好的,如果当前月份的第一天是星期日,我已经临时添加了一个额外的检查 - 然后网格列 属性 没有分配: https://codepen.io/moogeek/pen/JjNBBqo

<div v-for="(day, index) in days"
    :data-date="day.format('DD.MM.YYYY')" 
    :style="{gridColumn:setGridColumn(index)}"
    :class="{ day, today: isToday(day) }">
    <span>{{ day.format('D') }}</span>
</div>
//inside methods
setGridColumn(index){
      return (this.days[0].startOf('month').format('e')!=6) ? this.column(index) : null;
}

但也许还有其他选择可以实现此目的,或者可能会导致其他错误?

我的现代解决方案 date-fns can be found on github: https://github.com/kissu/so-date-fns-calendar

它也托管在这里:https://so-date-fns-calendar.netlify.app/

这里是实现所有这些的相关代码

<template>
  <div>
    <div>
      <button @click="substractOneMonth">previous</button>
      <span style="margin-left: 2rem">
        current: {{ format(currentMonth, 'MMM yyyy') }}
      </span>
      <button style="margin-left: 2rem" @click="addOneMonth">next</button>
    </div>
    <div class="week-days">
      <p v-for="weekName in weekNames" :key="weekName">{{ weekName }}</p>
    </div>

    <div class="days">
      <p
        v-for="(day, index) in daysOfCurrentMonth"
        :key="day"
        class="day"
        :style="`grid-column: column(${index}); grid-column-start: ${
          index === 0 ? weekdayOffset : '0' // basically first-child with a param
        }; color: ${isToday(day) ? 'red' : 'black'};`"
      >
        {{ format(day, 'dd') }}
      </p>
    </div>

    <button @click="resetToToday">Go back to today</button>
  </div>
</template>

<script>
import {
  startOfMonth,
  addMonths,
  format,
  subMonths,
  addDays,
  startOfWeek,
  sub,
  add,
  eachDayOfInterval,
  getDay,
  isToday,
} from 'date-fns'
import fr from 'date-fns/locale/fr'

export default {
  data() {
    return {
      currentMonth: startOfMonth(new Date()),
      firstDayOfWeek: startOfWeek(new Date(), {
        locale: fr,
        weekStartsOn: 1, // monday
      }),
    }
  },
  computed: {
    previousMonth() {
      return startOfMonth(subMonths(new Date(this.currentMonth), 1))
    },
    nextMonth() {
      return startOfMonth(addMonths(new Date(this.currentMonth), 1))
    },
    daysOfCurrentMonth() {
      return eachDayOfInterval({
        start: this.currentMonth,
        end: sub(this.nextMonth, { days: 1 }),
      })
    },
    weekNames() {
      return [...Array(7)].map((_, index) =>
        format(addDays(this.firstDayOfWeek, index), 'EEEEEE')
      )
    },
    weekdayOffset() {
      return (getDay(this.currentMonth) + 7) % 7 || 7 // `|| 7` is basically for sunday, edge case
    },
  },
  methods: {
    format,
    isToday,

    substractOneMonth() {
      this.currentMonth = sub(this.currentMonth, { months: 1 })
    },
    addOneMonth() {
      this.currentMonth = add(this.currentMonth, { months: 1 })
    },
    resetToToday() {
      this.currentMonth = startOfMonth(new Date())
    },
  },
}
</script>

<style scoped>
.days,
.week-days {
  display: grid;
  grid-template-columns: repeat(7, 50px);
  grid-column: 7;
}
</style>

我在 1 年前和 1 年后检查过,一切看起来都很好。
如果您需要任何其他评论或类似信息,请告诉我!

顺便说一句,我的应用程序是 Nuxt 应用程序,因为我只是想快速旋转一些东西而且我已经习惯了,但是这里的代码与 vanilla Vue 100% 兼容。