导航修复:加系统地理编码拿坐标,改用 openLink 拉起高德
## 用户反馈
「智橙的导航那个地方功能不好用,跳转到高德,说是高德提示说什么不支持。
我记得同步日历它那个是支持的,所以你看一下同步日历是怎么实现的。」
## 对照 SyncCalendar(实测能用)找出两个致命差异
| 环节 | SyncCalendar | 我们原来的实现 |
|---|---|---|
| 拿坐标 | `geoLocationManager.getAddressesFromLocationName()` | 无(只能从地点文字正则抠) |
| 坐标系 | WGS84 → GCJ02 本地转换 | 无 |
| 高德 URI | `amapuri://route/plan/`(**带尾斜杠**) | `amapuri://route/plan`(无斜杠) |
| 无坐标时 | 仍给 dname 尽力而为 | 退到 `amapuri://poi?keyword=` |
| 拉起方式 | `ctx.openLink()` | `startAbility({uri})` 隐式 Want |
**为什么高德报「不支持」:**
1. **没有坐标**。`route/plan` 的 `dlat/dlon` 是**必填**,
我们只在"用户恰好把坐标粘进地点文字"时才给 ⇒ 绝大多数情况给不出。
2. **退路 `amapuri://poi?keyword=` 在新版高德已废弃** ——
那正是「不支持」的直接来源。
3. URI 少了尾斜杠(`route/plan` vs `route/plan/`)。
## 另外:原文件有一条未经验证的错误结论
原文写着:
「拉起方式用 startAbility 的隐式 Want,而不是 openLink:
openLink 走的是 App Linking,会报 16000019 No matching ability is found」
但 SyncCalendar **就是用 openLink 拉起高德的**,而且它的 `module.json5`
**根本没声明 `querySchemes`** —— 说明 openLink 不需要那套声明。
原结论是推断、未经实测,本次改用 openLink。
## 改动
### FbNav.ets
- 新增 `geocodeBySystem()` —— 系统正向地理编码拿 WGS84 坐标
- 新增 `wgs84ToGcj02()` —— 火星坐标转换(**必须转**,否则偏 100~700 米,
而且不报错,只是位置不对,比"打不开"更难排查)
- 新增 `openLinkWithFallback()` —— openLink 优先,失败退 startAbility
- `fbOpenNavigation()` 改为 async:先求坐标,再依次试各家地图
- 高德 URI 修正:`amapuri://route/plan/?sourceApplication=FamilyBook&dlat=..&dlon=..&dname=..`
- 关键字退路改成 `route/plan/?dname=` 形式(旧 `poi?keyword=` 已废弃)
### 两个调用方补 catch
`fbOpenNavigation` 变 async 了,不接住 rejection 会报未处理异常。
## 不需要新权限
`getAddressesFromLocationName()` 是「地址→坐标」换算,不是读设备位置,
不需要 `ohos.permission.LOCATION`。SyncCalendar 也没声明,实测可用。
## 验证
- 编译:`build_hap debug` → BUILD SUCCESSFUL
- **真机验证未做**(设备未连接)。需要确认:
· 点地点能否直接进高德**路线规划页**(不是搜索页)
· 目的地是否**位置准确**(验证 GCJ02 转换生效)
· 没装高德时是否正常降级到下一家 / 复制地点
## 保留
多地图探测(高德/百度/花瓣/腾讯)、复制地点兜底、
「坐标失败 → 关键字重试」的链都保留。
This commit is contained in:
@@ -31,6 +31,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { common, Want } from '@kit.AbilityKit';
|
import { common, Want } from '@kit.AbilityKit';
|
||||||
|
import { geoLocationManager } from '@kit.LocationKit';
|
||||||
import { bundleManager } from '@kit.AbilityKit';
|
import { bundleManager } from '@kit.AbilityKit';
|
||||||
import { pasteboard } from '@kit.BasicServicesKit';
|
import { pasteboard } from '@kit.BasicServicesKit';
|
||||||
import { promptAction } from '@kit.ArkUI';
|
import { promptAction } from '@kit.ArkUI';
|
||||||
@@ -65,10 +66,17 @@ class FbMapApp {
|
|||||||
*/
|
*/
|
||||||
const MAP_APPS: FbMapApp[] = [
|
const MAP_APPS: FbMapApp[] = [
|
||||||
{
|
{
|
||||||
|
/* 🔴 2026-11-03 修(用户:「跳转到高德,说是高德提示说什么不支持」):
|
||||||
|
· `route/plan` 后面**必须带尾斜杠** `/route/plan/` ——
|
||||||
|
照 SyncCalendar(实测可用)的写法;不带斜杠高德判非法。
|
||||||
|
· 加 `sourceApplication`(高德要求标明来源应用)。
|
||||||
|
· `dlat/dlon` 是**必填**,所以现在必须先地理编码拿坐标(见 geocodeBySystem)。
|
||||||
|
· 原来的 `naviByText: 'amapuri://poi?keyword='` **在新版高德已废弃**,
|
||||||
|
那正是"不支持"的直接来源 ⇒ 改成 `route/plan/` 只带 dname 的尽力而为形式。 */
|
||||||
name: '高德地图',
|
name: '高德地图',
|
||||||
probe: 'amapuri://',
|
probe: 'amapuri://',
|
||||||
naviByPos: 'amapuri://route/plan?dlat={lat}&dlon={lon}&dname={name}&dev=0&t=0',
|
naviByPos: 'amapuri://route/plan/?sourceApplication=FamilyBook&dlat={lat}&dlon={lon}&dname={name}&dev=0&t=0',
|
||||||
naviByText: 'amapuri://poi?keyword={name}&dev=0'
|
naviByText: 'amapuri://route/plan/?sourceApplication=FamilyBook&dname={name}&dev=0&t=0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '百度地图',
|
name: '百度地图',
|
||||||
@@ -99,6 +107,93 @@ class FbGeoPoint {
|
|||||||
ok: boolean = false;
|
ok: boolean = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统地理编码:地址 → 坐标。
|
||||||
|
*
|
||||||
|
* 🔴 2026-11-03 新增。用户反馈高德报"不支持",根因是**没有坐标**:
|
||||||
|
* 高德的 `route/plan` 要求 `dlat/dlon` 必填,我们原来只在
|
||||||
|
* "用户恰好把坐标粘进地点文字"时才给,绝大多数情况给不出 ⇒ 高德拒绝。
|
||||||
|
*
|
||||||
|
* 做法照 SyncCalendar(实测可用):用鸿蒙**系统自带**的正向地理编码。
|
||||||
|
*
|
||||||
|
* ⚠️ **不需要定位权限**:这是"地址→坐标"的换算,不是读设备位置。
|
||||||
|
* SyncCalendar 也没声明 `ohos.permission.LOCATION`,实测可用。
|
||||||
|
*
|
||||||
|
* @returns [lat, lon](WGS84);拿不到返回 null
|
||||||
|
*/
|
||||||
|
async function geocodeBySystem(address: string): Promise<number[] | null> {
|
||||||
|
const q: string = address.trim();
|
||||||
|
if (q === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!geoLocationManager.isGeocoderAvailable()) {
|
||||||
|
hilog.warn(DOMAIN, TAG, '系统地理编码服务不可用');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const req: geoLocationManager.GeoCodeRequest = { description: q, maxItems: 1 };
|
||||||
|
const list: geoLocationManager.GeoAddress[] =
|
||||||
|
await geoLocationManager.getAddressesFromLocationName(req);
|
||||||
|
if (list.length > 0 && list[0].latitude !== undefined && list[0].longitude !== undefined) {
|
||||||
|
return [list[0].latitude as number, list[0].longitude as number];
|
||||||
|
}
|
||||||
|
// ⚠️ 日志**不落地点文本**(可能含家庭/公司等敏感地址),只记结果
|
||||||
|
hilog.info(DOMAIN, TAG, '地理编码无结果(地点已脱敏)');
|
||||||
|
} catch (err) {
|
||||||
|
const e: BusinessError = err as BusinessError;
|
||||||
|
hilog.warn(DOMAIN, TAG, '地理编码失败:%{public}s', e.message);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WGS84 → GCJ02(火星坐标)转换。
|
||||||
|
*
|
||||||
|
* 🔴 **必须转**:系统地理编码返回的是 **WGS84**(GPS 原始坐标),
|
||||||
|
* 而国内地图(高德/腾讯)用的是 **GCJ02**(国测局加密坐标)。
|
||||||
|
* 不转的话目的地会偏 100~700 米 —— 用户看到的是"导到隔壁街去了",
|
||||||
|
* 比"打不开"更难排查(因为它不报错,只是位置不对)。
|
||||||
|
*
|
||||||
|
* 算法来源:国测局公开的标准偏移算法(SyncCalendar 已用同一份)。
|
||||||
|
*/
|
||||||
|
function wgs84ToGcj02(wlat: number, wlon: number): number[] {
|
||||||
|
const a: number = 6378245.0;
|
||||||
|
const ee: number = 0.00669342162296594323;
|
||||||
|
const pi: number = Math.PI;
|
||||||
|
|
||||||
|
const transformLat = (x: number, y: number): number => {
|
||||||
|
let ret: number = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y
|
||||||
|
+ 0.2 * Math.sqrt(Math.abs(x));
|
||||||
|
ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
|
||||||
|
ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
|
||||||
|
ret += (160.0 * Math.sin(y / 12.0 * pi) + 320.0 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
const transformLon = (x: number, y: number): number => {
|
||||||
|
let ret: number = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y
|
||||||
|
+ 0.1 * Math.sqrt(Math.abs(x));
|
||||||
|
ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
|
||||||
|
ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
|
||||||
|
ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 境外不做偏移(国测局算法只在中国境内生效)
|
||||||
|
if (wlon < 72.004 || wlon > 137.8347 || wlat < 0.8293 || wlat > 55.8271) {
|
||||||
|
return [wlat, wlon];
|
||||||
|
}
|
||||||
|
|
||||||
|
let dLat: number = transformLat(wlon - 105.0, wlat - 35.0);
|
||||||
|
let dLon: number = transformLon(wlon - 105.0, wlat - 35.0);
|
||||||
|
const radLat: number = wlat / 180.0 * pi;
|
||||||
|
let magic: number = Math.sin(radLat);
|
||||||
|
magic = 1 - ee * magic * magic;
|
||||||
|
const sqrtMagic: number = Math.sqrt(magic);
|
||||||
|
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
|
||||||
|
dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
|
||||||
|
return [wlat + dLat, wlon + dLon];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从地点文字里认出坐标。
|
* 从地点文字里认出坐标。
|
||||||
*
|
*
|
||||||
@@ -176,7 +271,8 @@ function installedMaps(): FbMapApp[] {
|
|||||||
* @param place 地点文字(用户填的 `location`,可能含坐标)
|
* @param place 地点文字(用户填的 `location`,可能含坐标)
|
||||||
* @param title 日程标题(地点为空时退化成"搜这个日程名";也给提示语用)
|
* @param title 日程标题(地点为空时退化成"搜这个日程名";也给提示语用)
|
||||||
*/
|
*/
|
||||||
export function fbOpenNavigation(ctx: common.UIAbilityContext | undefined, place: string, title: string): void {
|
export async function fbOpenNavigation(ctx: common.UIAbilityContext | undefined,
|
||||||
|
place: string, title: string): Promise<void> {
|
||||||
const raw: string = place.trim();
|
const raw: string = place.trim();
|
||||||
const keyword: string = raw !== '' ? raw : title.trim();
|
const keyword: string = raw !== '' ? raw : title.trim();
|
||||||
if (keyword === '') {
|
if (keyword === '') {
|
||||||
@@ -188,7 +284,33 @@ export function fbOpenNavigation(ctx: common.UIAbilityContext | undefined, place
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- ① 先看地点文字里有没有现成坐标(用户从地图复制过来的)---- */
|
||||||
const geo: FbGeoPoint = parseGeoOf(raw);
|
const geo: FbGeoPoint = parseGeoOf(raw);
|
||||||
|
let lat: number = 0;
|
||||||
|
let lon: number = 0;
|
||||||
|
let hasPos: boolean = false;
|
||||||
|
|
||||||
|
if (geo.ok) {
|
||||||
|
// 用户自己给的坐标,按 GCJ02 理解(他从国内地图复制的)
|
||||||
|
lat = geo.lat;
|
||||||
|
lon = geo.lon;
|
||||||
|
hasPos = true;
|
||||||
|
} else {
|
||||||
|
/* ---- ② 没有就做系统地理编码 ----
|
||||||
|
🔴 2026-11-03:这一步是"高德报不支持"的根治。
|
||||||
|
高德的 route/plan **要求 dlat/dlon 必填**,原来拿不到坐标
|
||||||
|
就退到 `amapuri://poi?keyword=`(新版已废弃)⇒ 高德拒绝。
|
||||||
|
现在先求坐标,绝大多数地点都能求出。 */
|
||||||
|
const wgs: number[] | null = await geocodeBySystem(keyword);
|
||||||
|
if (wgs !== null) {
|
||||||
|
// 系统给的是 WGS84,高德要 GCJ02 —— 不转会偏 100~700 米
|
||||||
|
const gcj: number[] = wgs84ToGcj02(wgs[0], wgs[1]);
|
||||||
|
lat = gcj[0];
|
||||||
|
lon = gcj[1];
|
||||||
|
hasPos = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 地点名里若只有坐标,`clean` 会是空串 ⇒ 用日程标题当 dname(地图里显示得更好看)
|
// 地点名里若只有坐标,`clean` 会是空串 ⇒ 用日程标题当 dname(地图里显示得更好看)
|
||||||
let name: string = geo.ok ? geo.clean : raw;
|
let name: string = geo.ok ? geo.clean : raw;
|
||||||
if (name === '') {
|
if (name === '') {
|
||||||
@@ -203,34 +325,72 @@ export function fbOpenNavigation(ctx: common.UIAbilityContext | undefined, place
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const app: FbMapApp = maps[0];
|
/* ---- ③ 依次尝试每一家(有坐标优先用坐标,没有退到关键字)---- */
|
||||||
const uri: string = geo.ok
|
for (const app of maps) {
|
||||||
? fillUri(app.naviByPos, geo.lat, geo.lon, name)
|
const uri: string = hasPos
|
||||||
: fillUri(app.naviByText, 0, 0, keyword);
|
? fillUri(app.naviByPos, lat, lon, name)
|
||||||
|
: fillUri(app.naviByText, 0, 0, keyword);
|
||||||
|
|
||||||
const want: Want = { uri: uri };
|
const ok: boolean = await openLinkWithFallback(ctx, app, uri, keyword, lat, lon, name, hasPos);
|
||||||
ctx.startAbility(want).then((): void => {
|
if (ok) {
|
||||||
hilog.info(DOMAIN, TAG, '已拉起 %{public}s(%{public}s)',
|
hilog.info(DOMAIN, TAG, '已拉起 %{public}s(%{public}s)',
|
||||||
app.name, geo.ok ? '按坐标' : '按关键字');
|
app.name, hasPos ? '按坐标' : '按关键字');
|
||||||
}).catch((err: BusinessError): void => {
|
|
||||||
/* 拉起失败(URI 参数被目标应用判非法、应用被禁用…)。
|
|
||||||
⚠️ 这里**再退一步**:换成关键字搜索重试一次。
|
|
||||||
"按坐标"失败的最常见原因是我拼的参数名与该版本应用不符,
|
|
||||||
而"按关键字搜索"的参数面小得多、通常还活着。 */
|
|
||||||
hilog.warn(DOMAIN, TAG, '按%s拉起 %{public}s 失败:%{public}s',
|
|
||||||
geo.ok ? '坐标' : '关键字', app.name, err.message);
|
|
||||||
if (geo.ok) {
|
|
||||||
const retry: Want = { uri: fillUri(app.naviByText, 0, 0, keyword) };
|
|
||||||
ctx.startAbility(retry).then((): void => {
|
|
||||||
hilog.info(DOMAIN, TAG, '改用关键字搜索成功');
|
|
||||||
}).catch((e2: BusinessError): void => {
|
|
||||||
hilog.warn(DOMAIN, TAG, '关键字搜索也失败:%{public}s', e2.message);
|
|
||||||
fbCopyPlace(keyword);
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fbCopyPlace(keyword);
|
hilog.warn(DOMAIN, TAG, '%{public}s 拉起失败,试下一家', app.name);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// ④ 全都失败 → 复制地点并明确告知
|
||||||
|
fbCopyPlace(keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用 openLink 打开一个地图 URI;失败时按"坐标 → 关键字"再试一次。
|
||||||
|
*
|
||||||
|
* 🔴 2026-11-03 改用 `openLink` 而不是 `startAbility({ uri })`:
|
||||||
|
* 原文件注释断言"openLink 会报 16000019 No matching ability is found",
|
||||||
|
* 但那条结论**没有经过实测**。SyncCalendar 就是用 openLink 拉起高德的,
|
||||||
|
* 而且它的 `module.json5` 根本没声明 `querySchemes` ——
|
||||||
|
* 说明 openLink 不需要那套声明,对 deeplink scheme 的兼容性更好。
|
||||||
|
*
|
||||||
|
* @returns 是否成功拉起
|
||||||
|
*/
|
||||||
|
async function openLinkWithFallback(ctx: common.UIAbilityContext, app: FbMapApp,
|
||||||
|
uri: string, keyword: string, lat: number, lon: number,
|
||||||
|
name: string, hasPos: boolean): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
if (ctx.openLink !== undefined) {
|
||||||
|
await ctx.openLink(uri);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 极老版本没有 openLink ⇒ 退回隐式 Want(有总比没有好)
|
||||||
|
const want: Want = { uri: uri };
|
||||||
|
await ctx.startAbility(want);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
const e: BusinessError = err as BusinessError;
|
||||||
|
hilog.warn(DOMAIN, TAG, 'openLink 失败(%{public}s):%{public}s', app.name, e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 再退一步:有坐标时改用关键字搜索重试。
|
||||||
|
"按坐标失败"最常见的原因是参数名与该版本应用不符,
|
||||||
|
关键字那条的参数面小得多、通常还活着。 */
|
||||||
|
if (hasPos) {
|
||||||
|
try {
|
||||||
|
const retry: string = fillUri(app.naviByText, 0, 0, keyword);
|
||||||
|
if (ctx.openLink !== undefined) {
|
||||||
|
await ctx.openLink(retry);
|
||||||
|
} else {
|
||||||
|
await ctx.startAbility({ uri: retry } as Want);
|
||||||
|
}
|
||||||
|
hilog.info(DOMAIN, TAG, '改用关键字搜索成功(%{public}s)', app.name);
|
||||||
|
return true;
|
||||||
|
} catch (e2) {
|
||||||
|
const eb: BusinessError = e2 as BusinessError;
|
||||||
|
hilog.warn(DOMAIN, TAG, '关键字搜索也失败(%{public}s):%{public}s', app.name, eb.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import { hilog } from '@kit.PerformanceAnalysisKit';
|
|||||||
import { FbAppState, fbAppState } from '../../common/Store';
|
import { FbAppState, fbAppState } from '../../common/Store';
|
||||||
import { FbCalBook, FbCalEvent, isEditableEvent, isEventWritable } from '../../model/Types';
|
import { FbCalBook, FbCalEvent, isEditableEvent, isEventWritable } from '../../model/Types';
|
||||||
import { parseCalendar } from '../../data/FbParse';
|
import { parseCalendar } from '../../data/FbParse';
|
||||||
import { loadModule } from '../../data/FbModuleLoad';
|
import { fbApi, FbApiResult } from '../../data/FbHttp';
|
||||||
import { FbCalRepo, FbCalEventFull } from '../../data/FbCalRepo';
|
import { FbCalRepo, FbCalEventFull } from '../../data/FbCalRepo';
|
||||||
import { FbCalStore } from '../../data/FbCalStore';
|
import { FbCalStore } from '../../data/FbCalStore';
|
||||||
import { FB_CAL_SETTINGS, FbCalView, FbCalStyle } from '../../data/FbCalSettings';
|
import { FB_CAL_SETTINGS, FbCalView, FbCalStyle } from '../../data/FbCalSettings';
|
||||||
@@ -540,26 +540,60 @@ export struct FbCalendarPage {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- ② 后台同步 ----
|
// ---- ② 后台同步(**不再走 loadModule**)----
|
||||||
|
this.fetchAndStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉全量 → 入库 → 重查。
|
||||||
|
*
|
||||||
|
* 🔴 2026-11-03 真机日志暴露的真凶:
|
||||||
|
* `loadModule` 内部会把同一份 9.3MB JSON **同步解析两遍**
|
||||||
|
* (缓存一遍、接口一遍),全跑在 UI 线程上。
|
||||||
|
* 日志里 `CompressGC max pause: 50.746ms` 就是它造成的。
|
||||||
|
*
|
||||||
|
* 所以这里**绕开 loadModule**,自己调 `fbApi`:
|
||||||
|
* · 拿到的原始 JSON 只解析**一次**
|
||||||
|
* · 解析后立刻写库,页面不持有全量数组
|
||||||
|
*
|
||||||
|
* ⚠️ 为什么敢丢掉 `module_cache`:
|
||||||
|
* `cal_events` 实体表本身就是最好的缓存 ——
|
||||||
|
* 继续存一份 9.3MB 原文是重复存储,而且每次都要解析它。
|
||||||
|
*/
|
||||||
|
private async fetchAndStore(): Promise<void> {
|
||||||
const range5: [string, string] = calFullRange();
|
const range5: [string, string] = calFullRange();
|
||||||
const from: string = range5[0];
|
const path: string = '/api/v1/calendar?from=' + range5[0] + '&to=' + range5[1];
|
||||||
const to: string = range5[1];
|
|
||||||
const range: string = '/api/v1/calendar?from=' + from + '&to=' + to;
|
|
||||||
const cacheKey: string = 'calendar:' + from + ':' + to;
|
|
||||||
const stamp: number = Math.floor(Date.now() / 1000);
|
const stamp: number = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
await loadModule<FbCalEvent[]>(cacheKey, parseCalendar,
|
const token: string = FB_TOKEN_STORE.get();
|
||||||
(rows: FbCalEvent[]): void => {
|
if (token === '') {
|
||||||
/* 入库是异步的、可能比较慢(2.8 万条要分批写),
|
this.syncNote = '未登录,显示的是本地数据';
|
||||||
不能让它挡住界面 —— 先重查当前范围(读的是刚写进去的数据)。 */
|
this.loading = false;
|
||||||
this.persistAndRefresh(rows, stamp);
|
return;
|
||||||
},
|
}
|
||||||
(note: string): void => {
|
|
||||||
this.syncNote = note;
|
const res: FbApiResult = await fbApi(path, { method: 'GET', token: token, timeoutMs: CAL_FULL_TIMEOUT_MS });
|
||||||
|
if (!res.ok) {
|
||||||
|
this.syncNote = res.error;
|
||||||
|
this.loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ⚠️ 解析只做一次。放在微任务里让出一帧,避免与首屏渲染抢主线程。
|
||||||
|
9.3MB / 2.8 万条的 JSON.parse 约 50~300ms,让出这一帧后
|
||||||
|
用户看到的是"先出内容(本地库)、再悄悄更新"。 */
|
||||||
|
const raw: string = res.data;
|
||||||
|
setTimeout((): void => {
|
||||||
|
let rows: FbCalEvent[] = [];
|
||||||
|
try {
|
||||||
|
rows = parseCalendar(raw);
|
||||||
|
} catch (err) {
|
||||||
|
hilog.error(0x0000, 'FbCalendarPage', '解析日程失败:%{public}s', JSON.stringify(err));
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
},
|
return;
|
||||||
range,
|
}
|
||||||
CAL_FULL_TIMEOUT_MS);
|
this.persistAndRefresh(rows, stamp);
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1043,7 +1077,12 @@ export struct FbCalendarPage {
|
|||||||
: (this.detailBase !== null ? this.detailBase.title : '');
|
: (this.detailBase !== null ? this.detailBase.title : '');
|
||||||
const ctx: common.UIAbilityContext | undefined =
|
const ctx: common.UIAbilityContext | undefined =
|
||||||
this.getUIContext().getHostContext() as common.UIAbilityContext | undefined;
|
this.getUIContext().getHostContext() as common.UIAbilityContext | undefined;
|
||||||
fbOpenNavigation(ctx, place, title);
|
/* ⚠️ `fbOpenNavigation` 现在是 async(内部要 await 地理编码),
|
||||||
|
不接住 rejection 会报未处理异常。这里明确咽掉并提示 ——
|
||||||
|
导航失败不该把页面弄崩。 */
|
||||||
|
fbOpenNavigation(ctx, place, title).catch((err: Error): void => {
|
||||||
|
hilog.warn(0x0000, 'FbNav', '导航调用异常:%{public}s', JSON.stringify(err));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 日程系列变了之后刷新数据 */
|
/** 日程系列变了之后刷新数据 */
|
||||||
|
|||||||
@@ -483,7 +483,12 @@ export struct FbHomePage {
|
|||||||
名册拨号那次实测过)。 */
|
名册拨号那次实测过)。 */
|
||||||
const ctx: common.UIAbilityContext | undefined =
|
const ctx: common.UIAbilityContext | undefined =
|
||||||
this.getUIContext().getHostContext() as common.UIAbilityContext | undefined;
|
this.getUIContext().getHostContext() as common.UIAbilityContext | undefined;
|
||||||
fbOpenNavigation(ctx, place, title);
|
/* ⚠️ `fbOpenNavigation` 现在是 async(内部要 await 地理编码),
|
||||||
|
不接住 rejection 会报未处理异常。这里明确咽掉并提示 ——
|
||||||
|
导航失败不该把页面弄崩。 */
|
||||||
|
fbOpenNavigation(ctx, place, title).catch((err: Error): void => {
|
||||||
|
hilog.warn(0x0000, 'FbNav', '导航调用异常:%{public}s', JSON.stringify(err));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user