В последнее время я работаю над требованием, похожим на учебный план.Мне нужно создать календарь для поддержки функции и отображения.Кстати, я буду изучать, как разработать компонент календаря.
возобновить
- 2.23 Исправлена ошибка, из-за которой в феврале 2026 года отображалось более одной строки. Спасибо за ошибку, предложенную @陳青一一人場. Решение состоит в том, чтобы придать особое значение календарю в феврале, новой дате (год, месяц+ 1, 0).getDay() === 6 больше не будет отображать более поздние даты.
- Обновление после окончания работы, больше научной логики
// if (total_calendar_list.length > 35) { // nextNum = 42 - total_calendar_list.length; // } else { // nextNum = 35 - total_calendar_list.length; // } // if (month === 1 && new Date(year, month, 0).getDay() === 6) { // nextNum = 0 // } nextNum = 6 - new Date(year, month+1, 0).getDay()
Эта статья в основном охватывает следующее содержание:
- Как разработать скин календаря?
- Как рассчитать год месяц день?
- Как разработать функции, связанные с календарем?
- Резюме и исходный код DEMO
Как разработать скин календаря?
Слои разделены, блоки независимы
Прежде чем разобраться с логикой календаря, я хочу отметить некоторые проблемы, связанные со стилем календаря:
Ниже приведено адаптивное преобразование, основанное на vw в качестве основной единицы, написанное путем заимствования из режима px2rem. Проще говоря, когда наш проект дизайна представляет собой двойное изображение iPhone8, мы вычисляем отношение ширины элемента к 375 (максимальная ширина iPhone8) и умножаем его на 100vw, чтобы получить значение vw элемента. .Поскольку vw — это процентная единица относительно экрана, мы можем добиться желаемого адаптивного эффекта: на разных экранах коэффициент отображения одного и того же элемента одинаков.
// 借鉴了Rem布局
@function pxWithVw($n) {
@return 100vw * $n / 375;
}
// 规定极限宽度,避免PC上观感太差
@function pxWithVwMax($n) {
@return 480px * $n / 375;
}
С вышеописанной функцией SCSS нам в принципе не нужно рассматривать проблему экранной адаптации, и мы можем печатать сколько угодно. Что касается стиля календаря, то на самом деле усложнять не плохо, просто нужно хорошо разделить слои, прежде чем это делать.
Как показано на рисунке выше, каждый блок представляет собой слой элементов, и, наконец, будет такой макет:
<!--最外层的div限定整个日历的宽度以及一些圆角阴影等样式-->
<div class="calendar">
<!--header则为上图中绿色框的内容,包含上下月切换以及日历title-->
<div class="calendar__header"></div>
<!--顾名思义main则是整个日历的核心内容,也就是日期的展示区域-->
<div class="calendar__main">
<!--星期一~星期日的展示头,列表渲染固定的7个block-->
<div class="main__block-head"></div>
<!--相应月份的日期展示区域,列表渲染-->
<div class="main__block"></div>
</div>
</div>
Может после прочтения макет в calendar__main странный.Почему фиксированные заголовки дисплея не были разделены?Когда вы на самом деле напишете это здесь, вы обнаружите, что это не нужно.
Поскольку мы используем pxWithVw для указания ширины calendar__main и ширины каждого блока, это также гарантирует, что каждые 7 блоков элементов будут занимать нашу строку, а затем мы используем justify-content: space-around, чтобы гарантировать, что промежутки между нашими элементами последовательный.
Ну, с разделением слоев покончено, что такое блочная независимость?
Это в основном относится к блоку отображения даты.Мы независимы от каждого блока, поэтому при его рендеринге мы можем легко решить, какой стиль должен отображаться или какие события должны быть привязаны к нему, что дает нам точность. удобно контролировать представление каждой даты.
Как рассчитать год месяц день?
Содержание этого раздела можно выразить одним предложением:
Нам нужно только знать, что 1-е число месяца — это день недели, чтобы отобразить весь календарь.
Что касается расчета года, месяца и дня, у меня здесь есть два режима: один — вычислять только дату текущего месяца, а другой — вычислять дату всего года. В этой статье я хочу сосредоточиться на записи первого способа записи.Если вы хотите узнать о втором способе, вы можете перейти на мой github, чтобы увидеть демо этого календаря.
Давайте посмотрим на картинку и поговорим.В этом феврале 28 дней, и 1 число - четверг. Означает ли это, что нам просто нужно отрендерить 28 'main__block' последовательно, начиная с четверга? На самом деле это так.Ключ в том, как найти наш номер 1 в четверг.Пока это может быть точно расположено, наш календарь естественно выйдет.
// 定义每个月的天数,如果是闰年第二月改为29天
// year=2018;month=1(js--month=0~11)
let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
daysInMonth[1] = 29;
}
// 获得指定年月的1号是星期几
let targetDay = new Date(year, month, 1).getDay();
// 将要在calendar__main中渲染的列表
let total_calendar_list = [];
let preNum = targetDay;
// 首先先说一下,我们的日期是(日--六)这个顺序也就是(0--6)
// 有了上述的前提我们可以认为targetDay为多少,我们就只需要在total_calendar_list的数组中push几个content为''的obj作为占位
if (targetDay > 0) {
for (let i = 0; i < preNum; i++) {
let obj = {
type: "pre",
content: ""
};
total_calendar_list.push(obj);
}
}
Таким образом, позиция №1 естественным образом дойдет до нужного нам четверга, а дальше нам останется только отрендерить по порядку. Ниже показано заполнение оставшегося массива дат.После заполнения он возвращается для использования нашим слоем представления.
for (let i = 0; i < daysInMonth[month]; i++) {
let obj = {
type: "normal",
content: i + 1
};
total_calendar_list.push(obj);
}
nextNum = 6 - new Date(year, month+1, 0).getDay()
// 与上面的type=pre同理
for (let i = 0; i < nextNum; i++) {
let obj = {
type: "next",
content: ""
};
total_calendar_list.push(obj);
}
return total_calendar_list;
Как разработать функции, связанные с календарем?
Как выбрать предыдущий месяц или следующий месяц?
data() {
return {
// ...
selectedYear: new Date().getFullYear(),
selectedMonth: new Date().getMonth(),
selectedDate: new Date().getDate()
};
}
handlePreMonth() {
if (this.selectedMonth === 0) {
this.selectedYear = this.selectedYear - 1
this.selectedMonth = 11
this.selectedDate = 1
} else {
this.selectedMonth = this.selectedMonth - 1
this.selectedDate = 1
}
}
handleNextMonth() {
if (this.selectedMonth === 11) {
this.selectedYear = this.selectedYear + 1
this.selectedMonth = 0
this.selectedDate = 1
} else {
this.selectedMonth = this.selectedMonth + 1
this.selectedDate = 1
}
}
Это так просто. Обратите внимание на преобразование времени по годам. Нам нужно изменить год при изменении месяца, чтобы можно было отобразить правильную дату.
У вас могут возникнуть вопросы, почему после смены месяца или года не нужно пересчитывать дату? На самом деле расчет есть.Интересно, вы еще помните, что Vue — это изменение, управляемое данными.Нам нужно только обращать внимание на изменение данных, а Vue поможет нам решить другие вещи.
Если выбран определенный день?
handleDayClick(item) {
if (item.type === 'normal') {
// do anything...
this.selectedDate = Number(item.content)
}
}
При рендеринге списка я привязываю событие клика к каждому блоку.Преимущество этого в том,что очень удобно вызывать.При нажатии на каждый блок можно получить содержимое блока и делать все что угодно
Конечно, мы можем также привязать прослушиватели событий к внешнему родительскому элементу, и решать событие щелчка каждого блока через поток событий.Тут это зависит от личных привычек~ Ведь количество элементов не особенно велико
Суммировать
Мобильный календарь вроде бы сделали без риска.Вообще говоря,календарь еще неравнодушен к стилю.Требования к логике не особо высокие,но требования к стилю достаточно высокие.Необходимо иметь определенное понимание flexbox Макет.Быстро выставил скелет календаря.Хотя не обязательно использовать флекс, лично я думаю, что эффективность использования флекса будет несколько выше.
Календарь DEMO написан на основе Vue -- Github
Многословно, почему ты не забываешь писать календарь? Конечно, это бизнес-требование, поэтому этот компонент календаря вначале был написан на React, а позже изменен на Vue, если я хотел попробовать его во Vue. На самом деле запись в React аналогична, но я извлеку блок даты в компонент без состояния, и это не сделает его лучше :)