vue 日历组件

遇到一个需求,不用任何第三方库,只基于vue2实现一个日历组件,末尾附上我的代码,单文件,不要找文件长度的茬。下面是需求

  • 样式是类似于window10日历
  • 支持控制周一还是周日在第一列
  • 支持鼠标滑动切换
  • 支持单选,拖动鼠标多选,范围选择
  • 支持年月日选择切换
  • 支持传入选中数据
  • 支持隐藏非本月日期

一共七个需求,不多。我的代码附上,欢迎批评指出不足

1<template> 2 <!-- 外部 --> 3 <div class="Q-calendar" @mouseup.stop="onMouseUp" @mouseleave.stop="onMouseleave"> 4 <!-- 头部 --> 5 <div class="Q-calendar-title" v-if="hideTitle"> 6 <div class="Q-calendar-title-box" style="padding-left: 8px"> 7 <div @click="onClickYears" class="Q-calendar-title-box-text"> 8 {{ this.currentYear }}年 9 </div> 10 <div @click="onClickMonth" class="Q-calendar-title-box-text"> 11 {{ this.currentMonth }}月 12 </div> 13 <!-- <div>{{this.currentDay}}号</div> --> 14 </div> 15 <slot name="Q-calendar-header-slot" v-if="isSlot===1||isSlot===2"></slot> 16 <div class="Q-calendar-title-box" v-if="isSlot===0 ||isSlot===2"> 17 <div class="Q-calendar-title-box-text" @click="onClickUp">上</div> 18 <div class="Q-calendar-title-box-text" @click="onClickDown">下</div> 19 </div> 20 </div> 21 <div v-else class="Q-calendar-title"> 22 <div class="Q-calendar-title-box" style="margin: 0 auto; font-weight: 700"> 23 <div class="Q-calendar-title-box-text">{{ this.currentYear }}年</div> 24 <div class="Q-calendar-title-box-text">{{ this.currentMonth }}月</div> 25 </div> 26 </div> 27 <!-- 日历 --> 28 <transition name="change"> 29 <div class="Q-calendar-day" v-if="hide === 1" @mousewheel="onMousewheel"> 30 <!-- 周一到周日 --> 31 <div class="Q-calendar-week"> 32 <p v-for="item in isMonday ? Monday : sun" :key="item">{{ item }}</p> 33 </div> 34 <div class="Q-calendar-box"> 35 <!-- 上个月剩余天数 --> 36 <div class="Q-calendar-surplus" v-for="item in lastMonthDays" :key="'dayA' + item"> 37 <span v-show="thisDate"> {{ item }}</span> 38 </div> 39 <!-- 当前月份天数 --> 40 <div v-for="item in currentMonthDays" :key="'dayB' + item.day" class="Q-calendar-current-month" 41 :style="cssProps" @click="onChangeDay(item)" :class="{ 42 checked: item.checked, 43 nowCss: 44 new Date().getFullYear()+'' === item.year && 45 (new Date().getMonth() + 1)+'' === item.month && 46 new Date().getDate()+'' === item.day, 47 }" @mouseover="dragDay(item)" @mousedown="onMouseDown(item)"> 48 <span> {{ item.day }}</span> 49 </div> 50 <!-- 下月余出 --> 51 <div class="Q-calendar-surplus" v-for="item in this.nextMonth()" :key="'dayC' + item"> 52 <span v-show="thisDate">{{ item }}</span> 53 </div> 54 </div> 55 </div> 56 </transition> 57 <!-- 月 --> 58 <transition name="change"> 59 <div class="Q-calendar-years" v-if="hide === 2" @mousewheel="onMousewheel"> 60 <div class="Q-calendar-years-box"> 61 <div v-for="item in clickMonth" :key="'monthA' + item.value" @click="onChangeMonth(item)" :class="{ 62 nowCss: isNowYear && new Date().getMonth() + 1 === item.value, 63 }"> 64 <span>{{ item.key }}</span> 65 </div> 66 <div class="Q-calendar-surplus" v-for="item in lastMonth" :key="'monthB' + item"> 67 <span>{{ item }}</span> 68 </div> 69 </div> 70 </div> 71 </transition> 72 <!-- 年 --> 73 <transition name="change"> 74 <div class="Q-calendar-years Q-calendar-year" v-if="hide === 3" @mousewheel="onMousewheel"> 75 <div class="Q-calendar-years-box"> 76 <div class="Q-calendar-surplus" v-for="item in lastYear" :key="'yearA' + item"> 77 <span>{{ item }}</span> 78 </div> 79 <div v-for="item in thisYear" :key="'yearB' + item" @click="onChangeYear" 80 :class="{ nowCss: new Date().getFullYear() === item }"> 81 <span>{{ item }}</span> 82 </div> 83 </div> 84 </div> 85 </transition> 86 </div> 87</template> 88<script> 89/** 90 * 日历组件 91 * @description 92 * @property {Boolean} thisDate false 是否展示非本月份的日期 93 * @property {Boolean} hideTitle true 是否展示title 94 * @property {Boolean} multiSelect false 是否开启摁下鼠标进行多选 95 * @property {String} checkBGColor "#4152b3" 选中的背景色 96 * @property {String} checkColor "#ffffff" 选中的文字色 97 * @property {Boolean} isMonday true 是否从周一在开头 98 * @property {Number} selectionMethod 1 单选1,多选2,范围选3 99 * @property {Array} selectList [] 选中数据的数组 100 * @property {Number} isSlot 0 默认非插槽0,插槽需要传入1,插槽和默认上下选择同事存在2 101 * 102 */ 103export default { 104 name: "QCalendar", 105 props: { 106 thisDate: { 107 type: Boolean, 108 default: false, 109 }, 110 hideTitle: { 111 type: Boolean, 112 default: true, 113 }, 114 multiSelect: { 115 type: Boolean, 116 default: false, 117 }, 118 checkBGColor: { 119 type: String, 120 default: "#4152b3", 121 }, 122 checkColor: { 123 type: String, 124 default: "#ffffff", 125 }, 126 isMonday: { 127 type: Boolean, 128 default: true, 129 }, 130 selectionMethod: { 131 type: Number, 132 default: 1, 133 }, 134 selectList: { 135 type: Array, 136 default: () => [] 137 }, 138 isSlot: { 139 type: Number, 140 default: 0, 141 } 142 }, 143 data() { 144 return { 145 isMouseDown: false, 146 arr: this.selectList, 147 isNowYear: true, 148 isNowMOnth: true, 149 hide: 1, 150 sun: ["日", "一", "二", "三", "四", "五", "六"], 151 Monday: ["一", "二", "三", "四", "五", "六", "日"], 152 clickMonth: [ 153 { key: "一月", value: 1 }, 154 { key: "二月", value: 2 }, 155 { key: "三月", value: 3 }, 156 { key: "四月", value: 4 }, 157 { key: "五月", value: 5 }, 158 { key: "六月", value: 6 }, 159 { key: "七月", value: 7 }, 160 { key: "八月", value: 8 }, 161 { key: "九月", value: 9 }, 162 { key: "十月", value: 10 }, 163 { key: "十一月", value: 11 }, 164 { key: "十二月", value: 12 }, 165 ], 166 lastMonth: ["一月", "二月", "三月", "四月"], 167 lastYear: [], 168 thisYear: [], 169 // 当前日 170 currentDay: new Date().getDate(), 171 // 当前月 172 currentMonth: new Date().getMonth() + 1, 173 // 当前年 174 currentYear: new Date().getFullYear(), 175 }; 176 }, 177 created() { 178 this.initParameter(); 179 }, 180 computed: { 181 cssProps() { 182 return { 183 "--background-color": this.checkBGColor, 184 "--color": this.checkColor, 185 }; 186 }, 187 // 当前月的天数 188 currentMonthDays() { 189 let dayLength = new Date( 190 this.currentYear, 191 this.currentMonth, 192 0 193 ).getDate(); 194 let arr = []; 195 for (let h = 0; h < dayLength; h++) { 196 let dataObj = { 197 year: this.currentYear + "", 198 month: this.currentMonth + "", 199 day: (h + 1) + "", 200 checked: false, 201 }; 202 arr[h] = dataObj; 203 } 204 for (let p = 0; p < this.arr.length; p++) { 205 for (let k = 0; k < arr.length; k++) { 206 if ((this.arr[p].year === arr[k].year + "") && (this.arr[p].month === arr[k].month + "") && (this.arr[p].day === arr[k].day + "") && this.arr[p].checked) { 207 arr[k].checked = true 208 } 209 } 210 } 211 return arr; 212 }, 213 // 获取上个月的剩余多少天 214 lastMonthDays() { 215 const lastLength = new Date( 216 this.currentYear, 217 this.currentMonth - 1, 218 0 219 ).getDate(); 220 let cutLength; 221 if (this.isMonday) { 222 cutLength = new Date( 223 this.currentYear, 224 this.currentMonth - 1, 225 0 226 ).getDay(); 227 } else { 228 cutLength = new Date( 229 this.currentYear, 230 this.currentMonth - 1, 231 1 232 ).getDay(); 233 } 234 let arr = []; 235 for (let h = lastLength - cutLength + 1; h <= lastLength; h++) { 236 arr.push(h); 237 } 238 return arr; 239 }, 240 }, 241 methods: { 242 onMousewheel(e) { 243 let evt = e || window.event; //考虑兼容性 244 evt.preventDefault(); 245 if (evt.deltaY > 0) { 246 // console.log("向下滚动"); 247 this.onClickDown(); 248 } else { 249 // console.log("向上滚动"); 250 this.onClickUp(); 251 } 252 //检查事件 253 // console.log(evt.type, evt.deltaX, evt.deltaY, evt.deltaZ); 254 }, 255 dragDay(dayObj) { 256 if (!this.multiSelect) { 257 return; 258 } else { 259 if (!this.isMouseDown) { 260 return; 261 } else { 262 this.onChangeDay(dayObj); 263 } 264 } 265 }, 266 onMouseDown(dayObj) { 267 if (!this.multiSelect) { 268 return; 269 } else { 270 if (this.isMouseDown) this.onChangeDay(dayObj); 271 this.isMouseDown = true; 272 } 273 }, 274 onMouseUp() { 275 // console.log('松开'); 276 this.isMouseDown = false; 277 }, 278 onMouseleave() { 279 // console.log('超出'); 280 if (this.isMouseDown) { 281 this.isMouseDown = false; 282 } 283 }, 284 // 点击多选标签 285 onChangeDay(val) { 286 // console.log(val,1111111111); 287 // 判断单选,多选,还是范围选,对应值1.2.3. 288 if (this.selectionMethod === 1) { 289 if (this.arr.length === 0) { 290 val.checked = true; 291 this.arr.push(val); 292 } else if (this.arr.length === 1) { 293 // console.log(this.arr[0],val,3333); 294 if ((this.arr[0].year === val.year) && (this.arr[0].month === val.month) && (this.arr[0].day === val.day)) { 295 this.arr = [] 296 } else { 297 this.arr = [] 298 val.checked = true; 299 this.arr.push(val); 300 } 301 } else { 302 return 303 } 304 } else if (this.selectionMethod === 2) { 305 if (val.checked) { 306 // 剔除 307 val.checked = false; 308 this.arr = this.arr.filter((ele) => { 309 return !( 310 ele.year === val.year && 311 ele.month === val.month && 312 ele.day === val.day 313 ); 314 }); 315 } else { 316 // 添加 317 val.checked = true; 318 this.arr.push(val); 319 } 320 } else if (this.selectionMethod === 3) { 321 // 范围选择, 322 if (this.arr.length === 0) { 323 val.checked = true; 324 this.arr.push(val); 325 } else if (this.arr.length === 1) { 326 val.checked = true; 327 this.arr.push(val); 328 const newArr1 = JSON.stringify(this.arr[0]) 329 const newArr2 = JSON.stringify(this.arr[1]) 330 // console.log(this.arr,'this.arr'); 331 const arrS = this.getRangeDay(newArr1, newArr2) 332 this.arr = [] 333 // this.arr.push(...arrS) 334 this.arr = arrS 335 } else { 336 this.arr = [] 337 val.checked = true; 338 this.arr.push(val); 339 } 340 } 341 this.$emit("getSelectedData", this.arr); 342 }, 343 // 获取日期范围内天数 344 getRangeDay(startDate, endDate) { 345 startDate = JSON.parse(startDate) 346 endDate = JSON.parse(endDate) 347 const result = []; 348 const db = new Date(); 349 db.setUTCFullYear(startDate.year, startDate.month - 1, startDate.day); 350 const de = new Date(); 351 de.setUTCFullYear(endDate.year, endDate.month - 1, endDate.day); 352 let smallDate 353 let bigDate 354 if (db.getTime() > de.getTime()) { 355 smallDate = de.getTime() 356 bigDate = db.getTime() 357 } else { 358 smallDate = db.getTime() 359 bigDate = de.getTime() 360 } 361 // console.log(smallDate,bigDate,6666666); 362 // const unixDb = db.getTime(); 363 // const unixDe = de.getTime(); 364 for (let k = smallDate; k <= bigDate;) { 365 result.push({ 366 year: this.parseTime(k, "{y}"), 367 month: this.parseTime(k, "{m}"), 368 day: this.parseTime(k, "{d}"), 369 checked: true 370 }); 371 k = k + 24 * 60 * 60 * 1000; 372 } 373 return result; 374 }, 375 // 日期格式化 376 parseTime(time, pattern) { 377 if (arguments.length === 0 || !time) return null; 378 const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'; 379 let date; 380 if (typeof time === 'object') { 381 date = time; 382 } else { 383 if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { 384 time = parseInt(time); 385 } else if (typeof time === 'string') { 386 time = time.replace(new RegExp(/-/gm), '/'); 387 } 388 if ((typeof time === 'number') && (time.toString().length === 10)) { 389 time = time * 1000; 390 } 391 date = new Date(time); 392 } 393 394 const formatObj = { 395 y: date.getFullYear(), 396 m: date.getMonth() + 1, 397 d: date.getDate(), 398 h: date.getHours(), 399 i: date.getMinutes(), 400 s: date.getSeconds(), 401 a: date.getDay(), 402 }; 403 404 return format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 405 // @ts-ignore 406 let value = formatObj[key]; 407 // Note: getDay() returns 0 on Sunday 408 if (key === 'a') { 409 return ['日', '一', '二', '三', '四', '五', '六'][value]; 410 } 411 // if (result.length > 0 && value < 10) { 412 // value = '0' + value; 413 // } 414 return value || 0; 415 }); 416 }, 417 initParameter() { 418 let currentYear = this.currentYear - 1; 419 for (let p = 3; p >= 0; p--) { 420 this.lastYear[p] = currentYear--; 421 } 422 currentYear = this.currentYear; 423 for (let l = 0; l < 12; l++) { 424 this.thisYear[l] = currentYear++; 425 } 426 }, 427 onChangeYear(val) { 428 this.hide = 2; 429 let currentYear = new Date().getFullYear(); 430 this.currentYear = val.srcElement.innerText; 431 if (val.srcElement.innerText + "" === currentYear + "") { 432 this.isNowYear = true; 433 } else { 434 this.isNowYear = false; 435 } 436 }, 437 onChangeMonth(val, vap) { 438 this.hide = 1; 439 let currentMonth = new Date().getMonth() + 1; 440 this.currentMonth = val.value; 441 if (val.value + "" === currentMonth + "") { 442 this.isNowMOnth = true; 443 } else { 444 this.isNowMOnth = false; 445 } 446 }, 447 // 点击年 448 onClickYears(val) { 449 let currentYear = this.currentYear - 1; 450 for (let p = 3; p >= 0; p--) { 451 this.lastYear[p] = currentYear--; 452 } 453 this.hide = 3; 454 }, 455 // 点击月 456 onClickMonth(val) { 457 this.hide = 2; 458 }, 459 // 获取上个月的剩余多少天 460 nextMonth() { 461 const ac = 42 - this.currentMonthDays.length - this.lastMonthDays.length; 462 return ac; 463 }, 464 // 上 465 onClickUp() { 466 let currentYear = new Date().getFullYear(); 467 // let currentMonth= new Date().getMonth() + 1 468 if (this.hide === 1) { 469 if (this.currentMonth === 1) { 470 this.currentYear--, (this.currentMonth = 13); 471 } 472 this.currentMonth--; 473 } else if (this.hide === 2) { 474 this.currentYear--; 475 if (this.currentYear !== currentYear) { 476 this.isNowYear = false; 477 } else { 478 this.isNowYear = true; 479 } 480 } else { 481 this.switchingYear(1); 482 } 483 }, 484 // 下 485 onClickDown() { 486 let currentYear = new Date().getFullYear(); 487 if (this.hide === 1) { 488 // 日 489 if (this.currentMonth === 12) { 490 this.currentYear++, (this.currentMonth = 0); 491 } 492 this.currentMonth++; 493 } else if (this.hide === 2) { 494 // 月默认切换年 495 this.currentYear++; 496 if (this.currentYear !== currentYear) { 497 this.isNowYear = false; 498 } else { 499 this.isNowYear = true; 500 } 501 } else { 502 // 切换年的选择 503 this.switchingYear(2); 504 } 505 }, 506 switchingYear(type) { 507 // 1上,2下 508 if (type === 1) { 509 // last最后一个为this的最后一个 510 let thisAnchor = this.lastYear[3] - 11; 511 let lastAnchor = this.lastYear[3] - 15; 512 this.thisYear = []; 513 for (let p = 0; p < 12; p++) { 514 this.thisYear[p] = thisAnchor++; 515 } 516 this.lastYear = []; 517 for (let l = 0; l < 4; l++) { 518 this.lastYear[l] = lastAnchor++; 519 } 520 } else if (type === 2) { 521 let anchor = this.thisYear[11] + 1; 522 this.lastYear = []; 523 for (let p = 3; p >= 0; p--) { 524 this.lastYear[p] = this.thisYear[11]--; 525 } 526 this.thisYear = []; 527 for (let l = 0; l < 12; l++) { 528 this.thisYear[l] = anchor++; 529 } 530 } 531 }, 532 }, 533}; 534</script> 535 536<style scoped lang="scss"> 537.change-enter-active, 538.change-leave-active { 539 transition: opacity 0.5s; 540} 541 542.change-enter, 543.change-leave-to 544 545/* .fade-leave-active, 2.1.8 版本以下 */ 546 { 547 opacity: 0; 548} 549 550.Q-calendar { 551 width: 100%; 552 height: 100%; 553 margin: 0 auto; 554 overflow: hidden; 555 background-color: #ffffff; 556 color: #000; 557 user-select: none; 558 border: 1px solid #4152b3; 559 560 .Q-calendar-title { 561 height: 50px; 562 width: 100%; 563 box-sizing: border-box; 564 display: flex; 565 justify-content: space-between; 566 567 div { 568 align-self: center; 569 } 570 571 .Q-calendar-button, 572 .top { 573 align-self: center; 574 } 575 576 .Q-calendar-title-box { 577 width: calc((100% / 7) * 2); 578 display: flex; 579 justify-content: space-around; 580 cursor: default; 581 582 .Q-calendar-title-box-text { 583 width: 50%; 584 text-align: center; 585 align-self: center; 586 } 587 588 .Q-calendar-title-box-text:hover { 589 // color:yellowgreen; 590 color: #4152b3; 591 font-weight: 700; 592 // background-color: #4152b3; 593 } 594 } 595 } 596 597 // rili 598 .Q-calendar-day { 599 height: calc(100% - 50px); 600 601 /* 周末 */ 602 .Q-calendar-week { 603 display: flex; 604 justify-content: inherit; 605 cursor: default; 606 607 p { 608 display: flex; 609 justify-content: center; 610 width: calc(100% / 7); 611 box-sizing: border-box; 612 } 613 } 614 615 /* 日历内容 */ 616 .Q-calendar-box { 617 display: flex; 618 justify-content: inherit; 619 flex-wrap: wrap; 620 width: 100%; 621 height: 80%; 622 623 div:hover { 624 color: yellowgreen; 625 font-weight: 700; 626 } 627 628 .Q-calendar-current-month { 629 box-sizing: border-box; 630 cursor: default; 631 // border: 1px solid pink; 632 633 } 634 635 .Q-calendar-current-month:hover { 636 // color: blueviolet; 637 color: #4152b3; 638 font-weight: 700; 639 font-size: 20px; 640 } 641 642 div { 643 display: flex; 644 justify-content: center; 645 width: calc(100% / 7); 646 height: calc(100% / 7); 647 648 span { 649 margin: auto; 650 } 651 } 652 653 p { 654 display: flex; 655 justify-content: center; 656 width: calc(100% / 7); 657 } 658 } 659 } 660 661 .Q-calendar-years { 662 height: calc(100% - 50px); 663 664 .Q-calendar-years-box { 665 // border: 1px solid pink; 666 display: flex; 667 justify-content: inherit; 668 flex-wrap: wrap; 669 height: 98%; 670 671 div { 672 display: flex; 673 box-sizing: border-box; 674 justify-content: center; 675 width: calc(100% / 4); 676 height: calc(100% / 4); 677 678 span { 679 margin: auto; 680 } 681 } 682 683 div:hover { 684 font-weight: 700; 685 color: yellowgreen; 686 } 687 } 688 } 689} 690 691/* //非本月时间内,或非本年内 */ 692.Q-calendar-surplus { 693 color: #898989; 694} 695 696.nowCss { 697 // border:1px solid pink; 698 background: #f1f3f4; 699 color: #40b8ff; 700} 701 702.checked { 703 span { 704 color: var(--color); 705 background-color: var(--background-color); 706 border-radius: 10px; 707 width: 50%; 708 height: 50%; 709 text-align: center; 710 line-height: 24px; 711 } 712} 713</style> 714
点赞
收藏

评论区

加载中...

相关推荐

vue3中基于script setup语法糖的$refs使用

在用vue3开发项目的时候,需要调用子组件的方法,于是想着用$refs来实现,但是我是使用scriptsetup语法糖,原先vue2的语法已经不适用了。于是一番折腾和查阅资料,终于搞定。vue2语法vue2语法在组件上设置ref属性后,在代码里可以通过this.$refs.ref值访问到对应的子组件。一个设置ref值的组件:html在js代码中可以通

Taro小程序自定义顶部导航栏

微信自带的顶部导航栏是无法支持自定义icon和增加元素的,在开发小程序的时候自带的根本满足不了需求,分享一个封装好的组件,支持自定义icon、扩展dom,适配安卓、ios、h5,全面屏。我用的是京东的Taro多端编译框架写的小程序,原生的也可以适用,用到的微信/taro的api做调整就行,实现效果如下。!在这里插入图片描述(https://i

FullCalendar使用踩坑之popover

FullCalendar是一个强大的jquery日历插件,可以实现很多的常用的日历功能。不过虽然英文文档很强大,也有很多热心的大神将英文文档翻译成了中文文档,但是文档中还是有一些遗漏的地方。这里就分享一下我使用FullCalendar日历popover所学到的东西。由于event事件标签太小,所以需要用到hover显示更多的信息,最开始使用了eventH

# pc端个性化日历实现

pc端个性化日历实现技术:vuevfor、slotscop插槽域需求:需要实现日历上每一天动态显示不同的信息思路:运用vue中slotscop插槽域的知识点,将个性化的代码样式放到slot中再通过slotscop获取这个插槽中的所需数据一、实现日历组件

JavaScript 搞出一个日历控件

日历控件基本上所有的前端都会用到,而且我相信8成的JSer都是直接把开源的组件拿来用,很多设计师似乎跟开发们也有默契,对日历控件只要能用就行,样式啥的不做太多要求,但是某些设计师就是有强迫症,一定要你按着TA的设计来,保不准产品也要舔一把火,往日历里塞些稀奇古怪的业务,咋办?初级开发可能就去网上找符合要求的控件,然后拼命说服他们;普通的开发就可能直接在已有的

简洁菜单栏日历软件分享

简洁菜单栏日历软件分享Dato激活版,一款功能强大、简单好用的菜单栏日历应用程序,它的自定义日历、多语言支持、快速操作、与日历应用程序同步、支持快捷键等特点可以帮助用户更加方便地管理日程安排。自定义日历:Dato可以显示日历、时间、天气等信息,用户可以自定