摘要:實(shí)現(xiàn)代碼于文章末尾處構(gòu)思頁(yè)面結(jié)構(gòu)組件由輸入框和日歷面板組成,寫好頁(yè)面主體結(jié)構(gòu)。輸入框點(diǎn)擊顯示或隱藏日歷面板方法改變布爾值控制日歷面板的顯示隱藏。同時(shí),當(dāng)組件銷毀時(shí),也要及時(shí)清除該監(jiān)聽(tīng)器。
最近研究了 DatePicker 的實(shí)現(xiàn)原理后做了一個(gè) vue 的 DatePicker 組件,今天帶大家一步一步實(shí)現(xiàn) DatePicker 的 vue 組件。
原理DatePicker 的原理是——計(jì)算日歷面板中當(dāng)月或選中月份的總天數(shù)及前后月份相近的日子,根據(jù)點(diǎn)擊事件計(jì)算日歷面板顯示內(nèi)容,以及將所選值賦值給標(biāo)簽。實(shí)現(xiàn)
CSS 代碼于文章末尾處
1. 構(gòu)思頁(yè)面結(jié)構(gòu)DatePicker 組件由輸入框和日歷面板組成,寫好頁(yè)面主體結(jié)構(gòu)。
輸入框點(diǎn)擊顯示或隱藏日歷面板,openPanel()方法改變 panelState 布爾值控制日歷面板的顯示隱藏。
日歷面板由頂部條和面板兩部分組成,而面板則由年份選擇面板,月份選擇面板,日期選擇面板所組成,結(jié)構(gòu)如下:
2. 頁(yè)面數(shù)據(jù)實(shí)現(xiàn)
- {{item}}
- {{item.label}}
{{item.label}}
DatePicker 所對(duì)應(yīng)的 data 代碼
data() { return { dateValue: "", // 輸入框顯示日期 date: new Date().getDate(), // 當(dāng)前日期 panelState: false, // 初始值,默認(rèn)panel關(guān)閉 tmpMonth: new Date().getMonth(), // 臨時(shí)月份,可修改 month: new Date().getMonth(), tmpYear: new Date().getFullYear(), // 臨時(shí)年份,可修改 weekList: [ { label: "Sun", value: 0 }, { label: "Mon", value: 1 }, { label: "Tue", value: 2 }, { label: "Wed", value: 3 }, { label: "Thu", value: 4 }, { label: "Fri", value: 5 }, { label: "Sat", value: 6 } ], // 周 monthList: [ { label: "Jan", value: 0 }, { label: "Feb", value: 1 }, { label: "Mar", value: 2 }, { label: "Apr", value: 3 }, { label: "May", value: 4 }, { label: "Jun", value: 5 }, { label: "Jul", value: 6 }, { label: "Aug", value: 7 }, { label: "Sept", value: 8 }, { label: "Oct", value: 9 }, { label: "Nov", value: 10 }, { label: "Dec", value: 11 } ], // 月 nowValue: 0, // 當(dāng)前選中日期值 panelType: "date" // 面板狀態(tài) }; },
DatePicker 的核心在于日期面板的數(shù)據(jù)。我們知道,一個(gè)月最多31天,最少28天。面板按周日至周六設(shè)計(jì),最極端的情況如下:
最多的極端情況:
日 | 一 | 二 | 三 | 四 | 五 | 六 |
---|---|---|---|---|---|---|
* | * | * | * | * | * | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 1 | 2 | 3 | 4 | 5 |
最少的極端情況:
日 | 一 | 二 | 三 | 四 | 五 | 六 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
根據(jù)上表我們可以得知一個(gè)月最多占六個(gè)星期,最少四個(gè)星期,所以日歷面板必須設(shè)計(jì)為 6 行,剩余的用下個(gè)月的日期補(bǔ)上,最多補(bǔ)14天。因此日期數(shù)組可以這么設(shè)計(jì):
computed: { dateList() { //獲取當(dāng)月的天數(shù) let currentMonthLength = new Date( this.tmpYear, this.tmpMonth + 1, 0 ).getDate(); //先將當(dāng)月的日期塞入dateList let dateList = Array.from( { length: currentMonthLength }, (val, index) => { return { currentMonth: true, value: index + 1 }; } ); // 獲取當(dāng)月1號(hào)的星期是為了確定在1號(hào)前需要插多少天 let startDay = new Date(this.tmpYear, this.tmpMonth, 1).getDay(); // 確認(rèn)上個(gè)月一共多少天 let previousMongthLength = new Date( this.tmpYear, this.tmpMonth, 0 ).getDate(); // 在1號(hào)前插入上個(gè)月日期 for (let i = 0, len = startDay; i < len; i++) { dateList = [ { previousMonth: true, value: previousMongthLength - i } ].concat(dateList); } // 補(bǔ)全剩余位置,至少14天,則 i < 15 for (let i = 1, item = 1; i < 15; i++, item++) { dateList[dateList.length] = { nextMonth: true, value: i }; } return dateList; }, }
changeTmpMonth 為選擇月份后顯示的文案,yearList 為年份列表,為了與月份數(shù)量保持一致,我們也設(shè)長(zhǎng)度為12.
computed: { changeTmpMonth() { return this.monthList[this.tmpMonth].label; }, // 通過(guò)改變this.tmpYear則可以改變年份數(shù)組 yearList() { return Array.from({ length: 12 }, (value, index) => this.tmpYear + index); } }3. 實(shí)現(xiàn)頁(yè)面功能
(1)面板切換功能
點(diǎn)擊輸入框,除了打開(kāi)日歷面板,同時(shí)也默認(rèn)為日期面板
openPanel() { this.panelState = !this.panelState; this.panelType = "date"; },
點(diǎn)擊 2018 年份進(jìn)入年份面板,點(diǎn)擊相對(duì)應(yīng)年份顯示該年份并進(jìn)入月份選擇面板
{{tmpYear}}
selectYear(item) { this.tmpYear = item; this.panelType = "month"; },
點(diǎn)擊 Aug 月份進(jìn)入月份面板,點(diǎn)擊相對(duì)應(yīng)月份顯示該月份并進(jìn)入日期選擇面板
{{changeTmpMonth}}
selectMonth(item) { this.tmpMonth = item.value; this.panelType = "date"; },
點(diǎn)擊日期選擇日期,關(guān)閉面板同時(shí)賦值給輸入框
// methods selectDate(item) { // 賦值 當(dāng)前 nowValue,用于控制樣式突出顯示當(dāng)前月份日期 this.nowValue = item.value; // 選擇了上個(gè)月 if (item.previousMonth) this.tmpMonth--; // 選擇了下個(gè)月 if (item.nextMonth) this.tmpMonth++; // 獲取選中日期的 date let selectDay = new Date(this.tmpYear, this.tmpMonth, this.nowValue); // 格式日期為字符串后,賦值給 input this.dateValue = this.formatDate(selectDay.getTime()); // 關(guān)閉面板 this.panelState = !this.panelState; }, // 日期格式方法 formatDate(date, fmt = this.format) { if (date === null || date === "null") { return "--"; } date = new Date(Number(date)); var o = { "M+": date.getMonth() + 1, // 月份 "d+": date.getDate(), // 日 "h+": date.getHours(), // 小時(shí) "m+": date.getMinutes(), // 分 "s+": date.getSeconds(), // 秒 "q+": Math.floor((date.getMonth() + 3) / 3), // 季度 S: date.getMilliseconds() // 毫秒 }; if (/(y+)/.test(fmt)) fmt = fmt.replace( RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length) ); for (var k in o) { if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace( RegExp.$1, RegExp.$1.length === 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length) ); } return fmt; }, // 確認(rèn)是否為當(dāng)前月份 validateDate(item) { if (this.nowValue === item.value && item.currentMonth) return true; },
(2)topbar 中左右箭頭功能,具體詳看下面方法
// < left() { if (this.panelType === "year") this.tmpYear--; else { if (this.tmpMonth === 0) { this.tmpYear--; this.tmpMonth = 11; } else this.tmpMonth--; } }, // << leftBig() { if (this.panelType === "year") this.tmpYear -= 12; else this.tmpYear--; }, // > right() { if (this.panelType === "year") this.tmpYear++; else { if (this.tmpMonth === 11) { this.tmpYear++; this.tmpMonth = 0; } else this.tmpMonth++; } }, // >> rightBig() { if (this.panelType === "year") this.tmpYear += 12; else this.tmpYear++; },
(3) 實(shí)現(xiàn)輸入框的雙向綁定及格式規(guī)定
props
props: { value: { type: [Date, String], default: "" }, format: { type: String, default: "yyyy-MM-dd" } },
其中 value 支持日期格式和字符串,當(dāng)設(shè)置了props時(shí),則需在monted鉤子函數(shù)中初始化input 值。format 默認(rèn)值為 "yyyy-MM-dd", 當(dāng)然你也可以設(shè)置為 "dd-MM-yyyy"等。
mounted() { if (this.value) { this.dateValue = this.formatDate(new Date(this.value).getTime()); } },
雙向綁定父組件賦值 props 為 value, 子組件傳遞的事件為input, 因此需在 selectDate 方法中 emit 事件及數(shù)據(jù)給父組件
selectDate(item) { ... this.$emit("input", selectDay); },
這樣,父組件便可以進(jìn)行雙向綁定了
(4)點(diǎn)擊頁(yè)面其他位置收起日歷面板
原理監(jiān)聽(tīng)頁(yè)面的點(diǎn)擊事件,檢測(cè)到有點(diǎn)擊事件時(shí)關(guān)閉面板,但點(diǎn)擊組件內(nèi)容時(shí)也會(huì)觸發(fā)點(diǎn)擊事件,因此需要在組件內(nèi)部阻止冒泡。同時(shí),當(dāng)組件銷毀時(shí),也要及時(shí)清除該監(jiān)聽(tīng)器。
組件最外層阻止冒泡
頁(yè)面創(chuàng)建設(shè)置監(jiān)聽(tīng)
mounted() { ... window.addEventListener("click", this.eventListener); }
頁(yè)面銷毀清除監(jiān)聽(tīng)
destroyed() { window.removeEventListener("click", this.eventListener); }
公共方法
eventListener() { this.panelState = false; },
項(xiàng)目Demo
項(xiàng)目源碼
有用就點(diǎn)個(gè)贊唄~
最后,貼上 CSS 代碼...fadeDownBig 后面的樣式為 vue
.topbar { padding-top: 8px; } .topbar span { display: inline-block; width: 20px; height: 30px; line-height: 30px; color: #515a6e; cursor: pointer; } .topbar span:hover { color: #2d8cf0; } .topbar .year, .topbar .month { width: 60px; } .year-list { height: 200px; width: 210px; } .year-list .selected { background: #2d8cf0; border-radius: 4px; color: #fff; } .year-list li { display: inline-block; width: 70px; height: 50px; line-height: 50px; border-radius: 10px; cursor: pointer; } .year-list span { display: inline-block; line-height: 16px; padding: 8px; } .year-list span:hover { background: #e1f0fe; } .weekday { display: inline-block; font-size: 13px; width: 30px; color: #c5c8ce; text-align: center; } .date-picker { width: 210px; text-align: center; font-family: "Avenir", Helvetica, Arial, sans-serif; } .date-panel { width: 210px; box-shadow: 0 0 8px #ccc; background: #fff; } ul { list-style: none; padding: 0; margin: 0; } .date-list { width: 210px; text-align: left; height: 180px; overflow: hidden; margin-top: 4px; } .date-list li { display: inline-block; width: 28px; height: 28px; line-height: 30px; text-align: center; cursor: pointer; color: #000; border: 1px solid #fff; border-radius: 4px; } .date-list .selected { border: 1px solid #2d8cf0; } .date-list .invalid { background: #2d8cf0; color: #fff; } .date-list .preMonth, .date-list .nextMonth { color: #c5c8ce; } .date-list li:hover { background: #e1f0fe; } input { display: inline-block; box-sizing: border-box; width: 100%; height: 32px; line-height: 1.5; padding: 4px 7px; font-size: 12px; border: 1px solid #dcdee2; border-radius: 4px; color: #515a6e; background-color: #fff; background-image: none; position: relative; cursor: text; transition: border 0.2s ease-in-out, background 0.2s ease-in-out, box-shadow 0.2s ease-in-out; margin-bottom: 6px; } .fadeDownBig-enter-active, .fadeDownBig-leave-active, .fadeInDownBig { -webkit-animation-duration: 0.5s; animation-duration: 0.5s; -webkit-animation-fill-mode: both; animation-fill-mode: both; } .fadeDownBig-enter-active { -webkit-animation-name: fadeInDownBig; animation-name: fadeInDownBig; } .fadeDownBig-leave-active { -webkit-animation-name: fadeOutDownBig; animation-name: fadeOutDownBig; } @-webkit-keyframes fadeInDownBig { from { opacity: 0.8; -webkit-transform: translate3d(0, -4px, 0); transform: translate3d(0, -4px, 0); } to { opacity: 1; -webkit-transform: none; transform: none; } } @keyframes fadeInDownBig { from { opacity: 0.8; -webkit-transform: translate3d(0, -4px, 0); transform: translate3d(0, -4px, 0); } to { opacity: 1; -webkit-transform: none; transform: none; } } @-webkit-keyframes fadeOutDownBig { from { opacity: 1; } to { opacity: 0.8; -webkit-transform: translate3d(0, -4px, 0); transform: translate3d(0, -4px, 0); } } @keyframes fadeOutDownBig { from { opacity: 1; } to { opacity: 0; } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/97640.html
摘要:在上家公司裸辭之后,經(jīng)過(guò)一段時(shí)間休整,月中下旬面試了一些公司,由于本人框架使用的是,所以面試題涉及到框架的都是,現(xiàn)將面試題整理一下列舉常用的特性。事件冒泡以及事件捕獲。其他前端分頁(yè)和后端分頁(yè)優(yōu)缺點(diǎn)。包含多個(gè)子節(jié)點(diǎn)及孫節(jié)點(diǎn),遍歷。 在上家公司裸辭之后,經(jīng)過(guò)一段時(shí)間休整,5月中下旬面試了一些公司,由于本人框架使用的是vue,所以面試題涉及到框架的都是vue,現(xiàn)將面試題整理一下: es6 ...
摘要:在上家公司裸辭之后,經(jīng)過(guò)一段時(shí)間休整,月中下旬面試了一些公司,由于本人框架使用的是,所以面試題涉及到框架的都是,現(xiàn)將面試題整理一下列舉常用的特性。事件冒泡以及事件捕獲。其他前端分頁(yè)和后端分頁(yè)優(yōu)缺點(diǎn)。包含多個(gè)子節(jié)點(diǎn)及孫節(jié)點(diǎn),遍歷。 在上家公司裸辭之后,經(jīng)過(guò)一段時(shí)間休整,5月中下旬面試了一些公司,由于本人框架使用的是vue,所以面試題涉及到框架的都是vue,現(xiàn)將面試題整理一下: es6 ...
摘要:在他的重學(xué)前端課程中提到到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系中的重要崗位之一。大部分前端工程師的知識(shí),其實(shí)都是來(lái)自于實(shí)踐和工作中零散的學(xué)習(xí)。一基礎(chǔ)前端工程師吃飯的家伙,深度廣度一樣都不能差。 開(kāi)篇 前端開(kāi)發(fā)是一個(gè)非常特殊的行業(yè),它的歷史實(shí)際上不是很長(zhǎng),但是知識(shí)之繁雜,技術(shù)迭代速度之快是其他技術(shù)所不能比擬的。 winter在他的《重學(xué)前端》課程中提到: 到現(xiàn)在為止,前端工程師已經(jīng)成為研...
摘要:在他的重學(xué)前端課程中提到到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系中的重要崗位之一。大部分前端工程師的知識(shí),其實(shí)都是來(lái)自于實(shí)踐和工作中零散的學(xué)習(xí)。一基礎(chǔ)前端工程師吃飯的家伙,深度廣度一樣都不能差。開(kāi)篇 前端開(kāi)發(fā)是一個(gè)非常特殊的行業(yè),它的歷史實(shí)際上不是很長(zhǎng),但是知識(shí)之繁雜,技術(shù)迭代速度之快是其他技術(shù)所不能比擬的。 winter在他的《重學(xué)前端》課程中提到: 到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系...
答案自己谷歌或百度找。 一、來(lái)源背景 面試題是來(lái)自微博@牛客網(wǎng)發(fā)布的真實(shí)大廠前端面經(jīng)題目,我一直在收集題目長(zhǎng)期一個(gè)一個(gè)的記錄下來(lái)的,可能會(huì)有重復(fù),但基本前端的面試大綱和需要掌握的知識(shí)都在其中了,面試題僅做學(xué)習(xí)參考,學(xué)習(xí)者閱后也要用心鉆研其中的原理,重要知識(shí)需要系統(tǒng)學(xué)習(xí)、透徹學(xué)習(xí),形成自己的知識(shí)鏈。 二、532道前端真實(shí)大廠面試題 express和koa的對(duì)比,兩者中間件的原理,koa捕獲異常多種情...
閱讀 2785·2021-11-04 16:15
閱讀 3458·2021-09-29 09:35
閱讀 4032·2021-09-22 15:45
閱讀 1417·2019-08-30 15:55
閱讀 1689·2019-08-30 15:44
閱讀 2711·2019-08-29 12:56
閱讀 2698·2019-08-26 13:30
閱讀 2169·2019-08-23 17:00