- 数据: Habit/CheckIn/CheckList/CheckListItem/CheckListTemplate + 迁移 + 重新生成 client
- 后端: server/checkin/period.js 周期计算(日/周/月/年, 周一起始, Asia/Shanghai)
/api/checkin/* 打卡项CRUD/打卡/撤销/补打/某日详情/月历
/api/checklists/* 清单与检查项CRUD/勾选/全选/复制/模板(系统预置出差·回老家)
- 前端: 底部新增「打卡」Tab -> /checkin, 页内三视图
环形进度 RingProgress + 完成特效(粒子/光晕/触感, 可关闭)
打卡视图: 待办列表/乐观更新/撤销/新建编辑
日历视图: 月历网格 + 今天·昨天·明天 + 日期标记/完成率
清单视图: 场景清单/勾选进度条/模板生成/另存模板/归档
- 文档: docs/V0.2/需求文档.md 补充实现约定(附录B)与接口清单
89 lines
2.6 KiB
JavaScript
89 lines
2.6 KiB
JavaScript
// 前端日期/周期工具: 与 server/checkin/period.js 保持同一套口径 (Asia/Shanghai)
|
|
export const TZ = 'Asia/Shanghai'
|
|
|
|
const FMT = new Intl.DateTimeFormat('en-CA', {
|
|
timeZone: TZ, year: 'numeric', month: '2-digit', day: '2-digit',
|
|
})
|
|
|
|
/** 任意时间 -> 本地日历日 "YYYY-MM-DD" */
|
|
export function toDateStr(d = new Date()) {
|
|
return FMT.format(d instanceof Date ? d : new Date(d))
|
|
}
|
|
|
|
function toUTC(s) {
|
|
const [y, m, d] = String(s).split('-').map(Number)
|
|
return new Date(Date.UTC(y, (m || 1) - 1, d || 1))
|
|
}
|
|
|
|
function fromUTC(dt) {
|
|
return dt.toISOString().slice(0, 10)
|
|
}
|
|
|
|
export function addDays(s, n) {
|
|
const d = toUTC(s)
|
|
d.setUTCDate(d.getUTCDate() + n)
|
|
return fromUTC(d)
|
|
}
|
|
|
|
export function addMonths(s, n) {
|
|
const [y, m] = String(s).split('-').map(Number)
|
|
const first = new Date(Date.UTC(y, (m || 1) - 1 + n, 1))
|
|
const day = Number(String(s).slice(8, 10)) || 1
|
|
const lastDay = new Date(Date.UTC(first.getUTCFullYear(), first.getUTCMonth() + 1, 0)).getUTCDate()
|
|
first.setUTCDate(Math.min(day, lastDay))
|
|
return fromUTC(first)
|
|
}
|
|
|
|
/** 周一为一周第一天 */
|
|
export function startOfWeek(s) {
|
|
const dow = (toUTC(s).getUTCDay() + 6) % 7
|
|
return addDays(s, -dow)
|
|
}
|
|
|
|
export const PERIOD_LABEL = { day: '每天', week: '每周', month: '每月', year: '每年' }
|
|
|
|
export function periodRange(period, s) {
|
|
switch (period) {
|
|
case 'week': {
|
|
const start = startOfWeek(s)
|
|
return { start, end: addDays(start, 6) }
|
|
}
|
|
case 'month': {
|
|
const start = String(s).slice(0, 8) + '01'
|
|
const [y, m] = String(s).split('-').map(Number)
|
|
return { start, end: fromUTC(new Date(Date.UTC(y, m || 1, 0))) }
|
|
}
|
|
case 'year': {
|
|
const y = String(s).slice(0, 4)
|
|
return { start: y + '-01-01', end: y + '-12-31' }
|
|
}
|
|
default: return { start: s, end: s }
|
|
}
|
|
}
|
|
|
|
/** 月历网格: 返回 6*7 = 42 天 (含前后月的补位), 周一起始 */
|
|
export function monthGrid(monthStr) {
|
|
const first = monthStr + '-01'
|
|
const gridStart = startOfWeek(first)
|
|
const days = []
|
|
for (let i = 0; i < 42; i++) days.push(addDays(gridStart, i))
|
|
return days
|
|
}
|
|
|
|
export function weekdayLabels() {
|
|
return ['一', '二', '三', '四', '五', '六', '日']
|
|
}
|
|
|
|
/** 相对今天的文案: 昨天 / 今天 / 明天 / MM-DD */
|
|
export function dayLabel(dateStr, today = toDateStr()) {
|
|
if (dateStr === today) return '今天'
|
|
if (dateStr === addDays(today, -1)) return '昨天'
|
|
if (dateStr === addDays(today, 1)) return '明天'
|
|
return dateStr.slice(5).replace('-', '/')
|
|
}
|
|
|
|
export function fmtMonth(monthStr) {
|
|
const [y, m] = monthStr.split('-')
|
|
return y + '年' + Number(m) + '月'
|
|
}
|