Initial Commit

This commit is contained in:
yuming
2025-10-26 19:29:30 +08:00
commit 6747ade9c4
33 changed files with 4387 additions and 0 deletions
+234
View File
@@ -0,0 +1,234 @@
// calendar.js
const storage = require('../../utils/storage')
const dateUtils = require('../../utils/date')
Page({
data: {
currentYear: 2024,
currentMonth: 1,
calendarDays: [],
monthEvents: []
},
onLoad() {
const today = new Date()
this.setData({
currentYear: today.getFullYear(),
currentMonth: today.getMonth() + 1
})
this.renderCalendar()
this.loadMonthEvents()
},
/**
* 渲染日历
*/
renderCalendar() {
const { currentYear, currentMonth } = this.data
const anniversaries = storage.getAnniversaries()
// 获取本月第一天是星期几
const firstDay = new Date(currentYear, currentMonth - 1, 1)
const startWeekday = firstDay.getDay()
// 获取本月天数
const daysInMonth = new Date(currentYear, currentMonth, 0).getDate()
// 获取上个月的天数
const prevMonthDays = new Date(currentYear, currentMonth - 1, 0).getDate()
// 构建日历天数数组
const calendarDays = []
const today = new Date()
// 填充上个月的日期
for (let i = startWeekday - 1; i >= 0; i--) {
const day = prevMonthDays - i
calendarDays.push({
day,
date: new Date(currentYear, currentMonth - 2, day),
isToday: false,
isOtherMonth: true,
events: [],
hasMore: false
})
}
// 填充本月的日期
for (let day = 1; day <= daysInMonth; day++) {
const date = new Date(currentYear, currentMonth - 1, day)
const isToday = dateUtils.isToday(date)
// 查找该日期的纪念日
const events = anniversaries
.filter(a => this.isAnniversaryOnDate(a, date))
.map(a => ({
id: a.id,
color: this.getEventColor(a)
}))
.slice(0, 3)
calendarDays.push({
day,
date,
isToday,
isOtherMonth: false,
events,
hasMore: events.length > 2,
moreCount: anniversaries.filter(a => this.isAnniversaryOnDate(a, date)).length - 2
})
}
// 填充下个月的日期(补全6行)
const remainingDays = 42 - calendarDays.length
for (let day = 1; day <= remainingDays; day++) {
calendarDays.push({
day,
date: new Date(currentYear, currentMonth, day),
isToday: false,
isOtherMonth: true,
events: [],
hasMore: false
})
}
this.setData({ calendarDays })
},
/**
* 判断纪念日是否在某日期
*/
isAnniversaryOnDate(anniversary, date) {
return anniversary.solarYear === date.getFullYear() &&
anniversary.solarMonth === date.getMonth() + 1 &&
anniversary.solarDay === date.getDate()
},
/**
* 获取事件颜色
*/
getEventColor(anniversary) {
const colors = {
high: '#ff5722',
medium: '#ff9800',
low: '#07c160'
}
return colors[anniversary.importance] || '#07c160'
},
/**
* 加载本月事件
*/
loadMonthEvents() {
const { currentYear, currentMonth } = this.data
const persons = storage.getPersons()
const anniversaries = storage.getAnniversaries()
// 筛选本月纪念日
const monthEvents = anniversaries
.filter(a => a.solarYear === currentYear && a.solarMonth === currentMonth)
.map(a => {
const person = persons.find(p => p.id === a.personId)
const date = new Date(currentYear, currentMonth - 1, a.solarDay)
const daysUntil = dateUtils.getDaysUntil(date)
return {
...a,
personName: person ? person.name : '未知',
typeIcon: this.getTypeIcon(a.type),
typeName: a.customTypeName || this.getTypeName(a.type),
dateText: dateUtils.formatDate(date, 'MM月DD日'),
daysUntil
}
})
.sort((a, b) => a.solarDay - b.solarDay)
this.setData({ monthEvents })
},
/**
* 获取类型图标
*/
getTypeIcon(type) {
const icons = {
birthday: '🎂',
lunar_birthday: '🌙',
wedding: '💍',
engagement: '💕',
other: '📅'
}
return icons[type] || '📅'
},
/**
* 获取类型名称
*/
getTypeName(type) {
const names = {
birthday: '公历生日',
lunar_birthday: '农历生日',
wedding: '结婚纪念日',
engagement: '订婚纪念日',
other: '其他纪念日'
}
return names[type] || '其他'
},
/**
* 上一个月
*/
onPrevMonth() {
let { currentYear, currentMonth } = this.data
if (currentMonth === 1) {
currentYear--
currentMonth = 12
} else {
currentMonth--
}
this.setData({ currentYear, currentMonth })
this.renderCalendar()
this.loadMonthEvents()
},
/**
* 下一个月
*/
onNextMonth() {
let { currentYear, currentMonth } = this.data
if (currentMonth === 12) {
currentYear++
currentMonth = 1
} else {
currentMonth++
}
this.setData({ currentYear, currentMonth })
this.renderCalendar()
this.loadMonthEvents()
},
/**
* 回到今天
*/
onGoToday() {
const today = new Date()
this.setData({
currentYear: today.getFullYear(),
currentMonth: today.getMonth() + 1
})
this.renderCalendar()
this.loadMonthEvents()
},
/**
* 点击事件
*/
onEventTap(e) {
const id = e.currentTarget.dataset.id
const anniversary = storage.getAnniversaries().find(a => a.id === id)
if (anniversary) {
wx.navigateTo({
url: `/pages/person-detail/person-detail?id=${anniversary.personId}`
})
}
}
})
+52
View File
@@ -0,0 +1,52 @@
<!--calendar.wxml-->
<view class="container">
<!-- 日历头部 -->
<view class="calendar-header">
<view class="month-navigation">
<text class="nav-btn" bindtap="onPrevMonth"></text>
<text class="month-title">{{currentYear}}年{{currentMonth}}月</text>
<text class="nav-btn" bindtap="onNextMonth"></text>
</view>
<button class="today-btn" bindtap="onGoToday">今天</button>
</view>
<!-- 星期标题 -->
<view class="week-header">
<view wx:for="{{['日', '一', '二', '三', '四', '五', '六']}}" wx:key="*this" class="week-title">{{item}}</view>
</view>
<!-- 日历内容 -->
<scroll-view class="calendar-content" scroll-y>
<view class="calendar-grid">
<view wx:for="{{calendarDays}}" wx:key="index" class="calendar-cell {{item.isToday ? 'today' : ''}} {{item.isOtherMonth ? 'other-month' : ''}}">
<text class="date-num">{{item.day}}</text>
<view class="events">
<view wx:for="{{item.events}}" wx:key="id" wx:for-item="event" class="event-dot" style="background-color: {{event.color}};"></view>
</view>
<view wx:if="{{item.hasMore}}" class="more-text">+{{item.moreCount}}</view>
</view>
</view>
<!-- 列表视图 -->
<view class="events-list">
<view class="section-title">本月纪念日</view>
<view wx:if="{{monthEvents.length === 0}}" class="empty-state">
<text class="icon">📅</text>
<text class="text">本月没有纪念日</text>
</view>
<view wx:for="{{monthEvents}}" wx:key="id" class="event-card" bindtap="onEventTap" data-id="{{item.id}}">
<view class="event-header">
<text class="event-icon">{{item.typeIcon}}</text>
<text class="event-title">{{item.personName}} - {{item.typeName}}</text>
<text class="event-date">{{item.dateText}}</text>
</view>
<view class="event-info">
<text wx:if="{{item.isLunar}}" class="lunar-badge">农历</text>
<text wx:if="{{item.daysUntil === 0}}" class="status urgent">今天</text>
<text wx:elif="{{item.daysUntil === 1}}" class="status warning">明天</text>
</view>
</view>
</view>
</scroll-view>
</view>
+215
View File
@@ -0,0 +1,215 @@
/**calendar.wxss**/
.container {
min-height: 100vh;
background-color: #f5f5f5;
}
.calendar-header {
background-color: #fff;
padding: 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #f0f0f0;
}
.month-navigation {
display: flex;
align-items: center;
gap: 32rpx;
}
.nav-btn {
font-size: 48rpx;
color: #07c160;
font-weight: 300;
width: 60rpx;
text-align: center;
}
.month-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
min-width: 240rpx;
text-align: center;
}
.today-btn {
padding: 12rpx 32rpx;
background-color: #f5f5f5;
color: #666;
border-radius: 40rpx;
font-size: 24rpx;
border: none;
}
.today-btn::after {
border: none;
}
.week-header {
display: flex;
background-color: #fff;
border-bottom: 1px solid #f0f0f0;
}
.week-title {
flex: 1;
text-align: center;
padding: 20rpx 0;
font-size: 24rpx;
color: #999;
font-weight: 500;
}
.calendar-content {
height: calc(100vh - 200rpx);
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
background-color: #fff;
}
.calendar-cell {
min-height: 120rpx;
border: 1px solid #f0f0f0;
padding: 8rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.calendar-cell.other-month {
background-color: #fafafa;
}
.calendar-cell.today .date-num {
background-color: #07c160;
color: #fff;
border-radius: 50%;
width: 48rpx;
height: 48rpx;
line-height: 48rpx;
text-align: center;
}
.date-num {
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
}
.calendar-cell.other-month .date-num {
color: #ccc;
}
.events {
display: flex;
gap: 4rpx;
}
.event-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
}
.more-text {
font-size: 20rpx;
color: #999;
margin-top: 4rpx;
}
/* 事件列表 */
.events-list {
padding: 32rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 24rpx;
}
.empty-state {
text-align: center;
padding: 120rpx 40rpx;
}
.empty-state .icon {
font-size: 120rpx;
display: block;
margin-bottom: 32rpx;
}
.empty-state .text {
font-size: 28rpx;
color: #999;
}
.event-card {
background-color: #fff;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
}
.event-header {
display: flex;
align-items: center;
}
.event-icon {
font-size: 40rpx;
margin-right: 16rpx;
}
.event-title {
flex: 1;
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.event-date {
font-size: 24rpx;
color: #999;
}
.event-info {
display: flex;
align-items: center;
gap: 16rpx;
margin-top: 16rpx;
}
.lunar-badge {
padding: 4rpx 12rpx;
background-color: #e3f2fd;
color: #1976d2;
border-radius: 4rpx;
font-size: 20rpx;
}
.status {
font-size: 22rpx;
padding: 4rpx 12rpx;
border-radius: 4rpx;
}
.status.urgent {
background-color: #fff3f0;
color: #ff5722;
}
.status.warning {
background-color: #fff8e6;
color: #ff9800;
}