Files
wxserver/pages/add-anniversary/add-anniversary.js
T
yuming ddcfe3334e
部署到群晖 / deploy (push) Successful in 44s
v2.1.0 流程改造 + 农历准确性修复 + 双向同步 + 闰月支持
- Phase 1: 添加纪念日合并人物创建流程(方案 B)
- Phase 2: 农历提醒按 lunarMonth/Day 计算每年公历
- Phase 3: 人员数据同步到后端(新增 /api/person)
- Phase 4: 新设备启动从云端恢复数据
- Phase 5: 工具函数收敛 utils/format.js
- Phase 6: 同步失败入队 + 启动重试
- Phase 7: 闰月生日完整支持(含 isLeapMonth + UI 警示)
- 修复 lunarInfo 数据表错位(替换为权威源 jjonline/calendar.js)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 05:51:17 +08:00

401 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// add-anniversary.js
const storage = require('../../utils/storage')
const dateUtils = require('../../utils/date')
const sync = require('../../utils/sync')
const lunar = require('../../utils/lunar')
Page({
data: {
anniversaryId: null,
personId: null,
personList: [],
inputName: '',
typeList: ['公历生日', '农历生日', '结婚纪念日', '订婚纪念日', '其他纪念日'],
typeIndex: 0,
showCustomType: false,
dateValue: '',
remindDaysList: ['提前3天', '提前7天', '提前14天', '提前30天', '自定义'],
remindDaysIndex: 0,
// UI 反馈:选了农历日期时展示对应农历文本 + 闰月警示
lunarText: '',
isLeapMonth: false,
formData: {
isLunar: false,
type: 'birthday',
customTypeName: '',
solarYear: '',
solarMonth: '',
solarDay: '',
lunarYear: '',
lunarMonth: '',
lunarDay: '',
isLeapMonth: false,
importance: 'low',
remindEnabled: true,
remindDays: 7,
remark: ''
}
},
onLoad(options) {
// 获取人员列表用于快捷选择
const persons = storage.getPersons()
this.setData({ personList: persons })
if (options.personId) {
// 从人员详情页进入,预选关联人员
const person = persons.find(p => p.id === options.personId)
if (person) {
this.setData({
personId: person.id,
inputName: person.name
})
}
}
if (options.id) {
// 编辑模式
this.setData({ anniversaryId: options.id })
this.loadAnniversary(options.id)
} else {
// 设置默认日期为今天
const today = new Date()
this.setData({
dateValue: dateUtils.formatDate(today, 'YYYY-MM-DD')
})
}
},
/**
* 加载纪念日信息(编辑模式)
*/
loadAnniversary(id) {
const anniversaries = storage.getAnniversaries()
const anniversary = anniversaries.find(a => a.id === id)
if (anniversary) {
const date = dateUtils.formatDate(
new Date(anniversary.solarYear, anniversary.solarMonth - 1, anniversary.solarDay),
'YYYY-MM-DD'
)
const typeIndex = this.getTypeIndex(anniversary.type)
// 编辑时不允许改关联人员,姓名展示用 personName 或从 personList 查
const person = this.data.personList.find(p => p.id === anniversary.personId)
this.setData({
formData: anniversary,
dateValue: date,
typeIndex,
showCustomType: anniversary.type === 'other',
personId: anniversary.personId,
inputName: person ? person.name : (anniversary.personName || '')
})
// 农历日期:编辑时也要回显
if (anniversary.isLunar) this._refreshLunar()
wx.setNavigationBarTitle({ title: '编辑纪念日' })
}
},
/**
* 获取类型索引
*/
getTypeIndex(type) {
const indexMap = {
birthday: 0,
lunar_birthday: 1,
wedding: 2,
engagement: 3,
other: 4
}
return indexMap[type] || 0
},
/**
* 姓名输入:用户打字时实时更新;点 chip 时也会触发
* 输入框值变了就清掉已绑定的 personId,提交时再按姓名查找/创建
*/
onNameInput(e) {
const name = e.detail.value
const matched = this.data.personList.find(p => p.name === name.trim())
this.setData({
inputName: name,
personId: matched ? matched.id : null
})
},
/**
* 点已有人员快捷 chip
*/
onPickPerson(e) {
const { id, name } = e.currentTarget.dataset
this.setData({ personId: id, inputName: name })
},
/**
* 选择类型
*/
onTypeChange(e) {
const index = parseInt(e.detail.value)
const types = ['birthday', 'lunar_birthday', 'wedding', 'engagement', 'other']
const isOther = index === 4
this.setData({
typeIndex: index,
showCustomType: isOther,
'formData.type': types[index]
})
},
/**
* 自定义类型输入
*/
onCustomTypeInput(e) {
this.setData({
'formData.customTypeName': e.detail.value
})
},
/**
* 日期类型改变
*/
onDateTypeChange(e) {
const isLunar = e.detail.value === 'lunar'
this.setData({ 'formData.isLunar': isLunar })
this._refreshLunar()
},
/**
* 日期改变
*/
onDateChange(e) {
const dateStr = e.detail.value
const parts = dateStr.split('-')
this.setData({
dateValue: dateStr,
'formData.solarYear': parseInt(parts[0]),
'formData.solarMonth': parseInt(parts[1]),
'formData.solarDay': parseInt(parts[2])
})
this._refreshLunar()
},
/**
* 选了"农历"时,把当前公历日期反算成农历,写入 formData + 提示文案
* Why:闰月生日必须在 UI 上让用户确认这是不是他想要的农历日期
*/
_refreshLunar() {
const { formData } = this.data
if (!formData.isLunar || !formData.solarYear || !formData.solarMonth || !formData.solarDay) {
this.setData({ lunarText: '', isLeapMonth: false, 'formData.isLeapMonth': false })
return
}
const solarDate = new Date(formData.solarYear, formData.solarMonth - 1, formData.solarDay)
const ld = lunar.solarToLunar(solarDate)
this.setData({
lunarText: ld.lunarText,
isLeapMonth: ld.isLeap,
'formData.lunarYear': ld.year,
'formData.lunarMonth': ld.month,
'formData.lunarDay': ld.day,
'formData.isLeapMonth': ld.isLeap
})
},
/**
* 重要程度改变
*/
onImportanceChange(e) {
this.setData({
'formData.importance': e.detail.value
})
},
/**
* 提醒开关改变
*/
onRemindEnabledChange(e) {
this.setData({
'formData.remindEnabled': e.detail.value
})
},
/**
* 提醒天数改变
*/
onRemindDaysChange(e) {
const index = parseInt(e.detail.value)
this.setData({ remindDaysIndex: index })
if (index === 4) {
// 自定义天数
wx.showModal({
title: '自定义提前天数',
editable: true,
placeholderText: '请输入天数(1-365',
success: (res) => {
if (res.confirm) {
const days = parseInt(res.content)
if (!days || days < 1 || days > 365) {
wx.showToast({ title: '请输入1-365之间的天数', icon: 'none' })
this.setData({ remindDaysIndex: 1, 'formData.remindDays': 7 })
return
}
this.setData({ 'formData.remindDays': days })
} else {
// 取消则回到默认7天
this.setData({ remindDaysIndex: 1, 'formData.remindDays': 7 })
}
}
})
} else {
const days = [3, 7, 14, 30][index]
this.setData({ 'formData.remindDays': days })
}
},
/**
* 备注改变
*/
onRemarkChange(e) {
this.setData({
'formData.remark': e.detail.value
})
},
/**
* 取消
*/
onCancel() {
wx.navigateBack()
},
/**
* 提交
*/
async onSubmit() {
const { formData, anniversaryId, inputName } = this.data
let { personId } = this.data
// 验证姓名
const name = (inputName || '').trim()
if (!name) {
wx.showToast({ title: '请输入姓名', icon: 'none' })
return
}
// 验证日期
if (!formData.solarYear || !formData.solarMonth || !formData.solarDay) {
wx.showToast({ title: '请选择日期', icon: 'none' })
return
}
// 验证自定义类型
if (formData.type === 'other' && !formData.customTypeName) {
wx.showToast({ title: '请输入自定义类型', icon: 'none' })
return
}
// 如果开启了提醒,请求订阅消息授权
if (formData.remindEnabled) {
try {
await this.requestSubscribe()
} catch (err) {
console.log('用户拒绝订阅消息')
}
}
// 新增模式且没绑定 personId → 按姓名查找或自动创建
if (!anniversaryId && !personId) {
const person = storage.ensurePerson(name)
if (!person) {
wx.showToast({ title: '保存失败', icon: 'none' })
return
}
personId = person.id
}
// 农历生日:兜底反算(onDateChange/_refreshLunar 通常已填好,这里防边界)
if (formData.isLunar) {
const solarDate = new Date(formData.solarYear, formData.solarMonth - 1, formData.solarDay)
const lunarDate = lunar.solarToLunar(solarDate)
formData.lunarYear = lunarDate.year
formData.lunarMonth = lunarDate.month
formData.lunarDay = lunarDate.day
formData.isLeapMonth = lunarDate.isLeap
}
const personName = name
if (anniversaryId) {
// 编辑模式
const success = storage.updateAnniversary(anniversaryId, {
personId,
personName,
...formData
})
if (success) {
// 同步到云端
this.syncToCloud(anniversaryId, {
personId,
personName,
...formData
}, 'update')
wx.showToast({ title: '保存成功', icon: 'success' })
setTimeout(() => wx.navigateBack(), 1500)
}
} else {
// 新增模式
const newAnniversary = storage.addAnniversary({
personId,
personName,
...formData
})
if (newAnniversary) {
// 同步到云端
this.syncToCloud(newAnniversary.id, newAnniversary, 'add')
wx.showToast({ title: '添加成功', icon: 'success' })
setTimeout(() => wx.navigateBack(), 1500)
}
}
},
/**
* 请求订阅消息授权
*/
requestSubscribe() {
return new Promise((resolve, reject) => {
wx.requestSubscribeMessage({
tmplIds: ['6J7Stt-lu7DKU6jblJ0nZGq_D81z5glnksf7qWfy5Yw'],
success: (res) => {
console.log('订阅消息授权结果:', res)
resolve(res)
},
fail: (err) => {
console.error('订阅消息授权失败:', err)
reject(err)
}
})
})
},
/**
* 同步纪念日到后端(失败自动入队,启动时 flush)
*/
syncToCloud(id, data, action) {
const openid = wx.getStorageSync('openid')
if (!openid) {
console.log('未获取到openid,跳过云端同步')
return
}
sync.syncOrEnqueue({ kind: 'anniversary', action, data: { id, ...data } })
}
})