需要写一个日历组件有以下功能,element无法满足,自己造一个
目录
一、 按照 "日", "一", "二", "三", "四", "五", "六" 把一个月的日期排列
页面
- <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: {//标记
- 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();//当前月的最后一天
- const firstDayOfWeek = moment(props.month).startOf("month").day();
- // const daysInMonth = moment(props.month).daysInMonth();//当前月的天数
-
- 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;
- }
- .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',
- } : {};
- }
- const isCtrl = ref(false)
- const isShift = ref(false)
- const onKeyEvent = () => {
- window.addEventListener('keydown', e => {
- e.preventDefault();//取消默认事件
- 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 selectList = ref<any[]>([]);//已选择的
- const shiftNum = ref(0);//shift复制的起始位置
- const lastSelect = ref<any[]>([]);//按住shift倒数第二次复制的
- //遵循excel点击、ctrl、shift组合操作规范
- 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复制的
- selectList.value = selectList.value.concat(currentSelect);//添加本次按shift复制的
- 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);
- }
- <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: {//标记
- 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();//当前月的最后一天
- const firstDayOfWeek = moment(props.month).startOf("month").day();
- // const daysInMonth = moment(props.month).daysInMonth();//当前月的天数
-
- 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();//取消默认事件
- 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复制的起始位置
- const lastSelect = ref<any[]>([]);//按住shift倒数第二次复制的
- //遵循excel点击、ctrl、shift组合操作规范
- 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复制的
- selectList.value = selectList.value.concat(currentSelect);//添加本次按shift复制的
- 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>
使用: