1/* 2 * Copyright (c) 2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import Constants from '../constant/Constants'; 17import { CalendarController, CalendarViewType } from '../components/CustomCalendar'; 18import { CalendarStyle, CalendarSwitch } from '../model/CalendarModel'; 19import { MonthViewItem } from './MonthViewItem'; 20import { TimeUtils } from '../utils/TimeUtils'; 21import CommonData from '../common/CommonData'; 22 23/** 24 * 月视图 25 */ 26@Component 27export struct MonthView { 28 // 当前显示的年份 29 @State currentShowYear: number = Constants.TODAY_YEAR; 30 // 当前显示的月份 31 @State currentShowMonth: number = Constants.TODAY_MONTH; 32 // 当前显示的年月 33 @State currentYearMonth: string = Constants.TODAY_YEAR + '-' + Constants.TODAY_MONTH; 34 // 当前选中的日期 35 @State @Watch('onSelectDayChange') currentSelectDate: string = 36 Constants.TODAY_YEAR + '-' + Constants.TODAY_MONTH + '-' + Constants.TODAY + '-' + '0'; 37 // 下个月对应的年月信息 38 @State nextYearMonth: string = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth); 39 // 上个月对应的年月信息 40 @State lastYearMonth: string = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth); 41 // swiper当前显示的子组件索引 42 @State swiperMonthIndex: number = 1; 43 // 记录swiper上一次显示的子组件索引。 44 private oldMonthViewIndex: number = 1; 45 // 添加日程后,重新刷新月视图的控制器 46 private oneController = new CalendarController(); 47 private twoController = new CalendarController(); 48 private threeController = new CalendarController(); 49 // 自定义日历样式 50 calendarStyle: CalendarStyle = {}; 51 // 年、月、周视图切换场景的相关设置 52 calendarSwitch: CalendarSwitch = { isYearMonthHidden: false }; 53 // 日期点击回调 54 onDateClick: (year: number, month: number, date: number) => void = () => { 55 }; 56 // 年、月、周视图左右滑动切换回调 57 onChangeYearMonth: (year: number, month: number) => void = () => { 58 }; 59 /** 60 * 用于年、月、周视图间切换场景下刷新日期数据 61 */ 62 private swiperRefresh = (value: CalendarViewType) => { 63 if (value === CalendarViewType.MONTH) { 64 if (this.calendarSwitch.currentSelectDay) { 65 // 重置swiper索引 66 this.swiperMonthIndex = 1; 67 this.oldMonthViewIndex = 1; 68 // 获取当前选中的日期 69 this.currentSelectDate = 70 this.calendarSwitch.currentSelectDay.year + '-' + this.calendarSwitch.currentSelectDay.month + '-' + 71 this.calendarSwitch.currentSelectDay.date; 72 // 更新年月数据 73 this.currentShowYear = this.calendarSwitch.currentSelectDay.year; 74 this.currentShowMonth = this.calendarSwitch.currentSelectDay.month; 75 this.currentYearMonth = 76 this.calendarSwitch.currentSelectDay.year + '-' + this.calendarSwitch.currentSelectDay.month; 77 this.lastYearMonth = TimeUtils.getLastYearMonth(this.calendarSwitch.currentSelectDay.year, 78 this.calendarSwitch.currentSelectDay.month); 79 this.nextYearMonth = TimeUtils.getNextYearMonth(this.calendarSwitch.currentSelectDay.year, 80 this.calendarSwitch.currentSelectDay.month); 81 } 82 } 83 } 84 /** 85 * 用于刷新在年视图上点击月后要跳转的月视图数据 86 */ 87 private swiperYearToMonthRefresh = (year: number, month: number) => { 88 // 重置swiper索引 89 this.swiperMonthIndex = 1; 90 this.oldMonthViewIndex = 1; 91 // 更新年月数据 92 this.currentShowYear = year; 93 this.currentShowMonth = month; 94 this.currentYearMonth = year + '-' + month; 95 this.lastYearMonth = TimeUtils.getLastYearMonth(year, month); 96 this.nextYearMonth = TimeUtils.getNextYearMonth(year, month); 97 } 98 /** 99 * 用于添加日程后,重刷月视图数据 100 */ 101 private schedulePointRefresh = () => { 102 this.oneController.schedulePointRefresh(); 103 this.twoController.schedulePointRefresh(); 104 this.threeController.schedulePointRefresh(); 105 } 106 107 aboutToAppear() { 108 if (this.calendarSwitch.controller) { 109 // 给controller对应的方法赋值 110 this.calendarSwitch.controller.swiperRefresh = this.swiperRefresh; 111 this.calendarSwitch.controller.swiperYearToMonthRefresh = this.swiperYearToMonthRefresh; 112 this.calendarSwitch.controller.schedulePointRefresh = this.schedulePointRefresh; 113 } 114 } 115 116 /** 117 * 日期选择改变 118 */ 119 onSelectDayChange() { 120 // 记录选中的月视图日期,拉起添加日程页面会根据选中日期显示对应的"开始时间" 121 CommonData.CURRENT_SELECT_DATE = this.currentSelectDate; 122 const PARTS: string[] = this.currentSelectDate.split('-'); 123 // 更新年月数据 124 this.currentShowYear = Number(PARTS[0]); 125 this.currentShowMonth = Number(PARTS[1]); 126 this.onChangeYearMonth(this.currentShowYear, this.currentShowMonth); 127 const WEEK = Number(PARTS[3]); 128 /** 129 * 月视图中点击非当月日期时,会切换相应的月份,根据日期中的this.currentSelectDay.week值进行判断是切换上个月(week等于0)还是下个月(week 130 * 等于2)。在TimeUtils的byMonthDayForYear中月视图中上个月日期中的week会写入0,下个月的日期写入2。 131 */ 132 if (WEEK === 0) { 133 // 月视图中如果点击了上个月的日期,即切换到上个月,类似右滑切换月份,只是swiper索引不变,所以需要刷新两个月 134 if (this.oldMonthViewIndex === 0) { 135 // 月视图当前swiper显示的是索引0,则刷新索引1和2的月份 136 this.currentYearMonth = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth); 137 this.nextYearMonth = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth); 138 } else if (this.oldMonthViewIndex === 1) { 139 // 月视图当前swiper显示的是索引1,则刷新索引0和2的月份 140 this.lastYearMonth = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth); 141 this.nextYearMonth = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth); 142 } else if (this.oldMonthViewIndex === 2) { 143 // 月视图当前swiper显示的是索引2,则刷新索引0和1的月份 144 this.lastYearMonth = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth); 145 this.currentYearMonth = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth); 146 } 147 } else if (WEEK === 2) { 148 // 月视图中如果点击了下个月的日期,即切换到下个月,类似左滑切换月份,只是swiper索引不变,所以需要刷新两个月 149 if (this.oldMonthViewIndex === 0) { 150 //刷新索引1和2 151 this.currentYearMonth = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth); 152 this.nextYearMonth = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth); 153 } else if (this.oldMonthViewIndex === 1) { 154 //刷新索引0和2 155 this.lastYearMonth = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth); 156 this.nextYearMonth = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth); 157 } else if (this.oldMonthViewIndex === 2) { 158 //刷新索引0和1 159 this.lastYearMonth = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth); 160 this.currentYearMonth = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth); 161 } 162 } 163 } 164 165 /** 166 * 星期 167 */ 168 @Builder 169 weeks() { 170 Row() { 171 ForEach(Constants.WEEKS, (text: string, index: number) => { 172 Text(text) 173 .fontSize(Constants.WEEK_FONT_SIZE * 174 (this.calendarStyle.textScaling ? this.calendarStyle.textScaling : Constants.FONT_MULTIPLIER)) 175 .fontColor((index === 0 || index === 6) ? Color.Grey : Color.Black) 176 .width($r('app.integer.calendar_switch_size_forty')) 177 .textAlign(TextAlign.Center) 178 }, (text: string) => text) 179 } 180 .width(Constants.MONTH_VIEW_WIDTH) 181 .justifyContent(FlexAlign.SpaceBetween) 182 .margin({ bottom: $r('app.integer.calendar_switch_size_ten') }) 183 } 184 185 build() { 186 // 月视图 187 Column() { 188 if (!this.calendarSwitch.isYearMonthHidden) { 189 // 年月信息标题 190 Text(`${this.currentShowYear}年${this.currentShowMonth}月`) 191 .fontSize(Constants.YEAR_MONTH_FONT_SIZE * 192 (this.calendarStyle.textScaling ? this.calendarStyle.textScaling : Constants.FONT_MULTIPLIER)) 193 .fontWeight(Constants.FONT_WEIGHT_FIVE_HUNDRED) 194 .width($r('app.string.calendar_switch_full_size')) 195 .padding({ left: $r('app.integer.calendar_switch_size_ten') }) 196 .margin({ bottom: $r('app.integer.calendar_switch_size_ten') }) 197 } 198 // 星期 199 this.weeks() 200 201 Swiper() { 202 // 月视图子组件 203 MonthViewItem({ 204 yearMonth: this.lastYearMonth, 205 currentSelectDate: this.currentSelectDate, 206 onDateClick: (year: number, month: number, date: number) => { 207 this.onDateClick(year, month, date); 208 }, 209 calendarStyle: { 210 textScaling: this.calendarStyle.textScaling, 211 backgroundColor: this.calendarStyle.backgroundColor, 212 monthDayColor: this.calendarStyle.monthDayColor, 213 noMonthDayColor: this.calendarStyle.noMonthDayColor, 214 lunarColor: this.calendarStyle.lunarColor 215 }, 216 controller: this.oneController 217 }) 218 MonthViewItem({ 219 yearMonth: this.currentYearMonth, 220 currentSelectDate: this.currentSelectDate, 221 onDateClick: (year: number, month: number, date: number) => { 222 this.onDateClick(year, month, date); 223 }, 224 calendarStyle: { 225 textScaling: this.calendarStyle.textScaling, 226 backgroundColor: this.calendarStyle.backgroundColor, 227 monthDayColor: this.calendarStyle.monthDayColor, 228 noMonthDayColor: this.calendarStyle.noMonthDayColor, 229 lunarColor: this.calendarStyle.lunarColor 230 }, 231 controller: this.twoController 232 }) 233 MonthViewItem({ 234 yearMonth: this.nextYearMonth, 235 currentSelectDate: this.currentSelectDate, 236 onDateClick: (year: number, month: number, date: number) => { 237 this.onDateClick(year, month, date); 238 }, 239 calendarStyle: { 240 textScaling: this.calendarStyle.textScaling, 241 backgroundColor: this.calendarStyle.backgroundColor, 242 monthDayColor: this.calendarStyle.monthDayColor, 243 noMonthDayColor: this.calendarStyle.noMonthDayColor, 244 lunarColor: this.calendarStyle.lunarColor 245 }, 246 controller: this.threeController 247 }) 248 } 249 .onAnimationStart((index: number, targetIndex: number, extraInfo: SwiperAnimationEvent) => { 250 /** 251 * TODO 知识点:年视图,月视图和周视图都是根据Swiper的onAnimationStart事件(切换动画开始时触发该回调)进行年,月或周的切换。以月 252 * 视图为例,通过oldMonthViewIndex存储上一次的Swiper索引值,然后跟本次切换的索引targetIndex进行比较,来识别月份是左滑还是右滑。 253 * 然后根据当前切换后的索引值去刷新所需的月份。例如,假设swiper索引0(7月),swiper索引1(8月),swiper索引2(9月),当前Swiper 254 * 显示的索引为1。当Swiper右滑从索引1(8月)切换到索引0(7月)时,需要把Swiper里索引2(9月)的月份更新为6月的数据。年视图和周视图 255 * 的onAnimationStart也是类似处理逻辑,这里不再赘述。 256 */ 257 if (this.oldMonthViewIndex === targetIndex) { 258 // 如果手指滑动swiper松开时,targetIndex和之前记录子组件索引oldMonthViewIndex一样,说明swiper没有切换子组件,不需要切换月份 259 return; 260 } 261 // 记录子组件索引 262 this.oldMonthViewIndex = targetIndex; 263 // 判断是否右滑切换月份 264 const IS_RIGHT_SLIDE: boolean = (index === 1 && targetIndex === 0) || (index === 0 && targetIndex === 2) || 265 (index === 2 && targetIndex === 1); 266 // TODO: 高性能知识点: 左右滑动切换月时,每次切换月只更新一个月的数据,以优化月视图左右切换时的性能。年视图和周视图也类似,这里不再赘述。 267 // 右滑切换到上个月 268 if (IS_RIGHT_SLIDE) { 269 // 将当前月份设置为上个月 270 this.currentShowYear = TimeUtils.getLastYear(this.currentShowYear, this.currentShowMonth); 271 this.currentShowMonth = TimeUtils.getLastMonth(this.currentShowYear, this.currentShowMonth); 272 this.onChangeYearMonth(this.currentShowYear, this.currentShowMonth); 273 if (targetIndex === 0) { 274 /** 275 * swiper索引右滑到0时,修改swiper索引2的月份为当前月份(索引0)的上一个月。比如,假设swiper索引0(7月),swiper索引1(8月) 276 * ,swiper索引2(9月)。当右滑切换到索引0(7月)时,需要把索引2(9月)的月份改成6月。 277 */ 278 // 修改swiper索引2的月份为当前月份(索引0)的上一个月 279 this.nextYearMonth = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth); 280 } else if (targetIndex === 1) { 281 // swiper索引右滑到1时,修改swiper索引0的月份为当前月份(索引1)的上一个月。 282 this.lastYearMonth = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth); 283 } else if (targetIndex === 2) { 284 // swiper索引右滑到2时,修改swiper索引1的月份为当前月份(索引2)的上一个月。 285 this.currentYearMonth = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth); 286 } 287 } else { 288 // 右滑切换到下个月 289 // 将当前月份设置为下个月 290 this.currentShowYear = TimeUtils.getNextYear(this.currentShowYear, this.currentShowMonth); 291 this.currentShowMonth = TimeUtils.getNextMonth(this.currentShowYear, this.currentShowMonth); 292 this.onChangeYearMonth(this.currentShowYear, this.currentShowMonth); 293 if (targetIndex === 0) { 294 // swiper索引左滑到0时,修改swiper索引1的月份为当前月份(索引0)的下一个月。 295 this.currentYearMonth = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth); 296 } else if (targetIndex === 1) { 297 // swiper索引左滑到1时,修改swiper索引2的月份为当前月份(索引1)的下一个月。 298 this.nextYearMonth = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth); 299 } else if (targetIndex === 2) { 300 // swiper索引左滑到2时,修改swiper索引0的月份为当前月份(索引2)的下一个月。 301 this.lastYearMonth = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth); 302 } 303 } 304 }) 305 .indicator(false) 306 .loop(true) 307 .index($$this.swiperMonthIndex) 308 } 309 .width(Constants.MONTH_VIEW_WIDTH) 310 .height(Constants.MONTH_VIEW_HEIGHT) 311 } 312}