'日曆組件'在後臺管理系統裏面是十分常見的, 在pc端的展現方式基本都爲一個方方的表格, 別看功能單一, 這個組件作起來仍是有點意思的, 本次我來實現的組件只包含最核心的功能,也就是日期的選擇, Element-ui裏面的日期組件功能不少有興趣的同窗能夠去看看他的思想.css
效果展現
vue
vue-cc-ui/src/components/DatePicker/index.jsnode
import DatePicker from './main/datePicker.vue' DatePicker.install = function(Vue) { Vue.component(DatePicker.name, DatePicker); }; export default DatePicker
vue-cc-ui/src/components/DatePicker/main/datePicker.vuegit
<template> <div class="cc-date" ref='popover'> // 用來展現日期的那個輸入框 <input readonly type="text" class="cc-date-input" // 這是個頗有用的指令, 接下來我講一下他 v-clickoutside='hide' :value='formatDare' // 每次聚焦都會呼出日曆 @focus='isShowPanel = true'> // 接下來的'日曆'就在它裏面作了. <div v-show='isShowPanel' class="cc-date-pannel" ref='content' :style="{ top:top+'px', left:left+'px' }"> </div> </div> </template>
export default { name: "ccDatePicker", props: { value: { type: Date, // 指定類型不準是日期類型 default: () => new Date() // 你不傳我就取當前時間唄 } }, data() { return { top: 0, left: 0, isShowPanel: false, }; }, //...
v-clickoutside : 判斷點擊的是否是自身
這個方法必定要掛在組件內部的指令上, 不要污染全局.github
const Clickoutside = { bind(el, bindings, vnode) { // 單獨抽出來是爲了最後好把它移除 const handleClick = function(e) { // 若是點擊的元素不在目標元素的包裹內, 那就說明點擊了與元素無關的位置. if (!el.contains(e.target)) { // 虛擬dom的context屬性能夠找到這個實例, 調用他的hide方法能夠隱藏這個dom vnode.context[bindings.expression](); } }; el.handleClick = handleClick; document.addEventListener('click', handleClick); }, unbind(el) { document.removeEventListener('click', el.handleClick); } }; export default Clickoutside;
創給指令的hide方法express
methods: { hide() { this.isShowPanel = false; }, //...
給他定個位把, 具體出如今哪裏
其實這個咱們上一個組件已經封裝好了方法
咱們先觀察這個isShowPanel, 若是他出現, 那咱們就計算出現的位置segmentfault
watch: { isShowPanel(val) { if (val) { this.$nextTick(() => { this.setPosion(); // 這個方法是真正獲取位置的 }); } } },
setPosion設計模式
setPosion() { let { popover, content } = this.$refs; let { left, top } = getPopoverPosition( // 這個函數上一集有說明, 不贅述了. popover, content, "bottom-start", 3 ); this.top = top; this.left = left; }
上面的步驟咱們作到了點擊input彈出日期選擇, 點擊其餘地方讓其消失數組
展現一下結構代碼
首先是第一排dom
<div class="pannel-nav"> <span><</span> <span>←</span> <div class="pannel-selected"> // 像這種結構有人用v-for生成... // 其實有時候直接寫出來更直觀, 仁者見仁吧. <span>{{formatDare.split('-')[0]}}年 </span> <span>{{formatDare.split('-')[1]}}月 </span> <span>{{formatDare.split('-')[2]}}日</span> </div> <span>→</span> <span>></span> </div>
formatDare: 是用來展現時間的 --> '年-月-日'
computed: { formatDare() { let { year, month, day } = getYMD(this.value), result = `${year}-${month + 1}-${day}`; return result; }, // ...
展現星期
<div class="pannel-content"> <ul class="pannel-content__title"> <li v-for="i in weeksList" :key="i">{{i}}</li> </ul> //...
data() { return { top: 0, left: 0, isShowPanel: false, weeksList: ["日", "一", "二", "三", "四", "五", "六"] }; },
重頭戲: 展現day天
思路: 例如當前是' x年n月 ';
template
<ul class="pannel-content__item" v-for="i in 6" :key="i"> <li v-for="j in 7" :key="j">{{getVisibeDaysIndex(i,j).day}}</li> </ul>
計算當前有多少天
getVisibeDaysIndex(i, j) { i = i - 1; j = j - 1; let index = i * 7 + j; // 當前第幾個格子 return this.visibeDays[index]; },
visibeDays: 它是比較核心的方法
visibeDays() { let result = [], { year, month } = getYMD(this.value), // 傳入年,月,日,就會返回相應的date實例, 用getDay取得星期幾; dayOffset = new Date(year, month, 1).getDay(), // 傳入年月, 求出本月幾天, 這個方法下面會講. dateCountOfMonth = getDayCountOfMonth(year, month), // 這個是求得上一個月 previousMonth = month - 1; // 沒有0月, 因此須要變爲12月, 年份-1; if (previousMonth === 0) { year--; previousMonth = 12; } // 取得上個月有多少天, 這樣才能知道現實上個月的最後一天是否是31; let dateCountOfLastMonth = getDayCountOfMonth(year, previousMonth); // 把取得完畢的數據傳給專門把它們作成數組用於展現的函數; this.getDayList( dayOffset, dateCountOfMonth, dateCountOfLastMonth, result ); // 這個結果直接返回出去就行 return result; }
vue-cc-ui/src/assets/js/handelDate.js
這裏面就是對日期相關的處理
export function getYMD(date){ let day = date.getDate(); let month = date.getMonth(); let year = date.getFullYear(); return { year, month, day } } export const getDayCountOfMonth = function(year, month) { if (month === 3 || month === 5 || month === 8 || month === 10) { return 30; } if (month === 1) { if (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0) { return 29; } else { return 28; } } return 31; };
把日期整理爲使用的數組
getDayList
getDayList(dayOffset, dateCountOfMonth, dateCountOfLastMonth, result) { // 處理上個月的日期, 沒有的話固然就不走這個循環 for (let i = 0; i < dayOffset; i++) { result.unshift({ readOnly: true, day: dateCountOfLastMonth - i }); } // 處理當前月的天數 let day = getYMD(this.value).day; for (let i = 1; i <= dateCountOfMonth; i++) { let obj = { day: i, activate: true }; if (day !== i) { obj.activate = false; } result.push(obj); } // 總個數減去已使用的數, 把剩餘空間填滿 let len = 42 - result.length; for (let i = 1; i <= len; i++) { result.push({ readOnly: true, day: i }); } // 這個函數處理好了也不必有返回值 },
上面的步驟作完其實就已經能夠正常顯示當前月了
其實隨着核心代碼的完成, 周邊的功能都是很好添加的, 這也就是爲何寫代碼必定要符合設計模式;
選中某一天
<li v-for="j in 7" @click="handlerActiveDay(getVisibeDaysIndex(i,j,true))" :class="{ 'active-date': getVisibeDaysIndex(i,j).activate, 'read-only':getVisibeDaysIndex(i,j).readOnly }" :key="j">{{getVisibeDaysIndex(i,j).day}}</li>
handlerActiveDay: 這裏我在getVisibeDaysIndex傳了第三個參數
由於這裏我只須要他返回給我具體的序號就好了, 而不是具體哪天.
getVisibeDaysIndex(i, j, type) { i = i - 1; j = j - 1; let index = i * 7 + j; return type ? index : this.visibeDays[index]; },
handlerActiveDay(index) { let result = this.visibeDays[index], { year, month } = getYMD(this.value); if (!result.readOnly) { // 這一步實際上是與用戶的 v-model相結合的. this.$emit("input", new Date(year, month, result.day)); } },
前進與後退
<span @click="handlerChangeYear(-1)"><</span> <span @click="handlerChangeMonth(-1)">←</span> // ... <span @click="handlerChangeMonth(1)">→</span> <span @click="handlerChangeYear(1)">></span>
月份的
handlerChangeMonth
注意不要超出邊界
handlerChangeMonth(n) { let { year, month } = getYMD(this.value); month += n; if (month === 0) { month = 12; year += n; } else if (month === 13) { month = 1; year += n; } this.$emit("input", new Date(year, month, 1)); },
年份
handlerChangeYear
不必判斷負數了, 畢竟選一個公元前的時間這種狀況太極端了, 不必浪費性能去判斷了.
handlerChangeYear(n) { let { year, month } = getYMD(this.value); year += n; this.$emit("input", new Date(year, month, 1)); },
@import './common/var.scss'; @import './common/mixin.scss'; @import './common/extend.scss'; @include b(date) { position: relative; display: inline-block; @include b(date-input){ border: 1px solid $--color-disabled; // 輸入框的outline根據需求來判斷到底要不要清理吧. outline: 0px; padding: 8px; font-size: 16px; border-radius:7px; } @include b(date-pannel){ // 這種彈出框確定是要針對視口定位的 position: fixed; border: 1px solid $--color-disabled; background-color: $--color-white; width: 280px; padding: 8px; border-radius:7px; .pannel-nav{ display: flex; align-items: center; // 總體有一個環繞效果 justify-content: space-around; // 外圈的輪廓 box-shadow: 0px 2px 2px 2px $--color-difference; padding: 6px 0; margin-bottom: 10px; .pannel-selected{ width: 160px; text-align: center; } &>span{ &:hover{ cursor: pointer; color: $--color-nomal } } } .pannel-content{ box-shadow: 0px 2px 2px 2px $--color-difference; ul{ display: flex; } li{ text-align: center; flex: 1; height: 35px; line-height: 35px; } .read-only{ color: $--color-disabled; } .active-date{ @extend .active-item; } .pannel-content__item{ cursor: pointer; border: 1px solid $--color-difference; // li標籤中, 沒有.read-only class的標籤; li:not(.read-only){ // 平時是處於縮小狀態的 transition: all .2s; transform: scale(.8); &:hover{ transform: scale(1.3); @extend .active-item; } } } } } }
你們均可以一塊兒交流, 共同窗習,共同進步, 早日實現自我價值!!
下一集聊聊'tree組件'
做者對tree組件有些不同的理解, 因此作出來的組件也比較怪異吧,可是我挺喜歡個人想法, 下一期與你們分享一下另類的tree.
github:github
我的技術博客(組件的官網):博客
仿寫Vue項目(這個項目裏面也有不少有趣的想法): 項目地址
相關文章:連接描述