vue可ctrl,shift多選,可添加標(biāo)記日歷組件詳細(xì)

需要寫一個日歷組件有以下功能,element無法滿足,自己造一個
只顯示當(dāng)前月日期,前后月日期不顯示也不可選擇;周六、周日顏色與工作日區(qū)分日期可以打標(biāo)記(左上角標(biāo)記)可ctrl+shift+鼠標(biāo)左鍵多選
一、 按照 "日", "一", "二", "三", "四", "五", "六" 把一個月的日期排列
頁面:
<template>
<div class="calendar">
<div class="calendar_header">{{currentMonth}}月</div>
<table cellspacing="0" cellpadding="0" class="calendar_table">
<thead>
<th v-for="day in WEEK_DAYS" :key="day" :class="['thead_th',day=='六'||day=='日'?'thead_th_red':'']">
{{day}}</th>
</thead>
<tbody>
<tr v-for="(row,index) in rows" :key="index">
<td v-for="(cell,key) in row" :key="key" class="td" @click="handlePickDay(cell)">
<div :class="getCellClass(cell,key)">
<div v-if="cell.type=='current'" class="triangle" :style="getCelltriangleStyle(cell)"></div>
<span class="cell_text">{{cell.text==0?'':cell.text}}</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>組件定義props、emits
const props = defineProps({
markList: {//標(biāo)記
type: Array<any>,
default: (): any[] => {
return [];
},
},
month: {
type: Date,
required: true,
},
disabled: {
type: Boolean,
default: false,
},
disableBefore: {//禁用今天之前的日期
type: Boolean,
default: true,
},
});
const emits = defineEmits(['change']);當(dāng)前月的日期數(shù)組:
type CalendarDateCellType = 'next' | 'prev' | 'current'
type CalendarDateCell = {
text: number,
type: CalendarDateCellType
}
const WEEK_DAYS = ref(["日", "一", "二", "三", "四", "五", "六"]);
const currentMonth = computed(() => {
return moment(props.month).format('M');
})
onMounted(() => {
onKeyEvent();
})
const rows = computed(() => {
let days: CalendarDateCell[] = []
const firstDay = moment(props.month).startOf("month").date();
// const endDay = moment(props.month).endOf('month').date();//當(dāng)前月的最后一天
const firstDayOfWeek = moment(props.month).startOf("month").day();
// const daysInMonth = moment(props.month).daysInMonth();//當(dāng)前月的天數(shù)
const prevMonthDays: CalendarDateCell[] = getPrevMonthLastDays(
firstDay,
firstDayOfWeek - firstDay
).map((day) => ({
text: 0,//上月補0
type: 'prev',
}))
const currentMonthDays: CalendarDateCell[] = getMonthDays(moment(props.month).daysInMonth()).map(
(day) => ({
text: day,
type: 'current',
})
)
days = [...prevMonthDays, ...currentMonthDays]
const remaining = 7 - (days.length % 7 || 7)
const nextMonthDays: CalendarDateCell[] = rangeArr(remaining).map(
(_, index) => ({
text: 0,//下月補0
type: 'next',
})
)
days = days.concat(nextMonthDays)
// console.log(currentMonth.value, firstDay, endDay, moment(props.month).startOf("month").day());
return toNestedArr(days)
})二、單元格樣式處理
- 禁用
- 選中
- 周六、周日紅色字體
const getCellClass = (cell: CalendarDateCell, key: number) => {
let date = getCellDate(cell);
if (props.disableBefore && date.getTime() < new Date().getTime()) {
return ['cell', 'cell_disabled'];//禁用
}
let classes: string[] = ['cell', 'cell_enabled'];
if (key == 0 || key == 6) {//周六、周日
classes.push('cell_red');
}
let index = selectList.value.indexOf(moment(date).format('YYYY-MM-DD'));
if (index != -1) {
classes.push('cell_active');//選中
}
return classes;
}左上角三角形標(biāo)記
.triangle {
position: absolute;
left: 0;
top: 0;
width: 0;
height: 0;
// border-top: 30px solid #e6e911;
border-right: 30px solid transparent;
}const getCelltriangleStyle = (cell: CalendarDateCell) => {
let date = getCellDate(cell);
let day = props.markList.find(item => item.date == moment(date).format('YYYY-MM-DD'));
return day ? {
borderTop: '30px solid #e6e911',
} : {};
}三、單機、按住ctrl點擊、按住shift點擊事件處理
1.記錄鍵盤按下ctrl、shift事件
const isCtrl = ref(false)
const isShift = ref(false)
const onKeyEvent = () => {
window.addEventListener('keydown', e => {
e.preventDefault();//取消默認(rèn)事件
let e1 = e || window.event
switch (e1.keyCode) {
case 16:
isShift.value = true;
break;
case 17:
isCtrl.value = true;
break;
}
})
window.addEventListener('keyup', e => {
e.preventDefault();
let e1 = e || window.event
switch (e1.keyCode) {
case 16:
isShift.value = false;
break;
case 17:
isCtrl.value = false;
break;
}
})
}2.點擊事件處理
const selectList = ref<any[]>([]);//已選擇的
const shiftNum = ref(0);//shift復(fù)制的起始位置
const lastSelect = ref<any[]>([]);//按住shift倒數(shù)第二次復(fù)制的
//遵循excel點擊、ctrl、shift組合操作規(guī)范
const handlePickDay = (cell: CalendarDateCell) => {
let date = getCellDate(cell);
if (cell.type != 'current') {
return;
}
if (props.disableBefore && date.getTime() < new Date().getTime()) {
return
}
// console.log(isCtrl.value, isShift.value);
let dateStr = moment(date).format('YYYY-MM-DD');
let currentSelect: string[] = [];
//按住ctrl
if (isCtrl.value) {
if (selectList.value.includes(dateStr)) {
selectList.value.splice(selectList.value.indexOf(dateStr), 1);
} else {
selectList.value.push(dateStr);
}
lastSelect.value = [];
} else if (isShift.value) {//按住shift
if (shiftNum.value == 0) {//無上次點擊
shiftNum.value = cell.text;
if (selectList.value.includes(dateStr)) {
selectList.value.splice(selectList.value.indexOf(dateStr), 1);
} else {
selectList.value.push(dateStr);
}
} else {
if (shiftNum.value < cell.text) {
currentSelect = getDatesInRange(shiftNum.value, cell.text);
} else if (shiftNum.value > cell.text) {
currentSelect = getDatesInRange(cell.text, shiftNum.value);
} else {
currentSelect = [dateStr];
}
selectList.value = selectList.value.filter(item => !lastSelect.value.includes(item));//移除上次按shift復(fù)制的
selectList.value = selectList.value.concat(currentSelect);//添加本次按shift復(fù)制的
lastSelect.value = currentSelect;
}
} else {
selectList.value = [dateStr];
}
if (!isShift.value) {
shiftNum.value = cell.text;
}
selectList.value = [...new Set(selectList.value)].sort();//去重、排序
console.log(shiftNum.value, selectList.value);
emits('change', selectList.value);
}3.遵循excel點擊的操作方式:
- 未按ctrl、shift點擊=>只選擇當(dāng)前點擊的;
- 按ctrl點擊=>未選中則選中,已選中則取消選中;
- 按住shift點擊=>記錄shift按下時點擊位置-->再次點擊時把期間內(nèi)的選中,并移除倒數(shù)第二次的選中(否則都會選中,并在按下ctrl時釋放上次選中)
四、組件代碼:
<template>
<div class="calendar">
<div class="calendar_header">{{currentMonth}}月</div>
<table cellspacing="0" cellpadding="0" class="calendar_table">
<thead>
<th v-for="day in WEEK_DAYS" :key="day" :class="['thead_th',day=='六'||day=='日'?'thead_th_red':'']">
{{day}}</th>
</thead>
<tbody>
<tr v-for="(row,index) in rows" :key="index">
<td v-for="(cell,key) in row" :key="key" class="td" @click="handlePickDay(cell)">
<div :class="getCellClass(cell,key)">
<div v-if="cell.type=='current'" class="triangle" :style="getCelltriangleStyle(cell)"></div>
<span class="cell_text">{{cell.text==0?'':cell.text}}</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from "vue";
import moment from "moment";
const props = defineProps({
markList: {//標(biāo)記
type: Array<any>,
default: (): any[] => {
return [];
},
},
month: {
type: Date,
required: true,
},
disabled: {
type: Boolean,
default: false,
},
disableBefore: {//禁用今天之前的日期
type: Boolean,
default: true,
},
});
const emits = defineEmits(['change']);
type CalendarDateCellType = 'next' | 'prev' | 'current'
type CalendarDateCell = {
text: number,
type: CalendarDateCellType
}
const WEEK_DAYS = ref(["日", "一", "二", "三", "四", "五", "六"]);
const currentMonth = computed(() => {
return moment(props.month).format('M');
})
onMounted(() => {
onKeyEvent();
})
const rows = computed(() => {
let days: CalendarDateCell[] = []
const firstDay = moment(props.month).startOf("month").date();
// const endDay = moment(props.month).endOf('month').date();//當(dāng)前月的最后一天
const firstDayOfWeek = moment(props.month).startOf("month").day();
// const daysInMonth = moment(props.month).daysInMonth();//當(dāng)前月的天數(shù)
const prevMonthDays: CalendarDateCell[] = getPrevMonthLastDays(
firstDay,
firstDayOfWeek - firstDay
).map((day) => ({
text: 0,//上月補0
type: 'prev',
}))
const currentMonthDays: CalendarDateCell[] = getMonthDays(moment(props.month).daysInMonth()).map(
(day) => ({
text: day,
type: 'current',
})
)
days = [...prevMonthDays, ...currentMonthDays]
const remaining = 7 - (days.length % 7 || 7)
const nextMonthDays: CalendarDateCell[] = rangeArr(remaining).map(
(_, index) => ({
text: 0,//下月補0
type: 'next',
})
)
days = days.concat(nextMonthDays)
// console.log(currentMonth.value, firstDay, endDay, moment(props.month).startOf("month").day());
return toNestedArr(days)
})
const rangeArr = (n: number) => {
return Array.from(Array.from({ length: n }).keys())
}
const toNestedArr = (days: CalendarDateCell[]) =>
rangeArr(days.length / 7).map((index) => {
const start = index * 7
return days.slice(start, start + 7)
});
const getPrevMonthLastDays = (lastDay: number, count: number) => {
return rangeArr(count).map((_, index) => lastDay - (count - index - 1))
}
const getMonthDays = (days: number) => {
return rangeArr(days).map((_, index) => index + 1)
}
/**
* 獲取范圍期間所有日期
* @ return array['YYYY-MM-DD']
*/
const getDatesInRange = (start: number, end: number): string[] => {
let list = [];
for (let i = start; i <= end; i++) {
let dateStr = moment(getCellDate({ text: i, type: 'current' })).format('YYYY-MM-DD');
list.push(dateStr);
}
return list;
}
const isCtrl = ref(false)
const isShift = ref(false)
const onKeyEvent = () => {
window.addEventListener('keydown', e => {
e.preventDefault();//取消默認(rèn)事件
let e1 = e || window.event
switch (e1.keyCode) {
case 16:
isShift.value = true;
break;
case 17:
isCtrl.value = true;
break;
}
})
window.addEventListener('keyup', e => {
e.preventDefault();
let e1 = e || window.event
switch (e1.keyCode) {
case 16:
isShift.value = false;
break;
case 17:
isCtrl.value = false;
break;
}
})
}
const getCellClass = (cell: CalendarDateCell, key: number) => {
let date = getCellDate(cell);
if (props.disableBefore && date.getTime() < new Date().getTime()) {
return ['cell', 'cell_disabled'];//禁用
}
let classes: string[] = ['cell', 'cell_enabled'];
if (key == 0 || key == 6) {//周六、周日
classes.push('cell_red');
}
let index = selectList.value.indexOf(moment(date).format('YYYY-MM-DD'));
if (index != -1) {
classes.push('cell_active');//選中
}
return classes;
}
const getCelltriangleStyle = (cell: CalendarDateCell) => {
let date = getCellDate(cell);
let day = props.markList.find(item => item.date == moment(date).format('YYYY-MM-DD'));
return day ? {
borderTop: '30px solid #e6e911',
} : {};
}
const getCellDate = (cell: CalendarDateCell) => new Date(props.month.getFullYear(), props.month.getMonth(), cell.text)
const selectList = ref<any[]>([]);//已選擇的
const shiftNum = ref(0);//shift復(fù)制的起始位置
const lastSelect = ref<any[]>([]);//按住shift倒數(shù)第二次復(fù)制的
//遵循excel點擊、ctrl、shift組合操作規(guī)范
const handlePickDay = (cell: CalendarDateCell) => {
let date = getCellDate(cell);
if (cell.type != 'current') {
return;
}
if (props.disableBefore && date.getTime() < new Date().getTime()) {
return
}
// console.log(isCtrl.value, isShift.value);
let dateStr = moment(date).format('YYYY-MM-DD');
let currentSelect: string[] = [];
//按住ctrl
if (isCtrl.value) {
if (selectList.value.includes(dateStr)) {
selectList.value.splice(selectList.value.indexOf(dateStr), 1);
} else {
selectList.value.push(dateStr);
}
lastSelect.value = [];
} else if (isShift.value) {//按住shift
if (shiftNum.value == 0) {//無上次點擊
shiftNum.value = cell.text;
if (selectList.value.includes(dateStr)) {
selectList.value.splice(selectList.value.indexOf(dateStr), 1);
} else {
selectList.value.push(dateStr);
}
} else {
if (shiftNum.value < cell.text) {
currentSelect = getDatesInRange(shiftNum.value, cell.text);
} else if (shiftNum.value > cell.text) {
currentSelect = getDatesInRange(cell.text, shiftNum.value);
} else {
currentSelect = [dateStr];
}
selectList.value = selectList.value.filter(item => !lastSelect.value.includes(item));//移除上次按shift復(fù)制的
selectList.value = selectList.value.concat(currentSelect);//添加本次按shift復(fù)制的
lastSelect.value = currentSelect;
}
} else {
selectList.value = [dateStr];
}
if (!isShift.value) {
shiftNum.value = cell.text;
}
selectList.value = [...new Set(selectList.value)].sort();//去重、排序
console.log(shiftNum.value, selectList.value);
emits('change', selectList.value);
}
</script>
<style lang="scss" scoped>
.calendar {
width: 100%;
padding: 12px 20px 35px;
&_header {
display: flex;
justify-content: center;
border: 1px solid var(--el-border-color-lighter);
padding: 12px 20px;
}
&_table {
width: 100%;
.thead_th {
padding: 12px 0;
color: var(--el-text-color-regular);
font-weight: 400;
&_red {
color: red;
}
}
}
.td {
border: 1px solid var(--el-border-color-lighter);
-moz-user-select: none;
/*火狐*/
-webkit-user-select: none;
/*webkit瀏覽器*/
-ms-user-select: none;
/*IE10*/
-khtml-user-select: none;
/*早期瀏覽器*/
user-select: none;
}
.cell {
// background-color: #409eff;
position: relative;
text-align: center;
min-height: 50px;
display: flex;
justify-content: center;
&_enabled {
cursor: pointer;
color: #373737
}
&_disabled {
cursor: not-allowed;
color: #9b9da1;
}
&_red {
color: red;
}
&_active {
background-color: #409eff;
}
.triangle {
position: absolute;
left: 0;
top: 0;
width: 0;
height: 0;
// border-top: 30px solid #e6e911;
border-right: 30px solid transparent;
}
&_text {
margin: auto;
}
}
}
</style>使用:
<Calendar :month="month" :markList="list" @change="onChange"></Calendar>
到此這篇關(guān)于vue可ctrl,shift多選,可添加標(biāo)記的日歷組件的文章就介紹到這了,更多相關(guān)vue ctrl shift內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue中數(shù)組常用的6種循環(huán)方法代碼示例
在vue項目開發(fā)中,我們需要對數(shù)組進(jìn)行處理等問題,這里簡單記錄遍歷數(shù)組的幾種方法,這篇文章主要給大家介紹了關(guān)于vue中數(shù)組常用的6種循環(huán)方法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-03-03
如何在Vue3中正確使用ElementPlus,親測有效,避坑
這篇文章主要介紹了如何在Vue3中正確使用ElementPlus,親測有效,避坑!具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
基于 Vue.js 2.0 酷炫自適應(yīng)背景視頻登錄頁面實現(xiàn)方式
本文講述如何實現(xiàn)擁有酷炫背景視頻的登錄頁面,瀏覽器窗口隨意拉伸,背景視頻及前景登錄組件均能完美適配,背景視頻可始終鋪滿窗口,前景組件始終居中,視頻的內(nèi)容始終得到最大限度的保留,可以得到最好的視覺效果2018-01-01
vue項目接入高德地圖點擊地圖獲取經(jīng)緯度以及省市區(qū)功能
這篇文章主要給大家介紹了關(guān)于vue項目接入高德地圖點擊地圖獲取經(jīng)緯度以及省市區(qū)功能的相關(guān)資料,開發(fā)中我們需要地圖定位,就是用戶輸入位置,自動定位獲取經(jīng)緯度,需要的朋友可以參考下2023-08-08
Vue實現(xiàn)刷新當(dāng)前頁面的三種方式總結(jié)
項目當(dāng)中如果做新增/修改/刪除等等操作通常情況下都需要刷新數(shù)據(jù)或者刷新當(dāng)前頁面。本文為大家整理了三種不同的實現(xiàn)方法,需要的可以參考一下2023-01-01

