Files
FamilyBook_harmonyos/entry/src/main/ets/components/FbFormField.ets
T

149 lines
5.3 KiB
Plaintext
Raw Normal View History

/**
* 全局表单字段组件(GLOBAL-FORM-SPEC)。
*
* 🔴 用户 2026-11-05 定的**全局规范**(原话):
*
* 「你记住,以后我们所有的功能全部都这样:
* 1. 点击名册里面的"添加",姓名之类的内容全部都按这种形式显示。
* 它会按照主题色,加粗大一点显示。**所有地方都需要这样改**。
* 2. 为了解决字数原因导致输入框左右来回对不齐的问题,我们全部按两排来显示:
* 第一排显示片名,因为是必选必填,所以用红色;下一行开始是片名的输入框,
* 以及开始时间、结束时间的输入框,全部放在下一行,这样一列下来就行了。」
*
* ## 规范
*
* | 项 | 规则 |
* |---|---|
* | 标签颜色 | **主题色**(传入的 `accent`) |
* | 标签字重 | **Bold** |
* | 标签字号 | `fontSmall`(比正文大一号) |
* | 必填标记 | **红色** `#C0533F`,**不加星号** |
* | 版式 | **标签独占第一排,控件第二排整宽** |
*
* ## 为什么做成组件而不是各表单各写一份 `@Builder`
*
* 之前 5 个表单各有一份 `field()` / `labelRow()`,改一处就要改五处,
* 而且各文件的**外层结构不一致**(有的 `Row` 有的 `Column`),
* 批量脚本改坏过两次(`@Builder` 重复、括号错乱)。
*
* ⇒ 收敛到一个组件,**表单只调用,不自己实现**:
* · 规范改一次,全站生效
* · 结构统一,不会再出现"这个表单两排、那个单排"
*
* ## 为什么两排能解决左右对齐
*
* 原来是 `标签 | 输入框` 同行,标签宽度由**字数**决定:
*
* 姓名 [__________] ← 2 字
* 我对他的称呼 [__________] ← 6 字,输入框起点更靠右
*
* 有人用 `标签.width(48)` 硬凑,但字数一变就得改数字,
* 而且旁边多个按钮(如「自动获取」)又会挤歪。
*
* 标签独占一行后,所有控件都从**同一左边缘**开始,与字数、有无侧边按钮**无关**。
*/
import { FbColors, FbMetric } from '../common/Theme';
/**
* 一行表单字段(标签 + 输入框,两排)。
*
* @example
* FbFormField({
* label: '片名',
* value: this.title,
* placeholder: '看了哪一部?',
* accent: this.accentColor(),
* required: true,
* onChange: (v: string): void => { this.title = v; }
* })
*/
@ComponentV2
export struct FbFormField {
/** 标签文字 */
@Param label: string = '';
/** 当前值 */
@Param value: string = '';
/** 占位提示(输入框为空时显示) */
@Param placeholder: string = '';
/** 主题色 —— 标签用这个颜色(`required` 时被红色覆盖) */
@Param accent: string = '#3F7D5A';
/** 是否必填 —— 必填时标签变红,**不加星号**(用户明确要求) */
@Param required: boolean = false;
/** 是否多行(简介、备注这类长文本) */
@Param multiline: boolean = false;
/** 输入框高度(多行时用) */
@Param boxHeight: number = 42;
/** 值变化回调 */
@Event onChange: (v: string) => void = (_v: string): void => {
};
/** 标签颜色:必填红,否则主题色 */
private labelColor(): string {
return this.required ? '#C0533F' : this.accent;
}
build() {
Column({ space: 6 }) {
/* ① 第一排:标签(主题色/必填红,Bold,大一号,无星号) */
Text(this.label)
.fontSize(FbMetric.fontSmall)
.fontWeight(FontWeight.Bold)
.fontColor(this.labelColor())
.width('100%')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
/* ② 第二排:输入框(整宽,与标签左边缘对齐) */
if (this.multiline) {
TextArea({ text: this.value, placeholder: this.placeholder })
.placeholderColor(FbColors.faint)
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.ink)
.backgroundColor(FbColors.inputBg)
.borderRadius(FbMetric.radiusS)
.width('100%')
.height(this.boxHeight)
.padding({ left: 12, right: 12, top: 10, bottom: 10 })
.onChange((v: string): void => {
this.onChange(v);
})
} else {
TextInput({ text: this.value, placeholder: this.placeholder })
.placeholderColor(FbColors.faint)
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.ink)
.backgroundColor(FbColors.inputBg)
.borderRadius(FbMetric.radiusS)
.width('100%')
.height(this.boxHeight)
.padding({ left: 12, right: 12 })
.onChange((v: string): void => {
this.onChange(v);
})
}
}
.width('100%')
.alignItems(HorizontalAlign.Start)
}
}
/**
* 只读的「标签 + 说明」两排块(用于"参与的人""照片"这类
* 内容由别处渲染的字段,只需要一个统一的标签样式)。
*/
@ComponentV2
export struct FbFormLabel {
@Param label: string = '';
@Param accent: string = '#3F7D5A';
@Param required: boolean = false;
build() {
Text(this.label)
.fontSize(FbMetric.fontSmall)
.fontWeight(FontWeight.Bold)
.fontColor(this.required ? '#C0533F' : this.accent)
.width('100%')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
}