当前位置:   article > 正文

前端开发攻略---封装calendar日历组件,实现日期多选。可根据您的需求任意调整,可玩性强。

前端开发攻略---封装calendar日历组件,实现日期多选。可根据您的需求任意调整,可玩性强。

1、演示

2、简介

1、该日历组件是纯手搓出来的,没依赖任何组件库,因此您可以随意又轻松的改变代码,以实现您的需求。

2、代码清爽干净,逻辑精妙,您可以好好品尝。

3、好戏开场。

3、代码(Vue3写法)

1、子组件

 您可以在components文件夹下创建一个干净的组件,直接将代码复制粘贴即可。

src/components/py-calendar/index.vue

  1. <template>
  2. <div class="box">
  3. <div class="left">
  4. <div class="top">
  5. <div>
  6. <span class="iconfont" @click="changeMonth(-1)"></span>
  7. </div>
  8. <span>{{ startMonth.year }}年{{ startMonth.month }}月</span>
  9. <span></span>
  10. </div>
  11. <div class="calendarMain">
  12. <div class="weekDays">
  13. <span></span>
  14. <span></span>
  15. <span></span>
  16. <span></span>
  17. <span></span>
  18. <span></span>
  19. <span></span>
  20. </div>
  21. <div class="days">
  22. <div
  23. class="day"
  24. v-for="item in startMonth.dates"
  25. :class="dayClass(item)"
  26. :style="dayStyle(item)"
  27. @mouseenter="dayMouseMove(item)"
  28. @click="dayMouseClick(item)"
  29. >
  30. {{ item.day }}
  31. <span v-if="item.yymmdd == selectDate[0]?.yymmdd">开始</span>
  32. <span v-if="item.yymmdd == selectDate[1]?.yymmdd">结束</span>
  33. </div>
  34. </div>
  35. </div>
  36. </div>
  37. <div class="right">
  38. <div class="top">
  39. <span></span>
  40. <span>{{ endMonth.year }}年{{ endMonth.month }}月</span>
  41. <div>
  42. <span class="iconfont" @click="changeMonth(1)"></span>
  43. </div>
  44. </div>
  45. <div class="calendarMain">
  46. <div class="weekDays">
  47. <span></span>
  48. <span></span>
  49. <span></span>
  50. <span></span>
  51. <span></span>
  52. <span></span>
  53. <span></span>
  54. </div>
  55. <div class="days">
  56. <div
  57. class="day"
  58. v-for="item in endMonth.dates"
  59. :class="dayClass(item)"
  60. :style="dayStyle(item)"
  61. @mouseenter="dayMouseMove(item)"
  62. @click="dayMouseClick(item)"
  63. >
  64. {{ item.day }}
  65. <span v-if="item.yymmdd == selectDate[0]?.yymmdd">开始</span>
  66. <span v-if="item.yymmdd == selectDate[1]?.yymmdd">结束</span>
  67. </div>
  68. </div>
  69. </div>
  70. </div>
  71. </div>
  72. </template>
  73. <script setup>
  74. import { ref, reactive, onMounted, computed } from 'vue'
  75. import { getMonthDates } from './index.js'
  76. const currentMonthIndex = ref(0)
  77. const startMonth = ref({})
  78. const endMonth = ref({})
  79. const selectDate = ref([])
  80. const isMove = ref(false)
  81. const emits = defineEmits(['change'])
  82. onMounted(() => {
  83. initCalendar()
  84. })
  85. const initCalendar = () => {
  86. getCalendarData()
  87. const startIndex = startMonth.value.dates.findIndex(item => !item.isTodayBefore)
  88. if (startIndex == startMonth.value.dates.length - 1) {
  89. selectDate.value[0] = startMonth.value.dates[startIndex]
  90. selectDate.value[1] = endMonth.value.dates[0]
  91. } else {
  92. selectDate.value[0] = startMonth.value.dates[startIndex]
  93. selectDate.value[1] = startMonth.value.dates[startIndex + 1]
  94. }
  95. }
  96. const getCalendarData = () => {
  97. startMonth.value = getMonthDates(currentMonthIndex.value)
  98. endMonth.value = getMonthDates(currentMonthIndex.value + 1)
  99. }
  100. const changeMonth = num => {
  101. currentMonthIndex.value += num
  102. getCalendarData()
  103. }
  104. const dayClass = item => {
  105. if (item.isTodayBefore) return 'disabled'
  106. if (item.yymmdd == selectDate.value[0]?.yymmdd) return 'active'
  107. if (item.yymmdd == selectDate.value[1]?.yymmdd) return 'active'
  108. if (getDatesBetween(selectDate.value[0]?.yymmdd, selectDate.value[1]?.yymmdd).includes(item.yymmdd)) return 'middle'
  109. }
  110. const dayStyle = item => {
  111. if (item.day == 1) return { marginLeft: item.week * 50 + 'px' }
  112. if (!item.isTodayBefore && (item.week == 0 || item.week == 6)) return { color: '#266fff' }
  113. }
  114. const dayMouseClick = item => {
  115. if (item.isTodayBefore) return
  116. let arr = [...selectDate.value]
  117. if (arr[0] && arr[1] && !isMove.value) {
  118. arr = []
  119. arr[0] = item
  120. isMove.value = true
  121. } else if (arr[0] && arr[1] && isMove.value) {
  122. isMove.value = false
  123. arr[1] = item
  124. } else if (arr[0] && !arr[1] && !isMove.value) {
  125. arr[0] = item
  126. isMove.value = false
  127. } else if (arr[0] && !arr[1] && isMove.value) {
  128. arr[0] = item
  129. isMove.value = true
  130. }
  131. selectDate.value = arr
  132. if (arr[0] && arr[1]) {
  133. emits('change', getDatesBetween(arr[0].yymmdd, arr[1].yymmdd))
  134. }
  135. }
  136. const dayMouseMove = item => {
  137. if (item.isTodayBefore) return
  138. if (!isMove.value) return
  139. if (item.yymmdd <= selectDate.value[0]?.yymmdd) {
  140. selectDate.value[1] = ''
  141. } else {
  142. selectDate.value[1] = item
  143. }
  144. }
  145. const getDatesBetween = (date1, date2) => {
  146. let dates = []
  147. let currentDate = new Date(date1)
  148. let endDate = new Date(date2)
  149. while (currentDate <= endDate) {
  150. let dateString = currentDate.toISOString().substr(0, 10)
  151. dates.push(dateString)
  152. currentDate.setDate(currentDate.getDate() + 1)
  153. }
  154. return dates
  155. }
  156. </script>
  157. <style scoped lang="scss">
  158. .box {
  159. width: 793px;
  160. height: 436px;
  161. box-shadow: 2px 2px 6px #0003;
  162. display: flex;
  163. justify-content: space-between;
  164. flex-wrap: wrap;
  165. padding: 30px 15px;
  166. .left,
  167. .right {
  168. width: 46%;
  169. height: 95%;
  170. .top {
  171. display: flex;
  172. justify-content: space-between;
  173. font-weight: bold;
  174. .iconfont {
  175. cursor: pointer;
  176. user-select: none;
  177. }
  178. }
  179. .calendarMain {
  180. .weekDays {
  181. font-weight: bold;
  182. margin-top: 20px;
  183. display: flex;
  184. justify-content: space-between;
  185. & > span {
  186. display: inline-block;
  187. width: 50px;
  188. height: 50px;
  189. line-height: 50px;
  190. text-align: center;
  191. }
  192. & > span:first-child,
  193. & > span:last-child {
  194. color: #266fff;
  195. }
  196. }
  197. .days {
  198. display: flex;
  199. flex-wrap: wrap;
  200. cursor: pointer;
  201. .day {
  202. width: 50px;
  203. height: 50px;
  204. height: 50px;
  205. text-align: center;
  206. line-height: 60px;
  207. color: #111;
  208. font-size: 14px;
  209. position: relative;
  210. & > span {
  211. position: absolute;
  212. left: 11px;
  213. top: -18px;
  214. }
  215. }
  216. .disabled {
  217. color: #ccc;
  218. cursor: not-allowed;
  219. }
  220. .active {
  221. background-color: #266fff;
  222. color: #fff !important;
  223. }
  224. .middle {
  225. background-color: rgba(38, 111, 255, 0.3);
  226. color: #fff !important;
  227. }
  228. }
  229. }
  230. }
  231. .bottom {
  232. width: 100%;
  233. text-align: center;
  234. color: #111;
  235. }
  236. }
  237. </style>

src/components/py-calendar/index.js

  1. export function getMonthDates(monthOffset) {
  2. const today = new Date()
  3. const targetDate = new Date(today.getFullYear(), today.getMonth() + monthOffset, 1)
  4. const year = targetDate.getFullYear()
  5. let month = targetDate.getMonth() + 1 // 月份是从0开始的,所以要加1
  6. month = month >= 10 ? month : '0' + month
  7. const firstDay = new Date(year, targetDate.getMonth(), 1)
  8. const lastDay = new Date(year, targetDate.getMonth() + 1, 0)
  9. const monthDates = []
  10. for (let d = firstDay; d <= lastDay; d.setDate(d.getDate() + 1)) {
  11. const day = d.getDate()
  12. const dayOfWeek = d.getDay() // 返回0到6,0代表星期日
  13. const isTodayBefore = d.getTime() < today.setHours(0, 0, 0, 0) // 判断是否是今天之前的日期
  14. monthDates.push({
  15. day,
  16. week: dayOfWeek,
  17. isTodayBefore,
  18. yymmdd: `${year}-${month}-${day >= 10 ? day : '0' + day}`,
  19. })
  20. }
  21. return { year, month, dates: monthDates }
  22. }

2、父组件

随便在一个文件夹下使用日历组件

  1. <template>
  2. <div>
  3. <PYCalendar @change="change"></PYCalendar>
  4. </div>
  5. </template>
  6. <script setup>
  7. import { ref, reactive, computed } from 'vue'
  8. import PYCalendar from '@/components/py-calendar/index.vue'
  9. const change = dateArr => {
  10. console.log(dateArr, '父组件获取到的数据')
  11. }
  12. </script>
  13. <style scoped lang="scss"></style>

4、代码(Vue2写法 )

1、子组件

 您可以在components文件夹下创建一个干净的组件,直接将代码复制粘贴即可。

src/components/py-calendar/index.vue

  1. <template>
  2. <div class="box">
  3. <div class="left">
  4. <div class="top">
  5. <div>
  6. <span class="iconfont" @click="changeMonth(-1)"></span>
  7. </div>
  8. <span>{{ startMonth.year }}年{{ startMonth.month }}月</span>
  9. <span></span>
  10. </div>
  11. <div class="calendarMain">
  12. <div class="weekDays">
  13. <span></span>
  14. <span></span>
  15. <span></span>
  16. <span></span>
  17. <span></span>
  18. <span></span>
  19. <span></span>
  20. </div>
  21. <div class="days">
  22. <div
  23. class="day"
  24. v-for="item in startMonth.dates"
  25. :class="dayClass(item)"
  26. :style="dayStyle(item)"
  27. @mouseenter="dayMouseMove(item)"
  28. @click="dayMouseClick(item)"
  29. >
  30. {{ item.day }}
  31. <span v-if="item.yymmdd == selectDate[0]?.yymmdd">开始</span>
  32. <span v-if="item.yymmdd == selectDate[1]?.yymmdd">结束</span>
  33. </div>
  34. </div>
  35. </div>
  36. </div>
  37. <div class="right">
  38. <div class="top">
  39. <span></span>
  40. <span>{{ endMonth.year }}年{{ endMonth.month }}月</span>
  41. <div>
  42. <span class="iconfont" @click="changeMonth(1)"></span>
  43. </div>
  44. </div>
  45. <div class="calendarMain">
  46. <div class="weekDays">
  47. <span></span>
  48. <span></span>
  49. <span></span>
  50. <span></span>
  51. <span></span>
  52. <span></span>
  53. <span></span>
  54. </div>
  55. <div class="days">
  56. <div
  57. class="day"
  58. v-for="item in endMonth.dates"
  59. :class="dayClass(item)"
  60. :style="dayStyle(item)"
  61. @mouseenter="dayMouseMove(item)"
  62. @click="dayMouseClick(item)"
  63. >
  64. {{ item.day }}
  65. <span v-if="item.yymmdd == selectDate[0]?.yymmdd">开始</span>
  66. <span v-if="item.yymmdd == selectDate[1]?.yymmdd">结束</span>
  67. </div>
  68. </div>
  69. </div>
  70. </div>
  71. </div>
  72. </template>
  73. <script>
  74. import { getMonthDates } from './index.js'
  75. export default {
  76. data() {
  77. return {
  78. currentMonthIndex: 0,
  79. startMonth: {},
  80. endMonth: {},
  81. selectDate: [],
  82. isMove: false,
  83. }
  84. },
  85. mounted() {
  86. this.initCalendar()
  87. },
  88. methods: {
  89. initCalendar() {
  90. this.getCalendarData()
  91. const startIndex = this.startMonth.dates.findIndex(item => !item.isTodayBefore)
  92. if (startIndex == this.startMonth.dates.length - 1) {
  93. this.selectDate[0] = this.startMonth.dates[startIndex]
  94. this.selectDate[1] = this.endMonth.dates[0]
  95. } else {
  96. this.selectDate[0] = this.startMonth.dates[startIndex]
  97. this.selectDate[1] = this.startMonth.dates[startIndex + 1]
  98. }
  99. },
  100. getCalendarData() {
  101. this.startMonth = getMonthDates(this.currentMonthIndex)
  102. this.endMonth = getMonthDates(this.currentMonthIndex + 1)
  103. },
  104. changeMonth(num) {
  105. this.currentMonthIndex += num
  106. this.getCalendarData()
  107. },
  108. dayClass(item) {
  109. if (item.isTodayBefore) return 'disabled'
  110. if (item.yymmdd == this.selectDate[0]?.yymmdd) return 'active'
  111. if (item.yymmdd == this.selectDate[1]?.yymmdd) return 'active'
  112. if (this.getDatesBetween(this.selectDate[0]?.yymmdd, this.selectDate[1]?.yymmdd).includes(item.yymmdd)) return 'middle'
  113. },
  114. dayStyle(item) {
  115. if (item.day == 1) return { marginLeft: item.week * 50 + 'px' }
  116. if (!item.isTodayBefore && (item.week == 0 || item.week == 6)) return { color: '#266fff' }
  117. },
  118. dayMouseClick(item) {
  119. if (item.isTodayBefore) return
  120. let arr = [...this.selectDate]
  121. if (arr[0] && arr[1] && !this.isMove) {
  122. arr = []
  123. arr[0] = item
  124. this.isMove = true
  125. } else if (arr[0] && arr[1] && this.isMove) {
  126. this.isMove = false
  127. arr[1] = item
  128. } else if (arr[0] && !arr[1] && !this.isMove) {
  129. arr[0] = item
  130. this.isMove = false
  131. } else if (arr[0] && !arr[1] && this.isMove) {
  132. arr[0] = item
  133. this.isMove = true
  134. }
  135. this.selectDate = arr
  136. if (arr[0] && arr[1]) {
  137. this.$emit('change', this.getDatesBetween(arr[0].yymmdd, arr[1].yymmdd))
  138. }
  139. },
  140. dayMouseMove(item) {
  141. if (item.isTodayBefore) return
  142. if (!this.isMove) return
  143. if (item.yymmdd <= this.selectDate[0]?.yymmdd) {
  144. this.selectDate[1] = ''
  145. } else {
  146. this.selectDate[1] = item
  147. }
  148. },
  149. getDatesBetween(date1, date2) {
  150. let dates = []
  151. let currentDate = new Date(date1)
  152. let endDate = new Date(date2)
  153. while (currentDate <= endDate) {
  154. let dateString = currentDate.toISOString().substr(0, 10)
  155. dates.push(dateString)
  156. currentDate.setDate(currentDate.getDate() + 1)
  157. }
  158. return dates
  159. },
  160. },
  161. }
  162. </script>
  163. <style scoped lang="scss">
  164. .box {
  165. width: 793px;
  166. height: 436px;
  167. box-shadow: 2px 2px 6px #0003;
  168. display: flex;
  169. justify-content: space-between;
  170. flex-wrap: wrap;
  171. padding: 30px 15px;
  172. .left,
  173. .right {
  174. width: 46%;
  175. height: 95%;
  176. .top {
  177. display: flex;
  178. justify-content: space-between;
  179. font-weight: bold;
  180. .iconfont {
  181. cursor: pointer;
  182. user-select: none;
  183. }
  184. }
  185. .calendarMain {
  186. .weekDays {
  187. font-weight: bold;
  188. margin-top: 20px;
  189. display: flex;
  190. justify-content: space-between;
  191. & > span {
  192. display: inline-block;
  193. width: 50px;
  194. height: 50px;
  195. line-height: 50px;
  196. text-align: center;
  197. }
  198. & > span:first-child,
  199. & > span:last-child {
  200. color: #266fff;
  201. }
  202. }
  203. .days {
  204. display: flex;
  205. flex-wrap: wrap;
  206. cursor: pointer;
  207. .day {
  208. width: 50px;
  209. height: 50px;
  210. height: 50px;
  211. text-align: center;
  212. line-height: 60px;
  213. color: #111;
  214. font-size: 14px;
  215. position: relative;
  216. & > span {
  217. position: absolute;
  218. left: 11px;
  219. top: -18px;
  220. }
  221. }
  222. .disabled {
  223. color: #ccc;
  224. cursor: not-allowed;
  225. }
  226. .active {
  227. background-color: #266fff;
  228. color: #fff !important;
  229. }
  230. .middle {
  231. background-color: rgba(38, 111, 255, 0.3);
  232. color: #fff !important;
  233. }
  234. }
  235. }
  236. }
  237. .bottom {
  238. width: 100%;
  239. text-align: center;
  240. color: #111;
  241. }
  242. }
  243. </style>

src/components/py-calendar/index.js

  1. export function getMonthDates(monthOffset) {
  2. const today = new Date()
  3. const targetDate = new Date(today.getFullYear(), today.getMonth() + monthOffset, 1)
  4. const year = targetDate.getFullYear()
  5. let month = targetDate.getMonth() + 1 // 月份是从0开始的,所以要加1
  6. month = month >= 10 ? month : '0' + month
  7. const firstDay = new Date(year, targetDate.getMonth(), 1)
  8. const lastDay = new Date(year, targetDate.getMonth() + 1, 0)
  9. const monthDates = []
  10. for (let d = firstDay; d <= lastDay; d.setDate(d.getDate() + 1)) {
  11. const day = d.getDate()
  12. const dayOfWeek = d.getDay() // 返回0到6,0代表星期日
  13. const isTodayBefore = d.getTime() < today.setHours(0, 0, 0, 0) // 判断是否是今天之前的日期
  14. monthDates.push({
  15. day,
  16. week: dayOfWeek,
  17. isTodayBefore,
  18. yymmdd: `${year}-${month}-${day >= 10 ? day : '0' + day}`,
  19. })
  20. }
  21. return { year, month, dates: monthDates }
  22. }

2、父组件

随便在一个文件夹下使用日历组件

  1. <template>
  2. <div>
  3. <PYCalendar @change="change"></PYCalendar>
  4. </div>
  5. </template>
  6. <script>
  7. import PYCalendar from '@/components/py-calendar/index.vue'
  8. export default {
  9. data() {
  10. return {}
  11. },
  12. methods: {
  13. change(e) {
  14. console.log(e, '父组件')
  15. },
  16. },
  17. }
  18. </script>
  19. <style scoped lang="scss"></style>

5、代码解析

代码解析(子组件中的函数):

  1. initCalendar: 初始化日历,通过调用 getCalendarData() 获取日历的初始数据。然后找到第一个不在过去的日期的索引(isTodayBefore),并根据该索引设置初始选定的日期范围(selectDate)。如果起始索引是起始月份中的最后一个日期,则将结束日期设置为下个月的第一个日期。

  2. getCalendarData: 通过调用 getMonthDates 获取当前月份和下个月份的日历数据,分别为 currentMonthIndex.value 和 currentMonthIndex.value + 1

  3. changeMonth: 通过给定的偏移量(num)改变当前月份,更新 currentMonthIndex,然后调用 getCalendarData() 刷新日历数据。

  4. dayClass: 确定给定日期(item)的 CSS 类。如果日期在过去,则返回 ‘disabled’;如果日期与选定日期之一匹配,则返回 ‘active’;如果日期在两个选定日期之间,则返回 ‘middle’。

  5. dayStyle: 确定给定日期(item)的内联样式。为每个月的第一天设置左边距,并将周末的文字颜色设置为蓝色。

  6. dayMouseClick: 处理日期(item)的鼠标单击事件。根据单击的日期和选择的位置更新选定的日期范围(selectDate),并根据是否同时选择了两个日期来触发 ‘change’ 事件。

  7. dayMouseMove: 处理日期(item)的鼠标移动事件。如果日期选择正在进行中(isMove.value 为 true),则更新选定范围的结束日期。

代码解析(外部导入的函数):

  1. export function getMonthDates(monthOffset) {: 这是一个导出函数的声明,函数名为 getMonthDates,它接受一个参数 monthOffset,表示要获取的月份相对于当前月份的偏移量。

  2. const today = new Date(): 创建了一个当前日期的 Date 对象。

  3. const targetDate = new Date(today.getFullYear(), today.getMonth() + monthOffset, 1): 创建了一个目标日期的 Date 对象,该日期是当前日期加上指定的月份偏移量,月份从0开始计数,因此 today.getMonth() 返回的是当前月份的索引,例如1月是0,2月是1,以此类推。

  4. const year = targetDate.getFullYear(): 获取目标日期的年份。

  5. let month = targetDate.getMonth() + 1: 获取目标日期的月份,并加1,因为月份是从0开始计数的,需要进行修正。

  6. month = month >= 10 ? month : '0' + month: 如果月份小于10,就在前面补0,确保月份是两位数的字符串格式。

  7. const firstDay = new Date(year, targetDate.getMonth(), 1): 获取目标月份的第一天的日期对象。

  8. const lastDay = new Date(year, targetDate.getMonth() + 1, 0): 获取目标月份的最后一天的日期对象。这里将月份加1,然后日期设为0,相当于得到了目标月份的最后一天。

  9. const monthDates = []: 创建一个空数组,用于存储该月份的日期信息。

  10. for (let d = firstDay; d <= lastDay; d.setDate(d.getDate() + 1)) {: 使用 for 循环遍历从第一天到最后一天的日期。

  11. const day = d.getDate(): 获取当前日期的日份。

  12. const dayOfWeek = d.getDay(): 获取当前日期是星期几,返回值是一个从0到6的整数,0 代表星期日。

  13. const isTodayBefore = d.getTime() < today.setHours(0, 0, 0, 0): 判断当前日期是否在今天之前。getTime() 返回日期的毫秒数,通过比较毫秒数可以确定日期的先后顺序。

  14. monthDates.push({ day, week: dayOfWeek, isTodayBefore, yymmdd: ����−year−{month}-${day >= 10 ? day : ‘0’ + day} }): 将当前日期的信息以对象的形式添加到 monthDates 数组中,包括日期、星期几、是否在今天之前以及日期的格式化字符串(年-月-日)。

  15. return { year, month, dates: monthDates }: 返回包含年份、月份和该月份日期信息的对象。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号