Commit Graph

41 Commits

Author SHA1 Message Date
yuming 23278fee06 补最终修复报告,并标注计划文档里已过时的手工 SQL
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 10:28:29 +08:00
yuming 7bf84bff61 保存纪念日去掉 await 上报并加防重入,避免弱网下存出重复数据
onSubmit 原本 await requestSubscribe(),而它内部的 grant 上报走 api.request、
超时 10 秒。对已勾「总是保持以上选择」的用户连订阅弹窗都不会出现,点「保存」后
界面最长 10 秒毫无反馈;期间又没有任何提交中标志,重复点击会走两遍
storage.addAnniversary(),generateId() 每次生成新 id,直接存出两条重复纪念日。

改为:订阅授权 fire-and-forget(上报失败本就有 pending_sync_queue 兜底),
onSubmit 去掉 async、加实例级 submitting 标志防重入,本地写失败时放开标志并提示。

手势铁律未破:onSubmit 由 bindtap 直接绑定,方法体去掉了 async,从入口到
requestSubscribe 之间只有 if 判断、解构和三个同步校验,无任何 await 或 then。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 10:25:05 +08:00
yuming e47a399ad5 余额为 0 时改为发探针,不再无条件全部跳过
balance 只有前端上报这一条上升通道,没升级小程序的老用户在微信侧仍在
真实累积额度却永远不上报,记账会系统性低估。低估到 0 就一条不发,比
改造前更糟——改造前至少还会试。

改为:余额为 0 时仍发出优先级最高的那一条当探针。成功则证实微信侧有
额度,本轮继续发;43101 则立即中止(与原行为一致);非额度错误记 failed
且不再重复探。每用户每轮最多探一次。

探针成功仍走 quota.consume:内部 MAX(0, balance-1) 扣不出负数,净效果是
balance 保持 0、sentTotal +1。不趁机把 balance 调高——探针只证明「至少
还有 1 条」,凭空补猜出来的数字会让首页风险预告变成乐观的假话。

注:三个原有用例断言的是「余额 0 就一个请求都不发」这一被本次刻意推翻的
旧契约,已按新行为改写;改写后仍用变异测试确认能抓到探针被关掉。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 10:23:11 +08:00
yuming 30a40252c6 存量用户初始额度改为 db.js 里的自动迁移
原计划是部署后上服务器手工执行一条 SQL 补额度,漏执行的后果是所有
存量用户的提醒立刻大面积降级。改成 PRAGMA user_version 驱动的一次性
自动迁移,部署即执行、不可能漏。

幂等性两层保证:user_version 版本号 + INSERT OR IGNORE 只补尚无记录的
openid,已有记录一律不覆盖。整段包在事务里。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 10:19:47 +08:00
yuming 12b146c2c9 两处提醒查询补 ORDER BY id,保证平局取舍可复现
reminder.js 扫全表和 index.js 的 getQuotaStatus 都没有 ORDER BY,
两边的 sort 又都是稳定排序,同一天、同 kind、同 importance 的条目
先后完全由 SQLite 返回顺序决定。一个走全表扫、一个可能走索引,
顺序一致纯属巧合——一旦不一致,首页预告的人名就和实际被跳过的人对不上。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 10:19:47 +08:00
yuming 981e802532 已长期订阅的用户在点击人员时静默补额度 2026-08-16 09:45:27 +08:00
yuming 233a872c89 修复补额度反馈自相矛盾及订阅状态未就绪兜底显式化 2026-08-16 09:41:52 +08:00
yuming 7f420762ef 首页新增额度风险提示卡片,作为补额度主入口 2026-08-16 09:34:55 +08:00
yuming bc4fb7445d 修复保存纪念日时授权订阅消息未上报额度的缺口
requestSubscribe() 改为委托给 utils/subscribe.js 的
requestAndReport(),用户同意授权后会把新增额度同步上报后端,
避免最主要的授权入口被漏记导致余额系统性低估、定时任务拒发。
onSubmit 里原来依赖 reject 的 try/catch 改为直接判断布尔返回值,
消除死代码分支,"拒绝不阻塞保存"的行为保持不变。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 09:31:03 +08:00
yuming 1142346e24 新增前端订阅模块,收敛模板 ID 到唯一来源
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 09:25:12 +08:00
yuming d8573359a2 修复首页风险预告与实际发送的排序不一致问题
atRisk.js 之前只按 fireInDays 排序,同一天内多条提醒的先后完全未定义,
和 reminder.js 实际发送时「当天>提前、重要程度降序」的规则对不上,导致
额度卡在中间时首页提示的风险人名和定时任务实际跳过的人不一致。

- 把 importanceRank 从 reminder.js 抽到共享的 src/importance.js,两处复用
- atRisk.js 的 computeAtRisk 排序改为三段式:fireInDays 升序 →
  同天内 kind(当天优先于提前)→ 同 kind 内重要程度降序
- 补充 atRisk.test.js 用例覆盖「同一天多条事件、额度只够一部分」场景
2026-08-16 09:21:09 +08:00
yuming c7bdd3770a 补上43101保留原始错误信息的区分性断言,修复wx测试的恢复时机
- reminder.test.js:43101用例新增对 remind_logs.error 的断言,区分
  「真发送失败」(应为微信原始错误信息)与「因halted被跳过」(应为
  quota_exhausted兜底文案),此前该区别完全没有测试覆盖
- wx.test.js:把恢复 axios.get/post 的语句移入 try/finally,避免断言
  先抛错时污染同进程内后续用例

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 09:14:50 +08:00
yuming 57470ade4f 补充发送侧分组与错误分支的测试覆盖,保留43101原始错误信息的修复
- reminder.js:43101 跳过日志改为记录微信原始错误信息,便于和「额度耗尽」的
  兜底文案区分排查(前一实施者已验证,此次原样带入提交)
- reminder.test.js:新增 runOnce 按 openid 分组结算用例,验证多用户额度互不
  串号;将「非额度类错误」用例扩到 2 条数据,证明该分支不会误中止后续发送
- 新建 wx.test.js:验证 sendSubscribeMessage 在微信返回非 0 errcode 时,
  抛出的 Error 对象确实带有正确的 errcode 属性

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 09:07:46 +08:00
yuming 6548577a98 定时任务改为按用户分组、按优先级发送,额度耗尽即停 2026-08-15 18:04:12 +08:00
yuming b1949ac73e 新增 /api/subscribe 接口:授权上报与风险查询
- grantQuota/getQuotaStatus 从 index.js 导出供测试直接调用
- app.listen 加 require.main 守卫,避免测试 require 本文件时
  真的起服务占用端口、cron 定时器让进程无法退出
2026-08-15 17:55:40 +08:00
yuming 3a61b7952b 新增订阅额度风险预警计算 2026-08-15 17:43:19 +08:00
yuming 07ee98f838 修复 consume 缺行不建行导致 sentTotal 丢失的问题 2026-08-15 17:40:31 +08:00
yuming 8d63b47577 新增订阅消息额度记账表与模块 2026-08-15 17:34:46 +08:00
yuming 68acbb3809 抽出日期计算纯函数模块,并搭建后端测试基建
- 新增 server/src/occurrence.js:startOfDay/getNextOccurrence/daysBetween,today 可注入
- reminder.js 改为调用新模块,删除内联的 getThisYearDate/daysBetween
- 引入 node:test 作为测试框架,加 npm test 脚本
- 新增 test/helper.js(临时库辅助,供后续任务用)和 test/occurrence.test.js(7 个用例,覆盖农历跨年等边界)
2026-08-15 17:30:42 +08:00
yuming c92df874d1 忽略子代理执行过程的临时产物目录 2026-08-15 17:27:37 +08:00
yuming 955c80e221 实现计划:订阅消息额度治理
9 个任务,含单测代码与端到端验证脚本。用 Node 18 内置的 node:test
做后端单测(零新依赖),前端沿用 miniprogram-automator。

Task 1 是前置重构:把 reminder.js 内联的日期计算抽成可注入 today 的
纯函数模块,让核心逻辑第一次变得可单测,也供风险预警计算复用。

计划里记录了上线首日的一个真空风险:subscribe_quota 是新表,存量用户
初始余额均为 0,而新版 reminder 在余额为 0 时不发任何请求——后端一上线
存量用户提醒会立刻全部停发,且此时新版小程序还在审核中,用户无从补救。
附了按纪念日条数补初始余额的 SQL,须在部署后、当天 9 点定时任务前执行。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 16:55:18 +08:00
yuming 6d0119103f 设计文档:订阅消息额度治理
记录订阅消息额度供需倒挂问题的设计方案:服务端按 openid 记账、
用户点击时搭车补额度、额度不足时按优先级取舍并在首页预警。

核实并记录了几条决定设计走向的微信平台约束:
- 额度是 (用户 × 模板) 维度且永久有效,但用户关通知总开关会全部清零
- 无官方接口可查余额,只能自行记账并靠 43101 校正
- requestSubscribeMessage 必须由真实点击手势触发,无法在 onLaunch 静默调用
- 长期订阅类目不符,申请不到

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 16:44:19 +08:00
yuming e9c7330f31 数据自愈迁移 + 后端同步幂等,版本号 v2.1.7 → v2.1.8
部署到群晖 / deploy (push) Successful in 57s
后端(server/src/index.js)——两处同步幂等:

1. deleteAnniversary 删除不存在的记录时返回 success:true。
   原先返回 false,被前端 sync.js:34 当作失败重新入队,那条删除会永远留在
   pending_sync_queue 里每次启动重试且永远清不掉。

2. updateAnniversary / updatePerson 在记录不存在时退化为插入(upsert)。
   同一类问题:本地才是主真相源,若某条记录当初的 add 没同步成功,
   之后所有 update 都会失败并无限重试。

前端 —— 新增 utils/migrate.js,在 app.js onLaunch 执行:

3. 补齐老数据缺失的农历字段。早期 lunar_birthday 类型只记公历日期,
   没有 lunarMonth/lunarDay,这类记录会被安全降级成按公历算——不崩,但每年日期是错的。
   用它存的公历日期反算回农历补齐。

4. 为孤儿纪念日重建人员。部分纪念日的 personId 指向已不存在的人(历史上删人没级联干净),
   首页按 persons 遍历所以隐形,日历页按 anniversaries 遍历会显示成「未知」。
   用记录自带的 personName 重建,并沿用原 personId,纪念日无需改动。

迁移放在客户端而非后端跑 SQL:本地 wx.Storage 是主真相源,改服务端会被客户端同步覆盖。
每次启动都跑而非记版本号:函数是纯检测式的,无坏数据时零写入零请求,
还能顺带覆盖「从云端拉回坏数据」的情况。顺便把原本是死代码的 initData() 替换掉。

⚠️ 本次后端有改动,需要重新部署;部署顺序应为先后端、后小程序。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:55:44 +08:00
yuming 1943fd5c1a 修复 P0 五个问题,版本号 v2.1.6 → v2.1.7
1. 删除纪念日不同步云端:storage.deleteAnniversary 只删本地,后端记录仍是
   remindEnabled=1,定时任务会继续推已删除的纪念日,换设备恢复时还会被拉回来。
   补上 _syncAnniversary('delete')。

2. 农历纪念日日期计算三处口径不一:date.js 新增 getOccurrenceInYear /
   getNextOccurrenceOf,首页、详情页、日历页统一复用。
   顺带修掉一个隐藏更深的坑——老实现把公历年当农历年传给 lunarToSolar,
   农历腊月会整整错一年(农历2026腊月十五 = 公历2027-01-22,老算法给 2028-01-11)。
   新实现同时试 year-1 / year 两个农历年,取真正落在该公历年内的那次。
   删除已无调用方的旧 getNextOccurrence(month, day),它正是首页 bug 的来源。

3. 详情页倒计时永远显示「已过 N 天」:原先按录入年份算 daysUntil,
   改为按下一次发生算;「日期」一行仍展示原始录入日期。

4. 日历页切 tab 后不刷新:渲染从 onLoad 挪到 onShow(tabBar 页面实例常驻),
   不重置当前年月,保留用户翻到的月份。

5. 清空数据后重启会复活:只清本地的话 pullFromCloudIfEmpty 会把云端整份拉回来。
   新增 storage.clearCloudData()(sync 传空数组),设置页改为先清云端、
   成功才清本地;云端失败则中止并提示,本地数据保留。

均为小程序侧改动,server/ 未改动,无需重新部署。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 12:08:26 +08:00
yuming 192867a8d5 版本号显示 v2.1.5 → v2.1.6
部署到群晖 / deploy (push) Successful in 41s
带农历日期显示的版本,重新上传体验版。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 14:21:11 +08:00
yuming e643512dfa 纪念日新增农历日期显示
部署到群晖 / deploy (push) Successful in 44s
- 人员详情页:日期下方显示完整农历「农历 YYYY年 X月X日」
- 日历页本月纪念日列表:日期下方显示农历月日「X月X日」
- 公历事件用 solarToLunar 算当年对应农历
- 农历事件按当年公历落点反算农历(与原始农历一致)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 14:19:14 +08:00
yuming f0835726de 版本号显示 v2.1.4 → v2.1.5
部署到群晖 / deploy (push) Successful in 36s
带日历跨年修复的版本,重新上传体验版。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 13:36:28 +08:00
yuming 030e587480 修复日历跨年不显示纪念日
部署到群晖 / deploy (push) Successful in 40s
原 buildEventIndex / loadMonthEvents 用 a.solarYear === year 判断,
只匹配纪念日原始录入年份的那一年。生日/纪念日是周年性事件,应
每年这天都出现。

- 去掉 solarYear === year 判断
- 新增 getAnniversaryDateInYear():公历直接用 solarMonth/solarDay
- 农历用 lunarToSolar 重算当年公历,因农历对应公历每年浮动
- 当年无闰月时 lunarToSolar 自动忽略 isLeap 回退普通月

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 13:35:20 +08:00
yuming fb7b12ff76 版本号显示 v2.1.3 → v2.1.4
部署到群晖 / deploy (push) Successful in 48s
为即将上传的微信小程序新版做准备,保持「数据」页底部
版本号与微信后台一致。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 13:04:31 +08:00
yuming 3f30c9c017 修复 person-detail 按钮文字未居中
部署到群晖 / deploy (push) Successful in 40s
原因:微信 <button> 自带 min-height 88rpx 和固有 line-height,
会顶歪自定义高度。改用 <view> + flex 居中。

- 5 个 button 改为 view(保留 class/bindtap/data-id)
- action-btn / edit-btn / delete-btn 改 flex 居中
- add-btn 加 inline-flex 居中

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 12:54:27 +08:00
yuming fb036fd65f 设置页改造:「设置」→「数据」,做成数据中心
- tabBar 文字 ⚙️ 设置 → 💾 数据
- Header 收敛成 标题 + 一行统计 + 上次备份时间
- 删除「关于」「温馨提示」整块装饰内容
- 「数据管理」拆成「备份」+「危险操作」两组
- 导出成功后写入 lastBackupAt,进页面格式化展示
- 图标背景从冷色统一为纸感暖色
- 底部加版本号 v2.1.3 小字

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 12:54:20 +08:00
yuming 3f176924d0 视觉重构:紫渐变 SaaS 风 → 纸感编辑风(A 方案)
部署到群晖 / deploy (push) Successful in 43s
旧版多处违反 impeccable 设计规范(gradient text、6rpx 粗色条 border-left、
glassmorphism、bounce 动画)且整体呈现典型「AI/SaaS 通用紫渐变」(#6366f1 系、
#667eea 系),用户反馈「AI 味儿太浓」。

色板(OKLCH 概念,hex 落地):
- 背景:浅米黄 #FAF6ED
- 主文字:深墨蓝 #1F1D2B
- 暖灰副文字:#8A8278
- 强调(仅 today / FAB):墨红 #C8412F

涉及改动:
- app.wxss:删模板自带 .container padding 200rpx(首页/日历顶部空白真凶)+ 重写全局色板
- index.wxml/wxss:去紫渐变 Header + glassmorphism stats + border-left 大色条 + bounce
- calendar.wxml/wxss/json:去紫渐变月份头 + today 大色块 + section-title 6rpx 紫条
- settings.wxss:同款紫 Header + tips-card 6rpx 紫条 → 全卡边线 + 浅暖底
- add-anniversary.wxss:chip-active 紫 / 取消提交按钮渐变 / importance 高饱和色 → 统一
- add-person.wxss:gradient text + 紫 dashed 头像占位 + 多重渐变 → 全套重写
- person-detail.wxss:微信绿 + 灰底默认色 → 方向 A 色板
- utils/constants.js:IMPORTANCE_COLORS 高/中/低 = 墨红/焦糖/苔绿(替代橙红/橘/鲜绿)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 09:27:16 +08:00
yuming 323c566597 回滚 lazyCodeLoading 配置
部署到群晖 / deploy (push) Successful in 36s
启用后开发者工具基础库 3.10.3 报错 Component is not found in path "wx://not-found"
导致首页白屏。这是 lazyCodeLoading 跟某些基础库版本的已知兼容问题。
回滚 app.json,等基础库版本升级后再考虑开启。

代码质量扫描里"启用组件按需注入"会标记"未通过",但不影响审核。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 07:41:00 +08:00
yuming 035a946c4d app.json 启用 lazyCodeLoading=requiredComponents
部署到群晖 / deploy (push) Successful in 41s
微信代码质量扫描推荐项:让小程序按需注入组件,减少冷启动包加载量。
对老版本基础库会自动降级到全量注入,安全。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 07:35:21 +08:00
yuming 756d57d818 简化纪念日类型:去掉"农历生日"类型,公历/农历改由 isLunar 字段决定
部署到群晖 / deploy (push) Successful in 44s
- typeList 从 5 项简化到 4 项:生日 / 结婚纪念日 / 订婚纪念日 / 其他纪念日
- TYPE_NAMES / TYPE_ICONS 中 lunar_birthday 保留兼容映射(也映射到「生日」+ 🎂),
  让线上历史数据自然回显,无需数据库迁移(方案 A)
- getTypeIndex('lunar_birthday') = 0,老数据编辑时正确回显「生日」
- index.js 列表筛选和 wxml 图标判断本来已含 lunar_birthday 兼容,无需改

老数据自然淘汰:用户重新保存时新数据写 type='birthday',老数据 type 保留
直到下次编辑保存才升级。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 07:30:37 +08:00
yuming 59ed635dcf 修复 lunarToSolar 系统性少 1 天的 bug
部署到群晖 / deploy (push) Successful in 42s
BASE_DATE 用 new Date(1900, 0, 31)(本地时间)在 1900 年早期日期上某些
JS 引擎有时区偏差,导致每个农历月初对应公历都比真实少 1 天。改用
Date.UTC(1900, 0, 31) 算时间戳,再用 UTC 字段重新构造本地 Date 对象,
彻底避免歧义。

同时月循环改用 `leapM <= m` 写法(与权威 jjonline/calendar.js 一致),
和 solarToLunar 保持完美互逆。

修复验证(互逆 9 个用例全 OK):
- 2026-02-17 ↔ 正月初一
- 2026-06-09 ↔ 农历四月廿四(用户实测发现的差 1 天)
- 2023-03-22 ↔ 闰二月初一
- 2023-04-19 ↔ 闰二月廿九
- 2025-07-25 ↔ 闰六月初一
- 2025-08-22 ↔ 闰六月廿九

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 06:34:00 +08:00
yuming 320209a390 修复 solarToLunar 闰月期间非初一日期算错的 bug
部署到群晖 / deploy (push) Successful in 45s
原算法在月循环外的 if (offset < 0) 分支根据 isLeap 重新判断加哪个月份天数,
但闰月期间的非初一日期会因为变量切换被错算到下一个普通月。

用 jjonline/calendar.js 的权威实现替换:循环内统一 offset -= temp,
退出循环后用保留的 temp 加回,简洁且正确。

修复验证:
- 2023-03-22 → 闰二月初一 ✓(之前也对)
- 2023-03-23 → 闰二月初二 ✓(之前错为「三月初二」)
- 2023-04-19 → 闰二月廿九 ✓(之前错为「三月廿九」)
- 2025-08-22 → 闰六月廿九 ✓(之前错为「七月廿九」)

维护手册新增踩坑 #13。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 06:10:12 +08:00
yuming ddcfe3334e v2.1.0 流程改造 + 农历准确性修复 + 双向同步 + 闰月支持
部署到群晖 / deploy (push) Successful in 44s
- 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
yuming 06d22884b9 修复持久化目录创建:通过 docker 在宿主创建
部署到群晖 / deploy (push) Successful in 34s
runner 容器内 mkdir 创建的是容器路径,不是宿主上的真实路径。
改成通过已构建好的 birthday-server 镜像挂宿主 /volume1 来 mkdir。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 15:53:40 +08:00
yuming 3965e542fc 接入自建后端 + Gitea CI/CD
部署到群晖 / deploy (push) Failing after 6m22s
- 新增 server/:Node + Express + SQLite + node-cron 实现登录、纪念日 CRUD 和定时订阅消息推送
- 新增 .gitea/workflows/deploy.yml:推送即触发群晖 Docker 部署,监听 15002
- utils/api.js:自动按 envVersion 切换本地/线上 BASE_URL
- app.js 与 add-anniversary.js 移除 wx.cloud 调用,改走自建后端
- cloudfunctions/ 暂保留以便回滚
- 一并提交此前未入库的首页 / 设置页 / 日历 / 万年历等改造

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 15:44:09 +08:00
yuming 6747ade9c4 Initial Commit 2025-10-26 19:29:30 +08:00