• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}