Vue【原创】日历组件

发布时间 2023-08-28 14:48:18作者: Binyy_Wuhan

最近项目中封装了一个日历组件,用于节假日管理,支持输入默认选中的日期,选择管理日期。

效果图:

 

calendar组件:

  1 <template>
  2     <div class="calendar">
  3         <slot name="title">
  4             <div class="calendar-title">{{ curYearMonth }}</div>
  5         </slot>
  6 
  7         <table class="calendar-table">
  8             <thead>
  9                 <tr>
 10                     <th v-for="(item, i) in weeks" :key="i">{{ item }}</th>
 11                 </tr>
 12             </thead>
 13             <tbody>
 14                 <tr v-for="(dates, i) in res" :key="i" :style="{ height: cellHeight }">
 15                     <td v-for="(item, index) in dates" :key="index" :class="{
 16                             notCurMonth: !item.isCurMonth,
 17                             currentDay: item.date === curDate,
 18                             selectDay: item.isSelected,
 19                             rangeSelectd: item.isRangeSelected,
 20                             weekend: item.isWeekend
 21                         }" @click="handleItemClick(item, i, index)" @mouseover="handleItemMove(item, i, index)">
 22                         <!-- <span>{{ item.date.split('-').slice(1).join('-') }}</span> -->
 23                         <span>{{ item.date | cellDate }}</span>
 24                         <slot :data="item" />
 25                     </td>
 26                 </tr>
 27             </tbody>
 28         </table>
 29     </div>
 30 </template>
 31 
 32 <script>
 33     import {
 34         getDaysInMonth,
 35         handleCreateDate,
 36         handleCreateDatePicker,
 37         parseTime
 38     } from '../../../src/utils/dateUtils.js';
 39     
 40     const SELECT_MODE = {
 41         SINGLE: 'single',
 42         RANGE: 'range'
 43     }
 44     
 45     export default {
 46         name: 'LiloCalendar',
 47         components: {},
 48         filters: {
 49             cellDate(value) {
 50                 // value.split('-')[1] + '-' + value.split('-')[2]
 51                 return value.split('-')[2]
 52             }
 53         },
 54         props: {
 55             selectMode: {
 56                 type: String,
 57                 default: SELECT_MODE.SINGLE //'single,range'
 58             },
 59             startOfWeek: {
 60                 type: Number,
 61                 default: 1
 62             },
 63             canSelect: {
 64                 type: Boolean,
 65                 default: false
 66             },
 67             cellHeight: {
 68                 type: String,
 69                 default: '60px'
 70             },
 71             currentDate: {
 72                 type: String,
 73                 default: new Date().getFullYear() + '-' + (new Date().getMonth() + 1)
 74             },
 75             defaultSelectedDates: {
 76                 type: Array,
 77                 default () {
 78                     return []
 79                 }
 80             },
 81         },
 82         data() {
 83             return {
 84                 monthOptions: [],
 85                 yearOptions: [],
 86                 weeks: ['', '', '', '', '', '', ''],
 87                 curYear: 0, // 当前年
 88                 curMonth: 0, // 当前月
 89                 days: 0, // 当前月总共天数
 90                 curDate: parseTime(new Date().getTime()), // 当前日期 yyyy-MM-dd 格式,用来匹配是否是当前日期
 91                 prevDays: [], // 非当前月的上一月展示的日期
 92                 rearDays: [], // 非当前月的下一月展示的日期
 93                 curDays: [], // 当前月的日期
 94                 showDays: [], // 总共展示的42个日期
 95                 res: [], // 二维数组
 96                 selectedDates: [], // 选中的日期
 97                 selectedMode: false, // true表示点击, false表示滑动
 98                 moveIndex: [], // 两个,第一个是起始,第二个是结束
 99                 canMove: false // 当moveIndex数组有一个值时,可以触发滑动
100             };
101         },
102         computed: {
103             curYearMonth() {
104                 const temp = parseInt(this.curMonth) + 1;
105                 return this.curYear + '-' + (temp < 10 ? `0${temp}` : temp);
106             }
107         },
108         watch: {
109             curMonth: {
110                 handler(val) {
111                     this.handleGetDays(this.curYear, val, this.startOfWeek);
112                 }
113             },
114             curYear: {
115                 handler(val) {
116                     this.handleGetDays(val, this.curMonth, this.startOfWeek);
117                 }
118             },
119             currentDate: {
120                 handler(val) {
121                     this.setup();
122                 }
123             },
124             defaultSelectedDates: {
125                 handler(val) {
126                     this.setup();
127                 }
128             }
129         },
130         created() {},
131         mounted() {
132             this.setup();
133         },
134         methods: {
135             setup() {
136                 this.weeks.unshift(...this.weeks.splice(this.startOfWeek - 1));
137                 this.handleGetDays(this.curYear, this.curMonth, this.startOfWeek);
138                 this.selectedMode = this.selectMode === SELECT_MODE.SINGLE;
139 
140                 const temp = this.currentDate.split('-');
141                 this.curYear = parseInt(temp[0]);
142                 this.curMonth = parseInt(temp[1]) - 1;
143 
144                 this.monthOptions = handleCreateDatePicker().months;
145                 this.yearOptions = handleCreateDatePicker().years;
146                 if (localStorage.selectedDates) this.selectedDates = JSON.parse(localStorage.selectedDates);
147             },
148             handleGetDays(year, month, startOfWeek) {
149                 this.showDays = [];
150                 this.days = getDaysInMonth(year, month);
151                 let firstDayOfWeek = new Date(`${year}-${month + 1}-01`).getDay();
152 
153                 // 处理周起始日
154                 const obj = {
155                     1: '',
156                     2: '',
157                     3: '',
158                     4: '',
159                     5: '',
160                     6: '',
161                     0: ''
162                 };
163                 const firstDayInCN = obj[firstDayOfWeek];
164                 const index = this.weeks.indexOf(firstDayInCN);
165                 // console.log(firstDayOfWeek, index);
166 
167                 if (firstDayOfWeek === 0) {
168                     // 星期天为0 星期一为1 ,以此类推
169                     firstDayOfWeek = 7;
170                 }
171 
172                 this.prevDays = handleCreateDate(year, month, 1, index + 1, 'prev');
173                 this.rearDays = handleCreateDate(year, month, 1, 42 - this.days - index, 'rear');
174 
175                 this.curDays = handleCreateDate(year, month, 1, this.days, 'cur', this.defaultSelectedDates);
176                 this.showDays.unshift(...this.prevDays);
177                 this.showDays.push(...this.curDays);
178                 this.showDays.push(...this.rearDays);
179                 this.res = this.handleFormatDates(this.showDays);
180             },
181             handleFormatDates(arr, size = 7) {
182                 // 传入长度42的原数组,最终转换成二维数组
183                 const arr2 = [];
184                 for (let i = 0; i < size - 1; i++) {
185                     const temp = arr.slice(i * size, i * size + size);
186                     arr2.push(temp);
187                 }
188                 // console.log(arr2)
189                 return arr2;
190             },
191             handleTableHead(start) {
192                 const sliceDates = this.weeks.splice(start - 1);
193                 this.weeks.unshift(...sliceDates);
194             },
195             handleItemClick(item, i, j) {
196                 if (!this.canSelect) return;
197                 if (!item.isCurMonth) return;
198                 if (this.selectedMode) {
199                     this.$nextTick(() => {
200                         // this.$set(this.res[i][j], 'isSelected', )
201                         this.res[i][j].isSelected = !this.res[i][j].isSelected;
202                         if (this.res[i][j].isSelected) {
203                             this.selectedDates.push(this.res[i][j].date);
204                             this.selectedDates = Array.from(new Set(this.selectedDates));
205                             this.$emit('date-selected', {
206                                 selectedDates: this.selectedDates,
207                                 removeDate: '',
208                                 addDate: item.date
209                             });
210                         } else {
211                             this.selectedDates.splice(this.selectedDates.indexOf(item.date), 1);
212                             this.$emit('date-selected', {
213                                 selectedDates: this.selectedDates,
214                                 removeDate: item.date,
215                                 addDate: ''
216                             });
217                         }
218                     });
219                 } else {
220                     // 滑动模式下,第一次点击是起始,第二次点击是结束
221                     const index = i * 7 + j;
222                     this.canMove = true;
223                     if (this.moveIndex.length === 1) {
224                         this.canMove = false;
225                     }
226                     if (this.moveIndex.length === 2) {
227                         this.showDays.forEach(item => {
228                             item.isSelected = false;
229                             item.isRangeSelected = false;
230                         });
231                         this.canMove = true;
232                         this.moveIndex.length = 0;
233                     }
234                     this.moveIndex.push(index);
235                     this.moveIndex.sort((a, b) => a - b);
236                     this.selectedDates = this.showDays.slice(this.moveIndex[0], this.moveIndex[1] + 1);
237                     this.selectedDates.length !== 0 && this.$emit('date-selected', this.selectedDates);
238                 }
239             },
240             handleItemMove(data, i, j) {
241                 if (this.canMove && !this.selectedMode) {
242                     const index = i * 7 + j;
243                     this.showDays.forEach(item => {
244                         item.isSelected = false;
245                         item.isRangeSelected = false;
246                     });
247                     // 让第一个日期和最后一个日期显示蓝色高亮
248                     this.showDays[index].isSelected = true;
249                     this.showDays[this.moveIndex[0]].isSelected = true;
250 
251                     // 不同情况的判断,当用户的鼠标滑动进日期的索引小于起始日期的索引,要做if else处理
252                     if (this.moveIndex[0] < index) {
253                         for (let i = this.moveIndex[0] + 1; i < index; i++) {
254                             this.showDays[i].isRangeSelected = true;
255                         }
256                     } else {
257                         for (let i = index + 1; i < this.moveIndex[0]; i++) {
258                             this.showDays[i].isRangeSelected = true;
259                         }
260                     }
261                 }
262             },
263             handleQuickChange(type) {
264                 if (type === 'prev') {
265                     this.curMonth--;
266                     // console.log(this.curMonth);
267                     if (this.curMonth === -1) {
268                         this.curMonth = 11;
269                         this.curYear -= 1;
270                     }
271                 } else if (type === 'next') {
272                     this.curMonth++;
273                     if (this.curMonth === 12) {
274                         this.curMonth = 0;
275                         this.curYear += 1;
276                     }
277                 }
278             }
279         }
280     };
281 </script>
282 
283 <style scoped lang="scss">
284     .calendar {
285         display: flex;
286         align-items: center;
287         justify-content: center;
288         flex-direction: column;
289     }
290 
291     .calendar-title {
292         width: 100%;
293         padding-top: 8px;
294         padding-bottom: 5px;
295         font-weight: bold;
296         border-bottom: 1px solid rgba($color: #000000, $alpha: .1);
297     }
298 
299     .calendar-table {
300         width: 100%;
301         table-layout: fixed;
302         border-collapse: collapse;
303         transition: 0.3s;
304 
305         thead tr {
306             height: 50px;
307         }
308 
309         tbody tr {
310             &:first-child td {
311                 border-top: 1px solid rgba($color: #000000, $alpha: .1);
312             }
313 
314             td {
315                 cursor: pointer;
316                 border-right: 1px solid rgba($color: #000000, $alpha: .1);
317                 border-bottom: 1px solid rgba($color: #000000, $alpha: .1);
318                 text-align: center;
319 
320                 &:first-child {
321                     border-left: 1px solid rgba($color: #000000, $alpha: .1);
322                 }
323             }
324         }
325     }
326 
327     .notCurMonth {
328         transition: all .25s ease-out;
329         color: #c0c4cc;
330     }
331 
332     .currentDay {
333         transition: all .25s ease-out;
334         color: #fff;
335         background-color: #409eff;
336     }
337 
338     .selectDay {
339         transition: all .25s ease-out;
340         color: #fff;
341         background-color: #08a8a0;
342     }
343 
344     .rangeSelectd {
345         transition: all .25s ease-out;
346         color: #606266;
347         background-color: #dee2e9;
348     }
349 
350     .weekend {
351         transition: all .25s ease-out;
352         color: #f56c6c;
353     }
354 </style>
View Code

 

调用案例和参数说明(我这里说全局插件引入,单独使用需要自行import导入):

 1 <template>
 2     <div class="calendar-container">
 3         <lilo-calendar 
 4             :default-selected-dates="defaultSelectedDates" 
 5             :current-date="currentDate" 
 6             :start-of-week="startOfWeek"
 7             :cell-height="cellHeight"
 8             :can-select="canSelect"
 9             @date-selected="dateSelected">
10             <!-- <template #title> -->
11                 <!-- 标题栏可以设置插槽 -->
12                 <!-- <div class="custom-title">2023-08</div> -->
13             <!-- </template> -->
14         </lilo-calendar>
15     </div>
16 </template>
17 
18 <script>
19     export default {
20         data() {
21             return {
22                 defaultSelectedDates: [ '2023-08-01', '2023-08-03' ], //默认选中的日期
23                 currentDate: '2023-08', //当前月份
24                 startOfWeek: 1, //从星期几开始,
25                     // 1: '一',
26                     // 2: '二',
27                     // 3: '三',
28                     // 4: '四',
29                     // 5: '五',
30                     // 6: '六',
31                     // 0: '日'
32                 cellHeight: '120px', //日期单元的高度
33                 canSelect: true //是否可以选中,选中之后触发date-selected事件
34             }
35         },
36         methods: {
37             dateSelected(val) {
38                 console.log(val)
39             }
40         }
41     }
42 </script>
43 
44 <style lang="scss" scoped>
45     .calendar-container {
46         padding: 20px;
47         .custom-title {
48             width: 100%;
49             padding: 8px;
50             color: #409eff;
51             font-weight: bold;
52             font-size: 1.1rem;
53             border-bottom: 1px solid #0000001f;
54         }
55     }
56 </style>