1、当前组件说明
一个用vue实现的简单日历组件,可以手动设置日历的宽高,未设置时,有默认宽高,设置后,其中的每个日期项的宽高可以根据设定的宽高自适应、可以设置当前选中日期,可以设置时间可用范围,可以通过左右箭头进行日期切换。(第一次使用简书,代码格式比较丑,而且换行很高,也不知道怎么调整,大家见谅)
2、组件效果展示
3、组件实现代码
<template>
<div class="picker-box" :style="'width: ' + width + 'px; height: ' + height + 'px;'">
<div class="picker-tool" :style="'height: ' + toolHeight + 'px; line-height: ' + toolHeight + 'px;'">
<Button type="text" icon="ios-arrow-back" @click="toVdpUpper()"></Button>
{{vdpYear + '年' + vdpMonth + '月'}}
<Button type="text" icon="ios-arrow-forward" @click="toVdpLower()"></Button>
</div>
<div class="picker-body" :style="'height: ' + (height - 50) + 'px;'">
<div class="picker-title" v-for="item in xqList" :key="item" :style="itemStyles">{{item}}</div>
<div class="picker-item"
:style="itemStyles"
v-for="(item, i) in dateList" :key="i" @click="toSelect(item)"
:class="(item.disabled === false && item.actived ? 'actived' :'grey') + (item.checked ? ' checked' : '') + (item.today ? ' today' : '') + (item.disabled ? ' disabled' : '')">
{{item.day}}
</div>
<div style="clear: both;"></div>
</div>
</div>
</template>
<script>
export default {
name: 'cDatePicker',
props: {
// 要求传入的值 必须为 yyyy-MM-dd 格式数据
value: {
type: String
},
// 日历显示的宽度,默认300px
width: {
type: Number,
default: 300
},
// 日历显示的高度,默认300px
height: {
type: Number,
default: 300
},
// 日历工具栏的显示高度,默认32px
toolHeight: {
type: Number,
default: 32
},
// 日历中每个时间项的行高
lineHeight: {
type: Number
},
margin: {
type: Number,
default: 2
},
// 日历可选范围-开始时间
start: {
type: String,
default: ''
},
// 日历可选范围-结束时间
end: {
type: String,
default: ''
}
},
watch: {
// 监听 外部插入的value值变化
// value (n, l) {
// this.init()
// }
},
data () {
return {
xqList: ['日', '一', '二', '三', '四', '五', '六'],
vdpYear: 0, // 选择的年
vdpMonth: 0, // 选择的月
vdpDay: 0, // 选择的日
monthStart: 0, // 月初
monthEnd: 0, // 月末
monthWeek: 0,
dateList: [] // 当前时间集合
}
},
methods: {
// 初始化
init () {
const now = this.newDate(this.value || undefined)
this.vdpYear = now.getFullYear()
this.vdpMonth = now.getMonth() + 1
this.vdpDay = this.value ? now.getDate() : 0
this.computePicker()
},
// 选中某一天
toSelect (item) {
if (item && item.disabled === false) {
// // 通过重新生成dateList数据 达到改变选中效果
// this.vdpDay = item.day
// this.computePicker()
// 通过 直接循环修改dateList数据 达到改变选中效果
this.dateList = this.dateList.map(v => {
v.checked = false
return v
})
this.vdpDay = item.day
item.checked = true
this.$emit('input', item.date)
this.$emit('on-change', item.date, this.newDate(item.date))
}
},
// 上一个月
toVdpUpper () {
if (this.vdpMonth > 1) {
this.vdpMonth--
} else {
this.vdpYear--
this.vdpMonth = 12
}
this.vdpDay = 0
this.computePicker()
},
// 下一个月
toVdpLower () {
if (this.vdpMonth < 12) {
this.vdpMonth++
} else {
this.vdpYear++
this.vdpMonth = 1
}
this.vdpDay = 0
this.computePicker()
},
// 计算月初、月末、月初是星期几及当前日期数据
computePicker () {
const yc = new Date(this.vdpYear, this.vdpMonth - 1, 1) // 月初
const ym = new Date(this.vdpYear, this.vdpMonth, 0) // 月末
this.monthStart = yc.getDate()
this.monthEnd = ym.getDate()
this.monthWeek = yc.getDay()
const v = []
for (let i = 0; i < 42; i++) {
v.push(this.getVdpDay(i, '-'))
}
this.dateList = v
},
// 获取当前日期 核心方法
getVdpDay (index, defV) {
let _disabled = false
let _actived = false
let _year = this.vdpYear
let _month = this.vdpMonth
let _day = 0
if (this.monthWeek === 0) {
index = index - 7
}
if (index < this.monthWeek) {
// 计算上月
_month--
if (_month === 0) {
_year--
_month = 12
}
_day = new Date(_year, _month, 0).getDate() - this.monthWeek + index + 1 // 算当前选中月的上一月 月末
} else if (index > (this.monthWeek + this.monthEnd - 1)) {
// 计算下月
_month++
if (_month === 13) {
_year++
_month = 1
}
_day = new Date(this.vdpYear, this.vdpMonth, 1).getDate() + index - (this.monthEnd + this.monthWeek) // 算当前选中月的下一月 月初
} else {
// 计算当月
_day = this.monthStart + index - this.monthWeek
_actived = true
}
// 计算开始日期和结束日期
if (this.start || this.end) {
const _curtm = new Date(_year + '/' + _month + '/' + _day)
if (this.start) {
const _start = this.newDate(this.start)
if (_curtm < _start) {
_disabled = true
}
}
if (this.end) {
const _end = this.newDate(this.end)
if (_curtm > _end) {
_disabled = true
}
}
}
return { year: _year, month: _month, day: _day, date: this.generateDateStr(_year, _month, _day), checked: this.verifyCheckDate(_year, _month, _day), actived: _actived, today: this.isToday(_year, _month, _day), disabled: _disabled } // _disabled ? (defV || '') : _day
},
// 生成时间字符串
generateDateStr (year, month, day) {
return year + '-' + (month > 9 ? month : '0' + month) + '-' + (day > 9 ? day : '0' + day)
},
// 验证是否是当前选中的日期
verifyCheckDate (year, month, day) {
if ((this.value && this.value === this.generateDateStr(year, month, day)) || (this.vdpYear === year && this.vdpMonth === month && this.vdpDay === day)) {
return true
} else {
return false
}
},
// 验证是否今天
isToday (year, month, day) {
if (this.generateDateStr(year, month, day) === this.formatDate(new Date(), 'yyyy-MM-dd')) {
return true
} else {
return false
}
},
// 字符串转换 Date 解决ie11 时间兼容性问题
newDate (time, defV) {
let v = defV === undefined ? new Date() : defV
if (time) {
v = new Date(time.replace(/-/g, '/'))
}
return v
},
// 时间格式化 兼容IE
formatDate (date, format) {
if (date) {
var time
if (typeof date === 'string') {
time = new Date(date.replace(/-/g, '/'))
} else if (typeof date === 'object') {
time = new Date(date)
}
var o = {
'M+': time.getMonth() + 1, // 月份
'd+': time.getDate(), // 日
'h+': time.getHours(), // 小时
'm+': time.getMinutes(), // 分
's+': time.getSeconds(), // 秒
'q+': Math.floor((time.getMonth() + 3) / 3), // 季度
S: time.getMilliseconds() // 毫秒
}
if (/(y+)/.test(format)) format = format.replace(RegExp.$1, (time.getFullYear() + '').substr(4 - RegExp.$1.length))
for (var k in o) { if (new RegExp('(' + k + ')').test(format)) format = format.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))) }
return format
} else { return '' }
}
},
computed: {
itemStyles () {
const _w = (this.width / 7 - this.margin * 2)
const _h = ((this.height - this.toolHeight) / 7 - this.margin * 2)
const _lh = this.lineHeight ? this.lineHeight : (_h - 1) + 'px' // parseInt(_h)
return { width: _w + 'px', height: _h + 'px', margin: this.margin + 'px', 'line-height': _lh }
}
},
created () {
this.init()
}
}
</script>
<style scoped>
.picker-box {
width: 100%;
height: 100%;
margin: 0 auto;
font-size: 14px;
}
.picker-box .picker-tool {
height: 50px;
line-height: 50px;
text-align: center;
}
.picker-box .picker-body {
width: 100%;
}
.picker-box .picker-body .picker-item, .picker-box .picker-body .picker-title {
width: calc(14.285714285% - 10px);
height: calc(16.666666666% - 10px);
line-height: 2.6;
text-align: center;
border: 1px solid #ececec;
border-radius: 5px;
margin: 5px;
float: left;
}
.picker-box .picker-body .picker-item {
cursor: pointer;
}
.picker-box .picker-body .picker-title {
background: #eaf0f5;
}
.picker-box .picker-body .grey {
color: #b2b2b2;
border: 1px solid #ececec;
background: #fbfbfb;
}
.picker-box .picker-body .actived {
color: #3c3c3c;
border: 1px solid #ececec;
}
.picker-box .picker-body .today {
background: #d8f9d8;
border: 1px solid #ececec;
}
.picker-box .picker-body .checked {
color: #fff;
background: #19be6b;
border: 1px solid #19be6b;
}
.picker-box .picker-body .disabled {
color: #b2b2b2;
background: #f2f2f2;
border: 1px solid #ececec;
cursor: not-allowed;
}
</style>
4、组件调用示例
<template>
<div>
<cDatePicker v-model="timeText"></cDatePicker>
<cDatePicker v-model="timeText" :start="start" :end="end" :width="500" :height="500" :tool-height="50" :margin="5"></cDatePicker>
</div>
</template>
<script>
import cDatePicker from './cDatePicker'
export default {
components: {
cDatePicker
},
data () {
return {
timeText: '2020-07-16',
start: '2020-06-12',
end: '2020-08-05'
}
}
}
</script>
5、尚需待完善
a、点击年,进行年份切换
b、点击月,进行月份切换。