vue實(shí)現(xiàn)一個(gè)炫酷的日歷組件
公司業(yè)務(wù)新開了一個(gè)商家管理微信H5移動(dòng)端項(xiàng)目,日歷控件是商家管理員查看通過日程來篩選獲取某日用戶的訂單等數(shù)據(jù)。 如圖: 假設(shè)今天為2018-09-02

90天前:

90天后;

產(chǎn)品需求:
- 展示當(dāng)前日期(服務(wù)器時(shí)間)前后90天,一共181天的日期。
- 日歷可以左右滑動(dòng)切換月份。
- 當(dāng)月份的如果不在181天區(qū)間的,需要置灰并且不可點(diǎn)擊。
- 點(diǎn)擊日歷綁定的節(jié)點(diǎn)的外部,關(guān)閉彈窗。
涉及內(nèi)容:
- 獲取服務(wù)器時(shí)間,渲染日歷數(shù)據(jù)
- vue-touch監(jiān)聽手勢(shì)滑動(dòng)事件
- ios日期兼容處理
- clickOutSide自定義指令
- mock模擬數(shù)據(jù)
開發(fā):
參考了 基于Vue開發(fā)一個(gè)日歷組件 - 掘金 日歷的年月日計(jì)算方式。 核心思想:假設(shè)當(dāng)前月份是二月份,根據(jù)二月和三月的1號(hào)是星期幾,來對(duì)二月進(jìn)行布局。(如果需要在二月顯示一月和三月的日期,還需要知道一月份有多少天)
在項(xiàng)目開發(fā)中,為了與后臺(tái)同事并行開發(fā)。項(xiàng)目采用來mock模擬數(shù)據(jù)來攔截接口。
日歷展盤
// calendar.vue
<template>
<div class="cp-calendar">
<v-touch
@swipeleft="handleNextMonth"
@swiperight="handlePreMonth"
class="calendar">
<div class="calendar-main" >
<span class="item-con header-item"
v-for="(item, index) in calendarHeader"
:key="index">{{item}}</span>
<div :class="`item-con ${todayStyle(item.content) && 'item-con-today'} ${item.type === 'disabled' && 'disableStyle'}`"
:style="{opacity: isChangeMonth ? 0 : 1}"
@click.stop="handleDayClick(item)"
v-for="(item, index) in getMonthDays(selectedYear, selectedMonth)"
:key="item.type + item.content + `${index}`">
<span
:class="`main-content ${selectedDateStyle(item.content) && 'selectedColor'}`">
{{setContent(item.content)}}</span>
<span :class="`${selectedDateStyle(item.content) && 'item-con-point'}`" ></span>
</div>
</div>
</v-touch>
</div>
</template>
初始化數(shù)據(jù) 針對(duì)服務(wù)器時(shí)間進(jìn)行初始數(shù)據(jù)處理
// calendar.vue
// 設(shè)置初始數(shù)據(jù)
initData () {
this.today = this.currentDate || getDateStr(0) // 如果沒有服務(wù)器時(shí)間,拿本地時(shí)間
this.prevDate = getDateStr(-90, this.currentDate)
this.nextDate = getDateStr(90, this.currentDate)
// 是否有手動(dòng)選中的日期
let selectedFullDate = this.storeSelectedFullDate
if (!this.storeSelectedFullDate) {
selectedFullDate = this.currentDate || getDateStr(0) // 如果沒有服務(wù)器時(shí)間,拿本地時(shí)間
}
this.selectedYear = Number(selectedFullDate.split('-')[0])
this.selectedMonth = Number(selectedFullDate.split('-')[1]) - 1
this.selectedDate = Number(selectedFullDate.split('-')[2])
this.selectedFullDate = `${this.selectedYear}-${this.selectedMonth + 1}-${this.selectedDate}`
},
/ 渲染日期
getMonthDays(year, month) {
// 定義每個(gè)月的天數(shù),如果是閏年第二月改為29天
let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
daysInMonth[1] = 29;
}
// 當(dāng)月第一天為周幾
let targetDay = new Date(year, month, 1).getDay();
let calendarDateList = [];
let preNum = targetDay;
let nextNum = 0;
if (targetDay > 0) {
// 當(dāng)前月份1號(hào)前的自然周剩余日期,置空
for (let i = 0; i < preNum; i++) {
let obj = {
type: 'pre',
content: ''
};
calendarDateList.push(obj);
}
}
// 判斷當(dāng)前年月份
let formatMonth = month + 1 >= 10 ? month + 1 : '0' + (month + 1)
this.prevYearMonthBoolean = (`${year}-${formatMonth}` === this.prevYearMonth)
this.nextYearMonthBoolean = (`${year}-${formatMonth}` === this.nextYearMonth)
for (let i = 0; i < daysInMonth[month]; i++) {
// 正常顯示的日期
let obj = {
type: 'normal',
content: i + 1
};
// 判斷是否為最往前或者最往后的月份,篩選出不可點(diǎn)擊的日期
if (this.prevYearMonthBoolean) {
let prevDay = this.prevDate.split('-')[2]
if (i + 1 < prevDay) {
obj.type = 'disabled'
}
} else if (this.nextYearMonthBoolean) {
let nextDay = this.nextDate.split('-')[2]
if (i + 1 > nextDay) {
obj.type = 'disabled'
}
}
calendarDateList.push(obj);
}
nextNum = 6 - new Date(year, month + 1, 0).getDay()
// 當(dāng)前月份最后一天的自然周剩余日期,置空
for (let i = 0; i < nextNum; i++) {
let obj = {
type: 'next',
content: ''
};
calendarDateList.push(obj);
}
return calendarDateList;
},
// 設(shè)置日期
setContent (content) {
if (!content) return ''
return `${this.selectedYear}-${this.tf(this.selectedMonth + 1)}-${this.tf(content)}` === this.today ? '今天' : content
},
// '今天'樣式開關(guān)
todayStyle (content) {
if (!content) return false
// Toast(`${this.selectedYear}-${this.tf(this.selectedMonth + 1)}-${this.tf(content)}`)
return `${this.selectedYear}-${this.tf(this.selectedMonth + 1)}-${this.tf(content)}` === this.today
},
// 當(dāng)前選中的日期樣式開關(guān)
selectedDateStyle (content) {
if (!content) return false
return `${this.selectedYear}-${this.selectedMonth + 1}-${content}` === this.selectedFullDate
},
// src/config/utils.js
// 公共方法
/**
* @param AddDayCount 必傳 今天前后N天的日期
* @param dateStr: 非必傳 獲取傳入日期前后N天的日期:'2018-01-20'
* @param type 非必傳 'lhRili'類型格式如'2018-7-3'
* @return 返回日期'2018/01/20'
*/
export const getDateStr = (AddDayCount, dateStr, type) => {
// console.log('getDateStr', AddDayCount, dateStr, type)
var dd
if (!dateStr) {
dd = new Date()
} else {
// 判斷是否為IOS
const isIOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
let formatDateStr = isIOS ? dateStr.replace(/-/g, '/') : dateStr
dd = new Date((formatDateStr.length < 12) ? formatDateStr + ' 00:00:00' : formatDateStr)
}
dd.setDate(dd.getDate() + AddDayCount) // 獲取AddDayCount天后的日期
let y = dd.getFullYear()
let m
let d
if (type === 'lhRili') {
m = dd.getMonth() + 1
d = dd.getDate()
} else {
let currentMon = (dd.getMonth() + 1)
let getDate = dd.getDate()
m = currentMon < 10 ? '0' + currentMon : currentMon // 獲取當(dāng)前月份的日期,不足10補(bǔ)0
d = getDate < 10 ? '0' + getDate : getDate // 獲取當(dāng)前幾號(hào),不足10補(bǔ)0
}
let time = y + '-' + m + '-' + d
return time
}
左右觸摸滑動(dòng)事件 判斷是否月份還可以繼續(xù)滑動(dòng)
// calendar.vue
// 上一個(gè)月
handlePreMonth() {
if (this.prevYearMonthBoolean) {
return
}
if (this.selectedMonth === 0) {
this.selectedYear = this.selectedYear - 1
this.selectedMonth = 11
this.selectedDate = 1
} else {
this.selectedMonth = this.selectedMonth - 1
this.selectedDate = 1
}
},
// 下一個(gè)月
handleNextMonth() {
if (this.nextYearMonthBoolean) {
return
}
if (this.selectedMonth === 11) {
this.selectedYear = this.selectedYear + 1
this.selectedMonth = 0
this.selectedDate = 1
} else {
this.selectedMonth = this.selectedMonth + 1
this.selectedDate = 1
}
},
vuex存儲(chǔ)數(shù)據(jù)
// src/store/schedule.js
const schedule = {
state: {
selectedDate: '', // 手動(dòng)點(diǎn)擊選中的日期
currentDate: '' // 服務(wù)器當(dāng)前日期
},
getters: {
getSelectedDate: state => state.selectedDate,
getCurrentDate: state => state.currentDate
},
mutations: {
SET_SELECTED_DATE: (state, data) => {
state.selectedDate = data
},
SET_CURRENT_DATE: (state, data) => {
state.currentDate = data
}
},
actions: {
setSelectedDate: ({ commit }, data) => commit('SET_SELECTED_DATE', data),
setCurrentDate: ({ commit }, data) => commit('SET_CURRENT_DATE', data)
}
};
export default schedule;
clickOutSide指令 指令方法監(jiān)聽
// src/directive/click-out-side.js
export default{
bind (el, binding, vnode) {
function documentHandler (e) {
if (el.contains(e.target)) {
return false;
}
if (binding.expression) {
binding.value(e);
}
}
el.__vueClickOutside__ = documentHandler;
document.addEventListener('click', documentHandler);
},
unbind (el, binding) {
document.removeEventListener('click', el.__vueClickOutside__);
delete el.__vueClickOutside__;
}
}
注冊(cè)指令
// src/directive/index.js
import clickOutSide from './click-out-side'
const install = function (Vue) {
Vue.directive('click-outside', clickOutSide)
}
if (window.Vue) {
window.clickOutSide = clickOutSide
Vue.use(install); // eslint-disable-line
}
clickOutSide.install = install
export default clickOutSide
// src/main.js import clickOutSide from '@/directive/click-out-side/index' Vue.use(clickOutSide)
使用方式:當(dāng)某節(jié)點(diǎn)外部需要觸發(fā)事件時(shí),掛載到該節(jié)點(diǎn)上
// calendar.vue <div class="cp-calendar" v-click-outside="spaceClick"> .... </div>
這里需要使用 fastclick 庫(kù)來消除解決移動(dòng)端點(diǎn)擊事件300ms延時(shí)
// src/mian.js import FastClick from 'fastclick' // 在移動(dòng)端,手指點(diǎn)擊一個(gè)元素,會(huì)經(jīng)過:touchstart --> touchmove -> touchend --> click。 FastClick.attach(document.body);
mock數(shù)據(jù)
// src/mock/index.js
// mock數(shù)據(jù)入口
import Mock from 'mockjs'
import currentTime from './currentTime'
// 攔截接口請(qǐng)求
Mock.mock(/\/schedule\/getCurrentTime/, 'get', currentTime)
export default Mock
// src/mock/currentTime.js
import Mock from 'mockjs'
export default {
getList: () => {
return {
'status': 'true',
'code': '200',
'msg': null,
'info': {
'currentDate': '2018-09-02'
}
}
}
}
// src/main.js
// 開發(fā)環(huán)境引入mock
if (process.env.NODE_ENV === 'development') {
require('./mock') // 需要在這里引入mock數(shù)據(jù)才可以全局?jǐn)r截請(qǐng)求
}
坑點(diǎn)
- 在微信內(nèi)置瀏覽器中,ios的日期格式跟安卓的日期格式分別是:YY/MM/DD和YY-MM-DD。這里需要對(duì)微信內(nèi)置瀏覽器User Agent進(jìn)行判斷。
- 獲取服務(wù)器時(shí)間的異步問題,把獲取到的服務(wù)器時(shí)間保存在vuex里面,在calendar.vue頁(yè)面監(jiān)聽當(dāng)前日期的變化。及時(shí)將日歷數(shù)據(jù)計(jì)算渲染出來。
推薦:
感興趣的朋友可以關(guān)注小編的微信公眾號(hào)【碼農(nóng)那點(diǎn)事兒】,更多網(wǎng)頁(yè)制作特效源碼及學(xué)習(xí)干貨哦?。?!
總結(jié)
以上所述是小編給大家介紹的vue實(shí)現(xiàn)一個(gè)炫酷的日歷組件,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
vueCli?4.x升級(jí)5.x報(bào)錯(cuò):Progress?Plugin?Invalid?Options的解決方法
本文主要介紹了vueCli?4.x升級(jí)5.x報(bào)錯(cuò):Progress?Plugin?Invalid?Options的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01
vue頁(yè)面渲染數(shù)組中數(shù)據(jù)文案后添加逗號(hào)最后不加
這篇文章主要為大家介紹了vue頁(yè)面渲染數(shù)組中數(shù)據(jù)文案后添加逗號(hào)最后不加逗號(hào)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
Vue3+TypeScript+printjs實(shí)現(xiàn)標(biāo)簽批量打印功能的完整過程
最近在做vue項(xiàng)目時(shí)使用到了print-js打印,這里給大家分享下,這篇文章主要給大家介紹了關(guān)于Vue3+TypeScript+printjs實(shí)現(xiàn)標(biāo)簽批量打印功能的完整過程,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09
vue router點(diǎn)擊打開新的標(biāo)簽頁(yè)的方法(最新推薦)
vue router點(diǎn)擊打開新的標(biāo)簽頁(yè)的方法,只需要在router-link中加入target="_blank"即可在新的頁(yè)面打開標(biāo)簽,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2023-10-10

