feat(home): 修复swiper动画、宜忌布局、月历显示、节日速查
Build and Publish Server / build (push) Successful in 2m8s
Publish Mini Program Dev Version / publish (push) Failing after 11m17s

1. 修复swiper箭头点击动画丢失问题
   - 改用固定7天窗口,滑动到边缘时整体滚动数据
   - 保持dayIndex连续,确保swiper平滑动画

2. 修复宜忌两行显示压在一起
   - 设置item固定高度32rpx和行距12rpx
   - 超出2行显示...省略号

3. 修复月历数字竖向排列问题
   - 修正wxml嵌套结构,让42个格子正确排成6行7列
   - 压缩格子高度,放大数字字体

4. 节日速查功能优化
   - 默认显示3条,右侧添加更多
This commit is contained in:
gouki
2026-08-10 23:07:47 +00:00
parent 52c0d3c774
commit 2a68eae999
23 changed files with 2140 additions and 87 deletions
+188 -28
View File
@@ -1,14 +1,24 @@
const {
getDayInfo, getAlmanacInfo, getMonthCalendar,
getLegalHoliday, isWorkday, getUpcomingFestivals, getNextBuddhistFestival
getLegalHoliday, isWorkday, getUpcomingFestivals, getNextBuddhistFestival, getYearFestivals
} = require('../../utils/core/index.js');
const { SOLAR_TERM_TIMES } = require('../../utils/core/data.js');
const { request } = require('../../utils/request.js');
function pad(n) { return String(n).padStart(2, '0'); }
function fmt(y, m, d) { return `${y}-${pad(m)}-${pad(d)}`; }
/** 历史上的今天年份显示:负数为公元前,0 为年份不详 */
function fmtYear(year) {
if (!year) return '';
return year < 0 ? `${-year}` : `${year}`;
}
const OTD_CACHE_KEY = 'otd-cache';
const OTD_CACHE_TTL = 48 * 3600 * 1000; // 48 小时(数据按月日维度存储,变化频率低)
const MONTH_SPAN = 2; // 当前月前后各2个月,共5个月
const DAY_SPAN = 2; // 当前日前后各2天,共5天
const DAY_SPAN = 3; // 当前日前后各3天,共7天(固定窗口,避免动态增删导致动画丢失)
Page({
data: {
@@ -26,6 +36,12 @@ Page({
weekHeaders: ['日', '一', '二', '三', '四', '五', '六'],
// 当前选中日期(用于月历联动)
dayInfo: {},
// 历史上的今天加载中
otdLoading: false,
// 标记是否正在程序触发 swiper 切换(避免 onDaySwiperChange 重复处理)
swiperAnimating: false,
// 标记是否需要在动画完成后滚动窗口
pendingShift: 0,
// 全屏弹窗
zoomVisible: false,
zoomTitle: '',
@@ -86,11 +102,13 @@ Page({
key: fmt(y, m, d),
dayInfo, almanac, legalHoliday, workday, termTime,
isFirstOrFifteen, nextBuddhist, upcoming,
showBuddhist: this.data.showBuddhist
showBuddhist: this.data.showBuddhist,
otd: null, // 历史上的今天数据(异步加载)
otdTab: 'event' // event:大事记 birth:出生
};
},
/** 构建5天的数据,居中显示 */
/** 构建7天的数据,居中显示 */
buildDayList(centerDate) {
const dayList = [];
for (let i = -DAY_SPAN; i <= DAY_SPAN; i++) {
@@ -105,6 +123,7 @@ Page({
currentYear: center.dayInfo.solarYear,
currentMonth: center.dayInfo.solarMonth
});
this.ensureOtd(DAY_SPAN);
},
/** 设置变化后刷新 dayList */
@@ -121,7 +140,7 @@ Page({
/** 日视图 swiper 切换 */
onDaySwiperChange(e) {
const index = e.detail.current;
const { dayList } = this.data;
const { dayList, swiperAnimating } = this.data;
const cur = dayList[index];
this.setData({
dayIndex: index,
@@ -129,44 +148,164 @@ Page({
currentYear: cur.dayInfo.solarYear,
currentMonth: cur.dayInfo.solarMonth
});
this.ensureOtd(index);
// 边缘时向对应方向补一天,保持可无限滑动
// 程序触发切换时不处理边缘滚动(避免打断动画)
if (swiperAnimating) {
this.setData({ swiperAnimating: false });
return;
}
// 用户手动滑动到边缘时,标记待滚动方向,等动画完成后再滚动
if (index === 0) {
this.prependDay();
this.setData({ pendingShift: -1 });
} else if (index === dayList.length - 1) {
this.appendDay();
this.setData({ pendingShift: 1 });
}
},
prependDay() {
const { dayList } = this.data;
const first = dayList[0];
const date = new Date(first.dayInfo.solarYear, first.dayInfo.solarMonth - 1, first.dayInfo.solarDay - 1);
dayList.unshift(this.buildDayData(date));
if (dayList.length > 7) dayList.pop();
this.setData({ dayList, dayIndex: this.data.dayIndex + 1 });
/** 日视图 swiper 动画完成 */
onDaySwiperFinish() {
const { pendingShift } = this.data;
if (pendingShift !== 0) {
this.setData({ pendingShift: 0 });
this.shiftDayWindow(pendingShift);
}
},
appendDay() {
/** 滚动日数据窗口:direction -1=向前,1=向后 */
shiftDayWindow(direction) {
const { dayList } = this.data;
const last = dayList[dayList.length - 1];
const date = new Date(last.dayInfo.solarYear, last.dayInfo.solarMonth - 1, last.dayInfo.solarDay + 1);
dayList.push(this.buildDayData(date));
if (dayList.length > 7) dayList.shift();
this.setData({ dayList, dayIndex: this.data.dayIndex - 1 });
const edge = direction < 0 ? dayList[0] : dayList[dayList.length - 1];
const offset = direction < 0 ? -1 : 1;
const newDate = new Date(edge.dayInfo.solarYear, edge.dayInfo.solarMonth - 1, edge.dayInfo.solarDay + offset);
const newDay = this.buildDayData(newDate);
let newList, newIndex;
if (direction < 0) {
// 向前滚动:新数据加到前面,当前索引+1(因为前面多了一天)
newList = [newDay, ...dayList.slice(0, -1)];
newIndex = this.data.dayIndex + 1;
} else {
// 向后滚动:新数据加到后面,当前索引-1(因为前面少了一天)
newList = [...dayList.slice(1), newDay];
newIndex = this.data.dayIndex - 1;
}
// 滚动窗口后,current 保持连续,swiper 会平滑滑动到新位置
this.setData({
dayList: newList,
dayIndex: newIndex,
dayInfo: newList[newIndex].dayInfo,
currentYear: newList[newIndex].dayInfo.solarYear,
currentMonth: newList[newIndex].dayInfo.solarMonth
});
this.ensureOtd(newIndex);
},
prevDay() {
if (this.data.dayIndex > 0) {
this.setData({ dayIndex: this.data.dayIndex - 1 });
this.updateCurrentDayInfo();
const { dayIndex, dayList } = this.data;
if (dayIndex > 0) {
// 不在边缘,正常切换
const index = dayIndex - 1;
this.setData({ dayIndex: index, swiperAnimating: true });
this.ensureOtd(index);
} else {
// 在边缘,滚动窗口(窗口滚动后 dayIndex 会调整为 1swiper 从 0 滑到 1,有动画)
this.shiftDayWindow(-1);
}
},
nextDay() {
if (this.data.dayIndex < this.data.dayList.length - 1) {
this.setData({ dayIndex: this.data.dayIndex + 1 });
this.updateCurrentDayInfo();
const { dayIndex, dayList } = this.data;
if (dayIndex < dayList.length - 1) {
// 不在边缘,正常切换
const index = dayIndex + 1;
this.setData({ dayIndex: index, swiperAnimating: true });
this.ensureOtd(index);
} else {
// 在边缘,滚动窗口(窗口滚动后 dayIndex 会调整为 5swiper 从 6 滑到 5,有动画)
this.shiftDayWindow(1);
}
},
/** ===== 历史上的今天(数据来源:中文维基百科) ===== */
/** 确保指定 index 的日期已加载 OTD 数据(优先读缓存) */
ensureOtd(index) {
const item = this.data.dayList[index];
if (!item || item.otd || item.otdLoading) return;
const m = item.dayInfo.solarMonth;
const d = item.dayInfo.solarDay;
const cacheKey = `${m}-${d}`;
// 读本地缓存(按月日维度,48 小时有效)
try {
const cache = wx.getStorageSync(OTD_CACHE_KEY) || {};
const hit = cache[cacheKey];
if (hit && hit.data && Date.now() - hit.ts < OTD_CACHE_TTL) {
this.applyOtd(index, item.key, hit.data);
return;
}
} catch (e) { /* 缓存不可用时忽略 */ }
this.setData({ [`dayList[${index}].otdLoading`]: true });
request({ url: `/api/wiki/on-this-day?month=${m}&day=${d}` })
.then(res => {
if (res && res.code === 0 && res.data) {
this.applyOtd(index, item.key, res.data);
this.saveOtdCache(cacheKey, res.data);
}
})
.catch(() => { /* 网络失败时不显示卡片 */ })
.then(() => {
// 窗口可能已滚动,先确认 index 处仍是同一天
const cur = this.data.dayList[index];
if (cur && cur.key === item.key) {
this.setData({ [`dayList[${index}].otdLoading`]: false });
}
});
},
/** 将 OTD 数据写入指定日期(异步返回时窗口可能已滚动,需校验 key) */
applyOtd(index, key, data) {
const cur = this.data.dayList[index];
if (!cur || cur.key !== key) return;
const withYear = list => (list || []).map(it => ({ ...it, yearText: fmtYear(it.year) }));
const events = withYear(data.events);
const births = withYear(data.births);
const deaths = withYear(data.deaths);
const festivals = withYear(data.festivals);
if (!events.length && !births.length) return; // 无数据不显示卡片
this.setData({
[`dayList[${index}].otd`]: {
events, births, deaths, festivals,
eventsPreview: events.slice(0, 4),
birthsPreview: births.slice(0, 4)
}
});
},
/** 写入本地缓存(最多保留 80 天,超出淘汰最早的) */
saveOtdCache(cacheKey, data) {
try {
const cache = wx.getStorageSync(OTD_CACHE_KEY) || {};
cache[cacheKey] = { ts: Date.now(), data };
const keys = Object.keys(cache);
if (keys.length > 80) delete cache[keys[0]];
wx.setStorageSync(OTD_CACHE_KEY, cache);
} catch (e) { /* 存储失败时忽略 */ }
},
/** 卡片内切换 大事记/出生 */
switchOtdTab(e) {
const { date, tab } = e.currentTarget.dataset;
const index = this.data.dayList.findIndex(item => item.key === date);
if (index >= 0) {
this.setData({ [`dayList[${index}].otdTab`]: tab });
}
},
@@ -291,6 +430,12 @@ Page({
} else if (type === 'fest') {
zoomTitle = '近期节日';
zoomData = { upcoming: dayItem.upcoming };
} else if (type === 'fest-year') {
zoomTitle = `${dayInfo.solarYear}年节日`;
zoomData = { yearFestivals: getYearFestivals(dayInfo.solarYear) };
} else if (type === 'otd') {
zoomTitle = `${dayInfo.solarMonth}${dayInfo.solarDay}日 历史上的今天`;
zoomData = dayItem.otd;
}
this.setData({ zoomVisible: true, zoomTitle, zoomType: type, zoomData });
},
@@ -303,5 +448,20 @@ Page({
wx.navigateTo({ url: e.currentTarget.dataset.url });
},
noop() {}
noop() {},
// 分享给好友
onShareAppMessage() {
return {
title: '老黄历 - 传统农历黄历查询',
path: '/pages/home/home'
};
},
// 分享到朋友圈
onShareTimeline() {
return {
title: '老黄历 - 传统农历黄历查询'
};
}
});