iT邦幫忙

2021 iThome 鐵人賽

DAY 28
0
Modern Web

排版神器 Tailwind CSS~和兔兔一起快速上手漂亮的元件開發!系列 第 29

Day 28:「今天幾月幾號啊?」- 簡易日曆

Day28-Banner

「今天是幾月幾號啊?」

今天是 ... 等等!
不准看電腦上的!

你先等我造出一個,我們要看日期 ... 就看我們自己做的日曆!

(這兔是有什麼毛病,連這個時候都還要造元件)

carrotPoint 建立元件

首先,在專案裡的 ./src/components 資料夾中新增一個 SimpleCalendar.vue 的元件:

好了嗎?

那一樣,增添以下內容:

<template>
  
</template>

<script>
export default {
  name: "SimpleCalendar",
}
</script>

接著把元件新增到畫面中,然後因為這次只有一個元件,所以我們 App.vue 要記得讓內容置中:

<template>
  <div :class="[
      'w-screen h-screen',
      'flex',
      'justify-center items-center'
    ]"
  >
    <SimpleCalendar />
  </div>
</template>

<script>
import SimpleCalendar from './components/SimpleCalendar.vue'

export default {
  data() {
    return {

    }
  },
  components: {
    SimpleCalendar,
  }
}
</script>

做了三次應該都熟悉了吧?
下一步囉!
 

carrotPoint 簡易日曆

今天碰巧看到了一張 Windows App,
那我們就做的像這個吧!

我左看右看、上看下看,
原來每個女孩,都不簡單

不是啦,是看出端倪了!

不難發現日曆分成兩大塊,分別是日曆標題區塊 以及 日曆內容區塊

但這樣切分我覺得不夠好,
還可以依照內容物屬性分類的方式:

像這樣,即使 週期區塊日期區塊 非常的類似,但分開來做的話資料處理起來也會方便許多。

我們就先來完成基本的樣式,週期的部分用 v-for 跑迴圈來產生,資料兔兔已經先準備給你們了,在這裡:

weekDays: ['Su','Mo','Tu','We','Th','Fr','Sa']

而日期的部分也是用 v-for 先產生假資料就好,那麼大概就會像是這個樣子:

<template>
  <div class="border flex flex-col rounded-md overflow-hidden">
    <div class="flex justify-center items-center p-3 border-b bg-blue-600 text-white font-bold">
      2021 9 月
    </div>
    <div class="grid grid-cols-7 place-items-center">
      <div class="w-10 h-10 text-xs font-bold flex justify-center items-center" v-for="wd in weekDays" :key="wd">
        {{ wd }}
      </div>
    </div>
    <div class="grid grid-cols-7 place-items-center">
      <div
        :class="[
          'w-10 h-10',
          'text-sm',
          'flex justify-center items-center',
          'rounded-full transition-all'
        ]"
        v-for="d in 30" :key="d"
      >
        {{ d }}
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "SimpleCalendar",
  data() {
    return {
      weekDays: ['Su','Mo','Tu','We','Th','Fr','Sa']
    }
  }
}
</script>

已經開始有模有樣了!

不過上個月的末幾天、下個月的頭幾天和今天日期都還沒有出現,那麼我們接著就來弄這些。
 

carrotPoint 細節工程

其實只要把本月 1 號跟星期幾對上,就可以了,但這樣還是不夠完整,所以我們就要想辦法來補全其他的細節。

先再來看一次原圖:

透過原圖,我們可以知道開始日若是星期二,則前面會有兩天是上個月的;以此類推,開始日若是星期四,則前面會有四天是上個月的。

再來是下個月的部分。很直觀的來看,日曆上總共規劃了 42 天,除了前個月和本月的天數之外,剩下都會拿來顯示下個月的日期,那麼規則就很清晰了。

我們來試著完成這個部分,先做出前個月和本月的。

如何抓取前個月的末幾天?

js 中的 Date() 是可以允許倒數的,這麼一來就好辦了,我們可以利用迴圈來達成。

在開始撰寫函數之前,我們先在 data 中擺設我們所需要的資料,先給個預設值:

<script>
export default {
  name: "SimpleCalendar",
  data() {
    return {
      weekDays: ['Su','Mo','Tu','We','Th','Fr','Sa'],
      year: 2021,
      month: 9,
    }
  }
}
</script>

然後在 vue 中寫一個 computed 的 lastMonth() 函數:

lastMonth() {
  const days = []

  const current = new Date(this.year,this.month-1,1)
  const wd = current.getDay()

  for(let i=wd;i>0;i--) {
    const temp = new Date(current)
    temp.setDate(current.getDate()-i)
    days.push(temp.getDate())
  }

  return days
},

我們接著把函數返回的結果利用 v-for 顯示在日曆上當作前個月的日期,文字顏色記得用 text-gray-400 改淺一點:

<div class="border flex flex-col rounded-md overflow-hidden">
  <div class="flex justify-center items-center p-3 border-b bg-blue-600 text-white font-bold">
    2021 9 月
  </div>
  <div class="grid grid-cols-7 place-items-center">
    <div class="w-10 h-10 text-xs font-bold flex justify-center items-center" v-for="wd in weekDays" :key="wd">
      {{ wd }}
    </div>
  </div>
  <div class="grid grid-cols-7 place-items-center">
    <!-- 前個月的 -->
    <div
      :class="[
        'w-10 h-10',
        'text-sm',
        'flex justify-center items-center',
        'text-gray-400',
        'rounded-full transition-all'
      ]"
      v-for="d in lastMonth" :key="d"
    >
      {{ d }}
    </div>
    
    <!-- 本月的 -->
    <div
      :class="[
        'w-10 h-10',
        'text-sm',
        'flex justify-center items-center',
        'rounded-full transition-all'
      ]"
      v-for="d in 30" :key="d"
    >
      {{ d }}
    </div>
  </div>
</div>

又離完成更近了一步~ 那接著,馬上來完成本月吧!

本月的部分跟前個月的其實算是類似,我們只要知道這個月有幾天,然後就一樣可以用迴圈來達成。 在 js 中取得當月份天數的方法是:

let count = new Date(2021,9,0).getDate()

對,你沒看錯。
就是把日期設為零,取得到的日期就會是這個月份的總天數。

那麼有了這行,我們就可以快速做完 thisMonth() 函數了:

thisMonth() {
  const days = []

  const current = new Date(this.year,this.month,0)

  for(let i=1;i<=current.getDate();i++) {
    days.push(i)
  }

  return days
}

函數寫完歸寫完,別忘了應用到 template 上啊! 修改一下,把 30 改成 thisMonth

<div
  :class="[
    'w-10 h-10',
    'text-sm',
    'flex justify-center items-center',
    'rounded-full transition-all'
  ]"
  v-for="d in thisMonth" :key="d"
>
  {{ d }}
</div>

OK,看起來沒什麼變化很正常,因為這個月本來就是 30 天。

那再來,就輪到下個月的啦!
下個月的更簡單了,我們只要計算前個月的末幾天本月天數扣掉之後,42 天還剩多少天,剩下的全部都是下個月的啦~

所以,就會像是這樣:

nextMonth() {
  const count = 42 - this.lastMonth.length - this.thisMonth.length

  const days = []

  for(let i=1;i<=count;i++) {
    days.push(i)
  }

  return days
}

還是一樣,函數寫完要記得應用到 template 中:

<div class="border flex flex-col rounded-md overflow-hidden">
  <div class="flex justify-center items-center p-3 border-b bg-blue-600 text-white font-bold">
    2021 9 月
  </div>
  <div class="grid grid-cols-7 place-items-center">
    <div class="w-10 h-10 text-xs font-bold flex justify-center items-center" v-for="wd in weekDays" :key="wd">
      {{ wd }}
    </div>
  </div>
  <div class="grid grid-cols-7 place-items-center">
    <!-- 前個月的 -->
    <div
      :class="[
        'w-10 h-10',
        'text-sm',
        'flex justify-center items-center',
        'text-gray-400',
        'rounded-full transition-all'
      ]"
      v-for="d in lastMonth" :key="d"
    >
      {{ d }}
    </div>
    
    <!-- 本月的 -->
    <div
      :class="[
        'w-10 h-10',
        'text-sm',
        'flex justify-center items-center',
        'rounded-full transition-all'
      ]"
      v-for="d in thisMonth" :key="d"
    >
      {{ d }}
    </div>
    
    <!-- 下個月的 -->
    <div
      :class="[
        'w-10 h-10',
        'text-sm',
        'flex justify-center items-center',
        'text-gray-400',
        'rounded-full transition-all'
      ]"
      v-for="d in nextMonth" :key="d"
    >
      {{ d }}
    </div>
  </div>
</div>

完全大成 ... ㄍ ...

還沒,雖然看著很順眼但別忘了,日曆標題的年份和月份要跟隨實際時間的改變啊!

<!-- 修改前 -->
<div class="flex justify-center items-center p-3 border-b bg-blue-600 text-white font-bold">
  2021 9 月
</div>

<!-- 修改後 -->
<div class="flex justify-center items-center p-3 border-b bg-blue-600 text-white font-bold">
  {{year}} {{month}} 月
</div>

那麼最後終於就來到我們的本日日期顯示啦!

先在 data 中多加上一個變數來儲存今天日期

export default {
  name: "SimpleCalendar",
  data() {
    return {
      weekDays: ['Su','Mo','Tu','We','Th','Fr','Sa'],
      year: 2021,
      month: 9,
+     today: new Date(),
    }
  },
  ...
}

好了之後,我們就可以在 template 上動手腳啦!

把我們顯示當月日期的部分,在 tailwind 的樣式上用三元運算子加一行判斷,如果確定是本日日期,就將底色設定成藍色

<div
  :class="[
    'w-10 h-10',
    'text-sm',
    'flex justify-center items-center',
    (
      (year===today.getFullYear() && month-1===today.getMonth() && d===today.getDate())
      ?
      'bg-blue-600 text-white font-bold'
      :
      'hover:bg-gray-200'
    ),
    'rounded-full transition-all'
  ]"
  v-for="d in thisMonth" :key="d"
>
  {{ d }}
</div>

改好後就會發現! 完成了~

不過 ... 還有一個小問題存在著哦!
(你又來了...)

就是再過幾天這個日曆就會出 bug 了~
為什麼呢? 因為我們 data 中的 year 和 month 還是寫死的呀!

所以我們要在 mounted 的時候,依照今日時間,覆寫掉那兩個變數的內容:

mounted() {
  this.today = new Date()
  this.year = this.today.getFullYear()
  this.month = this.today.getMonth() + 1
}

這樣就真的全部完成啦~~
 

老實說,今天的好像也不難嘛 (?)

不過我留了一個挑戰給你們,
在作業中。

有興趣的人玩玩看嘿!

carrotPoint 給你們的回家作業:

  • 作業實施要點:
    • 實作一個自己的簡易日曆
    • 進階挑戰 - 讓日曆可以按按鈕切換月份
  • 範例成品:

關於兔兔們:


 


( # 兔兔小聲說 )

其實,我做的是不是叫做月曆,而不是日曆啊 ...?

剛剛才發現日曆是 ...


上一篇
Day 27:「流浪到淡水!」- 手風琴選單
下一篇
Day 29:「欸!你填一下這張表!」- 彈跳視窗、Modal
系列文
排版神器 Tailwind CSS~和兔兔一起快速上手漂亮的元件開發!32

1 則留言

1
jason06286
iT邦新手 5 級 ‧ 2021-09-30 14:07:47

Hi 兔兔
我來交作業了!!請問兔兔如果寫很多 computed 是否會影像效能?
還是其實還好!!
點我預覽
專案連結

我要留言

立即登入留言