生日卡重排:方框放"还剩几天"、右侧放农历日期 + 阳历日期 (#88)

用户 2026-11-05:「还有5天,也就是10月2号,这个不好。因为"还有5天"这个事情
比较重要,所以方形框里面应该写几天,比如还有5天就写5天;现在还有5天,
那个位置应该写上10月2日。如果是农历的话,就要写上农历十月二日。
下面还要写上阳历:换算完之后,今年阳历是多少?比如11月10日,
都放在右侧"满42周岁"这个位置。」

上一版信息分工正好是**反的**:方框里放了「日」(几号),右侧放了「还有 N 天」。
用户要的相反。

重排后:
  方框(84x84 浅主题色块)   = **还剩几天**(大数字)+ 下方「天」/「今天」
  右侧第一行                 = 日期(农历生日「农历十月初二」;其余「11月16日」)
  右侧第二行                 = 阳历换算(**只有农历生日才画**)
  右侧第三行                 = 满 N 周岁

服务端 Anniversary::occurrences() 补两个**成品文字**字段:
  lunar_label = 「十月初二」「闰四月初五」(农历生日才有)
  solar_md    = 「11月10日」(今年实际发生那天的公历月日)

⚠️ 农历文字为什么不在端上拼:Lunar 有完整的月名/日名对照表
   (正月/冬月/腊月、初一/廿三…),端上再抄一份必然漂移,
   而且闰月(闰四月)这种特例得一起处理。
⚠️ solar_md 用 $date(今年换算后的实际日期)而不是 $a['month']/$a['day']:
   农历生日的 month/day 是**农历**月日,直接拼出来会是错的公历。
⚠️ 公历生日时 dateSolar() 返回空串 ⇒ 不画第二行:
   那时第一行已经是「11月16日」,再画一次一模一样,纯噪音。
⚠️ days = 0(就是今天)时方框写「今」而不是「0」:
   0 看着像没数据,而且底下那行写「今天」,配成「今 / 今天」很奇怪。

实测服务端输出:
  高雪玉 的农历生日  days=5  lunar=十月初二  solar=11月10日  date=2026-11-10
  领证纪念日        days=7  lunar=(空)      solar=11月12日  date=2026-11-12
  杨永全 的农历生日  days=8  lunar=十月初五  solar=11月13日  date=2026-11-13
  杨永全 的生日      days=11 lunar=(空)      solar=11月16日  date=2026-11-16
  刘巧莲 的农历生日  days=29 lunar=十月廿六  solar=12月4日   date=2026-12-04

编译通过,已装真机。
This commit is contained in:
2026-11-05 20:39:44 +08:00
parent e1152941af
commit 7ee4810435
3 changed files with 96 additions and 48 deletions
@@ -110,35 +110,56 @@ export struct FbDayBirthdaySheet {
} }
/** /**
* 生日是「几号」—— 卡片正中的那个大数字。 * 方框里的大数字:**还剩几天**。
* *
* ⚠️ 优先用 `day`(用户填的那一天);为 0 才从 `date` 里截。 * 🔴 #88(用户 2026-11-05):「"还有5天"这个事情比较重要,
* 农历生日的 `day` 是农历日,与 `date` 的公历日**不一样**, * 所以方形框里面应该写几天,比如还有5天就写5天。」
* 用户要看的是"生日那天"。 *
* ⚠️ 就是今天(days = 0)时返回「今」而不是「0」——
* 0 看着像"没有数据",而且底下那行会写「今天」,配成「今 / 今天」很奇怪。
*/ */
private dayNum(): string { private daysNum(): string {
const d: FbRecentDay | null = this.item; const d: FbRecentDay | null = this.item;
if (d === null) { if (d === null) {
return ''; return '';
} }
if (d.day > 0) { return d.days === 0 ? '今' : d.days.toString();
return d.day.toString();
}
const dd: number = d.date.length >= 10 ? parseInt(d.date.substring(8, 10), 10) : 0;
return dd > 0 ? dd.toString() : '';
} }
/** 「11 月」—— 与「几号」配成「11 月 16 日」的读法 */ /**
private monthWord(): string { * 右侧第一行:这条纪念日的日期写法。
*
* · 农历生日 → 「十月初二」(服务端 `lunarLabel`)
* · 公历生日 / 自建纪念日 → 「11月16日」(服务端 `solarMd`)
*
* ⚠️ 「农历十月二日」里的「十月」+「初二」在服务端已拼成「十月初二」,
* 这里只补一个「农历」前缀把类别点明(用户原话就是这么念的)。
*/
private datePrimary(): string {
const d: FbRecentDay | null = this.item; const d: FbRecentDay | null = this.item;
if (d === null) { if (d === null) {
return ''; return '';
} }
if (d.month > 0) { if (d.lunarLabel !== '') {
return d.month.toString() + ' 月'; return '农历' + d.lunarLabel;
} }
const mm: number = d.date.length >= 7 ? parseInt(d.date.substring(5, 7), 10) : 0; return d.solarMd;
return mm > 0 ? mm.toString() + ' 月' : ''; }
/**
* 右侧第二行:阳历换算(**只有农历生日才需要**)。
*
* 🔴 #88:「下面还要写上阳历:换算完之后,今年阳历是多少?比如11月10日」
*
* ⚠️ 公历生日返回空串 ⇒ 不画这一行:
* 那时第一行已经是「11月16日」,再画一次一模一样,纯噪音。
*/
private dateSolar(): string {
const d: FbRecentDay | null = this.item;
if (d === null || d.lunarLabel === '' || d.solarMd === '') {
return '';
}
return '阳历 ' + d.solarMd;
} }
/** 完整日期 `2026 年 11 月 16 日`(下一次发生的公历日期) */ /** 完整日期 `2026 年 11 月 16 日`(下一次发生的公历日期) */
@@ -153,19 +174,6 @@ export struct FbDayBirthdaySheet {
return y.toString() + ' 年 ' + m.toString() + ' 月 ' + dd.toString() + ' 日'; return y.toString() + ' 年 ' + m.toString() + ' 月 ' + dd.toString() + ' 日';
} }
/** 「还有 12 天」/「就是今天」 */
private daysWord(): string {
const d: FbRecentDay | null = this.item;
if (d === null) {
return '';
}
if (d.days === 0) {
return '就是今天';
}
return '还有 ' + d.days.toString() + ' 天';
}
/** 是农历生日吗(决定副标题写「农历生日」还是「生日」) */
private isLunar(): boolean { private isLunar(): boolean {
const d: FbRecentDay | null = this.item; const d: FbRecentDay | null = this.item;
return d !== null && d.source === 'birthday_lunar'; return d !== null && d.source === 'birthday_lunar';
@@ -248,17 +256,33 @@ export struct FbDayBirthdaySheet {
.padding({ top: 4, bottom: 18 }) .padding({ top: 4, bottom: 18 })
.alignItems(VerticalAlign.Center) .alignItems(VerticalAlign.Center)
/* ---- 「几号」大字块 + 剩余天数 ---- */ /* ---- 方框(还剩几天)+ 右侧(日期 / 年龄)----
/* 用户第 2 条:「显示…生日的『日』和日期就可以了」 🔴🔴 #88(用户 2026-11-05):
⇒ 把「日」做成一个视觉焦点(大数字),日期在下面一行小字。 「还有5天,也就是10月2号,这个不好。因为"还有5天"这个事情比较重要,
底色用浅一档主题色(fbMixWhite 0.91),与下拉框/胶囊同一套。 */ 所以方形框里面应该写几天,比如还有5天就写5天;
Row({ space: 12 }) { 现在还有5天,那个位置应该写上10月2日。
Column({ space: 0 }) { 如果是农历的话,就要写上农历十月二日。下面还要写上阳历:
Text(this.dayNum()) 换算完之后,今年阳历是多少?比如11月10日,
都放在右侧"满42周岁"这个位置。」
⇒ 信息分工重排(上一版正好是反的:方框放"几号"、右侧放"还有几天"):
方框 = **还剩几天**(用户说这件事最重要,要大)
右侧第一行 = 日期(农历生日写「十月初二」,公历生日写「11月16日」)
右侧第二行 = 阳历换算(农历生日才需要;公历生日与第一行重复,不画)
右侧第三行 = 满 N 周岁
⚠️ 「还剩几天」为什么不做成"5 天"两个单位分开:
30 以上是两位数,分开排会挤;`fontSize(34)` 放「12」正好。
⚠️ 农历生日的两行日期**不是同一天**(十月初二 ↔ 11月10日),
所以必须都画;公历生日两行会一模一样,只画一行。 */
Row({ space: 14 }) {
Column({ space: 2 }) {
Text(this.daysNum())
.fontSize(34) .fontSize(34)
.fontWeight(FontWeight.Bold) .fontWeight(FontWeight.Bold)
.fontColor(this.accent) .fontColor(this.accent)
Text(this.monthWord()) /* 「还有 5 天」的时候,这一行是「天」;就是今天时写「今天」 */
Text(this.item !== null && this.item.days === 0 ? '今天' : '天')
.fontSize(FbMetric.fontTiny) .fontSize(FbMetric.fontTiny)
.fontColor(fbMixWhite(this.accent, 0.35)) .fontColor(fbMixWhite(this.accent, 0.35))
} }
@@ -269,21 +293,22 @@ export struct FbDayBirthdaySheet {
.borderRadius(FbMetric.radiusS) .borderRadius(FbMetric.radiusS)
Column({ space: 6 }) { Column({ space: 6 }) {
Text(this.daysWord()) /* 第一行:日期 */
.fontSize(FbMetric.fontSmall) Text(this.datePrimary())
.fontSize(FbMetric.fontBody)
.fontWeight(FontWeight.Medium) .fontWeight(FontWeight.Medium)
.fontColor(this.accent) .fontColor(this.accent)
if (this.item !== null && this.item.age >= 0) { /* 第二行:阳历换算(只有农历生日才画) */
Text('满 ' + this.item.age.toString() + ' 周岁') if (this.dateSolar() !== '') {
.fontSize(FbMetric.fontTiny) Text(this.dateSolar())
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.muted) .fontColor(FbColors.muted)
} }
if (this.isLunar()) { /* 第三行:满 N 周岁 */
/* 农历生日时「日」是农历日,与上面的公历日期不同 —— if (this.item !== null && this.item.age >= 0) {
不说明一句用户会以为"日子算错了"。 */ Text('满 ' + this.item.age.toString() + ' 周岁')
Text('农历日期,每年按农历换算') .fontSize(FbMetric.fontSmall)
.fontSize(FbMetric.fontTiny) .fontColor(FbColors.muted)
.fontColor(FbColors.faint)
} }
} }
.alignItems(HorizontalAlign.Start) .alignItems(HorizontalAlign.Start)
+4
View File
@@ -187,6 +187,8 @@ interface RawRecentDay {
/* #87:生日只读卡要用(服务端 Anniversary::occurrences 已输出) */ /* #87:生日只读卡要用(服务端 Anniversary::occurrences 已输出) */
person_name?: string | null; avatar?: string | null; avatar_color?: string | null; person_name?: string | null; avatar?: string | null; avatar_color?: string | null;
day?: number | null; month?: number | null; day?: number | null; month?: number | null;
/* #88:卡片右侧的两种日期写法(服务端给成品文字) */
lunar_label?: string | null; solar_md?: string | null;
} }
interface RawTaskBoard { interface RawTaskBoard {
@@ -921,6 +923,8 @@ export function parseRecentDays(v: Object | null | undefined): FbRecentDay[] {
avatarColor: s(r.avatar_color), avatarColor: s(r.avatar_color),
day: r.day !== null && r.day !== undefined ? n(r.day) : 0, day: r.day !== null && r.day !== undefined ? n(r.day) : 0,
month: r.month !== null && r.month !== undefined ? n(r.month) : 0, month: r.month !== null && r.month !== undefined ? n(r.month) : 0,
lunarLabel: s(r.lunar_label),
solarMd: s(r.solar_md),
}; };
out.push(it); out.push(it);
} }
+19
View File
@@ -238,6 +238,25 @@ export interface FbRecentDay {
day: number; day: number;
/** 「月」—— 同理(农历生日的月份是农历月) */ /** 「月」—— 同理(农历生日的月份是农历月) */
month: number; month: number;
/**
* 农历写法的成品文字,如「十月初二」「闰四月初五」。
* 只有农历生日才有;公历生日 / 自建纪念日为**空串**。
*
* 🔴 #88(用户 2026-11-05):「如果是农历的话,就要写上农历十月二日。」
* ⚠️ 为什么由服务端给成品:`Lunar` 有完整的月名/日名对照表
* (正月/冬月/腊月、初一/廿三…),端上再抄一份必然漂移,
* 闰月(闰四月)这种特例也得一起处理。
*/
lunarLabel: string;
/**
* 今年实际发生那天的公历月日,如「11月10日」。
*
* 🔴 #88(用户 2026-11-05):「下面还要写上阳历:换算完之后,今年阳历是多少?
* 比如11月10日,都放在右侧『满42周岁』这个位置。」
* ⚠️ 农历生日时它与 `lunarLabel` **不是同一天**(一个农历一个公历),
* 这正是用户要同时看到两个的原因。
*/
solarMd: string;
} }
/** 首页「模块卡片」—— 一块一块的入口,带一句关键数字 */ /** 首页「模块卡片」—— 一块一块的入口,带一句关键数字 */