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:
2026-10-19 08:29:44 +08:00
parent 7858de27e3
commit 1a67247407
11 changed files with 1120 additions and 740 deletions
+121 -107
View File
@@ -18,6 +18,7 @@ import { FbColors, FbMetric } from '../common/Theme';
import { FbCalEventFull } from '../data/FbCalRepo';
import { FbCalBook } from '../model/Types';
import { FbDot } from './Widgets';
import { FbSheetRing } from './FbSheetRing';
const FB_SHEET_BOTTOM_GAP: number = 16;
@@ -145,121 +146,134 @@ export struct FbEventSheet {
// 宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。
// ⚠️ 外层铺满 + Center;内层仍是那张 92% 宽的圆角卡。
Column() {
Column({ space: 0 }) {
// ---- 标题区 ----
Row({ space: 10 }) {
FbDot({ dia: 12, fillC: this.bookColor })
Text(this.event !== null && this.event.title !== ''
? this.event.title : '(无标题)')
.fontSize(FbMetric.fontTitle)
.fontWeight(FontWeight.Medium)
.fontColor(FbColors.ink)
.maxLines(2)
.layoutWeight(1)
if (this.event !== null && this.event.all_day) {
Text('全天')
.fontSize(FbMetric.fontTiny)
.fontColor(FbColors.muted)
.padding({ left: 8, right: 8, top: 3, bottom: 3 })
.backgroundColor(FbColors.inputBg)
.borderRadius(FbMetric.radiusPill)
}
}
.width('100%')
.alignItems(VerticalAlign.Top)
Divider()
.color(FbColors.line)
.margin({ top: 12, bottom: 4 })
if (this.loading) {
Row({ space: 8 }) {
LoadingProgress().width(18).height(18).color(FbColors.faint)
Text('正在读取…')
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.faint)
}
.width('100%')
.padding({ top: 20, bottom: 20 })
.justifyContent(FlexAlign.Center)
} else if (this.event === null) {
Text(this.error !== '' ? ('读不到这条日程:' + this.error) : '读不到这条日程')
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.faint)
.width('100%')
.padding({ top: 20, bottom: 20 })
} else {
Scroll() {
Column({ space: 0 }) {
this.row('时间', this.timeText())
if (this.bookName !== '') {
this.row('日历本', this.bookName)
// 🔴 2026-10-19:卡片外面的主题色环统一由 `FbSheetRing` 给
// (用户:「所有弹框都加线,保持一致」)。环色用**这本日历的颜色**,
// 一眼能看出这条日程属于哪个日历本。
Column() {
FbSheetRing({ radius: FbMetric.radius, ringColor: this.bookColor }) {
Column({ space: 0 }) {
// ---- 标题区 ----
Row({ space: 10 }) {
FbDot({ dia: 12, fillC: this.bookColor })
Text(this.event !== null && this.event.title !== ''
? this.event.title : '(无标题)')
.fontSize(FbMetric.fontTitle)
.fontWeight(FontWeight.Medium)
.fontColor(FbColors.ink)
.maxLines(2)
.layoutWeight(1)
if (this.event !== null && this.event.all_day) {
Text('全天')
.fontSize(FbMetric.fontTiny)
.fontColor(FbColors.muted)
.padding({ left: 8, right: 8, top: 3, bottom: 3 })
.backgroundColor(FbColors.inputBg)
.borderRadius(FbMetric.radiusPill)
}
// ⚠️ build() 里**不能声明变量**(只有 UI 组件语法)⇒
// 条件直接调方法(方法返回空串就不显示)
if (this.repeatText() !== '') {
this.row('重复', this.repeatText())
}
if (this.remindText() !== '') {
this.row('提醒', this.remindText())
}
if (this.event.location !== '') {
this.row('地点', this.event.location)
}
if (this.event.note !== '') {
this.row('备注', this.event.note)
}
Row().height(8)
}
.width('100%')
.alignItems(HorizontalAlign.Start)
}
.layoutWeight(1)
.width('100%')
.scrollBar(BarState.Off)
.align(Alignment.TopStart)
}
.alignItems(VerticalAlign.Top)
// ---- 底部按钮 ----
Row({ space: 10 }) {
Text('关闭')
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.muted)
.textAlign(TextAlign.Center)
.layoutWeight(1)
.height(42)
.backgroundColor(FbColors.inputBg)
.borderRadius(FbMetric.radiusS)
.onClick((): void => {
this.onClose();
})
if (this.canEdit && this.event !== null) {
Text('编辑日程')
.fontSize(FbMetric.fontSmall)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.layoutWeight(1)
.height(42)
.backgroundColor('#4A7C82')
.borderRadius(FbMetric.radiusS)
.onClick((): void => {
this.onEdit();
})
Divider()
.color(FbColors.line)
.margin({ top: 12, bottom: 4 })
if (this.loading) {
Row({ space: 8 }) {
LoadingProgress().width(18).height(18).color(FbColors.faint)
Text('正在读取…')
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.faint)
}
.width('100%')
.padding({ top: 20, bottom: 20 })
.justifyContent(FlexAlign.Center)
} else if (this.event === null) {
Text(this.error !== '' ? ('读不到这条日程:' + this.error) : '读不到这条日程')
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.faint)
.width('100%')
.padding({ top: 20, bottom: 20 })
} else {
Scroll() {
Column({ space: 0 }) {
this.row('时间', this.timeText())
if (this.bookName !== '') {
this.row('日历本', this.bookName)
}
// ⚠️ build() 里**不能声明变量**(只有 UI 组件语法)⇒
// 条件直接调方法(方法返回空串就不显示)
if (this.repeatText() !== '') {
this.row('重复', this.repeatText())
}
if (this.remindText() !== '') {
this.row('提醒', this.remindText())
}
if (this.event.location !== '') {
this.row('地点', this.event.location)
}
if (this.event.note !== '') {
this.row('备注', this.event.note)
}
Row().height(8)
}
.width('100%')
.alignItems(HorizontalAlign.Start)
}
/* 🔴 2026-10-19:原来是 `.layoutWeight(1)` —— 它让 Scroll **吃满剩余高度**,
于是短内容的日程也会把卡片顶到 `maxHeight`(72%),
按钮被推到最底、中间留一大片空白(真机截图:标题下面空半屏)。
⇒ 改成 `flexShrink(1)`:卡片按内容自适应高,
只有当内容真的超过 72% 时这一块才收缩并滚动。
⚠️ 不能只删掉 `layoutWeight`:`Column` 的子项默认 `flexShrink = 0`,
不收缩 ⇒ 长内容会**溢出卡片**被裁掉(反方向踩坑)。 */
.flexShrink(1)
.width('100%')
.scrollBar(BarState.Off)
.align(Alignment.TopStart)
}
// ---- 底部按钮 ----
Row({ space: 10 }) {
Text('关闭')
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.muted)
.textAlign(TextAlign.Center)
.layoutWeight(1)
.height(42)
.backgroundColor(FbColors.inputBg)
.borderRadius(FbMetric.radiusS)
.onClick((): void => {
this.onClose();
})
if (this.canEdit && this.event !== null) {
Text('编辑日程')
.fontSize(FbMetric.fontSmall)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.layoutWeight(1)
.height(42)
.backgroundColor('#4A7C82')
.borderRadius(FbMetric.radiusS)
.onClick((): void => {
this.onEdit();
})
}
}
.width('100%')
.margin({ top: 14 })
}
// 🔴 用户 2026-10-16:「它应该弹出一个**圆角的、浮在上面的卡片**……
// **不要让它左右充满整个屏幕,下面也不要挨着屏幕**,
// 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」
// ⇒ 不再贴底满宽:左右留边、四角圆角、垂直居中(由宿主的 Stack 对齐)。
.width('100%')
.constraintSize({ maxHeight: '72%' })
.padding({ left: 18, right: 18, top: 18, bottom: 20 + FB_SHEET_BOTTOM_GAP })
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.margin({ top: 14 })
}
// 🔴 用户 2026-10-16:「它应该弹出一个**圆角的、浮在上面的卡片**……
// **不要让它左右充满整个屏幕,下面也不要挨着屏幕**,
// 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」
// ⇒ 不再贴底满宽:左右留边、四角圆角、垂直居中(由宿主的 Stack 对齐)。
.width('92%')
.constraintSize({ maxHeight: '72%' })
.padding({ left: 18, right: 18, top: 18, bottom: 20 + FB_SHEET_BOTTOM_GAP })
.backgroundColor(FbColors.surface)
.borderRadius(FbMetric.radius)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.height('100%')