v0.7.0 接上真实服务器:登录 / 设备会话 / 全量同步与各模块真实数据

从「演示数据壳子」走到「真的能连家里的服务器用」。

登录(用户 2026-10-17「完整的登录功能」)
  · 首次启动先填服务器地址 → /api/v1/ping 自报门户(认得出"这地址上真是家书")
  · 手机号或用户名 + 密码 → 拿令牌;失败提示统一,不泄露账号是否存在
  · FbDeviceStore:首次启动生成并持久化设备 UUID(三方应用拿不到 UDID),
    设备名取机型(marketName),并上报 platform / app_version

设备会话(v57,用户 2026-10-17)
  · 一把令牌 = 一台设备的一条会话;每次启动打一次 /me(有网就验)
  · 两种锁分开:need_login(401/被踢 → 清令牌)与 offline_expired
    (离线超 24h → 只锁界面、不清数据、联网自动恢复)——
    混成一种就会违背用户要的"一直联网就不用每天输密码"
  · 运行期任何请求 401 → 网络层全局回调立刻锁定跳登录页(FbHttp 的 authProbe
    让登录/校验类请求跳过,避免"两个地方同时跳登录页")
  · 退出登录:先清本地令牌(主防线),再尽力通知服务端
  · 设置页显示版本号与「这台设备」

数据层
  · FbDb / FbSchema / FbParse / 各 Repo(日程 / 名册 / 观影 / 模块)+ FbSyncAll
  · 首页改为服务端 /home(农历/节气/纪念日/生日/天气/统计/往年的今天)
  · 日程页用 FbDayEventPanel,与网页端显示方式/红线/并排三处一致

验证:ArkTS 静态守卫 231/0;hvigor 编译 BUILD SUCCESSFUL(entry-default-signed.hap)。
This commit is contained in:
2026-10-18 03:36:04 +08:00
parent 7eaa641437
commit 776c95ba0b
64 changed files with 18297 additions and 1519 deletions
+49 -75
View File
@@ -12,10 +12,10 @@
* ⚠️ 全文件统一 `Fb` 前缀命名,避免与其他文件顶层名冲突(arkts-no-decl-merging)。
*/
import { FbAccount, FbAnniversaryItem, FbBookItem, FbCalBook, FbCalEvent, FbGroup,
FbKinChip, FbKinRelation, FbLedgerBook, FbLedgerEntry, FbModuleCard, FbMovieItem,
import { FbAccount, FbBookItem, FbCalBook, FbCalEvent, FbGroup,
FbKinChip, FbKinRelation, FbLedgerBook, FbLedgerEntry, FbMovieItem,
FbNoteItem, FbNotificationItem, FbResourceItem, FbRosterItem, FbSession,
FbTaskItem, FbTripItem, FbTripNode, FbWeather, FbWeekStat } from './Types';
FbTaskItem, FbTripItem, FbTripNode } from './Types';
/** 假数据里的「我」 */
export const FB_DEMO_ME: FbAccount = {
@@ -42,65 +42,38 @@ function fbChip(id: number, shown: string, kinship: string, color: string): FbKi
return { id: id, shown: shown, kinship: kinship, avatarColor: color, isAdmin: false };
}
/** 首页「我的家人」 */
export const FB_DEMO_KIN: FbKinChip[] = [
fbChip(2, '爱人', '配偶', '#9A5C7A'),
fbChip(3, '儿子', '儿子', '#4A7C82'),
fbChip(4, '女儿', '女儿', '#6B5B95'),
fbChip(5, '爸爸', '父亲', '#8A6B4B'),
fbChip(6, '妈妈', '母亲', '#B0763A'),
fbChip(7, '弟弟', '弟弟', '#3F7D5A')
];
/** 首页天气 */
export const FB_DEMO_WEATHER: FbWeather = {
place: '青岛市 · 崂山区',
temp: 21,
text: '多云',
wind: '东南风 3 级'
};
/** 首页近七天统计 */
export const FB_DEMO_WEEK_STAT: FbWeekStat = {
events: 11,
todos: 4,
overdue: 1
};
/** 首页日子标记 */
export const FB_DEMO_MARKS: string[] = ['寒露', '重阳节', '儿子生日'];
/** 首页模块卡片(顺序与 Web 端 Module::DEFAULT_NAV 一致) */
export const FB_DEMO_MODULE_CARDS: FbModuleCard[] = [
{ key: 'people', name: '名册', big: '12 人', lines: ['1089 人在这个家里'], color: '#4A7C82' },
{ key: 'movies', name: '观影', big: '86 部', lines: ['最近看了《漫长的季节》'], color: '#7A4E8C' },
{ key: 'trips', name: '旅程', big: '9 次', lines: ['最近去了青岛·崂山'], color: '#3F7D5A' },
{ key: 'calendar', name: '日程', big: '11 条', lines: ['未来七天'], color: '#B0763A' },
{ key: 'resources', name: '资源', big: '37 项', lines: ['证件 / 资料 / 附件'], color: '#5566A8' },
{ key: 'tasks', name: '任务', big: '3 件在做', lines: ['4 件待办 · 1 件逾期'], color: '#A8574F' },
{ key: 'days', name: '纪念日', big: '6 天后', lines: ['结婚周年'], color: '#B4633C' },
{ key: 'ledger', name: '财务', big: '¥86.4 万', lines: ['3 本账(净资产)'], color: '#5E7A3C' },
{ key: 'books', name: '读书', big: '2 本在读', lines: ['31 本读完 · 48 条笔记'], color: '#6B5B95' },
{ key: 'notes', name: '笔记', big: '63 条', lines: ['最近:阳台改造清单'], color: '#7A6A4F' }
];
/* ============================================================
* 🔴 v54(用户 2026-10-16)—— 首页的演示常量**全部删掉**:
* `FB_DEMO_KIN`(我的家人)/ `FB_DEMO_MARKS`(寒露·重阳节·儿子生日)/
* `FB_DEMO_WEATHER` / `FB_DEMO_WEEK_STAT` / `FB_DEMO_MODULE_CARDS`。
*
* 用户原话:「里面的数据好像有些不符,比如**寒露、重阳节、儿子的生日**,
* 这些都是**乱写的**,要用**真实数据**填写一下。」
* —— 那三个字面值就是 `FB_DEMO_MARKS`,它一直显示给真实用户看,
* 因为 `/api/v1/home` 当时**根本没有**农历/标记/天气/统计这几样字段。
*
* ⇒ 服务端已补齐(见 `ApiHandler::home()`),端上全部改接真实数据。
* ⚠️ **不要再往这个文件加首页的演示常量** —— 首页现在没有任何"占位",
* 接口没给就不画那一块(这比画一个假的好)。
* ============================================================ */
// ============================================================
// 名册
// ============================================================
export const FB_DEMO_ROSTER: FbRosterItem[] = [
{ id: 1, name: '杨永全', kinship: '我', group: '本家', tags: ['管理员'], avatarColor: '#A84A28', roster: 'member', birthday: '03-12', isAdmin: true },
{ id: 2, name: '李慧', kinship: '配偶', group: '本家', tags: [], avatarColor: '#9A5C7A', roster: 'member', birthday: '07-08', isAdmin: false },
{ id: 3, name: '杨小满', kinship: '儿子', group: '本家', tags: ['学生'], avatarColor: '#4A7C82', roster: 'member', birthday: '10-18', isAdmin: false },
{ id: 4, name: '杨小禾', kinship: '女儿', group: '本家', tags: ['学生'], avatarColor: '#6B5B95', roster: 'member', birthday: '01-04', isAdmin: false },
{ id: 5, name: '杨建国', kinship: '父亲', group: '长辈', tags: ['退休'], avatarColor: '#8A6B4B', roster: 'member', birthday: '05-21', isAdmin: false },
{ id: 6, name: '王秀兰', kinship: '母亲', group: '长辈', tags: [], avatarColor: '#B0763A', roster: 'member', birthday: '11-30', isAdmin: false },
{ id: 7, name: '杨永强', kinship: '弟弟', group: '本家', tags: [], avatarColor: '#3F7D5A', roster: 'member', birthday: '08-15', isAdmin: false },
{ id: 8, name: '李国栋', kinship: '岳父', group: '姻亲', tags: [], avatarColor: '#5566A8', roster: 'member', birthday: '02-26', isAdmin: false },
{ id: 9, name: '陈静', kinship: '岳母', group: '姻亲', tags: [], avatarColor: '#A8574F', roster: 'member', birthday: '09-09', isAdmin: false },
{ id: 10, name: '张伟', kinship: '朋友', group: '亲友', tags: ['同事'], avatarColor: '#5E7A3C', roster: 'friend', birthday: '06-14', isAdmin: false },
{ id: 11, name: '刘敏', kinship: '朋友', group: '亲友', tags: ['同学'], avatarColor: '#7A6A4F', roster: 'friend', birthday: '12-02', isAdmin: false },
{ id: 12, name: '赵鹏', kinship: '朋友', group: '亲友', tags: ['邻居'], avatarColor: '#3D6B8E', roster: 'friend', birthday: '04-17', isAdmin: false }
{ id: 1, name: '杨永全', kinship: '我', group: '本家', tags: ['管理员'], avatarColor: '#A84A28', roster: 'member', birthday: '03-12', avatar: '', phone: '', initials: 'x', isAdmin: true, phones: [] },
{ id: 2, name: '李慧', kinship: '配偶', group: '本家', tags: [], avatarColor: '#9A5C7A', roster: 'member', birthday: '07-08', avatar: '', phone: '', initials: 'x', isAdmin: false, phones: [] },
{ id: 3, name: '杨小满', kinship: '儿子', group: '本家', tags: ['学生'], avatarColor: '#4A7C82', roster: 'member', birthday: '10-18', avatar: '', phone: '', initials: 'x', isAdmin: false, phones: [] },
{ id: 4, name: '杨小禾', kinship: '女儿', group: '本家', tags: ['学生'], avatarColor: '#6B5B95', roster: 'member', birthday: '01-04', avatar: '', phone: '', initials: 'x', isAdmin: false, phones: [] },
{ id: 5, name: '杨建国', kinship: '父亲', group: '长辈', tags: ['退休'], avatarColor: '#8A6B4B', roster: 'member', birthday: '05-21', avatar: '', phone: '', initials: 'x', isAdmin: false, phones: [] },
{ id: 6, name: '王秀兰', kinship: '母亲', group: '长辈', tags: [], avatarColor: '#B0763A', roster: 'member', birthday: '11-30', avatar: '', phone: '', initials: 'x', isAdmin: false, phones: [] },
{ id: 7, name: '杨永强', kinship: '弟弟', group: '本家', tags: [], avatarColor: '#3F7D5A', roster: 'member', birthday: '08-15', avatar: '', phone: '', initials: 'x', isAdmin: false, phones: [] },
{ id: 8, name: '李国栋', kinship: '岳父', group: '姻亲', tags: [], avatarColor: '#5566A8', roster: 'member', birthday: '02-26', avatar: '', phone: '', initials: 'x', isAdmin: false, phones: [] },
{ id: 9, name: '陈静', kinship: '岳母', group: '姻亲', tags: [], avatarColor: '#A8574F', roster: 'member', birthday: '09-09', avatar: '', phone: '', initials: 'x', isAdmin: false, phones: [] },
{ id: 10, name: '张伟', kinship: '朋友', group: '亲友', tags: ['同事'], avatarColor: '#5E7A3C', roster: 'friend', birthday: '06-14', avatar: '', phone: '', initials: 'x', isAdmin: false, phones: [] },
{ id: 11, name: '刘敏', kinship: '朋友', group: '亲友', tags: ['同学'], avatarColor: '#7A6A4F', roster: 'friend', birthday: '12-02', avatar: '', phone: '', initials: 'x', isAdmin: false, phones: [] },
{ id: 12, name: '赵鹏', kinship: '朋友', group: '亲友', tags: ['邻居'], avatarColor: '#3D6B8E', roster: 'friend', birthday: '04-17', avatar: '', phone: '', initials: 'x', isAdmin: false, phones: [] },
];
/** 家谱连线(父母 / 配偶 / 子女) */
@@ -166,17 +139,17 @@ export const FB_DEMO_CAL_BOOKS: FbCalBook[] = [
];
export const FB_DEMO_EVENTS_TODAY: FbCalEvent[] = [
{ id: 1, title: '儿子数学兴趣班', date: '2026-10-11', time: '09:00', calName: '孩子的课表', color: '#4A7C82', allDay: false, place: '小区活动中心' },
{ id: 2, title: '家庭大扫除', date: '2026-10-11', time: '10:30', calName: '家庭日程', color: '#B0763A', allDay: false, place: '家' },
{ id: 3, title: '晚上看电影', date: '2026-10-11', time: '19:30', calName: '观影', color: '#7A4E8C', allDay: false, place: '家里' },
{ id: 4, title: '妈妈体检预约', date: '2026-10-11', time: '', calName: '家庭日程', color: '#B0763A', allDay: true, place: '市立医院' }
{ id: 1, title: '儿子数学兴趣班', date: '2026-10-11', time: '09:00', calName: '孩子的课表', color: '#4A7C82', allDay: false, place: '小区活动中心', calId: 0, endTime: '', note: '', kind: 'custom' },
{ id: 2, title: '家庭大扫除', date: '2026-10-11', time: '10:30', calName: '家庭日程', color: '#B0763A', allDay: false, place: '家', calId: 0, endTime: '', note: '', kind: 'custom' },
{ id: 3, title: '晚上看电影', date: '2026-10-11', time: '19:30', calName: '观影', color: '#7A4E8C', allDay: false, place: '家里', calId: 0, endTime: '', note: '', kind: 'custom' },
{ id: 4, title: '妈妈体检预约', date: '2026-10-11', time: '', calName: '家庭日程', color: '#B0763A', allDay: true, place: '市立医院', calId: 0, endTime: '', note: '', kind: 'custom' },
];
export const FB_DEMO_EVENTS_SOON: FbCalEvent[] = [
{ id: 11, title: '女儿钢琴课', date: '10-12', time: '16:00', calName: '孩子的课表', color: '#4A7C82', allDay: false, place: '琴行' },
{ id: 12, title: '结婚周年', date: '10-17', time: '', calName: '家庭日程', color: '#B0763A', allDay: true, place: '' },
{ id: 13, title: '周末露营', date: '10-18', time: '08:00', calName: '家庭日程', color: '#B0763A', allDay: false, place: '仰口' },
{ id: 14, title: '家长会', date: '10-20', time: '18:30', calName: '孩子的课表', color: '#4A7C82', allDay: false, place: '学校' }
{ id: 11, title: '女儿钢琴课', date: '10-12', time: '16:00', calName: '孩子的课表', color: '#4A7C82', allDay: false, place: '琴行', calId: 0, endTime: '', note: '', kind: 'custom' },
{ id: 12, title: '结婚周年', date: '10-17', time: '', calName: '家庭日程', color: '#B0763A', allDay: true, place: '', calId: 0, endTime: '', note: '', kind: 'custom' },
{ id: 13, title: '周末露营', date: '10-18', time: '08:00', calName: '家庭日程', color: '#B0763A', allDay: false, place: '仰口', calId: 0, endTime: '', note: '', kind: 'custom' },
{ id: 14, title: '家长会', date: '10-20', time: '18:30', calName: '孩子的课表', color: '#4A7C82', allDay: false, place: '学校', calId: 0, endTime: '', note: '', kind: 'custom' },
];
// ============================================================
@@ -196,17 +169,18 @@ export const FB_DEMO_TASKS: FbTaskItem[] = [
/** 任务看板(tasks/settings) */
export const FB_DEMO_TASK_BOARDS: string[] = ['家务', '家人健康', '孩子', '车与房'];
// ============================================================
// 纪念日
// ============================================================
export const FB_DEMO_ANNIVERSARIES: FbAnniversaryItem[] = [
{ id: 1, title: '结婚周年', date: '10-17', daysLeft: 6, kind: 'anniversary', people: [fbChip(1, '我', '我', '#A84A28'), fbChip(2, '爱人', '配偶', '#9A5C7A')], yearly: true },
{ id: 2, title: '儿子生日', date: '10-18', daysLeft: 7, kind: 'birthday', people: [fbChip(3, '儿子', '儿子', '#4A7C82')], yearly: true },
{ id: 3, title: '重阳节', date: '10-29', daysLeft: 18, kind: 'holiday', people: [], yearly: true },
{ id: 4, title: '爸爸生日', date: '05-21', daysLeft: 222, kind: 'birthday', people: [fbChip(5, '爸爸', '父亲', '#8A6B4B')], yearly: true },
{ id: 5, title: '搬家纪念日', date: '2019-11-08', daysLeft: 28, kind: 'anniversary', people: [], yearly: true }
];
/* ============================================================
* 🔴 v54(用户 2026-10-16)—— `FB_DEMO_ANNIVERSARIES` 已删除。
*
* 用户原话:「里面的数据好像有些不符,比如**寒露、重阳节、儿子的生日**,
* 这些都是**乱写的**,要用**真实数据**填写一下。」
* ("寒露"来自已删的 `FB_DEMO_MARKS`,"重阳节 / 儿子生日"来自这里。)
*
* 它**早已无人引用**(纪念日页接的是 `/api/v1/days`),
* 只是文件里还留着 —— 留着就有被"顺手拿来当占位"的风险
* (这次首页的假数据就是这么来的)。**没有引用就删掉**,
* 比"留着备用"安全。
* ============================================================ */
// ============================================================
// 财务
@@ -0,0 +1,61 @@
/**
* 家书 FamilyBook · 各模块的「设置菜单」条目(鸿蒙端)
*
* 🔴 用户 2026-10-16:「所有页面的设置按钮全部都改成和网页版一致,
* 就是**三个横线**那种设置按钮。」
*
* 这份表**逐条对齐网页版** `templates/<模块>/_settings_menu.php` 里的菜单项
* (那是权威来源;别自己发明条目)。
*
* ⚠️ `action` 为**空串** = 该功能在鸿蒙端**还没做**:
* 界面会画成灰色 + 「暂未开放」,点它**不响应**
* (比"点了没反应"好 —— 后者用户以为是卡了)。
* ⚠️ 补上某个功能时,只要把对应 `action` 填上、并在页面里处理它即可;
* 表本身不用改结构。
*/
import { FbSettingItem } from '../components/FbModuleSettingsSheet';
/** 观影(对应网页版 templates/movies/_settings_menu.php) */
export const FB_SET_MOVIES: FbSettingItem[] = [
{ icon: 'movies', label: '影院管理', hint: '看过的影院,按名字排', action: 'cinema' },
{ icon: 'search', label: '高级搜索', hint: '按年份 / 类型 / 评分筛', action: '' },
{ icon: 'chart', label: '统计', hint: '每年看了多少、类型分布', action: '' },
{ icon: 'notes', label: '年度报告', hint: '一句话总结这一年', action: '' }
];
/** 读书(templates/books/_settings_menu.php) */
export const FB_SET_BOOKS: FbSettingItem[] = [
{ icon: 'chart', label: '读书统计', hint: '每年读了多少、多少页', action: '' },
{ icon: 'plus', label: '导入读书笔记', hint: '从笔记文件批量导入', action: '' },
{ icon: 'share', label: '微信读书导入', hint: '同步微信读书的书单', action: '' }
];
/** 纪念日(templates/days/_settings_menu.php) */
export const FB_SET_DAYS: FbSettingItem[] = [
{ icon: 'filter', label: '纪念日分类', hint: '全家共用一套分类', action: '' }
];
/** 账本(templates/ledger/_settings_menu.php) */
export const FB_SET_LEDGER: FbSettingItem[] = [
{ icon: 'share', label: '共享给谁', hint: '对方就能记能看', action: '' },
{ icon: 'edit', label: '账本名字', hint: '改这本账本的名字', action: '' }
];
/** 任务(templates/tasks/_settings_menu.php) */
export const FB_SET_TASKS: FbSettingItem[] = [
{ icon: 'tasks', label: '分类与看板', hint: '管分类、管看板', action: '' },
{ icon: 'check', label: '已完成', hint: '看已经完成的任务', action: '' }
];
/** 旅程(templates/trips/_settings_menu.php) */
export const FB_SET_TRIPS: FbSettingItem[] = [
{ icon: 'nav', label: '车辆管理', hint: '自驾时的车与油耗', action: '' },
{ icon: 'trips', label: '出行方式', hint: '飞机 / 高铁 / 自驾…', action: '' },
{ icon: 'dot', label: '节点类型', hint: '行程节点的类型', action: '' }
];
/** 资源(网页版没有独立菜单;这里只放已有的入口) */
export const FB_SET_RESOURCES: FbSettingItem[] = [
{ icon: 'filter', label: '格式筛选', hint: '只看某类资源', action: '' }
];
+224 -12
View File
@@ -63,24 +63,75 @@ export interface FbTodayMark {
export interface FbWeather {
/** 地点名 */
place: string;
/** 温度(摄氏度,整数) */
temp: number;
/**
* 温度(**带单位的字符串**,如 `21°C`)。
*
* 🔴 v54:原来是 `number`,但服务端 `weatherOut()` 给的是 `'21°C'`(带单位)——
* 端上按数字解析会得到 NaN。改成字符串**原样承接**,
* 而不是"端上再拼一次单位"(那样单位口径会在两处各写一遍)。
*/
temp: string;
/** 天气描述,如「晴」「多云」 */
text: string;
/** 风向风力,如「东南风 3 级」 */
/**
* 天气**符号**(服务端给的彩色图标字符:☀ / ⛅ / ☂ / ❄ / ⚡ / ≋)。
* ⚠️ 用字符而不是端上图标资源:服务端已经按 WMO 码分好档了
* (晴=暖黄、雨=蓝…),端上再映射一遍必然与网页版飘。
*/
icon: string;
/** 色调档位:sunny|cloud|rain|snow|storm|fog(决定图标用什么颜色) */
tone: string;
/** 风:`东南风 12 km/h`(可为空串) */
wind: string;
/** 风向(`东南风`,可为空串) */
wdir: string;
/** 今日最高 / 最低(`28°`,可为空串) */
tmax: string;
tmin: string;
}
/** 首页统计块(近七天) */
export interface FbWeekStat {
/** 未来七天日程条数 */
events: number;
/** 待办件数 */
todos: number;
/** 逾期件数 */
/**
* 首页概述卡底部的四个数字(与网页版 `hb-stats` 同源)。
*
* 🔴 v54:原来是「未来七天 / 待办 / 逾期」三格,其中第一格是
* "今天起往后 7 天的日程条数"。用户 2026-10-16 说
* 「下面……**未来7天**……这几个就是去掉了,不要了。」
* ⇒ 换成与**网页版概述卡一致**的四格(网页版本来也是这四个):
* 条日程(今天)/ 件在做 / 件待办 / 件逾期。
* ⚠️ 四个数字必须与**下面那块任务列表同源** —— v24 第 25 批用户报过
* 「为什么显示 0 条待办呢?任务看板明明有一个进行中的任务啊」,
* 根因就是"数字与列表两个口径"。服务端为此直接数 `$tasks` 那个数组。
*/
export interface FbStat {
/** 今天的日程条数 */
todayEvents: number;
/** 进行中的任务数 */
doing: number;
/** 待办的任务数 */
todo: number;
/** 已逾期的任务数 */
overdue: number;
}
/**
* 首页「日子标记」:今天是什么节气 / 节日 / 纪念日 / 谁的生日。
*
* 🔴 v54(用户 2026-10-16):「寒露、重阳节、儿子的生日,这些都是**乱写的**,
* 要用**真实数据**填写一下。」
* ⇒ 标记改由服务端 `Home::todayMarks()` 给(与网页版首页**同一份实现**:
* 法定节假日 / 二十四节气 / 传统节日 / 纪念日 / 生日)。
*/
export interface FbMark {
/** 显示词:`寒露` / `国庆节` / `妈妈生日` */
word: string;
/**
* 类别(决定配色,与网页版 `.hb-mark.is-*` 一一对应):
* `holiday` 法定节假日 / `term` 节气 / `festival` 传统节日 /
* `anniv` 纪念日 / `birthday` 生日
*/
kind: string;
}
/** 首页「我的家人」里的一个人(带称谓与称呼) */
export interface FbKinChip {
id: number;
@@ -94,6 +145,32 @@ export interface FbKinChip {
isAdmin: boolean;
}
/**
* 首页「往年的今天」里的一条。
*
* 🔴 v54b(用户 2026-10-17):「近期任务的下面,应该加上**往年的今天**,
* 跟电脑端一样,你看看那个是什么逻辑。」
* 电脑端的逻辑在 `App\Home::memories()`(原来的 `index.php::home_memories()`):
* · **只认同一天**(月与日都相同)—— 用户 2026-10-02 明确过
* 「"10 月 2 日"给我看"去年 10 月 8 日去看了场电影"……不是同一天的事」,
* 所以**不退回同月**;
* · 两类来源:看过的电影(`events.kind='movie'`)、
* 走过的**实际行程**节点(`trip_nodes.stage='actual'`,计划中的不算);
* · 合并按时间倒序、最多 6 条。
*/
export interface FbMemoryItem {
/** 源记录 id(movie 是 events.id,trip_node 是 trip_nodes.id) */
id: number;
/** 标题(旅程节点已在服务端拼成「旅程名 · 节点名」) */
title: string;
/** `YYYY-MM-DD HH:MM:SS` */
startsAt: string;
/** `movie` | `trip_node`(决定图标与圆点色) */
kind: string;
/** 距今几年(服务端算好,`2026` 年的记录 `years_ago=2`) */
yearsAgo: number;
}
/** 首页「模块卡片」—— 一块一块的入口,带一句关键数字 */
export interface FbModuleCard {
/** 模块 key(点击后切到哪个模块) */
@@ -108,6 +185,45 @@ export interface FbModuleCard {
color: string;
}
/**
* 首页数据(`GET /api/v1/home`)。
*
* 🔴 v54(用户 2026-10-16)—— 服务端**补齐**了网页版首页有的那几样。
* 在此之前这里写着"天气、纪念日、周统计是本地算或另拉",而端上其实是拿
* `DemoData` 的演示常量顶着 ⇒ 用户看到「寒露 / 重阳节 / 儿子生日」这种假数据。
* ⇒ 现在起这些全部**由接口给**,端上不再有演示常量。
* ⚠️ 不要改回"端上自己算农历/节气":那些表在服务端是一份权威实现,
* 两端各算一遍早晚会出现"网页说今天小寒、手机说昨天"。
*/
export interface FbHomeData {
/** 「晚上好,今天有 49 件事,慢慢来。」 */
greeting: string;
/** 'YYYY-MM-DD' */
date: string;
/** 「星期一」 */
weekday: string;
/** 抬头日期 `10 月 16 日`(服务端 `today_label`,与网页版同格式) */
todayLabel: string;
/** 农历短写 `八月廿二`(空 = 算不出) */
lunarShort: string;
/** 农历全称(含年份,详情/悬停用) */
lunarLine: string;
/** 日子标记:节气 / 节日 / 纪念日 / 生日(空数组 = 今天没有特别的) */
marks: FbMark[];
/** 天气(`null` = 没配地点或取不到 ⇒ **整块不画**,与网页版一致) */
weather: FbWeather | null;
/** 概述卡底部的四个数字(与网页版 `hb-stats` 同源) */
stat: FbStat;
/** 往年的今天(与电脑端 `Home::memories()` 同一个逻辑) */
memories: FbMemoryItem[];
/** 今天的日程(服务端已排好:全天在前、其余按时间) */
todayEvents: FbCalEvent[];
/** 近期任务(**进行中在前、待办在后**,用户 2026-10-16 要求含待办) */
tasks: FbTaskItem[];
/** 未读通知数 */
unread: number;
}
// ============================================================
// 名册 / 家谱
// ============================================================
@@ -124,6 +240,25 @@ export interface FbRosterItem {
tags: string[];
/** 头像色 */
avatarColor: string;
/**
* 头像图(服务端的 OSS 相对路径,如 `avatars/xxx.jpg`;空 = 这个人没传过头像)。
*
* ⚠️ 实测 800 人里只有 **19 人有图** ⇒ 绝大多数要**回落到色块字**
* (`FbAvatar` 的默认行为)。只有当它非空时才用 `fbImageUrl()` 显示真图。
*/
avatar: string;
/** 手机号明文(空 = 没录)。= `phones[0]` */
phone: string;
/**
* **全部电话**(主号在前)。
*
* 🔴 用户 2026-10-15:「一个人可能有多个电话号码……应该拨打最近的那个电话。」
* ⇒ 端上按"**有主号打主号**"取 `phones[0]`;
* 一个都没有时点行改为"打开详情"(点了没反应比什么都让人困惑)。
*/
phones: string[];
/** 姓名首字母(拼音,如「杨永全」→ yyq);搜索用 */
initials: string;
/** 归属:member(家里的人)/ friend(亲友通讯录) */
roster: string;
/** 生日('MM-DD' 或 '') */
@@ -132,6 +267,38 @@ export interface FbRosterItem {
isAdmin: boolean;
}
/**
* 家谱里的一个人(`GET /api/v1/kin`)。
*
* ⚠️ 与 `FbRosterItem`(名册平铺名单)**不是一个东西**:
* 名册是"我名册里的人"(可能上千),家谱是"**以我为中心的关系网**"
* (只含上下两代,十来人),关键是带**代数**与**亲属边**。
*/
export interface FbKinPerson {
id: number;
name: string;
/** 「我怎么叫他」(昵称优先) */
shown: string;
/** 「我该怎么称呼他」(服务端按图谱推导,如"岳父""外甥女") */
label: string;
/** 代数:0 = 我,负 = 长辈,正 = 晚辈 */
gen: number;
avatarColor: string;
/** 户(夫妻单元)号 */
unit: number;
parents: number[];
children: number[];
spouses: number[];
siblings: number[];
}
/** 家谱整体 */
export interface FbKinGraph {
meId: number;
people: FbKinPerson[];
total: number;
}
/** 家谱连线(kin/relate:父母 / 配偶 / 子女) */
export interface FbKinRelation {
id: number;
@@ -228,15 +395,34 @@ export interface FbTripNode {
// 日程(calendar)
// ============================================================
/** 一条日程(calendar/index.php 的格子/时间轴条目) */
/**
* 一条日程(`/api/v1/calendar` 的条目;含节日/农历/纪念日等**所有**日历条目)。
*
* 🔴 v52(用户 2026-10-15 要日程照 SyncCalendar 做)补齐了几个原本漏掉的字段 ——
* 实测接口**给了**、端上却没解析,于是"新建/编辑日程"没有 id 可用:
* · `calId` —— **所在日历本的真实 id**。缺了它,新建时就没法指定
* 往哪本写(旧代码甚至从条目里**臆造** id,见 `FbCalRepo.fetchBooks`)。
* · `endTime` —— 结束时间(时间轴上要画时长、详情要显示区间)。
* · `kind` —— 条目类型(`custom`/`anniv`/`lunar`/`holiday`…):
* 只有 `custom` 那种才是**用户自己的日程**、可点开可编辑;
* 农历/节气/节假日是**系统衍生条目**,点开也不该给"编辑"。
* · `note` —— 备注。
*/
export interface FbCalEvent {
id: number;
/**
* 所在日历本的**真实 id**(服务端条目里的 `cal`)。
* ⚠️ 0 表示"没有对应的可写日历本"(如纪念日、农历这类衍生条目)。
*/
calId: number;
/** 标题 */
title: string;
/** 日期 'YYYY-MM-DD' */
date: string;
/** 时间 'HH:mm'(全天事件为空串) */
/** 开始时间 'HH:mm'(全天事件为空串) */
time: string;
/** 结束时间 'HH:mm'(空 = 没设) */
endTime: string;
/** 所属日历本名 */
calName: string;
/** 日历本色标 */
@@ -245,6 +431,15 @@ export interface FbCalEvent {
allDay: boolean;
/** 地点(可为空) */
place: string;
/** 备注(可为空) */
note: string;
/** 条目类型:`custom` = 用户自己的日程;其余是衍生条目(农历/节气/节假日/纪念日…) */
kind: string;
}
/** 这个条目能不能当"用户日程"来编辑(只有 custom 才算) */
export function isEditableEvent(e: FbCalEvent): boolean {
return e.kind === 'custom' && e.id > 0 && e.calId > 0;
}
/** 日历本(calendar/calendars) */
@@ -338,6 +533,23 @@ export interface FbLedgerEntry {
updatedAt: string;
}
/**
* 一本账的明细(`GET /api/v1/ledger/<id>/entries`)。
*
* ⚠️ **净资产只在明细接口里**(概览 `/ledger` 不返回)
* ⇒ 账本页面必须先拉概览拿到 id、再拉明细才有汇总。
* 实测:`summary: {asset, debt, net, by_kind, count}`。
*/
export interface FbLedgerDetail {
entries: FbLedgerEntry[];
/** 净资产(分)= 资产 - 负债 */
netCent: number;
/** 总资产(分) */
assetCent: number;
/** 总负债(分) */
debtCent: number;
}
// ============================================================
// 资源(resources)
// ============================================================