v0.8.1 界面六条:删离线入口 / 标语统一 / 最近纪念日 / 图标真上色 / 真头像 / 日程四修
用户 2026-10-18 六条(全部已在真机逐条验证):
① 登录页「先不登录 / 离线使用」彻底删除
用户:「把那些不登录、看离线数据之类的提示全部去掉。以后就不再拥有这样的
功能了,把这些功能也全部删除。」
⇒ 两个按钮 + onSkip + allowSkip 参数 + Index 里的 skip 分支一起删净。
这是产品决定(不是隐藏):该路径本身就是安全缺口,v58 上了整库加密后更要堵死。
② 标语统一为「一家人用岁月写成的一本书」
用户:「这也是我们"家书"这个名字的由来。所以你不要再乱换这个标语了,
只要是想出现标语或者副标题的地方,就用这样一句话。」
· 端上 FB_DEFAULT_SLOGAN 是唯一定义处(登录页副标题 + 抽屉回落都用它);
· 抽屉左上角原来是家庭名,现在显示**标语**;
· 三层优先级(个人 slogan → 家庭 motto → 应用默认口号)**在服务端算**
(Family::slogan,与网页端首页 heroSlogan 同一套),/login 与 /me 都返回。
用户还说:「如果这个用户自己有标语……这个地方就是用户自己自定义的标语。」
真机实测:登录者杨永全自己写了「修身,齐家」⇒ 抽屉就显示他那句。
③ 首页新增「最近纪念日」(放在近期任务之后、往年的今天之前)
用户:「未来最近的三个纪念日,包括每个人的生日,以及手动填写的各个纪念日。
以倒计时形式展示:距离哪个纪念日还有多少天,距离谁谁谁的生日还有多少天。」
· /home.recent_days = Anniversary::upcoming(fam,pid,370) 取前 3 ——
取满一年:窗口太小会让"刚过完生日"的家庭一个都取不到,模块直接空着;
· 数据源 all() = 派生生日 + 自建纪念日,正好覆盖用户说的两样;
· 倒计时:0 天显示「就是今天」(写「还有 0 天」读起来像已经过了)。
④ 悬浮栏图标也上色(用户:「除了下面的汉字,上面图标的颜色也变成跟汉字一样的颜色」)
🔴🔴 真根因两层,都是"无报错失效":
· 第一层:Image.fillColor() **只对 SVG 生效**,而本工程 30+ 图标**全是 PNG**
⇒ 全工程 24 处图标染色一直是空操作(文字走 fontColor 所以只有文字变了)。
· 第二层(真机才现形):改用 colorFilter 后,矩阵写成了"系数乘 RGB"
(R' = R_原 × 系数) —— 对白色图标成立,但本工程图标是**纯黑**,
黑 × 任何系数 = 仍是黑 ⇒ 真机上图标像素依旧是 (0,0,0)。
⇒ 最终形态:`fbTint` 用 **alpha 当载体**(R' = (r/255) × A_原):
系数是归一化约定(官方灰度示例系数和为 1,量纲确定);
形状完全由原图 alpha 决定 ⇒ 黑/白图标都成立,描边抗锯齿自然过渡。
真机像素实测:日程项图标与文字同为 (176,120,64)≈#B0763A;
未选中项图标 (44,39,35) 深灰(不再是纯黑)。
⚠️ 全工程 24 处 fillColor 一并改掉;FbIconButton.tint 类型改成 string,
让"误传 Resource"变成编译错误而不是又一个静默失效。
⑤ 顶栏:真实头像 + 拉开按钮间距
用户:「右上角的头像应该是真实的……现在提醒的按钮距离头像的按钮太近了。」
· /home 给 me_avatar、/login 给 avatar_url(avatar_url() 按线上存储驱动拼好);
· 三颗按钮(同步 / 铃铛 / 头像)统一用 FbMetric.gapTopActions = 8。
⑥ 日程四处
· 「日期下面有几个点」⇒ 月视图与周视图的圆点全部删除,
连 FbDot / dots() / count / dotColor 一起清(不留死代码);
· 「农历全部都是初一」⇒ 真根因是端上 lunarOf() **硬编码 return '初一'**,
而服务端 lunarRange() 本来就每天给真实农历(title)⇒ 改取 title;
· 「列表这个视图,今天的数据缺一条」⇒ 服务端 /calendar 的 from/to **默认只给本月**,
而列表视图要显示 -7 ~ +60 天 ⇒ 端上显式传范围
(loadModule 新增 pathOverride 参数);
· 「点击了某一天之后并不明显,应该把这一天的颜色变深,而今天用一个较浅的颜色」
⇒ 照 SyncCalendar:选中 = 深色实底白字、今天 = 浅色底。
真机实测:17 号(选中)bg=#FFB0763A,18 号(今天)bg=#FFF5F0EA。
另:FbKeyStore 补上一个真机抓到的 HUKS 坑 —— isKeyItemExist 在密钥不存在时
**会抛 12000011**(不是返回 false),首次运行必被误判成"本机不支持 HUKS"
⇒ 永久明文降级。改成把"不存在"当正常分支,且降级态每次启动重试一次
(把同一把 DEK 重新用 KEK 包起来,不换密钥以免重下全部附件)。
验证:hvigor BUILD SUCCESSFUL;_fb_arkts_guard.js 328/0;
_fb_v59ui_falsify.py 19 项注入 23/0;_fb_home_api.py 60/0;
六条已在真机逐条取像素/布局树确认。
This commit is contained in:
@@ -36,7 +36,8 @@ import { FbLoginPage } from './login/FbLoginPage';
|
||||
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
||||
import { FB_SERVER_STORE } from '../data/FbServerStore';
|
||||
import { FB_DEVICE_STORE } from '../data/FbDeviceStore';
|
||||
import { FB_SESSION, SESSION_NEED_LOGIN, SESSION_OFFLINE } from '../data/FbSession';
|
||||
import { FB_SESSION, FbMeIdentityResult, SESSION_NEED_LOGIN, SESSION_OFFLINE } from '../data/FbSession';
|
||||
import { FbAccount, FbSession } from '../model/Types';
|
||||
import { fbOnUnauthorized } from '../data/FbHttp';
|
||||
import { FbDb } from '../data/FbDb';
|
||||
import { FB_ATTACH } from '../data/FbAttachQueue';
|
||||
@@ -188,6 +189,14 @@ struct Index {
|
||||
this.app.needLogin = false;
|
||||
this.app.offlineLocked = false;
|
||||
this.entered = true;
|
||||
/* 🔴 v59:把 `/me` 带回来的**身份**写进全局状态(姓名 / 真头像 /
|
||||
家庭名 / 标语)。用户 2026-10-18 两条要求都靠它:
|
||||
· 「右上角的头像应该是真实的」 → `me.avatarUrl`
|
||||
· 抽屉左上角要显示标语 → `session.slogan`
|
||||
⚠️ 必须判 `has`:断网启动时 `check()` 仍会回 SESSION_OK(离线可用),
|
||||
但**没有**身份数据。不判就会把已有的头像/标语冲成空 —— 那是
|
||||
"一次断网,头像就没了"这种最难解释的 bug。 */
|
||||
this.applyIdentity(r.identity);
|
||||
}
|
||||
this.bootChecked = true;
|
||||
}
|
||||
@@ -360,6 +369,46 @@ struct Index {
|
||||
this.reopenLocal();
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔴 v59:把 `/me` 带回来的身份写进全局状态。
|
||||
*
|
||||
* 处理的是"**启动时校验会话**"这条路径(登录那一刻的身份由登录页自己写,
|
||||
* 见 `FbLoginPage.applySession`);两条路径都必须能更新这些字段,
|
||||
* 否则会出现"登录后标语是对的,重启一次又变回旧的"。
|
||||
*
|
||||
* ⚠️ 逐项判空后再写:`check()` 在**断网**时也会回 SESSION_OK(离线可用),
|
||||
* 但那时没有身份数据。整块替换会把头像/标语清成空
|
||||
* (症状:"坐一趟飞机回来,头像和标语都没了")。
|
||||
* ⚠️ `slogan` 只在服务端给了非空值时覆盖 —— 兜底那句话由端上的
|
||||
* `FB_DEFAULT_SLOGAN` 负责,服务端不给就保持原值。
|
||||
*/
|
||||
private applyIdentity(id: FbMeIdentityResult | undefined): void {
|
||||
if (id === undefined || !id.has) {
|
||||
return;
|
||||
}
|
||||
const old: FbSession = this.app.session;
|
||||
// 姓名 / 头像 / 角色 变了才整体换(V2 对 interface 的深层属性观察不可靠,
|
||||
// 换引用才能保证读它的页面都刷新 —— 与登录页 applySession 同一理由)
|
||||
const me: FbAccount = {
|
||||
id: old.me.id,
|
||||
name: id.name !== '' ? id.name : old.me.name,
|
||||
kinship: old.me.kinship,
|
||||
avatarColor: old.me.avatarColor,
|
||||
avatarUrl: id.avatarUrl !== '' ? id.avatarUrl : old.me.avatarUrl,
|
||||
role: old.me.role,
|
||||
isGuest: old.me.isGuest,
|
||||
canWrite: old.me.canWrite,
|
||||
};
|
||||
const next: FbSession = {
|
||||
loggedIn: true,
|
||||
familyId: old.familyId,
|
||||
familyTitle: id.familyName !== '' ? id.familyName : old.familyTitle,
|
||||
slogan: id.slogan !== '' ? id.slogan : old.slogan,
|
||||
me: me,
|
||||
};
|
||||
this.app.session = next;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新建立本地库 + 附件索引(登录成功后调)。
|
||||
*
|
||||
@@ -440,26 +489,20 @@ struct Index {
|
||||
// ---- 未登录 / 被要求重新登录:登录页(**不显示抽屉/悬浮栏**)----
|
||||
// 🔴 v55:`needLogin` 是设置页「退出登录 / 更换服务器」置的位 ——
|
||||
// 本应用没有路由栈,登录页只能靠这个状态位回到前台。
|
||||
//
|
||||
// 🔴🔴 v59(用户 2026-10-18):**「先不登录 / 离线使用」已彻底移除**。
|
||||
// 用户原话:「登录页面,把那些不登录、看离线数据之类的提示全部去掉。
|
||||
// 以后就不再拥有这样的功能了,把这些功能也全部删除。」
|
||||
// ⇒ 没有 `allowSkip`、没有 `onSkip`,**登录页就是唯一下车口**。
|
||||
// ⚠️ 这是产品决定,别再"顺手"加回一个跳过按钮 —— 它本来就是个
|
||||
// 安全缺口(被踢之后不该能看任何内容),v58 上了整库加密之后更要堵死。
|
||||
FbLoginPage({
|
||||
// 传 app 只为让它拿到**状态栏高度**(全屏布局下登录页也要自己让位)
|
||||
app: this.app,
|
||||
// 从第几步开始(1=连地址一起换,2=只重登,0=自动)
|
||||
startStep: this.app.loginStartStep,
|
||||
/* 🔴 v57:只有"从没登录过"才允许「先不登录」。
|
||||
被踢 / 主动退出后必须重新登录 —— 用户原话:
|
||||
「退到登录页面之后,里面所有的资源、所有的内容都是不允许看的。」
|
||||
留着那个按钮就等于留了一扇后门(v58 上了加密才真正堵死)。 */
|
||||
allowSkip: this.app.lockReason === '',
|
||||
onLoggedIn: (): void => {
|
||||
this.onLoggedIn();
|
||||
},
|
||||
onSkip: (): void => {
|
||||
// 「先不登录」:不登录也进主界面 —— 本地优先架构下,
|
||||
// 没令牌照样能看已同步的数据。把用户堵在登录页是错的。
|
||||
// ⚠️ 必须同时复位 needLogin,否则又会被判回登录页。
|
||||
this.app.needLogin = false;
|
||||
this.app.loginStartStep = 0;
|
||||
this.entered = true;
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* 「在线读」「上传 EPUB 解析」「笔记导入」这些属于交互+文件能力,留给第二轮。
|
||||
*/
|
||||
|
||||
import { FbColors, FbMetric } from '../../common/Theme';
|
||||
import { FbColors, FbMetric, fbTint, FbTint } from '../../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../../common/Store';
|
||||
import { FbBookItem, FbGroup } from '../../model/Types';
|
||||
import { fbGroupBook } from '../../model/DemoData';
|
||||
@@ -103,12 +103,12 @@ export struct FbBooksPage {
|
||||
.layoutWeight(1)
|
||||
|
||||
// 上传 / 统计(Web 端在设置菜单里)
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_chart'), tint: FbColors.muted })
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_chart'), tint: FbTint.muted })
|
||||
// 🔴 设置按钮 = 网页版那种**三条横线**(☰)—— 用户 2026-10-16。
|
||||
// 点开是本模块的设置菜单(条目照网页版 `templates/<模块>/_settings_menu.php`)。
|
||||
FbIconButton({
|
||||
icon: $r('app.media.fb_ic_menu'),
|
||||
tint: FbColors.muted,
|
||||
tint: FbTint.muted,
|
||||
onTap: (): void => {
|
||||
this.settingsOpen = true;
|
||||
}
|
||||
@@ -232,7 +232,7 @@ export struct FbBooksPage {
|
||||
.width(11)
|
||||
.height(11)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
.colorFilter(fbTint(FbTint.faint))
|
||||
Text(b.noteCount.toString())
|
||||
.fontSize(10)
|
||||
.fontColor(FbColors.faint)
|
||||
@@ -255,7 +255,7 @@ export struct FbBooksPage {
|
||||
.width(13)
|
||||
.height(13)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
.colorFilter(fbTint(FbTint.faint))
|
||||
}
|
||||
.width('100%')
|
||||
.padding(14)
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* 旧代码从条目里臆造 id,导致新建日程根本落不到正确的本上(见 `FbCalRepo.fetchBooks`)。
|
||||
*/
|
||||
|
||||
import { FbColors, FbMetric, fbPalette } from '../../common/Theme';
|
||||
import { FbColors, FbMetric, fbPalette, FbTint } from '../../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../../common/Store';
|
||||
import { FbCalBook, FbCalEvent, isEditableEvent } from '../../model/Types';
|
||||
import { parseCalendar } from '../../data/FbParse';
|
||||
@@ -31,7 +31,7 @@ import { loadModule } from '../../data/FbModuleLoad';
|
||||
import { FbCalRepo, FbCalEventFull } from '../../data/FbCalRepo';
|
||||
import { FB_CAL_SETTINGS, FbCalView, FbCalStyle } from '../../data/FbCalSettings';
|
||||
import { FB_TOKEN_STORE } from '../../data/FbTokenStore';
|
||||
import { FbDot, FbEmptyBox, FbIconButton } from '../../components/Widgets';
|
||||
import { FbEmptyBox, FbIconButton } from '../../components/Widgets';
|
||||
import { FbEventSheet } from '../../components/FbEventSheet';
|
||||
import { FbEventEditSheet } from '../../components/FbEventEditSheet';
|
||||
import { FbCalSettingsSheet } from '../../components/FbCalSettingsSheet';
|
||||
@@ -40,7 +40,16 @@ import { FB_DOCK_RESERVE } from '../../components/FloatDock';
|
||||
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
|
||||
|
||||
/** 月历上「非本月」日期点的灰(hex 常量:格子数据结构里存的是字符串色,不能用 Resource) */
|
||||
const FB_FAINT: string = '#A8988A';
|
||||
/**
|
||||
* 列表(和月历)要覆盖的日期范围:今天 -7 ~ 今天 +60 天。
|
||||
*
|
||||
* 🔴 这两个数**必须**同时用于两处(改一处就得改另一处):
|
||||
* ① `load()` 里发给服务端的 `from`/`to`(不传的话服务端只给本月 ⇒ 跨月缺数据);
|
||||
* ② `agendaDays()` 的循环边界(页面实际渲染哪些天)。
|
||||
* 分开写死就会变成"请求了 A 段、显示 B 段"——那正是本 bug 最容易复发的形态。
|
||||
*/
|
||||
const AGENDA_BACK_DAYS: number = 7;
|
||||
const AGENDA_FWD_DAYS: number = 60;
|
||||
|
||||
/** 月历格子(一个日期) */
|
||||
interface FbCalCell {
|
||||
@@ -52,10 +61,10 @@ interface FbCalCell {
|
||||
inMonth: boolean;
|
||||
/** 是否今天 */
|
||||
isToday: boolean;
|
||||
/** 该日的条目数(0 不显示点) */
|
||||
count: number;
|
||||
/** 该日第一个条目的色(画小点用) */
|
||||
dotColor: string;
|
||||
/* ⚠️ v59:原来的 `count` / `dotColor` 已删除 —— 它们只服务于"日期下面那排
|
||||
小圆点",而用户 2026-10-18 明确要求**把那排点全部去掉**。
|
||||
留着这两个字段会让人以为"哪里还在画点",也会让 `cellOf` 白算一遍。 */
|
||||
|
||||
/** 农历/节日的短文字(初一显示月名、节日显示节日名) */
|
||||
lunar: string;
|
||||
}
|
||||
@@ -75,7 +84,7 @@ interface FbWeekDay {
|
||||
/** 一 二 三 … */
|
||||
w: string;
|
||||
isToday: boolean;
|
||||
count: number;
|
||||
/** ⚠️ v59:原来的 `count`(画小点用)已删 —— 用户要求去掉日期下那排点。 */
|
||||
lunar: string;
|
||||
}
|
||||
|
||||
@@ -176,6 +185,18 @@ export struct FbCalendarPage {
|
||||
* ⇒ 拉一次全量、端上按日期筛;切换月份/视图**不再请求**。
|
||||
*/
|
||||
private async load(): Promise<void> {
|
||||
/* 🔴🔴 v59(用户 2026-10-18):「列表这个视图,今天的数据缺一条。」
|
||||
真根因:服务端 `/calendar` 的 from/to **默认只给本月**
|
||||
(`date('Y-m-01')` ~ `date('Y-m-t')`),而列表视图要显示 `-7 ~ +60` 天。
|
||||
跨月那一段服务端压根没返回 ⇒ 落在下月的日程(含"今天往后几天")
|
||||
在列表里凭空少掉;月视图因为本来就只看本月,所以看不出来。
|
||||
⇒ 这里**显式传范围**,覆盖列表要用的那一整段(前后各留够余量)。
|
||||
⚠️ 不要只传"本月",也不要依赖服务端默认值 —— 列表与月视图用的
|
||||
`allEvents` 是同一份,范围必须**取两者所需的并集**(否则修了列表
|
||||
又让月视图翻页时缺数据)。
|
||||
⚠️ 往前 7 天 / 往后 60 天这两个数与 `agendaDays()` 是**同一套口径**,
|
||||
改一处必须改另一处(分散成两个魔法数字必然漂移)。 */
|
||||
const range: string = '/api/v1/calendar?from=' + this.agendaFrom() + '&to=' + this.agendaTo();
|
||||
await loadModule<FbCalEvent[]>('calendar', parseCalendar,
|
||||
(rows: FbCalEvent[]): void => {
|
||||
this.allEvents = rows;
|
||||
@@ -184,7 +205,20 @@ export struct FbCalendarPage {
|
||||
(note: string): void => {
|
||||
this.syncNote = note;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
range);
|
||||
}
|
||||
|
||||
/** 列表/月历所需范围的起点(今天 - 7 天,`YYYY-MM-DD`) */
|
||||
private agendaFrom(): string {
|
||||
return this.isoOfDate(new Date(new Date().getFullYear(), new Date().getMonth(),
|
||||
new Date().getDate() - AGENDA_BACK_DAYS));
|
||||
}
|
||||
|
||||
/** 列表/月历所需范围的终点(今天 + 60 天,`YYYY-MM-DD`) */
|
||||
private agendaTo(): string {
|
||||
return this.isoOfDate(new Date(new Date().getFullYear(), new Date().getMonth(),
|
||||
new Date().getDate() + AGENDA_FWD_DAYS));
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
@@ -205,6 +239,11 @@ export struct FbCalendarPage {
|
||||
return dt.getFullYear().toString() + '-' + this.pad(dt.getMonth() + 1) + '-' + this.pad(dt.getDate());
|
||||
}
|
||||
|
||||
/** `Date` → `YYYY-MM-DD`(服务端 from/to 用的格式) */
|
||||
private isoOfDate(dt: Date): string {
|
||||
return this.isoOf(dt.getFullYear(), dt.getMonth() + 1, dt.getDate());
|
||||
}
|
||||
|
||||
/** 只看"可见的日历本"里的条目(设置里能逐本关掉) */
|
||||
private visibleEvents(): FbCalEvent[] {
|
||||
if (this.books.length === 0) {
|
||||
@@ -271,10 +310,22 @@ export struct FbCalendarPage {
|
||||
return this.shortTitle(e.title);
|
||||
}
|
||||
}
|
||||
// 其次:初一(`kind=lunar` 的条目只在初一有)
|
||||
// 其次:农历日名
|
||||
//
|
||||
// 🔴🔴 2026-10-18 修的真 bug(用户:「农历全部都是初一,说明是错误的数据」):
|
||||
// 这里原来**硬编码返回 `'初一'`**,注释还写着"`kind=lunar` 的条目只在初一有" ——
|
||||
// 而事实相反:服务端 `Calendar::month()` 里 `Holiday::lunarRange()` 是
|
||||
// **每一天都出一条**(`title` = `Lunar::monthName()`(初一显示月名)
|
||||
// 或 `Lunar::dayName()`),端上把这个真值丢掉了,于是**整月都显示"初一"**。
|
||||
// ⇒ 直接取服务端给的 title(它本来就是"这一天的农历文字")。
|
||||
// ⚠️ title 可能带节日后缀(`八月十五 · 中秋`)—— `shortTitle` 截前 3 字,
|
||||
// 而这张格子里只有 8sp 的位置,本来就该显示短的。
|
||||
for (const e of this.allEvents) {
|
||||
if (e.date === iso && e.kind === 'lunar') {
|
||||
return '初一';
|
||||
const t: string = this.shortTitle(e.title);
|
||||
if (t !== '') {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
@@ -333,25 +384,13 @@ export struct FbCalendarPage {
|
||||
|
||||
private cellOf(y: number, m: number, d: number, inMonth: boolean, today: string): FbCalCell {
|
||||
const iso: string = this.isoOf(y, m, d);
|
||||
const list: FbCalEvent[] = this.eventsOf(iso);
|
||||
// 只数"用户日程",不算农历/节气(否则每个格子都有一点)
|
||||
let n: number = 0;
|
||||
let color: string = FB_FAINT;
|
||||
for (const e of list) {
|
||||
if (e.kind === 'custom' || e.kind === 'anniv') {
|
||||
if (n === 0) {
|
||||
color = e.color;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
}
|
||||
// ⚠️ v59:这里原来还要遍历一遍 events 去数"有几条"(为画小点用)——
|
||||
// 那排点已被用户要求删除 ⇒ 这次遍历**整段去掉**(格子现在是 O(1) 的)。
|
||||
const c: FbCalCell = {
|
||||
day: d,
|
||||
iso: iso,
|
||||
inMonth: inMonth,
|
||||
isToday: iso === today,
|
||||
count: n,
|
||||
dotColor: color,
|
||||
lunar: inMonth ? this.lunarOf(iso) : ''
|
||||
};
|
||||
return c;
|
||||
@@ -381,16 +420,11 @@ export struct FbCalendarPage {
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const dt: Date = new Date(monday.getFullYear(), monday.getMonth(), monday.getDate() + i);
|
||||
const iso: string = this.isoOf(dt.getFullYear(), dt.getMonth() + 1, dt.getDate());
|
||||
const list: FbCalEvent[] = this.eventsOf(iso);
|
||||
let n: number = 0;
|
||||
for (const e of list) {
|
||||
if (e.kind === 'custom' || e.kind === 'anniv') {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
// ⚠️ v59:这里原来查一遍 events 只为数"有几条"(画小点用)——
|
||||
// 那排点已按用户要求删除 ⇒ 整段去掉。
|
||||
out.push({
|
||||
iso: iso, day: dt.getDate(), w: this.weekLabels[i],
|
||||
isToday: iso === today, count: n, lunar: this.lunarOf(iso)
|
||||
isToday: iso === today, lunar: this.lunarOf(iso)
|
||||
});
|
||||
}
|
||||
return out;
|
||||
@@ -404,8 +438,8 @@ export struct FbCalendarPage {
|
||||
private agendaDays(): FbAgendaDay[] {
|
||||
const out: FbAgendaDay[] = [];
|
||||
const now: Date = new Date();
|
||||
// 往前 7 天到往后 60 天(够用且不至于太长)
|
||||
for (let i = -7; i <= 60; i++) {
|
||||
// ⚠️ 边界必须与 `load()` 请求的 from/to 一致(见 AGENDA_BACK_DAYS 的说明)
|
||||
for (let i = -AGENDA_BACK_DAYS; i <= AGENDA_FWD_DAYS; i++) {
|
||||
const dt: Date = new Date(now.getFullYear(), now.getMonth(), now.getDate() + i);
|
||||
const iso: string = this.isoOf(dt.getFullYear(), dt.getMonth() + 1, dt.getDate());
|
||||
const list: FbCalEvent[] = this.eventsOf(iso);
|
||||
@@ -705,7 +739,7 @@ export struct FbCalendarPage {
|
||||
// (网页版日程页的 `_settings_menu.php` 用的就是那个三横线 SVG。)
|
||||
FbIconButton({
|
||||
icon: $r('app.media.fb_ic_menu'),
|
||||
tint: FbColors.muted,
|
||||
tint: FbTint.muted,
|
||||
onTap: (): void => {
|
||||
this.settingsOpen = true;
|
||||
}
|
||||
@@ -865,29 +899,27 @@ export struct FbCalendarPage {
|
||||
Text(d.w)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
/* 🔴 v59:选中 / 今天的配色与月视图**同一套**(选中深底白字、
|
||||
今天浅底)—— 用户要的是"点哪天哪天明显",两处不一致会让人
|
||||
以为换视图就丢了选中。判据同样走方法(见 `isPickedCell` 的说明)。 */
|
||||
Text(d.day.toString())
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontWeight(d.isToday || d.iso === this.pickedIso
|
||||
.fontWeight(d.iso === this.pickedIso || d.isToday
|
||||
? FontWeight.Medium : FontWeight.Normal)
|
||||
.fontColor(d.isToday ? Color.White
|
||||
: (d.iso === this.pickedIso ? this.accent : FbColors.ink))
|
||||
.fontColor(d.iso === this.pickedIso
|
||||
? Color.White
|
||||
: FbColors.ink)
|
||||
.width(30)
|
||||
.height(30)
|
||||
.textAlign(TextAlign.Center)
|
||||
.borderRadius(15)
|
||||
.backgroundColor(d.isToday
|
||||
.backgroundColor(d.iso === this.pickedIso
|
||||
? this.accent
|
||||
: (d.iso === this.pickedIso
|
||||
? fbPalette(this.accent).brand + '1F' : Color.Transparent))
|
||||
// 该日条目数(小圆点,最多 3 个)
|
||||
Row({ space: 2 }) {
|
||||
if (d.count > 0) {
|
||||
ForEach(this.dots(d.count), (i: number) => {
|
||||
FbDot({ dia: 4, fillC: this.accent })
|
||||
}, (i: number): string => 'wd_' + d.iso + '_' + i.toString())
|
||||
}
|
||||
}
|
||||
.height(5)
|
||||
: (d.isToday ? fbPalette(this.accent).wash : Color.Transparent))
|
||||
/* 🔴 v59(用户 2026-10-18):「日期下面有几个点,合格都去掉。」
|
||||
⇒ 周视图这一排小圆点也删(同一条要求,别只改月视图)。
|
||||
⚠️ 留等高占位,避免周条高度变化。 */
|
||||
Row().height(5)
|
||||
if (d.lunar !== '') {
|
||||
Text(d.lunar)
|
||||
.fontSize(8)
|
||||
@@ -990,42 +1022,50 @@ export struct FbCalendarPage {
|
||||
@Builder
|
||||
dayCell(c: FbCalCell) {
|
||||
Column({ space: 2 }) {
|
||||
/* 🔴🔴 v59(用户 2026-10-18):「现在点击了某一天之后,并不明显,
|
||||
应该把这一天的颜色变深,而今天用一个较浅的颜色来显示,
|
||||
你参考 synccalendar 的做法。」
|
||||
⇒ 照本机范本 SyncCalendar 的 `weekStrip`:
|
||||
· **选中** → `selected_bg` **深色实底** + 白字(最强调);
|
||||
· **今天** → `today_bg` **浅色底** + 常规文字色(弱强调)。
|
||||
原来的写法正好**反了**:今天用实底(`this.accent` + 白字)抢走了
|
||||
全部注意力,而选中的那天只有一层 12% 的淡色 —— 点下去几乎看不出变化。
|
||||
⚠️ 两个都免得同时成立(选中的那天可能也是今天):**选中优先**,
|
||||
否则"我今天点了今天,却看不到选中效果"。
|
||||
⚠️ 深浅对照是**这套配色的核心**:选中实底、今天浅底 —— 别再改回去。 */
|
||||
Text(c.day.toString())
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontWeight(c.isToday || c.iso === this.pickedIso ? FontWeight.Medium : FontWeight.Normal)
|
||||
.fontColor(c.isToday
|
||||
.fontWeight(this.isPickedCell(c) || c.isToday ? FontWeight.Medium : FontWeight.Normal)
|
||||
.fontColor(this.isPickedCell(c)
|
||||
? Color.White
|
||||
: (c.inMonth ? FbColors.ink : FbColors.faint))
|
||||
.width(26)
|
||||
.height(26)
|
||||
.textAlign(TextAlign.Center)
|
||||
.borderRadius(13)
|
||||
.backgroundColor(c.isToday
|
||||
? this.accent
|
||||
: (c.iso === this.pickedIso && c.inMonth
|
||||
? fbPalette(this.accent).brand + '1F'
|
||||
.backgroundColor(this.isPickedCell(c)
|
||||
? this.accent // 选中:深色实底
|
||||
: (c.isToday ? fbPalette(this.accent).wash // 今天:浅色底
|
||||
: Color.Transparent))
|
||||
|
||||
// 农历/节日(有才显示)
|
||||
if (c.lunar !== '') {
|
||||
Text(c.lunar)
|
||||
.fontSize(8)
|
||||
.fontColor(c.iso === this.pickedIso && c.inMonth ? FbColors.muted : FbColors.faint)
|
||||
.fontColor(this.isPickedCell(c) && c.inMonth ? FbColors.muted : FbColors.faint)
|
||||
.maxLines(1)
|
||||
} else {
|
||||
// 没有农历就留出等高的空,免得两行格子和一行格子高度不一
|
||||
Row().height(10)
|
||||
}
|
||||
|
||||
// 事件小点(最多 3 个)
|
||||
Row({ space: 2 }) {
|
||||
if (c.count > 0) {
|
||||
ForEach(this.dots(c.count), (i: number) => {
|
||||
FbDot({ dia: 4, fillC: c.dotColor })
|
||||
}, (i: number): string => 'dot_' + c.iso + '_' + i.toString())
|
||||
}
|
||||
}
|
||||
.height(5)
|
||||
/* 🔴 v59(用户 2026-10-18):「日程,日期下面有几个点,合格都去掉。」
|
||||
⇒ 那一排小圆点**已删除**(连同 `FbDot` 的引用与 `dots()` 辅助函数
|
||||
在月视图的调用点)。用户认为它是噪音 —— 日程内容在下面那块
|
||||
「选中日的日程」里看得更清楚,格子里不需要再暗示"有几条"。
|
||||
⚠️ 周视图的圆点也一并去掉(同一条要求),别只删月视图。
|
||||
⚠️ 这里**保留等高占位**:不留的话格子高度会变,7 行月历会整体上移。 */
|
||||
Row().height(5)
|
||||
}
|
||||
.width('100%')
|
||||
.height(48)
|
||||
@@ -1041,6 +1081,18 @@ export struct FbCalendarPage {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 这一格是不是"当前选中的那天"。
|
||||
*
|
||||
* ⚠️ 必须是**方法调用**(build 里读 `this.pickedIso`)而不是把结果当参数传进
|
||||
* `dayCell` —— 后者按值传递,@Builder 的多值参数不参与观察,
|
||||
* 选中高亮会**永远停在初始那天**(本项目已在 FbTagChip 上踩过同一个坑,
|
||||
* 见本文件 `dayPanel()` 上那段说明)。
|
||||
*/
|
||||
private isPickedCell(c: FbCalCell): boolean {
|
||||
return c.inMonth && c.iso === this.pickedIso;
|
||||
}
|
||||
|
||||
/**
|
||||
* ⚠️ 日程条目行(列表模式)已经搬进 `FbDayEventPanel` 组件里
|
||||
* (月/周/列表三处共用同一份 + 跟随"显示方式"设置)——
|
||||
@@ -1057,14 +1109,4 @@ export struct FbCalendarPage {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 造「点」的索引数组(ForEach 需要数组,不能直接循环次数) */
|
||||
private dots(count: number): number[] {
|
||||
const n: number = Math.min(3, count);
|
||||
const out: number[] = [];
|
||||
for (let i = 0; i < n; i++) {
|
||||
out.push(i);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
* 因为「还有几天」是这个模块唯一真正要一眼看到的信息。
|
||||
*/
|
||||
|
||||
import { FbColors, FbMetric } from '../../common/Theme';
|
||||
import { FbColors, FbMetric, FbTint } from '../../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../../common/Store';
|
||||
import { FbAnniversaryItem, FbKinChip } from '../../model/Types';
|
||||
import { parseDays } from '../../data/FbParse';
|
||||
@@ -112,7 +112,7 @@ export struct FbDaysPage {
|
||||
// 点开是本模块的设置菜单(条目照网页版 `templates/<模块>/_settings_menu.php`)。
|
||||
FbIconButton({
|
||||
icon: $r('app.media.fb_ic_menu'),
|
||||
tint: FbColors.muted,
|
||||
tint: FbTint.muted,
|
||||
onTap: (): void => {
|
||||
this.settingsOpen = true;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
|
||||
import { FbColors, FbMetric } from '../../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../../common/Store';
|
||||
import { FbCalEvent, FbHomeData, FbMark, FbMemoryItem, FbTaskItem, FbWeather } from '../../model/Types';
|
||||
import { FbCalEvent, FbHomeData, FbMark, FbMemoryItem, FbRecentDay, FbTaskItem,
|
||||
FbWeather } from '../../model/Types';
|
||||
import { parseHome } from '../../data/FbParse';
|
||||
import { loadModule } from '../../data/FbModuleLoad';
|
||||
import { FB_CAL_SETTINGS, FbCalStyle } from '../../data/FbCalSettings';
|
||||
@@ -91,6 +92,8 @@ export struct FbHomePage {
|
||||
@Local statOverdue: number = 0;
|
||||
/** 往年的今天(`/home` 的 memories) */
|
||||
@Local memories: FbMemoryItem[] = [];
|
||||
/** 🔴 v59:最近三个纪念日(`/home` 的 recent_days,服务端已排好序) */
|
||||
@Local recentDays: FbRecentDay[] = [];
|
||||
/** 顶部提示(同步失败等) */
|
||||
@Local syncNote: string = '';
|
||||
/**
|
||||
@@ -129,6 +132,7 @@ export struct FbHomePage {
|
||||
this.statTodo = d.stat.todo;
|
||||
this.statOverdue = d.stat.overdue;
|
||||
this.memories = d.memories;
|
||||
this.recentDays = d.recentDays;
|
||||
},
|
||||
(note: string): void => {
|
||||
this.syncNote = note;
|
||||
@@ -425,10 +429,58 @@ export struct FbHomePage {
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12 })
|
||||
|
||||
// ==================== ④ 往年的今天 ====================
|
||||
// ==================== ④ 最近纪念日 ====================
|
||||
// 🔴🔴 v59(用户 2026-10-18):
|
||||
// 「首页应该加一个『最近纪念日』模块,放在『往年的今天』前面、
|
||||
// 『近期任务』后面。内容包括:
|
||||
// 1. 未来最近的三个纪念日,包括每个人的生日,以及手动填写的各个纪念日。
|
||||
// 2. 以倒计时形式展示:距离哪个纪念日还有多少天,
|
||||
// 距离谁谁谁的生日还有多少天。」
|
||||
// ⇒ 位置就卡在这两段之间(**不是**"放在首页某处")。
|
||||
// ⇒ 数据来自 `/home` 的 `recent_days`(服务端已排好序、最多 3 条),
|
||||
// 来源是"派生生日 + 自建纪念日",正好覆盖用户说的两样。
|
||||
Column({ space: 0 }) {
|
||||
FbSectionTitle({
|
||||
title: '最近纪念日',
|
||||
extra: this.recentDays.length > 0
|
||||
? this.recentDays.length.toString() + ' 个'
|
||||
: ''
|
||||
})
|
||||
Column({ space: 0 }) {
|
||||
if (this.recentDays.length === 0) {
|
||||
Column({ space: 6 }) {
|
||||
Text('还没有要过的日子')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.ink)
|
||||
Text('在「纪念日」里添一个,或者给家人的资料里填上生日。')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.muted)
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.padding({ top: 12, bottom: 12 })
|
||||
} else {
|
||||
ForEach(this.recentDays, (d: FbRecentDay, idx: number) => {
|
||||
this.recentDayRow(d, idx === this.recentDays.length - 1)
|
||||
}, (d: FbRecentDay, idx: number): string =>
|
||||
'rd_' + d.date + '_' + d.title + '_' + idx.toString())
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.cardPad, right: FbMetric.cardPad, top: 4, bottom: 6 })
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12 })
|
||||
|
||||
// ==================== ⑤ 往年的今天 ====================
|
||||
// 🔴 用户 2026-10-17:「近期任务的下面,应该加上**往年的今天**,
|
||||
// 跟电脑端一样,你看看那个是什么逻辑。」
|
||||
// ⇒ 位置**就在近期任务下面**(用户明确指定,不是"放在首页某处")。
|
||||
// ⚠️ v59 起它上面多了一块「最近纪念日」(用户 2026-10-18 指定插在
|
||||
// 「近期任务之后、往年的今天之前」)—— 两块都是"日子"类信息,
|
||||
// 顺序是用户定的,别再调。
|
||||
// 电脑端的逻辑见 `App\Home::memories()`:只认**往年的同一天**
|
||||
// (不退回同月),来源是「看过的电影」+「走过的**实际行程**」,
|
||||
// 合并按时间倒序取 6 条。
|
||||
@@ -643,4 +695,106 @@ export struct FbHomePage {
|
||||
private memoryDate(s: string): string {
|
||||
return s.length >= 10 ? s.substring(0, 10) : s;
|
||||
}
|
||||
|
||||
/**
|
||||
* 「最近纪念日」里的一行(v59,用户 2026-10-18)。
|
||||
*
|
||||
* 形态:**倒计时徽标**(`还有 12 天`)+ 标题 + 日期/周岁。
|
||||
* 用户要的就是这个倒计时:「距离哪个纪念日还有多少天,
|
||||
* 距离谁谁谁的生日还有多少天」—— `title` 里已经带了姓名
|
||||
* (服务端拼的「老杨 的生日」),所以一句话读完就是用户要的表达。
|
||||
*
|
||||
* ⚠️ 与 `memoryRow` 同一个反模式提醒:这里也是**两参数 @Builder**。
|
||||
* 它安全的前提是"整块由 ForEach 按 key 重建"(数据一次性到齐)——
|
||||
* 若将来出现"数据变了但行文字没变",**先改成组件**(如 `FbStatCell`),
|
||||
* 别在这个 Builder 上打补丁。
|
||||
*/
|
||||
@Builder
|
||||
recentDayRow(d: FbRecentDay, isLast: boolean) {
|
||||
Column({ space: 0 }) {
|
||||
Row({ space: 10 }) {
|
||||
// ---- 倒计时徽标:把"还剩几天"做成一眼可见的那一块 ----
|
||||
// 今天那种用实底强调(它是"就是今天",比"还有 N 天"更该被看见)
|
||||
Text(this.countdownText(d.days))
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontWeight(d.days === 0 ? FontWeight.Medium : FontWeight.Normal)
|
||||
.fontColor(d.days === 0 ? Color.White : this.recentTint(d.source))
|
||||
.backgroundColor(d.days === 0 ? this.recentTint(d.source) : this.recentWash(d.source))
|
||||
.borderRadius(FbMetric.radiusPill)
|
||||
.padding({ left: 9, right: 9, top: 3, bottom: 3 })
|
||||
.textAlign(TextAlign.Center)
|
||||
|
||||
// ---- 标题(已含姓名 / 纪念日名)----
|
||||
Text(d.title)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.ink)
|
||||
.layoutWeight(1)
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
|
||||
// ---- 右侧:日期(+ 周岁 / 第几个,有才显示)----
|
||||
Text(this.recentMeta(d))
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
.maxLines(1)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ top: 11, bottom: 11 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
if (!isLast) {
|
||||
Divider()
|
||||
.color(FbColors.line2)
|
||||
.margin({ left: 18 })
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
|
||||
/**
|
||||
* 倒计时文案。
|
||||
*
|
||||
* ⚠️ `days === 0` 必须说「就是今天」而不是「还有 0 天」——
|
||||
* 后者读起来像"已经过了",正是最容易搞反的地方。
|
||||
*/
|
||||
private countdownText(days: number): string {
|
||||
if (days <= 0) {
|
||||
return '就是今天';
|
||||
}
|
||||
return '还有 ' + days.toString() + ' 天';
|
||||
}
|
||||
|
||||
/**
|
||||
* 右侧那行小字:日期 +(周岁 / 第几个)。
|
||||
*
|
||||
* · 日期一律显示(`MM-DD`,年份对"年年过"的纪念日没有信息量);
|
||||
* · `age >= 0` ⇒ 生日,附「N 岁」(0 岁是**真实存在**的,不能当"没有");
|
||||
* · `nth >= 1` ⇒ 用户填了起始年,附「第 N 个」。
|
||||
*/
|
||||
private recentMeta(d: FbRecentDay): string {
|
||||
const md: string = d.date.length >= 10 ? d.date.substring(5, 10) : d.date;
|
||||
const parts: string[] = [md];
|
||||
if (d.age >= 0) {
|
||||
parts.push(d.age.toString() + ' 岁');
|
||||
}
|
||||
if (d.nth >= 1) {
|
||||
parts.push('第 ' + d.nth.toString() + ' 个');
|
||||
}
|
||||
return parts.join(' · ');
|
||||
}
|
||||
|
||||
/** 纪念日的强调色:生日用粉、其余用紫(与网页版 `hb-mark` 的 birthday/anniv 一致) */
|
||||
private recentTint(source: string): string {
|
||||
return this.isBirthday(source) ? '#A03060' : '#6B4A86';
|
||||
}
|
||||
|
||||
/** 对应的极浅底(与上面同一档色的淡化) */
|
||||
private recentWash(source: string): string {
|
||||
return this.isBirthday(source) ? '#FDECF3' : '#F2ECF7';
|
||||
}
|
||||
|
||||
/** 是不是"生日"那类(服务端派生的生日条目;`custom` 是用户自己填的纪念日) */
|
||||
private isBirthday(source: string): boolean {
|
||||
return source === 'birthday_solar' || source === 'birthday_lunar';
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* 任何地方都不要拿「元」去加总 —— 浮点误差会越滚越大。
|
||||
*/
|
||||
|
||||
import { FbColors, FbMetric } from '../../common/Theme';
|
||||
import { FbColors, FbMetric, fbTint, FbTint } from '../../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../../common/Store';
|
||||
import { FbGroup, FbLedgerBook, FbLedgerEntry, FbLedgerDetail} from '../../model/Types';
|
||||
import { fbLedgerKindName, fbMoney } from '../../model/DemoData';
|
||||
@@ -155,7 +155,7 @@ export struct FbLedgerPage {
|
||||
// 点开是本模块的设置菜单(条目照网页版 `templates/<模块>/_settings_menu.php`)。
|
||||
FbIconButton({
|
||||
icon: $r('app.media.fb_ic_menu'),
|
||||
tint: FbColors.muted,
|
||||
tint: FbTint.muted,
|
||||
onTap: (): void => {
|
||||
this.settingsOpen = true;
|
||||
}
|
||||
@@ -251,7 +251,7 @@ export struct FbLedgerPage {
|
||||
.width(13)
|
||||
.height(13)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
.colorFilter(fbTint(FbTint.faint))
|
||||
Text('净资产变化(盘点成线)')
|
||||
.fontSize(10)
|
||||
.fontColor(FbColors.faint)
|
||||
|
||||
@@ -20,9 +20,17 @@
|
||||
*
|
||||
* 🔴 三种进入方式(都要有,别只做第一种):
|
||||
* ① **登录**:地址 + 账号密码(正常路径);
|
||||
* ② **先不登录**:本地优先架构下,没令牌照样能看已同步的数据。
|
||||
* 把用户堵在登录页是**错的**(他可能只是没网,或家里老人不需要同步);
|
||||
* ③ 已登录(令牌还在)时**根本不显示本页**(在 Index 里就分流掉了)。
|
||||
* ② 已登录(令牌还在)时**根本不显示本页**(在 Index 里就分流掉了)。
|
||||
*
|
||||
* 🔴🔴 v59(用户 2026-10-18):**「先不登录 / 离线使用」这两个出口已彻底删除**。
|
||||
* 用户原话:「登录页面,把那些不登录、看离线数据之类的提示全部去掉。
|
||||
* 以后就不再拥有这样的功能了,把这些功能也全部删除。」
|
||||
* ⇒ 下面这些一起删干净(不能再留半截):
|
||||
* · 两个「先不登录…」按钮及其 `onSkip` 回调;
|
||||
* · `allowSkip` 参数(它本来是"首次允许、被踢不允许"的开关,现在**一律不允许**);
|
||||
* · `Index.ets` 里 `onSkip` 的分支与"没令牌也进主界面"的逻辑。
|
||||
* ⚠️ 它是**产品决定**(不是"暂时隐藏"):这条路径本身就是个安全缺口
|
||||
* (被踢之后本来就不该能看任何内容),v58 上了整库加密之后更要堵死。
|
||||
*
|
||||
* ⚠️ 密码**不做任何本地缓存**:只在提交那一刻用一次,用完立刻清
|
||||
* (连变量都尽量短命)。App 里唯一要留的是**令牌**。
|
||||
@@ -48,6 +56,7 @@ import { FbSyncAllResult, syncAll } from '../../data/FbSyncAll';
|
||||
// 这个 `FB_SESSION` 是**会话状态机**(校验/离线计时),见 data/FbSession.ets
|
||||
import { FB_SESSION } from '../../data/FbSession';
|
||||
import { FbAccount, FbSession } from '../../model/Types';
|
||||
import { FB_DEFAULT_SLOGAN } from '../../model/DemoData';
|
||||
|
||||
const DOMAIN: number = 0x0000;
|
||||
const TAG: string = 'FbLoginPage';
|
||||
@@ -73,9 +82,6 @@ export struct FbLoginPage {
|
||||
/** 登录成功回调(由 Index 传入:进主界面) */
|
||||
@Event onLoggedIn: () => void = (): void => {
|
||||
};
|
||||
/** 「先不登录」回调(不登录也进主界面) */
|
||||
@Event onSkip: () => void = (): void => {
|
||||
};
|
||||
/**
|
||||
* 从第几步开始(0 = 按本地有没有地址自动决定)。
|
||||
*
|
||||
@@ -86,18 +92,6 @@ export struct FbLoginPage {
|
||||
*/
|
||||
@Param startStep: number = 0;
|
||||
|
||||
/**
|
||||
* 🔴 v57(用户 2026-10-17):是否允许「先不登录 / 离线使用」。
|
||||
*
|
||||
* `true` = 从没登录过(首次使用)—— 本地优先,不该把人堵在登录页;
|
||||
* `false` = 被服务端踢了、或用户主动退出登录 ——
|
||||
* 用户原话:「退到登录页面之后,里面所有的资源、所有的内容都是不允许看的。」
|
||||
* ⇒ 这时候留一个"先不登录"按钮,等于给安全策略开了个后门。
|
||||
*
|
||||
* ⚠️ `@Param` 由 `Index` 按 `app.lockReason === ''` 决定,登录页自己不改它。
|
||||
*/
|
||||
@Param allowSkip: boolean = true;
|
||||
|
||||
/** 当前步骤:1 = 填服务器地址,2 = 填账号密码 */
|
||||
@Local step: number = 1;
|
||||
/** 服务器地址输入框 */
|
||||
@@ -316,6 +310,9 @@ export struct FbLoginPage {
|
||||
name: res.personName !== '' ? res.personName : old.me.name,
|
||||
kinship: old.me.kinship,
|
||||
avatarColor: old.me.avatarColor,
|
||||
/* 🔴 v59:真头像 URL(服务端 `avatar_url()` 给的,空串=没传过头像)
|
||||
—— 顶栏右上角显示真图就靠它,用户 2026-10-18:「头像应该是真实的」。 */
|
||||
avatarUrl: res.avatarUrl,
|
||||
role: res.role !== '' ? res.role : old.me.role,
|
||||
isGuest: res.role === 'guest',
|
||||
canWrite: res.role !== '' && res.role !== 'guest',
|
||||
@@ -324,7 +321,14 @@ export struct FbLoginPage {
|
||||
loggedIn: true,
|
||||
familyId: old.familyId,
|
||||
familyTitle: res.familyName !== '' ? res.familyName : old.familyTitle,
|
||||
slogan: old.slogan,
|
||||
/* 🔴 v59:标语由服务端按 **个人 → 家庭 → 应用默认** 三层算好
|
||||
(`Family::slogan()`,与网页端首页同一个函数)。
|
||||
有个人 slogan 就是他那句、没有就是家庭 motto、再没有才是默认口号。
|
||||
⚠️ 服务端没给(老版本)时回落端上的 `FB_DEFAULT_SLOGAN`
|
||||
—— 但**不要**覆盖掉已有的非空值(避免把用户的个性标语冲掉)。 */
|
||||
slogan: res.slogan !== ''
|
||||
? res.slogan
|
||||
: (old.slogan !== '' ? old.slogan : FB_DEFAULT_SLOGAN),
|
||||
me: me,
|
||||
};
|
||||
this.app.session = next;
|
||||
@@ -367,9 +371,18 @@ export struct FbLoginPage {
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(this.step === 1 ? '先连上你家的服务器' : '一家人的名册、日程与记忆')
|
||||
/* 🔴 v59(用户 2026-10-18):副标题**一律**用应用口号。
|
||||
用户原话:「我们应用的口号或者标语是:一家人用岁月写成的一本书。
|
||||
……只要是想出现标语或者副标题的地方,就用这样一句话。」
|
||||
⚠️ 原来这里是「先连上你家的服务器」/「一家人的名册、日程与记忆」
|
||||
—— 两句都在这次全部换掉,别再各写一句。
|
||||
⚠️ 取 `FB_DEFAULT_SLOGAN` 常量而不是就地写字符串:
|
||||
口号只允许有**一个**定义处(详见 model/DemoData 里的说明)。 */
|
||||
Text(FB_DEFAULT_SLOGAN)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.muted)
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
@@ -455,21 +468,6 @@ export struct FbLoginPage {
|
||||
.textAlign(TextAlign.Center)
|
||||
.width('100%')
|
||||
.margin({ top: 10 })
|
||||
|
||||
// 没服务器也能进(看本机已同步的数据)
|
||||
// 🔴 v57:只有「从没登录过」才给这个出口(见 allowSkip 的说明)
|
||||
if (this.allowSkip) {
|
||||
Text('先不连接,离线使用')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.muted)
|
||||
.textAlign(TextAlign.Center)
|
||||
.width('100%')
|
||||
.padding({ top: 16, bottom: 16 })
|
||||
.margin({ top: 6 })
|
||||
.onClick((): void => {
|
||||
this.onSkip();
|
||||
})
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad })
|
||||
@@ -575,21 +573,6 @@ export struct FbLoginPage {
|
||||
this.submit();
|
||||
})
|
||||
|
||||
// ---- 先不登录 ----
|
||||
// 🔴 v57:被踢 / 主动退出后**没有**这个出口(见 allowSkip 的说明)
|
||||
if (this.allowSkip) {
|
||||
Text('先不登录(看本地已有的数据)')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.muted)
|
||||
.textAlign(TextAlign.Center)
|
||||
.width('100%')
|
||||
.padding({ top: 16, bottom: 16 })
|
||||
.margin({ top: 6 })
|
||||
.onClick((): void => {
|
||||
this.onSkip();
|
||||
})
|
||||
}
|
||||
|
||||
Text('账号和密码与网页版是同一套;手机号或姓名都可以。')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* 断网时更是完全空白。**先渲染本地、再后台同步**是这套架构的要点。
|
||||
*/
|
||||
|
||||
import { FbColors, FbMetric, fbPalette } from '../../common/Theme';
|
||||
import { FbColors, FbMetric, fbPalette, fbTint, FbTint } from '../../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../../common/Store';
|
||||
import { FbKinChip } from '../../model/Types';
|
||||
import { FbCinemaItem } from '../../data/FbMovieRepo';
|
||||
@@ -427,7 +427,7 @@ export struct FbMoviesPage {
|
||||
// 它是观影模块的一部分,从设置菜单里进(与网页版一致)。
|
||||
FbIconButton({
|
||||
icon: $r('app.media.fb_ic_menu'),
|
||||
tint: FbColors.muted,
|
||||
tint: FbTint.muted,
|
||||
dia: 36,
|
||||
onTap: (): void => {
|
||||
this.settingsOpen = true;
|
||||
@@ -444,7 +444,7 @@ export struct FbMoviesPage {
|
||||
.width(15)
|
||||
.height(15)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
.colorFilter(fbTint(FbTint.faint))
|
||||
Text('搜索片名、影院、观影人…')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.faint)
|
||||
@@ -581,7 +581,7 @@ export struct FbMoviesPage {
|
||||
.width(22)
|
||||
.height(22)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
.colorFilter(fbTint(FbTint.faint))
|
||||
Text(mv.title)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.muted)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* 每圈一张卡,卡里按人列。关系表达上等价,宽度可控。
|
||||
*/
|
||||
|
||||
import { FbColors, FbMetric } from '../../common/Theme';
|
||||
import { FbColors, FbMetric, fbTint, FbTint } from '../../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../../common/Store';
|
||||
import { FbKinGraph, FbKinPerson, FbRosterItem } from '../../model/Types';
|
||||
import { parseKin } from '../../data/FbParse';
|
||||
@@ -606,7 +606,7 @@ export struct FbPeoplePage {
|
||||
.width(15)
|
||||
.height(15)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
.colorFilter(fbTint(FbTint.faint))
|
||||
TextInput({ placeholder: '搜索:姓名 / 拼音首字母(yyq)/ 手机 / 单位', text: this.keyword })
|
||||
.placeholderColor(FbColors.faint)
|
||||
.placeholderFont({ size: FbMetric.fontSmall })
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* 那一排「类别筛选胶囊」对应 Web 端页头的分类入口。
|
||||
*/
|
||||
|
||||
import { FbColors, FbMetric } from '../../common/Theme';
|
||||
import { FbColors, FbMetric, fbTint, FbTint } from '../../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../../common/Store';
|
||||
import { FbGroup, FbResourceItem } from '../../model/Types';
|
||||
import { fbGroupResource } from '../../model/DemoData';
|
||||
@@ -111,7 +111,7 @@ export struct FbResourcesPage {
|
||||
// 点开是本模块的设置菜单(条目照网页版 `templates/<模块>/_settings_menu.php`)。
|
||||
FbIconButton({
|
||||
icon: $r('app.media.fb_ic_menu'),
|
||||
tint: FbColors.muted,
|
||||
tint: FbTint.muted,
|
||||
onTap: (): void => {
|
||||
this.settingsOpen = true;
|
||||
}
|
||||
@@ -212,7 +212,7 @@ export struct FbResourcesPage {
|
||||
.width(19)
|
||||
.height(19)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(this.accent)
|
||||
.colorFilter(fbTint(this.accent))
|
||||
}
|
||||
.width(42)
|
||||
.height(42)
|
||||
@@ -244,7 +244,7 @@ export struct FbResourcesPage {
|
||||
.width(11)
|
||||
.height(11)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
.colorFilter(fbTint(FbTint.faint))
|
||||
Text(r.fileCount.toString() + ' 个附件')
|
||||
.fontSize(10)
|
||||
.fontColor(FbColors.faint)
|
||||
@@ -262,7 +262,7 @@ export struct FbResourcesPage {
|
||||
.width(13)
|
||||
.height(13)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
.colorFilter(fbTint(FbTint.faint))
|
||||
}
|
||||
.width('100%')
|
||||
.padding(14)
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* 抽屉里列什么,正是由这里决定的。
|
||||
*/
|
||||
|
||||
import { FbColors, FbMetric, fbPalette } from '../../common/Theme';
|
||||
import { FbColors, FbMetric, fbPalette, fbTint, FbTint } from '../../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../../common/Store';
|
||||
import { FbModuleDef, FB_MODULES, FB_PRESET_COLORS } from '../../model/Modules';
|
||||
import { FbAvatar, FbDot, FbPrimaryButton, FbTag } from '../../components/Widgets';
|
||||
@@ -278,7 +278,7 @@ export struct FbSettingsPage {
|
||||
.width(14)
|
||||
.height(14)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
.colorFilter(fbTint(FbTint.faint))
|
||||
}
|
||||
.padding(FbMetric.cardPad)
|
||||
.backgroundColor(FbColors.surface)
|
||||
@@ -529,7 +529,7 @@ export struct FbSettingsPage {
|
||||
.width(17)
|
||||
.height(17)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(fbPalette(m.color).brand)
|
||||
.colorFilter(fbTint(fbPalette(m.color).brand))
|
||||
}
|
||||
.width(32)
|
||||
.height(32)
|
||||
@@ -598,7 +598,7 @@ export struct FbSettingsPage {
|
||||
.width(13)
|
||||
.height(13)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(Color.White)
|
||||
.colorFilter(fbTint(FbTint.white))
|
||||
}
|
||||
}
|
||||
.margin({ right: 8, bottom: 8 })
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* 看板名仍保留(卡片上那个小色块标签),点它可以切到别的看板。
|
||||
*/
|
||||
|
||||
import { FbColors, FbMetric } from '../../common/Theme';
|
||||
import { FbColors, FbMetric, fbTint, FbTint } from '../../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../../common/Store';
|
||||
import { FbGroup, FbTaskItem } from '../../model/Types';
|
||||
import { fbGroupTask } from '../../model/DemoData';
|
||||
@@ -114,7 +114,7 @@ export struct FbTasksPage {
|
||||
// 点开是本模块的设置菜单(条目照网页版 `templates/<模块>/_settings_menu.php`)。
|
||||
FbIconButton({
|
||||
icon: $r('app.media.fb_ic_menu'),
|
||||
tint: FbColors.muted,
|
||||
tint: FbTint.muted,
|
||||
onTap: (): void => {
|
||||
this.settingsOpen = true;
|
||||
}
|
||||
@@ -295,7 +295,7 @@ export struct FbTasksPage {
|
||||
.width(11)
|
||||
.height(11)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
.colorFilter(fbTint(FbTint.faint))
|
||||
Text(t.childDone.toString() + '/' + t.childTotal.toString())
|
||||
.fontSize(10)
|
||||
.fontColor(FbColors.faint)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* 时间轴节点分「计划 / 实际」两阶段 —— 与 Web 端一致(v41 加的四种动作按钮)。
|
||||
*/
|
||||
|
||||
import { FbColors, FbMetric } from '../../common/Theme';
|
||||
import { FbColors, FbMetric, fbTint, FbTint } from '../../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../../common/Store';
|
||||
import { FbTripItem, FbTripNode } from '../../model/Types';
|
||||
import { fbMoney } from '../../model/DemoData';
|
||||
@@ -129,7 +129,7 @@ export struct FbTripsPage {
|
||||
// 点开是本模块的设置菜单(条目照网页版 `templates/<模块>/_settings_menu.php`)。
|
||||
FbIconButton({
|
||||
icon: $r('app.media.fb_ic_menu'),
|
||||
tint: FbColors.muted,
|
||||
tint: FbTint.muted,
|
||||
onTap: (): void => {
|
||||
this.settingsOpen = true;
|
||||
}
|
||||
@@ -252,7 +252,7 @@ export struct FbTripsPage {
|
||||
.width(14)
|
||||
.height(14)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
.colorFilter(fbTint(FbTint.faint))
|
||||
.rotate({ angle: this.isExpanded(t.id) ? 90 : 0 })
|
||||
}
|
||||
.width('100%')
|
||||
|
||||
Reference in New Issue
Block a user