MCalendar.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. <template>
  2. <div class="calendar">
  3. <div class="c__hd">
  4. <div class="c__date" @click="onToggleType">{{ curDays.year }}年<span>{{ curDays.month.toString().length >= 2 ? curDays.month : '0' + curDays.month }}月</span></div>
  5. <div class="c__change" v-if="calendarType !== 'small'">
  6. <!-- <van-icon name="arrow-left" />
  7. <van-icon name="arrow" /> -->
  8. <img :src="imgs.arrow1" alt="" @touchstart.stop.prevent="slidePrev" @touchend.stop.prevent="endTouch" class="arrow arrow-left">
  9. <img :src="imgs.arrow2" alt="" @touchstart.stop.prevent="slideNext" @touchend.stop.prevent="endTouch" class="arrow">
  10. </div>
  11. </div>
  12. <div class="week-title">
  13. <span>一</span>
  14. <span>二</span>
  15. <span>三</span>
  16. <span>四</span>
  17. <span>五</span>
  18. <span>六</span>
  19. <span>日</span>
  20. </div>
  21. <swiper :options="swiperOption" ref="mySwiper" @slideChangeTransitionEnd="slideChangeTransitionEnd" style="height: auto">
  22. <swiper-slide v-for="(day, current) in daySet" :key="current">
  23. <div class="tr">
  24. <template v-for="(i, index) in day">
  25. <div :key="index" :class="[calendarType == 'small' ? 'small' : '', 'td']">
  26. <span @click.stop="onSelectDay(i)"
  27. :class="[i.disabled ? 'disabled' : '',
  28. i.noCheck? 'noCheck' : '',
  29. i.toDay ? 'today' : '',
  30. i.nowDate == selectElement ? 'active' : '']">{{ i.dayNum }}</span>
  31. </div>
  32. </template>
  33. </div>
  34. </swiper-slide>
  35. </swiper>
  36. </div>
  37. </template>
  38. <script>
  39. import 'swiper/dist/css/swiper.css'
  40. import { swiper, swiperSlide } from 'vue-awesome-swiper'
  41. import dayjs from 'dayjs'
  42. export default {
  43. name: 'calendar',
  44. components: { swiper, swiperSlide },
  45. props: {
  46. dataList: {
  47. type: Array,
  48. default() {
  49. return []
  50. }
  51. },
  52. dayList: { // 当月哪天有数据
  53. type: Array,
  54. default() {
  55. return []
  56. }
  57. },
  58. type: {
  59. type: String,
  60. default: 'default' // small 一排日历
  61. }
  62. },
  63. data() {
  64. return {
  65. calendarType: this.type, // 日历类型
  66. dayDateList: [],
  67. swiperOption: {
  68. autoHeight: true,
  69. initialSlide: 13,
  70. // loop:false,
  71. // mode: 'horizontal',
  72. // freeMode:false,
  73. // touchRatio:0.5,
  74. // longSwipesRatio:0.1,
  75. // threshold:50,
  76. // followFinger:false,
  77. centeredSlides: true,
  78. observer: true,//修改swiper自己或子元素时,自动初始化swiper
  79. observeParents: true,//修改swiper的父元素时,自动初始化swiper
  80. // effect : 'coverflow',
  81. },
  82. OS: Object.prototype.toString,
  83. // 一周的第一天
  84. // 0表示周日,依次类推
  85. startWeek: 1,
  86. daySet: [], // 月数据
  87. curDays: {
  88. year: new Date().getFullYear(),
  89. month: new Date().getMonth() + 1
  90. },
  91. points: { // 手势移动位置
  92. startX: 0,
  93. startY: 0,
  94. endX: 0,
  95. endY: 0
  96. },
  97. selectElement: null, // 选中的元素 根据日期来判断是否一致
  98. imgs:{
  99. arrow1:require('@/assets/images/arrow.png'),
  100. arrow2: require('@/assets/images/arrow.png')
  101. }
  102. }
  103. },
  104. methods: {
  105. isLeap(y) { // 是否是润年
  106. return (y % 100 !== 0 && y % 4 === 0) || (y % 400 === 0);
  107. },
  108. isDate(obj) { // 是否是日期格式
  109. return this.OS.call(obj === '[object Date]')
  110. },
  111. dateToString(date) {
  112. if(!this.isDate(date)) {
  113. return false
  114. }
  115. return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate()
  116. },
  117. getSiblingsMonth(y, m, n) { // 获取弟兄年月
  118. var d = new Date(y, m - 1)
  119. d.setMonth(m - 1 + n)
  120. return {
  121. y: d.getFullYear(),
  122. m: d.getMonth() + 1
  123. }
  124. },
  125. getDaysNum(y, m) { // 获取m月有几天
  126. let num = 31;
  127. switch (m) {
  128. case 2:
  129. num = this.isLeap(y) ? 29 : 28;
  130. break;
  131. case 4:
  132. case 6:
  133. case 9:
  134. case 11:
  135. num = 30;
  136. break;
  137. }
  138. return num;
  139. },
  140. getPrevMonth(y, m, n) {
  141. return this.getSiblingsMonth(y, m, 0 - (n || 1))
  142. },
  143. getNextMonth(y, m, n) {
  144. return this.getSiblingsMonth(y, m, n || 1)
  145. },
  146. getListMonth() { // 获取14个月的数据
  147. this.daySet = [] //
  148. // 生成前12个月的数据
  149. for(let i = 12; i >= -2; i--) {
  150. this.daySet.push(this.getDaysSet(new Date(this.curDays.year, this.curDays.month - (i + 2))))
  151. }
  152. },
  153. getDayItem(y, m, d, f) { // 填充数据
  154. let toDay = false // 是否是当天
  155. let cD = new Date()
  156. if(cD.getFullYear() === y && cD.getMonth() + 1 === m && cD.getDate() === d && f === 2) {
  157. toDay = true
  158. this.selectElement = new Date(y, m - 1, d)
  159. }
  160. let tDisabled
  161. if(this.calendarType == 'small') {
  162. tDisabled = 0
  163. } else {
  164. if(f === 2) {
  165. tDisabled = 0
  166. } else {
  167. tDisabled = 1
  168. }
  169. }
  170. let noCheck = true // 是否可以选中
  171. // if(this.dataList.length > 0) {
  172. // this.dataList.forEach(item => {
  173. // if(item == d) {
  174. // noCheck = false
  175. // }
  176. // })
  177. // }
  178. return {
  179. dayNum: d,
  180. monthType: f,
  181. disabled: tDisabled, // 是否是当月
  182. noCheck: noCheck, // 是否可选
  183. toDay: toDay,
  184. nowDate: new Date(y, m - 1, d) // 当前日期
  185. }
  186. },
  187. getDaysSet(y, m) { // 获取某月显示的日期
  188. let year, month, firstWeek, daysNum, prevM, prevDiff, prevDaysNum, nextM,
  189. days = [] // 日期数据
  190. if(this.isDate(y)) {
  191. year = y.getFullYear()
  192. month = y.getMonth() + 1
  193. } else {
  194. year = Number(y)
  195. month = Number(m)
  196. }
  197. // 星期天则为7
  198. firstWeek = new Date(year, month - 1, 1).getDay() || 7
  199. prevDiff = firstWeek - this.startWeek
  200. daysNum = this.getDaysNum(year, month)
  201. prevM = this.getPrevMonth(year, month)
  202. prevDaysNum = this.getDaysNum(year, prevM.m)
  203. nextM = this.getNextMonth(year, month)
  204. // month flag
  205. let PREV_FLAG = 1,
  206. CURR_FLAG = 2,
  207. NEXT_FLAG = 3,
  208. count = 0
  209. // 上月最后几天
  210. for(let p = prevDaysNum - prevDiff + 1; p <= prevDaysNum; p++, count++) {
  211. days.push(this.getDayItem(prevM.y, prevM.m, p, PREV_FLAG))
  212. }
  213. // 本月天数
  214. for(let c = 1; c <= daysNum; c++, count++) {
  215. days.push(this.getDayItem(year, month, c, CURR_FLAG))
  216. }
  217. // 下月天数
  218. let c = (count % 7) === 0 ? count : (7 - (count % 7) + count)
  219. for (let n = 1, nl = c - count; n <= nl; n++) {
  220. days.push(this.getDayItem(nextM.y, nextM.m, n, NEXT_FLAG))
  221. }
  222. return days
  223. },
  224. getSmallCalendar(y, m, d) {
  225. let year, month, day, firstWeek,
  226. days = [] // 日期数据
  227. if(this.isDate(y)) {
  228. year = y.getFullYear()
  229. month = y.getMonth() + 1
  230. day = y.getDate()
  231. } else {
  232. year = Number(y)
  233. month = Number(m)
  234. day = Number(d)
  235. }
  236. let CURR_FLAG = 2,
  237. // NEXT_FLAG = 3,
  238. // PREV_FLAG = 1,
  239. count = 0
  240. // 星期天则为7
  241. firstWeek = new Date(year, month - 1, day).getDay() || 7
  242. // 在本周当天之前的日期
  243. for (let p = (firstWeek - 1); p > 0; p--, count++) {
  244. let tempD = new Date(year, month - 1, day - p)
  245. let pYear = tempD.getFullYear()
  246. let pMonth = tempD.getMonth() + 1
  247. let pDay = tempD.getDate()
  248. days.push(this.getDayItem(pYear, pMonth, pDay, CURR_FLAG))
  249. }
  250. // 当天日期
  251. days.push(this.getDayItem(year, month, day, CURR_FLAG))
  252. count++
  253. // 在本周当天之后的日期
  254. for (let n = 1; n <= (7 - count); n++) {
  255. let tempD = new Date(year, month - 1, day + n)
  256. let nYear = tempD.getFullYear()
  257. let nMonth = tempD.getMonth() + 1
  258. let nDay = tempD.getDate()
  259. days.push(this.getDayItem(nYear, nMonth, nDay, CURR_FLAG))
  260. }
  261. this.daySet.push(days)
  262. },
  263. slideChangeTransitionEnd() { // 滑动完成
  264. let swiper = this.swiper
  265. // 当前滑动到的日期
  266. let checkDate = this.daySet[swiper.activeIndex][6].nowDate
  267. this.curDays = {
  268. year: checkDate.getFullYear(),
  269. month: checkDate.getMonth() + 1
  270. }
  271. // 是否是第一页
  272. // if(swiper.isBeginning) { // 由于体验问题不使用
  273. // let tempDays = this.getDaysSet(new Date(this.curDays.year, this.curDays.month - 1))
  274. // this.daySet.unshift(tempDays)
  275. // }
  276. // 是否是最后一页
  277. if(swiper.isEnd) {
  278. this.daySet.push(this.getDaysSet(new Date(this.curDays.year, this.curDays.month)))
  279. }
  280. this.$emit('onChangeMonth', checkDate)
  281. },
  282. slidePrev() { // 上一页
  283. this.imgs.arrow1 = require('@/assets/images/arrow-active.png')
  284. this.swiper.slidePrev()
  285. },
  286. slideNext() { // 下一页
  287. this.imgs.arrow2 =require('@/assets/images/arrow-active.png')
  288. this.swiper.slideNext()
  289. },
  290. endTouch(){
  291. this.imgs.arrow1 = require('@/assets/images/arrow.png')
  292. this.imgs.arrow2 = require('@/assets/images/arrow.png')
  293. },
  294. touchStart() {
  295. let touch = event.touches[0]
  296. this.points.startY = touch.pageY
  297. this.points.startX = touch.pageX
  298. },
  299. touchMove() {
  300. // let touch = event.touches[0]
  301. // this.points.endX = touch.pageX
  302. // this.points.endY = touch.pageY
  303. // let pt = this.points,
  304. // X = pt.endX - pt.startX,
  305. // Y = pt.endY - pt.startY
  306. // if ( Math.abs(X) > Math.abs(Y) && X > 0 ) {
  307. // console.log("left 2 right");
  308. // }
  309. // else if ( Math.abs(X) > Math.abs(Y) && X < 0 ) {
  310. // console.log("right 2 left");
  311. // }
  312. // else
  313. // if ( Math.abs(Y) > Math.abs(X) && Y > 0) {
  314. // console.log("top 2 bottom")
  315. // this.onTouchDown()
  316. // }
  317. // else if ( Math.abs(Y) > Math.abs(X) && Y < 0 ) {
  318. // console.log("bottom 2 top")
  319. // this.onTouchUp()
  320. // }
  321. // else{
  322. // console.log("just touch");
  323. // }
  324. },
  325. onTouchUp() { // 向上滑
  326. // let activeIndex = this.swiper.activeIndex
  327. // let acitveDayIndex // 选中日期索引
  328. // let curDays = this.daySet[activeIndex]
  329. // curDays.forEach((day, index) => {
  330. // if(this.dateToString(day.nowDate) == this.dateToString(this.selectElement)) {
  331. // // console.log(index)
  332. // acitveDayIndex = index
  333. // }
  334. // })
  335. // let arr = []
  336. // for(let i = 0; i < 7; i++) {
  337. // arr.push({
  338. // dayNum: i + 1,
  339. // monthType: 2,
  340. // disabled: false,
  341. // toDay: i == 3 ? true : false,
  342. // nowDate: new Date()
  343. // })
  344. // }
  345. // console.log(arr)
  346. // this.daySet[activeIndex] = arr
  347. // this.$forceUpdate()
  348. // this.swiper.detachEvents() //移除所有slide监听事件
  349. // this.swiper.attachEvents() //重新绑定所有监听事件。
  350. // return {
  351. // dayNum: d,
  352. // monthType: f,
  353. // disabled: f === 2 ? 0 : 1, // 是否是当月
  354. // toDay: toDay,
  355. // nowDate: new Date(y, m - 1, d) // 当前日期
  356. // }
  357. },
  358. onTouchDown() { // 向下滑
  359. },
  360. touchEnd() {
  361. // let pt = this.points,
  362. // X = pt.endX - pt.startX,
  363. // Y = pt.endY - pt.startY
  364. // if ( Math.abs(X) > Math.abs(Y) && X > 0 ) {
  365. // console.log("left 2 right");
  366. // }
  367. // else if ( Math.abs(X) > Math.abs(Y) && X < 0 ) {
  368. // console.log("right 2 left");
  369. // }
  370. // else
  371. // if ( Math.abs(Y) > Math.abs(X) && Y > 0) {
  372. // console.log("top 2 bottom");
  373. // }
  374. // else if ( Math.abs(Y) > Math.abs(X) && Y < 0 ) {
  375. // console.log("bottom 2 top");
  376. // }
  377. // else{
  378. // console.log("just touch");
  379. // }
  380. },
  381. onSelectDay(item) {
  382. // 除了当月其它月份不能点击, 可选日期
  383. if(item.monthType == 2 && !item.noCheck) {
  384. this.selectElement = item.nowDate
  385. // 暴露出方法
  386. this.$emit('onSelectDay', item.nowDate)
  387. }
  388. },
  389. onToggleType() { //切换日历类型
  390. // this.calendarType = this.calendarType === 'default' ? 'small' : 'default'
  391. if(this.calendarType === 'default') {
  392. this.calendarType = 'small'
  393. this.getSmallCalendar(new Date())
  394. } else {
  395. this.calendarType = 'default'
  396. this.getListMonth()
  397. }
  398. this.$forceUpdate()
  399. }
  400. },
  401. computed: {
  402. swiper() {
  403. return this.$refs.mySwiper.swiper
  404. }
  405. },
  406. watch: {
  407. dayList(value) {
  408. this.dayDateList = value
  409. const selectDate = this.daySet[this.swiper.activeIndex]
  410. selectDate.forEach(item => {
  411. if(item.disabled == 0 && item.monthType == 2) {
  412. if(value.includes(dayjs(item.nowDate).format('DD'))) {
  413. item.noCheck = false
  414. } else {
  415. item.noCheck = true
  416. }
  417. }
  418. })
  419. }
  420. },
  421. mounted() {
  422. // 选择不同的日历类型
  423. if(this.calendarType == 'default') {
  424. this.getListMonth()
  425. } else {
  426. this.getSmallCalendar(new Date())
  427. }
  428. // current swiper instance
  429. // 然后你就可以使用当前上下文内的swiper对象去做你想做的事了
  430. // console.log('this is current swiper instance object', this.swiper)
  431. // this.swiper.slideTo(0, 1000, false)
  432. }
  433. }
  434. </script>
  435. <style lang='less' scoped>
  436. @import url("../assets/commonLess/variable.less");
  437. .calendar {
  438. background: @whiteColor;
  439. padding: .15rem .2rem;
  440. .c__hd {
  441. display: flex;
  442. align-items: center;
  443. justify-content: space-between;
  444. }
  445. .c__date {
  446. color: #333;
  447. font-size: .24rem;
  448. // font-weight: bold;
  449. // span {
  450. // padding-right: .08rem;
  451. // font-size: .26rem;
  452. // }
  453. }
  454. .van-icon {
  455. margin-left: .1rem;
  456. width: .24rem;
  457. height: .24rem;
  458. font-size: .14rem;
  459. color: #c4c4c4;
  460. background: #eee;
  461. text-align: center;
  462. line-height: .24rem;
  463. border-radius: 50%;
  464. transition: all .2s ease;
  465. &:active {
  466. background: @mColor;
  467. color: #4eada7;
  468. color: #fff;
  469. }
  470. }
  471. .week-title {
  472. display: flex;
  473. align-items: center;
  474. justify-content: space-between;
  475. text-align: center;
  476. padding: .2rem 0 .1rem;
  477. color: #777;
  478. span {
  479. flex: 1;
  480. font-size: .16rem;
  481. color: @tFontColor;
  482. font-weight: 600;
  483. }
  484. }
  485. .tr {
  486. display: flex;
  487. align-items: center;
  488. text-align: center;
  489. font-size: .15rem;
  490. color: @mFontColor;
  491. // justify-content: space-around;
  492. flex-wrap: wrap;
  493. }
  494. .td {
  495. width: 14.2857%;
  496. margin-top: .03rem;
  497. &.small {
  498. span {
  499. background: #F6F8F9;
  500. }
  501. }
  502. span {
  503. position: relative;
  504. display: block;
  505. margin: 0 auto;
  506. width: .36rem;
  507. height: .36rem;
  508. background: transparent;
  509. border-radius: 50%;
  510. line-height: .36rem;
  511. font-weight: 500;
  512. &.noCheck {
  513. color: #ccc;
  514. }
  515. &.today::before {
  516. content: '今';
  517. display: block;
  518. width: calc(100% - 2px);
  519. height: calc(100% - 2px);
  520. background: #fff;
  521. color: @orangeColor;
  522. border: 1px solid @orangeColor;
  523. border-radius: 50%;
  524. position: absolute;
  525. left: 0;
  526. top: 0;
  527. }
  528. &.active {
  529. background: @mColor;
  530. color: @whiteColor;
  531. transition: all .2s ease;
  532. }
  533. &.disabled { // 如果不是当月则不显示
  534. color: transparent !important;
  535. background: #fff !important;
  536. &::before {
  537. content: '';
  538. color: #fff;
  539. border: 1px solid #fff;
  540. }
  541. }
  542. }
  543. }
  544. .swiper-slide {
  545. transition: all .5s ease;
  546. }
  547. .arrow {
  548. width: .22rem;
  549. height: .22rem;
  550. }
  551. .arrow-left {
  552. transform: rotate(180deg);
  553. margin-right: .12rem;
  554. }
  555. }
  556. </style>