EmailFeedbackPage 意见反馈 + AboutPage 关于页

前言
当用户使用「海风日记」一段时间后,可能会想:这个 App 是谁做的?我有问题该反馈给谁?于是 关于页(AboutPage) 与 意见反馈页(EmailFeedbackPage) 成了产品「最后一公里」的关键触点。
本文将从源码出发,深入讲解:
- EmailFeedbackPage 的反馈类型选择 + 文本输入 + 邮箱留档 + 提交校验
- AboutPage 的品牌头部 + 版本信息列表 + 版权尾部
- 表单校验的
enabled联动 - 用户协议、隐私政策的「内部链接」跳转
- 两个页面的视觉一致性原则
一个好的「关于页」,应该让用户感受到「这是有人在认真做的产品」,而不是冷冰冰的版权声明。
一、EmailFeedbackPage:四段式表单
意见反馈页采用经典的「类型选择 + 文本输入 + 邮箱留档 + 提交」结构。
typescript
@Entry
@Component
struct EmailFeedbackPage {
@State feedbackType: number = 0
@State feedbackText: string = ''
@State email: string = ''
build() {
Column() {
// 1. 导航栏
this.NavBarBuilder('意见反馈')
// 2. 表单主体
Scroll() {
Column({ space: 16 }) {
this.TypeSelectorBuilder() // 反馈类型
this.ContentInputBuilder() // 问题描述
this.EmailInputBuilder() // 联系邮箱
this.SubmitButtonBuilder() // 提交按钮
}
.padding({ left: 16, right: 16, top: 16, bottom: 40 })
}
.layoutWeight(1)
.scrollBar(BarState.Off)
}
.height('100%').backgroundColor(COLOR_BG)
}
}
1.1 页面结构图
┌────────────────────────────┐
│ ‹ 意见反馈 │ ← 导航栏
├────────────────────────────┤
│ 反馈类型 │
│ [功能建议][问题反馈]... │ ← 类型选择
│ │
│ 问题描述 │
│ ┌────────────────────────┐│
│ │ TextArea 多行输入 ││ ← 文本输入
│ │ ││
│ └────────────────────────┘│
│ │
│ 联系邮箱(选填) │
│ ┌────────────────────────┐│
│ │ Email 输入框 ││ ← 邮箱留档
│ └────────────────────────┘│
│ │
│ ┌────────────────────────┐│
│ │ 提交反馈 ││ ← 提交按钮
│ └────────────────────────┘│
└────────────────────────────┘
二、反馈类型选择:胶囊按钮组
typescript
@Builder
TypeSelectorBuilder() {
Column() {
Text('反馈类型').fontSize(12).fontColor(COLOR_TEXT_HINT)
Row({ space: 8 }) {
ForEach(['功能建议', '问题反馈', '体验问题', '其他'], (label, i) => {
Text(label)
.fontSize(13)
.fontColor(this.feedbackType === i ? COLOR_WHITE : COLOR_TEXT_MAIN)
.backgroundColor(
this.feedbackType === i ? COLOR_PRIMARY : COLOR_BUTTON_INACTIVE_BG
)
.borderRadius(14)
.padding({ left: 12, right: 12, top: 5, bottom: 5 })
.onClick(() => { this.feedbackType = i })
})
}
.margin({ top: 8 })
}
.alignItems(HorizontalAlign.Start)
.width('100%')
}
2.1 视觉对比
| 状态 | 背景色 | 字色 |
|---|---|---|
| 选中 | COLOR_PRIMARY(琥珀) |
白色 |
| 未选 | COLOR_BUTTON_INACTIVE_BG(浅灰) |
主文本色 |
胶囊形 borderRadius(14) 与按钮高度(约 28vp)形成半圆,符合 iOS 胶囊按钮规范。
2.2 为什么不用单选 radio?
- 空间占用:radio + label 横向排列,4 个选项需要 2 行
- 可读性:胶囊按钮的文字更醒目
- 触控友好:胶囊按钮的点击区域更大
三、问题描述:TextArea 多行输入
typescript
@Builder
ContentInputBuilder() {
Column() {
Text('问题描述').fontSize(12).fontColor(COLOR_TEXT_HINT)
TextArea({ placeholder: '请详细描述您遇到的问题', text: this.feedbackText })
.constraintSize({ minHeight: 140 })
.backgroundColor(COLOR_CARD_BG)
.borderRadius(CARD_RADIUS)
.padding(12)
.onChange((v) => this.feedbackText = v)
.margin({ top: 8 })
}
.alignItems(HorizontalAlign.Start).width('100%')
}
3.1 关键属性
| 属性 | 值 | 作用 |
|---|---|---|
constraintSize.minHeight |
140vp | 输入框最少高度,避免一开始只剩一行 |
placeholder |
提示文字 | 输入为空时显示 |
backgroundColor |
卡片色 | 与页面背景形成层次 |
3.2 字符校验
提交按钮的 enabled 状态会根据 feedbackText 的长度变化:
typescript
this.feedbackText.trim().length > 10
- 少于 10 字:按钮禁用,避免无意义提交
- 多于 500 字:可选截断或提示「请精简描述」
四、联系邮箱:InputType.Email
typescript
@Builder
EmailInputBuilder() {
Column() {
Text('联系邮箱(选填)').fontSize(12).fontColor(COLOR_TEXT_HINT)
TextInput({ placeholder: '方便我们回复您', text: this.email })
.type(InputType.Email)
.backgroundColor(COLOR_CARD_BG)
.borderRadius(INPUT_RADIUS)
.padding(12)
.onChange((v) => this.email = v)
.margin({ top: 8 })
}
.alignItems(HorizontalAlign.Start).width('100%')
}
4.1 InputType.Email 的作用
- 移动端键盘自动显示
@和.快捷键 - 部分输入法会校验邮箱格式
4.2 邮箱格式校验(可选)
typescript
const emailRegex = /^[\w.-]+@[\w.-]+\.\w+$/
if (!emailRegex.test(this.email)) {
promptAction.showToast({ message: '邮箱格式不正确' })
return
}
五、提交按钮:条件启用 + 视觉反馈
typescript
@Builder
SubmitButtonBuilder() {
Button('提交反馈')
.width('90%').height(50)
.fontSize(16).fontColor(COLOR_WHITE).fontWeight(FontWeight.Bold)
.backgroundColor(
this.feedbackText.trim().length > 10 ? COLOR_PRIMARY : COLOR_BUTTON_DISABLED_BG
)
.borderRadius(BTN_RADIUS_FULL)
.enabled(this.feedbackText.trim().length > 10)
.onClick(() => this.submitFeedback())
}
5.1 三层校验
typescript
submitFeedback() {
// 1. 必填校验
if (this.feedbackText.trim().length <= 10) {
promptAction.showToast({ message: '请至少输入 10 个字符' })
return
}
// 2. 邮箱格式校验(如果填了)
if (this.email && !/^[\w.-]+@[\w.-]+\.\w+$/.test(this.email)) {
promptAction.showToast({ message: '邮箱格式不正确' })
return
}
// 3. 提交(伪代码)
feedbackService.submit({
type: this.feedbackType,
content: this.feedbackText,
email: this.email
}).then(() => {
promptAction.showToast({ message: '反馈已提交,感谢支持!' })
router.back()
})
}
六、AboutPage:三段式品牌展示
关于页采用「品牌头部 + 信息列表 + 版权尾部」三段式结构。
typescript
@Entry
@Component
struct AboutPage {
build() {
Column() {
this.NavBarBuilder('关于海风')
Scroll() {
Column({ space: 24 }) {
this.BrandHeaderBuilder() // 1. 品牌 Logo
this.InfoListBuilder() // 2. 信息列表
this.CopyrightFooterBuilder() // 3. 版权信息
}
.padding({ left: 16, right: 16, top: 32, bottom: 40 })
}
.layoutWeight(1)
.scrollBar(BarState.Off)
}
.height('100%').backgroundColor(COLOR_BG)
}
}
七、品牌头部:Logo + 名称 + 版本 + Slogan
typescript
@Builder
BrandHeaderBuilder() {
Column({ space: 12 }) {
Stack() {
Circle().width(80).height(80).fill(COLOR_AVATAR_BG)
Text('🌊').fontSize(38)
}
Text('海风日记').fontSize(22).fontColor(COLOR_TEXT_MAIN).fontWeight(FontWeight.Bold)
Text('Version 1.0.0').fontSize(13).fontColor(COLOR_TEXT_HINT)
Text('让每一天随海风飘散').fontSize(14).fontColor(COLOR_TEXT_SECONDARY)
}
}
7.1 Logo 设计
- 80vp 圆形底色
COLOR_AVATAR_BG - 上层叠加 🌊 emoji,简单直观
- 相比于绘制 SVG,emoji 跨平台一致性更好
7.2 版本号格式
Version 1.0.0 遵循 语义化版本(SemVer):
1------ 主版本(不兼容更新)0------ 次版本(新增功能,向后兼容)0------ 修订号(bug 修复)
八、信息列表:ForEach 动态渲染
typescript
@Builder
InfoListBuilder() {
Column() {
ForEach([
{ label: '版本号', value: '1.0.0' },
{ label: '开发商', value: '海风科技' },
{ label: '更新日期', value: '2025年1月' },
{ label: '用户协议', value: '查看 ›' },
{ label: '隐私政策', value: '查看 ›' }
], (item) => {
Row() {
Text(item.label).fontSize(14).fontColor(COLOR_TEXT_MAIN).layoutWeight(1)
Text(item.value)
.fontSize(14)
.fontColor(item.value.includes('›') ? COLOR_AMBER : COLOR_TEXT_SECONDARY)
}
.height(50)
.padding({ left: PAGE_PADDING, right: PAGE_PADDING })
Divider().color(COLOR_DIVIDER).margin({ left: PAGE_PADDING })
})
}
.backgroundColor(COLOR_CARD_BG)
.borderRadius(CARD_RADIUS)
}
8.1 列表项高度统一
每项固定 height(50),确保用户在快速浏览时视线流动稳定。
8.2 「查看 ›」的视觉提示
「用户协议」「隐私政策」这两项是可点击的内部链接,用 COLOR_AMBER(琥珀色)标识,并附加 › 箭头符号提示「可跳转」。
typescript
.onClick(() => {
if (item.label === '用户协议') router.pushUrl({ url: 'pages/UserAgreementPage' })
if (item.label === '隐私政策') router.pushUrl({ url: 'pages/PrivacyPolicyPage' })
})
九、版权尾部:Slogan + Copyright
typescript
@Builder
CopyrightFooterBuilder() {
Column({ space: 6 }) {
Text('🌊 用文字守护每一天的珍贵').fontSize(13).fontColor(COLOR_AMBER)
Text('© 2025 海风科技').fontSize(11).fontColor(COLOR_TEXT_HINT)
}
}
9.1 版权符号 ©
Unicode 编码 U+00A9,在 HTML 中也可以用 © 表示。
9.2 Slogan 的情感价值
「用文字守护每一天的珍贵」比「让记录更简单」更有温度。这种情感化文案是品牌差异化的关键。
十、视觉一致性原则
两个页面虽然功能不同,但都遵循统一的设计规范:
| 设计要素 | 取值 | 作用 |
|---|---|---|
| 页面背景 | COLOR_BG |
浅米色,温暖 |
| 卡片背景 | COLOR_CARD_BG |
比页面背景略白 |
| 主色 | COLOR_PRIMARY / COLOR_AMBER |
品牌琥珀色 |
| 标题字号 | 17vp | 标准导航栏字号 |
| 正文字号 | 14vp | 易读 |
| 圆角 | CARD_RADIUS 12vp / BTN_RADIUS_FULL |
统一圆角体系 |
十一、常见问题与踩坑记录
Q1:TextArea 在 iOS 上换行触发 onChange 频繁?
原因 :合成输入期间多次触发。
解决 :用防抖 setTimeout 或在 onSubmit 时统一处理。
Q2:InputType.Email 在某些输入法上不显示 @?
原因 :输入法兼容性问题。
解决 :用 InputType.Normal + 手动校验。
Q3:版本号写死在代码里,更新时容易遗漏?
原因 :硬编码。
解决 :从 app.json5 的 versionName 动态读取:
typescript
import app from '@system.app'
app.getInfo().then((info) => {
this.version = info.versionName
})
十二、总结
本文通过「海风日记」的意见反馈和关于页面,深入讲解了:
- EmailFeedbackPage ------ 四段式表单:类型、内容、邮箱、提交
- AboutPage ------ 三段式品牌展示:Logo、信息列表、版权
- 表单校验 ------
enabled联动 + 字符长度 + 邮箱正则 - 视觉一致性 ------ 统一的颜色常量、字号、圆角
- 情感化文案 ------ Slogan 与 Copyright 的温度
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!