'日历组件'在后台管理系统里面是十分常见的, 在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项目(这个项目里面也有不少有趣的想法): 项目地址
相关文章:连接描述