摘要:演示地址希望大家能給個(gè)功能期望這個(gè)目前僅實(shí)現(xiàn)了一些常用的功能選擇時(shí)間這話說得有點(diǎn)多余最大最小時(shí)間限制中英文切換其實(shí)也就星期和月份需要切換可以以形式使用,也可在瀏覽器環(huán)境中直接使用沒了。。。
前言
寫插件是很有意思,也很鍛煉人,因?yàn)檫@個(gè)過程中能發(fā)現(xiàn)許多的細(xì)節(jié)問題。在前端發(fā)展的過程中,jQuery無疑是一個(gè)重要的里程碑,圍繞著這個(gè)優(yōu)秀項(xiàng)目也出現(xiàn)了很多優(yōu)秀的插件可以直接使用,大大節(jié)省了開發(fā)者們的時(shí)間。jQuery最重要的作用是跨瀏覽器,而現(xiàn)在瀏覽器市場雖不完美,但已遠(yuǎn)沒有從前那么慘,數(shù)據(jù)驅(qū)動視圖的思想倍受歡迎,大家開始使用前端框架取代jQuery,我個(gè)人比較喜歡Vue.js,所以想試著用Vue.js寫一個(gè)組件出來。
為了發(fā)布到npm上,所以給項(xiàng)目地址改名字了,但是內(nèi)部代碼沒有改,使用方法比之前方便。
Demo演示: Here
GitHub地址: Here希望大家能給個(gè)star
這個(gè)datepicker目前僅實(shí)現(xiàn)了一些常用的功能:
選擇時(shí)間(這話說得有點(diǎn)多余)
最大/最小時(shí)間限制
中/英文切換(其實(shí)也就星期和月份需要切換)
可以以.vue形式使用,也可在瀏覽器環(huán)境中直接使用
沒了。。。
目錄結(jié)構(gòu)萬事的第一步依然是創(chuàng)建項(xiàng)目,只是單一組件,結(jié)構(gòu)并不復(fù)雜,Datepicker.vue是最重要的組件文件,dist是webpack的輸出文件夾,index.js是webpack打包的入口文件,最后是webpack的配置文件,用來對我們的庫文件進(jìn)行打包用的。因此項(xiàng)目結(jié)構(gòu)就是這樣:
. ├── Datepicker.vue ├── LICENSE ├── README.md ├── dist │?? └── vue-datepicker.js ├── index.js ├── package.json └── webpack.config.js從Datepicker.vue入手
以.vue的方式寫Vue組件是一種特殊寫法,每個(gè)Vue文件包括template, script, style三部分,template最好不要成為片段實(shí)例,所以最外層先套一層div,當(dāng)做整個(gè)組件的根元素。一個(gè)datepicker一般由兩部分組成,一個(gè)用來顯示日期的input框,一個(gè)用來選擇日期的panel,因?yàn)槲野l(fā)現(xiàn)input在移動端會自動喚起鍵盤,所以沒有使用input,直接用了div模擬,通過點(diǎn)擊事件決定panel的顯隱。value是最終的結(jié)果,需要和父組件通信,所以將value寫成了prop,在父組件中使用value.sync="xxx",datepicker的value就和父組件的xxx雙向綁定了。
export default { data () { return { panelState: false //初始值,默認(rèn)panel關(guān)閉 } }, props: { value: String } } 渲染日期列表 一個(gè)月最少是28天,如果把周日排在開頭,那么最少(1號恰好是周日)需要4行,但是每個(gè)月天數(shù)30,31居多,而且1號又不一定是周日,我索性干脆按最多的情況設(shè)計(jì)了,共6行,當(dāng)月日期沒填滿的地方用上個(gè)月或下個(gè)月的日期補(bǔ)齊,這樣就方便計(jì)算了,而且切換月份時(shí)候panel高度不會變化。日期列表的數(shù)組需要動態(tài)計(jì)算,Vue提供了computed這個(gè)屬性,所以直接將日期列表dateList寫成計(jì)算屬性。我的方法是將日期列表固定為長度為42的數(shù)組,然后將本月,上個(gè)月,下個(gè)月的日期依次填充。
computed: { dateList () { //獲取當(dāng)月的天數(shù) let currentMonthLength = new Date(this.tmpMonth, 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號的星期是為了確定在1號前需要插多少天 let startDay = new Date(this.year, this.tmpMonth, 1).getDay() //確認(rèn)上個(gè)月一共多少天 let previousMongthLength = new Date(this.year, this.tmpMonth, 0).getDate() } //在1號前插入上個(gè)月日期 for(let i = 0, len = startDay; i < len; i++){ dateList = [{previousMonth: true, value: previousMongthLength - i}].concat(dateList) } //補(bǔ)全剩余位置 for(let i = 0, item = 1; i < 42; i++, item++){ dateList[dateList.length] = {nextMonth: true, value: i} } return dateList }這里用Array.from來初始化了一個(gè)數(shù)組,傳入一個(gè)Array Like,轉(zhuǎn)化成數(shù)組,在拼接字符串時(shí)候采用了arr[arr.length]和[{}].concat(arr)這種方式,因?yàn)樵贘sTips上學(xué)到這樣做性能更好,文章的最后會貼出相關(guān)鏈接。
這樣,日期列表就構(gòu)建好了,在template中使用v-for循環(huán)渲染出來
樣式上就可以自己發(fā)揮了,怎么喜歡怎么寫。需要注意的是循環(huán)日期可能會出現(xiàn)上個(gè)月或這個(gè)月的日期,我通過previuosMonth,currentMonth和nextMonth分別做了標(biāo)記,對其他功能提供判斷條件。
年份和月份的列表都是差不多的道理,年份列表的初始值我直接寫在了data里,以當(dāng)前年份為第一個(gè),為了和月份保持一致,每次顯示12個(gè),都通過v-for渲染。data () { return { yearList: Array.from({length: 12}, (value, index) => new Date().getFullYear() + index) } }選擇日期功能選擇順序是:年 -> 月 -> 日,所以我們可以通過一個(gè)狀態(tài)變量來控制panel中顯示的內(nèi)容,綁定適合的函數(shù)切換顯示狀態(tài)。
選擇日期的方法就不細(xì)說了,在selectYear,selectMonth中對年份,月份變量賦值,再分別將panelType推向下一步就實(shí)現(xiàn)了日期選擇功能。
不過在未選擇完日期之前,你可能不希望當(dāng)前年月的真實(shí)值發(fā)生變化,所以在這些方法中可先將選擇的值賦給一個(gè)臨時(shí)變量,等到seletDate的時(shí)候再一次性全部賦值。selectMonth (month) { if(this.validateMonth(month)){ return }else{ //臨時(shí)變量 this.tmpMonth = month //切換panel狀態(tài) this.panelType = "date" } }, selectDate (date) { //validate logic above... //一次性全部賦值 this.year = tmpYear this.month = tmpMonth this.date = date.value this.value = `${this.tmpYear}-${("0" + (this.month + 1)).slice(-2)}-${("0" + this.date).slice(-2)}` //選擇完日期后,panel自動隱藏 this.panelState = false }最大/小時(shí)間限制最大/小值是需要從父組件傳遞下來的,因此應(yīng)該使用props,另外,這個(gè)值可以是字符串,也應(yīng)該可以是變量(比如同時(shí)存在兩個(gè)datepicker,第二個(gè)的日期不能比第一個(gè)大這種邏輯),所以應(yīng)該使用Dynamically bind的方式傳值。
增加了限制條件,對于不合法的日期,其按鈕應(yīng)該變?yōu)橹没覡顟B(tài),我用了比較時(shí)間戳的方式來判斷日期是否合法,因?yàn)榫退惝?dāng)前panel中的日期是跨年或是跨月的,通過日期構(gòu)造函數(shù)創(chuàng)建時(shí)都會幫你轉(zhuǎn)換成對應(yīng)的合法值,省去很多判斷的麻煩:
new Date(2015, 0, 0).getTime() === new Date(2014, 11, 31).getTime() //true new Date(2015, 12, 0).getTime() === new Date(2016, 0, 0).getTime() //true因此驗(yàn)證日期是否合法的函數(shù)是這樣的:
validateDate (date) { let mon = this.tmpMonth if(date.previousMonth){ mon -= 1 }else if(date.nextMonth){ mon += 1 } if(new Date(this.tmpYear, mon, date.value).getTime() >= new Date(this.minYear, this.minMonth - 1, this.minDate).getTime() && new Date(this.tmpYear, mon, date.value).getTime() <= new Date(this.maxYear, this.maxMonth - 1, this.maxDate).getTime()){ return false } return true}
動態(tài)計(jì)算位置當(dāng)頁面右側(cè)有足夠的空間顯示時(shí),datepicker的panel會定位為相對于父元素left: 0的位置,如果沒有足夠的空間,則應(yīng)該置于right: 0的位置,這一點(diǎn)可以通過Vue提供的動態(tài)樣式和樣式對象來實(shí)現(xiàn)(動態(tài)class和動態(tài)style其實(shí)只是動態(tài)props的特例),而計(jì)算位置的時(shí)刻,我放在了組件聲明周期的ready周期中,因?yàn)檫@時(shí)組件已經(jīng)插入到DOM樹中,可以獲取style進(jìn)行動態(tài)計(jì)算:
ready () { if(this.$el.parentNode.offsetWidth + this.$el.parentNode.offsetLeft - this.$el.offsetLeft <= 300){ this.coordinates = {right: "0", top: `${window.getComputedStyle(this.$el.children[0]).offsetHeight + 4}px`} }else{ this.coordinates = {left: "0", top: `${window.getComputedStyle(this.$el.children[0]).offsetHeight + 4}px`} } }為了panel的顯隱可以平滑過渡,可以使用transition做過渡動畫,這里我簡單地通過一個(gè)0.2秒的透明度過渡讓顯隱更平滑。
//less syntax .toggle{ &-transition{ transition: all ease .2s; } &-enter, &-leave{ opacity: 0; } }中英文切換這里其實(shí)也很簡單,這種多語言切換實(shí)質(zhì)就是一個(gè)key根據(jù)不同的type而輸出不同的value,所以使用filter可以很容易的實(shí)現(xiàn)它!比如渲染星期的列表:
filters : { week (item, lang){ switch (lang) { case "en": return {0: "Su", 1: "Mo", 2: "Tu", 3: "We", 4: "Th", 5: "Fr", 6: "Sa"}[item] case "ch": return {0: "日", 1: "一", 2: "二", 3: "三", 4: "四", 5: "五", 6: "六"}[item] default: return item } } } 多種使用方式
對于一個(gè)Vue組件,如果是使用webpack + vue-loader的.vue單文件寫法,我希望這樣使用:
//App.vue如果是直接在瀏覽器中使用,那么我希望datepicker這個(gè)組件是暴露在全局下的,可以這么使用:
//index.html這里我選擇了webpack作為打包工具,使用webpack的output.library和output.linraryTarget這兩個(gè)屬性就可以把你的bundle文件作為庫文件打包。library定義了庫的名字,libraryTarget定義了你想要打包的格式,具體可以看文檔。我希望自己的庫可以通過datepicker加載到,并且打包成umd格式,因此我的webpack.config.js是這樣的:
module.exports = { entry: "./index.js", output: { path: "./dist", library: "datepicker", filename: "vue-datepicker.js", libraryTarget: "umd" }, module: { loaders: [ {test: /.vue$/, loaders: ["vue"]}, {test: /.js$/, exclude: /node_modules/, loaders: ["babel"]} ] } }打包完成的模塊就是一個(gè)umd格式的模塊啦,可以在瀏覽器中直接使用,也可以配合require.js等模塊加載器使用!
適配 Vue 2.xVue 2.0已經(jīng)發(fā)布有段時(shí)間了,現(xiàn)在把之前的組件適配到Vue 2.0。遷移過程還是很順利的,核心API改動不大,可以借助vue-migration-helper來找出廢棄的API再逐步修改。這里只列舉一些我需要修改的API。
filter2.0中的filter只能在mustache綁定中使用,如果想在指令式綁定中綁定過濾后的值,可以選擇計(jì)算屬性。我在月份和星期的顯示中使用到了過濾器來過濾語言類型,但我之前是在指令式綁定中使用的filter,所以需要如下修改,:
//修改前 //修改后,filter傳參的方式也變了,變成了函數(shù)調(diào)用的風(fēng)格移除$index和$key{{tmpMonth + 1 | month(language)}}這兩個(gè)屬性不會在v-for中被自動創(chuàng)建了,如需使用,要在v-for中自行聲明:
// ready 生命周期移除ready從生命周期鉤子中移除了,遷移方法很簡單,使用mounted和this.$nextTick來替換。
prop.sync棄用prop的sync棄用了,遷移方案是使用自定義事件,而且Datepicker這種input類型組件,可以使用表單輸入組件的自定義事件作為替換方案。自定義組件也可以使用v-model指令了,但是必須滿足兩個(gè)條件:
接收一個(gè)value的prop
值發(fā)生變化時(shí),觸發(fā)一個(gè)input事件,傳入新值。
所以Datepicker的使用方式也不是
了,而是 。組件自身向父級傳值的方式也不一樣了: //1.x版本,設(shè)置了value的值會同步到父級 this.value = `${this.tmpYear}-${("0" + (this.month + 1)).slice(-2)}-${("0" + this.date).slice(-2)}` //2.x版本,需要自己觸發(fā)input事件,將新值作為參數(shù)傳遞回去 let value = `${this.tmpYear}-${("0" + (this.month + 1)).slice(-2)}-${("0" + this.date).slice(-2)}` this.$emit("input", value)總結(jié)以上就是我在寫這個(gè)datepicker時(shí)的大致思路,本身也是很簡單的事情,沒有處處展開來說,寫在這里作為自己的一個(gè)總結(jié),如果有剛開始使用Vue的同學(xué)也希望這篇文章可以在思路上幫助到你們:P,對于各位老鳥如果有什么指點(diǎn)的地方我也很感謝:D,那么差不多就這樣,后面貼一些相關(guān)推薦閱讀。
推薦閱讀高效地向數(shù)組中插值
Vue.js-片段實(shí)例
Vue.js-動態(tài)綁定
Js日期對象基礎(chǔ)
Webpack: export bundle as library
UMD(universial Module Defination)
Migration from Vue 1.x文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/90895.html
摘要:基于日期選擇組件是一款稍微復(fù)雜的組件,其中涉及的日歷計(jì)算與顯示需要比較清晰的邏輯。 vue-datepicker - 基于Vue2.x日期選擇組件 Datepicker是一款稍微復(fù)雜的組件,其中涉及的日歷計(jì)算與顯示需要比較清晰的邏輯。 項(xiàng)目地址 https://github.com/watson-yan... 預(yù)覽圖 showImg(https://segmentfault.com/i...
摘要:實(shí)現(xiàn)代碼于文章末尾處構(gòu)思頁面結(jié)構(gòu)組件由輸入框和日歷面板組成,寫好頁面主體結(jié)構(gòu)。輸入框點(diǎn)擊顯示或隱藏日歷面板方法改變布爾值控制日歷面板的顯示隱藏。同時(shí),當(dāng)組件銷毀時(shí),也要及時(shí)清除該監(jiān)聽器。 最近研究了 DatePicker 的實(shí)現(xiàn)原理后做了一個(gè) vue 的 DatePicker 組件,今天帶大家一步一步實(shí)現(xiàn) DatePicker 的 vue 組件。 原理 DatePicker 的原理是—...
摘要:全面配置文章系列安裝依賴修改修改 ???nuxt.js全面配置??? ???nuxt文章系列??? babel-plugin-component 安裝依賴 npm i -D babel-plugin-component // or yarn add -D babel-plugin-component 修改nuxt.config.js module.exports = { bui...
摘要:好多時(shí)候在移動端需要一個(gè)的日期選擇器,由于在應(yīng)用上有可能應(yīng)用各種框架庫等所以說一個(gè)無依賴的,這樣易于上層進(jìn)行封裝。主要包含兩種選擇器日期和時(shí)間。 好多時(shí)候在移動端需要一個(gè)的日期選擇器,由于在應(yīng)用上有可能應(yīng)用各種框架庫(Vue.js, React.js, zepto.js等);所以說一個(gè)無依賴的,這樣易于上層進(jìn)行封裝。直接開門見山,先來張動圖看看效果: showImg(https://s...
摘要:然則明明是定義了的,只是驗(yàn)證兩個(gè)類文件的話,均未出現(xiàn)任何語法錯誤。打開開發(fā)者工具,勾上,觀察發(fā)生異常時(shí)的狀況,一遍又一遍,我漸漸意識到,發(fā)生這個(gè)錯誤的時(shí)候,還未能在的環(huán)境中完成注冊。 開發(fā)日歷控件的時(shí)候,對方變更了一下需求,基本上將最終產(chǎn)品分成兩個(gè): 選擇連續(xù)時(shí)間段 選擇多個(gè)不連續(xù)時(shí)間 那么我們知道,對于這種大部分功能一致,只有若干函數(shù)邏輯不同的產(chǎn)品,最合適的就是狀態(tài)模式。于是很自...
閱讀 1588·2021-09-26 09:46
閱讀 2675·2021-09-07 09:59
閱讀 2760·2021-09-07 09:59
閱讀 1887·2019-08-30 14:20
閱讀 936·2019-08-26 13:39
閱讀 3184·2019-08-26 12:24
閱讀 781·2019-08-26 11:55
閱讀 1222·2019-08-23 16:49