- 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>
This commit is contained in:
@@ -1,21 +1,24 @@
|
||||
// add-anniversary.js
|
||||
const storage = require('../../utils/storage')
|
||||
const dateUtils = require('../../utils/date')
|
||||
const api = require('../../utils/api')
|
||||
const sync = require('../../utils/sync')
|
||||
const lunar = require('../../utils/lunar')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
anniversaryId: null,
|
||||
personId: null,
|
||||
personList: [],
|
||||
personIndex: 0,
|
||||
selectedPerson: '',
|
||||
inputName: '',
|
||||
typeList: ['公历生日', '农历生日', '结婚纪念日', '订婚纪念日', '其他纪念日'],
|
||||
typeIndex: 0,
|
||||
showCustomType: false,
|
||||
dateValue: '',
|
||||
remindDaysList: ['提前3天', '提前7天', '提前14天', '提前30天', '自定义'],
|
||||
remindDaysIndex: 0,
|
||||
// UI 反馈:选了农历日期时展示对应农历文本 + 闰月警示
|
||||
lunarText: '',
|
||||
isLeapMonth: false,
|
||||
formData: {
|
||||
isLunar: false,
|
||||
type: 'birthday',
|
||||
@@ -26,6 +29,7 @@ Page({
|
||||
lunarYear: '',
|
||||
lunarMonth: '',
|
||||
lunarDay: '',
|
||||
isLeapMonth: false,
|
||||
importance: 'low',
|
||||
remindEnabled: true,
|
||||
remindDays: 7,
|
||||
@@ -34,18 +38,17 @@ Page({
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
// 获取人员列表
|
||||
// 获取人员列表用于快捷选择
|
||||
const persons = storage.getPersons()
|
||||
this.setData({ personList: persons })
|
||||
|
||||
if (options.personId) {
|
||||
// 从人员详情页进入
|
||||
const index = persons.findIndex(p => p.id === options.personId)
|
||||
if (index !== -1) {
|
||||
this.setData({
|
||||
personIndex: index,
|
||||
personId: options.personId,
|
||||
selectedPerson: persons[index].name
|
||||
// 从人员详情页进入,预选关联人员
|
||||
const person = persons.find(p => p.id === options.personId)
|
||||
if (person) {
|
||||
this.setData({
|
||||
personId: person.id,
|
||||
inputName: person.name
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -75,17 +78,23 @@ Page({
|
||||
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'
|
||||
showCustomType: anniversary.type === 'other',
|
||||
personId: anniversary.personId,
|
||||
inputName: person ? person.name : (anniversary.personName || '')
|
||||
})
|
||||
|
||||
|
||||
// 农历日期:编辑时也要回显
|
||||
if (anniversary.isLunar) this._refreshLunar()
|
||||
|
||||
wx.setNavigationBarTitle({ title: '编辑纪念日' })
|
||||
}
|
||||
},
|
||||
@@ -105,18 +114,26 @@ Page({
|
||||
},
|
||||
|
||||
/**
|
||||
* 选择人员
|
||||
* 姓名输入:用户打字时实时更新;点 chip 时也会触发
|
||||
* 输入框值变了就清掉已绑定的 personId,提交时再按姓名查找/创建
|
||||
*/
|
||||
onPersonChange(e) {
|
||||
const index = parseInt(e.detail.value)
|
||||
const person = this.data.personList[index]
|
||||
onNameInput(e) {
|
||||
const name = e.detail.value
|
||||
const matched = this.data.personList.find(p => p.name === name.trim())
|
||||
this.setData({
|
||||
personIndex: index,
|
||||
personId: person.id,
|
||||
selectedPerson: person.name
|
||||
inputName: name,
|
||||
personId: matched ? matched.id : null
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 点已有人员快捷 chip
|
||||
*/
|
||||
onPickPerson(e) {
|
||||
const { id, name } = e.currentTarget.dataset
|
||||
this.setData({ personId: id, inputName: name })
|
||||
},
|
||||
|
||||
/**
|
||||
* 选择类型
|
||||
*/
|
||||
@@ -146,9 +163,8 @@ Page({
|
||||
*/
|
||||
onDateTypeChange(e) {
|
||||
const isLunar = e.detail.value === 'lunar'
|
||||
this.setData({
|
||||
'formData.isLunar': isLunar
|
||||
})
|
||||
this.setData({ 'formData.isLunar': isLunar })
|
||||
this._refreshLunar()
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -157,13 +173,36 @@ Page({
|
||||
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
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -237,11 +276,13 @@ Page({
|
||||
* 提交
|
||||
*/
|
||||
async onSubmit() {
|
||||
const { formData, personId, anniversaryId, personList } = this.data
|
||||
const { formData, anniversaryId, inputName } = this.data
|
||||
let { personId } = this.data
|
||||
|
||||
// 验证关联人员
|
||||
if (!personId) {
|
||||
wx.showToast({ title: '请选择关联人员', icon: 'none' })
|
||||
// 验证姓名
|
||||
const name = (inputName || '').trim()
|
||||
if (!name) {
|
||||
wx.showToast({ title: '请输入姓名', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
@@ -263,13 +304,30 @@ Page({
|
||||
await this.requestSubscribe()
|
||||
} catch (err) {
|
||||
console.log('用户拒绝订阅消息')
|
||||
// 继续保存,即使用户拒绝订阅
|
||||
}
|
||||
}
|
||||
|
||||
// 获取人员名称
|
||||
const person = personList.find(p => p.id === personId)
|
||||
const personName = person ? person.name : ''
|
||||
// 新增模式且没绑定 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) {
|
||||
// 编辑模式
|
||||
@@ -328,21 +386,15 @@ Page({
|
||||
},
|
||||
|
||||
/**
|
||||
* 同步到自建后端
|
||||
* 同步纪念日到后端(失败自动入队,启动时 flush)
|
||||
*/
|
||||
async syncToCloud(id, data, action) {
|
||||
try {
|
||||
const openid = wx.getStorageSync('openid')
|
||||
if (!openid) {
|
||||
console.log('未获取到openid,跳过云端同步')
|
||||
return
|
||||
}
|
||||
const res = await api.anniversary(action, { id, ...data })
|
||||
console.log('云端同步成功:', res)
|
||||
} catch (err) {
|
||||
console.error('云端同步失败:', err)
|
||||
// 不影响本地保存
|
||||
syncToCloud(id, data, action) {
|
||||
const openid = wx.getStorageSync('openid')
|
||||
if (!openid) {
|
||||
console.log('未获取到openid,跳过云端同步')
|
||||
return
|
||||
}
|
||||
sync.syncOrEnqueue({ kind: 'anniversary', action, data: { id, ...data } })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
<!--add-anniversary.wxml-->
|
||||
<view class="container">
|
||||
<view class="form">
|
||||
<!-- 关联人员 -->
|
||||
<!-- 关联人员(姓名输入 + 已有人员快捷选择) -->
|
||||
<view class="form-item">
|
||||
<text class="label required">关联人员</text>
|
||||
<picker mode="selector" range="{{personList}}" range-key="name" value="{{personIndex}}" bindchange="onPersonChange">
|
||||
<view class="picker">
|
||||
<text wx:if="{{selectedPerson}}" class="picker-text">{{selectedPerson}}</text>
|
||||
<text wx:else class="picker-placeholder">请选择关联人员</text>
|
||||
<text class="picker-arrow">›</text>
|
||||
</view>
|
||||
</picker>
|
||||
<text class="label required">为谁记录</text>
|
||||
<input class="input" placeholder="输入姓名,例如:妈妈" value="{{inputName}}" bindinput="onNameInput" disabled="{{!!anniversaryId}}" />
|
||||
<view wx:if="{{personList.length > 0 && !anniversaryId}}" class="chips">
|
||||
<view
|
||||
wx:for="{{personList}}"
|
||||
wx:key="id"
|
||||
class="chip {{item.id === personId ? 'chip-active' : ''}}"
|
||||
data-id="{{item.id}}"
|
||||
data-name="{{item.name}}"
|
||||
bindtap="onPickPerson"
|
||||
>{{item.name}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 纪念日类型 -->
|
||||
@@ -49,6 +53,14 @@
|
||||
<text class="picker-arrow">›</text>
|
||||
</view>
|
||||
</picker>
|
||||
<!-- 选了农历后展示对应农历日期,闰月给醒目提示 -->
|
||||
<view wx:if="{{formData.isLunar && lunarText}}" class="lunar-hint {{isLeapMonth ? 'lunar-hint-warn' : ''}}">
|
||||
<text>对应农历:{{lunarText}}</text>
|
||||
<text wx:if="{{isLeapMonth}}" class="leap-tag">⚠ 闰月</text>
|
||||
</view>
|
||||
<view wx:if="{{formData.isLunar && isLeapMonth}}" class="lunar-warn-detail">
|
||||
该日期属于闰月。无闰月的年份将按对应普通月份提醒(例如闰二月初一 → 二月初一)。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 重要程度 -->
|
||||
|
||||
@@ -59,13 +59,72 @@
|
||||
}
|
||||
|
||||
.input {
|
||||
margin-top: 16rpx;
|
||||
margin-top: 0;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 24rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.input[disabled] {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.chip {
|
||||
font-size: 24rpx;
|
||||
color: #555;
|
||||
padding: 10rpx 22rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 100rpx;
|
||||
border: 2rpx solid transparent;
|
||||
}
|
||||
|
||||
.chip-active {
|
||||
color: #6366f1;
|
||||
background-color: #eef0ff;
|
||||
border-color: #6366f1;
|
||||
}
|
||||
|
||||
.lunar-hint {
|
||||
margin-top: 16rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.lunar-hint-warn {
|
||||
color: #d97706;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.leap-tag {
|
||||
font-size: 22rpx;
|
||||
background-color: #fef3c7;
|
||||
color: #b45309;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
|
||||
.lunar-warn-detail {
|
||||
margin-top: 12rpx;
|
||||
padding: 14rpx 20rpx;
|
||||
background-color: #fffbeb;
|
||||
border-left: 4rpx solid #d97706;
|
||||
border-radius: 4rpx;
|
||||
font-size: 24rpx;
|
||||
color: #92400e;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user