Files
FamilyBook_harmonyos/entry/src/main/ets/data/FbCalSettings.ets
T
laoyang 776c95ba0b 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)。
2026-10-18 03:36:04 +08:00

161 lines
5.5 KiB
Plaintext

/**
* 家书 FamilyBook · 日程设置(鸿蒙端)
*
* 存"日程模块自己的偏好"——它们是**这台设备上的人怎么看日历**,
* 与服务器无关、也不该同步给别人。
*
* 🔴 用户 2026-10-15:「我们家书的日程功能模块,包括它的界面设计和功能,
* 跟 SyncCalendar 做得一样,就差不多。……有几种不同的视图方式,
* 然后包括下面日程的显示啊什么之类的。」
* ⇒ 对齐 SyncCalendar 的设置项:
* · **默认视图**(月 / 周 / 列表)—— 打开日程页时先用哪个
* · **日程显示方式**(时间轴 / 列表)—— 同一天里日程怎么画
* · **新建日程落到哪本日历**(默认日历本)
* · **哪些日历本可见**(隐藏不想看的那几本)
*
* 🔴 为什么用 `preferences` 而不是本地库:都是**单个键值**,
* 不是多行记录(同一范式见 `FbTokenStore` 的说明)。
* ⚠️ 必须**同步读**(`get*()`):页面一渲染就要知道用哪个视图,
* 若只能 await,每个调用点都要 async、把简单逻辑搞碎。
* ⇒ 用"启动时预加载进内存 + 写时同时更新内存"(同 FbTokenStore)。
*/
import { preferences } from '@kit.ArkData';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { common } from '@kit.AbilityKit';
const DOMAIN: number = 0x0000;
const TAG: string = 'FbCalSettings';
/** preferences 实例名 */
const STORE_NAME: string = 'fb_calendar';
const K_VIEW: string = 'default_view'; // month | week | agenda
const K_STYLE: string = 'display_style'; // timeline | list
const K_BOOK: string = 'default_book_id'; // 新建日程默认落到哪本
const K_HIDDEN: string = 'hidden_books'; // 逗号分隔的日历本 id
/** 视图:月 / 周 / 列表(与 SyncCalendar 的 VIEW_ORDER 同一套) */
export type FbCalView = 'month' | 'week' | 'agenda';
/** 日程画法:时间轴 / 列表(与 SyncCalendar 的 displayStyle 同一套) */
export type FbCalStyle = 'timeline' | 'list';
class FbCalSettingsImpl {
private view: FbCalView = 'month';
private style: FbCalStyle = 'timeline';
private bookId: number = 0;
private hidden: string = '';
private store: preferences.Preferences | null = null;
private ready: boolean = false;
/** 预加载进内存(应用启动时调一次;幂等) */
async init(ctx: common.Context): Promise<void> {
if (this.ready) {
return;
}
try {
this.store = await preferences.getPreferences(ctx, STORE_NAME);
const v: preferences.ValueType = await this.store.get(K_VIEW, 'month');
const s: preferences.ValueType = await this.store.get(K_STYLE, 'timeline');
const b: preferences.ValueType = await this.store.get(K_BOOK, 0);
const h: preferences.ValueType = await this.store.get(K_HIDDEN, '');
this.view = (v === 'week' || v === 'agenda') ? v : 'month';
this.style = (s === 'list') ? 'list' : 'timeline';
this.bookId = typeof b === 'number' ? b : 0;
this.hidden = typeof h === 'string' ? h : '';
this.ready = true;
hilog.info(DOMAIN, TAG, '日程设置预加载完成:%{public}s / %{public}s', this.view, this.style);
} catch (err) {
// ⚠️ 读不到就用默认值,**不要**让启动挂掉
hilog.warn(DOMAIN, TAG, '日程设置预加载失败:%{public}s', JSON.stringify(err));
this.ready = true;
}
}
isReady(): boolean {
return this.ready;
}
// ---- 同步读 ----
getView(): FbCalView {
return this.view;
}
getStyle(): FbCalStyle {
return this.style;
}
getBookId(): number {
return this.bookId;
}
/** 被隐藏的日历本 id 列表 */
hiddenIds(): number[] {
const out: number[] = [];
if (this.hidden === '') {
return out;
}
for (const s of this.hidden.split(',')) {
const n: number = parseInt(s, 10);
if (!isNaN(n) && n > 0) {
out.push(n);
}
}
return out;
}
/** 某本日历是否可见(默认都可见) */
isVisible(id: number): boolean {
return this.hiddenIds().indexOf(id) < 0;
}
// ---- 异步写(同时更新内存) ----
async setView(v: FbCalView): Promise<void> {
this.view = v;
await this.put(K_VIEW, v);
}
async setStyle(s: FbCalStyle): Promise<void> {
this.style = s;
await this.put(K_STYLE, s);
}
async setBookId(id: number): Promise<void> {
this.bookId = id;
await this.put(K_BOOK, id);
}
/** 设置某本日历的可见性 */
async setVisible(id: number, visible: boolean): Promise<void> {
const ids: number[] = this.hiddenIds();
const at: number = ids.indexOf(id);
if (visible && at >= 0) {
ids.splice(at, 1);
} else if (!visible && at < 0) {
ids.push(id);
}
const parts: string[] = [];
for (const n of ids) {
parts.push(n.toString());
}
this.hidden = parts.join(',');
await this.put(K_HIDDEN, this.hidden);
}
private async put(key: string, val: preferences.ValueType): Promise<void> {
try {
if (this.store === null) {
hilog.warn(DOMAIN, TAG, '还没有 store,设置没能落盘(重启会丢)');
return;
}
await this.store.put(key, val);
// ⚠️ `put` 只写内存,**必须 `flush`** 才落盘(同 FbTokenStore 的坑)
await this.store.flush();
} catch (err) {
hilog.error(DOMAIN, TAG, '日程设置保存失败:%{public}s', JSON.stringify(err));
}
}
}
/** 全局单例 */
export const FB_CAL_SETTINGS: FbCalSettingsImpl = new FbCalSettingsImpl();