v73:家谱自绘重做(Canvas 画线 + 原生卡片),去掉列表只留图谱
用户原话:「手机端这个家谱实在是很难看,能有什么库之类的,让它好看点吗」 改法:不再全 Canvas 手绘(只有彩色圆点 + 首字,中文又平又硬、做不出圆角阴影), 改成「Canvas 只画线 + 原生 Column/Text/FbAvatar 绝对定位」。 不能改用 Path 组件:Path.commands 单位是 px,而 .position() 是 vp,混用会缩 1/3; Canvas 绘制坐标系本身是 vp,与 .position() 自洽。 布局算法抽成纯函数 common/KinLayout.ets(可单独验证,不再和"画"揉在一起): - 横向单位是「夫妻单元」而不是「人」,夫妻并排紧邻 - 子女挂在夫妻单元中点下方(family-chart 语义) - 迭代松弛 24 轮容纳冲突约束(家谱不是树:姻亲的"挂父母下方"与"夫妻并排"互相打架) - 全程确定性(排序带 minId 兜底),避免每次重画卡片抖位置 真机抓到并修掉两个真 bug: 1. 户号 0 被当成"没分户":服务端 unit_of 从 0 编号,端上判 > 0 ⇒ 我与妻子被拆两户(真机 Δx=680px)。修:布局层 >= 0,解析层缺失给 -1, 不把任何数值当哨兵。 2. 线没连对(用户原话「主要是线没连对」):buildBloodEdges 用 gen 数值当行号, minGen=-1 ⇒ 长辈线从 y=-148 起(屏幕外),整组线串到上一代卡片。 修:新增 rowIndex(gens, gen),用走标换算而不是数值。 验证: - 离线布局 29/0(含户号 0、脏数据一户三人、镜像常量与端上源码一致) - 反向验证 11/0(9 处注入,含"户号 0 当没分户"与"用 gen 数值当行号"两条真 bug 复现) - 真机几何 15/0、真实数据+屏幕对照 9/0(反解密度 3.125x,残差 0.23/0.32px; 12 个血缘线端点全部贴在卡片上下沿) - ArkTS 静态守卫 905/0
This commit is contained in:
@@ -168,21 +168,10 @@ function isSubsequence(q: string, s: string): boolean {
|
||||
/**
|
||||
* 家谱里的一代(一层)。
|
||||
*
|
||||
* ⚠️ 由 `FbKinRing`(旧的"三圈"演示结构)改成"按代数分层" ——
|
||||
* 因为真实接口 `/api/v1/kin` 给的就是**按代数**的人(`gen` 字段),
|
||||
* 而"三圈"(直系/其他亲属)是当初照着演示数据设计的分法,
|
||||
* 与真实数据的形状对不上。
|
||||
* ⚠️ v73:这个接口连带「列表」视图一起撤掉了 —— 家谱现在只有图一种形态,
|
||||
* 代数分层由**图谱的纵向位置**表达(长辈在上、晚辈在下),
|
||||
* 不再需要「这一代叫什么名字」这种中间结构。
|
||||
*/
|
||||
interface FbKinGen {
|
||||
/** 代数:0 = 我,负 = 长辈,正 = 晚辈 */
|
||||
gen: number;
|
||||
/** 层名,如「父母辈」 */
|
||||
title: string;
|
||||
/** 说明,如「父辈与母辈」 */
|
||||
sub: string;
|
||||
/** 这一代的人 */
|
||||
people: FbKinPerson[];
|
||||
}
|
||||
|
||||
@ComponentV2
|
||||
export struct FbPeoplePage {
|
||||
@@ -322,12 +311,10 @@ export struct FbPeoplePage {
|
||||
/** 家谱的同步提示(与名册分开:两批数据可能一个成功一个失败) */
|
||||
@Local kinSyncNote: string = '';
|
||||
/**
|
||||
* 家谱的显示方式:'graph'(知识图谱,默认)/ 'list'(分层列表)。
|
||||
*
|
||||
* 用户要求把它做成知识图谱那样的图,所以默认给图谱。
|
||||
* 但保留列表:小屏上人多了图会挤,列表能看清每个人的关系摘要。
|
||||
* ⚠️ v73:原来这里有 `kinMode`('graph' / 'list' 两种视图)。
|
||||
* 用户 2026-10-21 要求去掉列表、只留图谱 ⇒ 状态本身也不用了。
|
||||
* 「列表」那套是按代数分组的通讯录,名册页已经有同样的能力。
|
||||
*/
|
||||
@Local kinMode: string = 'graph';
|
||||
/** 我在家谱里的 id(图谱里要标出「我」) */
|
||||
@Local kinMeId: number = 0;
|
||||
private repo: FbPersonRepo = FbPersonRepo.inst();
|
||||
@@ -568,8 +555,8 @@ export struct FbPeoplePage {
|
||||
*/
|
||||
private confirmDelete(): void {
|
||||
/* 🔴 v59e:删除入口从"简要卡"挪到了"详情卡底部" ⇒ 目标人先取 `detailPerson`。
|
||||
⚠️ 不能只取 `quickSource()`:详情有可能是**从家谱那行直接打开**的
|
||||
(`kinRow` 的 onClick 直接写 `detailPerson`,`quickPerson` 一直是 null)
|
||||
⚠️ 不能只取 `quickSource()`:详情有可能是**从家谱图上直接点开**的
|
||||
(点卡片直接写 `detailPerson`,`quickPerson` 一直是 null)
|
||||
⇒ 那样点删除会**静默什么都不发生**(`who === null` 就 return 了)。
|
||||
这正是"按钮点了没反应"最难查的一类:没有报错、没有日志。 */
|
||||
const who: FbRosterPerson | null =
|
||||
@@ -1385,17 +1372,55 @@ export struct FbPeoplePage {
|
||||
* ⚠️ 用 `this.detailToken` 防止"快速连点两个人,前一个的响应把后一个覆盖"。
|
||||
*/
|
||||
private openDetail(id: number): void {
|
||||
const raw: FbRosterPerson | undefined = this.rawById.get(id);
|
||||
if (raw === undefined) {
|
||||
return;
|
||||
}
|
||||
this.detailPerson = raw;
|
||||
const token: string = FB_TOKEN_STORE.get();
|
||||
if (token === '') {
|
||||
return;
|
||||
}
|
||||
/* 🔴🔴 v73 真 bug:点家谱卡片**静默没反应**。
|
||||
根因:这里原来只从 `rawById` 取,而 `rawById` 来自 `repo.list()`
|
||||
—— 那条 SQL 带 `JOIN person_roster`,**只包含"我挑进名册的人"**。
|
||||
家谱里的人(父母、岳父母、配偶)**不一定在我的名册里** ⇒ 查不到 ⇒ 直接 return。
|
||||
字面上就是"点了没反应",而且不打日志的话 hilog 一个字都抓不到。
|
||||
|
||||
修法分两段(与 `byId()` 的注释同一条思路):
|
||||
① 名册里有 ⇒ 立刻用本地那份开浮层(不等网络,点开就有内容);
|
||||
② 名册里没有 ⇒ 先空着把浮层**打开**(`detailPerson` 用一个占位),
|
||||
再异步 `byId()` / 网络详情补上 —— **不能**因为本地没缓存就不响应点击。
|
||||
⚠️ 注意 `detailPerson` 是 `FbRosterPerson`,不能塞 `null` 当作"只开框",
|
||||
所以第 ② 种情况必须先构造一个最小可渲染对象(只有 id + 姓名)。
|
||||
*/
|
||||
const cached: FbRosterPerson | undefined = this.rawById.get(id);
|
||||
hilog.info(DOMAIN, TAG, 'openDetail(%{public}d):名册命中=%{public}s(名册共 %{public}d 人)',
|
||||
id, (cached !== undefined ? 'yes' : 'no'), this.rawById.size);
|
||||
|
||||
this.detailToken += 1;
|
||||
const my: number = this.detailToken;
|
||||
|
||||
if (cached !== undefined) {
|
||||
this.detailPerson = cached;
|
||||
} else {
|
||||
// ② 名册里没有 —— 先用家谱那份拼一个最小对象把浮层打开
|
||||
const kin: FbKinPerson | undefined = this.kinPeople.find(
|
||||
(p: FbKinPerson): boolean => p.id === id);
|
||||
if (kin === undefined) {
|
||||
hilog.warn(DOMAIN, TAG, 'openDetail(%{public}d):名册与家谱都没有这个人', id);
|
||||
return;
|
||||
}
|
||||
this.detailPerson = placeholderPerson(kin);
|
||||
}
|
||||
|
||||
const token: string = FB_TOKEN_STORE.get();
|
||||
if (token === '') {
|
||||
hilog.warn(DOMAIN, TAG, 'openDetail(%{public}d):令牌为空,浮层已开但不取详情', id);
|
||||
return;
|
||||
}
|
||||
|
||||
// ③ 本地库补一份(家谱那边的人往往不 JOIN roster,这里能补到)
|
||||
this.repo.byId(id).then((local: FbRosterPerson | null): void => {
|
||||
if (local === null || my !== this.detailToken) {
|
||||
return;
|
||||
}
|
||||
if (this.detailPerson !== null && this.detailPerson.id === id) {
|
||||
this.detailPerson = local;
|
||||
}
|
||||
});
|
||||
|
||||
this.repo.fetchDetail(id, token).then((full: FbRosterPerson | null): void => {
|
||||
// ① 期间用户已经关了浮层、或又点开别人 ⇒ 丢弃这次结果
|
||||
if (full === null || my !== this.detailToken) {
|
||||
@@ -1482,7 +1507,7 @@ export struct FbPeoplePage {
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 家谱的说明条("以我为中心" + 图谱/列表切换)。
|
||||
* 家谱的说明条(「以我为中心」+ 人数)。
|
||||
*
|
||||
* 🔴 v68:这里原来还有一颗「补关系」按钮,**从来没有 `onTap`** ——
|
||||
* 点了一点反应都没有(全仓库扫过,只剩这一颗)。
|
||||
@@ -1491,9 +1516,11 @@ export struct FbPeoplePage {
|
||||
* ⇒ 与其留一个永远点不动的按钮,先撤掉(免得被当成"坏了")。
|
||||
* 要做得先加 `POST /api/v1/persons/<id>/relations`,那是独立一批活。
|
||||
*
|
||||
* ⚠️ 从 `kinView` 里抽出来的原因:**列表模式**要把它**一起放进滚动的 Scroll**
|
||||
* (这样它才跟着内容滚走),而**图谱模式**它是固定的一层。
|
||||
* 抽成一个 Builder,两处引用同一份实现,不会改一处漏一处。
|
||||
* 🔴 v73(用户 2026-10-21):右侧的「图谱 / 列表」切换**整块撤掉**。
|
||||
* 原话是"手机端这个家谱实在是很难看",而那个"列表"视图
|
||||
* 本质就是个按代数分组的通讯录 —— 名册页早就有同样的能力
|
||||
* (还能搜索、筛选标签),留着它只会让人以为家谱有两种画法。
|
||||
* 现在家谱只有一种形态:**图**。
|
||||
*/
|
||||
@Builder
|
||||
kinHeader() {
|
||||
@@ -1509,15 +1536,6 @@ export struct FbPeoplePage {
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
|
||||
// 图谱 / 列表 切换
|
||||
Row({ space: 0 }) {
|
||||
this.kinModeBtn('图谱', 'graph')
|
||||
this.kinModeBtn('列表', 'list')
|
||||
}
|
||||
.padding(2)
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
.borderRadius(FbMetric.radiusPill)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||
@@ -1527,13 +1545,8 @@ export struct FbPeoplePage {
|
||||
@Builder
|
||||
kinView() {
|
||||
Column() {
|
||||
// 🔴 图谱模式**不跟滚**(它是 Canvas 画的一整屏图,本就没有"滚动内容"可言)
|
||||
// ⇒ 本模式顶栏仍是固定的一层。
|
||||
// 「列表」模式则把页头移进内层 `Scroll` 一起滚(见下面的分支)。
|
||||
if (this.kinMode === 'graph') {
|
||||
this.pageHeaderItem()
|
||||
this.kinHeader()
|
||||
}
|
||||
this.pageHeaderItem()
|
||||
this.kinHeader()
|
||||
|
||||
if (this.loading) {
|
||||
Text('正在读取本地数据…')
|
||||
@@ -1549,8 +1562,8 @@ export struct FbPeoplePage {
|
||||
.textAlign(TextAlign.Center)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.faint)
|
||||
} else if (this.kinMode === 'graph') {
|
||||
// ---- 知识图谱(Canvas 画:圆节点 + 连线)----
|
||||
} else {
|
||||
// ---- 家谱图(Canvas 画线 + 原生卡片,见 components/FbKinGraph.ets)----
|
||||
Column() {
|
||||
FbKinGraphView({
|
||||
people: this.kinPeople,
|
||||
@@ -1563,43 +1576,6 @@ export struct FbPeoplePage {
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: FB_DOCK_RESERVE - 24 })
|
||||
} else {
|
||||
// ---- 分层列表 ----
|
||||
Scroll() {
|
||||
Column({ space: 0 }) {
|
||||
// 🔴 **顶栏 + 页头 + 说明条**都放进这个滚动的 Scroll 里
|
||||
// ⇒ 上滑时一起滚走(用户 2026-10-15 要求本页也按首页那套)。
|
||||
// ⚠️ 图谱模式**不能**这样放(它不滚动,见上面的分支)。
|
||||
this.pageHeaderItem()
|
||||
this.kinHeader()
|
||||
|
||||
ForEach(this.kinGens(), (g: FbKinGen) => {
|
||||
this.genBlock(g)
|
||||
}, (g: FbKinGen): string => 'kg_' + g.gen.toString())
|
||||
|
||||
Text('只显示以我为中心、上下两代以内的亲属(手机上再往外排就看不下了)。')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 8, bottom: 20 })
|
||||
|
||||
// 🔴 **末尾占位**:与名册列表同理 —— 悬浮栏留白必须做成**内容的一部分**,
|
||||
// 不能做成 Scroll 的 `padding(bottom)`(那会把可视底边往上收,
|
||||
// 看着就是"内容没滚到屏幕最底、下面空一块")。
|
||||
this.dockSpacerItem()
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
// 🔴 滚动方向上报(驱动底部悬浮栏随滚收起 / 唤回)—— 见 common/FbScroll.ets
|
||||
.onDidScroll(fbOnScroll)
|
||||
// 🔴 触摸方向兜底(内容不足一屏的页面滚不动,只能靠手指位移判断)
|
||||
// 与上面的 `onDidScroll` 互补、互不冲突 —— 见 common/FbScroll.ets
|
||||
.onTouch(fbOnTouch)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
@@ -1609,97 +1585,11 @@ export struct FbPeoplePage {
|
||||
/**
|
||||
* 一代人(家谱里的一层)。
|
||||
*
|
||||
* ⚠️ 自己居中、长辈在上、晚辈在下 —— 与"从上往下读"的直觉一致。
|
||||
* 标题写清"这是哪一代"(父母辈 / 我这一辈 / 子女辈),
|
||||
* 否则用户看到一堆名字不知道是什么关系。
|
||||
* ⚠️ v73:这个 Builder 与 `kinRow` 一起**已删除** —— 它们只服务
|
||||
* 「列表」视图,而用户 2026-10-21 要求去掉列表、只留图谱。
|
||||
* 原来这里按代数分组、每组一个白卡片 + 行分隔线,
|
||||
* 本质就是个通讯录;名册页已经有同样的能力(还能搜索/筛标签)。
|
||||
*/
|
||||
@Builder
|
||||
genBlock(g: FbKinGen) {
|
||||
Column({ space: 0 }) {
|
||||
// 代数标题
|
||||
Row({ space: 8 }) {
|
||||
Text(g.title)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(g.sub)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
Blank()
|
||||
Text(g.people.length.toString() + ' 人')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 14, right: 14, top: 10, bottom: 6 })
|
||||
.backgroundColor(FbColors.surface)
|
||||
|
||||
// 这一代的人
|
||||
ForEach(g.people, (p: FbKinPerson, idx: number) => {
|
||||
Column({ space: 0 }) {
|
||||
this.kinRow(p)
|
||||
if (idx < g.people.length - 1) {
|
||||
Divider()
|
||||
.strokeWidth(0.5)
|
||||
.color(FbColors.line)
|
||||
.margin({ left: 66 })
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
}, (p: FbKinPerson): string => 'kp_' + p.id.toString())
|
||||
}
|
||||
.width('100%')
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 10 })
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.clip(true)
|
||||
}
|
||||
|
||||
/** 家谱里的一行(头像 + 称呼 + 称谓 + 关系摘要) */
|
||||
@Builder
|
||||
kinRow(p: FbKinPerson) {
|
||||
Row({ space: 12 }) {
|
||||
FbAvatar({
|
||||
name: p.shown,
|
||||
color: p.avatarColor !== '' ? p.avatarColor : '#8A6B4B',
|
||||
dia: 40,
|
||||
fontSizeVp: 16
|
||||
})
|
||||
Column({ space: 3 }) {
|
||||
Row({ space: 6 }) {
|
||||
Text(p.shown)
|
||||
.fontSize(FbMetric.fontBody)
|
||||
.fontColor(FbColors.ink)
|
||||
.fontWeight(p.gen === 0 && p.label === '我' ? FontWeight.Bold : FontWeight.Medium)
|
||||
.maxLines(1)
|
||||
if (p.label !== '') {
|
||||
FbTag({ text: p.label, tone: 'muted' })
|
||||
}
|
||||
}
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// 关系摘要(有几个父母/子女/配偶)—— 让人一眼看出"这人有几个孩子"
|
||||
Text(this.relSummary(p))
|
||||
.fontSize(10)
|
||||
.fontColor(FbColors.faint)
|
||||
.maxLines(1)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 14, right: 14, top: 11, bottom: 11 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.onClick((): void => {
|
||||
// 点家谱里的人 ⇒ 复用名册的详情浮层(同一个人、同一份数据源)
|
||||
const raw: FbRosterPerson | undefined = this.rawById.get(p.id);
|
||||
if (raw !== undefined) {
|
||||
this.detailPerson = raw;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 一圈亲属卡 */
|
||||
|
||||
// ============================================================
|
||||
// 共用小件
|
||||
@@ -1770,99 +1660,10 @@ export struct FbPeoplePage {
|
||||
return list;
|
||||
}
|
||||
|
||||
/** 三圈:我 / 直系 / 其他亲属 */
|
||||
/** 家谱视图切换按钮(图谱 / 列表) */
|
||||
@Builder
|
||||
kinModeBtn(label: string, key: string) {
|
||||
Text(label)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(this.kinMode === key ? Color.White : FbColors.muted)
|
||||
.backgroundColor(this.kinMode === key ? '#4A7C82' : Color.Transparent)
|
||||
.borderRadius(FbMetric.radiusPill)
|
||||
.padding({ left: 12, right: 12, top: 5, bottom: 5 })
|
||||
.onClick((): void => {
|
||||
this.kinMode = key;
|
||||
})
|
||||
}
|
||||
|
||||
/** 家谱:按代数分组(服务端已按 gen 排序) */
|
||||
private kinGens(): FbKinGen[] {
|
||||
const map: Map<number, FbKinPerson[]> = new Map<number, FbKinPerson[]>();
|
||||
for (const p of this.kinPeople) {
|
||||
const cur: FbKinPerson[] | undefined = map.get(p.gen);
|
||||
if (cur !== undefined) {
|
||||
cur.push(p);
|
||||
} else {
|
||||
map.set(p.gen, [p]);
|
||||
}
|
||||
}
|
||||
// ⚠️ Map 的键是数字,直接 sort 会按**字符串**排(-1, 0, 1, -2 → -1,-2,0,1)
|
||||
// ⇒ 先转成数组、按数值排。
|
||||
const gens: number[] = [];
|
||||
map.forEach((_v: FbKinPerson[], k: number): void => {
|
||||
gens.push(k);
|
||||
});
|
||||
gens.sort((a: number, b: number): number => a - b);
|
||||
|
||||
const out: FbKinGen[] = [];
|
||||
for (const g of gens) {
|
||||
const ps: FbKinPerson[] = map.get(g) !== undefined ? (map.get(g) as FbKinPerson[]) : [];
|
||||
const it: FbKinGen = {
|
||||
gen: g,
|
||||
title: this.genTitle(g),
|
||||
sub: this.genSub(g),
|
||||
people: ps,
|
||||
};
|
||||
out.push(it);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 代数 → 中文层名 */
|
||||
private genTitle(g: number): string {
|
||||
if (g === 0) {
|
||||
return '我这一辈';
|
||||
}
|
||||
if (g === -1) {
|
||||
return '父母辈';
|
||||
}
|
||||
if (g <= -2) {
|
||||
return '祖辈(上 ' + (-g).toString() + ' 代)';
|
||||
}
|
||||
if (g === 1) {
|
||||
return '子女辈';
|
||||
}
|
||||
return '孙辈(下 ' + g.toString() + ' 代)';
|
||||
}
|
||||
|
||||
/** 代数说明 */
|
||||
private genSub(g: number): string {
|
||||
if (g === 0) {
|
||||
return '我与配偶、兄弟姐妹';
|
||||
}
|
||||
if (g < 0) {
|
||||
return '父辈与母辈';
|
||||
}
|
||||
return '孩子与侄甥';
|
||||
}
|
||||
|
||||
/** 「N 位父母 · N 位子女」这类摘要 */
|
||||
private relSummary(p: FbKinPerson): string {
|
||||
const parts: string[] = [];
|
||||
if (p.parents.length > 0) {
|
||||
parts.push('父母 ' + p.parents.length.toString());
|
||||
}
|
||||
if (p.spouses.length > 0) {
|
||||
parts.push('配偶 ' + p.spouses.length.toString());
|
||||
}
|
||||
if (p.siblings.length > 0) {
|
||||
parts.push('兄弟姐妹 ' + p.siblings.length.toString());
|
||||
}
|
||||
if (p.children.length > 0) {
|
||||
parts.push('子女 ' + p.children.length.toString());
|
||||
}
|
||||
return parts.length > 0 ? parts.join(' · ') : '暂无亲属关系';
|
||||
}
|
||||
/* ⚠️ v73:`kinModeBtn` / `kinGens` / `genTitle` / `genSub` / `relSummary`
|
||||
五个方法一起删了 —— 它们只为「列表」视图服务,而那个视图已撤。
|
||||
代数分层的信息现在由**图谱本身的纵向位置**表达(长辈在上、晚辈在下),
|
||||
不再需要「父母辈 / 我这一辈」这种文字小标题。 */
|
||||
|
||||
/** 家谱顶部的人数说明 */
|
||||
private kinTotalText(): string {
|
||||
@@ -1877,4 +1678,58 @@ export struct FbPeoplePage {
|
||||
const hit: FbRosterItem | undefined = this.all.find((p: FbRosterItem): boolean => p.id === id);
|
||||
return hit !== undefined ? hit.avatarColor : '#A84A28';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用「家谱里的那个人」拼一个**最小可渲染**的 `FbRosterPerson`。
|
||||
*
|
||||
* 🔴 为什么需要(v73 真 bug):家谱里的人**不一定在我名册里**
|
||||
* (`FbPersonRepo.list()` 带 `JOIN person_roster`,只含"我挑进名册的人"),
|
||||
* 那时点卡片以前是**静默 return ⇒ 点了没反应**。
|
||||
* 这里先拼一个只够画浮层头部的对象,**让浮层立刻打开**,
|
||||
* 随后 `repo.byId()` / `fetchDetail()` 到了再原地升级。
|
||||
*
|
||||
* ⚠️ 只填家谱真有的那几项,其余给空值 —— 不要瞎猜(填错比空着更糟:
|
||||
* 用户会以为"系统里有这个资料")。本地库/接口补上来后会被整份替换掉。
|
||||
*/
|
||||
function placeholderPerson(k: FbKinPerson): FbRosterPerson {
|
||||
const p: FbRosterPerson = {
|
||||
id: k.id,
|
||||
name: k.name,
|
||||
shown: k.shown !== '' ? k.shown : k.name,
|
||||
// 家谱那份的称谓是服务端算好的,这里能用
|
||||
label: k.label,
|
||||
tags: [],
|
||||
avatarColor: k.avatarColor,
|
||||
avatar: k.avatar,
|
||||
birthdaySolar: '',
|
||||
birthdayFull: '',
|
||||
birthdayLunar: '',
|
||||
phone: '',
|
||||
phones: [],
|
||||
initials: '',
|
||||
org: '',
|
||||
title: '',
|
||||
slogan: '',
|
||||
email: '',
|
||||
addrCity: '',
|
||||
gender: '',
|
||||
note: '',
|
||||
isAdmin: false,
|
||||
addrCountry: '',
|
||||
addrProvince: '',
|
||||
addrDistrict: '',
|
||||
address: '',
|
||||
birthRank: '',
|
||||
birthdayLunarLeap: false,
|
||||
birthdayLunarRaw: '',
|
||||
contacts: { phone: [], email: [], address: [], url: [] },
|
||||
jobs: [],
|
||||
myNote: '',
|
||||
myNickname: '',
|
||||
idCardMasked: '',
|
||||
canEditAll: false,
|
||||
isSelf: false
|
||||
};
|
||||
return p;
|
||||
}
|
||||
Reference in New Issue
Block a user