Files
wxserver/pages/index/index.js
T

251 lines
8.9 KiB
JavaScript

// index.js
const storage = require('../../utils/storage')
const dateUtils = require('../../utils/date')
const fmt = require('../../utils/format')
const api = require('../../utils/api')
const subscribe = require('../../utils/subscribe')
Page({
data: {
persons: [],
originalPersons: [],
searchKeyword: '',
currentFilter: 'all',
totalCount: 0,
upcomingCount: 0,
todayText: '',
atRiskText: ''
},
onLoad() {
this.loadPersons()
},
onShow() {
this.loadPersons()
this.refreshQuota()
// 提前查好订阅状态缓存到实例上:点击处理函数里不能再 await,否则手势上下文会丢
subscribe.getStatus().then(status => { this.subscribeStatus = status })
},
/**
* 加载人员列表
*/
loadPersons() {
const persons = storage.getPersons()
const anniversaries = storage.getAnniversaries()
const personsWithAnniversaries = persons.map(person => {
const personAnniversaries = anniversaries.filter(a => a.personId === person.id)
let nextAnniversary = null
if (personAnniversaries.length > 0) {
const upcoming = personAnniversaries
.map(a => {
// 农历纪念日的公历日期每年都不同,必须走 getNextOccurrenceOf 而不是直接用 solarMonth/solarDay
const { date, daysUntil } = dateUtils.getNextOccurrenceOf(a)
return { ...a, date, daysUntil }
})
.sort((a, b) => a.daysUntil - b.daysUntil)
if (upcoming.length > 0) {
const next = upcoming[0]
nextAnniversary = {
type: next.type,
typeName: fmt.getTypeName(next.type, next.customTypeName),
dateText: dateUtils.formatDate(next.date, 'MM月DD日'),
daysUntil: next.daysUntil,
daysUntilText: fmt.formatDaysUntil(next.daysUntil)
}
}
}
return { ...person, anniversaryCount: personAnniversaries.length, nextAnniversary }
})
const sorted = personsWithAnniversaries.sort((a, b) => {
if (!a.nextAnniversary && !b.nextAnniversary) return 0
if (!a.nextAnniversary) return 1
if (!b.nextAnniversary) return -1
return a.nextAnniversary.daysUntil - b.nextAnniversary.daysUntil
})
const today = new Date()
const weekdays = ['日', '一', '二', '三', '四', '五', '六']
const todayText = `${today.getMonth() + 1}${today.getDate()}日 周${weekdays[today.getDay()]}`
const upcomingCount = sorted.filter(p => p.nextAnniversary && p.nextAnniversary.daysUntil <= 7).length
this.setData({ originalPersons: sorted, totalCount: sorted.length, upcomingCount, todayText })
if (this.data.currentFilter === 'all' && !this.data.searchKeyword) {
this.setData({ persons: sorted })
} else {
this.filterPersons()
}
},
/**
* 搜索输入
*/
onSearchInput(e) {
const keyword = e.detail.value
this.setData({ searchKeyword: keyword })
this.filterPersons()
},
/**
* 筛选切换
*/
onFilterTap(e) {
const filter = e.currentTarget.dataset.filter
this.setData({ currentFilter: filter }, () => {
this.filterPersons()
})
},
/**
* 筛选人员
*/
filterPersons() {
const { originalPersons, searchKeyword, currentFilter } = this.data
const anniversaries = storage.getAnniversaries()
let filtered = [...originalPersons]
// 关键词搜索
if (searchKeyword) {
filtered = filtered.filter(p =>
p.name.includes(searchKeyword) ||
(p.nickname && p.nickname.includes(searchKeyword))
)
}
// 类型筛选
if (currentFilter !== 'all') {
filtered = filtered.filter(person => {
const personAnniversaries = anniversaries.filter(a => a.personId === person.id)
if (currentFilter === 'birthday') {
// 生日筛选:只显示有生日类型的人(公历生日或农历生日)
return personAnniversaries.some(a =>
a.type === 'birthday' || a.type === 'lunar_birthday'
)
} else if (currentFilter === 'anniversary') {
// 纪念日筛选:只显示有纪念日类型的人(结婚、订婚、其他)
return personAnniversaries.some(a =>
a.type === 'wedding' || a.type === 'engagement' || a.type === 'other'
)
} else if (currentFilter === 'upcoming') {
// 即将到来:只显示7天内有纪念日的人
return person.nextAnniversary && person.nextAnniversary.daysUntil <= 7
}
return true
})
}
// 排序:与 loadPersons() 保持一致,按最近的纪念日排序
const sorted = filtered.sort((a, b) => {
if (!a.nextAnniversary && !b.nextAnniversary) return 0
if (!a.nextAnniversary) return 1
if (!b.nextAnniversary) return -1
return a.nextAnniversary.daysUntil - b.nextAnniversary.daysUntil
})
this.setData({ persons: sorted })
},
/**
* 点击人员
*/
onPersonTap(e) {
const id = e.currentTarget.dataset.id
wx.navigateTo({
url: `/pages/person-detail/person-detail?id=${id}`
})
},
/**
* 拉取额度风险,拼成给用户看的文案
* 说「谁的提醒有风险」而不是「还剩几次额度」——用户理解不了额度这种抽象概念
* @returns {Promise<boolean>} 本次刷新是否成功拿到了最新数据。
* 失败时不清空/覆盖 atRiskText——网络抖动时清空反而会掩盖真实存在的风险,
* 比"文案没更新"更糟;调用方需要这个返回值来判断该给用户什么反馈(见 onTopUp)。
*/
async refreshQuota() {
try {
const res = await api.subscribe('get')
if (!res || !res.success) return false
const names = res.atRiskNames || []
let text = ''
if (names.length > 0 && names.length <= 3) {
text = names.join('、')
} else if (names.length > 3) {
text = names.slice(0, 3).join('、') + ' 等 ' + names.length + ' 人'
}
this.setData({ atRiskText: text })
return true
} catch (e) {
// 查询失败就不显示提示,静默处理,不打扰用户;atRiskText 保留旧值不清空(理由见上)
console.warn('[index] 额度查询失败', e)
return false
}
},
/**
* 点击风险提示卡片补额度
* 注意:wx.requestSubscribeMessage / wx.openSetting 必须在这里同步调用,
* 前面绝不能有 await,否则手势上下文丢失、调用必然失败
*/
onTopUp() {
const status = this.subscribeStatus
if (status === 'rejected' || status === 'mainSwitchOff') {
wx.openSetting({ withSubscriptions: true })
return
}
// 显式处理 status === undefined,不让它靠"没匹配上第一个 if"隐式落地:
// undefined 出现在 onShow 里 subscribe.getStatus() 还没返回、用户就点了卡片的情况;
// 这里没法现查状态兜底——getStatus() 是异步的,哪怕 await 一下也会丢失手势上下文,
// 所以有意让 undefined 和 unknown(查询失败)一样,都保守地按 willPrompt 处理,
// 与 utils/subscribe.js 里「unknown 按 willPrompt 保守处理」的取向一致。
// 代价:如果用户真实状态其实是 mainSwitchOff(通知总开关关了),这里不会拦截去设置页,
// 只会静默调用 wx.requestSubscribeMessage,微信不弹窗、直接判定未同意,
// 用户只会看到下面「未开启提醒」的兜底提示,不会被引导去设置页——
// 这是已知且可接受的体验降级,不在本次修复范围内。
const isConservativeFallback = status === undefined || status === 'unknown'
if (isConservativeFallback) {
// 不阻断流程,只留一条日志方便排查——命中这条分支时用户体验会比正常
// 情况弱一点(见上面的注释),出问题时能在控制台里定位到具体是哪种取舍
console.warn('[index] onTopUp 命中保守兜底分支,status =', status)
}
subscribe.requestAndReport().then(accepted => {
if (!accepted) {
wx.showToast({ title: '未开启提醒', icon: 'none' })
return
}
// 补额度这步已经成功(微信侧确实同意了),但下面刷新风险文案是另一次独立请求,
// 可能失败;两者不能混为一谈,否则会出现"已补充"toast 和纹丝不动的风险卡片
// 同时出现的自相矛盾画面。用刷新结果决定 toast 措辞,不对刷新结果做绝对断言。
this.refreshQuota().then(refreshed => {
if (refreshed) {
wx.showToast({ title: '已补充提醒次数', icon: 'success' })
} else {
wx.showToast({ title: '已补充,稍后自动更新', icon: 'none' })
}
})
})
},
/**
* 点击添加按钮:直接进添加纪念日(方案 B:流程合并,姓名不存在自动建人)
*/
onAddTap() {
wx.navigateTo({
url: '/pages/add-anniversary/add-anniversary'
})
}
})