mirror of
https://github.com/tenrok/vue2-datepicker.git
synced 2026-06-02 23:04:07 +03:00
init
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
@@ -0,0 +1,243 @@
|
||||
<template>
|
||||
<div class="calendar">
|
||||
<div class="calendar-header">
|
||||
<i class="calendar__prev-icon" @click="changeYear(-1)">«</i>
|
||||
<i class="calendar__prev-icon" @click="changeMonth(-1)">‹</i>
|
||||
<span>{{now.getFullYear() + '年'}}</span>
|
||||
<span>{{now.getMonth() + 1 + '月'}}</span>
|
||||
<i class="calendar__next-icon" @click="changeYear(1)">»</i>
|
||||
<i class="calendar__next-icon" @click="changeMonth(1)" >›</i>
|
||||
</div>
|
||||
<div class="calendar-content">
|
||||
<table class="calendar-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="day in days">{{day}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in dates">
|
||||
<td v-for="cell in row"
|
||||
:title="cell.title"
|
||||
:class="getClasses(cell)"
|
||||
@click="selectDate(cell)">{{cell.day}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatDate } from './utils.js'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
startAt: null,
|
||||
endAt: null,
|
||||
value: null,
|
||||
show: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
days: ['日', '一', '二', '三', '四', '五', '六'],
|
||||
dates: [],
|
||||
now: this.value ? new Date(this.value) : new Date(),
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.update()
|
||||
},
|
||||
watch: {
|
||||
show(val) {
|
||||
if (val) {
|
||||
this.now = this.value ? new Date(this.value) : new Date()
|
||||
}
|
||||
},
|
||||
value(val) {
|
||||
this.now = val ? new Date(val) : new Date()
|
||||
},
|
||||
now: 'update',
|
||||
},
|
||||
methods: {
|
||||
getCalendar(time, firstday, length, classes) {
|
||||
const today = new Date().setHours(0, 0, 0, 0)
|
||||
return Array.apply(null, { length }).map((v, i) => { // eslint-disable-line
|
||||
let day = firstday + i
|
||||
let type = classes
|
||||
const date = new Date(time.getFullYear(), time.getMonth(), day)
|
||||
const isToday = today === date.getTime()
|
||||
if (isToday) {
|
||||
day = '今天'
|
||||
type += ' today'
|
||||
}
|
||||
return {
|
||||
title: formatDate(date, 'yyyy-MM-dd'),
|
||||
date,
|
||||
day,
|
||||
type,
|
||||
}
|
||||
})
|
||||
},
|
||||
// 更新面板选择时间
|
||||
update() {
|
||||
const row = 6
|
||||
const col = 7
|
||||
const time = new Date(this.now)
|
||||
|
||||
time.setDate(0) // 把时间切换到上个月最后一天
|
||||
const lastMonthLength = time.getDay() + 1 // time.getDay() 0是星期天, 1是星期一 ...
|
||||
const lastMonthfirst = time.getDate() - (lastMonthLength - 1)
|
||||
const lastMonth = this.getCalendar(time, lastMonthfirst, lastMonthLength, 'lastMonth')
|
||||
|
||||
time.setMonth(time.getMonth() + 2, 0) // 切换到这个月最后一天
|
||||
const curMonthLength = time.getDate()
|
||||
const curMonth = this.getCalendar(time, 1, curMonthLength, 'curMonth')
|
||||
|
||||
time.setMonth(time.getMonth() + 1, 1)
|
||||
const nextMonthLength = (row * col) - (lastMonthLength + curMonthLength)
|
||||
const nextMonth = this.getCalendar(time, 1, nextMonthLength, 'nextMonth')
|
||||
|
||||
// 分割数组
|
||||
let index = 0
|
||||
let resIndex = 0
|
||||
const arr = lastMonth.concat(curMonth, nextMonth)
|
||||
const result = new Array(row)
|
||||
while (index < row * col) {
|
||||
result[resIndex++] = arr.slice(index, index += col)
|
||||
}
|
||||
this.dates = result
|
||||
},
|
||||
getClasses(cell) {
|
||||
const classes = []
|
||||
const cellTime = cell.date.getTime()
|
||||
const curTime = this.value ? new Date(this.value).setHours(0, 0, 0, 0) : 0
|
||||
const startTime = this.startAt ? this.startAt.setHours(0, 0, 0, 0) : 0
|
||||
const endTime = this.endAt ? this.endAt.setHours(0, 0, 0, 0) : 0
|
||||
classes.push(cell.type)
|
||||
if (startTime) {
|
||||
if (cellTime < startTime) {
|
||||
classes.push('disabled')
|
||||
} else if (curTime && cellTime <= curTime) {
|
||||
classes.push('inrange')
|
||||
}
|
||||
} else if (endTime) {
|
||||
if (cellTime > endTime) {
|
||||
classes.push('disabled')
|
||||
} else if (curTime && cellTime >= curTime) {
|
||||
classes.push('inrange')
|
||||
}
|
||||
}
|
||||
if (curTime === cellTime) {
|
||||
classes.push('current')
|
||||
}
|
||||
return classes.join(' ')
|
||||
},
|
||||
changeYear(flag) {
|
||||
const now = new Date(this.now)
|
||||
now.setDate(1)
|
||||
now.setFullYear(now.getFullYear() + flag)
|
||||
this.now = now
|
||||
},
|
||||
changeMonth(flag) {
|
||||
const now = new Date(this.now)
|
||||
now.setDate(1)
|
||||
now.setMonth(now.getMonth() + flag)
|
||||
this.now = now
|
||||
},
|
||||
selectDate(cell) {
|
||||
const classes = this.getClasses(cell)
|
||||
if (classes.indexOf('disabled') !== -1) {
|
||||
return
|
||||
}
|
||||
this.now = cell.date
|
||||
this.$emit('input', cell.date)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.calendar {
|
||||
float: left;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.calendar:nth-child(2) {
|
||||
border-left: 1px solid #e4e4e4;
|
||||
}
|
||||
|
||||
.calendar-header {
|
||||
line-height: 34px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.calendar__next-icon,
|
||||
.calendar__prev-icon {
|
||||
font-style: normal;
|
||||
font-size: 20px;
|
||||
padding: 0 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.calendar__next-icon:hover,
|
||||
.calendar__prev-icon:hover {
|
||||
color: #1284e7;
|
||||
}
|
||||
|
||||
.calendar__prev-icon {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.calendar__next-icon {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.calendar-table {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.calendar-table td,
|
||||
.calendar-table th {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.calendar-table td {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.calendar-table td.inrange,
|
||||
.calendar-table td:hover {
|
||||
background-color: #eaf8fe;
|
||||
}
|
||||
|
||||
.calendar-table td.current {
|
||||
color: #fff;
|
||||
background-color: #1284e7;
|
||||
}
|
||||
|
||||
.calendar-table td.disabled {
|
||||
cursor: not-allowed;
|
||||
color: #ccc;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
|
||||
.lastMonth,
|
||||
.nextMonth {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.today {
|
||||
color: #20a0ff;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<div class="datepicker"
|
||||
:style="{'width': width + 'px','min-width':range ? '210px' : '140px'}"
|
||||
v-clickoutside="closePopup">
|
||||
<input readonly
|
||||
class="input"
|
||||
:value="text"
|
||||
:placeholder="innerPlaceholder"
|
||||
ref="input"
|
||||
@click="togglePopup"
|
||||
@mousedown="$event.preventDefault()">
|
||||
<i class="input-icon"
|
||||
:class="showCloseIcon ? 'input-icon__close' : 'input-icon__calendar'"
|
||||
@mouseenter="hoverIcon"
|
||||
@mouseleave="hoverIcon"
|
||||
@click="clickIcon" ></i>
|
||||
<div class="datepicker-popup"
|
||||
:class="{'range':range}"
|
||||
v-show="showPopup">
|
||||
<template v-if="!range">
|
||||
<calendar-panel v-model="currentValue" :show="showPopup"></calendar-panel>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="datepicker-top">
|
||||
<span v-for="range in ranges" @click="selectRange(range)">{{range.text}}</span>
|
||||
</div>
|
||||
<calendar-panel style="width:50%" v-model="currentValue[0]" :end-at="currentValue[1]" :show="showPopup"></calendar-panel>
|
||||
<calendar-panel style="width:50%" v-model="currentValue[1]" :start-at="currentValue[0]" :show="showPopup"></calendar-panel>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatDate } from './utils.js'
|
||||
import CalendarPanel from './calendar-panel.vue'
|
||||
|
||||
export default {
|
||||
components: { CalendarPanel },
|
||||
props: {
|
||||
format: {
|
||||
type: String,
|
||||
default: 'yyyy-MM-dd',
|
||||
},
|
||||
range: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: 210,
|
||||
},
|
||||
palceholder: String,
|
||||
value: null,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showPopup: false,
|
||||
showCloseIcon: false,
|
||||
currentValue: this.value,
|
||||
ranges: [],
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(val) {
|
||||
if (!this.range) {
|
||||
this.currentValue = this.isValidDate(val) ? val : undefined
|
||||
} else {
|
||||
this.currentValue = this.isValidRange(val) ? val : [undefined, undefined]
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
currentValue(val) {
|
||||
if ((!this.range && val) || (this.range && val[0] && val[1])) {
|
||||
this.$emit('input', val)
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
innerPlaceholder() {
|
||||
return this.placeholder ? this.placeholder : (this.range ? '请选择日期范围' : '请选择日期')
|
||||
},
|
||||
text() {
|
||||
if (!this.range && this.currentValue) {
|
||||
return this.stringify(this.currentValue)
|
||||
}
|
||||
if (this.range && this.currentValue[0] && this.currentValue[1]) {
|
||||
return this.stringify(this.currentValue[0]) + ' ~ ' + this.stringify(this.currentValue[1])
|
||||
}
|
||||
return ''
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initRanges()
|
||||
},
|
||||
methods: {
|
||||
closePopup() {
|
||||
this.showPopup = false
|
||||
},
|
||||
togglePopup() {
|
||||
if (this.showPopup) {
|
||||
this.$refs.input.blur()
|
||||
this.showPopup = false
|
||||
} else {
|
||||
this.$refs.input.focus()
|
||||
this.showPopup = true
|
||||
}
|
||||
},
|
||||
hoverIcon(e) {
|
||||
if (e.type === 'mouseenter' && this.text) {
|
||||
this.showCloseIcon = true
|
||||
}
|
||||
if (e.type === 'mouseleave') {
|
||||
this.showCloseIcon = false
|
||||
}
|
||||
},
|
||||
clickIcon() {
|
||||
if (this.showCloseIcon) {
|
||||
this.$emit('input', '')
|
||||
} else {
|
||||
this.togglePopup()
|
||||
}
|
||||
},
|
||||
stringify(date) {
|
||||
return formatDate(date, this.format)
|
||||
},
|
||||
isValidDate(date) {
|
||||
return !!new Date(date).getTime()
|
||||
},
|
||||
isValidRange(date) {
|
||||
return Array.isArray(date) &&
|
||||
date.length === 2 &&
|
||||
this.isValidDate(date[0]) &&
|
||||
this.isValidDate(date[1])
|
||||
},
|
||||
selectRange(range) {
|
||||
this.$emit('input', [range.start, range.end])
|
||||
},
|
||||
initRanges() {
|
||||
const time = new Date()
|
||||
time.setMonth(time.getMonth() + 1, 0) // 切换到本月最后一天
|
||||
this.ranges.push({
|
||||
text: '今天',
|
||||
start: new Date(),
|
||||
end: new Date(),
|
||||
}, {
|
||||
text: '最近一周',
|
||||
start: new Date(Date.now() - 3600 * 1000 * 24 * 7),
|
||||
end: new Date(),
|
||||
}, {
|
||||
text: '今后一周',
|
||||
start: new Date(),
|
||||
end: new Date(Date.now() + 3600 * 1000 * 24 * 7),
|
||||
}, {
|
||||
text: '本月',
|
||||
start: new Date(time.getFullYear(), time.getMonth(), 1),
|
||||
end: time,
|
||||
}, {
|
||||
text: '最近一个月',
|
||||
start: new Date(Date.now() - 3600 * 1000 * 24 * 30),
|
||||
end: new Date(),
|
||||
}, {
|
||||
text: '最近三个月',
|
||||
start: new Date(Date.now() - 3600 * 1000 * 24 * 90),
|
||||
end: new Date()
|
||||
})
|
||||
},
|
||||
},
|
||||
directives: {
|
||||
clickoutside: {
|
||||
bind(el, binding, vnode) {
|
||||
el['@clickoutside'] = (e) => {
|
||||
if (!el.contains(e.target) && binding.expression && vnode.context[binding.expression]) {
|
||||
binding.value()
|
||||
}
|
||||
}
|
||||
document.addEventListener('click', el['@clickoutside'], true)
|
||||
},
|
||||
unbind(el) {
|
||||
document.removeEventListener('click', el['@clickoutside'], true)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.datepicker {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
color:#73879c;
|
||||
font: 14px/1.5 "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.datepicker * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.datepicker-popup {
|
||||
position: absolute;
|
||||
min-width: 234px;
|
||||
margin-top: 1px;
|
||||
border: 1px solid #d9d9d9;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.range {
|
||||
min-width: 468px;
|
||||
}
|
||||
|
||||
.input {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
padding: 6px 30px 6px 10px;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
color: #555;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
top: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
color: #888;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
.input-icon::after{
|
||||
content:'';
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.input-icon__calendar{
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA00lEQVQ4T72SzQ2CQBCF54UGKIES6EAswQq0BS/A3PQ0hAt0oKVQgiVYAkcuZMwSMOyCyRKNe9uf+d6b2Qf6csGtL8sy7vu+Zebn/E5EoiAIwjRNH/PzBUBEGiJqmPniAMw+YeZkFSAiJwA3j45aVT0wsxGitwOjDGDnASBVvU4OLQARRURk9e4CAcSqWn8CLHp3Ae6MXAe/B4yzUeMkz/P9ZgdFUQzFIwD/B4yKgwMTos0OtvzCHcDRJ0gAzlmW1VYSq6oKu66LfQBTjC2AT+Hamxcml5IRpPq3VQAAAABJRU5ErkJggg==);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.input-icon__close::before {
|
||||
content: '\2716';
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.datepicker-top {
|
||||
margin: 0 12px;
|
||||
line-height: 34px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, .05);
|
||||
}
|
||||
|
||||
.datepicker-top>span {
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.datepicker-top>span:hover {
|
||||
color: #1284e7;
|
||||
}
|
||||
|
||||
.datepicker-top>span:after {
|
||||
content: "|";
|
||||
margin: 0 10px;
|
||||
color: #48576a;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "vue2-datepicker",
|
||||
"version": "1.0.0",
|
||||
"description": "datepicker for Vue2",
|
||||
"main": "index.vue",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"vue",
|
||||
"datepicker"
|
||||
],
|
||||
"author": "xmx",
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
export function formatDate(date, fmt = 'YYYY-MM-DD hh:mm:ss') {
|
||||
date = date instanceof Date ? date : new Date(date)
|
||||
const map = {
|
||||
'M+': date.getMonth() + 1, // 月份
|
||||
'[Dd]+': date.getDate(), // 日
|
||||
'[Hh]+': date.getHours(), // 小时
|
||||
'm+': date.getMinutes(), // 分
|
||||
's+': date.getSeconds(), // 秒
|
||||
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
|
||||
'S': date.getMilliseconds(), // 毫秒
|
||||
}
|
||||
fmt = fmt.replace(/[Yy]+/g, function (str) {
|
||||
return ('' + date.getFullYear()).slice(4 - str.length)
|
||||
})
|
||||
Object.keys(map).forEach((key) => {
|
||||
fmt = fmt.replace(new RegExp(key), function (str) {
|
||||
const value = '' + map[key]
|
||||
return str.length === 1 ? value : ('00' + value).slice(value.length)
|
||||
})
|
||||
})
|
||||
return fmt
|
||||
}
|
||||
Reference in New Issue
Block a user