乐趣区

vue简单日历的实现方案改造

改造项目

CodePen 原项目

场景

自己写个聚合首页,方便自己统一管理常用的网页工具,参考的是 Mac OS 的仪表盘,如下:

虽然短时间没法做到这么精美,但是起码功能得差不多吧,时钟与天气都已经 OK,在做日历的时候觉得比较麻烦,就去 CodePen 找找看,便有了如此。

源码迁移

CodePen 上的原项目本身难度不高,繁琐在理清楚日历逻辑(原作者逻辑),以及将此作者项目中关于 DOM 的操作转换成 vue 的模板操作。

先看逻辑

  1. 本月 1 号之前有可能存在上个月的尾巴(假如我们需要将日历方格全部填满)
  2. 本月内容以及当天的样式
  3. 本月最后一天后,也有可能会有下个月的月头

分别对应了原项目中这三个函数,原作者也有注释:

lastDayOfLastMonth 上个月最后一天,这里应该是原作者命名问题,应该是 lastDayOfPreviousMonth

firstDayOfMonth 本月第一天

lastDateOfMonth 本月最后一天

i 是本月第几天,走了一个 do while 循环

// If not Sunday but first day of the month
// it will write the last days from the previous month
if (i == 1) {
  html += '<tr>';
  var k = lastDayOfLastMonth - firstDayOfMonth+1;
  for(var j=0; j < firstDayOfMonth; j++) {
    html += '<td class="not-current">' + k + '</td>';
    k++;
  }
}

上面的意思就是第一天时候把上个月尾巴给加到表格标签里面(最后统一加)

// Write the current day in the loop
var chk = new Date();
var chkY = chk.getFullYear();
var chkM = chk.getMonth();
if (chkY == this.currYear && chkM == this.currMonth && i == this.currDay) {html += '<td class="today">' + i + '</td>';} else {html += '<td class="normal">' + i + '</td>';}

上面特殊处理的今天,其他本月内容都是常规加入,可以从 class 上面区分看出来。

// If not Saturday, but last day of the selected month
// it will write the next few days from the next month
if (i == lastDateOfMonth) {
  var k=1;
  for(dow; dow < 6; dow++) {
    html += '<td class="not-current">' + k + '</td>';
    k++;
  }
}

上面的意思就是当本月最后一天的时候,顺便把下个月的月头加进来。

漏了,每行的首尾控制

// If Sunday, start new row
if (dow == 0) {html += '<tr>';}
// If Saturday, closes the row
if (dow == 6) {html += '</tr>';}

这里说一下,因为日历是从周日到周六为一行,所以作者这里判断方案是 Sunday 与 Saturday。

上面这些就是核心代码,当然还有下个月与上个月切换,不过就是清空当前日历再来一次,这些看源码即可,没有太多逻辑问题。

转 vue

转起来难度不是很大,更多的是思维变化。

原项目是直接操作 DOM,我们这里通过数据操作模板(姑且区分下),所以需要构建一个对象来承接每个”天“。

我们申明一个日历数组,由于日历是由若干个星期组成,所以我们就命名为:weeks,然后每个”天“给对象,属性如下:

{
  // 哪天
  label: [string],
  // 是否今天
  today: [boolean],
  // 是否本月
  current: [boolean],
}

那么我们的”天“会有这几种情况:

  1. 常规天
  2. 今天
  3. 上月或下月的天
{
  // 哪天
  label: [string],
  // 是否本月
  current: true,
}
{
  // 哪天
  label: [string],
  // 是否今天
  today: true,
  // 是否本月
  current: true,
}
{
  // 哪天
  label: [string],
}

接下来,我们把原逻辑里面对应的 html,换成 weeks,tr 换成 week(每次新建一个空数组),在周日或者第一天时候把 week 置空,在周六或本月最后一天把 week 闭合并且让 weeks 来 push 一下 week,循环走完,我们一个月的关于 weeks、week 以及”天“的处理就完成了。

核心逻辑如下:

const y = this.year, m = this.month
let d = new Date()
  // 本月第一天
  , firstDayOfMonth = new Date(y, m, 1).getDay()
  // 本月最后一天
  , lastDateOfMonth = new Date(y, m + 1, 0).getDate()
  // 上个月最后一天
  , lastDayOfPreMonth = m === 0 ? new Date(y - 1, 11, 0).getDate() : new Date(y, m, 0).getDate()

let week = []

// 月第一 = 几天
let i = 1
do {let dow = new Date(y, m, i).getDay()

  // 周日则单独一行
  if(dow === 0) {week = []
  }
  // 每月第一天关于上个月尾数几天当月显示处理
  else if(i === 1) {
    let k = lastDayOfPreMonth - firstDayOfMonth + 1
    for(let j = 0; j < firstDayOfMonth; j++) {
      let obj = {label: k,}
      week.push(obj)
      k++
    }
  }

  // 查询当天,高亮处理
  let chk = new Date()
  let chkY = chk.getFullYear()
  let chkM = chk.getMonth()
  if(chkY === this.year && chkM === this.month && i === this.day) {
    week.push({
      label: i,
      // 本月
      current: true,
      today: true,
    })
    // 其他本月常规天
  } else {
    week.push({
      label: i,
      // 本月
      current: true,
    })
  }
  // 周六则闭合
  if(dow === 6) {this.weeks.push(week)
  }
  // 本月最后一天,处理下个月首几日在当月内显示
  else if(i === lastDateOfMonth) {
    let k = 1
    for(dow; dow < 6; dow++) {
      let obj = {label: k,}
      week.push(obj)
      k++
    }
    this.weeks.push(week)
  }

  i++
} while(i <= lastDateOfMonth)

注意跨年问题

最终效果

退出移动版