v60b:所有弹框统一主题色描边环 + 编辑改居中弹框 + 修名册下拉回弹吃掉悬浮栏
1)9 个浮层统一描边环(抽组件,不复制 8 遍)
新增 components/FbSheetRing.ets:环宽 FB_RING_W=2 全工程唯一一处;
实现是"外层 Column 铺色 + padding",不是 border(border 画在盒子内侧);
外圆角 = 内圆角 + 环宽。简要卡/详情/编辑/日程详情/日程编辑/观影详情/影院/
日程设置/模块设置全部改用;各浮层的环色 = 所属模块的主题色。
2)编辑从"贴底满宽 + 只有上圆角"改成居中弹框
人员编辑与日程编辑:Stack 对齐改 Center,卡片 92% 宽 + 四角圆 + 环;
constraintSize 留在卡片自己身上(挪到外层会让内层 Scroll().layoutWeight
拿到 0 高度,资料区塌成一条线)。日程的日期时间选择弹层、两个删除确认框
一并套环,避免同一次交互里出现两种形态。
3)修名册下拉到头回弹后悬浮按钮消失
EdgeEffect.Spring 在越界松手后把内容弹回,每帧 offset>0 被当成"用户在往上滑"
⇒ 收起悬浮栏。判据 = 手指已抬起 + 最后一次方向是下滑 + 抬手不到 500ms;
拦截必须排在 offset >= HIDE_AT 快路径之前(回弹第一帧位移常 >= 12vp)。
4)真机截图暴露的两个观感问题(静态检查全绿也照错)
- Scroll().layoutWeight(1) 让短内容也撑满 maxHeight ⇒ 卡片空半屏
(日程详情/模块设置),改为 flexShrink(1);内容本来就长的浮层不动。
- 人员编辑"虚拟人物(还没弄清真实身份)"被卡片裁掉半句:两个小开关从
并排 Row 改为 Flex(wrap) 换行,间距改走 margin。
验证:ArkTS 守卫 444/0;反向验证 17/17;真机 _fb_v60b_verify.py 11/0,
且关掉回弹拦截重编重装后该用例确实变红(证明测试不是假绿);
9 个浮层逐个真机截图确认。
This commit is contained in:
@@ -24,9 +24,11 @@
|
||||
* 只是缺那几项详情专属字段,界面不会空。
|
||||
*/
|
||||
|
||||
import { FbColors, FbMetric, fbTint, FbTint, FB_HAIRLINE, FB_SHADOW, fbAlpha } from '../common/Theme';
|
||||
import { FbColors, FbMetric, fbTint, FbTint, fbAlpha } from '../common/Theme';
|
||||
import { FbModuleDef, fbModuleOf, fbThemeVars } from '../model/Modules';
|
||||
import { FbRosterPerson, FbJobRow, FbContactRow } from '../data/FbPersonRepo';
|
||||
import { FbAvatar, FbTag } from './Widgets';
|
||||
import { FbSheetRing } from './FbSheetRing';
|
||||
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
||||
import { fbImageSrc } from '../data/FbAttachQueue';
|
||||
|
||||
@@ -114,6 +116,26 @@ export struct FbPersonSheet {
|
||||
*/
|
||||
@Param embedded: boolean = false;
|
||||
|
||||
/**
|
||||
* 卡片外圈描边环的颜色(hex)。
|
||||
*
|
||||
* 不传则用**名册模块**的主题色 —— 本组件展示的是"人的资料",
|
||||
* 不管从哪个页面调起(名册 / 顶栏「我的资料」),环色都该是同一个色系。
|
||||
*
|
||||
* ⚠️ 必须是字符串:`FbSheetRing` 的 `ringColor` 要直接当背景色用,
|
||||
* 而 `FbColors.brand` 是 `Resource`(不能做 `@Param` 默认值参与运算)。
|
||||
*/
|
||||
@Param accent: string = '';
|
||||
|
||||
/** 环色:显式给了就用给的,否则取名册模块色 */
|
||||
private accentColor(): string {
|
||||
if (this.accent !== '') {
|
||||
return this.accent;
|
||||
}
|
||||
const m: FbModuleDef | undefined = fbModuleOf('people');
|
||||
return fbThemeVars(m !== undefined ? m.color : '#A84A28').brand;
|
||||
}
|
||||
|
||||
/** 一行的图标资源 */
|
||||
private iconOf(name: string): Resource {
|
||||
switch (name) {
|
||||
@@ -343,249 +365,256 @@ export struct FbPersonSheet {
|
||||
// 宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。
|
||||
// ⚠️ 外层铺满 + Center;内层仍是那张 92% 宽的圆角卡。
|
||||
Column() {
|
||||
Column({ space: 0 }) {
|
||||
// ---- 顶部:头像 + 姓名 + 称谓 ----
|
||||
// ⚠️ 这一块**不参与滚动**(它跟着卡片固定在顶部):
|
||||
// 用户往下滑看资料时,"这是谁"始终可见,不用滚回去确认。
|
||||
Row({ space: 14 }) {
|
||||
FbAvatar({
|
||||
name: this.person !== null ? this.person.shown : '',
|
||||
color: this.person !== null ? this.person.avatarColor : '#A84A28',
|
||||
dia: 60,
|
||||
fontSizeVp: 24,
|
||||
// 🔴 v52 补上(之前漏传 ⇒ 详情里永远看不到头像,见 avatarUrl 的说明)
|
||||
imageUrl: this.avatarUrl()
|
||||
})
|
||||
Column({ space: 6 }) {
|
||||
Row({ space: 8 }) {
|
||||
Text(this.person !== null ? this.person.shown : '')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
.maxLines(1)
|
||||
if (this.person !== null && this.person.isAdmin) {
|
||||
FbTag({ text: '管理员', tone: 'brand' })
|
||||
// ⚠️ 2026-10-19:**卡片外面那一圈主题色环**由共用组件 `FbSheetRing` 给
|
||||
// (用户要求"所有弹框都加线,保持一致")。
|
||||
// 这一层只负责**尺寸**(92% 宽 / 72% 高上限)—— 尺寸与外观分开,
|
||||
// 环组件里就不必再堆一堆 `if (embedded)`。
|
||||
Column() {
|
||||
FbSheetRing({
|
||||
radius: FbMetric.radius,
|
||||
ringColor: this.accentColor(),
|
||||
/* embedded 是"塞进 bindSheet"的形态:宿主自己已有容器与投影,
|
||||
再叠一层阴影会显脏(真机上看着像卡片边缘发黑)。 */
|
||||
shadowRadius: this.embedded ? 0 : 24
|
||||
}) {
|
||||
Column({ space: 0 }) {
|
||||
// ---- 顶部:头像 + 姓名 + 称谓 ----
|
||||
// ⚠️ 这一块**不参与滚动**(它跟着卡片固定在顶部):
|
||||
// 用户往下滑看资料时,"这是谁"始终可见,不用滚回去确认。
|
||||
Row({ space: 14 }) {
|
||||
FbAvatar({
|
||||
name: this.person !== null ? this.person.shown : '',
|
||||
color: this.person !== null ? this.person.avatarColor : '#A84A28',
|
||||
dia: 60,
|
||||
fontSizeVp: 24,
|
||||
// 🔴 v52 补上(之前漏传 ⇒ 详情里永远看不到头像,见 avatarUrl 的说明)
|
||||
imageUrl: this.avatarUrl()
|
||||
})
|
||||
Column({ space: 6 }) {
|
||||
Row({ space: 8 }) {
|
||||
Text(this.person !== null ? this.person.shown : '')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
.maxLines(1)
|
||||
if (this.person !== null && this.person.isAdmin) {
|
||||
FbTag({ text: '管理员', tone: 'brand' })
|
||||
}
|
||||
}
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// 本名(昵称与不同名时才有意义)
|
||||
if (this.person !== null && this.person.name !== this.person.shown) {
|
||||
Text('本名 ' + this.person.name)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.muted)
|
||||
}
|
||||
// 称谓(服务端按家族图谱推导)
|
||||
if (this.person !== null && this.person.label !== '') {
|
||||
FbTag({ text: this.person.label, tone: 'muted' })
|
||||
}
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// 本名(昵称与不同名时才有意义)
|
||||
if (this.person !== null && this.person.name !== this.person.shown) {
|
||||
Text('本名 ' + this.person.name)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
// ---- 标签 ----
|
||||
// ⚠️ 标签行**留在头部(不滚动)**:它很短,而且"这人是哪类人"是扫一眼就要的
|
||||
// 信息,跟着资料一起滚走反而不方便。详情里的"长内容"是资料行(见下面)。
|
||||
if (this.person !== null && this.person.tags.length > 0) {
|
||||
Row({ space: 6 }) {
|
||||
ForEach(this.person.tags, (t: string) => {
|
||||
FbTag({ text: t, tone: 'blue' })
|
||||
}, (t: string): string => 'pt_' + t)
|
||||
}
|
||||
.width('100%')
|
||||
.margin({ top: 12 })
|
||||
}
|
||||
|
||||
Divider()
|
||||
.color(FbColors.line)
|
||||
.margin({ top: 14, bottom: 4 })
|
||||
|
||||
// 🔴 **资料区可滚动**(用户 2026-10-15:「显示详细信息,并且可以往下滑」)。
|
||||
//
|
||||
// 为什么必须给它一个 `Scroll`:
|
||||
// 卡片整体限了 `maxHeight: 78%`,而资料行(手机/单位/职务/生日阴阳历/
|
||||
// 邮箱/地址/座右铭/备注)加起来很容易超过这个高度 ——
|
||||
// 没有 `Scroll` 时**超出的部分直接被裁掉、看不到也够不着**。
|
||||
// ⚠️ 用 `layoutWeight(1)` 让 Scroll 吃掉"头部与底部按钮之间的剩余高度",
|
||||
// 而不是给固定高度 —— 内容长短不同时都能正好铺满。
|
||||
// ⚠️ 底部按钮**留在 Scroll 外面**:它们是"动作",不该跟着内容滚走
|
||||
// (要滑到底才能点"编辑"是很糟的体验)。
|
||||
Scroll() {
|
||||
Column({ space: 0 }) {
|
||||
// ---- 资料 ----
|
||||
// ⚠️ 每条都**只在有值时显示** —— 空行会让浮层显得"很多没填",
|
||||
// 而实际上大部分人的手机/邮箱/地址本来就是空的。
|
||||
if (this.person !== null && this.person.phone !== '') {
|
||||
this.infoRow('phone', '手机号', this.person.phone)
|
||||
}
|
||||
// 🔴 补充电话(服务端下发的 `phones`,主号已在上面单独显示)
|
||||
if (this.person !== null && this.person.phones.length > 1) {
|
||||
this.infoRow('phone', '其他电话', this.otherPhones())
|
||||
}
|
||||
if (this.person !== null && this.person.org !== '') {
|
||||
this.infoRow('org', '单位', this.person.org)
|
||||
}
|
||||
if (this.person !== null && this.person.title !== '') {
|
||||
this.infoRow('org', '职务', this.person.title)
|
||||
}
|
||||
// 家中排行(只在有值时显示;1 = 老大)
|
||||
if (this.person !== null && this.person.birthRank !== '') {
|
||||
this.infoRow('org', '家中排行', '第 ' + this.person.birthRank + ' 个')
|
||||
}
|
||||
// 生日:阳历 + 农历(农历是服务端算的)—— 合并成一行显示更好读
|
||||
if (this.person !== null && this.birthdayLine() !== '') {
|
||||
this.infoRow('birth', '生日', this.birthdayLine())
|
||||
}
|
||||
if (this.person !== null && this.person.email !== '') {
|
||||
this.infoRow('mail', '邮箱', this.person.email)
|
||||
}
|
||||
// 地址(结构化五级 + 详细;旧数据只有 `addrCity` 也能显示)
|
||||
if (this.person !== null && this.fullAddress() !== '') {
|
||||
this.infoRow('addr', '地址', this.fullAddress())
|
||||
}
|
||||
// 身份证(打码)—— 只显示,编辑时可改
|
||||
if (this.person !== null && this.person.idCardMasked !== '') {
|
||||
this.infoRow('card', '身份证', this.person.idCardMasked)
|
||||
}
|
||||
|
||||
// ---- 补充联系方式(vCard 对齐:邮箱/地址/网址,可多行)----
|
||||
if (this.person !== null && this.person.contacts.email.length > 0) {
|
||||
this.blockText('其它邮箱', this.contactText(this.person.contacts.email))
|
||||
}
|
||||
if (this.person !== null && this.person.contacts.address.length > 0) {
|
||||
this.blockText('其它地址', this.contactText(this.person.contacts.address))
|
||||
}
|
||||
if (this.person !== null && this.person.contacts.url.length > 0) {
|
||||
this.blockText('网址', this.contactText(this.person.contacts.url))
|
||||
}
|
||||
|
||||
// ---- 工作经历(可多段)----
|
||||
if (this.person !== null && this.person.jobs.length > 0) {
|
||||
this.jobsBlock()
|
||||
}
|
||||
|
||||
// ---- 座右铭 / 公共备注 / 我的私人备注 ----
|
||||
if (this.person !== null && this.person.slogan !== '') {
|
||||
this.blockText('座右铭', '「' + this.person.slogan + '」')
|
||||
}
|
||||
if (this.person !== null && this.person.note !== '') {
|
||||
this.blockText('备注', this.person.note)
|
||||
}
|
||||
// 🔴 「我的私人备注」与公共"备注"**是两块**(只有我看得到)
|
||||
if (this.person !== null && this.person.myNote !== '') {
|
||||
this.blockText('我的备注(仅自己可见)', this.person.myNote)
|
||||
}
|
||||
// 底部一点余量:最后一行不贴着按钮
|
||||
Row().height(8)
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
|
||||
// ---- 底部按钮:编辑 / 关闭 ----
|
||||
Row({ space: 10 }) {
|
||||
Text('编辑')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(Color.White)
|
||||
.textAlign(TextAlign.Center)
|
||||
.layoutWeight(1)
|
||||
.height(40)
|
||||
.backgroundColor('#4A7C82')
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.onClick((): void => {
|
||||
this.onEdit();
|
||||
})
|
||||
Text('关闭')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.muted)
|
||||
.textAlign(TextAlign.Center)
|
||||
.layoutWeight(1)
|
||||
.height(40)
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.onClick((): void => {
|
||||
this.onClose();
|
||||
})
|
||||
}
|
||||
// 称谓(服务端按家族图谱推导)
|
||||
if (this.person !== null && this.person.label !== '') {
|
||||
FbTag({ text: this.person.label, tone: 'muted' })
|
||||
}
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.width('100%')
|
||||
.margin({ top: 18 })
|
||||
|
||||
// ---- 标签 ----
|
||||
// ⚠️ 标签行**留在头部(不滚动)**:它很短,而且"这人是哪类人"是扫一眼就要的
|
||||
// 信息,跟着资料一起滚走反而不方便。详情里的"长内容"是资料行(见下面)。
|
||||
if (this.person !== null && this.person.tags.length > 0) {
|
||||
Row({ space: 6 }) {
|
||||
ForEach(this.person.tags, (t: string) => {
|
||||
FbTag({ text: t, tone: 'blue' })
|
||||
}, (t: string): string => 'pt_' + t)
|
||||
/* ---- 危险区:删除联系人 ----
|
||||
*
|
||||
* 🔴 用户 2026-10-18:「应该在详情页面的最下面加一个删除按钮。」
|
||||
* ⇒ 位置在**编辑/关闭之下**、并且与它们之间隔一条分隔线:
|
||||
* 手指从上往下扫时不会顺手点到;想删必须**再往下够**一次。
|
||||
* ⚠️ 视觉上只给文字 + 图标(不做成大红块):
|
||||
* 它是"偶尔用一次"的操作,大红按钮会抢走"编辑"的注意力。
|
||||
* ⚠️ 确认框在页面侧(`FbPeoplePage.confirmDelete`)—— 见 `onDelete` 的说明。 */
|
||||
if (this.canDelete) {
|
||||
Divider()
|
||||
.color(FbColors.line)
|
||||
.margin({ top: 14 })
|
||||
|
||||
Row({ space: 6 }) {
|
||||
Image($r('app.media.fb_ic_trash'))
|
||||
.width(14)
|
||||
.height(14)
|
||||
/* ⚠️ PNG 图标必须 colorFilter(fillColor 只对 SVG 生效) */
|
||||
.colorFilter(fbTint(RED))
|
||||
Text('删除联系人')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(RED)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
}
|
||||
.width('100%')
|
||||
.height(42)
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.backgroundColor(fbAlpha(RED, 0.08))
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.margin({ top: 10 })
|
||||
.onClick((): void => {
|
||||
this.onDelete();
|
||||
})
|
||||
}
|
||||
}
|
||||
// 🔴 用户 2026-10-16:「它应该弹出一个**圆角的、浮在上面的卡片**……
|
||||
// **不要让它左右充满整个屏幕,下面也不要挨着屏幕**,
|
||||
// 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」
|
||||
// ⇒ 不再贴底满宽:左右留边(92%)、四角圆角、垂直居中(由宿主的 Stack 对齐)。
|
||||
//
|
||||
// ⚠️ 宽度由**这一层**给(不是卡片自己):卡片在环里必须是 100%,
|
||||
// 否则 0.92×0.92 ≈ 84.6%,会比别的浮层窄一圈。
|
||||
// ⚠️ `embedded`(宿主自己就是 sheet)时宽度交给 sheet 决定 ——
|
||||
// 再用 92% 会在 sheet 里又缩一次,看上去"卡片比容器窄一圈"。
|
||||
.width('100%')
|
||||
.margin({ top: 12 })
|
||||
}
|
||||
|
||||
Divider()
|
||||
.color(FbColors.line)
|
||||
.margin({ top: 14, bottom: 4 })
|
||||
|
||||
// 🔴 **资料区可滚动**(用户 2026-10-15:「显示详细信息,并且可以往下滑」)。
|
||||
//
|
||||
// 为什么必须给它一个 `Scroll`:
|
||||
// 卡片整体限了 `maxHeight: 78%`,而资料行(手机/单位/职务/生日阴阳历/
|
||||
// 邮箱/地址/座右铭/备注)加起来很容易超过这个高度 ——
|
||||
// 没有 `Scroll` 时**超出的部分直接被裁掉、看不到也够不着**。
|
||||
// ⚠️ 用 `layoutWeight(1)` 让 Scroll 吃掉"头部与底部按钮之间的剩余高度",
|
||||
// 而不是给固定高度 —— 内容长短不同时都能正好铺满。
|
||||
// ⚠️ 底部按钮**留在 Scroll 外面**:它们是"动作",不该跟着内容滚走
|
||||
// (要滑到底才能点"编辑"是很糟的体验)。
|
||||
Scroll() {
|
||||
Column({ space: 0 }) {
|
||||
// ---- 资料 ----
|
||||
// ⚠️ 每条都**只在有值时显示** —— 空行会让浮层显得"很多没填",
|
||||
// 而实际上大部分人的手机/邮箱/地址本来就是空的。
|
||||
if (this.person !== null && this.person.phone !== '') {
|
||||
this.infoRow('phone', '手机号', this.person.phone)
|
||||
}
|
||||
// 🔴 补充电话(服务端下发的 `phones`,主号已在上面单独显示)
|
||||
if (this.person !== null && this.person.phones.length > 1) {
|
||||
this.infoRow('phone', '其他电话', this.otherPhones())
|
||||
}
|
||||
if (this.person !== null && this.person.org !== '') {
|
||||
this.infoRow('org', '单位', this.person.org)
|
||||
}
|
||||
if (this.person !== null && this.person.title !== '') {
|
||||
this.infoRow('org', '职务', this.person.title)
|
||||
}
|
||||
// 家中排行(只在有值时显示;1 = 老大)
|
||||
if (this.person !== null && this.person.birthRank !== '') {
|
||||
this.infoRow('org', '家中排行', '第 ' + this.person.birthRank + ' 个')
|
||||
}
|
||||
// 生日:阳历 + 农历(农历是服务端算的)—— 合并成一行显示更好读
|
||||
if (this.person !== null && this.birthdayLine() !== '') {
|
||||
this.infoRow('birth', '生日', this.birthdayLine())
|
||||
}
|
||||
if (this.person !== null && this.person.email !== '') {
|
||||
this.infoRow('mail', '邮箱', this.person.email)
|
||||
}
|
||||
// 地址(结构化五级 + 详细;旧数据只有 `addrCity` 也能显示)
|
||||
if (this.person !== null && this.fullAddress() !== '') {
|
||||
this.infoRow('addr', '地址', this.fullAddress())
|
||||
}
|
||||
// 身份证(打码)—— 只显示,编辑时可改
|
||||
if (this.person !== null && this.person.idCardMasked !== '') {
|
||||
this.infoRow('card', '身份证', this.person.idCardMasked)
|
||||
}
|
||||
|
||||
// ---- 补充联系方式(vCard 对齐:邮箱/地址/网址,可多行)----
|
||||
if (this.person !== null && this.person.contacts.email.length > 0) {
|
||||
this.blockText('其它邮箱', this.contactText(this.person.contacts.email))
|
||||
}
|
||||
if (this.person !== null && this.person.contacts.address.length > 0) {
|
||||
this.blockText('其它地址', this.contactText(this.person.contacts.address))
|
||||
}
|
||||
if (this.person !== null && this.person.contacts.url.length > 0) {
|
||||
this.blockText('网址', this.contactText(this.person.contacts.url))
|
||||
}
|
||||
|
||||
// ---- 工作经历(可多段)----
|
||||
if (this.person !== null && this.person.jobs.length > 0) {
|
||||
this.jobsBlock()
|
||||
}
|
||||
|
||||
// ---- 座右铭 / 公共备注 / 我的私人备注 ----
|
||||
if (this.person !== null && this.person.slogan !== '') {
|
||||
this.blockText('座右铭', '「' + this.person.slogan + '」')
|
||||
}
|
||||
if (this.person !== null && this.person.note !== '') {
|
||||
this.blockText('备注', this.person.note)
|
||||
}
|
||||
// 🔴 「我的私人备注」与公共"备注"**是两块**(只有我看得到)
|
||||
if (this.person !== null && this.person.myNote !== '') {
|
||||
this.blockText('我的备注(仅自己可见)', this.person.myNote)
|
||||
}
|
||||
// 底部一点余量:最后一行不贴着按钮
|
||||
Row().height(8)
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
|
||||
// ---- 底部按钮:编辑 / 关闭 ----
|
||||
Row({ space: 10 }) {
|
||||
Text('编辑')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(Color.White)
|
||||
.textAlign(TextAlign.Center)
|
||||
.layoutWeight(1)
|
||||
.height(40)
|
||||
.backgroundColor('#4A7C82')
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.onClick((): void => {
|
||||
this.onEdit();
|
||||
})
|
||||
Text('关闭')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.muted)
|
||||
.textAlign(TextAlign.Center)
|
||||
.layoutWeight(1)
|
||||
.height(40)
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.onClick((): void => {
|
||||
this.onClose();
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.margin({ top: 18 })
|
||||
|
||||
/* ---- 危险区:删除联系人 ----
|
||||
*
|
||||
* 🔴 用户 2026-10-18:「应该在详情页面的最下面加一个删除按钮。」
|
||||
* ⇒ 位置在**编辑/关闭之下**、并且与它们之间隔一条分隔线:
|
||||
* 手指从上往下扫时不会顺手点到;想删必须**再往下够**一次。
|
||||
* ⚠️ 视觉上只给文字 + 图标(不做成大红块):
|
||||
* 它是"偶尔用一次"的操作,大红按钮会抢走"编辑"的注意力。
|
||||
* ⚠️ 确认框在页面侧(`FbPeoplePage.confirmDelete`)—— 见 `onDelete` 的说明。 */
|
||||
if (this.canDelete) {
|
||||
Divider()
|
||||
.color(FbColors.line)
|
||||
.margin({ top: 14 })
|
||||
|
||||
Row({ space: 6 }) {
|
||||
Image($r('app.media.fb_ic_trash'))
|
||||
.width(14)
|
||||
.height(14)
|
||||
/* ⚠️ PNG 图标必须 colorFilter(fillColor 只对 SVG 生效) */
|
||||
.colorFilter(fbTint(RED))
|
||||
Text('删除联系人')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(RED)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
}
|
||||
.width('100%')
|
||||
.height(42)
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.backgroundColor(fbAlpha(RED, 0.08))
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.margin({ top: 10 })
|
||||
.onClick((): void => {
|
||||
this.onDelete();
|
||||
/* 最多占屏幕 72%:上方留出可见列表,暗示"这是浮层、下面还有内容"。
|
||||
⚠️ 这个限制**必须写在卡片自己身上**(不能交给外面的环层):
|
||||
卡片内部有 `Scroll().layoutWeight(1)`,它要靠**父容器的确定高度**
|
||||
才能算出"剩下多少可以滚"。把 maxHeight 挪到外面那层,
|
||||
内层高度就变成"由内容决定"⇒ `layoutWeight` 拿到 0 ⇒
|
||||
**资料区直接塌成一条线**(编辑卡同理,别一起挪)。 */
|
||||
.constraintSize({ maxHeight: this.embedded ? '100%' : '72%' })
|
||||
// ⚠️ embedded 时**去掉底部手势区那段留白**:那 16 是给
|
||||
// "贴底遮罩 + 隐藏手势条"的场景补的(见 FB_SHEET_BOTTOM_GAP),
|
||||
// 而 bindSheet 自己已经把手势区让出来了 ⇒ 再补就是白白多一截空白。
|
||||
.padding({
|
||||
left: 18, right: 18, top: 18,
|
||||
bottom: this.embedded ? 20 : 20 + FB_SHEET_BOTTOM_GAP
|
||||
})
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
}
|
||||
// 🔴 用户 2026-10-16:「它应该弹出一个**圆角的、浮在上面的卡片**……
|
||||
// **不要让它左右充满整个屏幕,下面也不要挨着屏幕**,
|
||||
// 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」
|
||||
// ⇒ 不再贴底满宽:左右留边(92%)、四角圆角、垂直居中(由宿主的 Stack 对齐)。
|
||||
//
|
||||
// ⚠️ `embedded`(宿主自己就是 sheet)时宽度交给 sheet 决定 ——
|
||||
// 再用 92% 会在 sheet 里又缩一次,看上去"卡片比容器窄一圈"。
|
||||
.width(this.embedded ? 'auto' : '92%')
|
||||
// 最多占屏幕 72%:上方留出可见列表,暗示"这是浮层、下面还有内容"
|
||||
.constraintSize({ maxHeight: this.embedded ? '100%' : '72%' })
|
||||
// ⚠️ embedded 时**去掉底部手势区那段留白**:那 16 是给
|
||||
// "贴底遮罩 + 隐藏手势条"的场景补的(见 FB_SHEET_BOTTOM_GAP),
|
||||
// 而 bindSheet 自己已经把手势区让出来了 ⇒ 再补就是白白多一截空白。
|
||||
.padding({
|
||||
left: 18, right: 18, top: 18,
|
||||
bottom: this.embedded ? 20 : 20 + FB_SHEET_BOTTOM_GAP
|
||||
})
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
/* 🔴 v59c(用户 2026-10-18):「这个卡片弹出来之后是白色的,和下面列表
|
||||
* 中的白色都融合在一起,根本看不出是弹框。」
|
||||
*
|
||||
* 原因:原来只靠宿主那层 `#00000066` 遮罩区分 —— 而本应用**列表本身
|
||||
* 就是大面积白底**,白卡压在"被压暗的白"上,边界仍然发飘。
|
||||
* ⇒ 补两手(与 `FbPersonQuickSheet` 完全同一套,两级浮层观感一致):
|
||||
* ① 阴影:把卡片从背景上**抬起来**(有投影才有"浮在空中"的暗示);
|
||||
* ② 极浅描边:同色背景上仍然有一条**可见的界线**。
|
||||
* ⚠️ 只加阴影是不够的:浅色底上的投影很淡,真机上常常看不出来;
|
||||
* 描边才是"稳"的那一手(它不依赖光照假设)。
|
||||
*/
|
||||
.shadow({ radius: 24, color: FB_SHADOW, offsetX: 0, offsetY: 6 })
|
||||
.border({ width: 1, color: FB_HAIRLINE })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
/* 🔴 embedded(宿主自己就是 sheet)时**不能再铺满 + 居中**:
|
||||
内容是"按自适应高度"交给 sheet 的,铺 100% 高会与
|
||||
|
||||
Reference in New Issue
Block a user