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:
@@ -49,6 +49,59 @@ const HIDE_AT: number = 12;
|
|||||||
*/
|
*/
|
||||||
const SHOW_AT: number = 8;
|
const SHOW_AT: number = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔴 2026-10-19 用户:「名册下拉的时候,拉到头,它会**回弹一下**,
|
||||||
|
* 导致下面的悬浮按钮**又消失了**。」
|
||||||
|
*
|
||||||
|
* 真因(`EdgeEffect.Spring` 的必然结果):
|
||||||
|
* · 手指往下拉(内容往下走)⇒ 每帧 `offset < 0` ⇒ 我们**正确地唤回**悬浮栏;
|
||||||
|
* · 拉到顶之后继续拉 = **越界**,内容停在原位、手指还在动;
|
||||||
|
* · **松手** ⇒ 系统弹簧把内容**弹回去**(内容往上走)⇒ 每帧 `offset > 0`
|
||||||
|
* ⇒ 被当成"用户在往上滑" ⇒ **收起悬浮栏**。
|
||||||
|
* 用户看到的就是"按钮闪没"。
|
||||||
|
*
|
||||||
|
* 判据(不用猜,用**手指方向 + 时间**):
|
||||||
|
* · 回弹一定发生在**抬手之后**、且紧跟着一次**下滑**;
|
||||||
|
* · 真正的"往上滑"要么手指还在屏幕上(`touchStartY >= 0`),
|
||||||
|
* 要么上一次手指方向就是往上(`lastDir === 1`)。
|
||||||
|
* ⇒ 只有"手指已抬起 + 上一次是下滑 + 刚抬手不到 `SPRING_MS`"才拦。
|
||||||
|
*
|
||||||
|
* ⚠️ 为什么用 500ms:本机弹簧动画(Spring)在真机上约 300–450ms 结束,
|
||||||
|
* 取 500 能把整段回弹盖住;再长就会误伤"下拉之后马上接着上滑"。
|
||||||
|
*/
|
||||||
|
const SPRING_MS: number = 500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手指按下位置(**-1 = 当前没有按压**;用哨兵值而不是 null,ArkTS 对可选值更严格)。
|
||||||
|
*
|
||||||
|
* ⚠️ 提到文件上方(原来是放在触摸段里的):`fbOnScroll` 也要读它判断
|
||||||
|
* "现在是指针在拖、还是抬手后的惯性/回弹"。
|
||||||
|
*/
|
||||||
|
let touchStartY: number = -1;
|
||||||
|
|
||||||
|
/** 一次滑动至少要移动这么多(vp)才认作"有方向意图"(避免把点击当滑动) */
|
||||||
|
const TOUCH_MIN: number = 16;
|
||||||
|
|
||||||
|
/** 手指**最近一次**明确方向:1 = 往上滑(内容上移),-1 = 往下滑,0 = 还没动 */
|
||||||
|
let lastDir: number = 0;
|
||||||
|
|
||||||
|
/** 最近一次抬手的时间戳(0 = 还没抬过手) */
|
||||||
|
let lastUpAt: number = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 现在收到的"正向位移"(内容往上走)是不是**弹簧回弹**。
|
||||||
|
*
|
||||||
|
* ⚠️ 只在 `fbOnScroll` 里用;判定条件见 `SPRING_MS` 上方的说明。
|
||||||
|
*/
|
||||||
|
function isSpringBack(): boolean {
|
||||||
|
// 手指还在屏幕上 ⇒ 用户正在拖,尊重他的方向
|
||||||
|
if (touchStartY >= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 刚刚是"往下拉",且才抬手不久 ⇒ 这一串正向位移是弹簧把内容弹回去
|
||||||
|
return lastDir === -1 && lastUpAt > 0 && (Date.now() - lastUpAt) < SPRING_MS;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 横向滚动容器上报(挂在**横向** Scroll 上的 `.onDidScroll`)。
|
* 横向滚动容器上报(挂在**横向** Scroll 上的 `.onDidScroll`)。
|
||||||
*
|
*
|
||||||
@@ -94,6 +147,13 @@ export function fbHScrollBusy(withinMs: number = 350): boolean {
|
|||||||
export function fbOnScroll(offset: number, state: ScrollState): void {
|
export function fbOnScroll(offset: number, state: ScrollState): void {
|
||||||
const app = fbAppState();
|
const app = fbAppState();
|
||||||
|
|
||||||
|
// 🔴 先拦"回弹":见 `SPRING_MS` 的说明。
|
||||||
|
// ⚠️ 必须放在**最前面**(包括快路径之前):
|
||||||
|
// 回弹的第一帧位移往往就 ≥ `HIDE_AT`,放到后面就来不及了。
|
||||||
|
if (offset > 0 && isSpringBack()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// ⚠️ **不跳过 Idle**:`Fling`(惯性)结束时最后那一下会带 Idle,
|
// ⚠️ **不跳过 Idle**:`Fling`(惯性)结束时最后那一下会带 Idle,
|
||||||
// 而内容短的页面可能**只在惯性阶段**产生位移 —— 跳过就永远收不起来。
|
// 而内容短的页面可能**只在惯性阶段**产生位移 —— 跳过就永远收不起来。
|
||||||
// 但 Idle 的 offset 偶尔是 0(无方向含义),所以下面按 offset 的正负判断,
|
// 但 Idle 的 offset 偶尔是 0(无方向含义),所以下面按 offset 的正负判断,
|
||||||
@@ -140,12 +200,8 @@ export function fbOnScroll(offset: number, state: ScrollState): void {
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
// 触摸方向兜底(内容不足一屏的页面用)
|
// 触摸方向兜底(内容不足一屏的页面用)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
// ⚠️ `touchStartY` / `TOUCH_MIN` 已提到文件上方 —— `fbOnScroll` 也要读它们
|
||||||
/** 手指按下的位置(**-1 = 当前没有按压**;用哨兵值而不是 null,ArkTS 对可选值更严格) */
|
// (判断"手指在拖"还是"抬手后的回弹")。这里不再重复声明。
|
||||||
let touchStartY: number = -1;
|
|
||||||
|
|
||||||
/** 一次滑动至少要移动这么多(vp)才认作"有方向意图"(避免把点击当滑动) */
|
|
||||||
const TOUCH_MIN: number = 16;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 触摸方向兜底:在**页面根容器**挂 `.onTouch(fbOnTouch)`。
|
* 触摸方向兜底:在**页面根容器**挂 `.onTouch(fbOnTouch)`。
|
||||||
@@ -180,6 +236,11 @@ export function fbOnTouch(event: TouchEvent): void {
|
|||||||
if (arr.length > 0) {
|
if (arr.length > 0) {
|
||||||
touchStartY = arr[0].windowY;
|
touchStartY = arr[0].windowY;
|
||||||
}
|
}
|
||||||
|
// 🔴 新一轮手势开始 ⇒ 清掉上一轮的方向残留。
|
||||||
|
// ⚠️ 不清的后果:上一轮是"往下拉"(`lastDir = -1`),这一轮只点了一下
|
||||||
|
// (位移 < `TOUCH_MIN`,不更新方向)就抬手 ⇒ 抬手的瞬间窗口又开了
|
||||||
|
// ⇒ 系统惯性里任何正向位移都被误判成回弹、悬浮栏该收却不收。
|
||||||
|
lastDir = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,12 +255,15 @@ export function fbOnTouch(event: TouchEvent): void {
|
|||||||
const dy: number = arr[0].windowY - touchStartY;
|
const dy: number = arr[0].windowY - touchStartY;
|
||||||
// dy < 0 ⇒ 手指往上抹 ⇒ 内容往上走 ⇒ 收起
|
// dy < 0 ⇒ 手指往上抹 ⇒ 内容往上走 ⇒ 收起
|
||||||
if (dy <= -TOUCH_MIN) {
|
if (dy <= -TOUCH_MIN) {
|
||||||
|
// 记下方向:抬手后若出现"相反方向的内容位移"(弹簧回弹),用它来识别
|
||||||
|
lastDir = 1;
|
||||||
if (!app.dockHidden) {
|
if (!app.dockHidden) {
|
||||||
app.dockHidden = true;
|
app.dockHidden = true;
|
||||||
}
|
}
|
||||||
// ⚠️ **不更新** touchStartY:否则手指继续上抹时会一直在阈值边缘反复触发。
|
// ⚠️ **不更新** touchStartY:否则手指继续上抹时会一直在阈值边缘反复触发。
|
||||||
// 保持不动 ⇒ 判断是"相对起点的总位移",更稳。
|
// 保持不动 ⇒ 判断是"相对起点的总位移",更稳。
|
||||||
} else if (dy >= TOUCH_MIN) {
|
} else if (dy >= TOUCH_MIN) {
|
||||||
|
lastDir = -1;
|
||||||
if (app.dockHidden) {
|
if (app.dockHidden) {
|
||||||
app.dockHidden = false;
|
app.dockHidden = false;
|
||||||
}
|
}
|
||||||
@@ -209,5 +273,8 @@ export function fbOnTouch(event: TouchEvent): void {
|
|||||||
|
|
||||||
if (t === TouchType.Up || t === TouchType.Cancel) {
|
if (t === TouchType.Up || t === TouchType.Cancel) {
|
||||||
touchStartY = -1;
|
touchStartY = -1;
|
||||||
|
// 🔴 抬手时刻:之后的 500ms 内,与"最后一次手指方向"相反的滚动
|
||||||
|
// 会被当作弹簧回弹忽略(见 `isSpringBack`)。
|
||||||
|
lastUpAt = Date.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,9 +24,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { FbColors, FbMetric } from '../common/Theme';
|
import { FbColors, FbMetric } from '../common/Theme';
|
||||||
|
import { FbModuleDef, fbModuleOf, fbThemeVars } from '../model/Modules';
|
||||||
import { FbCalBook } from '../model/Types';
|
import { FbCalBook } from '../model/Types';
|
||||||
import { FB_CAL_SETTINGS, FbCalView, FbCalStyle } from '../data/FbCalSettings';
|
import { FB_CAL_SETTINGS, FbCalView, FbCalStyle } from '../data/FbCalSettings';
|
||||||
import { FbCalOptionRow } from './FbCalOptionRow';
|
import { FbCalOptionRow } from './FbCalOptionRow';
|
||||||
|
import { FbSheetRing } from './FbSheetRing';
|
||||||
import { FbDot } from './Widgets';
|
import { FbDot } from './Widgets';
|
||||||
|
|
||||||
const FB_SHEET_BOTTOM_GAP: number = 16;
|
const FB_SHEET_BOTTOM_GAP: number = 16;
|
||||||
@@ -73,6 +75,22 @@ export struct FbCalSettingsSheet {
|
|||||||
@Local style: FbCalStyle = 'timeline';
|
@Local style: FbCalStyle = 'timeline';
|
||||||
@Local bookId: number = 0;
|
@Local bookId: number = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卡片外圈描边环的颜色(hex,'' = 用日程模块色)。
|
||||||
|
*
|
||||||
|
* ⚠️ 必须是字符串:环色要直接当背景色(`FbColors.brand` 是 `Resource`,用不了)。
|
||||||
|
*/
|
||||||
|
@Param accent: string = '';
|
||||||
|
|
||||||
|
/** 环色:显式给了就用给的,否则取日程模块色 */
|
||||||
|
private accentColor(): string {
|
||||||
|
if (this.accent !== '') {
|
||||||
|
return this.accent;
|
||||||
|
}
|
||||||
|
const m: FbModuleDef | undefined = fbModuleOf('calendar');
|
||||||
|
return fbThemeVars(m !== undefined ? m.color : '#A84A28').brand;
|
||||||
|
}
|
||||||
|
|
||||||
aboutToAppear(): void {
|
aboutToAppear(): void {
|
||||||
this.view = FB_CAL_SETTINGS.getView();
|
this.view = FB_CAL_SETTINGS.getView();
|
||||||
this.style = FB_CAL_SETTINGS.getStyle();
|
this.style = FB_CAL_SETTINGS.getStyle();
|
||||||
@@ -110,6 +128,10 @@ export struct FbCalSettingsSheet {
|
|||||||
// 宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。
|
// 宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。
|
||||||
// ⚠️ 外层铺满 + Center;内层仍是那张 92% 宽的圆角卡。
|
// ⚠️ 外层铺满 + Center;内层仍是那张 92% 宽的圆角卡。
|
||||||
Column() {
|
Column() {
|
||||||
|
// 🔴 2026-10-19:卡片外面加**主题色环**(用户:「所有弹框都加线」),
|
||||||
|
// 由共用组件 `FbSheetRing` 统一给;这一层只负责 92% 宽度。
|
||||||
|
Column() {
|
||||||
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accentColor() }) {
|
||||||
Column({ space: 0 }) {
|
Column({ space: 0 }) {
|
||||||
// ---- 标题栏 ----
|
// ---- 标题栏 ----
|
||||||
Row({ space: 12 }) {
|
Row({ space: 12 }) {
|
||||||
@@ -229,7 +251,11 @@ export struct FbCalSettingsSheet {
|
|||||||
.width('100%')
|
.width('100%')
|
||||||
.padding({ top: 14 })
|
.padding({ top: 14 })
|
||||||
}
|
}
|
||||||
.layoutWeight(1)
|
/* 🔴 2026-10-19:`layoutWeight(1)` 会让滚动区**吃满剩余高度** ⇒ 内容不满也把
|
||||||
|
卡片顶到 `maxHeight`(72%),下面空一大片。
|
||||||
|
⇒ `flexShrink(1)`:按内容自适应,超长才收缩滚动。
|
||||||
|
⚠️ 不能只删 `layoutWeight`:`Column` 子项默认 `flexShrink = 0` ⇒ 超长内容溢出被裁。 */
|
||||||
|
.flexShrink(1)
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.scrollBar(BarState.Off)
|
.scrollBar(BarState.Off)
|
||||||
.align(Alignment.TopStart)
|
.align(Alignment.TopStart)
|
||||||
@@ -238,13 +264,14 @@ export struct FbCalSettingsSheet {
|
|||||||
// **不要让它左右充满整个屏幕,下面也不要挨着屏幕**,
|
// **不要让它左右充满整个屏幕,下面也不要挨着屏幕**,
|
||||||
// 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」
|
// 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」
|
||||||
// ⇒ 不再贴底满宽:左右留边、四角圆角、垂直居中(由宿主的 Stack 对齐)。
|
// ⇒ 不再贴底满宽:左右留边、四角圆角、垂直居中(由宿主的 Stack 对齐)。
|
||||||
.width('92%')
|
.width('100%')
|
||||||
.constraintSize({ maxHeight: '72%' })
|
.constraintSize({ maxHeight: '72%' })
|
||||||
.padding({ left: 18, right: 18, top: 18, bottom: 20 + FB_SHEET_BOTTOM_GAP })
|
.padding({ left: 18, right: 18, top: 18, bottom: 20 + FB_SHEET_BOTTOM_GAP })
|
||||||
.backgroundColor(FbColors.surface)
|
|
||||||
.borderRadius(FbMetric.radius)
|
|
||||||
.alignItems(HorizontalAlign.Start)
|
.alignItems(HorizontalAlign.Start)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height('100%')
|
.height('100%')
|
||||||
.justifyContent(FlexAlign.Center)
|
.justifyContent(FlexAlign.Center)
|
||||||
|
|||||||
@@ -13,7 +13,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { FbColors, FbMetric } from '../common/Theme';
|
import { FbColors, FbMetric } from '../common/Theme';
|
||||||
|
import { FbModuleDef, fbModuleOf, fbThemeVars } from '../model/Modules';
|
||||||
import { FbCinemaItem } from '../data/FbMovieRepo';
|
import { FbCinemaItem } from '../data/FbMovieRepo';
|
||||||
|
import { FbSheetRing } from './FbSheetRing';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 贴底卡片的手势区缓冲(vp)。
|
* 贴底卡片的手势区缓冲(vp)。
|
||||||
@@ -33,12 +35,32 @@ export struct FbCinemaSheet {
|
|||||||
@Event onClose: () => void = (): void => {
|
@Event onClose: () => void = (): void => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卡片外圈描边环的颜色(hex,'' = 用观影模块色)。
|
||||||
|
*
|
||||||
|
* ⚠️ 必须是字符串:环色要直接当背景色(`FbColors.brand` 是 `Resource`,用不了)。
|
||||||
|
*/
|
||||||
|
@Param accent: string = '';
|
||||||
|
|
||||||
|
/** 环色:显式给了就用给的,否则取观影模块色(影院属于观影) */
|
||||||
|
private accentColor(): string {
|
||||||
|
if (this.accent !== '') {
|
||||||
|
return this.accent;
|
||||||
|
}
|
||||||
|
const m: FbModuleDef | undefined = fbModuleOf('movies');
|
||||||
|
return fbThemeVars(m !== undefined ? m.color : '#A84A28').brand;
|
||||||
|
}
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
// FB_WRAP_CENTER:把卡片**垂直居中**(用户 2026-10-16 要求:浮在屏幕中间)。
|
// FB_WRAP_CENTER:把卡片**垂直居中**(用户 2026-10-16 要求:浮在屏幕中间)。
|
||||||
// ⚠️ 居中放在**组件内部**做,而不是改宿主的 Stack 对齐 ——
|
// ⚠️ 居中放在**组件内部**做,而不是改宿主的 Stack 对齐 ——
|
||||||
// 宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。
|
// 宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。
|
||||||
// ⚠️ 外层铺满 + Center;内层仍是那张 92% 宽的圆角卡。
|
// ⚠️ 外层铺满 + Center;内层仍是那张 92% 宽的圆角卡。
|
||||||
Column() {
|
Column() {
|
||||||
|
// 🔴 2026-10-19:卡片外面加**主题色环**(用户:「所有弹框都加线」),
|
||||||
|
// 由共用组件 `FbSheetRing` 统一给;这一层只负责 92% 宽度。
|
||||||
|
Column() {
|
||||||
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accentColor() }) {
|
||||||
Column({ space: 0 }) {
|
Column({ space: 0 }) {
|
||||||
// ---- 标题 ----
|
// ---- 标题 ----
|
||||||
Row() {
|
Row() {
|
||||||
@@ -143,13 +165,14 @@ export struct FbCinemaSheet {
|
|||||||
// **不要让它左右充满整个屏幕,下面也不要挨着屏幕**,
|
// **不要让它左右充满整个屏幕,下面也不要挨着屏幕**,
|
||||||
// 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」
|
// 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」
|
||||||
// ⇒ 不再贴底满宽:左右留边、四角圆角、垂直居中(由宿主的 Stack 对齐)。
|
// ⇒ 不再贴底满宽:左右留边、四角圆角、垂直居中(由宿主的 Stack 对齐)。
|
||||||
.width('92%')
|
.width('100%')
|
||||||
.constraintSize({ maxHeight: '72%' })
|
.constraintSize({ maxHeight: '72%' })
|
||||||
.padding({ left: 18, right: 18, top: 18, bottom: 20 + FB_SHEET_BOTTOM_GAP })
|
.padding({ left: 18, right: 18, top: 18, bottom: 20 + FB_SHEET_BOTTOM_GAP })
|
||||||
.backgroundColor(FbColors.surface)
|
|
||||||
.borderRadius(FbMetric.radius)
|
|
||||||
.alignItems(HorizontalAlign.Start)
|
.alignItems(HorizontalAlign.Start)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height('100%')
|
.height('100%')
|
||||||
.justifyContent(FlexAlign.Center)
|
.justifyContent(FlexAlign.Center)
|
||||||
|
|||||||
@@ -37,11 +37,13 @@
|
|||||||
|
|
||||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||||
import { FbColors, FbMetric, FB_MASK } from '../common/Theme';
|
import { FbColors, FbMetric, FB_MASK } from '../common/Theme';
|
||||||
|
import { FbModuleDef, fbModuleOf, fbThemeVars } from '../model/Modules';
|
||||||
import { FbCalEventFull, FbCalEventInput, FbCalRepo } from '../data/FbCalRepo';
|
import { FbCalEventFull, FbCalEventInput, FbCalRepo } from '../data/FbCalRepo';
|
||||||
import { FbCalBook } from '../model/Types';
|
import { FbCalBook } from '../model/Types';
|
||||||
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
||||||
import { FB_CAL_SETTINGS } from '../data/FbCalSettings';
|
import { FB_CAL_SETTINGS } from '../data/FbCalSettings';
|
||||||
import { FbEventChip } from './FbEventChip';
|
import { FbEventChip } from './FbEventChip';
|
||||||
|
import { FbSheetRing } from './FbSheetRing';
|
||||||
|
|
||||||
const DOMAIN: number = 0x0000;
|
const DOMAIN: number = 0x0000;
|
||||||
const TAG: string = 'FbEventEdit';
|
const TAG: string = 'FbEventEdit';
|
||||||
@@ -102,6 +104,23 @@ export struct FbEventEditSheet {
|
|||||||
@Param presetDate: string = '';
|
@Param presetDate: string = '';
|
||||||
/** 可选的日历本(新建时要选落到哪本) */
|
/** 可选的日历本(新建时要选落到哪本) */
|
||||||
@Param books: FbCalBook[] = [];
|
@Param books: FbCalBook[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卡片外圈描边环的颜色(hex,'' = 用日程模块色)。
|
||||||
|
*
|
||||||
|
* ⚠️ 必须是字符串:环色要直接当背景色(`FbColors.brand` 是 `Resource`,用不了)。
|
||||||
|
*/
|
||||||
|
@Param accent: string = '';
|
||||||
|
|
||||||
|
/** 环色:显式给了就用给的,否则取日程模块色 */
|
||||||
|
private accentColor(): string {
|
||||||
|
if (this.accent !== '') {
|
||||||
|
return this.accent;
|
||||||
|
}
|
||||||
|
const m: FbModuleDef | undefined = fbModuleOf('calendar');
|
||||||
|
return fbThemeVars(m !== undefined ? m.color : '#A84A28').brand;
|
||||||
|
}
|
||||||
|
|
||||||
/** 取消 */
|
/** 取消 */
|
||||||
@Event onCancel: () => void = (): void => {
|
@Event onCancel: () => void = (): void => {
|
||||||
};
|
};
|
||||||
@@ -343,8 +362,10 @@ export struct FbEventEditSheet {
|
|||||||
build() {
|
build() {
|
||||||
// 🔴 外层 `Stack` 是**必需**的:本组件有**三个**要显示的层
|
// 🔴 外层 `Stack` 是**必需**的:本组件有**三个**要显示的层
|
||||||
// (编辑卡片 + 日期时间弹层 + 删除确认遮罩),而 build() 只能有一个根节点。
|
// (编辑卡片 + 日期时间弹层 + 删除确认遮罩),而 build() 只能有一个根节点。
|
||||||
// ⚠️ `alignContent: Bottom` 让卡片/弹层都贴底(同一形态)。
|
// 🔴 2026-10-19 用户:「所有弹框都加线,编辑也弄成弹框,保持一致。」
|
||||||
Stack({ alignContent: Alignment.Bottom }) {
|
// ⇒ 原来 `alignContent: Bottom`(贴底抽屉),现在改成**居中**,
|
||||||
|
// 与名册的编辑/详情、日程详情同一个形态(四角圆角浮卡 + 主题色环)。
|
||||||
|
Stack({ alignContent: Alignment.Center }) {
|
||||||
this.editCard()
|
this.editCard()
|
||||||
|
|
||||||
// ---- 「日期 + 时间」选择弹层(照 SyncCalendar)----
|
// ---- 「日期 + 时间」选择弹层(照 SyncCalendar)----
|
||||||
@@ -362,6 +383,10 @@ export struct FbEventEditSheet {
|
|||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
editCard() {
|
editCard() {
|
||||||
|
// ⚠️ 这一层只给**宽度**(92%);卡片自己必须 100%,
|
||||||
|
// 否则 0.92×0.92 ≈ 84.6%,比别的浮层窄一圈。
|
||||||
|
Column() {
|
||||||
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accentColor() }) {
|
||||||
Column({ space: 0 }) {
|
Column({ space: 0 }) {
|
||||||
// ---- 标题栏 ----
|
// ---- 标题栏 ----
|
||||||
Row({ space: 12 }) {
|
Row({ space: 12 }) {
|
||||||
@@ -538,11 +563,12 @@ export struct FbEventEditSheet {
|
|||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.constraintSize({ maxHeight: '86%' })
|
.constraintSize({ maxHeight: '86%' })
|
||||||
.padding({ left: 18, right: 18, top: 18, bottom: 20 + FB_SHEET_BOTTOM_GAP })
|
.padding({ left: 18, right: 18, top: 18, bottom: 20 })
|
||||||
.backgroundColor(FbColors.surface)
|
|
||||||
.borderRadius({ topLeft: FbMetric.radius, topRight: FbMetric.radius })
|
|
||||||
.alignItems(HorizontalAlign.Start)
|
.alignItems(HorizontalAlign.Start)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* **一个「日期 + 时间」控件**(照 SyncCalendar 的 `EventEditPage`)。
|
* **一个「日期 + 时间」控件**(照 SyncCalendar 的 `EventEditPage`)。
|
||||||
@@ -640,6 +666,9 @@ export struct FbEventEditSheet {
|
|||||||
*/
|
*/
|
||||||
@Builder
|
@Builder
|
||||||
dateTimePickerSheet() {
|
dateTimePickerSheet() {
|
||||||
|
// 🔴 2026-10-19:与其它弹框统一(92% 宽 + 四角圆角 + 主题色环)。
|
||||||
|
Column() {
|
||||||
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accentColor() }) {
|
||||||
Column({ space: 14 }) {
|
Column({ space: 14 }) {
|
||||||
Text(this.pickIsEnd ? '选择结束时间' : '选择开始时间')
|
Text(this.pickIsEnd ? '选择结束时间' : '选择开始时间')
|
||||||
.fontSize(FbMetric.fontTitle)
|
.fontSize(FbMetric.fontTitle)
|
||||||
@@ -693,11 +722,12 @@ export struct FbEventEditSheet {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.backgroundColor(FbColors.surface)
|
.padding({ left: 18, right: 18, top: 8, bottom: 20 })
|
||||||
.borderRadius({ topLeft: FbMetric.radius, topRight: FbMetric.radius })
|
|
||||||
.padding({ left: 18, right: 18, top: 8, bottom: 20 + FB_SHEET_BOTTOM_GAP })
|
|
||||||
.alignItems(HorizontalAlign.Start)
|
.alignItems(HorizontalAlign.Start)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
}
|
||||||
|
|
||||||
/** `YYYY-MM-DD` → `10月16日 周五`(比裸日期好读,且和时刻并排也不长) */
|
/** `YYYY-MM-DD` → `10月16日 周五`(比裸日期好读,且和时刻并排也不长) */
|
||||||
private cnDate(iso: string): string {
|
private cnDate(iso: string): string {
|
||||||
@@ -733,6 +763,9 @@ export struct FbEventEditSheet {
|
|||||||
@Builder
|
@Builder
|
||||||
deleteConfirm() {
|
deleteConfirm() {
|
||||||
Column() {
|
Column() {
|
||||||
|
// 🔴 2026-10-19:确认框也加主题色环("所有弹框都加线"不能漏这一层)
|
||||||
|
Column() {
|
||||||
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accentColor() }) {
|
||||||
Column({ space: 8 }) {
|
Column({ space: 8 }) {
|
||||||
Text('删除这条日程?')
|
Text('删除这条日程?')
|
||||||
.fontSize(FbMetric.fontBody)
|
.fontSize(FbMetric.fontBody)
|
||||||
@@ -772,10 +805,11 @@ export struct FbEventEditSheet {
|
|||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.padding(18)
|
.padding(18)
|
||||||
.backgroundColor(FbColors.surface)
|
|
||||||
.borderRadius(FbMetric.radius)
|
|
||||||
.alignItems(HorizontalAlign.Start)
|
.alignItems(HorizontalAlign.Start)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height('100%')
|
.height('100%')
|
||||||
.justifyContent(FlexAlign.Center)
|
.justifyContent(FlexAlign.Center)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { FbColors, FbMetric } from '../common/Theme';
|
|||||||
import { FbCalEventFull } from '../data/FbCalRepo';
|
import { FbCalEventFull } from '../data/FbCalRepo';
|
||||||
import { FbCalBook } from '../model/Types';
|
import { FbCalBook } from '../model/Types';
|
||||||
import { FbDot } from './Widgets';
|
import { FbDot } from './Widgets';
|
||||||
|
import { FbSheetRing } from './FbSheetRing';
|
||||||
|
|
||||||
const FB_SHEET_BOTTOM_GAP: number = 16;
|
const FB_SHEET_BOTTOM_GAP: number = 16;
|
||||||
|
|
||||||
@@ -145,6 +146,11 @@ export struct FbEventSheet {
|
|||||||
// 宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。
|
// 宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。
|
||||||
// ⚠️ 外层铺满 + Center;内层仍是那张 92% 宽的圆角卡。
|
// ⚠️ 外层铺满 + Center;内层仍是那张 92% 宽的圆角卡。
|
||||||
Column() {
|
Column() {
|
||||||
|
// 🔴 2026-10-19:卡片外面的主题色环统一由 `FbSheetRing` 给
|
||||||
|
// (用户:「所有弹框都加线,保持一致」)。环色用**这本日历的颜色**,
|
||||||
|
// 一眼能看出这条日程属于哪个日历本。
|
||||||
|
Column() {
|
||||||
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.bookColor }) {
|
||||||
Column({ space: 0 }) {
|
Column({ space: 0 }) {
|
||||||
// ---- 标题区 ----
|
// ---- 标题区 ----
|
||||||
Row({ space: 10 }) {
|
Row({ space: 10 }) {
|
||||||
@@ -214,7 +220,14 @@ export struct FbEventSheet {
|
|||||||
.width('100%')
|
.width('100%')
|
||||||
.alignItems(HorizontalAlign.Start)
|
.alignItems(HorizontalAlign.Start)
|
||||||
}
|
}
|
||||||
.layoutWeight(1)
|
/* 🔴 2026-10-19:原来是 `.layoutWeight(1)` —— 它让 Scroll **吃满剩余高度**,
|
||||||
|
于是短内容的日程也会把卡片顶到 `maxHeight`(72%),
|
||||||
|
按钮被推到最底、中间留一大片空白(真机截图:标题下面空半屏)。
|
||||||
|
⇒ 改成 `flexShrink(1)`:卡片按内容自适应高,
|
||||||
|
只有当内容真的超过 72% 时这一块才收缩并滚动。
|
||||||
|
⚠️ 不能只删掉 `layoutWeight`:`Column` 的子项默认 `flexShrink = 0`,
|
||||||
|
不收缩 ⇒ 长内容会**溢出卡片**被裁掉(反方向踩坑)。 */
|
||||||
|
.flexShrink(1)
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.scrollBar(BarState.Off)
|
.scrollBar(BarState.Off)
|
||||||
.align(Alignment.TopStart)
|
.align(Alignment.TopStart)
|
||||||
@@ -254,13 +267,14 @@ export struct FbEventSheet {
|
|||||||
// **不要让它左右充满整个屏幕,下面也不要挨着屏幕**,
|
// **不要让它左右充满整个屏幕,下面也不要挨着屏幕**,
|
||||||
// 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」
|
// 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」
|
||||||
// ⇒ 不再贴底满宽:左右留边、四角圆角、垂直居中(由宿主的 Stack 对齐)。
|
// ⇒ 不再贴底满宽:左右留边、四角圆角、垂直居中(由宿主的 Stack 对齐)。
|
||||||
.width('92%')
|
.width('100%')
|
||||||
.constraintSize({ maxHeight: '72%' })
|
.constraintSize({ maxHeight: '72%' })
|
||||||
.padding({ left: 18, right: 18, top: 18, bottom: 20 + FB_SHEET_BOTTOM_GAP })
|
.padding({ left: 18, right: 18, top: 18, bottom: 20 + FB_SHEET_BOTTOM_GAP })
|
||||||
.backgroundColor(FbColors.surface)
|
|
||||||
.borderRadius(FbMetric.radius)
|
|
||||||
.alignItems(HorizontalAlign.Start)
|
.alignItems(HorizontalAlign.Start)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height('100%')
|
.height('100%')
|
||||||
.justifyContent(FlexAlign.Center)
|
.justifyContent(FlexAlign.Center)
|
||||||
|
|||||||
@@ -19,8 +19,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { FbColors, FbMetric, FbIcon, fbTint, FbTint } from '../common/Theme';
|
import { FbColors, FbMetric, FbIcon, fbTint, FbTint } from '../common/Theme';
|
||||||
|
import { FbSheetRing } from './FbSheetRing';
|
||||||
|
|
||||||
/** 贴底卡片的手势区缓冲(vp)—— 同其它浮层 */
|
/** 贴底卡片的手势区缓冲(vp)—— 同其它浮层
|
||||||
|
* ⚠️ 2026-10-19 起本浮层改成**居中弹框**,这个常量暂时没有引用;
|
||||||
|
* 保留它(而不是删掉)是因为"贴底浮层"这个形态本工程其它地方还在用,
|
||||||
|
* 删了以后有人要改回去还得再想一遍为什么是 16。 */
|
||||||
const FB_SHEET_BOTTOM_GAP: number = 16;
|
const FB_SHEET_BOTTOM_GAP: number = 16;
|
||||||
|
|
||||||
/** 设置菜单里的一条 */
|
/** 设置菜单里的一条 */
|
||||||
@@ -52,7 +56,29 @@ export struct FbModuleSettingsSheet {
|
|||||||
@Event onPick: (action: string) => void = (a: string): void => {
|
@Event onPick: (action: string) => void = (a: string): void => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卡片外圈描边环的颜色(hex,'' = 用默认品牌色)。
|
||||||
|
*
|
||||||
|
* 🔴 本组件是**通用**的(读书/观影/任务/资源…都用它)⇒ 环色由调用方传
|
||||||
|
* "那个模块的主题色";不传时用品牌砖红(至少不是灰色,不会看着像禁用)。
|
||||||
|
*/
|
||||||
|
@Param accent: string = '';
|
||||||
|
|
||||||
|
/** 环色:显式给了就用给的,否则用品牌色 */
|
||||||
|
private accentColor(): string {
|
||||||
|
return this.accent !== '' ? this.accent : FbTint.brand;
|
||||||
|
}
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
|
/* 🔴 2026-10-19:与其它浮层统一成"**居中的圆角弹框 + 主题色环**"
|
||||||
|
(用户:「所有弹框都加线,保持一致」)。
|
||||||
|
⚠️ 原来是"贴底满宽 + 只有上面两个角圆",宿主 Stack 的对齐是 `Bottom`
|
||||||
|
⇒ 它看起来像一条"抽屉",加了环也只会露在屏幕顶部一条。
|
||||||
|
⚠️ 这是一个**铺满屏幕**的容器 ⇒ 必须给 `hitTestBehavior(Transparent)`:
|
||||||
|
否则它把整屏的点击都吃掉,"点空白关掉"就失效了。 */
|
||||||
|
Column() {
|
||||||
|
Column() {
|
||||||
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accentColor() }) {
|
||||||
Column({ space: 0 }) {
|
Column({ space: 0 }) {
|
||||||
// ---- 标题栏 ----
|
// ---- 标题栏 ----
|
||||||
Row() {
|
Row() {
|
||||||
@@ -81,18 +107,31 @@ export struct FbModuleSettingsSheet {
|
|||||||
.width('100%')
|
.width('100%')
|
||||||
.padding({ top: 10 })
|
.padding({ top: 10 })
|
||||||
}
|
}
|
||||||
.layoutWeight(1)
|
/* 🔴 2026-10-19:原来是 `Scroll().layoutWeight(1)` —— 它让滚动区**吃满剩余高度**,
|
||||||
|
于是只有 4 条菜单也会把卡片顶到 `maxHeight`(70%),
|
||||||
|
下面留一大片空白(真机截图:菜单之后空半屏)。
|
||||||
|
⇒ `flexShrink(1)`:卡片按内容自适应高,
|
||||||
|
菜单多到超过 70% 时这一块才收缩并滚动。
|
||||||
|
⚠️ 不能只删掉 `layoutWeight`:`Column` 的子项默认 `flexShrink = 0`,
|
||||||
|
不收缩 ⇒ 超长菜单会**溢出卡片**被裁掉。 */
|
||||||
|
.flexShrink(1)
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.scrollBar(BarState.Off)
|
.scrollBar(BarState.Off)
|
||||||
.align(Alignment.TopStart)
|
.align(Alignment.TopStart)
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.constraintSize({ maxHeight: '70%' })
|
.constraintSize({ maxHeight: '70%' })
|
||||||
.padding({ left: 18, right: 18, top: 18, bottom: 20 + FB_SHEET_BOTTOM_GAP })
|
.padding({ left: 18, right: 18, top: 18, bottom: 20 })
|
||||||
.backgroundColor(FbColors.surface)
|
|
||||||
.borderRadius({ topLeft: FbMetric.radius, topRight: FbMetric.radius })
|
|
||||||
.alignItems(HorizontalAlign.Start)
|
.alignItems(HorizontalAlign.Start)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
.hitTestBehavior(HitTestMode.Transparent)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一条菜单项:**图标 + 文字**(照网页版:`每条文字前都要有图标`)。
|
* 一条菜单项:**图标 + 文字**(照网页版:`每条文字前都要有图标`)。
|
||||||
|
|||||||
@@ -16,9 +16,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { FbColors, FbMetric } from '../common/Theme';
|
import { FbColors, FbMetric } from '../common/Theme';
|
||||||
|
import { FbModuleDef, fbModuleOf, fbThemeVars } from '../model/Modules';
|
||||||
import { FbMovieAttendeeRow, FbMovieDetail } from '../data/FbMovieRepo';
|
import { FbMovieAttendeeRow, FbMovieDetail } from '../data/FbMovieRepo';
|
||||||
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
||||||
import { FbAvatar, FbRating } from './Widgets';
|
import { FbAvatar, FbRating } from './Widgets';
|
||||||
|
import { FbSheetRing } from './FbSheetRing';
|
||||||
import { fbImageSrc } from '../data/FbAttachQueue';
|
import { fbImageSrc } from '../data/FbAttachQueue';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,6 +48,22 @@ export struct FbMovieDetailSheet {
|
|||||||
@Event onClose: () => void = (): void => {
|
@Event onClose: () => void = (): void => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卡片外圈描边环的颜色(hex,'' = 用观影模块色)。
|
||||||
|
*
|
||||||
|
* ⚠️ 必须是字符串:环色要直接当背景色(`FbColors.brand` 是 `Resource`,用不了)。
|
||||||
|
*/
|
||||||
|
@Param accent: string = '';
|
||||||
|
|
||||||
|
/** 环色:显式给了就用给的,否则取观影模块色 */
|
||||||
|
private accentColor(): string {
|
||||||
|
if (this.accent !== '') {
|
||||||
|
return this.accent;
|
||||||
|
}
|
||||||
|
const m: FbModuleDef | undefined = fbModuleOf('movies');
|
||||||
|
return fbThemeVars(m !== undefined ? m.color : '#A84A28').brand;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- 全屏大图查看器 ----
|
// ---- 全屏大图查看器 ----
|
||||||
/** 查看器是否打开 */
|
/** 查看器是否打开 */
|
||||||
@Local viewerOpen: boolean = false;
|
@Local viewerOpen: boolean = false;
|
||||||
@@ -127,13 +145,20 @@ export struct FbMovieDetailSheet {
|
|||||||
// **同一个 Stack 里的后声明节点**,不能放进浮层内部的 Column
|
// **同一个 Stack 里的后声明节点**,不能放进浮层内部的 Column
|
||||||
// (那会被浮层的圆角裁剪、也会被内层 Scroll 的滚动带走)。
|
// (那会被浮层的圆角裁剪、也会被内层 Scroll 的滚动带走)。
|
||||||
Stack({ alignContent: Alignment.Center }) {
|
Stack({ alignContent: Alignment.Center }) {
|
||||||
|
/* ⚠️ 2026-10-19:外面这一层只给宽度(92%)——
|
||||||
|
以前卡片是 `width('100%')`(贴屏幕两边)+ 内部 Scroll 92%,
|
||||||
|
看上去是"一整块通栏白",加环也没处可加(环会跑出屏幕)。
|
||||||
|
现在:卡片 92% + 主题色环,内部滚动区 100%(等价于原来的内容宽度)。 */
|
||||||
|
Column() {
|
||||||
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accentColor() }) {
|
||||||
Column({ space: 0 }) {
|
Column({ space: 0 }) {
|
||||||
this.sheetHeader()
|
this.sheetHeader()
|
||||||
this.sheetBody()
|
this.sheetBody()
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.backgroundColor(FbColors.surface)
|
}
|
||||||
.borderRadius(FbMetric.radius)
|
}
|
||||||
|
.width('92%')
|
||||||
// ⚠️ 同样用 `Default`(不写)。`Block` 会挡住浮层内部的子节点
|
// ⚠️ 同样用 `Default`(不写)。`Block` 会挡住浮层内部的子节点
|
||||||
// (照片缩略图、关闭按钮都点不动)。
|
// (照片缩略图、关闭按钮都点不动)。
|
||||||
// `Default` 已足够:浮层在遮罩之后声明 ⇒ 是兄弟节点、会被优先命中。
|
// `Default` 已足够:浮层在遮罩之后声明 ⇒ 是兄弟节点、会被优先命中。
|
||||||
@@ -498,7 +523,9 @@ export struct FbMovieDetailSheet {
|
|||||||
// **不要让它左右充满整个屏幕,下面也不要挨着屏幕**,
|
// **不要让它左右充满整个屏幕,下面也不要挨着屏幕**,
|
||||||
// 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」
|
// 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」
|
||||||
// ⇒ 不再贴底满宽:左右留边、四角圆角、垂直居中(由宿主的 Stack 对齐)。
|
// ⇒ 不再贴底满宽:左右留边、四角圆角、垂直居中(由宿主的 Stack 对齐)。
|
||||||
.width('92%')
|
/* ⚠️ 100%(不是 92%):卡片本身已经是 92% 宽,
|
||||||
|
这里再 92% 就变成 84.6%("卡片比容器窄一圈"的经典错)。 */
|
||||||
|
.width('100%')
|
||||||
.constraintSize({ maxHeight: '72%' })
|
.constraintSize({ maxHeight: '72%' })
|
||||||
.scrollBar(BarState.Off)
|
.scrollBar(BarState.Off)
|
||||||
.edgeEffect(EdgeEffect.Spring)
|
.edgeEffect(EdgeEffect.Spring)
|
||||||
|
|||||||
@@ -22,7 +22,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||||
import { FbColors, FbMetric, FB_HAIRLINE, FB_MASK, FB_SHADOW } from '../common/Theme';
|
import { FbColors, FbMetric, FB_MASK } from '../common/Theme';
|
||||||
|
import { FbModuleDef, fbModuleOf, fbThemeVars } from '../model/Modules';
|
||||||
|
import { FbSheetRing } from './FbSheetRing';
|
||||||
import { FbRosterPerson, FbJobRow, FbContactRow } from '../data/FbPersonRepo';
|
import { FbRosterPerson, FbJobRow, FbContactRow } from '../data/FbPersonRepo';
|
||||||
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
||||||
import { FbApiOptions, FbApiResult, fbApi, fbApiBase } from '../data/FbHttp';
|
import { FbApiOptions, FbApiResult, fbApi, fbApiBase } from '../data/FbHttp';
|
||||||
@@ -128,6 +130,23 @@ export struct FbPersonEditSheet {
|
|||||||
* 没有"必须先建标签"的限制)⇒ 下面给了"新标签"输入框 + 加号。
|
* 没有"必须先建标签"的限制)⇒ 下面给了"新标签"输入框 + 加号。
|
||||||
*/
|
*/
|
||||||
@Param knownTags: string[] = [];
|
@Param knownTags: string[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卡片外圈描边环的颜色(hex,'' = 用名册模块色)。
|
||||||
|
*
|
||||||
|
* ⚠️ 必须是字符串:环色要直接当背景色(`FbColors.brand` 是 `Resource`,用不了)。
|
||||||
|
*/
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
/** 取消 */
|
/** 取消 */
|
||||||
@Event onCancel: () => void = (): void => {
|
@Event onCancel: () => void = (): void => {
|
||||||
};
|
};
|
||||||
@@ -519,7 +538,10 @@ export struct FbPersonEditSheet {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 通用小开关(闰月 / 虚拟人物) */
|
/** 通用小开关(闰月 / 虚拟人物)
|
||||||
|
* ⚠️ 外层间距用 `margin` 而不是父容器的 `space`:这两个开关在 `Flex(wrap)` 里,
|
||||||
|
* `Flex` 没有 `space` 参数;margin 在换行后也能保持间距。
|
||||||
|
*/
|
||||||
@Builder
|
@Builder
|
||||||
toggleChip(label: string, on: boolean, onTap: () => void) {
|
toggleChip(label: string, on: boolean, onTap: () => void) {
|
||||||
Row({ space: 6 }) {
|
Row({ space: 6 }) {
|
||||||
@@ -531,6 +553,7 @@ export struct FbPersonEditSheet {
|
|||||||
.fontColor(on ? Color.White : FbColors.muted)
|
.fontColor(on ? Color.White : FbColors.muted)
|
||||||
}
|
}
|
||||||
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
|
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
|
||||||
|
.margin({ right: 8, bottom: 6 })
|
||||||
.backgroundColor(on ? '#4A7C82' : FbColors.inputBg)
|
.backgroundColor(on ? '#4A7C82' : FbColors.inputBg)
|
||||||
.borderRadius(FbMetric.radiusPill)
|
.borderRadius(FbMetric.radiusPill)
|
||||||
.onClick((): void => {
|
.onClick((): void => {
|
||||||
@@ -679,8 +702,14 @@ export struct FbPersonEditSheet {
|
|||||||
build() {
|
build() {
|
||||||
// 🔴 外层 `Stack` 是**必需**的:本组件有**两个**要显示的层
|
// 🔴 外层 `Stack` 是**必需**的:本组件有**两个**要显示的层
|
||||||
// (编辑卡片 + 删除确认遮罩),而 build() 只能有一个根节点。
|
// (编辑卡片 + 删除确认遮罩),而 build() 只能有一个根节点。
|
||||||
// ⚠️ `alignContent: Bottom` 让编辑卡片贴底(与详情卡片同一形态)。
|
//
|
||||||
Stack({ alignContent: Alignment.Bottom }) {
|
// 🔴 2026-10-19 用户:「编辑,也弄成弹框,加线,保持一致。」
|
||||||
|
// ⇒ 原来是"贴底满宽 + 只有上面两个角圆",看起来是**抽屉**而不是弹框,
|
||||||
|
// 和简要卡/详情卡并排切换时观感断层。
|
||||||
|
// 现在与它们完全同一种形态:**92% 宽 + 四角圆角 + 屏幕居中 + 主题色环**。
|
||||||
|
// ⚠️ 居中对键盘不友好?——不会:输入框获得焦点时系统会整体上推页面,
|
||||||
|
// 居中卡片反而不会被键盘压住(贴底卡才会)。真机验证过。
|
||||||
|
Stack({ alignContent: Alignment.Center }) {
|
||||||
this.editCard()
|
this.editCard()
|
||||||
|
|
||||||
// ---- 删除确认(页面内遮罩,盖在卡片之上)----
|
// ---- 删除确认(页面内遮罩,盖在卡片之上)----
|
||||||
@@ -695,6 +724,10 @@ export struct FbPersonEditSheet {
|
|||||||
/** 编辑卡片本体 */
|
/** 编辑卡片本体 */
|
||||||
@Builder
|
@Builder
|
||||||
editCard() {
|
editCard() {
|
||||||
|
// ⚠️ 外面这一层只给**宽度**(92%):卡片自己必须是 100%,
|
||||||
|
// 否则 0.92×0.92 ≈ 84.6%,会比简要卡/详情卡窄一圈。
|
||||||
|
Column() {
|
||||||
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accentColor() }) {
|
||||||
Column({ space: 0 }) {
|
Column({ space: 0 }) {
|
||||||
// ---- 标题 ----
|
// ---- 标题 ----
|
||||||
Row({ space: 12 }) {
|
Row({ space: 12 }) {
|
||||||
@@ -751,7 +784,14 @@ export struct FbPersonEditSheet {
|
|||||||
this.field('生日(农历)', this.fBirthdayLunar, '如 1985-10-05', (v: string): void => {
|
this.field('生日(农历)', this.fBirthdayLunar, '如 1985-10-05', (v: string): void => {
|
||||||
this.fBirthdayLunar = v;
|
this.fBirthdayLunar = v;
|
||||||
})
|
})
|
||||||
Row({ space: 8 }) {
|
/* 🔴 2026-10-19:原来是一个 `Row({space:8}).width('100%')` ——
|
||||||
|
两个小开关并排时,"虚拟人物(还没弄清真实身份)"这一条**放不下**,
|
||||||
|
Row 只横向排、放不下就**直接溢出**,被卡片 `clip` 裁掉半句话
|
||||||
|
(真机截图里就是"…还没弄清真实"戛然而止)。
|
||||||
|
⇒ 换成 `Flex(wrap)`:放不下就**换行**,两个都不丢。
|
||||||
|
⚠️ 别改成给文字加 `maxLines(1)` + 省略号 —— 那句话是**解释功能**的,
|
||||||
|
截成"虚拟人物(还没弄清…"等于没解释。 */
|
||||||
|
Flex({ wrap: FlexWrap.Wrap, alignItems: ItemAlign.Center }) {
|
||||||
this.toggleChip('农历闰月', this.fLunarLeap, (): void => {
|
this.toggleChip('农历闰月', this.fLunarLeap, (): void => {
|
||||||
this.fLunarLeap = !this.fLunarLeap;
|
this.fLunarLeap = !this.fLunarLeap;
|
||||||
})
|
})
|
||||||
@@ -973,12 +1013,18 @@ export struct FbPersonEditSheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
|
/* ⚠️ 高度上限写在**卡片自己**身上(不是环层):卡片里有
|
||||||
|
`Scroll().layoutWeight(1)`,它要靠父容器的确定高度算"还能滚多少"。
|
||||||
|
挪到外面 ⇒ 内层高度变成内容驱动 ⇒ 滚动区塌成一条线。 */
|
||||||
.constraintSize({ maxHeight: '82%' })
|
.constraintSize({ maxHeight: '82%' })
|
||||||
.padding({ left: 18, right: 18, top: 18, bottom: 20 + FB_SHEET_BOTTOM_GAP })
|
/* 居中形态**不再需要**底部手势区缓冲(FB_SHEET_BOTTOM_GAP):
|
||||||
.backgroundColor(FbColors.surface)
|
卡片不贴屏幕底,手势区碰不到它。 */
|
||||||
.borderRadius({ topLeft: FbMetric.radius, topRight: FbMetric.radius })
|
.padding({ left: 18, right: 18, top: 18, bottom: 20 })
|
||||||
.alignItems(HorizontalAlign.Start)
|
.alignItems(HorizontalAlign.Start)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.width('92%')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除确认浮层。
|
* 删除确认浮层。
|
||||||
@@ -991,6 +1037,11 @@ export struct FbPersonEditSheet {
|
|||||||
@Builder
|
@Builder
|
||||||
deleteConfirm() {
|
deleteConfirm() {
|
||||||
Column() {
|
Column() {
|
||||||
|
/* 🔴 2026-10-19:确认框同样加**主题色环** ——
|
||||||
|
用户要的是"所有弹框都加线",这一层虽然小,但它也是弹框;
|
||||||
|
漏掉它就又出现"有的有线、有的没线"的不一致。 */
|
||||||
|
Column() {
|
||||||
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accentColor() }) {
|
||||||
Column({ space: 8 }) {
|
Column({ space: 8 }) {
|
||||||
Text('删除「' + (this.person !== null ? this.person.shown : '') + '」?')
|
Text('删除「' + (this.person !== null ? this.person.shown : '') + '」?')
|
||||||
.fontSize(FbMetric.fontBody)
|
.fontSize(FbMetric.fontBody)
|
||||||
@@ -1031,15 +1082,11 @@ export struct FbPersonEditSheet {
|
|||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.padding(18)
|
.padding(18)
|
||||||
.backgroundColor(FbColors.surface)
|
|
||||||
.borderRadius(FbMetric.radius)
|
|
||||||
/* 🔴 v59c:与 `FbPersonSheet` / `FbPersonQuickSheet` 同一套"三层分离"
|
|
||||||
(用户 2026-10-18:「白色和下面列表的白色融合在一起,看不出是弹框」)。
|
|
||||||
⚠️ 三个浮层必须**同手段**,否则从简要卡 → 编辑切过去会突然"变平"。 */
|
|
||||||
.shadow({ radius: 24, color: FB_SHADOW, offsetX: 0, offsetY: 6 })
|
|
||||||
.border({ width: 1, color: FB_HAIRLINE })
|
|
||||||
.alignItems(HorizontalAlign.Start)
|
.alignItems(HorizontalAlign.Start)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height('100%')
|
.height('100%')
|
||||||
// 遮罩盖满整屏(点空白处关掉 = 取消)
|
// 遮罩盖满整屏(点空白处关掉 = 取消)
|
||||||
|
|||||||
@@ -36,24 +36,14 @@
|
|||||||
* 这些都是**页面**的职责。浮层之间也不互相引用(免得成环)。
|
* 这些都是**页面**的职责。浮层之间也不互相引用(免得成环)。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FbColors, FbMetric, FB_SHADOW, fbTint, FbTint, fbAlpha } from '../common/Theme';
|
import { FbColors, FbMetric, fbTint, FbTint, fbAlpha } from '../common/Theme';
|
||||||
import { FbModuleDef, fbModuleOf, fbThemeVars } from '../model/Modules';
|
import { FbModuleDef, fbModuleOf, fbThemeVars } from '../model/Modules';
|
||||||
import { FbRosterPerson } from '../data/FbPersonRepo';
|
import { FbRosterPerson } from '../data/FbPersonRepo';
|
||||||
import { FbAvatar, FbTag } from './Widgets';
|
import { FbAvatar, FbTag } from './Widgets';
|
||||||
|
import { FbSheetRing } from './FbSheetRing';
|
||||||
import { fbMediaUrl } from '../data/FbHttp';
|
import { fbMediaUrl } from '../data/FbHttp';
|
||||||
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
||||||
|
|
||||||
/** 卡片阴影半径(与详情浮层同一档观感) */
|
|
||||||
const CARD_SHADOW: number = 24;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主题色描边环的宽度(vp)。
|
|
||||||
*
|
|
||||||
* ⚠️ 2vp 是"看得见但不喧哗"的那一档:1vp 在深色遮罩上仍显得细弱,
|
|
||||||
* 3vp 以上就变成"彩色相框",抢了内容和按钮的注意力。
|
|
||||||
*/
|
|
||||||
const RING_W: number = 2;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 信息行之间的间距(vp)。
|
* 信息行之间的间距(vp)。
|
||||||
*
|
*
|
||||||
@@ -300,8 +290,11 @@ export struct FbPersonQuickSheet {
|
|||||||
build() {
|
build() {
|
||||||
Column() {
|
Column() {
|
||||||
/* 🔴 ② 主题色描边环(用户 2026-10-18 点名要的那一圈)
|
/* 🔴 ② 主题色描边环(用户 2026-10-18 点名要的那一圈)
|
||||||
⚠️ 它是"外层铺色 + 内边距",不是 `border` —— 见文件头说明。 */
|
⚠️ 2026-10-19 起改成**共用组件** `FbSheetRing`:
|
||||||
|
用户要求"所有弹框都加线、保持一致" ⇒ 环宽/圆角/阴影只能有一处定义。
|
||||||
|
原来这里是手写的"外层铺色 + padding",两者外观一样,但两份实现必然漂移。 */
|
||||||
Column() {
|
Column() {
|
||||||
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.brand() }) {
|
||||||
Column({ space: 14 }) {
|
Column({ space: 14 }) {
|
||||||
// ---- 头像 + 姓名 + 称谓 ----
|
// ---- 头像 + 姓名 + 称谓 ----
|
||||||
Row({ space: 14 }) {
|
Row({ space: 14 }) {
|
||||||
@@ -391,24 +384,18 @@ export struct FbPersonQuickSheet {
|
|||||||
/* 🔴 卡片本体:见文件头「三层分离」的说明。
|
/* 🔴 卡片本体:见文件头「三层分离」的说明。
|
||||||
⚠️ 宽度 **100%**(不是 92%):92% 由**外面那圈环**负责,
|
⚠️ 宽度 **100%**(不是 92%):92% 由**外面那圈环**负责,
|
||||||
这里再写 92% 就成了 0.92×0.92 ≈ 84.6%,卡片会比详情卡窄一圈。
|
这里再写 92% 就成了 0.92×0.92 ≈ 84.6%,卡片会比详情卡窄一圈。
|
||||||
⚠️ 四角圆角 + 居中 —— 与 `FbPersonSheet` 同一套观感
|
⚠️ 圆角与阴影**不在这里**:它们和环是同一套几何,统一由 `FbSheetRing` 给
|
||||||
(用户 2026-10-16 定:"圆角浮窗式、不占满宽、浮在屏幕中间")。 */
|
(两份写必然漂移,这正是用户说的"不一致"的来源)。 */
|
||||||
.width('100%')
|
.width('100%')
|
||||||
|
/* 高度上限写在**卡片自己**身上(不是外面的环层)——
|
||||||
|
环只负责视觉,尺寸归卡片;这条口径全应用统一,
|
||||||
|
免得"有的浮层限在外层、有的限在内层"以后又漂移。 */
|
||||||
.constraintSize({ maxHeight: '72%' })
|
.constraintSize({ maxHeight: '72%' })
|
||||||
.padding({ left: 18, right: 18, top: 18, bottom: 18 })
|
.padding({ left: 18, right: 18, top: 18, bottom: 18 })
|
||||||
.backgroundColor(FbColors.surface)
|
|
||||||
/* ⚠️ 内层圆角要**比外层小一档**(14 vs 16):两圈同心圆角差值
|
|
||||||
= 环宽 2,否则环在内侧会显厚、外侧会显薄(看着像描边歪了)。 */
|
|
||||||
.borderRadius(FbMetric.radius)
|
|
||||||
/* ③ 阴影:把卡片从"被压暗的列表"上抬起来 */
|
|
||||||
.shadow({ radius: CARD_SHADOW, color: FB_SHADOW, offsetX: 0, offsetY: 6 })
|
|
||||||
.alignItems(HorizontalAlign.Start)
|
.alignItems(HorizontalAlign.Start)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.width('92%')
|
.width('92%')
|
||||||
.padding(RING_W)
|
|
||||||
.backgroundColor(this.brand())
|
|
||||||
.borderRadius(FbMetric.radius + RING_W)
|
|
||||||
.alignItems(HorizontalAlign.Center)
|
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height('100%')
|
.height('100%')
|
||||||
|
|||||||
@@ -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 { FbRosterPerson, FbJobRow, FbContactRow } from '../data/FbPersonRepo';
|
||||||
import { FbAvatar, FbTag } from './Widgets';
|
import { FbAvatar, FbTag } from './Widgets';
|
||||||
|
import { FbSheetRing } from './FbSheetRing';
|
||||||
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
||||||
import { fbImageSrc } from '../data/FbAttachQueue';
|
import { fbImageSrc } from '../data/FbAttachQueue';
|
||||||
|
|
||||||
@@ -114,6 +116,26 @@ export struct FbPersonSheet {
|
|||||||
*/
|
*/
|
||||||
@Param embedded: boolean = false;
|
@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 {
|
private iconOf(name: string): Resource {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
@@ -343,6 +365,18 @@ export struct FbPersonSheet {
|
|||||||
// 宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。
|
// 宿主 Stack 里还有别的子元素(日程页的悬浮 +),改对齐会连带挪走它们。
|
||||||
// ⚠️ 外层铺满 + Center;内层仍是那张 92% 宽的圆角卡。
|
// ⚠️ 外层铺满 + Center;内层仍是那张 92% 宽的圆角卡。
|
||||||
Column() {
|
Column() {
|
||||||
|
// ⚠️ 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 }) {
|
Column({ space: 0 }) {
|
||||||
// ---- 顶部:头像 + 姓名 + 称谓 ----
|
// ---- 顶部:头像 + 姓名 + 称谓 ----
|
||||||
// ⚠️ 这一块**不参与滚动**(它跟着卡片固定在顶部):
|
// ⚠️ 这一块**不参与滚动**(它跟着卡片固定在顶部):
|
||||||
@@ -558,10 +592,17 @@ export struct FbPersonSheet {
|
|||||||
// 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」
|
// 而是放在**屏幕中间**,采用**圆角浮窗**式的形式。」
|
||||||
// ⇒ 不再贴底满宽:左右留边(92%)、四角圆角、垂直居中(由宿主的 Stack 对齐)。
|
// ⇒ 不再贴底满宽:左右留边(92%)、四角圆角、垂直居中(由宿主的 Stack 对齐)。
|
||||||
//
|
//
|
||||||
|
// ⚠️ 宽度由**这一层**给(不是卡片自己):卡片在环里必须是 100%,
|
||||||
|
// 否则 0.92×0.92 ≈ 84.6%,会比别的浮层窄一圈。
|
||||||
// ⚠️ `embedded`(宿主自己就是 sheet)时宽度交给 sheet 决定 ——
|
// ⚠️ `embedded`(宿主自己就是 sheet)时宽度交给 sheet 决定 ——
|
||||||
// 再用 92% 会在 sheet 里又缩一次,看上去"卡片比容器窄一圈"。
|
// 再用 92% 会在 sheet 里又缩一次,看上去"卡片比容器窄一圈"。
|
||||||
.width(this.embedded ? 'auto' : '92%')
|
.width('100%')
|
||||||
// 最多占屏幕 72%:上方留出可见列表,暗示"这是浮层、下面还有内容"
|
/* 最多占屏幕 72%:上方留出可见列表,暗示"这是浮层、下面还有内容"。
|
||||||
|
⚠️ 这个限制**必须写在卡片自己身上**(不能交给外面的环层):
|
||||||
|
卡片内部有 `Scroll().layoutWeight(1)`,它要靠**父容器的确定高度**
|
||||||
|
才能算出"剩下多少可以滚"。把 maxHeight 挪到外面那层,
|
||||||
|
内层高度就变成"由内容决定"⇒ `layoutWeight` 拿到 0 ⇒
|
||||||
|
**资料区直接塌成一条线**(编辑卡同理,别一起挪)。 */
|
||||||
.constraintSize({ maxHeight: this.embedded ? '100%' : '72%' })
|
.constraintSize({ maxHeight: this.embedded ? '100%' : '72%' })
|
||||||
// ⚠️ embedded 时**去掉底部手势区那段留白**:那 16 是给
|
// ⚠️ embedded 时**去掉底部手势区那段留白**:那 16 是给
|
||||||
// "贴底遮罩 + 隐藏手势条"的场景补的(见 FB_SHEET_BOTTOM_GAP),
|
// "贴底遮罩 + 隐藏手势条"的场景补的(见 FB_SHEET_BOTTOM_GAP),
|
||||||
@@ -570,23 +611,11 @@ export struct FbPersonSheet {
|
|||||||
left: 18, right: 18, top: 18,
|
left: 18, right: 18, top: 18,
|
||||||
bottom: this.embedded ? 20 : 20 + FB_SHEET_BOTTOM_GAP
|
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)
|
.alignItems(HorizontalAlign.Start)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.width(this.embedded ? 'auto' : '92%')
|
||||||
|
}
|
||||||
/* 🔴 embedded(宿主自己就是 sheet)时**不能再铺满 + 居中**:
|
/* 🔴 embedded(宿主自己就是 sheet)时**不能再铺满 + 居中**:
|
||||||
内容是"按自适应高度"交给 sheet 的,铺 100% 高会与
|
内容是"按自适应高度"交给 sheet 的,铺 100% 高会与
|
||||||
`SheetSize.FIT_CONTENT` 互相打架 ⇒ 浮层起来立刻被关(真机实测)。 */
|
`SheetSize.FIT_CONTENT` 互相打架 ⇒ 浮层起来立刻被关(真机实测)。 */
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* 家书 FamilyBook · 浮层卡片的「主题色描边环」统一容器(鸿蒙端)
|
||||||
|
*
|
||||||
|
* 🔴 用户 2026-10-19 原话:
|
||||||
|
* 「既然弹框加了线,那就**所有弹框都加线**,编辑,也弄成弹框,加线,保持一致。」
|
||||||
|
*
|
||||||
|
* ⇒ 以前只有「简要卡」自己手写了一层环(外层 `Column` 铺色 + `padding` + 内层白卡)。
|
||||||
|
* 手写一次没问题,**手写十次必然漂移**(环宽/圆角/阴影/边距一处一个样)——
|
||||||
|
* 所以抽成这个组件:**所有浮层都套它**,环宽和圆角只在这里定义。
|
||||||
|
*
|
||||||
|
* ⚠️ 为什么是"外层铺色 + 内边距",而不是给白卡加 `border`:
|
||||||
|
* `border` 画在**盒子内侧**,会把卡片内容区撑窄 2vp,且圆角处顺着**内圆角**走,
|
||||||
|
* 看着像"白卡描了彩边";用户要的是"**外面**加一圈" ⇒ 必须垫一层。
|
||||||
|
* (`FbPersonQuickSheet` 第一版就踩过,注释里留着证据。)
|
||||||
|
*
|
||||||
|
* ⚠️ `clip(true)` 不是装饰:卡片内部有 `Scroll`(详情/编辑都很长),
|
||||||
|
* 不裁的话滚动内容会**越过圆角**画到环上,看着像"内容从卡片里漏出来"。
|
||||||
|
* 如果调用方内部还要往外"溢出显示"(如全屏图片查看器),传 `clipContent: false`
|
||||||
|
* —— 并且查看器必须放在**这个组件之外**的兄弟层(现在 `FbMovieDetailSheet` 就是这么做的)。
|
||||||
|
*
|
||||||
|
* ⚠️ 本组件只管"外观"(宽度由调用方给、内容由尾随闭包给):
|
||||||
|
* 它**不设** maxHeight / padding / 背景层次 —— 那些属于各卡片自己的版式。
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FbColors, FbMetric, FB_SHADOW, FbTint } from '../common/Theme';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 环的宽度(vp)—— **全应用唯一一处定义**。
|
||||||
|
*
|
||||||
|
* ⚠️ 2vp 是"看得见但不喧哗"的那一档:1vp 在深色遮罩上仍显得细弱(真机上像一条发丝),
|
||||||
|
* 3vp 以上就变成"彩色相框",抢了内容和按钮的注意力。
|
||||||
|
* ⚠️ 内层圆角 = `radius`,外层圆角 = `radius + 环宽`:
|
||||||
|
* 两圈同心圆角的差值必须正好等于环宽,否则环在内侧显厚、外侧显薄(看着像描边歪了)。
|
||||||
|
*/
|
||||||
|
export const FB_RING_W: number = 2;
|
||||||
|
|
||||||
|
@ComponentV2
|
||||||
|
export struct FbSheetRing {
|
||||||
|
/** 卡片(内层)圆角;环层自动 +FB_RING_W */
|
||||||
|
@Param radius: number = FbMetric.radius;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 环的颜色 —— 各模块的主题色(砖红 / 青 / 靛蓝…)。
|
||||||
|
*
|
||||||
|
* ⚠️ 必须是 **hex 字符串**(不能用 `Resource`):这里要直接当背景色用,
|
||||||
|
* 而默认值在 ArkTS 里也必须是常量表达式。
|
||||||
|
*/
|
||||||
|
@Param ringColor: string = FbTint.brand;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阴影半径(vp)。0 = **不画阴影**。
|
||||||
|
*
|
||||||
|
* ⚠️ 嵌在别的卡片里时(顶栏「我的资料」塞进 bindSheet)要传 0:
|
||||||
|
* 宿主已经有自己的容器与投影,再叠一层会显得脏。
|
||||||
|
*/
|
||||||
|
@Param shadowRadius: number = 24;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否裁剪内容到圆角。
|
||||||
|
*
|
||||||
|
* 默认 `true`;只有在卡片内部还有"要溢出显示"的层时传 `false`
|
||||||
|
* (见文件头 `clip` 的说明)。
|
||||||
|
*/
|
||||||
|
@Param clipContent: boolean = true;
|
||||||
|
|
||||||
|
/** 卡片本体(尾随闭包传入) */
|
||||||
|
@BuilderParam content: () => void;
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
Column() {
|
||||||
|
this.content()
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.backgroundColor(FbColors.surface)
|
||||||
|
.borderRadius(this.radius)
|
||||||
|
.clip(this.clipContent)
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.padding(FB_RING_W)
|
||||||
|
.backgroundColor(this.ringColor)
|
||||||
|
.borderRadius(this.radius + FB_RING_W)
|
||||||
|
.alignItems(HorizontalAlign.Center)
|
||||||
|
.shadow({ radius: this.shadowRadius, color: FB_SHADOW, offsetX: 0, offsetY: 6 })
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user