Files
FamilyBook_harmonyos/entry/src/main/ets/components/Widgets.ets
T
laoyang b42e657bb2 标题颜色/看板/截止/负责人/备注/协作人/留言头像 全部修正 (#77)
用户 2026-11-05:「还是不对,标题的颜色不对。分组看板那个地方没改,截止没改,
负责人没改,备注没改,协作人没改。评论那个地方的头像怎么又没了?」

逐条查下来,四个真问题:

① 上一轮只改了 3 个标题,漏了一大片
   只换了「子任务/附件/留言」,而 **看板、状态、协作人**、
   以及「更多信息」里的**字段标签**全是手写的 fontTiny + faint。
   ⇒ 看板/状态/协作人 补 FbSectionTitle(Bold + accent + 字距 0.6)。
   ⇒ 截止/负责人/备注/分类/循环 这些**行内字段标签**新增 fieldLb() Builder:
     同样取主题色 + Bold + letterSpacing,但宽度 48、无内边距
     (不能直接用 FbSectionTitle:它 width 100% + padding,会把右侧控件顶下去)。
   ⚠️ 这与「编辑表单」的口径不同:那边 `期限` 仍是浅灰小字。
      用户明确要求这个面板里字段标签也用主题色粗体 —— 按用户要求走,
      已在 fieldLb 的注释里写明这个差异,避免以后有人"统一"回去。

② 留言头像丢失的真因
   不是数据问题,是 `FbPersonChip` **不透传 imageUrl**。
   它内部的 `FbAvatar({ name, color, dia, fontSizeVp })` 漏了这个参数,
   而 `FbAvatar` 自己是支持的(注释里还写着"800 人里只有 19 人有头像,
   有图显示图、没图回落色块字")。
   ⇒ 上一轮我换成 FbPersonChip 后,原来手写 FbAvatar 时**能显示的头像反而没了**。
      给 FbPersonChip 补 `@Param imageUrl` 并传给内部 FbAvatar,
      调用点把 `fbImageSrc(c.avatar, token)` 传进去。

③ 状态区原来没有标题
   三个 chip 直接飘在那(上下每个区段都有标题)。补 FbSectionTitle(状态)。

④ 协作人标题改组件
   顺手把「+ 共享」按钮挪到标题下面单独一行(FbSectionTitle 是 100% 宽,
   放同一个 Row 里会把它压成 0 宽)。

编译通过,已装真机。
2026-11-05 19:34:12 +08:00

615 lines
22 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 家书 FamilyBook · 共享 UI 组件(鸿蒙端)
*
* 这些都是**无状态或轻状态**的展示件,对应 Web 端 `assets/css/app.css` 里
* 那几个反复出现的类:`.card` / `.page-head` / `.empty` / `.hint` / `.node-badge` …
*
* ⚠️ 全部用 @ComponentV2 + @Param(只读入参),不在子组件里改父组件状态。
* ⚠️ 所有 struct 加 `Fb` 前缀(arkts-no-decl-merging:同 hap 内顶层名不得重复)。
*/
import { FbColors, FbIcon, FbMetric, fbTint, FbTint } from '../common/Theme';
/**
* 圆点 / 色块(时间轴的点、日历的日程小点、看板的色标…)。
*
* ⚠️ 为什么不用 `Circle()`:它的 `.fill()` 是 **API 26.0.0** 才有的重载,
* 本工程 compatibleSdkVersion 是 6.1.1(24),编译器会告警
* 「This API is supported since SDK version 26.0.0」,真机上可能不生效。
* 改用「方块 + 圆角 = 圆」的写法:`Column().width().height().borderRadius(dia/2)`
* 只用通用属性,任何版本都成立,而且圆形效果完全等价。
*
* @Param dia 直径(vp)
* @Param fillC 填充色(ResourceColor:hex 字符串或 $r 语义色)
* @Param ringC 描边色(不传则不描边)
*/
@ComponentV2
export struct FbDot {
@Param dia: number = 8;
@Param fillC: ResourceColor = '#A84A28';
@Param ringC?: ResourceColor = undefined;
build() {
Stack({ alignContent: Alignment.Center }) {
// 底:填充
Column()
.width(this.dia)
.height(this.dia)
.borderRadius(this.dia / 2)
.backgroundColor(this.fillC)
// 描边(空心点用:内层挖一个背景色圆,等效于 1.4vp 环形)
if (this.ringC !== undefined) {
Column()
.width(this.dia - 2.8)
.height(this.dia - 2.8)
.borderRadius((this.dia - 2.8) / 2)
.backgroundColor(FbColors.surface)
}
}
.width(this.dia)
.height(this.dia)
}
}
/**
* 空心状态圈(任务卡的完成圈:未完成时空心、完成时实心 + 勾)。
*
* 同样避开 Circle().fill()/stroke(),用嵌套方块实现。
*/
@ComponentV2
export struct FbCheckCircle {
/** 直径 */
@Param dia: number = 16;
/** 是否已完成(实心 + 勾) */
@Param done: boolean = false;
/** 是否进行中(实心小点) */
@Param doing: boolean = false;
/** 主色 */
@Param color: string = '#A8574F';
build() {
Stack({ alignContent: Alignment.Center }) {
// 外圈:完成/进行中是实心主色,否则用浅灰实心(代表描边)
Column()
.width(this.dia)
.height(this.dia)
.borderRadius(this.dia / 2)
.backgroundColor(this.done || this.doing ? this.color : '#A8988A')
// 内层:未勾选时用卡片底「挖空」,形成空心环
if (!this.done && !this.doing) {
Column()
.width(this.dia - 2.8)
.height(this.dia - 2.8)
.borderRadius((this.dia - 2.8) / 2)
.backgroundColor(FbColors.surface)
}
// 已完成:打勾图标
if (this.done) {
Image($r('app.media.fb_ic_check'))
.width(this.dia * 0.66)
.height(this.dia * 0.66)
.renderMode(ImageRenderMode.Template)
.colorFilter(fbTint(FbTint.white))
} else if (this.doing) {
// 进行中:中心一个白点
Column()
.width(this.dia * 0.42)
.height(this.dia * 0.42)
.borderRadius(this.dia * 0.21)
.backgroundColor(Color.White)
}
}
.width(this.dia)
.height(this.dia)
}
}
/** 空态:一块居中的提示卡(Web 端 `.card.empty`) */
@ComponentV2
export struct FbEmptyBox {
/** 主标题 */
@Param title: string = '这里还是空的';
/** 副说明 */
@Param hint: string = '';
/** 可选的插图图标(默认圆点) */
@Param icon: Resource = $r('app.media.fb_ic_dot');
build() {
Column({ space: 8 }) {
Image(this.icon)
.width(34)
.height(34)
.opacity(0.35)
.renderMode(ImageRenderMode.Template)
.colorFilter(fbTint(FbTint.faint))
Text(this.title)
.fontSize(FbMetric.fontBody)
.fontWeight(FontWeight.Medium)
.fontColor(FbColors.ink)
if (this.hint.length > 0) {
Text(this.hint)
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.muted)
.textAlign(TextAlign.Center)
.lineHeight(20)
}
}
.width('100%')
.padding({ top: 40, bottom: 40, left: 24, right: 24 })
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
}
/** 区块小标题(Web 端 `.cinema-h2`:一行标题 + 可选的右侧说明) */
@ComponentV2
export struct FbSectionTitle {
@Param title: string = '';
@Param extra: string = '';
/**
* 🔴 v84(用户 2026-10-23):「日程,没有新建日程按钮,名册,也没有新增成员
* 这个按钮。……首页上的其他卡片也应该有这些基本功能。」
*
* `addText` 非空 ⇒ 在 extra 右边给一颗**小号文字按钮**(空 = 不画,行为与原来完全一致)。
*
* ⚠️ 为什么是"可选参数 + 默认空"而不是新加一个组件:
* 本组件被首页 4 个区块、日程页、任务页等多处引用,
* 改签名会波及所有调用点;`@Param` 带默认值则**老调用点一行都不用改**。
* ⚠️ 为什么按钮文案用文字而不是 `+` 图标:
* 首页区块标题这一行只有 20vp 高,放图标按钮手指点不准(实测点击热区
* 会小于 40vp);文字按钮横向量足,且与网页端 `datatable` 的「新增」观感一致。
* ⚠️ 点击热区用 `padding` 撑到 **44vp 高**(不是 fontTiny 那么点大)——
* `hitTestBehavior` 默认是 `Default`,padding 区**算在热区内**。
*/
@Param addText: string = '';
/** 点「新增」;`addText` 为空时永不触发 */
@Event onAdd: () => void = (): void => {
};
/**
* 区块的**模块主题色**(#60)。
*
* 🔴 用户 2026-11-05:「"今天的日程",既然是日程,标题颜色就应该用日程的
* 主题色。11条后边"新建"的地方变成一个按钮,按钮颜色也是主题色。」
* 「下面的"近期任务"改法跟上面一样,还有"最近纪念日"也不说了,全都是一样的。」
*
* ⇒ 标题与「新建」按钮都用这个色。
*
* ⚠️ **空串 = 保持原来的灰色**(老调用点不受影响):
* 本组件被首页 4 个区块 + 日程页 + 任务页 + 资源页等多处引用,
* 改签名会波及所有调用点;`@Param` 带默认值则老调用点一行都不用改。
* ⚠️ 调用方**不要硬编码色值**,从模块设定取(用户可能在"模块设置"里改色)。
*/
@Param accent: string = '';
/** 标题实际用的颜色:有 accent 用之,否则退回原来的 muted 灰 */
private titleColor(): string {
return this.accent !== '' ? this.accent : '#8A7A6D';
}
build() {
Row() {
Text(this.title)
.fontSize(FbMetric.fontSmall)
/* #60:有主题色时加粗一档 —— 光换色在浅色背景上辨识度不够 */
.fontWeight(this.accent !== '' ? FontWeight.Bold : FontWeight.Medium)
.fontColor(this.titleColor())
.letterSpacing(0.6)
if (this.extra.length > 0) {
Blank()
Text(this.extra)
.fontSize(FbMetric.fontTiny)
.fontColor(FbColors.faint)
}
if (this.addText.length > 0) {
/* extra 存在时用 6vp 间距隔开;extra 为空时靠 Blank 顶到右边 */
if (this.extra.length === 0) {
Blank()
}
/* 🔴 #60(用户 2026-11-05):「11条后边"新建"的地方变成一个按钮,
按钮颜色也是主题色。」
改前:纯文字 + brand 色 ⇒ 看着像链接,不醒目。
改后:**胶囊按钮** —— 底色 = 模块主题色(无 accent 时退回 brand),
文字白色,圆角全圆。
⚠️ 点击热区:胶囊本身 + `padding` 撑到约 44vp 高。
原来靠 `padding({top:12,bottom:12})` 撑热区,现在胶囊有实体高度,
热区天然够大,不必再叠大 padding。
⚠️ `FbColors` 里**没有** `accent` 字段(编译会报
"Property 'accent' does not exist")⇒ 用本组件的 `this.accent`。 */
Text(this.addText)
.fontSize(FbMetric.fontTiny)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Medium)
/* 🔴 #60(用户 2026-11-05):「右边11条,这个和那个"新建"挨得太近了。」
原因:`extra` 后面直接接胶囊,Row 无 space、胶囊无 margin ⇒ 两者贴死。
加 12vp 左外边距(不是 8)—— 胶囊有实体底色,视觉间距会比纯文字
显得更紧,8vp 看着仍然像一坨;12vp 才有清晰的分隔感。 */
.margin({ left: 12 })
.padding({ left: 10, right: 10, top: 5, bottom: 5 })
.backgroundColor(this.accent !== '' ? this.accent : '#A84A28')
.borderRadius(999)
.onClick((): void => {
this.onAdd();
})
}
}
.width('100%')
.padding({ left: 4, right: 4, top: 12, bottom: 8 })
.alignItems(VerticalAlign.Center)
}
}
/**
* 头像(Web 端 `nav_avatar.php`:姓名首字 + 由名字哈希出的底色)。
*
* 端上不做「上传头像」—— 先把「首字 + 色块」这套做出来,
* 接接口后再叠真实图片。
*/
@ComponentV2
export struct FbAvatar {
/** 显示名(取首字;也是**没有图片时**的回落) */
@Param name: string = '';
/** 底色(由业务侧算好传进来) */
@Param color: string = '#A84A28';
/** 直径(vp) */
@Param dia: number = 36;
/** 字号(vp);不传则按直径推算 */
@Param fontSizeVp: number = 0;
/**
* 头像图 URL(**空 = 用色块字回落**)。
*
* 🔴 为什么要支持真图(用户 2026-10-13:"名册的头像怎么都没有?"):
* 之前这里只有色块字,看起来像"没有头像"。
* 实测名册 800 人里只有 19 人传过头像 ⇒ **两者都要有**:
* 有图的显示图、没图的回落到色块字(这是绝大多数)。
* ⚠️ URL 由调用方用 `fbImageUrl(key, token)` 拼好传进来 ——
* `Image()` 不支持自定义请求头,令牌只能走查询串(见 `fbImageUrl` 的说明)。
*/
@Param imageUrl: string = '';
/** 取名字的第一个字(含中文名的姓) */
private initial(): string {
const t: string = this.name.trim();
if (t.length === 0) {
return '?';
}
return t.substring(0, 1);
}
build() {
/* 🔴🔴 v62(用户 2026-10-19):「**如果一个人没头像,在列表是有一个文字的
头像的,那么点击后弹框,也应该有。**」
⇒ 列表有文字头像(因为 `imageUrl` 是空 ⇒ 走 Text 分支),
而详情/简要卡是**纯色块**。
真因:这里原来是 `if/else` —— 只要 `imageUrl` 非空就**只画 Image**,
而那张图**加载失败时 `Image` 什么都不显示**(只露出 `.backgroundColor(color)`
的纯色块)⇒ 看起来就是"一个光板,没有文字头像"
(`alt()` 只在**加载中**起作用,失败后是空白)。
⚠️ 而"有 imageUrl 的人"其实不多(实测 800 人里 19 人),
但**空 key 与坏 key 的观感差别很大**:前者有字,后者是光板。
⇒ 改成**叠加**:底色 + 文字头像**常驻**,图片有就叠在上面
(图片一旦加载成功就完全盖住文字,失败则自动露出文字头像)。
⚠️ 之前注释说"图层叠会错位"——那是因为当时想靠 Image 的 intrinsic size 撑尺寸。
现在**两层都显式给 `dia`**,尺寸来源唯一,不存在错位。
⚠️ 用 Stack 而不是 `backgroundImage`:`backgroundImage` 不接受网络地址,
而头像走的是 `media.php?k=...`(见 `fbImageUrl` 的说明)。 */
Stack({ alignContent: Alignment.Center }) {
// ① 底色 + 文字头像(**永远在**,是"图的兜底")
Text(this.initial())
.width(this.dia)
.height(this.dia)
.borderRadius(this.dia / 2)
.backgroundColor(this.color)
.fontColor(Color.White)
.fontSize(this.fontSizeVp > 0 ? this.fontSizeVp : Math.round(this.dia * 0.42))
.fontWeight(FontWeight.Medium)
.textAlign(TextAlign.Center)
// ② 真头像(有才画;加载成功即盖住①,失败则①露出来)
if (this.imageUrl !== '') {
Image(this.imageUrl)
.width(this.dia)
.height(this.dia)
.borderRadius(this.dia / 2)
.objectFit(ImageFit.Cover)
}
}
.width(this.dia)
.height(this.dia)
}
}
/**
* 「同看的人」这类人员标签(Web 端 `person_chip`:小头像 + 称呼)。
*
* ⚠️ 会出现在 <a> 内部的行内场景,所以整块不可点、纯展示。
*/
@ComponentV2
export struct FbPersonChip {
@Param name: string = '';
@Param color: string = '#A84A28';
/** 是否显示文字(只放头像时传 false) */
@Param withText: boolean = true;
/**
* 头像图 URL(空 = 用色块字回落)。
*
* 🔴 #77i(用户 2026-11-05):「评论那个地方的头像怎么又没了?」
*
* 真因:`FbAvatar` **本来就支持** `imageUrl`(它自己的注释里写着
* 「名册 800 人里只有 19 人传过头像 ⇒ 两者都要有:有图显示图、没图回落色块字」),
* 但 `FbPersonChip` **没有把这个参数透传下去** ——
* 它内部只写了 `FbAvatar({ name, color, dia, fontSizeVp })`。
* ⇒ 凡是用 `FbPersonChip` 的地方,头像永远是首字色块,真图显示不出来。
*
* ⚠️ 这是我上一轮"改用规范组件"时替换掉的:原来手写的
* `FbAvatar({ ..., imageUrl: ... })` 是**能显示图的**,
* 换成 `FbPersonChip` 后反而丢了图 —— 用规范组件没错,
* 但规范组件本身缺了这层透传,得先补它。
* ⚠️ URL 由调用方用 `fbImageSrc(key, token)` 拼好传进来(与 `FbAvatar` 同口径)。
*/
@Param imageUrl: string = '';
build() {
Row({ space: 4 }) {
FbAvatar({
name: this.name,
color: this.color,
dia: 18,
fontSizeVp: 10,
imageUrl: this.imageUrl
})
if (this.withText) {
Text(this.name)
.fontSize(FbMetric.fontTiny)
.fontColor(FbColors.muted)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
}
.padding({ left: 4, right: 8, top: 2, bottom: 2 })
.borderRadius(FbMetric.radiusPill)
.backgroundColor(FbColors.inputBg)
}
}
/** 小徽标(Web 端 `.node-badge` / `.badge`:管理员、访客、标签…) */
@ComponentV2
export struct FbTag {
@Param text: string = '';
@Param tone: string = 'muted';
private fgColor(): string {
if (this.tone === 'brand') {
return '#A84A28';
}
if (this.tone === 'red') {
return '#C0533F';
}
if (this.tone === 'green') {
return '#4E7C3F';
}
if (this.tone === 'blue') {
return '#2F6FB0';
}
return '#7E6B5A';
}
private bgColor(): string {
if (this.tone === 'brand') {
return '#14A84A28';
}
if (this.tone === 'red') {
return '#14C0533F';
}
if (this.tone === 'green') {
return '#146F9E5C';
}
if (this.tone === 'blue') {
return '#142F6FB0';
}
return '#0F2C231D';
}
build() {
Text(this.text)
.fontSize(FbMetric.fontTiny)
.fontColor(this.fgColor())
.backgroundColor(this.bgColor())
.padding({ left: 7, right: 7, top: 2, bottom: 2 })
.borderRadius(FbMetric.radiusPill)
.maxLines(1)
}
}
/**
* 页头的「设置 / 更多」按钮(Web 端 `.btn.btn-icon`)。
*
* ⚠️ 必须是 `Button() { Image() }` 形式:`Button('label') { ... }` 同时给
* label 和子组件会报 10905202。
*/
@ComponentV2
export struct FbIconButton {
@Param icon: Resource = $r('app.media.fb_ic_gear');
/** 图标色:ResourceColor 同时接受 Resource($r 语义色)与 '#RRGGBB' 字符串 */
/**
* 图标颜色。**必须是 hex 字符串**(不是 `Resource`)——
* `colorFilter(fbTint(...))` 只吃 hex(颜色矩阵的入参)。
* ⚠️ 传 `FbColors.*`(Resource)会编译报错 `ResourceColor is not assignable to string`,
* 那是**故意的**:逼调用方用 `FbTint.*` 这组与 color.json 同值的常量。
*/
@Param tint: string = FbTint.muted;
/** 直径 */
@Param dia: number = 36;
@Event onTap: () => void = () => {
};
build() {
Button({ type: ButtonType.Circle }) {
Image(this.icon)
.width(18)
.height(18)
/* ⚠️ 本工程图标是 PNG ⇒ 必须 colorFilter(fillColor 只对 SVG 生效)。 */
.colorFilter(fbTint(this.tint))
}
.width(this.dia)
.height(this.dia)
.backgroundColor(FbColors.inputBg)
.onClick(() => {
this.onTap();
})
}
}
/**
* 主操作按钮(Web 端 `.btn`:模块色底 + 白字)。
*
* ⚠️ 同样用 `Button() { Row() { … } }` 形式,label 与子组件二选一。
*/
@ComponentV2
export struct FbPrimaryButton {
@Param label: string = '';
/** 底色(一般传模块主题色的 hex 字符串,也可传 Resource 语义色) */
@Param bg: ResourceColor = '#A84A28';
/** 前置图标(不传则只显示文字) */
@Param icon?: Resource = undefined;
/** 是否撑满宽度 */
@Param block: boolean = false;
@Event onTap: () => void = () => {
};
build() {
Button({ type: ButtonType.ROUNDED_RECTANGLE }) {
Row({ space: 6 }) {
if (this.icon !== undefined) {
Image(this.icon)
.width(16)
.height(16)
.renderMode(ImageRenderMode.Template)
.colorFilter(fbTint(FbTint.white))
}
Text(this.label)
.fontSize(FbMetric.fontSmall)
.fontColor(Color.White)
.fontWeight(FontWeight.Medium)
}
.justifyContent(FlexAlign.Center)
}
.height(36)
.padding({ left: 14, right: 14 })
.backgroundColor(this.bg)
.borderRadius(FbMetric.radiusS)
.stateEffect(true)
.width(this.block ? '100%' : undefined)
.onClick(() => {
this.onTap();
})
}
}
/**
* 一行「标签 + 值」的信息条(名册详情、观影信息、财务条目…都用它)。
*
* 对应 Web 端各页面里那种 `圆点 + 文字` 的行。
*/
@ComponentV2
export struct FbDotLine {
/** 圆点色(不传则不画点) */
@Param dotColor: string = '';
/** 左侧主文字 */
@Param label: string = '';
/** 右侧值 */
@Param tail: string = '';
/** 主文字淡化成次要色(用于「没有内容」的占位行) */
@Param muted: boolean = false;
build() {
Row({ space: 8 }) {
if (this.dotColor.length > 0) {
FbDot({ dia: 7, fillC: this.dotColor })
}
Text(this.label)
.fontSize(FbMetric.fontSmall)
.fontColor(this.muted ? FbColors.faint : FbColors.ink)
.layoutWeight(1)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
if (this.tail.length > 0) {
Text(this.tail)
.fontSize(FbMetric.fontTiny)
.fontColor(FbColors.faint)
.maxLines(1)
}
}
.width('100%')
.alignItems(VerticalAlign.Center)
}
}
/**
* 一条「N 格数字」统计条 —— 🔴🔴 **已删除(v54b,2026-10-17)**。
*
* 原来这里有个 `FbStatTriple`(三格:日程 / 待办 / 逾期),内部用
* `@Builder cell(label, value, alarm)` 画每一格。
*
* 用户 2026-10-17 报「概述卡……**日程、在做、待办的数量是错误的**」,
* 真机实测:接口与端上缓存都是 `{1,3,2,0}`(正确),界面却是 `0/0/0`。
* 根因:SDK 规则 `r/quick-rules/05-state.md` →
* > **@Builder 参数传递**:「**两个及以上参数不触发刷新**」
* ⇒ `cell()` 那份子树只按**初始值**渲染一次(那时还是默认的 0),
* 数据回来、`build()` 重跑了,但**这个多值 @Builder 的子树不重建**。
*
* ⇒ **删除而不是修补**,理由:
* ① 它当时**已无人引用**(首页改成四格 + `FbStatCell` 之后);
* ② 留着它,以后有人"顺手用一下"就会原样复发(本项目已踩过 6 次这个坑);
* ③ 它的三格形态也不再匹配需求(现在要四格)。
* ⇒ 新的写法用 **`components/FbStatCell.ets`**(`@ComponentV2` + `@Param`,
* **@Param 参与观察**),需要几格就摆几个。**不要再恢复 `@Builder` 版本。**
*/
/**
* 进度条(读书进度、旅程完成度…)。
*
* ⚠️ 用 Progress 的 Linear 类型;`ProgressType` 枚举是 Linear / Ring / Eclipse…
*/
@ComponentV2
export struct FbProgressLine {
/** 0–100 */
@Param percent: number = 0;
@Param color: string = '#A84A28';
build() {
Progress({ value: this.percent, total: 100, type: ProgressType.Linear })
.width('100%')
.height(4)
.color(this.color)
.backgroundColor(FbColors.inputBg)
}
}
/** 评分(观影 /·/10 分、读书 5 星)—— 用文字而非图形,避免臆造评分组件 */
@ComponentV2
export struct FbRating {
/** 分值 */
@Param score: number = 0;
/** 满分(10 或 5) */
@Param max: number = 10;
build() {
Row({ space: 2 }) {
Text('★')
.fontSize(FbMetric.fontTiny)
.fontColor('#C08A2E')
Text(this.max === 5 ? this.score.toFixed(1) : this.score.toFixed(1))
.fontSize(FbMetric.fontTiny)
.fontWeight(FontWeight.Medium)
.fontColor('#C08A2E')
Text('/' + this.max.toString())
.fontSize(FbMetric.fontTiny)
.fontColor(FbColors.faint)
}
}
}