vue時(shí)間組件DatePicker組件的手寫示例
概述
在日常工作中,比不可少會(huì)用到時(shí)間組件,我們的第一反應(yīng)就是直接到組件庫去找一下現(xiàn)成的來用下,畢竟,時(shí)間組件看起來還是很復(fù)雜的,對于沒接觸過的人來說,要自己去寫一個(gè)這樣的組件出來,還是有難度的,但是作為一名前端開發(fā),這么常見的組件,我們還是值得取自己寫一個(gè)這樣的組件的,現(xiàn)在就手把手帶你實(shí)現(xiàn)vue中的DatePicker組件??赐瓴粫?huì)找我。
前置知識(shí)
在開始寫代碼之前,建議代價(jià)先看看時(shí)間組件的布局,目前主流的組件庫iview ,element等提供的時(shí)間組件布局都基本類似,功能也差不多,因此在這里實(shí)現(xiàn)的組件庫的布局也也element家的布局差不多,大家先看下布局的最終樣子。 這是日的時(shí)間組件,當(dāng)我們這個(gè)能實(shí)現(xiàn)的時(shí)候,像那些年,月的就更簡單了,因此這里我們只實(shí)現(xiàn)一個(gè),其他的可以自己擴(kuò)展。

布局方面
- 我們可以看到最終時(shí)間組件可以拆分為兩大部分,最上面可以拆分為一個(gè)時(shí)間切換的組件,下面為一個(gè)table,用于記錄日期,下面的表格我們又可以拆分為上面的星期組件,和下面的日期具體實(shí)現(xiàn)組件。
- 然后,我們注意觀察,表格是一個(gè)6行七列共計(jì)42個(gè)單元格的布局形式,包含上月的剩余天數(shù),當(dāng)前月份的全部天數(shù),以及下月的開始天數(shù),加起來組成42個(gè)單元格。其中上月和下月我們布局樣式區(qū)別于當(dāng)前月份的布局,還有就是當(dāng)前天數(shù)的那個(gè)日期,我們需要高亮顯示。
具體的實(shí)現(xiàn)思路
- 在清楚布局之后,我們需要根據(jù)用戶傳入的時(shí)間,生成一個(gè)6*7=42的天數(shù)的td單元格,在這42個(gè)單元格中,包含上月剩余的天數(shù),當(dāng)前月份的全部天數(shù),下月的開始天數(shù)。
- 如果你清楚了步驟一,那么我們接下來就容易多了,我們要計(jì)算上月的天數(shù),當(dāng)前月份的全部天數(shù),下月的開始天數(shù),以及當(dāng)前月份1號(hào)星期幾。
- 由于頭部是星期日,星期一,星期二,星期三,星期四,星期五,星期六的布局,因此我們需要計(jì)算當(dāng)前月份1號(hào)星期幾,這樣我們就能找到上月的剩余天數(shù)了,下月的剩余天數(shù)就等于42-當(dāng)前月份天數(shù)-上月剩余天數(shù)。
- 最后我們就需要提供計(jì)算月份天數(shù),月份1號(hào)星期幾,以及一個(gè)生成對應(yīng)數(shù)據(jù)的工具函數(shù)了。
具體實(shí)現(xiàn)
目錄結(jié)構(gòu)

utils.js(工具函數(shù)(核心))
- .components/DatePicker/utils.js
/**
* @Description 獲取當(dāng)前月份的天數(shù)
* @param { Array } 年月組成的數(shù)組,例如:[2022,7]
* @return {Number}例如:2022年7月有31天 返回31
**/
export function getCurrentMonthCount([year, month]) {
// 當(dāng)我們實(shí)例化Date函數(shù)的時(shí)候,傳入第三個(gè)參數(shù)為0可以通過getDate獲取到當(dāng)前月份具體有多少天
return new Date(year, month, 0).getDate();
}
/**
* @Description 獲取當(dāng)前月份1號(hào)是星期二幾
* @param { Array } 年月組成的數(shù)組,例如:[2022,7]
* @return {Number} 例如2022-7-1是星期5,返回5
**/
export function getFirstMonthDayWeek([year, month]) {
return new Date(year, month - 1, 1).getDay();
}
/**
* @Description 根據(jù)年月,組裝渲染天的表格數(shù)據(jù)
* @param { Array } 年月組成的數(shù)組,例如:[2022,7]
* @return {Array}
**/
/*
在這里介紹下我們時(shí)間組件寫法的思路:
1.對于時(shí)間組件的布局,可以先去參考iview element等開源組件庫的date-picker組件的布局,基本上都是一樣的
2.在清楚布局之后,我們需要根據(jù)用戶傳入的時(shí)間,生成一個(gè)6*7=42的天數(shù)的td單元格,在這42個(gè)單元格中,包含上月剩余的天數(shù),當(dāng)前月份的全部天數(shù),下月的開始天數(shù)
3.如果你清楚了步驟二,那么我們接下來就容易多了,我們要計(jì)算上月的天數(shù),當(dāng)前月份的全部天數(shù),下月的開始天數(shù),以及當(dāng)前月份1號(hào)星期幾
4.由于頭部是星期日,星期一,星期二,星期三,星期四,星期五,星期六的布局,因此我們需要計(jì)算當(dāng)前月份1號(hào)星期幾,這樣我們就能找到上月的剩余天數(shù)了,下月的剩余天數(shù)就等于42-當(dāng)前月份天數(shù)-上月剩余天數(shù)
5.在上面步驟知道后我們就可以著手根據(jù)上面提供的工具函數(shù),生成我們需要的表格數(shù)據(jù)了
最終生成的是6*7的二維數(shù)組,因?yàn)楸砀裉鞌?shù)的布局為6*7的布局,數(shù)據(jù)格式如下:
數(shù)組的個(gè)數(shù)代表了渲染的列數(shù),內(nèi)部每項(xiàng)數(shù)組代表每列的td個(gè)數(shù)
[
[
{
//代表當(dāng)前的td幾號(hào)
value: xxx,
//上個(gè)月的號(hào)數(shù)和下個(gè)月的號(hào)數(shù)標(biāo)識(shí)下,渲染的時(shí)候,我們樣式另外布局
disbled: true,
//當(dāng)前td的時(shí)間格式,用于點(diǎn)擊了,給input顯示以及供用戶使用格式為2022-7-22
date: xxx,
// 當(dāng)前天的時(shí)間td,我們需要高亮顯示。添加標(biāo)識(shí)
active:xxx,
//當(dāng)前td的索引
index: xxx,
},
{},
{},
{},
{},
{},
{}
],
[],
[],
[],
[],
[],
]
*/
export function genarateDayData([year, month]) {
// 獲取上月天數(shù)
let lastMonthCount = getCurrentMonthCount([year, month - 1]);
// 獲取當(dāng)月天數(shù)
let currentMonthCount = getCurrentMonthCount([year, month]);
// 獲取當(dāng)月1號(hào)星期
let currentMonthFirstDayWeek = getFirstMonthDayWeek([year, month]);
let dayList = [];
let lastMonthPointer = 1;
let currentMonthPoiner = 1;
let nextMonthPointer = 1;
// 根據(jù)日期組件的天數(shù)布局,共計(jì)42天,包含上月剩余天數(shù)+當(dāng)月天數(shù)+下月初始天數(shù)
for (let i = 0; i < 42; i++) {
// 上個(gè)月需要渲染的td個(gè)數(shù),以及對應(yīng)的值
if (lastMonthPointer <= currentMonthFirstDayWeek) {
// 上月
dayList.unshift({
value: lastMonthCount--,
disbled: true,
date: year + "-" + (month - 1) + "-" + (lastMonthCount + 1),
index: i,
});
lastMonthPointer++;
} else if (currentMonthPoiner <= currentMonthCount) {
// 當(dāng)月
dayList.push({
value: currentMonthPoiner++,
disbled: false,
active:
new Date().getFullYear() == year &&
new Date().getMonth() + 1 == month &&
currentMonthPoiner - 1 == new Date().getDate(),
date: year + "-" + month + "-" + (currentMonthPoiner - 1),
index: i,
});
} else {
// 下月
dayList.push({
value: nextMonthPointer++,
disbled: true,
date: year + "-" + (month + 1) + "-" + (nextMonthPointer - 1),
index: i,
});
}
}
// 當(dāng)前天數(shù)高亮
// 最后將數(shù)據(jù)生成二維數(shù)組返回:對應(yīng)的就是6*7的二維數(shù)組用于渲染天數(shù)表格列
let result = [];
let index = 1;
let i = 0;
while (index <= 6) {
let arr = [];
for (let j = 0; j < 7; j++) {
arr.push(dayList[i]);
i++;
}
result.push(arr);
index++;
}
return result;
}
constant.js
- .components/DatePicker/constant.js(常量文件)
//用于保存組建的常量,靜態(tài)數(shù)據(jù),比如表頭的星期 export const weekList = ["日", "一", "二", "三", "四", "五", "六"];
DatePicker.vue
- .components/DatePicker/DatePicker.vue(整體包裹組件供外部使用)
<template>
<div class="date-picker-wrap">
<div class="date-eidtor">
<!-- 顯示時(shí)間的input -->
<input
type="text"
:placeholder="placeholder"
class="date-edit-input"
v-model="currentDate"
@click.stop="showDatePannel = !showDatePannel"
/>
</div>
<!-- 面包通過過渡組件包裹,實(shí)現(xiàn)顯示隱藏友好過渡 -->
<transition name="date-picker">
<div class="date-pocker-panel" v-show="showDatePannel">
<!-- 時(shí)間控件的頭部,用戶切換年月 -->
<date-picker-head
@dateRangeChange="dateRangeChange"
:date="curDate"
></date-picker-head>
<!-- 主要的時(shí)間顯示列表組件,用于顯示對應(yīng)月份的時(shí)間 -->
<date-table :list="list" @dateChange="dateChange"></date-table>
</div>
</transition>
</div>
</template>
<script>
import { genarateDayData } from "./utils";
import DatePickerHead from "./DatePickerHead.vue";
import DateTable from "./DateTable.vue";
export default {
components: {
DatePickerHead,
DateTable,
},
props: {
// 輸入框提示
placeholder: {
type: String,
default: "選擇時(shí)間",
},
// 時(shí)間,為Date類型,默認(rèn)為當(dāng)前時(shí)間
date: {
type: Date,
default() {
return new Date();
},
},
},
data() {
return {
// 用于控制面包顯示與隱藏
showDatePannel: false,
// 表格數(shù)據(jù)
list: [],
// 處理props時(shí)間為數(shù)組格式[年,月]
curDate: [this.date.getFullYear(), this.date.getMonth() + 1],
// 用戶input顯示時(shí)間
currentDate: "",
};
},
mounted() {
// 獲取當(dāng)前月份的時(shí)間數(shù)據(jù)
this.getDateList();
// 除開時(shí)間組件的其他地方點(diǎn)擊,關(guān)閉時(shí)間面板
window.addEventListener("click", () => {
this.showDatePannel = false;
});
},
methods: {
// 監(jiān)聽每個(gè)td時(shí)間項(xiàng)點(diǎn)擊
dateChange(date) {
this.$emit("dateChange", date);
this.showDatePannel = false;
this.currentDate = date;
},
// 頭部年月切換
dateRangeChange(type) {
switch (type) {
// 上一年點(diǎn)擊
case "lastYear":
this.curDate = [this.curDate[0] - 1, this.curDate[1]];
break;
// 上一月點(diǎn)擊(月份<1,就要返回到上一年的12月份)
case "lastMonth":
this.curDate = [
this.curDate[1] - 1 <= 0 ? this.curDate[0] - 1 : this.curDate[0],
this.curDate[1] - 1 <= 0 ? 12 : this.curDate[1] - 1,
];
break;
// 下一年點(diǎn)擊
case "nextYear":
this.curDate = [this.curDate[0] + 1, this.curDate[1]];
break;
case "nextMonth":
// 下一月點(diǎn)擊(月份>12,就要到下一年的一月份)
this.curDate = [
this.curDate[1] + 1 > 12 ? this.curDate[0] + 1 : this.curDate[0],
this.curDate[1] + 1 > 12 ? 1 : this.curDate[1] + 1,
];
break;
}
this.getDateList();
},
// 通過props傳遞的時(shí)間,組裝成長度為42的數(shù)組,具體看utils文件下下面的這個(gè)方法
getDateList() {
this.list = genarateDayData(this.curDate);
},
},
};
</script>
<style lang="less">
.date-picker-wrap {
position: relative;
.date-pocker-panel {
position: absolute;
left: 0;
top: 50px;
width: 324px;
height: 343px;
color: #606266;
border: 1px solid #e4e7ed;
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
background: #fff;
border-radius: 4px;
line-height: 30px;
padding: 12px;
text-align: center;
}
.date-eidtor {
width: 220px;
.date-edit-input {
background-color: #fff;
background-image: none;
border-radius: 4px;
border: 1px solid #dcdfe6;
box-sizing: border-box;
color: #606266;
display: inline-block;
font-size: inherit;
height: 40px;
line-height: 40px;
outline: none;
padding: 0 15px;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
width: 100%;
}
}
.date-picker-enter-active,
.date-picker-leave-active {
transition: all 0.25s ease;
}
.date-picker-enter,
.date-picker-leave-to {
opacity: 0;
height: 0;
}
}
</style>
DatePickerHead.vue
- .components/DatePicker/DatePickerHead.vue(頂部時(shí)間切換組件)
<template>
<!-- 頂部的時(shí)間切換組件 -->
<div class="date-picker-head">
<div class="arrow-left">
<!-- 上一年點(diǎn)擊 -->
<span class="last-year arrow" @click.stop="toogleDate('lastYear')"></span>
<!-- 上一月點(diǎn)擊 -->
<span class="last-month arrow" @click.stop="toogleDate('lastMonth')"></span>
</div>
<!-- 顯示當(dāng)前的年和月 -->
<div class="date-content">{{ date[0] + "年" + date[1] + "月" }}</div>
<div class="arrow-right">
<!-- 下一月點(diǎn)擊 -->
<span class="next-month arrow" @click.stop="toogleDate('nextMonth')"></span>
<!-- 下一年點(diǎn)擊 -->
<span class="next-year arrow" @click.stop="toogleDate('nextYear')"></span>
</div>
</div>
</template>
<script>
export default {
props: {
// 時(shí)間
date: {
type: Array,
default() {
return [new Date().getFullYear(), new Date().getMonth() + 1];
},
},
},
methods: {
// 派發(fā)table事件處理邏輯,參數(shù)為當(dāng)前td的時(shí)間,格式為2022-7-22
toogleDate(type) {
this.$emit("dateRangeChange", type);
},
},
};
</script>
<style lang="less">
.date-picker-head {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
.last-year,
.next-month {
margin-right: 15px;
}
.arrow {
cursor: pointer;
}
}
</style>
DateTable.vue(表格組件)
- .components/DatePicker/DateTable.vue
<template>
<div class="date-table">
<!-- 時(shí)間表格 -->
<table>
<!-- 頂部的星期組件 -->
<date-picker-week-bar></date-picker-week-bar>
<!-- 下方的6*7的月份天數(shù)組件 -->
<date-picker-day-content
:list="list"
@dateChange="dateChange"
></date-picker-day-content>
</table>
</div>
</template>
<script>
import DatePickerWeekBar from "./DatePickerWeekBar.vue";
import DatePickerDayContent from "./DatePickerDayContent.vue";
export default {
props: {
// 表格數(shù)據(jù)
list: {
type: Array,
default() {
return [];
},
},
},
components: {
DatePickerWeekBar,
DatePickerDayContent,
},
methods: {
// 派發(fā)td天數(shù)點(diǎn)擊事件,參數(shù)為當(dāng)前天數(shù)的時(shí)間格式為 2022-07-22
dateChange(date) {
this.$emit("dateChange", date);
},
},
};
</script>
<style lang="less">
.date-table {
font-size: 12px;
}
</style>
DatePickerWeekBar.vue(表頭組件,渲染星期)
- .components/DatePicker/DatePickerWeekBar.vue
<template>
<thead class="date-picker-week-bar">
<tr>
<th v-for="item in weekList" :key="item">{{ item }}</th>
</tr>
</thead>
</template>
<script>
import { weekList } from "./constant.js";
export default {
data() {
return {
weekList,
};
},
};
</script>
<style lang="less">
.date-picker-week-bar {
th {
width: 42px;
height: 42px;
color: #606266;
font-weight: 400;
border-bottom: 1px solid #ebeef5;
}
}
</style>
DatePickerDayContent.vue
表格主題內(nèi)容組件,用于渲染具體日期
- .components/DatePicker/DatePickerDayContent.vue
<template>
<tbody class="date-picker-day-content">
<tr v-for="(item, index) in list" :key="index">
<td
v-for="(subItem, index) in item"
:key="index"
:class="[
subItem.disbled ? 'disble-item' : 'day-item',
subItem.active ? 'active' : '',
subItem.index == currentDay ? 'active-click' : '',
]"
@click="handleDayClick(subItem)"
>
{{ subItem.value }}
</td>
</tr>
</tbody>
</template>
<script>
export default {
props: {
//表格數(shù)據(jù)
list: {
type: Array,
default() {
return [];
},
},
},
data() {
return {
//當(dāng)前點(diǎn)擊項(xiàng)活躍高亮
currentDay: -1,
};
},
methods: {
// 處理天的表格點(diǎn)擊,觸發(fā)關(guān)閉時(shí)間控件面板,設(shè)置時(shí)間input的值
handleDayClick(item) {
if (item.currentDay == item.index) return;
this.currentDay = item.index;
this.$emit("dateChange", item.date);
},
},
};
</script>
<style lang="less">
.date-picker-day-content {
td {
width: 40px;
height: 40px;
color: #606266;
font-weight: 400;
text-align: center;
cursor: pointer;
}
.disble-item {
cursor: not-allowed;
color: #c0c4cc;
}
.day-item.active {
color: #008c8c;
font-weight: bold;
}
.day-item.active-click {
border-radius: 50%;
width: 30px;
height: 30px;
line-height: 30px;
color: #fff;
background-color: #008c8c;
}
}
</style>
index.js(按需導(dǎo)出文件)
- .components/DatePicker/index.js
import DatePicker from "./DatePicker.vue";
export { DatePicker };
使用
- App.vue
<template>
<div id="app">
<div class="test-date-picker">
<date-picker :date="date" @dateChange="dateChange"></date-picker>
</div>
</div>
</template>
<script>
import { DatePicker } from "./components/DatePicker/index";
export default {
name: "App",
components: {
DatePicker,
},
data() {
return {
date: new Date(),
};
},
};
</script>
<style lang="less">
html,
body,
#app {
height: 100%;
width: 100%;
}
#app {
.test-date-picker {
width: 50%;
margin: 20px auto;
}
}
</style>
最終效果

總結(jié)
組件庫很多看著很難的組件,只要我們認(rèn)真斟酌,然后試著去是實(shí)現(xiàn)下,還是不難的,實(shí)現(xiàn)上面的這種類型的組件之后,其他的年和月類型的就更簡答了,大家可以自己擴(kuò)展,更多關(guān)于vue時(shí)間組件DatePicker組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue 動(dòng)態(tài)設(shè)置img的src地址無效,npm run build 后找不到文件的解決
這篇文章主要介紹了vue 動(dòng)態(tài)設(shè)置img的src地址無效,npm run build 后找不到文件的解決,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07
vue3 選中對話框時(shí)對話框右側(cè)出一個(gè)箭頭效果
本文主要介紹了Vue3實(shí)現(xiàn)選中對話框帶箭頭效果的方法,首先通過后臺(tái)獲取數(shù)據(jù)進(jìn)行遍歷,利用ts代碼判斷選中下標(biāo)與循環(huán)游標(biāo)是否一致以改變樣式,感興趣的朋友一起看看吧2024-10-10
項(xiàng)目中如何使用axios過濾多次重復(fù)請求詳解
在項(xiàng)目開發(fā)中經(jīng)常需要處理重復(fù)點(diǎn)擊導(dǎo)致多次調(diào)用接口的問題,這篇文章主要介紹了項(xiàng)目中如何使用axios過濾多次重復(fù)請求的相關(guān)資料,需要的朋友可以參考下2021-07-07
Vue項(xiàng)目保存代碼之后頁面自動(dòng)更新問題
這篇文章主要介紹了Vue項(xiàng)目保存代碼之后頁面自動(dòng)更新問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10
vue-quill-editor 自定義工具欄和自定義圖片上傳路徑操作
這篇文章主要介紹了vue-quill-editor 自定義工具欄和自定義圖片上傳路徑操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08
vue項(xiàng)目webpack中配置src路徑別名及使用方式
這篇文章主要介紹了vue項(xiàng)目webpack中配置src路徑別名及使用方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03

