fix(地点): 候选改为 bindPopup 浮层,根治输入框跳动与选完复现

旧方案把候选做成内联列表,虽放在输入框上方,但:
  1. 候选高度变化(搜索中 / 出结果 / 条数变化)会改变表单高度,输入框随之反复移动,
     再靠 scrollBy 事后纠正,表现为输入框来回跳好几次;
  2. 选中候选后 applyPlace 程序回写 text 会触发 TextInput.onChange,
     把 placePicked 重置为 false 并重新发起搜索,导致候选框消失后又冒出来一次。

改为:
- 候选用 .bindPopup($$placeOpen, { placement: Top }) 挂在输入框上,
  不参与布局,出现与消失完全不移动输入框;
- 移除全部 scrollBy 对齐逻辑(keyboardPadVp / ensurePlaceVisible / 键盘监听),
  键盘避让交由 KeyboardAvoidMode.RESIZE 的默认光标可见行为;
- 用 lastAppliedText 精确识别「程序回写 text 造成的 onChange」,不再发搜索;
- runSuggest 期间浮层持续展开(搜索中 → 结果),避免收起再弹造成闪烁;
- 抽出 closePlacePopup() 作为统一收起入口。
This commit is contained in:
2026-10-21 09:03:31 +08:00
parent 6ab7bcfb76
commit a32c049199
+142 -210
View File
@@ -1,6 +1,6 @@
// entry/src/main/ets/pages/EventEditPage.ets // entry/src/main/ets/pages/EventEditPage.ets
// 日程编辑页:新建 / 修改 / 删除日程(一律属于某个 CalDAV 日历本,不再支持"本机"日程) // 日程编辑页:新建 / 修改 / 删除日程(一律属于某个 CalDAV 日历本,不再支持"本机"日程)
import { curves, router, KeyboardAvoidMode, ComponentUtils, window } from '@kit.ArkUI'; import { curves, router, KeyboardAvoidMode } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit'; import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit'; import { BusinessError } from '@kit.BasicServicesKit';
import { notificationManager } from '@kit.NotificationKit'; import { notificationManager } from '@kit.NotificationKit';
@@ -37,11 +37,13 @@ struct EventEditPage {
@State location: string = ''; @State location: string = '';
/** /**
* ⭐ 地点补全(鸿蒙 Map Kit / Site Kit): * ⭐ 地点补全(鸿蒙 Map Kit / Site Kit):
* placeItems —— 输入框下方的候选列表(内联展开,**不新增 bindSheet**: * placeItems —— 候选数据(**不参与页面布局**,由 bindPopup 渲染成浮层)
* 本页只能挂一个弹层,见文件末尾注释) * placeLoading —— 是否正在请求,浮层内显示"搜索中"
* placeLoading —— 是否正在请求,用于显示"搜索中" * placeOpen —— 浮层是否弹出(绑在输入框上,向上弹)
* placeOpen —— 候选列表是否展开 * placeHint —— 无结果/地图能力不可用时的提示(不阻塞手输地点)
* placeHint —— 地图能力不可用时的提示(不阻塞用户手输地点) *
* ⚠️ 为什么必须是浮层而不是内联列表:内联列表改变表单高度 ⇒ 输入框跟着移动 ⇒
* 每次"搜索中/出结果/结果变少"都要滚动纠正 ⇒ 用户看到输入框反复上下跳。
*/ */
@State placeItems: PlaceItem[] = []; @State placeItems: PlaceItem[] = [];
@State placeLoading: boolean = false; @State placeLoading: boolean = false;
@@ -50,27 +52,17 @@ struct EventEditPage {
/** 用户是否已从候选里选中(选中后不再自动弹候选,避免"选完又弹出") */ /** 用户是否已从候选里选中(选中后不再自动弹候选,避免"选完又弹出") */
private placePicked: boolean = false; private placePicked: boolean = false;
/** /**
* 地点输入框当前是否聚焦。 * 最后一次**程序写入**输入框的文本(选中候选后回写的地址)。
* 用途:候选条数变化会改变输入框上方的内容高度 → 输入框在屏幕上会挪位。 * TextInput 的 text 被程序改写同样会触发 onChange;用它精确识别"这次 onChange
* 只要还在聚焦状态(用户正在打字),就每次重新钉一次,保证它始终贴住键盘上沿。 * 不是用户打字造成的",从而不重复发起搜索。
*/ */
private lastAppliedText: string = '';
/** 地点输入框当前是否聚焦(仅用于日志/状态,不再驱动滚动) */
private placeFocused: boolean = false; private placeFocused: boolean = false;
/** 输入防抖定时器 */ /** 输入防抖定时器 */
private placeTimer: number = -1; private placeTimer: number = -1;
/** /** 表单滚动控制器(键盘避让由系统 RESIZE 模式负责,这里不再主动滚) */
* ⭐ 表单滚动控制器。
* 地点是表单第 7 个字段(标题/全天/时间/重复/提醒/日历本之后),
* 键盘弹起后输入框本身已经在屏幕下半部,候选列表更是被键盘压住。
* ArkUI 默认 OFFSET 模式只保证"光标可见",不会为候选列表留空间,
* 所以聚焦后要**主动滚动**,把输入框钉到可视区底边(= 键盘上沿)。
*/
private formScroller: Scroller = new Scroller(); private formScroller: Scroller = new Scroller();
/** 键盘高度(px),0 = 键盘未弹起。由 window 的 TYPE_KEYBOARD 避让区取得 */
private keyboardHeight: number = 0;
/** 键盘避让区变化监听器(页面销毁时注销) */
private keyboardWatcher: ((opt: window.AvoidAreaOptions) => void) | undefined = undefined;
/** 本页所属窗口(异步取得,拿到后才能挂监听) */
private hostWindow: window.Window | undefined = undefined;
@State description: string = ''; @State description: string = '';
@State allDay: boolean = false; @State allDay: boolean = false;
@State startMs: number = 0; @State startMs: number = 0;
@@ -129,63 +121,29 @@ struct EventEditPage {
return this.initPage(); return this.initPage();
} }
/** 页面销毁:清掉地点补全的防抖定时器,并注销键盘监听 */ /** 页面销毁:清掉地点补全的防抖定时器 */
aboutToDisappear(): void { aboutToDisappear(): void {
this.cancelSuggestTimer(); this.cancelSuggestTimer();
this.releaseKeyboardWatch();
} }
/** /**
* ⭐ 键盘避让:改成 RESIZE 模式。 * ⭐ 键盘避让:改成 RESIZE 模式。
* 默认 OFFSET 是「整页上移」,页面上移后表单底部(地点/备注/按钮)会被顶出屏幕, * 默认 OFFSET 是「整页上移」,页面上移后表单底部(地点/备注/按钮)会被顶出屏幕。
* 且候选列表展开时不会重新计算可见区 → 列表被键盘压住。
* RESIZE 是「把页面高度压缩到键盘上沿」,滚动区域随之变矮, * RESIZE 是「把页面高度压缩到键盘上沿」,滚动区域随之变矮,
* 此时 Scroll 的矩形底边就是真正的键盘上沿,据此把输入框钉住即可。 * 聚焦的输入框由系统自动滚入可见区,**无需再手动 scrollBy**。
*
* ⚠️ 曾经写过"手动量矩形 + scrollBy 对齐"的方案,是错的方向:
* ① getRectangleById 返回 px、scrollBy 收 vp,单位混用会把输入框推飞;
* ② 更根本的是——只要候选参与布局,位置就会反复变化,
* 靠"事后纠正"必然表现为抖动。所以候选改成了浮层(见 placePopup)。
*/ */
private async setupKeyboardAvoid(): Promise<void> { private setupKeyboardAvoid(): void {
try { try {
this.getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE); this.getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE);
} catch (err) { } catch (err) {
const e = err as BusinessError; const e = err as BusinessError;
LogUtil.write(`设置键盘避让模式失败: ${e.message}`); LogUtil.write(`设置键盘避让模式失败: ${e.message}`);
} }
// 记录键盘高度:Scroll 矩形取不到时用它兜底算可视底边
try {
const c = this.getUIContext().getHostContext();
if (c === undefined) {
return;
}
// ⚠️ UIAbilityContext 上没有 window 属性,必须用 window.getLastWindow 取
const win: window.Window = await window.getLastWindow(c);
this.hostWindow = win;
this.keyboardWatcher = (opt: window.AvoidAreaOptions): void => {
if (opt.type !== window.AvoidAreaType.TYPE_KEYBOARD) {
return;
}
this.keyboardHeight = opt.area.visible ? opt.area.bottomRect.height : 0;
// 键盘弹起/收起后重新对齐一次(收起时不滚,保持用户当前浏览位置)
if (this.keyboardHeight > 0) {
this.ensurePlaceVisible();
}
};
win.on('avoidAreaChange', this.keyboardWatcher);
} catch (err) {
const e = err as BusinessError;
LogUtil.write(`监听键盘高度失败: ${e.message}`);
}
}
private releaseKeyboardWatch(): void {
if (this.keyboardWatcher === undefined || this.hostWindow === undefined) {
return;
}
try {
this.hostWindow.off('avoidAreaChange', this.keyboardWatcher);
} catch (err) {
// 页面已在销毁,注销失败无所谓
}
this.keyboardWatcher = undefined;
this.hostWindow = undefined;
} }
private async initPage(): Promise<void> { private async initPage(): Promise<void> {
@@ -324,14 +282,7 @@ struct EventEditPage {
this.cancelSuggestTimer(); this.cancelSuggestTimer();
const q: string = v.trim(); const q: string = v.trim();
if (q.length < 1) { if (q.length < 1) {
this.placeItems = []; this.closePlacePopup();
this.placeOpen = false;
this.placeLoading = false;
this.placeHint = '';
// 候选清空后上方内容变矮,输入框会往下掉 —— 重新钉一次
if (this.placeFocused) {
this.ensurePlaceVisible();
}
return; return;
} }
this.placeTimer = setTimeout(() => { this.placeTimer = setTimeout(() => {
@@ -354,18 +305,15 @@ struct EventEditPage {
if (PlaceSuggest.isUnavailable()) { if (PlaceSuggest.isUnavailable()) {
// 地图服务没开通:给一次明确提示,然后永久静默(不阻塞手输) // 地图服务没开通:给一次明确提示,然后永久静默(不阻塞手输)
this.placeHint = '地图服务不可用,可直接手动输入地点'; this.placeHint = '地图服务不可用,可直接手动输入地点';
this.placeOpen = false; this.placeItems = [];
this.placeOpen = true;
return; return;
} }
// 用户输入时若带空格,PlaceSuggest 内部会 trim // ⭐ 浮层一直保持展开(从"搜索中"过渡到结果),中间不收起、不重弹 ——
// ⭐ 先把旧候选收起再显示"搜索中":候选区高度变化会让输入框上下跳, // 收起再弹会让浮层闪一下,看起来像"消失了又出现"。
// 收起后由 ensurePlaceVisible() 统一重新钉住。
this.placeOpen = false;
this.placeLoading = true; this.placeLoading = true;
this.placeHint = ''; this.placeHint = '';
if (this.placeFocused) { this.placeOpen = true;
this.ensurePlaceVisible();
}
try { try {
const c = this.getUIContext().getHostContext(); const c = this.getUIContext().getHostContext();
const ctx: common.Context | undefined = c; const ctx: common.Context | undefined = c;
@@ -379,94 +327,115 @@ struct EventEditPage {
return; return;
} }
this.placeItems = items.slice(0, 6); this.placeItems = items.slice(0, 6);
this.placeOpen = this.placeItems.length > 0;
// ⭐ 候选条数变化改变了输入框上方的高度 → 重新钉住输入框
if (this.placeFocused || this.placeOpen) {
this.ensurePlaceVisible();
}
if (this.placeItems.length === 0) { if (this.placeItems.length === 0) {
this.placeHint = PlaceSuggest.isUnavailable() this.placeHint = PlaceSuggest.isUnavailable()
? '地图服务不可用,可直接手动输入地点' ? '地图服务不可用,可直接手动输入地点'
: '没有找到匹配地点,可直接手动输入'; : '没有找到匹配地点,可直接手动输入';
// 无结果也留在浮层里给提示;用户再输入会被下一次请求覆盖
this.placeOpen = true;
} }
} catch (err) { } catch (err) {
const e = err as BusinessError; const e = err as BusinessError;
LogUtil.write(`地点补全异常: ${e.message}`); LogUtil.write(`地点补全异常: ${e.message}`);
this.placeItems = [];
this.placeHint = '地点搜索失败,可直接手动输入'; this.placeHint = '地点搜索失败,可直接手动输入';
this.placeOpen = false; this.placeOpen = true;
} finally { } finally {
this.placeLoading = false; this.placeLoading = false;
} }
} }
/** /**
* ⭐ 把「地点输入框」钉在键盘正上方:滚到「输入框底边 = 可视区底边 − 10vp」, * ⭐ 候选浮层(bindPopup)的内容。
* 候选列表在它**上方**向上生长,所以输入框不会被候选顶走,用户可以边看边继续打字。
* *
* 两个曾经踩过的坑,这里都规避掉: * 为什么是浮层而不是内联列表:
* ① **不要重复扣键盘高度**。本页已设 KeyboardAvoidMode.RESIZE —— 窗口本身 * 内联列表会改变表单高度 ⇒ 输入框位置随之变化 ⇒ 每次"搜索中/出结果/结果变少"
* 已经压缩到键盘上沿之外,`Scroll` 的矩形底边就是真正可视底边。 * 都要靠滚动去纠正 ⇒ 用户看到的就是**输入框上下跳好几次**。
* 再减一次 keyboardHeight 会滚过头(输入框被推飞出屏幕)。 * 浮层不参与布局,出现与消失**完全不移动输入框**,这是唯一能根治该问题的结构。
* ② **单位必须换算**。`getRectangleById()` 返回 **px**,而 `Scroller.scrollBy()` *
* 收 **vp**。直接把 px 当 vp 传,在高密度屏上等于多滚了 3 倍 —— * 为什么向上弹(Placement.Top):
* 这就是上一版"输入框突然跑到上面去、看不见了"的真正原因。 * 输入框在表单下部、键盘又在下方,向下弹必然被键盘盖住;
* 向上弹则整块候选都落在输入框与标题之间的空白区。
*/ */
private ensurePlaceVisible(): void { @Builder
// 双延时:等键盘收缩动画 + 候选列表重新布局都稳定后再量尺寸 placePopup() {
setTimeout(() => { Column({ space: 0 }) {
setTimeout(() => { if (this.placeLoading) {
try { Row({ space: 8 }) {
const ui = this.getUIContext(); LoadingProgress().width(16).height(16).color($r('app.color.text_hint'))
// ⚠️ getRectangleById 在 ComponentUtils 上,不在 UIContext 上 Text('搜索中…')
const cu: ComponentUtils = ui.getComponentUtils(); .fontSize(13)
const vp2px: number = ui.vp2px(1); .fontColor($r('app.color.text_hint'))
let rowBottomPx: number = 0;
let visibleBottomPx: number = 0;
try {
const row = cu.getRectangleById('placeRow');
rowBottomPx = row.windowOffset.y + row.size.height;
} catch (e) {
return; // 组件还没上树,放弃本次校正
} }
// 首选:Scroll 的真实矩形(RESIZE 模式下它已经被裁到键盘上沿) .width('100%')
try { .padding({ left: 14, right: 14, top: 12, bottom: 12 })
const sc = cu.getRectangleById('formScroll'); } else if (this.placeItems.length > 0) {
visibleBottomPx = sc.windowOffset.y + sc.size.height; // 限高 + 内部滚动:候选最多 6 条,一次全铺开会顶到屏幕顶部
} catch (e2) { List() {
visibleBottomPx = 0; ForEach(this.placeItems, (it: PlaceItem, idx: number) => {
ListItem() {
Column({ space: 2 }) {
Text(it.title)
.fontSize(14)
.fontColor($r('app.color.text_primary'))
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
.textAlign(TextAlign.Start)
if (it.subtitle !== '') {
Text(it.subtitle)
.fontSize(12)
.fontColor($r('app.color.text_secondary'))
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
.textAlign(TextAlign.Start)
} }
// 兜底:拿不到 Scroll 矩形时用「窗口高 − 键盘高」
if (visibleBottomPx <= 0 && this.keyboardHeight > 0 && this.hostWindow !== undefined) {
visibleBottomPx = this.hostWindow.getWindowProperties().windowRect.height - this.keyboardHeight;
} }
if (visibleBottomPx <= 0) { .alignItems(HorizontalAlign.Start)
return; .width('100%')
.padding({ left: 14, right: 14, top: 10, bottom: 10 })
} }
const gapPx: number = vp2px * 10; .onClick(() => {
const deltaPx: number = rowBottomPx - (visibleBottomPx - gapPx); this.applyPlace(it);
// ±6vp 内视为已对齐,避免每次按键都抖一下 })
if (Math.abs(deltaPx) > vp2px * 6) { }, (it: PlaceItem, idx: number) => `${it.siteId}_${idx}`)
// ⚠️ px → vp 换算,见上面坑②
this.formScroller.scrollBy(0, deltaPx / vp2px);
LogUtil.write(`地点对齐:滚 ${Math.round(deltaPx / vp2px)}vp(行底 ${Math.round(rowBottomPx / vp2px)}vp / 可视底 ${Math.round(visibleBottomPx / vp2px)}vp)`);
} }
} catch (err) { .width('100%')
const e = err as BusinessError; .constraintSize({ maxHeight: 216 })
LogUtil.write(`地点对齐失败: ${e.message}`); .scrollBar(BarState.Auto)
.divider({ strokeWidth: 0.5, color: $r('app.color.shadow_color') })
} else {
Text(this.placeHint !== '' ? this.placeHint : '没有找到匹配地点,可直接手动输入')
.fontSize(12)
.fontColor($r('app.color.text_hint'))
.width('100%')
.padding({ left: 14, right: 14, top: 12, bottom: 12 })
} }
}, 60); }
}, 60); .width('100%')
.padding({ top: 4, bottom: 4 })
} }
/** 选中一条候选:写进地点输入框并收起列表 */ /** 选中一条候选:写进地点输入框并收起列表 */
private applyPlace(it: PlaceItem): void { private applyPlace(it: PlaceItem): void {
// 写入完整地址(formatAddress),这样同步到服务器/其他客户端时地址是完整的 // ⭐ 顺序很关键:先收起浮层,再写文本。
this.location = it.locationText; // TextInput 的 text 被程序改写会触发 onChange,若此时 placePicked 已被
this.placePicked = true; // onChange 重置为 false,就会**重新发起一次搜索** → 用户看到
this.placeItems = []; // "候选框消失了一下又冒出来一次"。用 lastAppliedText 精确识别这类回写。
this.placeOpen = false;
this.placeHint = '';
this.cancelSuggestTimer(); this.cancelSuggestTimer();
this.closePlacePopup();
this.placePicked = true;
this.lastAppliedText = it.locationText;
this.location = it.locationText;
}
/** 收起候选浮层(统一入口,保证 placePicked 之外的状态都被清干净) */
private closePlacePopup(): void {
this.placeOpen = false;
this.placeLoading = false;
this.placeItems = [];
this.placeHint = '';
} }
/** 打开系统地图选点页(可搜索、可周边、可拖图钉) */ /** 打开系统地图选点页(可搜索、可周边、可拖图钉) */
@@ -1457,68 +1426,9 @@ struct EventEditPage {
.borderRadius(12) .borderRadius(12)
.backgroundColor($r('app.color.card_bg')) .backgroundColor($r('app.color.card_bg'))
// ⭐ 候选列表放在输入框**上方**:这样候选展开时只需把"输入框"贴到键盘上沿,
// 候选自然向上占据剩余空间,输入框不会被顶走,用户能一边看候选一边继续打字。
if (this.placeLoading) {
Row({ space: 6 }) {
LoadingProgress().width(16).height(16).color($r('app.color.text_hint'))
Text('搜索中…')
.fontSize(12)
.fontColor($r('app.color.text_hint'))
}
.width('100%')
.padding({ left: 14, top: 2, bottom: 6 })
} else if (this.placeOpen && this.placeItems.length > 0) {
// ⭐ 用 List 而不是 Column:候选可能 6 条,全展开会占掉大半个屏幕。
// 限高 216vp(约 4 条)后内部可滚,既看得全又不会把输入框挤出屏幕。
List() {
ForEach(this.placeItems, (it: PlaceItem, idx: number) => {
ListItem() {
Column({ space: 2 }) {
Text(it.title)
.fontSize(14)
.fontColor($r('app.color.text_primary'))
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
.textAlign(TextAlign.Start)
if (it.subtitle !== '') {
Text(it.subtitle)
.fontSize(12)
.fontColor($r('app.color.text_secondary'))
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
.textAlign(TextAlign.Start)
}
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.padding({ left: 14, right: 14, top: 9, bottom: 9 })
}
.onClick(() => {
this.applyPlace(it);
})
}, (it: PlaceItem, idx: number) => `${it.siteId}_${idx}`)
}
.width('100%')
.constraintSize({ maxHeight: 216 })
.borderRadius(12)
.backgroundColor($r('app.color.card_bg'))
.padding({ top: 4, bottom: 4 })
.scrollBar(BarState.Auto)
.divider({ strokeWidth: 0.5, color: $r('app.color.shadow_color') })
.margin({ bottom: 8 })
.id('placeList')
} else if (this.placeHint !== '') {
Text(this.placeHint)
.fontSize(12)
.fontColor($r('app.color.text_hint'))
.width('100%')
.padding({ left: 14, bottom: 6 })
}
// 地点(输入几个字 → 系统地图服务补全候选) // 地点(输入几个字 → 系统地图服务补全候选)
// ⭐ 候选列表**不在布局流里**:它通过 bindPopup 挂在输入框上、向**上方**弹出。
// 这样候选的出现/消失完全不改变输入框位置 —— 从根上消除"输入框上下跳"。
Row({ space: 8 }) { Row({ space: 8 }) {
TextInput({ text: this.location, placeholder: '地点(可选,输入几个字可搜索)' }) TextInput({ text: this.location, placeholder: '地点(可选,输入几个字可搜索)' })
.height(44) .height(44)
@@ -1530,6 +1440,15 @@ struct EventEditPage {
.placeholderColor($r('app.color.text_hint')) .placeholderColor($r('app.color.text_hint'))
.caretColor($r('app.color.brand')) .caretColor($r('app.color.brand'))
.onChange((v: string) => { .onChange((v: string) => {
// ⭐ 程序改写 text(选中候选后回写地址)也会触发 onChange。
// 精确比对最后一次程序写入值:命中就不再发起搜索,
// 否则会"选完 → 候选框消失 → 又冒出来一次"。
if (v === this.lastAppliedText) {
this.lastAppliedText = '';
this.location = v;
return;
}
this.lastAppliedText = '';
this.location = v; this.location = v;
// 用户重新输入时解除"已选中"状态,恢复自动补全 // 用户重新输入时解除"已选中"状态,恢复自动补全
this.placePicked = false; this.placePicked = false;
@@ -1540,19 +1459,33 @@ struct EventEditPage {
this.cancelSuggestTimer(); this.cancelSuggestTimer();
this.runSuggest(this.location, true); this.runSuggest(this.location, true);
}) })
// ⭐ 一聚焦就把输入框钉到键盘上方:键盘弹起是异步的,延时对齐两次
// (320ms 时键盘基本已展开;ensurePlaceVisible 内部还会再等 120ms 稳定布局)
.onFocus(() => { .onFocus(() => {
// 键盘避让交给系统(RESIZE 已保证光标可见);
// 候选是浮层,不需要再滚动页面去给它腾位置
this.placeFocused = true; this.placeFocused = true;
setTimeout(() => {
this.ensurePlaceVisible();
}, 320);
}) })
.onBlur(() => { .onBlur(() => {
// 失焦(点"地图"按钮、点别处、收起键盘)→ 收起候选,避免列表残留在屏幕上 // 失焦(点"地图"按钮、点别处、收起键盘)→ 收起候选浮层
this.placeFocused = false; this.placeFocused = false;
this.closePlacePopup();
})
.bindPopup($$this.placeOpen, {
builder: (): void => {
this.placePopup()
},
placement: Placement.Top,
popupColor: $r('app.color.card_bg'),
enableArrow: false,
showInSubWindow: false,
focusable: false,
autoCancel: true,
radius: 12,
onStateChange: (e: PopupStateChangeParam) => {
// 用户点浮层外部/floating 自动关闭时,同步状态,避免下次不弹
if (!e.isVisible && this.placeOpen) {
this.placeOpen = false; this.placeOpen = false;
this.placeLoading = false; }
}
}) })
.id('placeAnchor') .id('placeAnchor')
// 在地图上选点(系统地图界面,可选可搜可周边) // 在地图上选点(系统地图界面,可选可搜可周边)
@@ -1614,7 +1547,6 @@ struct EventEditPage {
.layoutWeight(1) .layoutWeight(1)
.scrollBar(BarState.Off) .scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.Spring) .edgeEffect(EdgeEffect.Spring)
.id('formScroll')
.align(Alignment.Top) .align(Alignment.Top)
} }
.width('100%') .width('100%')