diff --git a/README.md b/README.md index d6c7b93b18d4a248f5a87310dc450a01a29da033..4099d26d80587ff58d22e4e474c9481aaeb3a6ab 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,14 @@ ZRouter是一款轻量级且非侵入性的鸿蒙动态路由框架,可解决H - 注解参数支持使用静态常量,可跨模块定义; - 支持自定义与全局拦截器,可设优先级及中断逻辑,可实现页面重定向、登录验证等业务场景。 - **支持服务路由,可实现Har/Hsp模块间的通信;** -- 支持全局及单个页面的生命周期函数管理,可使任意类都能享有与组件相同的生命周期特性,可实现页面埋点统计等业务场景; +- 支持全局及单个页面的生命周期函数管理,可使任意类都能享有与组件相同的生命周期特性,可用于页面埋点统计等业务场景; - **支持跨多级页面参数携带返回监听;** - 支持自定义URL路径跳转,可在拦截器内自行解析URL实现业务逻辑; -- 内置多种转场动画效果(平移、旋转、渐变、缩放、高斯模糊),并支持自定义动画; +- 内置多种转场动画效果(平移、旋转、渐变、缩放、高斯模糊、共享一镜到底动画),并支持自定义动画; - 支持启动模式、混淆、嵌套Navigation、Hap; -- 支持第三方Navigation的使用本库API; - **支持与您现有项目中的Navigation无缝融合,实现零成本向本库迁移;** -- 未来计划:支持共享元素动画、持续优化。 +- 支持ArkUI-X跨平台上使用; +- 支持第三方Navigation的使用本库API; **使用十分简单,没有繁琐的配置,两行代码就可以完成页面的跳转**,如下: @@ -29,19 +29,21 @@ ZRouter是一款轻量级且非侵入性的鸿蒙动态路由框架,可解决H ZRouter已上架录入到[华为鸿蒙生态伙伴组件专区](https://developer.huawei.com/consumer/cn/market/landing/component) -## router-register-plugin编译插件 +## **router-register-plugin编译插件 -- 很重要** ### 下载安装 -在项目根目录的hvigor目录下的hvigor-config.json5文件中配置安装 +**在项目根目录的hvigor目录下的`hvigor-config.json5`文件中配置安装** + +[![pEovcrt.png](https://s21.ax1x.com/2025/04/26/pEovcrt.png)](https://imgse.com/i/pEovcrt) ``` "dependencies": { - "router-register-plugin":"1.3.0" + "router-register-plugin":"x.x.x" }, ``` -![Static Badge](https://img.shields.io/badge/router-register-plugin?link=https%3A%2F%2Fgithub.com%2F751496032%2FRouterRegisterPlugin) +**编译插件最新版本**: ![Static Badge](https://img.shields.io/badge/router-register-plugin?link=https%3A%2F%2Fgithub.com%2F751496032%2FRouterRegisterPlugin) [![npm](https://img.shields.io/npm/v/router-register-plugin)](https://www.npmjs.com/package/router-register-plugin) @@ -54,11 +56,16 @@ ZRouter已上架录入到[华为鸿蒙生态伙伴组件专区](https://develope hvigorw --sync ``` -### 初始配置 +### 配置 -在每个模块中的hvigorfile.ts文件导入router-register-plugin插件模块,如下: +支持两种配置方式: +- **工程级配置**: 即在工程根目录下的hvigorfile.ts文件中全局配置; +- **模块级配置**:即在每个模块目录下的hvigorfile.ts文件中单独配置; +> 不建议一个项目同时使用两种配置方式,虽然是这种混合方式也是支持的,但容易出现配置冲突。模块级配置相对繁琐些,但配置项会更精细化些。 + +步骤: ``` // 1、导入 import { routerRegisterPlugin, PluginConfig } from 'router-register-plugin' @@ -68,9 +75,8 @@ const config: PluginConfig = { scanDirs: ['src/main/ets/pages', 'src/main/ets/views'], // 扫描的目录,如果不设置,默认是扫描src/main/ets目录 logEnabled: true, // 查看日志 viewNodeInfo: false, // 查看节点信息 - isAutoDeleteHistoryFiles: true // 删除无用编译产物 - lifecycleObserverAttributeName: 'xxx' // 可选,设置全局的生命周期实现类在组件上的属性名,默认值是lifecycleObserver - + ignoredModules:['RouterApi','common','xxx'], // 忽略的参与构建的模块,根据自己项目自行设置 + enableUiPreviewBuild: false, // 启用UI预览构建,不建议启动 } export default { // 3、添加插件 @@ -79,17 +85,23 @@ export default { ``` -常用的配置字段: +> **注意:hvigorfile.ts文件中默认配置不要删除了。** + +配置参数说明: -- scanDirs:扫描的目录,建议设置可更精准、更快扫描生成文件,如果不设置,默认是扫描src/main/ets目录 -- logEnabled:日志记录开关。 -- viewNodeInfo:查看节点信息的开关,只有logEnabled和viewNodeInfo同时开启才会生效 -- isAutoDeleteHistoryFiles:是否删除无用编译产物; -- lifecycleObserverAttributeName:设置全局的生命周期实现类在组件上的属性名,默认值是lifecycleObserver,若要设置单个页面的名称,可在@ZRoute注解中的loAttributeName属性上设置。 -PluginConfig配置对象还有其他属性,但不建议使用,使用默认值即可。 +| 参数名 | 类型 | 默认值 | 描述 | +|----------------------------------|----------|-------------------|------------------------------------------------------------------------------| +| `scanDirs` | string[] | ['src/main/ets'] | 扫描的目录,如果不设置,默认是扫描src/main/ets目录。建议配置该字段,避免扫描所有目录,影响工程编译效率** | | +| `logEnabled` | boolean | true | 是否打印日志 | | +| `viewNodeInfo` | boolean | false | 查看节点信息,只有与logEnable同时为true才会打印输出 | +| ~~`isAutoDeleteHistoryFiles`~~ | boolean | false | 是否在构建时删除编译产物,已弃用,在项目`clean`自动删除无用编译产物,请不要设置此参数。 | | +| `lifecycleObserverAttributeName` | string | lifecycleObserver | 如果使用了NavDest页面模板化功能,该配置字段会生效,默认属性名是lifecycleObserver,也可以在@Route注解上单独设置这个属性 | +| **`ignoredModules`** | string[] | [] | 忽略需要扫描的模块,填写模块名称,默认是全部模块;**插件在工程级时使用**,该字段才会生效。**建议配置该字段,避免扫描所有模块,影响工程编译效率** | +| `enableUiPreviewBuild` | boolean | false | 是否在ui预览构建时生成,默认不启用, 会降低ui预览构建效率 | -> 上面所有路径都是相对模块的src目录而言的,是相对路径。最后记得Sync Now或重新build让配置生效。 + +> **注意:** 以上配置参数都是可选的,建议配置`scanDirs`和`ignoredModules`字段,避免扫描所有目录和模块,影响工程编译效率。 其中`_generated`目录和`route_map.json`文件在编译阶段自动生成的,建议在git的`.gitignore`忽略掉这两个文件。 @@ -163,7 +175,7 @@ export class AppAbilityStage extends AbilityStage{ 2、在Index页面**使用Navigation作为根视图,通过ZRouter的getNavStack()方法获取NavPathStack实例,将其传入到Navigation的构造函数中。** -> 如果在Index入口文件中启动Splash页面,建议放在Navigation的onAppear方法中进行启动,或者组件的onPageShow方法,具体可参考demo +**如果在Index入口文件中启动Splash页面,建议放在Navigation的`onAppear`方法中进行启动,或者组件的`onPageShow`方法**,具体可参考demo ``` // Index 中使用 aboutToAppear 生命周期函数会因为 Navigation 还没初始化完成导致无法有效跳转,可使用替换成 onPageShow @@ -184,13 +196,17 @@ struct Index { .title('Main') .height('100%') .width('100%') + .onAppear(() => { + // 启动Splash页面 + ZRouter.push("SplashPage") + }) } } ``` -通过ZRouter的pushXX()方法进行页面跳转,参数是@Route装饰器上的name属性值。或者用ZRouter的getNavStack()方法来执行页面跳转。 +通过ZRouter的pushXX()方法进行页面跳转,参数是@Route装饰器上的name属性值;~~或者用ZRouter的getNavStack()方法来执行页面跳转~~。 -3、在NavDestination子页的使用自定义@Route或@ZRoute注解描述当前页面,其中name属性是必填的,页面跳转需要用到name值,建议使用驼峰式命名,还有另外三个可选属性分别是: +3、**在NavDestination子页的使用自定义`@Route`或`@ZRoute`注解标注页面**,其中name属性是必填的,页面跳转需要用到name值,还有另外三个可选属性分别是: - description:页面描述,没有功能作用; - needLogin:如果页面需要登录,可以将值设置为true,然后在拦截器中做页面重定向到登录页; @@ -225,13 +241,14 @@ export struct Page1 { ``` +> 如果觉得每个页面都要用NavDestination组件包裹太麻烦,可以使用[NavDestination模板化功能](https://gitee.com/common-apps/ZRouter/wikis/NavDestination%E9%A1%B5%E9%9D%A2%E6%A8%A1%E6%9D%BF%E5%8C%96%E8%83%BD%E5%8A%9B)。 + **建议通过ZRouter.getInstance()方式来操作路由的跳转与关闭,使用会更灵活简洁,之前的ZRouter的静态方法依然保留着,在1.2.0版本起将标记为过期状态了。** ```typescript ZRouter.getInstance() .setParam("root data") .setLunchMode(LaunchMode.STANDARD) // 启动模式 - .enableCrossPageParamReturn() // 跨页面参数返回 .setAnimate(true) .setPopListener((r) => { LogUtil.log("index result: ", r.data ," from: ", r.from); @@ -242,76 +259,68 @@ export struct Page1 { ### 拦截器 -ZRouter支持多个拦截器和全局拦截器,在拦截器中可以做页面跳转的拦截,比如登录拦截,404拦截、埋点、自定义URL路径跳转等。 +ZRouter支持多个拦截器和全局拦截器,在拦截器中可以做页面跳转的拦截,比如跳转前拦截、数据预取、登录拦截,404拦截、埋点、自定义URL路径等等。 #### 全局拦截器 -全局拦截器提供两种使用方式: -- 直接函数回调时的方式; -- 类实现接口的方式(建议使用,功能更全面),支持字面量对象和new创建的对象。 -函数回调的方式,代码示例: - -``` -@Entry -@Component -struct Index { - aboutToAppear(): void { - ZRouter.setGlobalInterceptor((info) => { - if (info.notRegistered) { - return - } - let isLogin = AppStorage.get("isLogin") - if (info.needLogin && !isLogin) { - let param = ZRouter.getParamByName(info.data?.name ?? "") - ZRouter.redirectForResult2("LoginPage", param, (data) => { - if (data.result) { - // 登录成功 - promptAction.showToast({ message: `登录成功` }) - return true // 返回true 则继续跳转登录前的页面 - } - return false - }) - } - }) +代码示例: - } - -} - -``` +```typescript +export class GlobalNavigateInterceptor implements IGlobalNavigateInterceptor{ -类实现接口的方式,代码示例: + static count = 0 + onNavigateBefore: (destInfo: DestinationInfo) => Promise = (destInfo) => { + console.log("IInterceptor Global onNavigateBefore -> ", destInfo.name) + return new Promise((resolve, _) => { + if (destInfo.name === RouterConstants.PAGE_BEFORE_PUSH) { + // 拦截跳转到ParamPage页面 + if (GlobalNavigateInterceptor.count === 0) { + destInfo.param = ' 在拦截器onNavigateBefore中已替换参数 ' + destInfo.next() // 继续跳转 默认的 ,可以不写 + } else if (GlobalNavigateInterceptor.count === 1) { + ToastUtils.show("拦截器onNavigateBefore中已拦截, 再点一次会继续执行") + destInfo.block() // 拦截跳转 + } else if (GlobalNavigateInterceptor.count === 2) { + destInfo.name = RouterConstants.LIFECYCLE_CASE_VIEW + } + GlobalNavigateInterceptor.count += 1 + } + resolve(destInfo) -```typescript + }) + } -export class GlobalNavigateInterceptor implements IGlobalNavigateInterceptor{ - onRootWillShow?: ((fromContext: NavDestinationContext) => void) | undefined = (fromContext) => { + onRootWillShow: ((fromContext: NavDestinationContext) => void) | undefined = (fromContext) => { console.log("IInterceptor Global onRootWillShow: ", fromContext.pathInfo.name) } - onPageWillShow?: ((fromContext: NavDestinationContext, toContext: NavDestinationContext) => void) | undefined = (from ,to)=>{ + onPageWillShow: ((fromContext: NavDestinationContext, toContext: NavDestinationContext) => void) | undefined = (from ,to)=>{ console.log("IInterceptor Global onPageWillShow: ", from, to.pathInfo.name, to.pathInfo.param) } - onNavigate?: ((context: InterceptorInfo) => void) | undefined = (info)=>{ + onNavigate: ((context: InterceptorInfo) => void) | undefined = (info)=>{ if (info.notRegistered) return - console.log("IInterceptor Global onNavigate: ", info.name) - + console.log("IInterceptor Global onNavigate -> ", info.name) let isLogin = AppStorage.get("isLogin") if (info.isNeedLogin && !isLogin) { let param = info.param - ZRouter.redirectForResult2("LoginPage", param, (data) => { - if (data.data) { - // 登录成功 - promptAction.showToast({ message: `登录成功` }) - return true // 返回true 则继续跳转登录前的页面 - } - return false - }) - } + ZRouter.getInstance() + .setParam(param) + .setAnimate(true) + .setPopListener((result) => { + if (result.data) { + // 登录成功 + promptAction.showToast({ message: `登录成功` }) + return true // 返回true 则继续跳转登录前的页面 + } else { + return false + } + }) + .redirect("LoginPage", RedirectType.REPLACE) + } } } @@ -321,7 +330,7 @@ ZRouter.setGlobalInterceptor(new GlobalNavigateInterceptor()) ``` -info.notRegistered()方法判断当前页面是否注册,如果没有注册,将使用ZRouter.redirect() 方法来重定向到404页面;通过ZRouter.redirectForResult() 方法来重定向到登录页面,这个方法接受一个回调函数,该回调函数会在用户登录成功或失败后被调用,在回调函数内部,使用 data.result判断是否登录 ,如果登录成功了给回调函数 return true 来指示继续执行登录前的页面跳转。如果登录失败,或者用户取消登录,回调函数将返回 false,表示不跳转。 +info.notRegistered()方法判断当前页面是否注册,如果没有注册,将使用ZRouter.redirect() 方法来重定向到404页面; 也可以通过redirect() 方法来重定向到登录页面,这个方法接受一个回调函数,该回调函数会在用户登录成功或失败后被调用,在回调函数内部,使用 data.result判断是否登录 ,如果登录成功了给回调函数 return true 来指示继续执行登录前的页面跳转。如果登录失败,或者用户取消登录,回调函数将返回 false,表示不跳转。 登录页面代码示例: @@ -354,9 +363,9 @@ export struct LoginPage{ 在登录成功后通过ZRouter.finishWithResult()方法携带数据关闭页面,会将状态传递给redirectForResult2()方法的回调函数。 -上面是全局拦截器,每个跳转都会触发,如果需要添加多个拦截器,则可以使用setInterceptor()方法。 +上面是全局拦截器,每个跳转都会触发,如果需要添加多个拦截器,则可以使用setInterceptor()方法,但不建议使用。 -#### 自定义拦截器 +#### 自定义拦截器 - 不建议使用 自定义拦截器,首先实现接口IInterceptor,然后使用setInterceptor()方法注册拦截器,,代码示例如下: @@ -475,6 +484,8 @@ export class UrlInterceptor implements IInterceptor { } ``` +**上面的逻辑也可以放在全局拦截器的onNavigateBefore()方法中处理。** + ## 注解上使用静态常量,可跨模块定义 router-register-plugin插件1.0.7版本起,@Route与@Service注解的name属性可使用静态常量,方便统一管理路由名称;静态常量支持当前模块或跨模块定义,常量的定义模版如下: @@ -607,15 +618,22 @@ ZRouter库是对NavPathStack对进行高度封装的,包括了页面跳转、 - 提交代码 - 新建 Pull Request -## 作者其他库 +## 其他库 - 鸿蒙数据库工具:https://gitee.com/HW-Commons/ZDbUtil -- 鸿蒙WebView桥接库:https://github.com/751496032/DSBridge-HarmonyOS +- 鸿蒙H5与原生的通信库:https://github.com/751496032/DSBridge-HarmonyOS - 鸿蒙日志库:https://gitee.com/common-apps/logger +## 计划 + +- 路由拦截器:支持pop拦截 +- 服务路由:模块entry支持动态注册 +- 路由插件:自动生成文件调整 + + ## 联系我们 - **欢迎大家提交issue、PR(可以统一收集问题,方便更多人查阅,会第一时间回复处理)** ,或进群交流(+v: 751496032)。 + **欢迎大家提交issue、PR、需求与建议(可以统一收集问题,方便更多人查阅,会第一时间回复处理)** ,或进群交流(+v: 751496032)。 [![_cgi-bin_mmwebwx-bin_webwxgetmsgimg__MsgID7985482268088807228skeycrypt_4f9ae0b8_0271518ab0cb7cd42bc056451ad75554mmweb_appidwx_webfilehelper.md.jpg](https://www.z4a.net/images/2025/03/12/_cgi-bin_mmwebwx-bin_webwxgetmsgimg__MsgID7985482268088807228skeycrypt_4f9ae0b8_0271518ab0cb7cd42bc056451ad75554mmweb_appidwx_webfilehelper.md.jpg)](https://www.z4a.net/image/ywEHV0) diff --git a/RouterApi/BuildProfile.ets b/RouterApi/BuildProfile.ets index 7b626e5a3069aba1d0261f9135c9c01ff9f97ab7..a0b7ec4364acfe48c21ab8e4042c6b309caf6b18 100644 --- a/RouterApi/BuildProfile.ets +++ b/RouterApi/BuildProfile.ets @@ -1,7 +1,7 @@ /** * Use these variables when you tailor your ArkTS code. They must be of the const type. */ -export const HAR_VERSION = '1.3.6'; +export const HAR_VERSION = '1.5.0'; export const BUILD_MODE_NAME = 'debug'; export const DEBUG = true; export const TARGET_NAME = 'default'; diff --git a/RouterApi/CHANGELOG.md b/RouterApi/CHANGELOG.md index 1fb5f247b73aa17def76df42ababc44ff5525605..1dd3461d4b4eab7bdb8cd442c78a7a087ec3a89d 100644 --- a/RouterApi/CHANGELOG.md +++ b/RouterApi/CHANGELOG.md @@ -1,56 +1,84 @@ ## 版本更新记录 +### 1.5.0 / 2025-5-26 + +- 新增一镜到底动画; +- 修复路由注解类型提示问题; +- 路由插件1.5.0版本 + - 支持工程级(全局配置)和模块级(模块独立配置)两种方式配置; + - 支持项目`clean`时自动删除编译产物; + - 优化构建效率,减少不必要的场景扫描构建生成;新增配置项`ignoredModules`,工程级配置可设置忽略模块,避免扫描所有模块;新增配置项`enableUiPreviewBuild`,避免在ui预览构建时生成, 影响ui预览效率。 + + + +### 1.4.1 / 2025-4-27 + +- 适配审核,修改`agcit_`。 + +### 1.4.0 / 2025-4-24 + +- 修复升级1.3.9后的编译错误; +- 修复拦截器`onNavigateBefore()`方法`name`参数不生效问题; + +### 1.3.9 / 2025-4-19 + +- 全局拦截器:新增 `onNavigateBefore()` 方法,用于在路由跳转前进行拦截操作; +- 新增`popToNameWithResult()` api,用于携带结果返回指定页面,中间页面会关闭; +- `@Route`注解可省略属性,生成的路由名为当前页面的类名; +- 修复跳转hsp页面出现闪退问题; +- `popNavWithResult` 、`enableCrossPageParamReturn` 已废弃。 + ### 1.3.6 / 2025-3-17 - 修复页面模板化时的生命周期函数不执行问题; ### 1.3.5 / 2025-3-11 -- 优化获取getParamByKey()方法,支持设置默认参数; +- 优化获取`getParamByKey()`方法,支持设置默认参数; - 优化生命周期管理,添加根页面判断逻辑; -- 新增replaceWithDefaultAnim() api,解决replace页面无动画的问题; +- 新增`replaceWithDefaultAnim() `api,解决replace页面无动画的问题; - 重定向新增REMOVE类型。 ### 1.3.4 / 2025-2-12 -- 修复setParam携带参数问题,处理Array等类型参数; -- 优化ZRouter.getInstance()、popResult()等api; +- 修复`setParam`携带参数问题,处理Array等类型参数; +- 优化`ZRouter.getInstance()`、`popResult()`等api; ### 1.3.3 / 2025-2-6 -- 修复setParam携带参数问题; +- 修复`setParam`携带参数问题; ### 1.3.2 / 2025-1-23 -- 修复setParam携带参数问题,默认不处理false和0; +- 修复`setParam`携带参数问题,默认不处理false和0; ### 1.3.1 / 2025-1-11 - 修复动画在ArkUI-X上的兼容问题; -- 修复setParam携带参数问题,支持false和0; +- 修复`setPara`m携带参数问题,支持false和0; - 修复启动模式在api11上闪退问题。 ### 1.3.0 / 2024-12-24 -- NavDestination页面模板化支持V2状态管理,编译插件需要升级到1.3.0版本; -- NavDestination页面模板化的生命周期实现类属性支持全局和单个页面自定义命名; +- `NavDestination`页面模板化支持V2状态管理,编译插件需要升级到1.3.0版本; +- `NavDestination`页面模板化的生命周期实现类属性支持全局和单个页面自定义命名; - 新增如下api: - - removeInterceptor:移除拦截器; - - withParam:携带页面参数,key-value形式; - - getParamByKey:获取页面参数,根据key获取; - - setModuleLoadedListener:设置动态模块加载状态监听; - - isDynamicLoadedComplete:判断动态模块是否加载完成; -- 修复普通拦截器在replace路由操作时不生效的问题; -- 修复全局拦截器页面首次跳转时onPageWillShow()方法不执行的问题; + - `removeInterceptor`:移除拦截器; + - `withParam`:携带页面参数,key-value形式; + - `getParamByKey`:获取页面参数,根据key获取; + - `setModuleLoadedListener`:设置动态模块加载状态监听; + - `isDynamicLoadedComplete`:判断动态模块是否加载完成; +- 修复普通拦截器在`replace`路由操作时不生效的问题; +- 修复全局拦截器页面首次跳转时`onPageWillShow()`方法不执行的问题; - 修复路由重定向问题,新增了重定向的类型; ### 1.2.0 / 2024-12-8 -- 新增NavDestination页面模板化能力,编译插件需要升级到1.2.0版本; -- 新增@ZRoute、@ZService、@ZLifecycle、@ZAttribute注解,其中@ZLifecycle和@ZAttribute用于辅助页面模板化能力; +- 新增`NavDestination`页面模板化能力,编译插件需要升级到1.2.0版本; +- 新增`@ZRoute`、`@ZService`、`@ZLifecycle`、`@ZAttribute`注解,其中`@ZLifecycle`和`@ZAttribute`用于辅助页面模板化能力; - 转场动画新增高斯模糊效果; -- ZRouter的路由静态方法标记为过期状态,建议使用NavDestBuilder的方法进行路由操作。 +- `ZRouter`的路由静态方法标记为过期状态,建议使用`NavDestBuilder`的方法进行路由操作。 ### 1.1.1 @@ -60,28 +88,28 @@ ### 1.1.0 - 新增组生命周期函数管理; -- 修复popWithResult()不支持布尔类型值的问题; +- 修复`popWithResult()`不支持布尔类型值的问题; ### 1.0.10 -- 修复在hsp模块跳转失败问题 +- 修复在`hsp`模块跳转失败问题 ### 1.0.9 -- 新增服务路由,可用于相互独立的Har/Hsp模块之间的通信;路由插件必须在1.0.9以上才支持; +- 新增服务路由,可用于相互独立的`Har/Hsp`模块之间的通信;路由插件必须在1.0.9以上才支持; ### 1.0.8 - 修复拦截器问题; -- 插件1.0.7版本支持@Route装饰器设置常量。 +- 插件1.0.7版本支持`@Route`装饰器设置常量。 ### 1.0.7 - 支持启动模式; - 重构拦截器,支持拦截器的优先级设置,全局拦截器支持页面显示监听; - 支持自定义URL路径跳转; -- 支持第三方Navigation系统路由表使用本库API。 +- 支持第三方`Navigation`系统路由表使用本库API。 ### 1.0.6 @@ -89,13 +117,13 @@ ### 1.0.4 -- 修复getParam问题 -- 新增redirectForResult2、finishWithResult api,两者是配对使用,通常用于登录成功后,继续跳转的场景。 +- 修复`getParam`问题 +- 新增`redirectForResult2`、`finishWithResult` api,两者是配对使用,通常用于登录成功后,继续跳转的场景。 ### 1.0.3 -- 新增日志开关,在init()初始化设置 -- 新增pushNavForResult、popNavWithResult、popToRootWithResult等api,可用于跨页面携带数据返回。 +- 新增日志开关,在`init()`初始化设置 +- 新增`pushNavForResult`、`popNavWithResult`、`popToRootWithResult`等api,可用于跨页面携带数据返回。 ### 1.0.2 @@ -104,7 +132,7 @@ ### 1.0.1 - 插件(1.0.2)支持一个ets文件定义多个子页面 -- 新增api,remove、move、getParam、replace等api +- 新增api,`remove`、`move`、`getParam`、`replace`等api ### 1.0.0 diff --git a/RouterApi/Index.ets b/RouterApi/Index.ets index 483e6c65aadbd870042be0e1bb6756a8c88bfa6e..9347d5436f78444b98c80ff64c452cb5be72dc51 100644 --- a/RouterApi/Index.ets +++ b/RouterApi/Index.ets @@ -1,6 +1,6 @@ export { Route, ZRoute} from './src/main/ets/annotation/Route' export { ZRouter } from './src/main/ets/api/Router' -export { PopResult,RouteMetadata , InterceptorInfoOrNull, InterceptorInfo } from './src/main/ets/model/Model' +export { PopResult,RouteMetadata , InterceptorInfoOrNull, InterceptorInfo, DestinationInfo , NavigationAction } from './src/main/ets/model/Model' export * from './src/main/ets/interceptions/IInterceptor' export { IProvider } from './src/main/ets/service/IProvider' export { Service, ZService } from './src/main/ets/annotation/Service' @@ -24,5 +24,7 @@ export { OpacityAnimateOptions } from './src/main/ets/animation/param/OpacityAni export { RotateAnimateOptions} from './src/main/ets/animation/param/RotateAnimateOptions' export { BlurAnimationOptions } from './src/main/ets/animation/param/BlurAnimationOptions' export { NavAnimParamBuilder } from './src/main/ets/animation/param/NavAnimParamBuilder' +export { CardSharedAnimationProperties } from './src/main/ets/animation/param/shared/CardSharedAnimationProperties' +export { SharedCardContainer } from './src/main/ets/animation/param/shared/components/SharedCardContainer' export { RedirectType } from './src/main/ets/model/Const' export { LaunchMode } from './src/main/ets/model/LaunchMode' diff --git a/RouterApi/README.md b/RouterApi/README.md index d6c7b93b18d4a248f5a87310dc450a01a29da033..4099d26d80587ff58d22e4e474c9481aaeb3a6ab 100644 --- a/RouterApi/README.md +++ b/RouterApi/README.md @@ -9,14 +9,14 @@ ZRouter是一款轻量级且非侵入性的鸿蒙动态路由框架,可解决H - 注解参数支持使用静态常量,可跨模块定义; - 支持自定义与全局拦截器,可设优先级及中断逻辑,可实现页面重定向、登录验证等业务场景。 - **支持服务路由,可实现Har/Hsp模块间的通信;** -- 支持全局及单个页面的生命周期函数管理,可使任意类都能享有与组件相同的生命周期特性,可实现页面埋点统计等业务场景; +- 支持全局及单个页面的生命周期函数管理,可使任意类都能享有与组件相同的生命周期特性,可用于页面埋点统计等业务场景; - **支持跨多级页面参数携带返回监听;** - 支持自定义URL路径跳转,可在拦截器内自行解析URL实现业务逻辑; -- 内置多种转场动画效果(平移、旋转、渐变、缩放、高斯模糊),并支持自定义动画; +- 内置多种转场动画效果(平移、旋转、渐变、缩放、高斯模糊、共享一镜到底动画),并支持自定义动画; - 支持启动模式、混淆、嵌套Navigation、Hap; -- 支持第三方Navigation的使用本库API; - **支持与您现有项目中的Navigation无缝融合,实现零成本向本库迁移;** -- 未来计划:支持共享元素动画、持续优化。 +- 支持ArkUI-X跨平台上使用; +- 支持第三方Navigation的使用本库API; **使用十分简单,没有繁琐的配置,两行代码就可以完成页面的跳转**,如下: @@ -29,19 +29,21 @@ ZRouter是一款轻量级且非侵入性的鸿蒙动态路由框架,可解决H ZRouter已上架录入到[华为鸿蒙生态伙伴组件专区](https://developer.huawei.com/consumer/cn/market/landing/component) -## router-register-plugin编译插件 +## **router-register-plugin编译插件 -- 很重要** ### 下载安装 -在项目根目录的hvigor目录下的hvigor-config.json5文件中配置安装 +**在项目根目录的hvigor目录下的`hvigor-config.json5`文件中配置安装** + +[![pEovcrt.png](https://s21.ax1x.com/2025/04/26/pEovcrt.png)](https://imgse.com/i/pEovcrt) ``` "dependencies": { - "router-register-plugin":"1.3.0" + "router-register-plugin":"x.x.x" }, ``` -![Static Badge](https://img.shields.io/badge/router-register-plugin?link=https%3A%2F%2Fgithub.com%2F751496032%2FRouterRegisterPlugin) +**编译插件最新版本**: ![Static Badge](https://img.shields.io/badge/router-register-plugin?link=https%3A%2F%2Fgithub.com%2F751496032%2FRouterRegisterPlugin) [![npm](https://img.shields.io/npm/v/router-register-plugin)](https://www.npmjs.com/package/router-register-plugin) @@ -54,11 +56,16 @@ ZRouter已上架录入到[华为鸿蒙生态伙伴组件专区](https://develope hvigorw --sync ``` -### 初始配置 +### 配置 -在每个模块中的hvigorfile.ts文件导入router-register-plugin插件模块,如下: +支持两种配置方式: +- **工程级配置**: 即在工程根目录下的hvigorfile.ts文件中全局配置; +- **模块级配置**:即在每个模块目录下的hvigorfile.ts文件中单独配置; +> 不建议一个项目同时使用两种配置方式,虽然是这种混合方式也是支持的,但容易出现配置冲突。模块级配置相对繁琐些,但配置项会更精细化些。 + +步骤: ``` // 1、导入 import { routerRegisterPlugin, PluginConfig } from 'router-register-plugin' @@ -68,9 +75,8 @@ const config: PluginConfig = { scanDirs: ['src/main/ets/pages', 'src/main/ets/views'], // 扫描的目录,如果不设置,默认是扫描src/main/ets目录 logEnabled: true, // 查看日志 viewNodeInfo: false, // 查看节点信息 - isAutoDeleteHistoryFiles: true // 删除无用编译产物 - lifecycleObserverAttributeName: 'xxx' // 可选,设置全局的生命周期实现类在组件上的属性名,默认值是lifecycleObserver - + ignoredModules:['RouterApi','common','xxx'], // 忽略的参与构建的模块,根据自己项目自行设置 + enableUiPreviewBuild: false, // 启用UI预览构建,不建议启动 } export default { // 3、添加插件 @@ -79,17 +85,23 @@ export default { ``` -常用的配置字段: +> **注意:hvigorfile.ts文件中默认配置不要删除了。** + +配置参数说明: -- scanDirs:扫描的目录,建议设置可更精准、更快扫描生成文件,如果不设置,默认是扫描src/main/ets目录 -- logEnabled:日志记录开关。 -- viewNodeInfo:查看节点信息的开关,只有logEnabled和viewNodeInfo同时开启才会生效 -- isAutoDeleteHistoryFiles:是否删除无用编译产物; -- lifecycleObserverAttributeName:设置全局的生命周期实现类在组件上的属性名,默认值是lifecycleObserver,若要设置单个页面的名称,可在@ZRoute注解中的loAttributeName属性上设置。 -PluginConfig配置对象还有其他属性,但不建议使用,使用默认值即可。 +| 参数名 | 类型 | 默认值 | 描述 | +|----------------------------------|----------|-------------------|------------------------------------------------------------------------------| +| `scanDirs` | string[] | ['src/main/ets'] | 扫描的目录,如果不设置,默认是扫描src/main/ets目录。建议配置该字段,避免扫描所有目录,影响工程编译效率** | | +| `logEnabled` | boolean | true | 是否打印日志 | | +| `viewNodeInfo` | boolean | false | 查看节点信息,只有与logEnable同时为true才会打印输出 | +| ~~`isAutoDeleteHistoryFiles`~~ | boolean | false | 是否在构建时删除编译产物,已弃用,在项目`clean`自动删除无用编译产物,请不要设置此参数。 | | +| `lifecycleObserverAttributeName` | string | lifecycleObserver | 如果使用了NavDest页面模板化功能,该配置字段会生效,默认属性名是lifecycleObserver,也可以在@Route注解上单独设置这个属性 | +| **`ignoredModules`** | string[] | [] | 忽略需要扫描的模块,填写模块名称,默认是全部模块;**插件在工程级时使用**,该字段才会生效。**建议配置该字段,避免扫描所有模块,影响工程编译效率** | +| `enableUiPreviewBuild` | boolean | false | 是否在ui预览构建时生成,默认不启用, 会降低ui预览构建效率 | -> 上面所有路径都是相对模块的src目录而言的,是相对路径。最后记得Sync Now或重新build让配置生效。 + +> **注意:** 以上配置参数都是可选的,建议配置`scanDirs`和`ignoredModules`字段,避免扫描所有目录和模块,影响工程编译效率。 其中`_generated`目录和`route_map.json`文件在编译阶段自动生成的,建议在git的`.gitignore`忽略掉这两个文件。 @@ -163,7 +175,7 @@ export class AppAbilityStage extends AbilityStage{ 2、在Index页面**使用Navigation作为根视图,通过ZRouter的getNavStack()方法获取NavPathStack实例,将其传入到Navigation的构造函数中。** -> 如果在Index入口文件中启动Splash页面,建议放在Navigation的onAppear方法中进行启动,或者组件的onPageShow方法,具体可参考demo +**如果在Index入口文件中启动Splash页面,建议放在Navigation的`onAppear`方法中进行启动,或者组件的`onPageShow`方法**,具体可参考demo ``` // Index 中使用 aboutToAppear 生命周期函数会因为 Navigation 还没初始化完成导致无法有效跳转,可使用替换成 onPageShow @@ -184,13 +196,17 @@ struct Index { .title('Main') .height('100%') .width('100%') + .onAppear(() => { + // 启动Splash页面 + ZRouter.push("SplashPage") + }) } } ``` -通过ZRouter的pushXX()方法进行页面跳转,参数是@Route装饰器上的name属性值。或者用ZRouter的getNavStack()方法来执行页面跳转。 +通过ZRouter的pushXX()方法进行页面跳转,参数是@Route装饰器上的name属性值;~~或者用ZRouter的getNavStack()方法来执行页面跳转~~。 -3、在NavDestination子页的使用自定义@Route或@ZRoute注解描述当前页面,其中name属性是必填的,页面跳转需要用到name值,建议使用驼峰式命名,还有另外三个可选属性分别是: +3、**在NavDestination子页的使用自定义`@Route`或`@ZRoute`注解标注页面**,其中name属性是必填的,页面跳转需要用到name值,还有另外三个可选属性分别是: - description:页面描述,没有功能作用; - needLogin:如果页面需要登录,可以将值设置为true,然后在拦截器中做页面重定向到登录页; @@ -225,13 +241,14 @@ export struct Page1 { ``` +> 如果觉得每个页面都要用NavDestination组件包裹太麻烦,可以使用[NavDestination模板化功能](https://gitee.com/common-apps/ZRouter/wikis/NavDestination%E9%A1%B5%E9%9D%A2%E6%A8%A1%E6%9D%BF%E5%8C%96%E8%83%BD%E5%8A%9B)。 + **建议通过ZRouter.getInstance()方式来操作路由的跳转与关闭,使用会更灵活简洁,之前的ZRouter的静态方法依然保留着,在1.2.0版本起将标记为过期状态了。** ```typescript ZRouter.getInstance() .setParam("root data") .setLunchMode(LaunchMode.STANDARD) // 启动模式 - .enableCrossPageParamReturn() // 跨页面参数返回 .setAnimate(true) .setPopListener((r) => { LogUtil.log("index result: ", r.data ," from: ", r.from); @@ -242,76 +259,68 @@ export struct Page1 { ### 拦截器 -ZRouter支持多个拦截器和全局拦截器,在拦截器中可以做页面跳转的拦截,比如登录拦截,404拦截、埋点、自定义URL路径跳转等。 +ZRouter支持多个拦截器和全局拦截器,在拦截器中可以做页面跳转的拦截,比如跳转前拦截、数据预取、登录拦截,404拦截、埋点、自定义URL路径等等。 #### 全局拦截器 -全局拦截器提供两种使用方式: -- 直接函数回调时的方式; -- 类实现接口的方式(建议使用,功能更全面),支持字面量对象和new创建的对象。 -函数回调的方式,代码示例: - -``` -@Entry -@Component -struct Index { - aboutToAppear(): void { - ZRouter.setGlobalInterceptor((info) => { - if (info.notRegistered) { - return - } - let isLogin = AppStorage.get("isLogin") - if (info.needLogin && !isLogin) { - let param = ZRouter.getParamByName(info.data?.name ?? "") - ZRouter.redirectForResult2("LoginPage", param, (data) => { - if (data.result) { - // 登录成功 - promptAction.showToast({ message: `登录成功` }) - return true // 返回true 则继续跳转登录前的页面 - } - return false - }) - } - }) +代码示例: - } - -} - -``` +```typescript +export class GlobalNavigateInterceptor implements IGlobalNavigateInterceptor{ -类实现接口的方式,代码示例: + static count = 0 + onNavigateBefore: (destInfo: DestinationInfo) => Promise = (destInfo) => { + console.log("IInterceptor Global onNavigateBefore -> ", destInfo.name) + return new Promise((resolve, _) => { + if (destInfo.name === RouterConstants.PAGE_BEFORE_PUSH) { + // 拦截跳转到ParamPage页面 + if (GlobalNavigateInterceptor.count === 0) { + destInfo.param = ' 在拦截器onNavigateBefore中已替换参数 ' + destInfo.next() // 继续跳转 默认的 ,可以不写 + } else if (GlobalNavigateInterceptor.count === 1) { + ToastUtils.show("拦截器onNavigateBefore中已拦截, 再点一次会继续执行") + destInfo.block() // 拦截跳转 + } else if (GlobalNavigateInterceptor.count === 2) { + destInfo.name = RouterConstants.LIFECYCLE_CASE_VIEW + } + GlobalNavigateInterceptor.count += 1 + } + resolve(destInfo) -```typescript + }) + } -export class GlobalNavigateInterceptor implements IGlobalNavigateInterceptor{ - onRootWillShow?: ((fromContext: NavDestinationContext) => void) | undefined = (fromContext) => { + onRootWillShow: ((fromContext: NavDestinationContext) => void) | undefined = (fromContext) => { console.log("IInterceptor Global onRootWillShow: ", fromContext.pathInfo.name) } - onPageWillShow?: ((fromContext: NavDestinationContext, toContext: NavDestinationContext) => void) | undefined = (from ,to)=>{ + onPageWillShow: ((fromContext: NavDestinationContext, toContext: NavDestinationContext) => void) | undefined = (from ,to)=>{ console.log("IInterceptor Global onPageWillShow: ", from, to.pathInfo.name, to.pathInfo.param) } - onNavigate?: ((context: InterceptorInfo) => void) | undefined = (info)=>{ + onNavigate: ((context: InterceptorInfo) => void) | undefined = (info)=>{ if (info.notRegistered) return - console.log("IInterceptor Global onNavigate: ", info.name) - + console.log("IInterceptor Global onNavigate -> ", info.name) let isLogin = AppStorage.get("isLogin") if (info.isNeedLogin && !isLogin) { let param = info.param - ZRouter.redirectForResult2("LoginPage", param, (data) => { - if (data.data) { - // 登录成功 - promptAction.showToast({ message: `登录成功` }) - return true // 返回true 则继续跳转登录前的页面 - } - return false - }) - } + ZRouter.getInstance() + .setParam(param) + .setAnimate(true) + .setPopListener((result) => { + if (result.data) { + // 登录成功 + promptAction.showToast({ message: `登录成功` }) + return true // 返回true 则继续跳转登录前的页面 + } else { + return false + } + }) + .redirect("LoginPage", RedirectType.REPLACE) + } } } @@ -321,7 +330,7 @@ ZRouter.setGlobalInterceptor(new GlobalNavigateInterceptor()) ``` -info.notRegistered()方法判断当前页面是否注册,如果没有注册,将使用ZRouter.redirect() 方法来重定向到404页面;通过ZRouter.redirectForResult() 方法来重定向到登录页面,这个方法接受一个回调函数,该回调函数会在用户登录成功或失败后被调用,在回调函数内部,使用 data.result判断是否登录 ,如果登录成功了给回调函数 return true 来指示继续执行登录前的页面跳转。如果登录失败,或者用户取消登录,回调函数将返回 false,表示不跳转。 +info.notRegistered()方法判断当前页面是否注册,如果没有注册,将使用ZRouter.redirect() 方法来重定向到404页面; 也可以通过redirect() 方法来重定向到登录页面,这个方法接受一个回调函数,该回调函数会在用户登录成功或失败后被调用,在回调函数内部,使用 data.result判断是否登录 ,如果登录成功了给回调函数 return true 来指示继续执行登录前的页面跳转。如果登录失败,或者用户取消登录,回调函数将返回 false,表示不跳转。 登录页面代码示例: @@ -354,9 +363,9 @@ export struct LoginPage{ 在登录成功后通过ZRouter.finishWithResult()方法携带数据关闭页面,会将状态传递给redirectForResult2()方法的回调函数。 -上面是全局拦截器,每个跳转都会触发,如果需要添加多个拦截器,则可以使用setInterceptor()方法。 +上面是全局拦截器,每个跳转都会触发,如果需要添加多个拦截器,则可以使用setInterceptor()方法,但不建议使用。 -#### 自定义拦截器 +#### 自定义拦截器 - 不建议使用 自定义拦截器,首先实现接口IInterceptor,然后使用setInterceptor()方法注册拦截器,,代码示例如下: @@ -475,6 +484,8 @@ export class UrlInterceptor implements IInterceptor { } ``` +**上面的逻辑也可以放在全局拦截器的onNavigateBefore()方法中处理。** + ## 注解上使用静态常量,可跨模块定义 router-register-plugin插件1.0.7版本起,@Route与@Service注解的name属性可使用静态常量,方便统一管理路由名称;静态常量支持当前模块或跨模块定义,常量的定义模版如下: @@ -607,15 +618,22 @@ ZRouter库是对NavPathStack对进行高度封装的,包括了页面跳转、 - 提交代码 - 新建 Pull Request -## 作者其他库 +## 其他库 - 鸿蒙数据库工具:https://gitee.com/HW-Commons/ZDbUtil -- 鸿蒙WebView桥接库:https://github.com/751496032/DSBridge-HarmonyOS +- 鸿蒙H5与原生的通信库:https://github.com/751496032/DSBridge-HarmonyOS - 鸿蒙日志库:https://gitee.com/common-apps/logger +## 计划 + +- 路由拦截器:支持pop拦截 +- 服务路由:模块entry支持动态注册 +- 路由插件:自动生成文件调整 + + ## 联系我们 - **欢迎大家提交issue、PR(可以统一收集问题,方便更多人查阅,会第一时间回复处理)** ,或进群交流(+v: 751496032)。 + **欢迎大家提交issue、PR、需求与建议(可以统一收集问题,方便更多人查阅,会第一时间回复处理)** ,或进群交流(+v: 751496032)。 [![_cgi-bin_mmwebwx-bin_webwxgetmsgimg__MsgID7985482268088807228skeycrypt_4f9ae0b8_0271518ab0cb7cd42bc056451ad75554mmweb_appidwx_webfilehelper.md.jpg](https://www.z4a.net/images/2025/03/12/_cgi-bin_mmwebwx-bin_webwxgetmsgimg__MsgID7985482268088807228skeycrypt_4f9ae0b8_0271518ab0cb7cd42bc056451ad75554mmweb_appidwx_webfilehelper.md.jpg)](https://www.z4a.net/image/ywEHV0) diff --git a/RouterApi/oh-package.json5 b/RouterApi/oh-package.json5 index 4ea0c31b0fed6f68ef7ede990925859d1258d4b1..f02ad183025ed049bebab4032cfbf6ad707ef6dd 100644 --- a/RouterApi/oh-package.json5 +++ b/RouterApi/oh-package.json5 @@ -1,6 +1,6 @@ { "name": "@hzw/zrouter", - "version": "1.3.6", + "version": "1.5.0", "description": "ZRouter是一款轻量级且非侵入性的动态路由框架,可解决HAR/HSP业务模块间的耦合与通信问题,提供了自定义拦截器、转场动画、生命周期函数管理、NavDestination模板化等功能", "main": "Index.ets", "author": "HZWei", diff --git a/RouterApi/src/main/ets/animation/NavAnimationMgr.ets b/RouterApi/src/main/ets/animation/NavAnimationMgr.ets index 74f6f6d09e44b15279ae9f0f7f5a9d425a526fb5..140d31728d38b3a6c18c9c49fa75f96e381aff54 100644 --- a/RouterApi/src/main/ets/animation/NavAnimationMgr.ets +++ b/RouterApi/src/main/ets/animation/NavAnimationMgr.ets @@ -7,6 +7,17 @@ import { NavAnimationModifier } from './modifier/NavAnimationModifier'; import { INavSingleAnimationParam } from './param/INavAnimationParam'; import { NavAnimParamBuilder } from './param/NavAnimParamBuilder'; import { util } from '@kit.ArkTS'; +import { componentSnapshot, display, window } from '@kit.ArkUI'; +import { WindowUtility } from './param/shared/utils/WindowUtility'; +import { WindowUtils } from './param/shared/utils/WindowUtils'; +import { ZRouter } from '../api/Router'; +import { image } from '@kit.ImageKit'; +import { SnapShotImage } from './param/shared/utils/SnapShotImage'; +import { ObjectOrNull } from '../model/Model'; +import { NavDestBuilder } from '../model/NavDestBuilder'; +import { CardSharedAnimationProperties } from './param/shared/CardSharedAnimationProperties'; +import { CardSharedAnimationOptions } from './param/shared/CardSharedAnimationOptions'; +import { CardUtil } from './param/shared/utils/CardUtil'; // 自定义接口,用来保存某个页面相关的转场动画回调和参数 export interface AnimateCallback { @@ -26,6 +37,11 @@ export interface NavAnimatePageInfo { modifier: NavAnimationModifier } +export interface SharedCardParams { + imageRes: ResourceStr, + componentId: string | undefined, +} + /** * 转场动画管理器 */ @@ -67,6 +83,10 @@ export class NavAnimationMgr { return NavAnimationStore.getInstance().buildAnimParam(component, ctx) } + public getSharedCardAnimationProperties(): CardSharedAnimationProperties | undefined { + return NavAnimationStore.getInstance().getSharedCardAnimationProperties() + } + /** * 如果已经注册了路由动画,则获取动画参数构建器,没注册则返回空 * @param modifier @@ -90,6 +110,134 @@ export class NavAnimationMgr { public getAnimCustomNavContentTransition() { return NavAnimationStore.customNavContentTransition } + + public initSharedAnim(windowStage: window.WindowStage) { + NavAnimationStore.getInstance().initSharedAnim(windowStage) + } + + public setNavigation(navigation: NavDestBuilder): NavAnimationStore { + return NavAnimationStore.getInstance().setNavigation(navigation) + } + + public withSharedCardParams(param: SharedCardParams): NavAnimationStore { + return NavAnimationStore.getInstance().withSharedCardParams(param) + } + + /** + * 页面跳转 + * @param name 页面路由名称 + */ + public push(name: string) { + NavAnimationStore.getInstance().push(name) + } + + public sharedCard(): NavSharedCardAnimMgr { + return new NavSharedCardAnimMgr() + } +} + +/** + * 卡片一镜到底转场动画管理器 + */ +export class NavSharedCardAnimMgr { + private _navBgColor: ResourceColor | undefined = undefined + private _isAdaptImmersive: boolean = false + + /** + * 注册卡片一镜到底转场动画 + * @param component + * @param ctx + * @returns + */ + public registerSharedCardAnimParam(component: object, ctx: NavDestinationContext): NavAnimParamBuilder { + return NavAnimationStore.getInstance() + .buildSharedCardAnimParam(component, ctx, this._navBgColor, this._isAdaptImmersive) + } + + /** + * 取消注册卡片一镜到底转场动画 + * @param component + */ + public unregisterSharedCardAnim(component: object) { + NavAnimationStore.getInstance().unregisterSharedCardAnim(component) + } + + /** + * 获取卡片图片资源 + * @returns + */ + public getSharedUrl(): ResourceStr { + let imageResource = ZRouter.getParamByKey(CardUtil.KEY_IMAGE_RESOURCE) as ResourceStr; + return imageResource; + } + + /** + * 获取卡片点击的组件id + * @returns + */ + public getSharedComponentId(): string | undefined { + let prePageCardId = ZRouter.getParamByKey(CardUtil.KEY_CLICKED_COMPONENT_ID) as string; + return CardUtil.getPostPageImageId(prePageCardId); + } + + /** + * 获取屏幕宽度 + * @returns + */ + public getWindowWidthPx(): number { + return WindowUtils.windowWidth_px + } + + /** + * 获取屏幕高度 + * @returns + */ + public getWindowHeightPx(): number { + return WindowUtils.windowHeight_px + } + + /** + * 获取顶部安全区域高度 + * @returns + */ + public getTopAvoidAreaHeightPx(): number { + return WindowUtils.topAvoidAreaHeight_px + } + + /** + * 获取导航栏高度 + * @returns + */ + public getNavigationIndicatorHeightPx(): number { + return WindowUtils.navigationIndicatorHeight_px + } + + /** + * 获取控件截图 + * @returns + */ + public getPixelMap(): image.PixelMap | undefined { + return SnapShotImage.pixelMap + } + + /** + * 设置背景色 + * @param color 颜色 + * @returns + */ + public setNavBgColor(color: ResourceColor) { + this._navBgColor = color + return this + } + + /** + * 设置是否适配沉浸式,如果页面是沉浸式,则设置为true + * @param b + */ + public setIsAdaptImmersive(b: boolean) { + this._isAdaptImmersive = b + return this + } } /** @@ -105,6 +253,11 @@ export class NavAnimationStore { // 根据 pageId 设置/获取 转场动画 private customTransitionMap: Map = new Map(); private _defaultCallback?: AnimateCallback + private currentBreakPoint: string = ''; + private navigation: NavDestBuilder | undefined = undefined + private cardSharedAnimPropMap: Map = new Map(); + private curComponent: Object | undefined + private sharedCardParams: SharedCardParams | undefined = undefined static getInstance() { return NavAnimationStore.delegate; @@ -175,6 +328,12 @@ export class NavAnimationStore { return NavAnimParamBuilder.builder(callback) } + getSharedCardAnimationProperties(): CardSharedAnimationProperties | undefined { + if (!this.curComponent) { + return undefined + } + return this.cardSharedAnimPropMap.get(this.getKey(this.curComponent)) + } /** * 如果已经注册了路由动画,则获取动画参数构建器,没注册则返回空 @@ -341,6 +500,103 @@ export class NavAnimationStore { } } + initSharedAnim(windowStage: window.WindowStage) { + WindowUtility.setWindow(windowStage) + WindowUtils.window = windowStage.getMainWindowSync(); + WindowUtils.windowWidth_px = WindowUtils.window.getWindowProperties().windowRect.width; + WindowUtils.windowHeight_px = WindowUtils.window.getWindowProperties().windowRect.height; + this.updateBreakpoint(WindowUtils.windowWidth_px); + let avoidArea = WindowUtils.window.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM); + WindowUtils.topAvoidAreaHeight_px = avoidArea.topRect.height; + let navigationArea = WindowUtils.window.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR); + WindowUtils.navigationIndicatorHeight_px = navigationArea.bottomRect.height; + try { + WindowUtils.window.on('windowSizeChange', (data) => { + WindowUtils.windowWidth_px = data.width; + WindowUtils.windowHeight_px = data.height; + this.updateBreakpoint(data.width); + }) + WindowUtils.window.on('avoidAreaChange', (data) => { + if (data.type === window.AvoidAreaType.TYPE_SYSTEM) { + let topRectHeight = data.area.topRect.height; + WindowUtils.topAvoidAreaHeight_px = topRectHeight; + } else if (data.type === window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) { + let bottomRectHeight = data.area.bottomRect.height; + WindowUtils.navigationIndicatorHeight_px = bottomRectHeight; + } + }) + } catch (exception) { + console.error(exception) + } + } + + updateBreakpoint(width: number) { + let windowWidthVp = width / (display.getDefaultDisplaySync().densityDPI / 160); + let newBreakPoint: string = ''; + if (windowWidthVp < 400) { + newBreakPoint = 'xs'; + } else if (windowWidthVp < 600) { + newBreakPoint = 'sm'; + } else if (windowWidthVp < 800) { + newBreakPoint = 'md'; + } else { + newBreakPoint = 'lg'; + } + if (this.currentBreakPoint !== newBreakPoint) { + this.currentBreakPoint = newBreakPoint; + // Record the current breakpoint value using the state variable. + AppStorage.setOrCreate('currentBreakpoint', this.currentBreakPoint); + } + } + + public withSharedCardParams(param: SharedCardParams): NavAnimationStore { + this.sharedCardParams = param + return this + } + + public setNavigation(navigation: NavDestBuilder): NavAnimationStore { + this.navigation = navigation + return this + } + + buildSharedCardAnimParam( + component: object, + ctx: NavDestinationContext, + navBgColor: ResourceColor | undefined, + isAdaptImmersive: boolean + ): NavAnimParamBuilder { + const prop = new CardSharedAnimationProperties() + prop.registerSharedCardAnim() + this.cardSharedAnimPropMap.set(this.getKey(component), prop) + this.curComponent = component + return ZRouter.animateMgr() + .registerAnimParam(component, ctx) + .addAnimateOptions(new CardSharedAnimationOptions(prop, navBgColor, isAdaptImmersive)); + } + + unregisterSharedCardAnim(component: object) { + this.unregisterAnim(component) + this.cardSharedAnimPropMap.delete(this.getKey(component)) + } + + push(name: string) { + const param = this.sharedCardParams + if (!param) { + return + } + componentSnapshot.get(param.componentId, (error: Error, pixelMap: image.PixelMap) => { + if (error) { + (this.navigation ?? ZRouter.getInstance()).push(name) + } else { + SnapShotImage.pixelMap = pixelMap; + (this.navigation ?? ZRouter.getInstance()) + .withParam(CardUtil.KEY_IMAGE_RESOURCE, param.imageRes) + .withParam(CardUtil.KEY_CLICKED_COMPONENT_ID, param.componentId) + .push(name) + } + }) + } + // 自定义转场动画回调,在Navigation().customNavContentTransition()设置 static customNavContentTransition = (from: NavContentInfo, to: NavContentInfo, operation: NavigationOperation) => { // if (from.mode == NavDestinationMode.DIALOG || to.mode == NavDestinationMode.DIALOG) { diff --git a/RouterApi/src/main/ets/animation/modifier/NavAnimationModifier.ets b/RouterApi/src/main/ets/animation/modifier/NavAnimationModifier.ets index 35b8723a08d79f8abac3cd5b0befdc5bde271afc..8fd16a512b34fc556b05163a7906ac1f8c1584b6 100644 --- a/RouterApi/src/main/ets/animation/modifier/NavAnimationModifier.ets +++ b/RouterApi/src/main/ets/animation/modifier/NavAnimationModifier.ets @@ -1,4 +1,4 @@ -import { AttributeUpdater } from '@kit.ArkUI' +import { AttributeUpdater } from '@kit.ArkUI'; /** * @author: HHBin @@ -6,4 +6,10 @@ import { AttributeUpdater } from '@kit.ArkUI' * @desc: 导航动态属性设置 */ export class NavAnimationModifier extends AttributeUpdater { +} + +export class StackAnimationModifier extends AttributeUpdater { +} + +export class ImageAnimationModifier extends AttributeUpdater { } \ No newline at end of file diff --git a/RouterApi/src/main/ets/animation/param/shared/CardSharedAnimationOptions.ets b/RouterApi/src/main/ets/animation/param/shared/CardSharedAnimationOptions.ets new file mode 100644 index 0000000000000000000000000000000000000000..b70b233ee07680724364a9aba29106fc2515e65f --- /dev/null +++ b/RouterApi/src/main/ets/animation/param/shared/CardSharedAnimationOptions.ets @@ -0,0 +1,131 @@ +import NavAnimateStatus from '../../const/NavAnimateStatus'; +import { NavAnimationModifier } from '../../modifier/NavAnimationModifier'; +import { INavAnimateOptions } from '../INavAnimateOptions'; +import { CardSharedAnimationProperties } from './CardSharedAnimationProperties'; + +/** + * @author: HHBin + * @date: 2025/4/28 + * @desc: 卡片一镜到底 + */ + +export class CardSharedAnimationOptions implements INavAnimateOptions { + _isBeforePage: boolean = false; + p: CardSharedAnimationProperties + _navBgColor: ResourceColor | undefined + _IsAdaptImmersive: boolean = false + + constructor( + longTakeAnimationProperties: CardSharedAnimationProperties, + bgColor: ResourceColor | undefined, + isAdaptImmersive: boolean + ) { + this.p = longTakeAnimationProperties + this._navBgColor = bgColor + this._IsAdaptImmersive = isAdaptImmersive + } + + updateModifier(modifier: NavAnimationModifier, isStart: boolean, status: NavAnimateStatus): void { + if (!modifier.attribute + || !this.p.stack1Modifier.attribute + || !this.p.stack2Modifier.attribute + || !this.p.stack3Modifier.attribute + || !this.p.imageModifier.attribute + ) { + return + } + switch (status) { + case NavAnimateStatus.PUSH_ENTER: + if (!this._isBeforePage) { + if (isStart) { + this.p.initParams(this._IsAdaptImmersive); + modifier.attribute.backgroundColor(Color.Transparent) + this.p.stack1Modifier.attribute + .scale({ + x: this.p.initScale, + y: this.p.initScale + }) + .translate({ + x: this.p.initTranslateX, + y: this.p.initTranslateY, + }) + .width(this.p.initClipWidth) + .height(this.p.initClipHeight) + .borderRadius(34) + this.p.stack2Modifier.attribute + .position({ + x: this.p.initPositionValueX, + y: this.p.initPositionValueY + }) + this.p.imageModifier.attribute + .size(this.p.snapShotSize) + .position({ + x: this.p.snapShotPositionX, + y: this.p.snapShotPositionY + }) + this.p.imageModifier.attribute.opacity(1.0) + animateTo({ + delay: 50, + duration: 200, + curve: Curve.Sharp + }, () => { + this.p.imageModifier.attribute?.opacity(0.0) + }); + this.p.stack3Modifier.attribute.opacity(0.0) + animateTo({ + duration: 200, + curve: Curve.Sharp + }, () => { + this.p.stack3Modifier.attribute?.opacity(1.0) + }); + } else { + modifier.attribute.backgroundColor(this._navBgColor ?? Color.White) + this.p.stack1Modifier.attribute + .scale({ + x: 1.0, + y: 1.0 + }) + .translate({ + x: 0, + y: 0 + }) + .width('100%') + .height('100%') + .borderRadius(0) + this.p.stack2Modifier.attribute + .position({ + x: 0, + y: 0 + }) + } + } + break + case NavAnimateStatus.POP_EXIT: + if (!this._isBeforePage) { + if (isStart) { + } else { + modifier.attribute.backgroundColor(Color.Transparent) + this.p.stack1Modifier.attribute + .scale({ + x: this.p.initScale, + y: this.p.initScale + }) + .translate({ + x: this.p.initTranslateX, + y: this.p.initTranslateY + }) + .width(this.p.initClipWidth) + .height(this.p.initClipHeight) + .borderRadius(16) + this.p.stack2Modifier.attribute + .position({ + x: this.p.initPositionValueX, + y: this.p.initPositionValueY + }) + } + } + break + } + } +} + \ No newline at end of file diff --git a/RouterApi/src/main/ets/animation/param/shared/CardSharedAnimationProperties.ets b/RouterApi/src/main/ets/animation/param/shared/CardSharedAnimationProperties.ets new file mode 100644 index 0000000000000000000000000000000000000000..6e1762e36ffd1d87bd4094086395a08db1c170ba --- /dev/null +++ b/RouterApi/src/main/ets/animation/param/shared/CardSharedAnimationProperties.ets @@ -0,0 +1,80 @@ +import { CardUtil } from './utils/CardUtil'; +import { WindowUtils } from './utils/WindowUtils'; +import { ComponentAttrUtils, RectInfoInPx } from './utils/ComponentAttrUtils'; +import { ZRouter } from '../../../api/Router'; +import { ImageAnimationModifier, StackAnimationModifier } from '../../modifier/NavAnimationModifier'; + +/** + * @author: HHBin + * @date: 2025/4/28 + * @desc: + */ +export class CardSharedAnimationProperties { + public stack1Modifier: StackAnimationModifier = new StackAnimationModifier(); + public stack2Modifier: StackAnimationModifier = new StackAnimationModifier(); + public stack3Modifier: StackAnimationModifier = new StackAnimationModifier(); + public imageModifier: ImageAnimationModifier = new ImageAnimationModifier(); + public snapShotSize: SizeOptions = { width: '100%', height: '100%' }; + public snapShotPositionX: number = 0; + public snapShotPositionY: number = 0; + public animationCount: number = 0; + public initScale: number = 1; + public initTranslateX: number = 0; + public initTranslateY: number = 0; + public initClipWidth: Dimension = 0; + public initClipHeight: Dimension = 0; + public initPositionValueX: number = 0; + public initPositionValueY: number = 0; + public cardItemInfo_px: RectInfoInPx = new RectInfoInPx(); + public clickedCardId: string = ''; + + public registerSharedCardAnim(): void { + let prePageCardId = ZRouter.getParamByKey(CardUtil.KEY_CLICKED_COMPONENT_ID) as string; + if (prePageCardId) { + this.clickedCardId = prePageCardId; + this.cardItemInfo_px = ComponentAttrUtils.getRectInfoById(WindowUtils.window.getUIContext(), prePageCardId); + } + } + + public initParams(isAdaptImmersive: boolean): void { + let postNode = WindowUtils.window.getUIContext().getFrameNodeById(CardUtil.getPostPageImageId(this.clickedCardId)); + let postNodePositionX_vp: number = 0; + let postNodePositionY_vp: number = 0; + let postNodeWidth_px: number = 0; + let postNodeHeight_px: number = 0; + + if (postNode) { + postNodePositionX_vp = postNode.getPositionToWindowWithTransform().x; + postNodePositionY_vp = postNode.getPositionToWindowWithTransform().y; + postNodeWidth_px = postNode.getMeasuredSize().width; + postNodeHeight_px = postNode.getMeasuredSize().height; + } + + this.initPositionValueX = -postNodePositionX_vp; + this.initPositionValueY = -postNodePositionY_vp + (isAdaptImmersive ? 0 : px2vp(WindowUtils.topAvoidAreaHeight_px)); + this.snapShotPositionY = postNodePositionY_vp - (isAdaptImmersive ? 0 : px2vp(WindowUtils.topAvoidAreaHeight_px)); + this.snapShotPositionX = postNodePositionX_vp; + + // 计算卡片的宽高与窗口的宽高之比。 + this.initScale = this.cardItemInfo_px.width / postNodeWidth_px; + if (!CardUtil.isLargeSize()) { + this.initTranslateX = px2vp(this.cardItemInfo_px.left - + (WindowUtils.windowWidth_px - this.cardItemInfo_px.width) / 2); + this.initClipWidth = '100%'; + this.initClipHeight = px2vp((this.cardItemInfo_px.height) / this.initScale); + this.snapShotSize = { width: '100%' }; + this.initTranslateY = px2vp(this.cardItemInfo_px.top - + ((vp2px(this.initClipHeight) - vp2px(this.initClipHeight) * this.initScale) / 2) - + (isAdaptImmersive ? 0 : WindowUtils.topAvoidAreaHeight_px)); + } else { + this.initClipHeight = px2vp(this.cardItemInfo_px.height / this.initScale); + this.initTranslateY = px2vp(this.cardItemInfo_px.top - + ((vp2px(this.initClipHeight) - vp2px(this.initClipHeight) * this.initScale) / 2) - + (isAdaptImmersive ? 0 : WindowUtils.topAvoidAreaHeight_px)); + this.initClipWidth = px2vp((this.cardItemInfo_px.width) / this.initScale); + this.snapShotSize = { width: px2vp(postNodeWidth_px) }; + this.initTranslateX = px2vp(this.cardItemInfo_px.left - + (WindowUtils.windowWidth_px / 2 - this.cardItemInfo_px.width / 2)); + } + } +} \ No newline at end of file diff --git a/RouterApi/src/main/ets/animation/param/shared/components/SharedCardContainer.ets b/RouterApi/src/main/ets/animation/param/shared/components/SharedCardContainer.ets new file mode 100644 index 0000000000000000000000000000000000000000..650d393f3b149f1526b518367f23f7b7f2e3f748 --- /dev/null +++ b/RouterApi/src/main/ets/animation/param/shared/components/SharedCardContainer.ets @@ -0,0 +1,64 @@ +import { ZRouter } from '../../../../api/Router'; +import { CardSharedAnimationProperties } from '../CardSharedAnimationProperties'; + +/** + * @author: HHBin + * @date: 2025/4/28 + * @desc: + */ +@Component +export struct SharedCardContainer { + @State properties: CardSharedAnimationProperties | undefined = undefined; + @Prop bgColor: ResourceColor = Color.White + @BuilderParam content: () => void = this.builder + + aboutToAppear(): void { + this.properties = ZRouter.animateMgr().getSharedCardAnimationProperties(); + } + + @Builder + builder() { + } + + build() { + if (this.properties) { + // Stack组件的alignContent设置为TopStart,否则在高度变化时,截图和内容会重新定位。 + Stack({ alignContent: Alignment.TopStart }) { + Stack({ alignContent: Alignment.TopStart }) { + // 显示前一页点击的卡片的截图 + Image(ZRouter.animateMgr().sharedCard().getPixelMap()) + .objectFit(ImageFit.Auto) + .syncLoad(true)// 显示前一页点击的卡片的截图 + .attributeModifier(this.properties.imageModifier) + + // 原始弹出页显示内容,添加透明度控制其动画显示。 + Stack() { + Column() { + Column() { + this.content() + } + .size({ + width: px2vp(ZRouter.animateMgr().sharedCard().getWindowWidthPx()), + height: px2vp(ZRouter.animateMgr().sharedCard().getWindowHeightPx()) + }) + } + .width('100%') + .height('100%') + } + .size({ + width: '100%', + height: '100%' + }) + .attributeModifier(this.properties.stack3Modifier) + } + .width('100%') + .attributeModifier(this.properties.stack2Modifier) + } + .clip(true) + .backgroundColor(this.bgColor) + .attributeModifier(this.properties.stack1Modifier) + } else { + this.content() + } + } +} \ No newline at end of file diff --git a/RouterApi/src/main/ets/animation/param/shared/utils/CardUtil.ets b/RouterApi/src/main/ets/animation/param/shared/utils/CardUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..b49123aaea4e6935a175f7f2a89f9a82d9d8e516 --- /dev/null +++ b/RouterApi/src/main/ets/animation/param/shared/utils/CardUtil.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class CardUtil { + // 一个卡片镜像被其他图片关联,弹出页上的图片需要标记id,在此统一设置id + public static getPostPageImageId(prePageClickedCardId: string | undefined): string | undefined { + if (!prePageClickedCardId) { + return undefined; + } + return 'Post_Page_Image' + prePageClickedCardId; + } + + public static isLargeSize(): boolean { + let currentBreakPoint: string | undefined = AppStorage.get('currentBreakpoint'); + return (currentBreakPoint === 'md' || currentBreakPoint === 'lg'); + } + public static KEY_IMAGE_RESOURCE = "Post_Page_Image_imageResource" + public static KEY_CLICKED_COMPONENT_ID = "Post_Page_Image_clickedComponentId" +} \ No newline at end of file diff --git a/RouterApi/src/main/ets/animation/param/shared/utils/ComponentAttrUtils.ets b/RouterApi/src/main/ets/animation/param/shared/utils/ComponentAttrUtils.ets new file mode 100644 index 0000000000000000000000000000000000000000..4c001978fa69d392f9fb47c9ea10aa2c7c5e0a9c --- /dev/null +++ b/RouterApi/src/main/ets/animation/param/shared/utils/ComponentAttrUtils.ets @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { componentUtils, UIContext } from '@kit.ArkUI'; +import { JSON } from '@kit.ArkTS'; +import { hilog } from '@kit.PerformanceAnalysisKit'; + +export class CustomExceptions { + public static EMPTY_OBJECT_EXCEPTION = new Error('object is empty'); + public static CUSTOM_TRANSITION_EXCEPTION = new Error('custom transition exception'); +} + +export class ComponentAttrUtils { + // 获取组件id对应的位置信息 + public static getRectInfoById(context: UIContext, id: string): RectInfoInPx { + if (!context || !id) { + throw CustomExceptions.EMPTY_OBJECT_EXCEPTION; + } + let componentInfo: componentUtils.ComponentInfo = context.getComponentUtils().getRectangleById(id); + + hilog.info(0x0000, 'ComponentAttrUtils', 'the value is ' + JSON.stringify(componentInfo)); + + if (!componentInfo) { + throw CustomExceptions.EMPTY_OBJECT_EXCEPTION; + } + + let rstRect: RectInfoInPx = new RectInfoInPx(); + const widthScaleGap = componentInfo.size.width * (1 - componentInfo.scale.x) / 2; + const heightScaleGap = componentInfo.size.height * (1 - componentInfo.scale.y) / 2; + rstRect.left = componentInfo.translate.x + componentInfo.screenOffset.x + widthScaleGap; + rstRect.top = componentInfo.translate.y + componentInfo.screenOffset.y + heightScaleGap; + rstRect.right = componentInfo.translate.x + componentInfo.screenOffset.x + componentInfo.size.width - widthScaleGap; + rstRect.bottom = + componentInfo.translate.y + componentInfo.screenOffset.y + componentInfo.size.height - heightScaleGap; + rstRect.width = rstRect.right - rstRect.left; + rstRect.height = rstRect.bottom - rstRect.top; + return { + left: rstRect.left, + right: rstRect.right, + top: rstRect.top, + bottom: rstRect.bottom, + width: rstRect.width, + height: rstRect.height + }; + } +} + +export class RectInfoInPx { + left: number = 0; + top: number = 0; + right: number = 0; + bottom: number = 0; + width: number = 0; + height: number = 0; +} + +export class RectJson { + $rect: Array = []; +} \ No newline at end of file diff --git a/RouterApi/src/main/ets/animation/param/shared/utils/SnapShotImage.ets b/RouterApi/src/main/ets/animation/param/shared/utils/SnapShotImage.ets new file mode 100644 index 0000000000000000000000000000000000000000..328979e30d8f25696bcd642d61c02e2410a35a15 --- /dev/null +++ b/RouterApi/src/main/ets/animation/param/shared/utils/SnapShotImage.ets @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { image } from '@kit.ImageKit'; + +export class SnapShotImage { + public static pixelMap: image.PixelMap | undefined = undefined; +} \ No newline at end of file diff --git a/RouterApi/src/main/ets/animation/param/shared/utils/WindowUtility.ets b/RouterApi/src/main/ets/animation/param/shared/utils/WindowUtility.ets new file mode 100644 index 0000000000000000000000000000000000000000..f2145f51575dc66239d734dbad285355dee552c6 --- /dev/null +++ b/RouterApi/src/main/ets/animation/param/shared/utils/WindowUtility.ets @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { display, window } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; + +export class WindowUtility { + private static isSet: boolean = false; + private static windowStage: window.WindowStage; + private static mainWindow: window.Window; + + public static setWindow(windowStage: window.WindowStage): void { + WindowUtility.windowStage = windowStage; + WindowUtility.mainWindow = windowStage.getMainWindowSync(); + WindowUtility.isSet = true; + } + + public static getWindowStage(): window.WindowStage { + if (!WindowUtility.isSet) { + hilog.info(0x0000, 'WindowUtility', '[getWindowStage] WindowUtil not set'); + } + return WindowUtility.windowStage; + } + + public static getWindow(): window.Window { + if (!WindowUtility.isSet) { + hilog.info(0x0000, 'WindowUtility', '[getWindow] WindowUtil not set'); + } + return WindowUtility.mainWindow; + } + + public static getWindowWidth(): number { + if (!WindowUtility.isSet) { + hilog.info(0x0000,'WindowUtility', '[getWindowWidth] WindowUtil not set'); + } + return px2vp(WindowUtility.mainWindow.getWindowProperties().windowRect.width); + } + + public static getWindowHeight(): number { + if (!WindowUtility.isSet) { + hilog.info(0x0000, 'WindowUtility', '[getWindowHeight] WindowUtil not set'); + } + return px2vp(WindowUtility.mainWindow.getWindowProperties().windowRect.height); + } + + public static getStatusHeight(): number { + if (!WindowUtility.isSet) { + hilog.info(0x0000, 'WindowUtility', '[getStatusHeight] WindowUtil not set'); + } + return px2vp(WindowUtility.mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect.height); + } + + public static getNavHeight(): number { + if (!WindowUtility.isSet) { + hilog.info(0x0000, 'WindowUtility', '[getNavHeight] WindowUtil not set'); + } + return px2vp(WindowUtility.mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) + .bottomRect.height); + } + + public static setWindowOrientation(context: Context, orientation: window.Orientation) { + // context: getContext(this) + if (!WindowUtility.isSet) { + hilog.info(0x0000, 'WindowUtility', '[setWindowColorDeep] WindowUtil not set'); + return; + } + // getLastWindow gets the topmost subwindow in the current application. If no subwindow is applied, it returns to the main application window. + window.getLastWindow(context).then((lastWindow) => { + lastWindow.setPreferredOrientation(orientation).then(() => { + hilog.info(0x0000, 'WindowUtility', `setPreferredOrientation to ${orientation} success`); + }).catch((error: BusinessError) => { + hilog.info(0x0000, 'WindowUtility', `setPreferredOrientation to ${orientation} failure` + JSON.stringify(error)); + }) + }).catch((error: BusinessError) => { + hilog.info(0x0000, 'WindowUtility', 'getLastWindow error: ' + JSON.stringify(error)); + }) + } + + public static getDisplayOrientation() { + if (!WindowUtility.isSet) { + hilog.info(0x0000, 'WindowUtility', '[setWindowColorDeep] WindowUtil not set'); + return; + } + let orientation: display.Orientation = display.getDefaultDisplaySync().orientation + switch (orientation) { + case display.Orientation.PORTRAIT: + hilog.info(0x0000, 'WindowUtility', '[getDisplayOrientation] display.Orientation.PORTRAIT'); + break; + case display.Orientation.LANDSCAPE: + hilog.info(0x0000, 'WindowUtility', '[getDisplayOrientation] display.Orientation.LANDSCAPE'); + break; + case display.Orientation.PORTRAIT_INVERTED: + hilog.info(0x0000, 'WindowUtility', '[getDisplayOrientation] display.Orientation.PORTRAIT_INVERTED'); + break; + case display.Orientation.LANDSCAPE_INVERTED: + hilog.info(0x0000, 'WindowUtility', '[getDisplayOrientation] display.Orientation.LANDSCAPE_INVERTED'); + break; + default: + hilog.info(0x0000, 'WindowUtility', `[getDisplayOrientation] ${orientation}`); + } + } + + public static getWindowOrientation() { + if (!WindowUtility.isSet) { + hilog.info(0x0000, 'WindowUtility', '[setWindowColorDeep] WindowUtil not set'); + return; + } + let orientation: window.Orientation = WindowUtility.mainWindow.getPreferredOrientation() + + switch (orientation) { + case window.Orientation.PORTRAIT: + hilog.info(0x0000, 'WindowUtility', '[getWindowOrientation] window.Orientation.PORTRAIT'); + break; + case window.Orientation.LANDSCAPE: + hilog.info(0x0000, 'WindowUtility', '[getWindowOrientation] window.Orientation.LANDSCAPE'); + break; + case window.Orientation.PORTRAIT_INVERTED: + hilog.info(0x0000, 'WindowUtility', '[getWindowOrientation] window.Orientation.PORTRAIT_INVERTED'); + break; + case window.Orientation.LANDSCAPE_INVERTED: + hilog.info(0x0000, 'WindowUtility', '[getWindowOrientation] window.Orientation.LANDSCAPE_INVERTED'); + break; + default: + hilog.info(0x0000, 'WindowUtility', `[getWindowOrientation] ${orientation}`); + } + } + + public static setWindowColorDeep() { + if (!WindowUtility.isSet) { + hilog.info(0x0000, 'WindowUtility', '[setWindowColorDeep] WindowUtil not set'); + return; + } + let sysBarProps: window.SystemBarProperties = { + statusBarColor: '#000000', + navigationBarColor: '#000000', + statusBarContentColor: '#ffffff', + navigationBarContentColor: '#ffffff' + }; + WindowUtility.mainWindow.setWindowSystemBarProperties(sysBarProps); + try { + WindowUtility.mainWindow.setWindowBackgroundColor('#000000'); + } catch (err) { + hilog.error(0x0000, 'WindowUtility', + '[setWindowColorDeep] Failed to setWindowBackgroundColor. Cause: ' + JSON.stringify(err)); + } + } + + public static setWindowColorWhite() { + if (!WindowUtility.isSet) { + hilog.info(0x0000, 'WindowUtility', '[setWindowColorWhite] WindowUtil not set'); + return; + } + let sysBarProps: window.SystemBarProperties = { + statusBarColor: '#ffffff', + navigationBarColor: '#ffffff', + statusBarContentColor: '#000000', + navigationBarContentColor: '#000000' + }; + WindowUtility.mainWindow.setWindowSystemBarProperties(sysBarProps); + try { + WindowUtility.mainWindow.setWindowBackgroundColor('#ffffff'); + } catch (err) { + hilog.error(0x0000, 'WindowUtility', + '[setWindowColorWhite] Failed to setWindowBackgroundColor. Cause: ' + JSON.stringify(err)); + } + } + + public static setWindowFullScreen(isLayoutFullScreen: boolean) { + if (!WindowUtility.isSet) { + hilog.info(0x0000, 'WindowUtility', '[setWindowFullScreen] WindowUtil not set'); + return; + } + + WindowUtility.mainWindow.setWindowLayoutFullScreen(isLayoutFullScreen) + .then(() => { + hilog.info(0x0000, 'WindowUtility', 'Succeeded in setting the window layout to full-screen mode.'); + }) + .catch((err: BusinessError) => { + hilog.error(0x0000, 'WindowUtility', + `Failed to set the window layout to full-screen mode. Code is ${err.code}, message is ${err.message}`); + }); + } + + public static SetSpecificSystemBarEnabled(name: window.SpecificSystemBar, enable: boolean, + enableAnimation: boolean) { + if (!WindowUtility.isSet) { + hilog.info(0x0000, 'WindowUtility', '[setWindowSpecificSystemBarEnabled] WindowUtil not set'); + return; + } + let info = 'invisible'; + if (enable) { + info = 'visible'; + } + if (canIUse('SystemCapability.Window.SessionManager')) + WindowUtility.mainWindow.setSpecificSystemBarEnabled(name, enable, enableAnimation) + .then(() => { + hilog.info(0x0000, 'WindowUtility', `Succeeded in setting the ${name} bar to be ${info}.`); + }) + .catch((err: BusinessError) => { + hilog.error(0x0000, 'WindowUtility', + `Failed to set the ${name} bar to be invisible. Code is ${err.code}, message is ${err.message}`); + }); + } +} + + + + diff --git a/RouterApi/src/main/ets/animation/param/shared/utils/WindowUtils.ets b/RouterApi/src/main/ets/animation/param/shared/utils/WindowUtils.ets new file mode 100644 index 0000000000000000000000000000000000000000..242e7b5923f144fd4553628a823d4d190f34d49b --- /dev/null +++ b/RouterApi/src/main/ets/animation/param/shared/utils/WindowUtils.ets @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { window } from '@kit.ArkUI'; + +export class WindowUtils { + public static window: window.Window; + + public static windowWidth_px: number; + + public static windowHeight_px: number; + + public static topAvoidAreaHeight_px: number; + + public static navigationIndicatorHeight_px: number; +} \ No newline at end of file diff --git a/RouterApi/src/main/ets/annotation/Route.ets b/RouterApi/src/main/ets/annotation/Route.ts similarity index 79% rename from RouterApi/src/main/ets/annotation/Route.ets rename to RouterApi/src/main/ets/annotation/Route.ts index 9284bfdedbde1f7e68385ea26b66deb2e4987ad5..6b557feb9867f7f2e372251030a172d88b35609f 100644 --- a/RouterApi/src/main/ets/annotation/Route.ets +++ b/RouterApi/src/main/ets/annotation/Route.ts @@ -4,24 +4,24 @@ * @desc: NavDestination页面注解 */ -export function Route(param: Param) { - return Object +export interface RouteDecorator extends ClassDecorator { + (param?: Param): ClassDecorator } +export declare const Route: RouteDecorator + /** * 可以替代@Route * @param param * @returns */ -export function ZRoute(param: Param){ - return Object -} +export declare const ZRoute: RouteDecorator interface Param { /** * 页面路由名称 */ - name: string, + name?: string, /** * 是否使用NavDestination模板,true时,则页面组件可以不用NavDestination组件包裹,在编译阶段会自动生成NavDestination组件。 * 可选,默认不使用 @@ -45,7 +45,7 @@ interface Param { /** * 页面生命周期函数属性名称,可选,只针对NavDestination页面模板 */ - loAttributeName ?: string + loAttributeName?: string /** * 页面描述,可选 @@ -56,5 +56,5 @@ interface Param { */ needLogin?: boolean extra?: string + param?: Record } - diff --git a/RouterApi/src/main/ets/api/NavStackMgr.ets b/RouterApi/src/main/ets/api/NavStackMgr.ets index 7f80e5cc0b01987a1e65708e76abe46cfe8d310c..e95d14a0a7df0247ccfe0127c3c64f77ef44cab5 100644 --- a/RouterApi/src/main/ets/api/NavStackMgr.ets +++ b/RouterApi/src/main/ets/api/NavStackMgr.ets @@ -49,7 +49,7 @@ export default class NavStackMgr { if (this.navStackMap.has(this._currentStackName!)) { const name = this._currentStackName! // this._currentStackName = undefined - return name + return name ?? DEFAULT_STACK_NAME } else { throw new Error(`stack name does not exist: 【 ${this._currentStackName} 】,please call method register: 【 registerNavStack 】`) } @@ -123,6 +123,7 @@ export default class NavStackMgr { let fromContext: NavDestinationContext let toContext: NavDestinationContext const interceptorInfo = InterceptorInfo.create(false) + interceptorInfo.operation = operation const isPushOrReplace = operation === NavigationOperation.PUSH || operation === NavigationOperation.REPLACE if (isPushOrReplace) { try { @@ -192,6 +193,9 @@ export default class NavStackMgr { public addRouterInfo(routerInfo: RouterInfo) { const name = this.currentStackName + if (ObjUtil.isEmpty(name)) { + return + } let list = this.navDesMap.get(name) if (list) { list.add(routerInfo) diff --git a/RouterApi/src/main/ets/api/Router.ets b/RouterApi/src/main/ets/api/Router.ets index 76eed084a0375e6bc958490945646837fb639928..c0f44a5868554c287e7a0ea4b6c0430057fc3840 100644 --- a/RouterApi/src/main/ets/api/Router.ets +++ b/RouterApi/src/main/ets/api/Router.ets @@ -24,6 +24,7 @@ import { NavAnimationMgr } from '../animation/NavAnimationMgr' import { ILifecycleObserver } from '../lifecycle/ILifecycleObserver' import { LifecycleObserver } from '../lifecycle/LifecycleEvent' import { TemplateMgr } from '../template/TemplateMgr' +import { LifecycleMgr } from '../lifecycle/LifecycleMgr' const KEY_Z_ROUTER = 'ZRouter' @@ -242,16 +243,16 @@ export class ZRouter { * @param observer * @returns */ - public static addGlobalLifecycleObserver(observer: IL) { - ZRouter.getRouterMgr().lifecycleMgr.addGlobalObserver(observer) + public static addGlobalLifecycleObserver(observer: IL): LifecycleMgr { + return ZRouter.getRouterMgr().lifecycleMgr.addGlobalObserver(observer) } /** * 添加单个NavDestination页面的生命周期观察者,在NavDestination页面模版模式下才会生效 * @param observer */ - public static addLifecycleObserver(observer: LifecycleObserver) { - TemplateMgr.getInstance().addObserver(observer) + public static addLifecycleObserver(observer: LifecycleObserver): TemplateMgr { + return TemplateMgr.getInstance().addObserver(observer) } /** @@ -397,7 +398,7 @@ export class ZRouter { * @param animated */ public static finishWithResult(result: T, animated: boolean = true) { - ZRouter.getRouterMgr().finishWithResult(result, animated) + return ZRouter.getRouterMgr().finishWithResult(result, animated) } /** diff --git a/RouterApi/src/main/ets/api/RouterMgr.ets b/RouterApi/src/main/ets/api/RouterMgr.ets index 480c657b65129a45dfd88d2f84da8cab70ad4b65..3975740af8cddff1af6d5644c59c075f2ae37ed3 100644 --- a/RouterApi/src/main/ets/api/RouterMgr.ets +++ b/RouterApi/src/main/ets/api/RouterMgr.ets @@ -6,7 +6,10 @@ import { ObjUtil } from '../utlis/ObjUtil' import { DEFAULT_STACK_NAME, + DestinationInfo, InterceptorInfo, + InterceptorInfoOrNull, + NavigationAction, NavRootEvent, ObjectOrNull, OnInterceptorCallback, @@ -164,8 +167,8 @@ export class RouterMgr { * @param name 是Route装饰器上的name属性值 * @param param 携带的参数 */ - public push(name: string, param?: ObjectOrNull, animated: boolean = false, builder?: NavDestBuilder) { - this.pushDestination(name, param, undefined, builder) + public push(name: string, param?: ObjectOrNull, animated: boolean = false, builder?: NavDestBuilder):Promise { + return this.pushDestination(name, param, undefined, builder) } /** @@ -178,15 +181,15 @@ export class RouterMgr { * @param callback */ public pushForResult(name: string, param?: ObjectOrNull, callback?: OnPopCallback, - builder?: NavDestBuilder) { - this.pushDestination(name, param, callback, builder) + builder?: NavDestBuilder): Promise { + return this.pushDestination(name, param, callback, builder) } /** * 监听返回结果跳转,必须与popNavWithResult配合使用 * 当跳转多个页面,并且返回需要携带结果时使用 * 比如 A->B->C->D 当页面在D时,需要返回到A并携带结果,此时在A就可以监听D的返回结果 - * pushNavForResult - popNavWithResult + * pushNavForResult - popNavWithResult 或 popToNameWithResult * pushNavForResult - popToRootWithResult * @see {popNavWithResult} * @param name @@ -194,9 +197,9 @@ export class RouterMgr { * @param callback */ public pushNavForResult(name: string, param?: ObjectOrNull, callback?: OnPopResultCallback, - builder?: NavDestBuilder) { + builder?: NavDestBuilder): Promise { const topName = this.getTopPathName() - this.pushDestination(name, param, undefined, builder).then(() => { + return this.pushDestination(name, param, undefined, builder).then(() => { let eName = '' if (ObjUtil.isNotEmpty(topName)) { eName = topName! @@ -237,12 +240,13 @@ export class RouterMgr { this.getNavStack().replacePathByName(name, param, builder?.options.animated) } else if (redirectType == RedirectType.REMOVE) { const topName = this.getTopPathName() - this.push(name, param, true) - setTimeout(() => { - if (topName) { - this.removeByName(topName) - } - }, 500) + this.push(name, param, true).then(() => { + setTimeout(() => { + if (topName) { + this.removeByName(topName) + } + }, 500) + }) }else { this.pop(false) this.push(name, param, true) @@ -275,10 +279,17 @@ export class RouterMgr { } - private pushDestination(name: string, param?: ObjectOrNull, callback?: OnPopCallback, + private async pushDestination(name: string, param?: ObjectOrNull, callback?: OnPopCallback, builder?: NavDestBuilder) { const animated = builder?.options.animated ?? true const launchMode = builder?.options?.lunchMode + const r = await this.beforePushOrReplace(name, NavigationOperation.PUSH, param, builder) + if (r.isContinue()) { + param = r.param + name = r.name + } else { + return Promise.resolve() + } if (DeviceUtil.isSdkVersion12()) { return this.getNavStack() .pushDestination({ @@ -315,27 +326,60 @@ export class RouterMgr { } } + + private async beforePushOrReplace(name: string, operation: NavigationOperation, param?: ObjectOrNull, + builder?: NavDestBuilder): Promise { + const state = this.getCurrentState() + let interceptorInfo = InterceptorInfo.create(true) + interceptorInfo.name = name + interceptorInfo.param = param + interceptorInfo.operation = operation + let r: DestinationInfo = DestinationInfo.create(interceptorInfo) + await this.interceptorMgr.runAsync(state.stackName, interceptorInfo, false, async (it) => { + try { + const result = await it.onNavigateBefore?.(r) + if (result) { + r = result + } + } catch (error) { + LogUtil.error('beforePushOrReplace error: ', error) + } + }) + if (!r.isContinue()) { + LogUtil.log('beforePushOrReplace next: ', r.isContinue()) + } + return Promise.resolve(r) + } + /** * 关闭到指定名称页面 * @param name * @param animated + * @returns 页面在栈中的索引index */ public popToName(name: string, animated: boolean = true) { - const num = this.getNavStack().popToName(name, animated) - if (num === -1) { - this.getNavStack()?.getParent()?.popToName(name, animated) - } + return this.popToNameInternal(name, undefined, animated) + } + + + /** + * 关闭到指定名称页面,并携带结果 + * @param name + * @param result + * @param animated + * @returns + */ + public popToNameWithResult(name: string, result?: T, animated: boolean = true) { + return this.popToNameInternal(name, result, animated) } /** * 返回上一页 * @param animated + * @returns boolean true: 表示成功关闭页面 */ public pop(animated: boolean = true) { - const r = this.getNavStack().pop(animated) - if (!r) { - this.getNavStack()?.getParent()?.pop(animated) - } + return this.popInternal(undefined, animated) } /** @@ -345,16 +389,47 @@ export class RouterMgr { * @see {pushForResult} * @param result * @param animated + * @returns boolean true: 表示成功关闭页面 */ public popWithResult(result: Object, animated: boolean = true) { - // 路由栈默认是不支持Boolean类型,转成字符串携带 - if (typeof result == "boolean") { - result = result + "" + return this.popInternal(result, animated) + } + + + private popInternal(result?: Object, animated: boolean = true): boolean { + if (result) { + // 路由栈默认是不支持Boolean类型,转成字符串携带 + if (typeof result == "boolean") { + result = result + "" + } + let r = this.getNavStack().pop(result, animated) + if (!r) { + r = this.getNavStack().getParent()?.pop(result, animated) + } + return r !== undefined + } else { + let r = this.getNavStack().pop(animated) + if (!r) { + r = this.getNavStack()?.getParent()?.pop(animated) + } + return r !== undefined } - const r = this.getNavStack().pop(result, animated) - if (!r) { - this.getNavStack().getParent()?.pop(result, animated) + } + + private popToNameInternal(name: string, result?: T, animated: boolean = true): number { + let index = -1 + if (result) { + index = this.getNavStack().popToName(name, result!, animated) + if (index === -1) { + index = this.getNavStack()?.getParent()?.popToName(name, result!, animated) ?? -1 + } + } else { + index = this.getNavStack().popToName(name, animated) + if (index === -1) { + index = this.getNavStack()?.getParent()?.popToName(name, animated) ?? -1 + } } + return index } @@ -365,6 +440,7 @@ export class RouterMgr { * @param name 路由名称 * @param result * @param animated + * @deprecated 建议使用popToNameWithResult()方法,该方法已经被弃用 */ public popNavWithResult(name: string, result: T, animated: boolean = true) { const currentName = this.getTopPathName() @@ -374,7 +450,7 @@ export class RouterMgr { } if (ObjUtil.isNotEmpty(name) && this.getAllPathName().includes(name!)) { if (currentName !== name) { - this.getNavStack().popToName(name, result!, animated) + this.popToNameWithResult(name, result!, animated) } else { LogUtil.error('不支持关闭当前页, 请使用popWithResult') } @@ -392,23 +468,25 @@ export class RouterMgr { * @param result * @param animated */ - public finishWithResult(result: T, animated: boolean = true) { + public finishWithResult(result: T, animated: boolean = true): boolean { const state = this.getCurrentState() if (state.redirectWithResultMode && ObjUtil.isNotEmpty(state.redirectInfo.name)) { const topName = this.getTopPathName() this.sendEvent(state.redirectInfo.name, topName!, result) this.pop(animated) + return true } + return false } public popToRootWithResult(result: T, animated: boolean = true) { const currentName = this.getTopPathName() if (ObjUtil.isEmpty(currentName)) { - LogUtil.error(currentName + " 不存在路由栈顶中") + LogUtil.error('路由栈为空,无法执行popToRootWithResult') return; } - this.getNavStack().clear(animated) + this.clear(animated) const name = NavRootEvent this.sendEvent(name!, currentName!, result) } @@ -425,10 +503,10 @@ export class RouterMgr { /** - * 清空页面栈,返回Navigation主页 + * 清空页面栈,返回Navigation 根视图 */ - public clear() { - this.getNavStack().clear() + public clear(animated: boolean = true) { + this.getNavStack().clear(animated) } @@ -519,10 +597,16 @@ export class RouterMgr { this.getNavStack().moveToTop(name) } - public replacePathByName(name: string, param?: ObjectOrNull, animated: boolean = true, + public async replacePathByName(name: string, param?: ObjectOrNull, animated: boolean = true, builder?: NavDestBuilder) { const anim = builder?.options.animated ?? animated const launchMode = builder?.options?.lunchMode + const r = await this.beforePushOrReplace(name, NavigationOperation.REPLACE, param, builder) + if (r.isContinue()) { + param = r.param + } else { + return + } if (DeviceUtil.isSdkVersion12()) { this.getNavStack() .replacePath({ name, param }, diff --git a/RouterApi/src/main/ets/interceptions/IInterceptor.ets b/RouterApi/src/main/ets/interceptions/IInterceptor.ets index 65b55c2f3691bd405efeb8a54813dabaf5898163..2a16eb7ec5252ff718702645d7e96913ced43a26 100644 --- a/RouterApi/src/main/ets/interceptions/IInterceptor.ets +++ b/RouterApi/src/main/ets/interceptions/IInterceptor.ets @@ -3,14 +3,25 @@ * @date: 2024/9/15 * @desc: */ -import { InterceptorInfo, InterceptorInfoOrNull } from '../model/Model'; +import { DestinationInfo, InterceptorInfo, InterceptorInfoOrNull } from '../model/Model'; -export interface IInterceptor { - process: (context: InterceptorInfo) => InterceptorInfoOrNull; - priority: number; -} +/** + * 全局拦截器 建议使用全局拦截器 + * 执行顺序: onNavigateBefore -> [IInterceptor#process] -> onNavigate -> onPageWillShow [onShowCallback] + */ export interface IGlobalNavigateInterceptor { + + /** + * 在跳转之前回调,可以在此回调中拦截跳转做一些自定义的逻辑,比如修改路由参数、数据预取、拦截跳转、拦截登录等场景 + * @param dest + * @returns DestinationInfo#action 为NavigationAction.BLOCK 则表示拦截跳转,NEXT继续执行 + * @note + * 如果通过ZRouter.getNavStack().push()方法跳转,则不会回调此方法,后续会考虑兼容 + * 只有通过ZRouter.getInstance().push()方法跳转时会回调此方法 + */ + onNavigateBefore?: (dest: DestinationInfo) => Promise; + /** * Navigation根视图显示时回调 * @param fromContext @@ -25,11 +36,30 @@ export interface IGlobalNavigateInterceptor { onPageWillShow?: (fromContext: NavDestinationContext, toContext: NavDestinationContext) => void; /** - * 页面跳转时回调 + * 页面push 或者 replace跳转时回调 * @param context */ onNavigate?: (context: InterceptorInfo) => void; + /** + * 页面显示回调,鸿蒙sdk默认的,没有处理逻辑,如果其他的回调函数无法满足你的需求,可考虑在这里实现 + * @param context + */ onShowCallback?: InterceptionShowCallback + +} + + +export interface IInterceptor { + /** + * 处理push和replace拦截器逻辑,返回null或undefined则表示拦截器处理完成,否则继续执行下一个拦截器 + * @param context + * @returns + */ + process: (context: InterceptorInfo) => InterceptorInfoOrNull; + /** + * 拦截器优先级,数值越大则优先执行 + */ + priority: number; } export type ProcessCallback = (context: InterceptorInfo) => InterceptorInfoOrNull; diff --git a/RouterApi/src/main/ets/interceptions/InterceptorMgr.ets b/RouterApi/src/main/ets/interceptions/InterceptorMgr.ets index 7d6c916f54a82ac1240edfe36c8c2ae3c3aad8b9..87d45af68b5078ef3a3a1ffc090e872dd88ceec0 100644 --- a/RouterApi/src/main/ets/interceptions/InterceptorMgr.ets +++ b/RouterApi/src/main/ets/interceptions/InterceptorMgr.ets @@ -28,7 +28,7 @@ export class InterceptorMgr { } - public addInterceptor(stackName: string, interceptor: IInterceptor) { + public addInterceptor(stackName: string, interceptor: IInterceptor): InterceptorMgr { if (this.interceptions.has(stackName)) { let interceptors = this.interceptions.get(stackName) if (interceptors === undefined) { @@ -44,6 +44,7 @@ export class InterceptorMgr { interceptors.sort((a, b) => b.priority - a.priority); this.interceptions.set(stackName, interceptors) } + return this } public removeInterceptor(stackName: string, interceptor: IInterceptor): boolean { @@ -90,4 +91,9 @@ export class InterceptorMgr { return context } + + public async runAsync(stackName: string, context: InterceptorInfo, isPush: boolean = false, + callback?: (it: IGlobalNavigateInterceptor) => void) { + return Promise.resolve(this.run(stackName, context, isPush, callback)) + } } diff --git a/RouterApi/src/main/ets/lifecycle/LifecycleMgr.ts b/RouterApi/src/main/ets/lifecycle/LifecycleMgr.ts index 3fa7d7b7589f93dbb4db9f898acb0ab4c6b1da7e..cd685552844269d891b16b54d494999814812326 100644 --- a/RouterApi/src/main/ets/lifecycle/LifecycleMgr.ts +++ b/RouterApi/src/main/ets/lifecycle/LifecycleMgr.ts @@ -29,11 +29,12 @@ export class LifecycleMgr { return LifecycleMgr._instance; } - public addGlobalObserver(observer: ILifecycleObserver){ + public addGlobalObserver(observer: ILifecycleObserver): LifecycleMgr{ if (this.globalObservers.has(observer)) { return; } this.globalObservers.add(observer) + return this } public removeGlobalObserver(observer: ILifecycleObserver){ diff --git a/RouterApi/src/main/ets/model/Const.ets b/RouterApi/src/main/ets/model/Const.ets index 3d5c206965a000987912c7e6e9a90dd7472d60e0..72f22597fd8ae2c70d4bb10fda186a4f6ac8e12b 100644 --- a/RouterApi/src/main/ets/model/Const.ets +++ b/RouterApi/src/main/ets/model/Const.ets @@ -9,6 +9,7 @@ export enum ErrorCode { INTERNAL_ERROR = 100001, NOT_REGISTERED = 100005, NOT_FOUND = 100006, + INTERCEPTED = 301 } export enum RedirectType { diff --git a/RouterApi/src/main/ets/model/Model.ets b/RouterApi/src/main/ets/model/Model.ets index 9cf3a9cb338eafc3a399e1fae13668c9cf7d8a64..37e68ffbaee03100ac40b617a86fc8e91c17c002 100644 --- a/RouterApi/src/main/ets/model/Model.ets +++ b/RouterApi/src/main/ets/model/Model.ets @@ -7,7 +7,14 @@ import { ObjUtil } from '../utlis/ObjUtil' import { HashMap } from '@kit.ArkTS' export class InterceptorInfo { + /** + * 页面是否注册 + */ private isNotRegistered: boolean = false + /** + * 拦截器携带的额外数据 预留 + * 目前暂时没有作用 + */ private extra = new HashMap() /** * 页面在没有注册的情况下,以下属性不可使用 @@ -18,10 +25,25 @@ export class InterceptorInfo { * @function notRegistered */ toContext: NavDestinationContext | undefined + /** + * @Route和@ZRoute注解上的元数据 - 参数 + */ metadata?: RouteMetadata - param?: ObjectOrNull // 跳转携带的参数 - name: string = '' // 跳转页面名称 - // isInterruptGlobalInterceptor = false // 是否中断全局拦截器 在单个拦截器中使用 + /** + * 跳转携带的参数 + */ + param?: ObjectOrNull + /** + * 页面跳转的name + * 对应RouteMetadata中的name + */ + name: string = '' + + /** + * 页面跳转的操作类型 + */ + operation?: NavigationOperation + @@ -66,6 +88,55 @@ export class RouteMetadata { needLogin: string = '' } + +export class DestinationInfo { + /** + * 跳转携带的参数 + */ + param?: ObjectOrNull + /** + * 页面跳转的name + * 对应RouteMetadata中的name + */ + name: string = '' + + /** + * 页面跳转的操作类型 + */ + operation?: NavigationOperation + + /** + * 是否拦截、继续 + */ + action: NavigationAction = NavigationAction.NEXT + + private constructor() { + } + static create(info : InterceptorInfo) { + const dest = new DestinationInfo() + dest.name = info.name + dest.param = info.param + dest.operation = info.operation + dest.action = NavigationAction.NEXT + return dest + } + isContinue() { + return this.action === NavigationAction.NEXT + } + next() { + this.action = NavigationAction.NEXT + } + block() { + this.action = NavigationAction.BLOCK + } +} + + +export enum NavigationAction { + NEXT = 'next', // 继续 + BLOCK = 'block', // 拦截 +} + /** * * @class ConfigInitializer @@ -94,4 +165,4 @@ export const NavRootEvent = 'NavRoot' export const DEFAULT_STACK_NAME = 'ZRouter' -export type InterceptorInfoOrNull = InterceptorInfo | null \ No newline at end of file +export type InterceptorInfoOrNull = InterceptorInfo | null | undefined \ No newline at end of file diff --git a/RouterApi/src/main/ets/model/NavDestBuilder.ets b/RouterApi/src/main/ets/model/NavDestBuilder.ets index 822ada24d9c5534d8526f310c33feb8d4d7c3764..0254aba7c97d44429f65b2ad89cae45ef595325c 100644 --- a/RouterApi/src/main/ets/model/NavDestBuilder.ets +++ b/RouterApi/src/main/ets/model/NavDestBuilder.ets @@ -12,6 +12,7 @@ import { ObjUtil } from '../utlis/ObjUtil'; import { RedirectType } from './Const'; import { LaunchMode } from './LaunchMode'; import Logger from '../utlis/Logger'; +import { ZRouter } from '../api/Router'; export class NavDestBuilder { private optionsImpl: INavDestOptions = new NavDestOptionsImpl(); @@ -103,11 +104,24 @@ export class NavDestBuilder { } /** - * 开启跨多级页面回调监听,这种场景返回需要使用popNavWithResult()方法 + * 开启跨多级页面回调监听,这种场景返回需要使用popNavWithResult()方法, popNavWithResult()已废弃 + * @date 2025/4/19 如果在Navigation根视图中push需要监听返回数据,则需要设置listenPopResultOnRootView为true + * @deprecated + * @see listenPopResultOnRootView替代 * @returns */ public enableCrossPageParamReturn(): NavDestBuilder { - this.optionsImpl.enableCrossPageParamReturn = true; + this.optionsImpl.listenPopResultOnRootView = true; + return this; + } + + /** + * 如果在Navigation根视图中push需要监听返回数据,则需要设置listenPopResultOnRootView为true + * 如果开启了,需要与popToRootWithResult()方法搭配使用 + * @returns + */ + public listenPopResultOnRootView(): NavDestBuilder { + this.optionsImpl.listenPopResultOnRootView = true; return this; } @@ -116,6 +130,7 @@ export class NavDestBuilder { * 这种场景由开发者决定是否需要启用,默认不启用 * 在setParam()方法中控制,必须在setParam()方法之前调用 * @returns + * @deprecated */ public enableConvertJumpParamZeroAndFalseToString(): NavDestBuilder { this.optionsImpl.isConvertJumpParamZeroAndFalseToString = true; @@ -166,7 +181,7 @@ export class NavDestBuilder { public navigation(name: string): void { this.updateCurrentStackName() if (this.optionsImpl.onPopListener) { - if (this.optionsImpl.enableCrossPageParamReturn) { + if (this.optionsImpl.listenPopResultOnRootView) { this.routerMgr.pushNavForResult(name, this.mergeParams(), this.optionsImpl.onPopListener,this) } else { this.routerMgr.pushForResult(name, this.mergeParams(), (data) => { @@ -235,37 +250,54 @@ export class NavDestBuilder { * 返回上一页 * @param animated */ - public pop(animated: boolean = true) { + public pop(animated: boolean = true): boolean { this.updateCurrentStackName() - this.routerMgr.pop(animated) + return this.routerMgr.pop(animated) } /** - * 返回指定路由名称的页面 + * 返回指定路由名称的页面,会关闭中间页面 * @param name 路由名称 * @param animated 是否执行动画 + * @returns number 页面在栈中的索引index */ - public popToName(name: string, animated: boolean = true) { + public popToName(name: string, animated: boolean = true): number { this.updateCurrentStackName() - this.routerMgr.popToName(name, animated) + return this.routerMgr.popToName(name, animated) + } + + + /** + * 携带结果返回指定路由名称的页面,会关闭中间页面,可以替代popNavWithResult + * 可在onPopListener回调函数内监听 + * @param name + * @param result + * @param animated + * @returns + */ + public popToNameWithResult(name: string, result: T, animated: boolean = true): number { + this.updateCurrentStackName() + return this.routerMgr.popToNameWithResult(name, result, animated) } /** * 携带结果返回上一页,可在onPopListener回调函数内监听 * @param result 返回携带的数据 * @param animated 是否执行动画 + * @returns boolean 是否返回成功 */ - public popWithResult(result: Object , animated: boolean = true) { + public popWithResult(result: Object , animated: boolean = true): boolean { this.updateCurrentStackName() - this.routerMgr.popWithResult(result, animated) + return this.routerMgr.popWithResult(result, animated) } /** - * 携带结果返回到指定的页面(可以跨多级页面),可在onPopListener回调函数内监听,前提是push时调用了enableCrossPageParamReturn()方法 + * 携带结果返回到指定的页面(可以跨多级页面),可在onPopListener回调函数内监听 * @param name 路由名称 * @param result 返回携带的数据 * @param animated 是否执行动画 + * @deprecated 建议使用popToNameWithResult()方法,该方法已经被弃用 */ public popNavWithResult(name: string, result: T, animated: boolean = true) { this.updateCurrentStackName() @@ -289,7 +321,8 @@ export class NavDestBuilder { */ public finishWithResult(result: T, animated: boolean = true) { this.updateCurrentStackName() - this.routerMgr.finishWithResult(result, animated) + const r = this.routerMgr.finishWithResult(result, animated) + return r } /** @@ -320,7 +353,7 @@ export class NavDestBuilder { /** * 按路由名称获取携带的参数 - * @param name + * @param name 路由名称 * @returns */ public getParamByName(name: string): Record[] { @@ -421,11 +454,11 @@ export class NavDestBuilder { /** - * @deprecated * @see {@link ZRouter.animateMgr()} * @returns */ public animateMgr(): NavAnimationMgr { + this.routerMgr.animateMgr.setNavigation(this as object as NavDestBuilder) return this.routerMgr.animateMgr } } \ No newline at end of file diff --git a/RouterApi/src/main/ets/model/NavDestOptions.ets b/RouterApi/src/main/ets/model/NavDestOptions.ets index 6a7b4e9b165f3d1849c26cf0c4d3a403d2235f61..606c11404c6067bdd7581e648cde3604d1144e9c 100644 --- a/RouterApi/src/main/ets/model/NavDestOptions.ets +++ b/RouterApi/src/main/ets/model/NavDestOptions.ets @@ -12,7 +12,7 @@ export interface INavDestOptions { lunchMode: LaunchMode; params?: ObjectOrNull; navStackName: string; - enableCrossPageParamReturn?: boolean; + listenPopResultOnRootView?: boolean; onPopListener?: OnPopResultCallback; redirectType?: RedirectType; isConvertJumpParamZeroAndFalseToString?: boolean; diff --git a/RouterApi/src/main/ets/template/TemplateMgr.ets b/RouterApi/src/main/ets/template/TemplateMgr.ets index 52af8d01ce53d6d85897ae4a95c6761292ac16d8..b40f3a7e619cedbc2bbd3a38183acad70802cf0d 100644 --- a/RouterApi/src/main/ets/template/TemplateMgr.ets +++ b/RouterApi/src/main/ets/template/TemplateMgr.ets @@ -25,11 +25,12 @@ export class TemplateMgr { return TemplateMgr._instance; } - addObserver(callback: LifecycleObserver) { + addObserver(callback: LifecycleObserver): TemplateMgr { const navDesId = ZRouter.getLastNavDestinationId() if (navDesId) { this._observers.set(navDesId, callback) } + return this } removeObserver(navDesId: string | undefined = undefined) { diff --git a/RouterApi/src/main/resources/base/element/string.json b/RouterApi/src/main/resources/base/element/string.json index f51a9c8461a55f6312ef950344e3145b7f82d607..d7d0d192d20f6d817c299cd4032b95d4b9ddebad 100644 --- a/RouterApi/src/main/resources/base/element/string.json +++ b/RouterApi/src/main/resources/base/element/string.json @@ -3,6 +3,10 @@ { "name": "page_show", "value": "page from package" + }, + { + "name": "agcit_router_tools", + "value": "1.5.0" } ] } diff --git a/features/harA/Index.ets b/features/harA/Index.ets index b3f847f90237e0148934a62f2a4b29ea9ba7b031..da3dd3eaa443d4b07d49dfd2bef42d6da90303d9 100644 --- a/features/harA/Index.ets +++ b/features/harA/Index.ets @@ -1 +1,2 @@ -export * from './src/main/ets/_generated/ZRService' export { MainPage } from './src/main/ets/components/MainPage' export { commonTest } from './src/main/ets/test/Test' // export { commonTest2 } from './src/main/ets/test/Test2' \ No newline at end of file +export * from './src/main/ets/_generated/ZRService' + export { MainPage } from './src/main/ets/components/MainPage' export { commonTest } from './src/main/ets/test/Test' // export { commonTest2 } from './src/main/ets/test/Test2' \ No newline at end of file diff --git a/features/harA/hvigorfile.ts b/features/harA/hvigorfile.ts index 5e0ce30c6a3597397dea307704b134e8fe7e3182..da30ed2b922be51dbd0d450ea5099cd6c811b8bf 100644 --- a/features/harA/hvigorfile.ts +++ b/features/harA/hvigorfile.ts @@ -1,11 +1,11 @@ import { harTasks } from '@ohos/hvigor-ohos-plugin'; -import { routerRegisterPlugin, PluginConfig } from 'router-register-plugin' -const config: PluginConfig = { - scanDirs: ["src/main/ets/components", "src/main/ets/service"], - logEnabled: true, - viewNodeInfo: false, -} +// import { routerRegisterPlugin, PluginConfig } from 'router-register-plugin' +// const config: PluginConfig = { +// scanDirs: ["src/main/ets/components", "src/main/ets/service"], +// logEnabled: true, +// viewNodeInfo: false, +// } export default { system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ - plugins:[routerRegisterPlugin(config)] /* Custom plugin to extend the functionality of Hvigor. */ + plugins:[/*routerRegisterPlugin(config)*/] /* Custom plugin to extend the functionality of Hvigor. */ } diff --git a/features/harA/obfuscation-rules.txt b/features/harA/obfuscation-rules.txt index 1b99f0a081f5330175144240c161c99b634c88b2..8cef65ee1450b0ceccd6d60e0e0387b6d61a4092 100644 --- a/features/harA/obfuscation-rules.txt +++ b/features/harA/obfuscation-rules.txt @@ -19,4 +19,8 @@ # -enable-property-obfuscation # -enable-toplevel-obfuscation # -enable-filename-obfuscation -# -enable-export-obfuscation \ No newline at end of file +# -enable-export-obfuscation +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/features/harA/oh-package-lock.json5 b/features/harA/oh-package-lock.json5 index 48b2e0a77eea110ab5b9f04bf1461c9776f7929d..8b3be03e6734a4817e420e92ce3c4e49b558d8b9 100644 --- a/features/harA/oh-package-lock.json5 +++ b/features/harA/oh-package-lock.json5 @@ -12,7 +12,7 @@ "packages": { "@hzw/zrouter@../../RouterApi": { "name": "@hzw/zrouter", - "version": "1.3.6", + "version": "1.5.0", "resolved": "../../RouterApi", "registryType": "local" }, @@ -27,7 +27,7 @@ }, "routerapi@../../RouterApi": { "name": "@hzw/zrouter", - "version": "1.3.6", + "version": "1.5.0", "resolved": "../../RouterApi", "registryType": "local" } diff --git a/features/harA/src/main/ets/components/BeforePushPage.ets b/features/harA/src/main/ets/components/BeforePushPage.ets new file mode 100644 index 0000000000000000000000000000000000000000..d05b5636c98238fe0ff0d3ab2376730082f46ca3 --- /dev/null +++ b/features/harA/src/main/ets/components/BeforePushPage.ets @@ -0,0 +1,79 @@ +/** + * @author: HZWei + * @date: 2025/4/9 + * @desc: + */ +import { KeyConst, RouterConstants, Student } from "common_library"; +import { ZRoute, ZRouter } from "routerapi"; +import { ArrayList, HashMap } from "@kit.ArkTS"; + + +@ZRoute({ name: RouterConstants.PAGE_BEFORE_PUSH, useTemplate: true, title: "跳转前拦截案例" }) +@Component +export struct BeforePushPage { + @State name :string ='' + @State age: number = 0 + @State score: number[] = [] + @State userInfo: Student = new Student() + @State params : string = '' + aboutToAppear(): void { + this.userInfo.s_name = '' + this.userInfo.s_age = 0 + if (ZRouter.getInstance().hasKey(KeyConst.KEY_NAME)) { + this.name = ZRouter.getInstance().getParamByKey(KeyConst.KEY_NAME) + this.age = ZRouter.getInstance().getParamByKey(KeyConst.KEY_AGE) + this.score = ZRouter.getInstance().getParamByKey(KeyConst.KEY_SCORE) + this.userInfo = ZRouter.getInstance().getParamByKey(KeyConst.KEY_USER_INFO) + // 建议使用泛型 + const map = new Map() + map.set('a',1) + const set = new Set() + set.add('a') + + const map1 = new HashMap() + + if (map1 instanceof Map) { + console.log('map1 is Map') + } + if (map1 instanceof HashMap) { + console.log('map1 is HashMap') + } + + const a = ZRouter.getParamByKey('a',0) + const b = ZRouter.getParamByKey('b','hello') + const c = ZRouter.getParamByKey('c',new Student()) + const d = ZRouter.getParamByKey('d',[1,2,3]) + const e = ZRouter.getParamByKey('e',[new Student(),new Student()]) + const f = ZRouter.getParamByKey('f',false) + const g = ZRouter.getParamByKey>('g',map) + const h = ZRouter.getParamByKey>('h',set) + const hh =ZRouter.getParamByKey>('hh',undefined) + const aa =ZRouter.getParamByKey('aa',null) + const hm =ZRouter.getParamByKey>('hm',new HashMap()) + const al =ZRouter.getParamByKey>('al',new ArrayList()) + console.log('a:',a,'b:',b,'c:',c.s_name,'d:',d,'e:',e?.[0].s_name,'f:',f,'g:',g.get('a'),'h:',h.has('a')) + } + this.params = JSON.stringify(ZRouter.getInstance().getParam()) + } + + build() { + Column({ space: 10 }) { + Text('getParamByKey: ') + .fontWeight(FontWeight.Bold) + .padding(10) + Text(`name: ${this.name}`) + Text(`age: ${this.age}`) + Text(`score: ${this.score}`) + Text(`userInfo: ${JSON.stringify(this.userInfo)}`) + Text('getParam: ') + .fontWeight(FontWeight.Bold) + .padding(10) + Text(`all params: ${this.params}`) + + + } + .margin({ top: 30 }) + + } +} + diff --git a/features/harA/src/main/ets/components/LoginPage.ets b/features/harA/src/main/ets/components/LoginPage.ets index c962949d16b47e35d973a3bb9afab0dec8722b87..6062e8620e048fec1b8fab0446da94ffc0513a6a 100644 --- a/features/harA/src/main/ets/components/LoginPage.ets +++ b/features/harA/src/main/ets/components/LoginPage.ets @@ -1,3 +1,4 @@ +import { RouterConstants } from 'common_library' import { Route, ZRouter } from 'routerapi' /** @@ -7,7 +8,7 @@ import { Route, ZRouter } from 'routerapi' */ -@Route({ name: 'LoginPage'}) +@Route @Component export struct LoginPage{ @@ -24,12 +25,15 @@ export struct LoginPage{ NavDestination(){ Column({space:15}){ Button('填写登录验证码').onClick((event: ClickEvent) => { - ZRouter.getInstance().push("loginVerifyCodePage") + ZRouter.getInstance().push(RouterConstants.LoginVerifyCodePage) }) Button('登录成功').onClick((event: ClickEvent) => { AppStorage.setOrCreate('isLogin', true) - ZRouter.getInstance().finishWithResult(true) + const r = ZRouter.getInstance().finishWithResult(true) + if (!r) { + ZRouter.getInstance().pop() + } }) } .width('100%') diff --git a/features/harA/src/main/ets/components/LoginVerifyCodePage.ets b/features/harA/src/main/ets/components/LoginVerifyCodePage.ets index 69bb801d62c45ed5093d9f1830724ede82f4dbe3..2720538c7f6d9e6a45dd5bb635b0d09b8dacbbcd 100644 --- a/features/harA/src/main/ets/components/LoginVerifyCodePage.ets +++ b/features/harA/src/main/ets/components/LoginVerifyCodePage.ets @@ -5,9 +5,10 @@ */ import { Route, ZRouter } from 'routerapi'; import { promptAction } from '@kit.ArkUI'; +import { RouterConstants } from 'common_library'; -@Route({ name: 'loginVerifyCodePage' }) +@Route({ name: RouterConstants.LoginVerifyCodePage }) @Component @Preview export struct LoginVerifyCodePage { @@ -31,8 +32,11 @@ export struct LoginVerifyCodePage { return } AppStorage.setOrCreate('isLogin', true) - ZRouter.removeByName("LoginPage") - ZRouter.finishWithResult(true) + ZRouter.getInstance().removeByName(RouterConstants.LOGIN_PAGE) + if (!ZRouter.getInstance().finishWithResult(true)) { + ZRouter.getInstance().pop() + } + }) } diff --git a/features/harA/src/main/ets/components/Page4.ets b/features/harA/src/main/ets/components/Page4.ets index a4bb4074293e362f695ba6e608c4dfca203ccaf8..1ef88ab80ae5e6528f4b808688a0b9d1e958fef6 100644 --- a/features/harA/src/main/ets/components/Page4.ets +++ b/features/harA/src/main/ets/components/Page4.ets @@ -1,4 +1,4 @@ -import { RouterConstants } from 'common_library'; +import { RouterConstants, ToastUtils } from 'common_library'; import { Route, ZRouter } from 'routerapi'; @@ -11,7 +11,11 @@ export struct Page4 { NavDestination(){ Column({space:12}){ Button('toHarAPage5').onClick((event: ClickEvent) => { - ZRouter.push(RouterConstants.HARA_PAGE5) + ZRouter.getInstance() + .setPopListener((result) => { + ToastUtils.show('from: ' + result.from + ' data: ' + JSON.stringify(result.data)) + }) + .push(RouterConstants.PAGE_POP_CASE) }) } diff --git a/features/harA/src/main/ets/components/Page5.ets b/features/harA/src/main/ets/components/Page5.ets deleted file mode 100644 index 4a94d42b6007b84926c84b6654476f0c22cdf5e5..0000000000000000000000000000000000000000 --- a/features/harA/src/main/ets/components/Page5.ets +++ /dev/null @@ -1,32 +0,0 @@ -import { Route, ZRouter } from 'routerapi'; -import systemTime from '@ohos.systemTime'; -import { systemDateTime } from '@kit.BasicServicesKit'; -import { RouterConstants } from 'common_library'; - - -@Route({ name: RouterConstants.HARA_PAGE5 }) -@Component -export struct Page5 { - @State message: string = 'Hello World'; - - build() { - NavDestination(){ - Column({space:12}){ - - Button('popNavWithResult - 跨多级页面返回携带数据').onClick((event: ClickEvent) => { - ZRouter.getInstance().popNavWithResult(RouterConstants.HARA_PAGE3, 'result data 11 ' + systemDateTime.getTime()) - }) - - Button('popToRootWithResult - 携带数据返回根视图').onClick((event: ClickEvent) => { - ZRouter.getInstance().popToRootWithResult('result data 22 ' + + systemDateTime.getTime()) - }) - - } - - } - .title('harAPage5') - .width('100%') - .height('100%') - - } -} diff --git a/features/harA/src/main/ets/components/ParamPage.ets b/features/harA/src/main/ets/components/ParamPage.ets index 3e4665d761b272e30277dd628c250655d3acd5e2..dab0de86dc2004bd991bbbdad0883fa52c79f6ce 100644 --- a/features/harA/src/main/ets/components/ParamPage.ets +++ b/features/harA/src/main/ets/components/ParamPage.ets @@ -69,6 +69,8 @@ export struct ParamPage { .fontWeight(FontWeight.Bold) .padding(10) Text(`all params: ${this.params}`) + + } .margin({ top: 30 }) diff --git a/features/harA/src/main/ets/components/PopCasePage.ets b/features/harA/src/main/ets/components/PopCasePage.ets new file mode 100644 index 0000000000000000000000000000000000000000..2d94bf14b30dd112ebf1f5dbe80d09290628261e --- /dev/null +++ b/features/harA/src/main/ets/components/PopCasePage.ets @@ -0,0 +1,50 @@ +import { Route, ZRouter } from 'routerapi'; +import systemTime from '@ohos.systemTime'; +import { systemDateTime } from '@kit.BasicServicesKit'; +import { RouterConstants } from 'common_library'; + + +@Route({ name: RouterConstants.PAGE_POP_CASE,useTemplate:true,title:"pop案例" }) +@Component +export struct PopCasePage5 { + @State message: string = 'Hello World'; + + build() { + Column({space:12}){ + + Text('popNavWithResult - 跨多级页面返回携带数据 已弃用 用popToNameWithResult替代') + .onClick((event: ClickEvent) => { + ZRouter.getInstance() + .popNavWithResult(RouterConstants.PAGE_PUSH_AND_POP_CASE, + 'popNavWithResult 跨多级页面返回携带数据 ' + systemDateTime.getTime()) + }) + .width('90%') + .backgroundColor(Color.Red) + .fontColor(Color.White) + .padding(10) + .borderRadius(40) + + Button('popToRootWithResult - 携带数据返回根视图').onClick((event: ClickEvent) => { + ZRouter.getInstance().popToRootWithResult('popToRootWithResult 携带数据返回根视图 ' + systemDateTime.getTime()) + }) + + Button('pop - 返回上一页').onClick((event: ClickEvent) => { + ZRouter.getInstance().pop() + }) + + Button('popWithResult - 携带参数返回上一页').onClick((event: ClickEvent) => { + ZRouter.getInstance().popWithResult('popWithResult 携带参数返回上一页 ' + + systemDateTime.getTime()) + }) + + Button('popToName - 返回指定页面').onClick((event: ClickEvent) => { + ZRouter.getInstance().popToName(RouterConstants.PAGE_PUSH_AND_POP_CASE) + }) + + Button('popToNameWithResult - 携带参数返回指定页面').onClick((event: ClickEvent) => { + ZRouter.getInstance().popToNameWithResult(RouterConstants.PAGE_PUSH_AND_POP_CASE, 'popToNameWithResult 携带参数返回指定页面 ' + systemDateTime.getTime()) + }) + + } + + } +} diff --git a/features/harA/src/main/ets/components/Page3.ets b/features/harA/src/main/ets/components/PushAndPopCasePage.ets similarity index 81% rename from features/harA/src/main/ets/components/Page3.ets rename to features/harA/src/main/ets/components/PushAndPopCasePage.ets index dc0a4c85deb1c495461fb3f736c79286bc469cff..a7b0a6c321b264be71b6ecc5a15287054621217a 100644 --- a/features/harA/src/main/ets/components/Page3.ets +++ b/features/harA/src/main/ets/components/PushAndPopCasePage.ets @@ -3,9 +3,9 @@ import { Route, ZRouter } from 'routerapi'; import LogUtil from 'routerapi/src/main/ets/utlis/LogUtil'; -@Route({ name: RouterConstants.HARA_PAGE3 ,needLogin: true}) +@Route({ name: RouterConstants.PAGE_PUSH_AND_POP_CASE ,needLogin: true}) @Component -export struct Page3 { +export struct PushAndPopCasePage { @State message: string = 'Hello World'; aboutToAppear(): void { @@ -17,7 +17,7 @@ export struct Page3 { NavDestination(){ Column({space:12}){ Text(this.message) - Button('pushForResult - 监听上一页返回携带的数据').onClick((event: ClickEvent) => { + Button('push - 监听上一页返回携带的数据').onClick((event: ClickEvent) => { // 上一个页面返回 ZRouter.getInstance() // .setParam('hello') @@ -29,11 +29,11 @@ export struct Page3 { }) - Button('pushNavForResult - 监听跨多级页面返回携带的数据').onClick((event: ClickEvent) => { + Button('push - 监听跨多级页面返回携带的数据').onClick((event: ClickEvent) => { // 上上个页面返回 ZRouter.getInstance() .setParam('hello') - .enableCrossPageParamReturn() + // .enableCrossPageParamReturn() .setPopListener((r)=>{ let msg = `from: ${r.from} data: ${r.data}`; this.message = msg @@ -67,7 +67,7 @@ export struct Page3 { } } - .title('跳转携带参数与监听pop返回所携带的参数') + .title('跳转携带参数与监听pop携带的参数 - PushAndPopCasePage') .width('100%') .height('100%') diff --git a/features/harB/hvigorfile.ts b/features/harB/hvigorfile.ts index a1626df9add254987ac2d75479a0b21637d4a543..6b3b3e3b124fa4b3d1067980c465526f8c51fb1b 100644 --- a/features/harB/hvigorfile.ts +++ b/features/harB/hvigorfile.ts @@ -1,13 +1,13 @@ import { harTasks } from '@ohos/hvigor-ohos-plugin'; -import { routerRegisterPlugin, PluginConfig } from 'router-register-plugin' - -const config: PluginConfig = { - scanDir: "src/main/ets/views", - logEnabled: false , - viewNodeInfo:false -} +// import { routerRegisterPlugin, PluginConfig } from 'router-register-plugin' +// +// const config: PluginConfig = { +// scanDir: "src/main/ets/views", +// logEnabled: true , +// viewNodeInfo:false +// } export default { system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ - plugins: [routerRegisterPlugin(config)] /* Custom plugin to extend the functionality of Hvigor. */ + plugins: [/*routerRegisterPlugin(config)*/] /* Custom plugin to extend the functionality of Hvigor. */ } diff --git a/features/harB/oh-package-lock.json5 b/features/harB/oh-package-lock.json5 index 48b2e0a77eea110ab5b9f04bf1461c9776f7929d..8b3be03e6734a4817e420e92ce3c4e49b558d8b9 100644 --- a/features/harB/oh-package-lock.json5 +++ b/features/harB/oh-package-lock.json5 @@ -12,7 +12,7 @@ "packages": { "@hzw/zrouter@../../RouterApi": { "name": "@hzw/zrouter", - "version": "1.3.6", + "version": "1.5.0", "resolved": "../../RouterApi", "registryType": "local" }, @@ -27,7 +27,7 @@ }, "routerapi@../../RouterApi": { "name": "@hzw/zrouter", - "version": "1.3.6", + "version": "1.5.0", "resolved": "../../RouterApi", "registryType": "local" } diff --git a/features/hspC/Index.ets b/features/hspC/Index.ets index 9b48cd011623397efce48e97ed88550aeb9dfc5d..2a35ca801685e173498c68b29acc1e2097ac8208 100644 --- a/features/hspC/Index.ets +++ b/features/hspC/Index.ets @@ -1 +1,2 @@ -export * from './src/main/ets/_generated/ZRService' export { add } from './src/main/ets/utils/Calc' \ No newline at end of file +export * from './src/main/ets/_generated/ZRService' +export { add } from './src/main/ets/utils/Calc' \ No newline at end of file diff --git a/features/hspC/hvigorfile.ts b/features/hspC/hvigorfile.ts index 0a68c37eb5a629a41947a3fd911bed2189bec00b..4a4a785d5efb75e15f3bf87702e0dcac7a3896ee 100644 --- a/features/hspC/hvigorfile.ts +++ b/features/hspC/hvigorfile.ts @@ -1,12 +1,12 @@ import { hspTasks } from '@ohos/hvigor-ohos-plugin'; -import { routerRegisterPlugin, PluginConfig } from 'router-register-plugin' - -const config: PluginConfig = { - scanDirs: ["src/main/ets/pages", "src/main/ets/service"], - logEnabled: false, - viewNodeInfo: false -} +// import { routerRegisterPlugin, PluginConfig } from 'router-register-plugin' +// +// const config: PluginConfig = { +// scanDirs: ["src/main/ets/pages", "src/main/ets/service"], +// logEnabled: true, +// viewNodeInfo: false +// } export default { system: hspTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ - plugins:[routerRegisterPlugin(config)] /* Custom plugin to extend the functionality of Hvigor. */ + plugins:[/*routerRegisterPlugin(config)*/] /* Custom plugin to extend the functionality of Hvigor. */ } diff --git a/features/hspC/oh-package-lock.json5 b/features/hspC/oh-package-lock.json5 index 115f5ad31be0003c9e3ab36c289ca68431598a25..095f94df88a645085ef9af76245947a911e650a3 100644 --- a/features/hspC/oh-package-lock.json5 +++ b/features/hspC/oh-package-lock.json5 @@ -21,13 +21,13 @@ }, "@hzw/zrouter@../../RouterApi": { "name": "@hzw/zrouter", - "version": "1.3.6", + "version": "1.5.0", "resolved": "../../RouterApi", "registryType": "local" }, "routerapi@../../RouterApi": { "name": "@hzw/zrouter", - "version": "1.3.6", + "version": "1.5.0", "resolved": "../../RouterApi", "registryType": "local" } diff --git a/features/hspC/src/main/ets/pages/Index.ets b/features/hspC/src/main/ets/pages/Index.ets index 0d25f43193a3d45247cfe054dde5cacf6c500d99..54230a8a9bd0497923d70b7b864f9cd5ac04a6fc 100644 --- a/features/hspC/src/main/ets/pages/Index.ets +++ b/features/hspC/src/main/ets/pages/Index.ets @@ -1,4 +1,4 @@ -import { RouterConstants } from '@hzw/common_library'; +import { RouterConstants, ToastUtils } from '@hzw/common_library'; import { Route, ZRouter } from 'routerapi'; @Route({ name: RouterConstants.HSPC_INDEX_PAGE, needLogin: true}) @@ -37,6 +37,18 @@ struct Index { // ZRouter.popWithResult('hspCIndex data') ZRouter.getInstance().popWithResult("from hspC index data") }) + + if (!ZRouter.getParamByKey('isRoot',false)) { + Button('pop 案例').onClick((event: ClickEvent) => { + ZRouter.getInstance() + .setPopListener((result) => { + ToastUtils.show('from: ' + result.from + ' data: ' + JSON.stringify(result.data)) + }) + .push(RouterConstants.PAGE_POP_CASE) + }) + } + + } .width('100%') } diff --git a/features/hspC/src/main/ets/pages/test/CustomComponentView.ets b/features/hspC/src/main/ets/pages/test/CustomComponentView.ets index 268b95c2579181edb133f6b74f98f3c3a7ba279a..7bfc536b5346a3cb630bf62c8bdad5c39ba40454 100644 --- a/features/hspC/src/main/ets/pages/test/CustomComponentView.ets +++ b/features/hspC/src/main/ets/pages/test/CustomComponentView.ets @@ -11,7 +11,7 @@ import { CustomCompLifecycleObserver } from './CustomCompLifecycleObserver'; @ZRoute({ name: RouterConstants.CUSTOM_COMPONENT_VIEW_HSP, useTemplate: true }) @Component export struct ChildView { - @Consume lifecycleObserver: CustomCompLifecycleObserver + @Consume viewModel: CustomCompLifecycleObserver aboutToAppear(): void { ZRouter.addLifecycleObserver((state, router) => { console.log("ChildView hsp addObserver ", state, router?.navDestinationId) @@ -20,9 +20,9 @@ export struct ChildView { build() { Column({ space: 15 }) { - Text(this.lifecycleObserver.counter + "") + Text(this.viewModel.counter + "") Button("+1").onClick((event: ClickEvent) => { - this.lifecycleObserver.counter++ + this.viewModel.counter++ }) Button("CustomComp-hsp").onClick((event: ClickEvent) => { diff --git a/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 index 52df15c5222c6ed0e21e1b77afb5999e031d4d51..dcaa849a14f1bd824477d1e7460fe9fa5556514f 100644 --- a/hvigor/hvigor-config.json5 +++ b/hvigor/hvigor-config.json5 @@ -1,9 +1,9 @@ { "modelVersion": "5.0.0", "dependencies": { - "router-register-plugin":"file:../plugins/router-register-plugin-1.3.0.alpha.6.tgz" -// "router-register-plugin":"1.2.1" - +// "router-register-plugin":"file:../plugins/router-register-plugin-1.5.0.tgz" +// "router-register-plugin":"1.5.0" + "router-register-plugin":"file:../libs/router-register-plugin-1.5.7.tgz" }, "execution": { // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ diff --git a/hvigorfile.ts b/hvigorfile.ts index f400914efd2235ad87d4ac612dd34f0194d20358..b26e1a5dc87e31f261d5cfcbc90d47654cd6983a 100644 --- a/hvigorfile.ts +++ b/hvigorfile.ts @@ -1,6 +1,17 @@ import { appTasks } from '@ohos/hvigor-ohos-plugin'; -// import {customPlugin} from 'generate-router-plugin' +import { routerRegisterPlugin, PluginConfig } from 'router-register-plugin' + +const config: PluginConfig = { + scanDirs: ["src/main/ets/components", "src/main/ets/service", "src/main/ets/views", "src/main/ets/pages", + 'src/main/ets/model'], + logEnabled: true, // 查看日志 + viewNodeInfo: false, // 查看节点信息 + lifecycleObserverAttributeName: 'viewModel', // 生命周期观察者属性名 + ignoredModules:['RouterApi','common'], // 忽略的参与构建的模块,建议设置 + enableUiPreviewBuild: false, // 启用UI预览构建,不建议启动 +} + export default { - system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ - plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins: [routerRegisterPlugin(config)] /* Custom plugin to extend the functionality of Hvigor. */ } diff --git a/library/common/src/main/resources/zh_CN/element/string.json b/img/string.json similarity index 35% rename from library/common/src/main/resources/zh_CN/element/string.json rename to img/string.json index f51a9c8461a55f6312ef950344e3145b7f82d607..047a707f1a91e2323d3addfb185dddcdd9e5484b 100644 --- a/library/common/src/main/resources/zh_CN/element/string.json +++ b/img/string.json @@ -1,8 +1,8 @@ { "string": [ { - "name": "page_show", - "value": "page from package" + "name": "agcit_router_tools", + "value": "1.4.1" } ] } diff --git a/img/zrouter-logo.png b/img/zrouter-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..faf02ab9cf84a7002b501e12bb7122658ede3e1f Binary files /dev/null and b/img/zrouter-logo.png differ diff --git a/library/common/oh-package-lock.json5 b/library/common/oh-package-lock.json5 index 2cb58d09649ea1a151de41e1aec45fc3c30ce47f..da32ffa0bf81bbe33d31e7c1a62cd917241bd574 100644 --- a/library/common/oh-package-lock.json5 +++ b/library/common/oh-package-lock.json5 @@ -10,7 +10,7 @@ "packages": { "@hzw/zrouter@../../RouterApi": { "name": "@hzw/zrouter", - "version": "1.3.6", + "version": "1.5.0", "resolved": "../../RouterApi", "registryType": "local" } diff --git a/library/common/src/main/ets/constants/RouterConstants.ets b/library/common/src/main/ets/constants/RouterConstants.ets index 2d71653cd694372c88d86102366b13a2ed176987..db38b547f7752b7a401a47437335e3d4dc3d29be 100644 --- a/library/common/src/main/ets/constants/RouterConstants.ets +++ b/library/common/src/main/ets/constants/RouterConstants.ets @@ -1,9 +1,9 @@ export class RouterConstants { public static readonly CUSTOM_URL_PAGE: string = "custom_url_page"; public static readonly HARA_MAIN_PAGE :string = "harAMainPage" - public static readonly HARA_PAGE3 = 'harAPage3' + public static readonly PAGE_PUSH_AND_POP_CASE = 'PushAndPopCasePage' public static readonly HARA_PAGE4 :string = "harAPage4" - public static readonly HARA_PAGE5 :string = "harAPage5" + public static readonly PAGE_POP_CASE :string = "PopCasePage5" public static readonly HARB_MAIN_PAGE :string = "harBMainPage" public static readonly HSPC_INDEX_PAGE: string = "hspCIndex" public static readonly HSPC_PAGE3: string = "hspCPage3" @@ -26,10 +26,16 @@ export class RouterConstants { public static readonly ANIM_DEMO = 'AnimTransitionDemo' public static readonly PAGE_ONE = 'PageOne' public static readonly PAGE_TWO = 'PageTwo' + public static readonly PAGE_CARD_ONE = 'PageCardOne' + public static readonly PAGE_CARD_TWO = 'PageCardTwo' public static readonly PAGE_THREE = 'PageThree' public static readonly PAGE_FOUR = 'PageFour' public static readonly PAGE_DIALOG_MODE = 'PageDialogMode' public static readonly PAGE_PARAM = 'ParamPage' + public static readonly PAGE_BEFORE_PUSH = 'BeforePushPage' public static readonly PAGE_NAVDEST_TEMPLATE_V2 = 'NavDestTemplateV2' public static readonly NEST_PAGE = 'NestPage' + public static readonly LOGIN_PAGE = 'LoginPage' + public static readonly LoginVerifyCodePage = 'loginVerifyCodePage' + // public static readonly PAGE_POP_CASE = 'POP_CASE_PAGE' } \ No newline at end of file diff --git a/library/common/src/main/resources/base/element/string.json b/library/common/src/main/resources/base/element/string.json index f51a9c8461a55f6312ef950344e3145b7f82d607..b87cac11465688936485a401b6c01e7b53743874 100644 --- a/library/common/src/main/resources/base/element/string.json +++ b/library/common/src/main/resources/base/element/string.json @@ -3,6 +3,18 @@ { "name": "page_show", "value": "page from package" + }, + { + "name": "img", + "value": "image" + }, + { + "name": "content", + "value": "The two nodes that appear and disappear are associated with location size content." + }, + { + "name": "card_title", + "value": "Card to the end" } ] } diff --git a/library/common/src/main/resources/en_US/element/string.json b/library/common/src/main/resources/en_US/element/string.json deleted file mode 100644 index f51a9c8461a55f6312ef950344e3145b7f82d607..0000000000000000000000000000000000000000 --- a/library/common/src/main/resources/en_US/element/string.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "string": [ - { - "name": "page_show", - "value": "page from package" - } - ] -} diff --git a/libs/RouterApi.har b/libs/RouterApi.har index da43738dcb934022146c99fd55a3719e1262040a..df9181b19edcf7a29cd0e2c17501ebfe9d537d13 100644 Binary files a/libs/RouterApi.har and b/libs/RouterApi.har differ diff --git a/libs/router-register-plugin-1.5.7.tgz b/libs/router-register-plugin-1.5.7.tgz new file mode 100644 index 0000000000000000000000000000000000000000..c247a29028803afcfea2702cfe81e78560f1209c Binary files /dev/null and b/libs/router-register-plugin-1.5.7.tgz differ diff --git a/plugins/router-register-plugin-1.3.1.tgz b/plugins/router-register-plugin-1.3.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..dc4a59229eb12297b426fc3e88e7831b83c1dcb4 Binary files /dev/null and b/plugins/router-register-plugin-1.3.1.tgz differ diff --git a/plugins/router-register-plugin-1.3.2.tgz b/plugins/router-register-plugin-1.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..c6d2eaf036bf2f543b3b1eafbfa69946b75bc71d Binary files /dev/null and b/plugins/router-register-plugin-1.3.2.tgz differ diff --git a/plugins/router-register-plugin-1.4.1.tgz b/plugins/router-register-plugin-1.4.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..0827a7a4639af8bc428fa9efc49371b0705be486 Binary files /dev/null and b/plugins/router-register-plugin-1.4.1.tgz differ diff --git a/plugins/router-register-plugin-1.5.0.tgz b/plugins/router-register-plugin-1.5.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..e9e3c4c1dca705bf51049b1671fcf71af20cb87f Binary files /dev/null and b/plugins/router-register-plugin-1.5.0.tgz differ diff --git a/products/entry/.hvigor/outputs/build-logs/build.log b/products/entry/.hvigor/outputs/build-logs/build.log new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/products/entry/Index.ets b/products/entry/Index.ets deleted file mode 100644 index ca3cb702613ea2c2b9bcc67f80ff5b0898d513ed..0000000000000000000000000000000000000000 --- a/products/entry/Index.ets +++ /dev/null @@ -1,5 +0,0 @@ -/** - * @author: HZWei - * @date: 2024/9/3 - * @desc: - */ \ No newline at end of file diff --git a/products/entry/hvigorfile.ts b/products/entry/hvigorfile.ts index 27e68ff54831251f73c6490bfe391b330c741b61..d4c78381882d8fd4671fa0ae3b307d2b7930bb9e 100644 --- a/products/entry/hvigorfile.ts +++ b/products/entry/hvigorfile.ts @@ -1,13 +1,13 @@ import { hapTasks } from '@ohos/hvigor-ohos-plugin'; -import { routerRegisterPlugin, PluginConfig } from 'router-register-plugin' -const config: PluginConfig = { - scanDirs: ['src/main/ets/pages', 'src/main/ets/views','src/main/ets/model'], // 扫描的目录,如果不设置,默认是扫描src/main/ets目录 - logEnabled: true, // 查看日志 - viewNodeInfo: false, // 查看节点信息 - lifecycleObserverAttributeName: 'viewModel', // 生命周期观察者属性名 - isAutoDeleteHistoryFiles: true // 删除无用的编译产物, -} +// import { routerRegisterPlugin, PluginConfig } from 'router-register-plugin' +// const config: PluginConfig = { +// scanDirs: ['src/main/ets/pages','src/main/ets/model'], // 扫描的目录,如果不设置,默认是扫描src/main/ets目录 +// logEnabled: true, // 查看日志 +// viewNodeInfo: false, // 查看节点信息 +// lifecycleObserverAttributeName: 'viewModel', // 生命周期观察者属性名 +// isAutoDeleteHistoryFiles: true // 删除无用的编译产物, +// } export default { system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ - plugins:[routerRegisterPlugin(config)] /* Custom plugin to extend the functionality of Hvigor. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ } diff --git a/products/entry/oh-package-lock.json5 b/products/entry/oh-package-lock.json5 index 9742de453e3ccc0d5e2c86a2ad057ab3b6a81363..c9961a8ddeab2577a57b9ec0b64e13959523a1b8 100644 --- a/products/entry/oh-package-lock.json5 +++ b/products/entry/oh-package-lock.json5 @@ -35,7 +35,7 @@ }, "@hzw/zrouter@../../RouterApi": { "name": "@hzw/zrouter", - "version": "1.3.6", + "version": "1.5.0", "resolved": "../../RouterApi", "registryType": "local" }, @@ -71,7 +71,7 @@ }, "routerapi@../../RouterApi": { "name": "@hzw/zrouter", - "version": "1.3.6", + "version": "1.5.0", "resolved": "../../RouterApi", "registryType": "local" } diff --git a/products/entry/src/main/ets/constants/RouterConst.ets b/products/entry/src/main/ets/constants/RouterConst.ets index 7026329b6e4fad99ed860bb1e2142a191b08b373..deadc064f3d83aa37bc92d30cad7688919e870e7 100644 --- a/products/entry/src/main/ets/constants/RouterConst.ets +++ b/products/entry/src/main/ets/constants/RouterConst.ets @@ -1,6 +1,7 @@ export class RouterConst { public static readonly TEST = "URL_PAGE" - public static readonly SPLASH_PAGE: string = "splash_page" + public static readonly SPLASH_PAGE: string = "SplashPage" public static readonly SPLASH_TAG: string = "splash_tag" public static readonly NAV_PAGE: string = 'pages/nav/NavPage' + public static readonly MAIN_PAGE: string = 'pages/main/MainPage' } \ No newline at end of file diff --git a/products/entry/src/main/ets/entryability/EntryAbility.ets b/products/entry/src/main/ets/entryability/EntryAbility.ets index f15ec17f7d82d1b50df0d79a85f3f4ace35731cb..c4b2785b44c79b85ce917c4c654fbe0e10b0f338 100644 --- a/products/entry/src/main/ets/entryability/EntryAbility.ets +++ b/products/entry/src/main/ets/entryability/EntryAbility.ets @@ -1,6 +1,7 @@ import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; -import { uiObserver, window } from '@kit.ArkUI'; +import { window } from '@kit.ArkUI'; +import { ZRouter } from 'routerapi'; export default class EntryAbility extends UIAbility { @@ -18,6 +19,8 @@ export default class EntryAbility extends UIAbility { // Main window is created, set main page for this ability hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + ZRouter.animateMgr().initSharedAnim(windowStage) + windowStage.loadContent('pages/Index', (err) => { if (err.code) { hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); diff --git a/products/entry/src/main/ets/interceptors/AddIntercetors.ets b/products/entry/src/main/ets/interceptors/AddIntercetors.ets index c366429cbd93cc030ccf06034f3160c56e7ba265..1f8de64d5fc3cc6d4141a04082e9dad1b517bc76 100644 --- a/products/entry/src/main/ets/interceptors/AddIntercetors.ets +++ b/products/entry/src/main/ets/interceptors/AddIntercetors.ets @@ -5,42 +5,13 @@ */ import { IInterceptor, InterceptorInfo, ZRouter } from "routerapi" -import { GlobalNavigateInterceptor, UrlInterceptor } from "./interceptors" +import { GlobalNavInterceptorMgr, UrlInterceptor } from "./interceptors" export function AddInterceptor(){ - // 第一种方式设置拦截器,字面量对象的形式 - ZRouter.setInterceptor({ - priority: 100, - process: (info: InterceptorInfo) => { - console.log("IInterceptor process: ", 100, info.name) - return info - } - } as IInterceptor) - // 第二种方式设置拦截器,类的形式 - ZRouter.setInterceptor(new UrlInterceptor()) // 可设置多个拦截器 - ZRouter.setGlobalInterceptor(new GlobalNavigateInterceptor()) // 全局拦截器 + // 可设置多个拦截器 + ZRouter.setInterceptor(new UrlInterceptor()) + // 全局拦截器 + ZRouter.setGlobalInterceptor(GlobalNavInterceptorMgr) - - - // ZRouter.addGlobalInterceptor((info) => { - // // LogUtil.log('GlobalInterceptor: ', JSON.stringify(info.metadata) , info.isNeedLogin) - // if (info.notRegistered) { - // // 页面不存在,重定向到提示页 PageNotFound - // ZRouter.redirect("PageNotFound2") - // return - // } - // let isLogin = AppStorage.get("isLogin") - // if (info.isNeedLogin && !isLogin) { - // let param = ZRouter.getParamByName(info.metadata?.name ?? "") - // ZRouter.redirectForResult2("LoginPage", param, (data) => { - // if (data.data) { - // // 登录成功 - // promptAction.showToast({ message: `登录成功` }) - // return true // 返回true 则继续跳转登录前的页面 - // } - // return false - // }) - // } - // }) } \ No newline at end of file diff --git a/products/entry/src/main/ets/interceptors/interceptors.ets b/products/entry/src/main/ets/interceptors/interceptors.ets index 862206a931cfc56922cc3b955aa79a5ddda054c4..779d62c12853ff70f44fcfc0775da7a55d3a5a38 100644 --- a/products/entry/src/main/ets/interceptors/interceptors.ets +++ b/products/entry/src/main/ets/interceptors/interceptors.ets @@ -1,38 +1,65 @@ /** * @author: HZWei * @date: 2024/9/22 - * @desc: 拦截器案例 + * @desc: 拦截器案例 */ -import { IInterceptor, InterceptorInfo, InterceptorInfoOrNull, IGlobalNavigateInterceptor, ZRouter, +import { + IInterceptor, + InterceptorInfo, + InterceptorInfoOrNull, + IGlobalNavigateInterceptor, + ZRouter, RedirectType, - } from 'routerapi'; + DestinationInfo, + NavigationAction, +} from 'routerapi'; import { promptAction } from '@kit.ArkUI'; import { HashMap } from '@kit.ArkTS'; +import { RouterConstants, ToastUtils } from '@hzw/common_library'; -export class GlobalNavigateInterceptor implements IGlobalNavigateInterceptor{ - onRootWillShow?: ((fromContext: NavDestinationContext) => void) | undefined = (fromContext) => { - console.log("IInterceptor Global onRootWillShow -> from name: ", fromContext.pathInfo.name) +class GlobalNavigateInterceptor implements IGlobalNavigateInterceptor { + count = 0 + onRootShow?: () => void + onNavigateBefore: (destInfo: DestinationInfo) => Promise = (destInfo) => { + console.log("IInterceptor Global onNavigateBefore -> ", destInfo.name) + return new Promise((resolve, _) => { + if (destInfo.name === RouterConstants.PAGE_BEFORE_PUSH) { + // 拦截跳转到ParamPage页面 + if (this.count === 0) { + destInfo.param = ' 在拦截器onNavigateBefore中已替换参数 ' + destInfo.next() // 继续跳转 默认的 ,可以不写 + } else if (this.count === 1) { + ToastUtils.show("拦截器onNavigateBefore中已拦截, 再点一次会继续执行") + destInfo.block() // 拦截跳转 + } else if (this.count === 2) { + destInfo.name = RouterConstants.LIFECYCLE_CASE_VIEW + } + this.count += 1 + resolve(destInfo) + } else { + resolve(destInfo) + } + + }) } - onPageWillShow?: ((fromContext: NavDestinationContext, toContext: NavDestinationContext) => void) | undefined = (from ,to)=>{ - console.log("IInterceptor Global onPageWillShow -> ", " form name: ", from.pathInfo.name," to name: ", to.pathInfo.name) + onRootWillShow: ((fromContext: NavDestinationContext) => void) | undefined = (fromContext) => { + console.log("IInterceptor Global onRootWillShow -> from name: ", fromContext.pathInfo.name) + this.onRootShow?.() } - - onNavigate?: ((context: InterceptorInfo) => void) | undefined = (info)=>{ - if (info.notRegistered) return + onPageWillShow: ((fromContext: NavDestinationContext, toContext: NavDestinationContext) => void) | undefined = + (from, to) => { + console.log("IInterceptor Global onPageWillShow -> ", " form name: ", from.pathInfo.name, " to name: ", + to.pathInfo.name) + } + onNavigate: ((context: InterceptorInfo) => void) | undefined = (info) => { + if (info.notRegistered) { + return + } console.log("IInterceptor Global onNavigate -> ", info.name) let isLogin = AppStorage.get("isLogin") if (info.isNeedLogin && !isLogin) { let param = info.param - // ZRouter.redirectForResult2("LoginPage", param, (data) => { - // if (data.data) { - // // 登录成功 - // promptAction.showToast({ message: `登录成功` }) - // return true // 返回true 则继续跳转登录前的页面 - // } - // return false - // }) - ZRouter.getInstance() .setParam(param) .setAnimate(true) @@ -45,13 +72,14 @@ export class GlobalNavigateInterceptor implements IGlobalNavigateInterceptor{ return false } }) - .redirect("LoginPage", RedirectType.REPLACE) + .redirect(RouterConstants.LOGIN_PAGE, RedirectType.REPLACE) } } } + export class UrlInterceptor implements IInterceptor { // 设置拦截器优先级,数值越大则优先执行 priority: number = 10000; @@ -74,7 +102,7 @@ export class UrlInterceptor implements IInterceptor { return context }; - isLink(str: string): boolean { + isLink(str: string): boolean { const linkRegex = /^(hzw:\/\/|http:\/\/|https:\/\/|www\.).+/; return linkRegex.test(str); } @@ -94,15 +122,16 @@ export class UrlInterceptor implements IInterceptor { } return params; } - } export class TestInterceptor implements IInterceptor { - process: (context: InterceptorInfo) => InterceptorInfoOrNull =(context)=>{ + process: (context: InterceptorInfo) => InterceptorInfoOrNull = (context) => { console.log("IInterceptor TestInterceptor: ", context.name) return context } - priority: number = 100; +} + +const globalInterceptor = new GlobalNavigateInterceptor() -} \ No newline at end of file +export { globalInterceptor as GlobalNavInterceptorMgr } \ No newline at end of file diff --git a/products/entry/src/main/ets/pages/Index.ets b/products/entry/src/main/ets/pages/Index.ets index f630762f07f8a218aacf33055576fa67159bba4b..b810f4276e9d06a5c9ed7902cf8713983ceccf90 100644 --- a/products/entry/src/main/ets/pages/Index.ets +++ b/products/entry/src/main/ets/pages/Index.ets @@ -1,5 +1,4 @@ -import { RouterConstants, ToastUtils } from '@hzw/common_library' -import { promptAction, router } from '@kit.ArkUI' +import { RouterConstants } from '@hzw/common_library' import { ILifecycleObserver, Lifecycle, @@ -12,37 +11,30 @@ import { } from 'routerapi' import { RouterConst } from '../constants/RouterConst' import { AddInterceptor } from '../interceptors/AddIntercetors' -import { common, Want } from '@kit.AbilityKit' -import { BusinessError } from '@kit.BasicServicesKit' -import { deviceInfo } from '@kit.BasicServicesKit'; +import { GlobalNavInterceptorMgr } from '../interceptors/interceptors' +import { AnimView } from '../views/AnimView' +import { BottomNavigationBar } from '../views/BottomNavigationBar' +import { MainView } from '../views/MainView' +import { MineView } from '../views/MineView' +import onBackPress from './onBackPress' @Entry @Component struct Index { + // 生命周期管理 如果使用模板化能力,LifecycleRegistry则不需要创建,ZRouter会自动创建接管NavDestiantion的生命周期 @Lifecycle lifecycle: LifecycleRegistry = LifecycleRegistry.create(this) - private context = getContext(this) as common.UIAbilityContext; - private sdkApiVer = deviceInfo.sdkApiVersion - private isSdkApi12 = this.sdkApiVer >= 12 - - desc(): string { - if (this.isSdkApi12) { - return "sdkApiVersion: " + this.sdkApiVer - } else { - return "sdkApiVersion: " + this.sdkApiVer + " 不支持启动模式" - } - } + @State isShowContent: boolean = false aboutToAppear(): void { + // 添加拦截器 AddInterceptor() + // 监听模块是否加载完成 服务路由 ZRouter.setModuleLoadedListener(() => { - // 模块加载完成 console.log("Index 模块加载完成") }) - console.log("Index aboutToAppear sdkApiVersion: " + this.sdkApiVer) - - // @Entry页面生命周期监听 + // @Entry页面生命周期监听 onPageShow onPageHide // 如果在Navigation根视图中设置生命周期监听,建议将inRootView设置为true const that = this this.lifecycle.addObserver({ @@ -59,126 +51,46 @@ struct Index { aboutToDisappear(): void { console.log('Index aboutToDisappear') }, - onWillShow(){ + onWillShow() { console.log('Index onWillShow') } } as ILifecycleObserver, true) - this.lifecycle.addListener((e) => { + // this.lifecycle.addListener((e) => { + // + // }, true) - }, true) + } + onBackPress(): boolean | void { + console.log('Index onBackPress') + return onBackPress() } build() { + // 初始化Navigation路由栈 ,必选 Navigation(ZRouter.getNavStack()) { - Scroll() { - Column({ space: 12 }) { - Text(this.desc()) - Button('har-跳转到HarA模块').onClick((event: ClickEvent) => { - ZRouter.getInstance().navigation(RouterConstants.HARA_MAIN_PAGE) - - }) - Button('har-跳转到HarB模块').onClick((event: ClickEvent) => { - ZRouter.push(RouterConstants.HARB_MAIN_PAGE) - }) - Button('hsp-跳转到Hsp模块').onClick((event: ClickEvent) => { - let data: Record = { "msg": "hello ZRouter", "num": 100 } - ZRouter.getInstance() - .setParam(data) - .setPopListener((info) => { - let msg = `返回携带的数据:${JSON.stringify(info.data)}` - ToastUtils.show(msg) - }) - .navigation(RouterConstants.HSPC_INDEX_PAGE) - }) - - Button("hap-跳转到Hap模块").onClick((event: ClickEvent) => { - let want: Want = { - deviceId: '', // deviceId为空表示本设备 - bundleName: 'com.harmony.router', - abilityName: 'HapAAbility', - moduleName: 'hapA', // moduleName非必选 - parameters: { - // 自定义信息 - route: RouterConstants.ORDER_PAGE - } - }; - this.context.startAbility(want).then(() => { - console.info("startAbility", '已启动Ability'); - }).catch((err: BusinessError) => { - // https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs-V5/faqs-ability-7-V5 - console.error("startAbility", `启动Ability失败. Code: ${err.code}, message: ${err.message}`); - }) - }) - - - Button('404页面').onClick((event: ClickEvent) => { - // 页面不存在,重定向到提示页 - ZRouter.getInstance().navigation("PageNotFound111") - }) - - Button('跳转携带参数与监听pop返回所携带的参数').onClick((event: ClickEvent) => { - ZRouter.getInstance() - .setParam("root data") - .setLunchMode(LaunchMode.STANDARD)// 启动模式 - .enableCrossPageParamReturn()// 跨页面参数返回 - .setAnimate(true) - .setPopListener((r) => { - ToastUtils.show(`pop result: ${r.data} from: ${r.from}`) - }) - .navigation(RouterConstants.HARA_PAGE3) - }) - - - Button("自定义URL跳转").onClick((event: ClickEvent) => { - ZRouter.getInstance().navigation(RouterConstants.CUSTOM_URL_PAGE) - }) - - Button("服务路由-实现模块通信").onClick((event: ClickEvent) => { - ZRouter.getInstance().navigation(RouterConstants.SERVICE_CASE_VIEW) - }) - - Button("生命周期函数管理").onClick((event: ClickEvent) => { - ZRouter.getInstance().navigation(RouterConstants.LIFECYCLE_CASE_VIEW) - }) - - Button("转场动画").onClick((event: ClickEvent) => { - ZRouter.getInstance().navigation(RouterConstants.ANIM_DEMO) - }) - - Button("NavDestination页面模板化").onClick((event: ClickEvent) => { - ZRouter.getInstance().navigation(RouterConstants.CUSTOM_COMPONENT_CHILD_VIEW) - }) - - Button("嵌套Navigation").onClick((event: ClickEvent) => { - ZRouter.getInstance().navigation(RouterConstants.NEST_PAGE) - }) - - Button('退出登录').onClick((event: ClickEvent) => { - AppStorage.set("isLogin", false) - promptAction.showToast({ message: "已退出登录" }) - }) - - Button("模拟第三方Navigation实例使用ZRouter库的Api").onClick((event: ClickEvent) => { - router.pushUrl({ url: RouterConst.NAV_PAGE }) - }).margin({ top: 30 }) - - - } + if (this.isShowContent) { + MainContentView() } } - // .hideNavBar(true) .onAppear(() => { let isStarted = AppStorage.get(RouterConst.SPLASH_TAG) if (!isStarted) { ZRouter.getInstance() + .setParam(Object({ids: 1})) .setAnimate(false) + .setPopListener((r) => { + console.log('Index setPopListener', r) + this.isShowContent = true + }) .navigation(RouterConst.SPLASH_PAGE) } }) + .hideTitleBar(true) .title('Main') .height('100%') .width('100%') + // 可选 .customNavContentTransition(ZRouter.animateMgr()// 创建默认动画管理器 .defaultAnimateBuilder()// 设置进场动画参数 .setEnterAnimate({ duration: 500 })// 设置退场动画参数 @@ -189,4 +101,67 @@ struct Index { .addAnimateOptions(new RotateAnimateOptions({ angle: 90 }))// 自定义转场动画回调 .getAnimCustomNavContentTransition()) } -} \ No newline at end of file +} + + +@ComponentV2 +struct MainContentView { + @Local currentIndex: number = 0; + previousIndex: number = -1; + + aboutToAppear(): void { + GlobalNavInterceptorMgr.onRootShow = () => { + if (this.isLogin && this.previousIndex > 0) { + this.currentIndex = this.previousIndex + } + if (this.previousIndex > 0) { + this.previousIndex = -1 + } + } + } + + build() { + Column() { + Tabs({ index: this.currentIndex }) { + TabContent() { + MainView() + } + + TabContent() { + AnimView() + } + + TabContent() { + MineView({onLogout: () => { + this.currentIndex = 0 + }}) + } + } + .barMode(BarMode.Fixed) + .scrollable(false) + .layoutWeight(1) + .barHeight(0) + + BottomNavigationBar({ + currentIndex: this.currentIndex!!, + onItemClick: (index) => { + if (index === 2) { + if (!this.isLogin) { + this.previousIndex = index + ZRouter.getInstance().navigation(RouterConstants.LOGIN_PAGE) + return true + } + } + return false + } + + }) + }.width('100%') + .height('100%') + } + + private get isLogin() { + return AppStorage.get('isLogin') + } +} + diff --git a/products/entry/src/main/ets/pages/SplashPage.ets b/products/entry/src/main/ets/pages/SplashPage.ets index 5d364d510f96cc19b607109203c441c00f8c89f2..09aca75562dab16f01c831efc8d255ae1dd3db58 100644 --- a/products/entry/src/main/ets/pages/SplashPage.ets +++ b/products/entry/src/main/ets/pages/SplashPage.ets @@ -4,16 +4,24 @@ import { RouterConst } from '../constants/RouterConst'; /** * @author: HZWei * @date: 2024/10/12 - * @desc: + * @desc: 如果@Route不设置任何属性,则默认使用类名作为路由名称 */ -@Route({ name: RouterConst.SPLASH_PAGE }) +@Route({ + param: { + "ids": { + default: -1 + } + } +}) @Component export struct SplashPage { + @State ids: number = -1 @State countdown: number = 3000 build() { NavDestination() { Column() { + Text("测试id参数=" + this.ids) Text('欢迎使用ZRouter') .fontColor(Color.Black) .textAlign(TextAlign.Center) @@ -47,7 +55,7 @@ export struct SplashPage { setInterval(() => { this.countdown -= 1000 if (this.countdown === 0) { - ZRouter.getInstance().pop(true) + ZRouter.getInstance().popWithResult(true) } }, 1000) }) @@ -55,5 +63,6 @@ export struct SplashPage { ZRouter.animateMgr().unregisterAnim(this) AppStorage.setOrCreate(RouterConst.SPLASH_TAG,true) }) + .onBackPressed(() => true) } } \ No newline at end of file diff --git a/products/entry/src/main/ets/pages/anim/AnimTransitionDemo.ets b/products/entry/src/main/ets/pages/anim/AnimTransitionDemo.ets index aab8e454c122331648a8d897947d7244fd0f2f25..bc13958bb44463695b85c4abab4e78c21999b974 100644 --- a/products/entry/src/main/ets/pages/anim/AnimTransitionDemo.ets +++ b/products/entry/src/main/ets/pages/anim/AnimTransitionDemo.ets @@ -64,6 +64,10 @@ export struct AnimTransitionDemo { Button('系统默认转场').onClick(() => { ZRouter.getInstance().push(RouterConstants.PAGE_FOUR) }) + Button("卡片一镜到底").onClick((event: ClickEvent) => { + ZRouter.getInstance() + .navigation(RouterConstants.PAGE_CARD_ONE) + }) } } diff --git a/products/entry/src/main/ets/pages/anim/card/CardComponent.ets b/products/entry/src/main/ets/pages/anim/card/CardComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..3f2b5f0c3c65c6710eb1fdb734041630b8247bf7 --- /dev/null +++ b/products/entry/src/main/ets/pages/anim/card/CardComponent.ets @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { CardAttr } from './WaterFlowDataSource'; + +@Component +export struct CardComponent { + @Prop url: ResourceStr = ''; + onColumnClicked?: (url: ResourceStr) => void = (_url: ResourceStr) => { + }; + + build() { + Column() { + Image(this.url) + .size({ width: '100%' }) + .objectFit(ImageFit.Auto) + .draggable(false) + + Column() { + Text($r('app.string.content')) + .size({ + width: '100%', + height: '100%' + }) + .fontColor('#E6000000') + .fontSize(14) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + } + .size({ + width: '100%', + height: 58 + }) + .padding(12) + } + .backgroundColor(Color.White) + .size({ width: '100%' }) + .onClick(() => { + this.onColumnClicked?.(this.url); + }) + } +} \ No newline at end of file diff --git a/products/entry/src/main/ets/pages/anim/card/CardLongTakeTransitionPageOne.ets b/products/entry/src/main/ets/pages/anim/card/CardLongTakeTransitionPageOne.ets new file mode 100644 index 0000000000000000000000000000000000000000..4236e3f2862a5f69219180c196afef888eecaea2 --- /dev/null +++ b/products/entry/src/main/ets/pages/anim/card/CardLongTakeTransitionPageOne.ets @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CardAttr, WaterFlowDataSource } from './WaterFlowDataSource'; +import { Route, ZRouter } from 'routerapi'; +import { RouterConstants } from '@hzw/common_library'; +import { CardComponent } from './CardComponent'; +import { getResourceString } from './ResourceString'; + + +@Route({ name: RouterConstants.PAGE_CARD_ONE }) +@Component +export struct CardLongTakeTransitionPageOne { + @State dataSource: WaterFlowDataSource = new WaterFlowDataSource(); + @State columnType: string = ''; + @StorageProp('currentBreakpoint') @Watch('upDateColumnData') currentBreakpoint: string = ''; + + aboutToAppear(): void { + for (let i = 0; i < 100; i++) { + this.dataSource.pushData(new CardAttr()); + } + this.upDateColumnData(); + } + + private upDateColumnData(): void { + let currentBreakpoint: string | undefined = AppStorage.get('currentBreakpoint'); + if (currentBreakpoint === 'xs' || currentBreakpoint === 'sm') { + this.columnType = '1fr 1fr'; + } else if (currentBreakpoint === 'md') { + this.columnType = '1fr 1fr 1fr'; + } else { + this.columnType = '1fr 1fr 1fr 1fr'; + } + } + + build() { + NavDestination() { + Stack() { + WaterFlow() { + LazyForEach(this.dataSource, (item: CardAttr, index: number) => { + FlowItem() { + CardComponent({ + url: $r(`app.media.img_${(index % 6)}`), + onColumnClicked: (url: ResourceStr) => { + ZRouter + .getInstance() + .animateMgr() + .withSharedCardParams({ + imageRes: url, + componentId: `FlowItem_${index}`, + }) + .push(RouterConstants.PAGE_CARD_TWO) + } + }) + } + .width('100%') + .borderRadius(10) + .clip(true) + .id(`FlowItem_${index}`) + }, (item: string) => item) + } + .columnsTemplate(this.columnType) + .columnsGap(10) + .rowsGap(10) + .width('100%') + .height('100%') + } + .size({ + width: '100%', + height: '100%' + }) + .padding({ + left: 16, + right: 16 + }) + } + .backgroundColor('#F1F3F5') + .title(getResourceString($r('app.string.card_title'), this)) + .onDisAppear(() => { + ZRouter.animateMgr().unregisterAnim(this) + }) + .attributeModifier(ZRouter.animateMgr().modifier(this)) + } +} \ No newline at end of file diff --git a/products/entry/src/main/ets/pages/anim/card/CardLongTakeTransitionPageTwo.ets b/products/entry/src/main/ets/pages/anim/card/CardLongTakeTransitionPageTwo.ets new file mode 100644 index 0000000000000000000000000000000000000000..c1d17a6ab4aeadbccf82a5e57430ab64d8aa0276 --- /dev/null +++ b/products/entry/src/main/ets/pages/anim/card/CardLongTakeTransitionPageTwo.ets @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RouterConstants } from '@hzw/common_library'; +import { Route, SharedCardContainer, ZRouter } from 'routerapi'; +import { CardComponent } from './CardComponent'; +import { CardAttr, WaterFlowDataSource } from './WaterFlowDataSource'; + +@Builder +export function CardLongTakeTransitionPageTwoBuilder() { + CardLongTakeTransitionPageTwo() +} + +@Route({ name: RouterConstants.PAGE_CARD_TWO }) +@Component +export struct CardLongTakeTransitionPageTwo { + @State dataSource: WaterFlowDataSource = new WaterFlowDataSource(); + @State flag: number = 0 + + aboutToAppear(): void { + for (let i = 0; i < 100; i++) { + this.dataSource.pushData(new CardAttr()); + } + this.flag = ZRouter.getInstance().getParamByKey("flag") + } + + onBackPressed(): boolean { + ZRouter.getInstance().pop() + return true; + } + + @Builder + Content() { + Scroll() { + Column() { + Row({ space: 10 }) { + Image($r('app.media.icon_back')) + .width(40) + .height(40) + .onClick(() => { + this.onBackPressed(); + }) + .margin({ left: 20 }) + + Text($r('app.string.page_show')) + .height(56) + .width('100%') + .fontSize(22) + .fontWeight(FontWeight.Bold) + } + .clip(true) + .width('100%') + .height(px2vp(182)) + .alignItems(VerticalAlign.Center) + + Image(ZRouter.animateMgr().sharedCard().getSharedUrl()) + .size({ width: '100%' }) + .objectFit(ImageFit.Auto) + .id(ZRouter.animateMgr().sharedCard().getSharedComponentId()) + + List() { + LazyForEach(this.dataSource, (item: CardAttr, index: number) => { + ListItem() { + CardComponent({ + url: $r(`app.media.img_${(index % 6)}`), + onColumnClicked: (url: ResourceStr) => { + ZRouter.animateMgr() + .setNavigation(ZRouter.getInstance().withParam("flag", this.flag+1)) + .withSharedCardParams({ + imageRes: url, + componentId: `FlowItem_${this.flag}_${index}`, + }) + .push(RouterConstants.PAGE_CARD_TWO) + } + }) + } + .borderRadius(10) + .clip(true) + .width(100) + .id(`FlowItem_${this.flag}_${index}`) + }, (item: string) => item) + } + .listDirection(Axis.Horizontal) + .width('100%') + .margin({ top: 1 }) + } + } + } + + build() { + NavDestination() { + SharedCardContainer({ + bgColor: Color.White, + content: () => { + this.Content() + } + }) + } + .onReady((context: NavDestinationContext) => { + ZRouter.animateMgr() + .sharedCard() + .setIsAdaptImmersive(false) + .setNavBgColor(Color.White) + .registerSharedCardAnimParam(this, context) + .setEnterAnimate({ duration: 500 }) + .setExitAnimate({ duration: 500 }) + }) + .onDisAppear(() => { + ZRouter.animateMgr().sharedCard().unregisterSharedCardAnim(this) + }) + .hideTitleBar(true) + .backgroundColor(Color.Transparent) + .attributeModifier(ZRouter.animateMgr().modifier(this)) + .onBackPressed(() => { + return this.onBackPressed(); + }) + } +} \ No newline at end of file diff --git a/products/entry/src/main/ets/pages/anim/card/DetailPageContent.ets b/products/entry/src/main/ets/pages/anim/card/DetailPageContent.ets new file mode 100644 index 0000000000000000000000000000000000000000..6208f28ccfd43bf196d7bcb501a535620eaa425b --- /dev/null +++ b/products/entry/src/main/ets/pages/anim/card/DetailPageContent.ets @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ZRouter } from 'routerapi'; + +@Component +export struct DetailPageContent { + @StorageProp('currentBreakpoint') @Watch('updateIsLargeSize') currentBreakpoint: string = ''; + @State isLargeSize: boolean = false; + public onBackPressed: () => void = () => { + }; + + private updateIsLargeSize(): void { + this.isLargeSize = (this.currentBreakpoint === 'md' || this.currentBreakpoint === 'lg'); + } + + aboutToAppear(): void { + this.updateIsLargeSize(); + } + + @Builder + MyTitleBuilder() { + Row({ space: 10 }) { + Image($r('app.media.icon_back')) + .width(40) + .height(40) + .onClick(() => { + this.onBackPressed(); + }) + .margin({ left: 20 }) + + Text($r('app.string.img')) + .height(56) + .width('100%') + .fontSize(22) + .fontWeight(FontWeight.Bold) + } + .clip(true) + .width('100%') + .height(px2vp(182)) + .alignItems(VerticalAlign.Center) + } + + build() { + if (!this.isLargeSize) { + Column() { + this.MyTitleBuilder() + Image(ZRouter.animateMgr().sharedCard().getSharedUrl()) + .size({ width: '100%' }) + .objectFit(ImageFit.Auto) + .id(ZRouter.animateMgr().sharedCard().getSharedComponentId()) + } + .size({ + width: px2vp(ZRouter.animateMgr().sharedCard().getWindowWidthPx()), + height: px2vp(ZRouter.animateMgr().sharedCard().getWindowHeightPx()) + }) + } else { + Column() { + this.MyTitleBuilder() + + Stack() { + Image(ZRouter.animateMgr().sharedCard().getSharedUrl()) + .size({ width: '50%' }) + .objectFit(ImageFit.Auto) + .id(ZRouter.animateMgr().sharedCard().getSharedComponentId()) + } + .width('100%') + + Text($r('app.string.content')) + .width('50%') + .align(Alignment.TopStart) + .margin({ top: 16 }) + } + .size({ + width: px2vp(ZRouter.animateMgr().sharedCard().getWindowWidthPx()), + height: px2vp(ZRouter.animateMgr().sharedCard().getWindowHeightPx()) + }) + } + } +} \ No newline at end of file diff --git a/products/entry/src/main/ets/pages/anim/card/ResourceString.ets b/products/entry/src/main/ets/pages/anim/card/ResourceString.ets new file mode 100644 index 0000000000000000000000000000000000000000..45b7542cb2fff0824b5319703dbb594be829abad --- /dev/null +++ b/products/entry/src/main/ets/pages/anim/card/ResourceString.ets @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function getResourceString(resource: Resource, __this: object): string { + let resourceString = getContext(__this).resourceManager.getStringSync(resource.id); + return resourceString; +} \ No newline at end of file diff --git a/products/entry/src/main/ets/pages/anim/card/WaterFlowDataSource.ets b/products/entry/src/main/ets/pages/anim/card/WaterFlowDataSource.ets new file mode 100644 index 0000000000000000000000000000000000000000..533ef28a504d0892594a305bf9c4cf1cb61f15eb --- /dev/null +++ b/products/entry/src/main/ets/pages/anim/card/WaterFlowDataSource.ets @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@Observed +export class CardAttr { + public isVisible: Visibility = Visibility.Visible; + public alphaValue: number = 1; + public scaleValue: number = 1; +} + +abstract class BasicDataSource implements IDataSource { + private listeners: DataChangeListener[] = []; + + public totalCount(): number { + return 0; + } + + abstract getData(index: number): CardAttr; + + registerDataChangeListener(listener: DataChangeListener): void { + if (this.listeners.indexOf(listener) < 0) { + this.listeners.push(listener); + } + } + + unregisterDataChangeListener(listener: DataChangeListener): void { + const pos = this.listeners.indexOf(listener); + if (pos >= 0) { + this.listeners.splice(pos, 1); + } + } + + notifyDataReload(): void { + this.listeners.forEach(listener => { + listener.onDataReloaded(); + }) + } + + notifyDataAdd(index: number): void { + this.listeners.forEach(listener => { + listener.onDataAdd(index); + }) + } + + notifyDataChange(index: number): void { + this.listeners.forEach(listener => { + listener.onDataChange(index); + }) + } + + notifyDataDelete(index: number): void { + this.listeners.forEach(listener => { + listener.onDataDelete(index); + }) + } + + notifyDataMove(from: number, to: number): void { + this.listeners.forEach(listener => { + listener.onDataMove(from, to); + }) + } +} + +export class WaterFlowDataSource extends BasicDataSource { + private dataArray: Array = []; + + public totalCount(): number { + return this.dataArray.length; + } + + public getData(index: number): CardAttr { + return this.dataArray[index]; + } + + public addData(index: number, data: CardAttr): void { + this.dataArray.splice(index, 0, data); + this.notifyDataAdd(index); + } + + public popData(): void { + this.dataArray.pop(); + this.notifyDataDelete(this.dataArray.length); + } + + public pushData(data: CardAttr): void { + this.dataArray.push(data); + this.notifyDataAdd(this.dataArray.length - 1); + } +} \ No newline at end of file diff --git a/products/entry/src/main/ets/pages/navdest/CustomComponentView.ets b/products/entry/src/main/ets/pages/navdest/CustomComponentView.ets index 2b7e68a474912961e5b7b04b78b074bcb2b1a953..3d53e46b657924a9426e70d136cec7329e2c7eac 100644 --- a/products/entry/src/main/ets/pages/navdest/CustomComponentView.ets +++ b/products/entry/src/main/ets/pages/navdest/CustomComponentView.ets @@ -1,5 +1,6 @@ import { RouterConstants, ToastUtils } from '@hzw/common_library'; import { LifecycleState, Route, ZRouter } from 'routerapi'; +import onBackPress from '../onBackPress'; import { CustomAttributeModifier } from './CustomAttributeModifier'; import { CustomCompLifecycleObserver } from './CustomCompLifecycleObserver'; @@ -15,7 +16,7 @@ import { CustomCompLifecycleObserver } from './CustomCompLifecycleObserver'; export struct ChildView { // 通过@Consume获取生命周期观察者 // 生命周期监听方式一 - @Consume lifecycleObserver: CustomCompLifecycleObserver + @Consume viewModel: CustomCompLifecycleObserver private lastBackTime: number = 0 @@ -27,15 +28,7 @@ export struct ChildView { console.log("ChildView CustomComponentView ", state, router?.navDestinationId) if (state === LifecycleState.ON_BACK_PRESS) { // 拦截返回事件,与可以在lifecycleObserver做拦截处理 - if (Date.now() - this.lastBackTime < 2000 || this.lastBackTime === 0) { - if (Date.now() - this.lastBackTime < 2000 ) { - return false - } - this.lastBackTime = Date.now() - ToastUtils.show("再按一次退出") - return true - } - return false + return onBackPress() } return false @@ -47,9 +40,9 @@ export struct ChildView { Text("NavDest页面模板化:即页面无需用NavDestination组件包裹,在编译阶段会自动生成对应的逻辑," + "提供了额外的生命周期回调,并支持自定义NavDestination属性。") .padding(20) - Text((this.lifecycleObserver.counter) + "") + Text((this.viewModel.counter) + "") Button("+1 - lifecycleObserver").onClick((event: ClickEvent) => { - this.lifecycleObserver.counter++ + this.viewModel.counter++ }) Button("CustomComp2").onClick((event: ClickEvent) => { diff --git a/products/entry/src/main/ets/pages/nesting/NestPage.ets b/products/entry/src/main/ets/pages/nesting/NestPage.ets index 8ce0a6816eb1117cdd9e24a5bc916eb41c2bdf06..93e5d7acaaa4275e989beaa04c778e6428f8379b 100644 --- a/products/entry/src/main/ets/pages/nesting/NestPage.ets +++ b/products/entry/src/main/ets/pages/nesting/NestPage.ets @@ -21,11 +21,11 @@ export struct NestPage { .width('100%') Button('返回 - 默认栈').onClick((event: ClickEvent) => { - ZRouter.getInstance('ZRouter').pop() + ZRouter.getInstance().pop() }).fontColor(Color.White) Button('返回').onClick((event: ClickEvent) => { - ZRouter.getInstance().pop() + ZRouter.getInstance(newTaskName).pop() }).fontColor(Color.White) } diff --git a/products/entry/src/main/ets/pages/onBackPress.ets b/products/entry/src/main/ets/pages/onBackPress.ets new file mode 100644 index 0000000000000000000000000000000000000000..1ba3f33d4abc9bf7d361abdd23a0e0b6893545b8 --- /dev/null +++ b/products/entry/src/main/ets/pages/onBackPress.ets @@ -0,0 +1,32 @@ +/** + * @author: HZWei + * @date: 2025/4/19 + * @desc: + */ + +import { ToastUtils } from "@hzw/common_library"; +import { common } from "@kit.AbilityKit"; + +// 使用闭包封装 backPressTime,避免全局变量污染 +const onBackPress = (): ((context?: common.UIAbilityContext) => boolean) => { + let backPressTime: number = 0; + const DOUBLE_PRESS_INTERVAL = 2000; + + return (context?: common.UIAbilityContext): boolean => { + const currentTime = Date.now(); + const isDoublePress = currentTime - backPressTime < DOUBLE_PRESS_INTERVAL; + + if (isDoublePress) { + if (context) { + context.terminateSelf(); + } + return false; + } + + backPressTime = currentTime; + ToastUtils.show("再按一次退出"); + return true; + }; +}; + +export default onBackPress(); \ No newline at end of file diff --git a/products/entry/src/main/ets/views/OrderDetailsPage.ets b/products/entry/src/main/ets/pages/order/OrderDetailsPage.ets similarity index 100% rename from products/entry/src/main/ets/views/OrderDetailsPage.ets rename to products/entry/src/main/ets/pages/order/OrderDetailsPage.ets diff --git a/products/entry/src/main/ets/views/OrderPage.ets b/products/entry/src/main/ets/pages/order/OrderPage.ets similarity index 100% rename from products/entry/src/main/ets/views/OrderPage.ets rename to products/entry/src/main/ets/pages/order/OrderPage.ets diff --git a/products/entry/src/main/ets/views/AnimView.ets b/products/entry/src/main/ets/views/AnimView.ets new file mode 100644 index 0000000000000000000000000000000000000000..7cc3914be3f0e867becb149506504c2f99c322a8 --- /dev/null +++ b/products/entry/src/main/ets/views/AnimView.ets @@ -0,0 +1,18 @@ +/** + * @author: HZWei + * @date: 2025/5/24 + * @desc: + */ +import { RouterConstants } from '@hzw/common_library' +import { ZRouter } from 'routerapi' + +@ComponentV2 +export struct AnimView { + build() { + Column({ space: 10 }) { + Button("转场动画").onClick((event: ClickEvent) => { + ZRouter.getInstance().navigation(RouterConstants.ANIM_DEMO) + }) + } + } +} \ No newline at end of file diff --git a/products/entry/src/main/ets/views/BottomNavigationBar.ets b/products/entry/src/main/ets/views/BottomNavigationBar.ets new file mode 100644 index 0000000000000000000000000000000000000000..6484cfa5a689a2f0124a0c595b2a033788e9c005 --- /dev/null +++ b/products/entry/src/main/ets/views/BottomNavigationBar.ets @@ -0,0 +1,63 @@ +/** + * @author: HZWei + * @date: 2025/5/24 + * @desc: + */ + +@ComponentV2 +export struct BottomNavigationBar { + @Param @Require currentIndex: number = 0; + @Event private $currentIndex: (index: number) => void = (index) => { + } + @Param onItemClick?: (index: number) => boolean | void = (index) => { + + } + private tabList = [ + '首页', '动画', '我的' + ] + + build() { + Flex({ + direction: FlexDirection.Row, + justifyContent: FlexAlign.Center, + alignItems: ItemAlign.Center, + }) { + ForEach(this.tabList, (item: string, index) => { + this.tabBuilder(item, index) + }) + }.width('100%') + .height(50) + .shadow({ + color: Color.Gray, + radius: 1 + }) + } + + @Builder + tabBuilder(title: string, targetIndex: number, selectedImg?: Resource, normalImg?: Resource) { + Column() { + if (selectedImg) { + Image(this.currentIndex === targetIndex ? selectedImg : normalImg) + .size({ width: 25, height: 25 }) + } + Text(title) + .fontWeight(this.currentIndex === targetIndex ? FontWeight.Bold : FontWeight.Normal) + .fontColor(this.currentIndex === targetIndex ? Color.Blue : '#6B6B6B') + } + .width('100%') + .height(50) + .justifyContent(FlexAlign.Center) + .onClick(() => { + if (this.onItemClick) { + const r = this.onItemClick(targetIndex) + if (typeof r === 'boolean' && r) { + } else { + this.$currentIndex(targetIndex) + } + } else { + this.$currentIndex(targetIndex) + } + + }) + } +} \ No newline at end of file diff --git a/products/entry/src/main/ets/views/MainView.ets b/products/entry/src/main/ets/views/MainView.ets new file mode 100644 index 0000000000000000000000000000000000000000..184662a91d32b2ca75ca679937ef995983c11dbc --- /dev/null +++ b/products/entry/src/main/ets/views/MainView.ets @@ -0,0 +1,134 @@ +/** + * @author: HZWei + * @date: 2025/4/19 + * @desc: + */ +import { LifecycleState, RouterInfo, ZRoute, ZRouter } from "routerapi"; +import { RouterConst } from "../constants/RouterConst"; +import { common, Want } from "@kit.AbilityKit"; +import { BusinessError, deviceInfo } from "@kit.BasicServicesKit"; +import { RouterConstants, ToastUtils } from "@hzw/common_library"; +import { promptAction, router } from "@kit.ArkUI"; + +@Component +export struct MainView { + private context = getContext(this) as common.UIAbilityContext; + private sdkApiVer = deviceInfo.sdkApiVersion + private isSdkApi12 = this.sdkApiVer >= 12 + + desc(): string { + if (this.isSdkApi12) { + return "sdkApiVersion: " + this.sdkApiVer + } else { + return "sdkApiVersion: " + this.sdkApiVer + " 不支持启动模式" + } + } + + + + build() { + this.MainBuilder() + } + + @Builder + MainBuilder() { + Scroll() { + Column({ space: 12 }) { + Text(this.desc()) + Button('har-跳转到HarA模块').onClick((event: ClickEvent) => { + ZRouter.getInstance().navigation(RouterConstants.HARA_MAIN_PAGE) + + }) + Button('har-跳转到HarB模块').onClick((event: ClickEvent) => { + ZRouter.push(RouterConstants.HARB_MAIN_PAGE) + }) + Button('hsp-跳转到Hsp模块').onClick((event: ClickEvent) => { + ZRouter.getInstance() + .withParam("isRoot", true) + .withParam("msg", "hello ZRouter") + .setPopListener((info) => { + let msg = `返回携带的数据:${JSON.stringify(info.data)}` + ToastUtils.show(msg) + }) + .navigation(RouterConstants.HSPC_INDEX_PAGE) + }) + + Button("hap-跳转到Hap模块").onClick((event: ClickEvent) => { + let want: Want = { + deviceId: '', // deviceId为空表示本设备 + bundleName: 'com.harmony.router', + abilityName: 'HapAAbility', + moduleName: 'hapA', // moduleName非必选 + parameters: { + // 自定义信息 + route: RouterConstants.ORDER_PAGE + } + }; + this.context.startAbility(want).then(() => { + console.info("startAbility", '已启动Ability'); + }).catch((err: BusinessError) => { + // https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs-V5/faqs-ability-7-V5 + console.error("startAbility", `启动Ability失败. Code: ${err.code}, message: ${err.message}`); + }) + }) + + + Button('404页面').onClick((event: ClickEvent) => { + // 页面不存在,重定向到提示页 + ZRouter.getInstance().navigation("PageNotFound111") + }) + + Button('跳转携带参数与监听pop携带的参数').onClick((event: ClickEvent) => { + ZRouter.getInstance() + .setParam("root data") + .setLunchMode(LaunchMode.STANDARD)// 启动模式 + .listenPopResultOnRootView()// 跨页面携带参数返回根视图 + .setAnimate(true) + .setPopListener((r) => { + ToastUtils.show(`pop result: ${r.data} from: ${r.from}`) + }) + .navigation(RouterConstants.PAGE_PUSH_AND_POP_CASE) + }) + + Button("拦截器案例 跳转前拦截 - onNavigateBefore").onClick((event: ClickEvent) => { + ZRouter.getInstance() + .setParam(" 正常数据 ") + .navigation(RouterConstants.PAGE_BEFORE_PUSH) + }) + + + Button("自定义URL跳转").onClick((event: ClickEvent) => { + ZRouter.getInstance().navigation(RouterConstants.CUSTOM_URL_PAGE) + }) + + Button("服务路由-实现模块通信").onClick((event: ClickEvent) => { + ZRouter.getInstance().navigation(RouterConstants.SERVICE_CASE_VIEW) + }) + + Button("生命周期函数管理").onClick((event: ClickEvent) => { + ZRouter.getInstance().navigation(RouterConstants.LIFECYCLE_CASE_VIEW) + }) + + + + Button("NavDestination页面模板化").onClick((event: ClickEvent) => { + ZRouter.getInstance().navigation(RouterConstants.CUSTOM_COMPONENT_CHILD_VIEW) + }) + + Button("嵌套Navigation").onClick((event: ClickEvent) => { + ZRouter.getInstance().navigation(RouterConstants.NEST_PAGE) + }) + + + + Button("模拟第三方Navigation实例使用ZRouter库的Api").onClick((event: ClickEvent) => { + router.pushUrl({ url: RouterConst.NAV_PAGE }) + }).margin({ top: 30 }) + .visibility(Visibility.None) + + } + } + } +} + + diff --git a/products/entry/src/main/ets/views/MineView.ets b/products/entry/src/main/ets/views/MineView.ets new file mode 100644 index 0000000000000000000000000000000000000000..8a6207f34e760a944ee5498e9a58cf5fc8bc66c6 --- /dev/null +++ b/products/entry/src/main/ets/views/MineView.ets @@ -0,0 +1,25 @@ +/** + * @author: HZWei + * @date: 2025/5/24 + * @desc: + */ +import { promptAction } from '@kit.ArkUI' +import { GlobalNavInterceptorMgr } from '../interceptors/interceptors' + +@Preview +@ComponentV2 +export struct MineView { + @Param onLogout: () => void = () => { + + } + build() { + Column({ space: 10 }) { + Button('退出登录').onClick((event: ClickEvent) => { + AppStorage.set("isLogin", false) + GlobalNavInterceptorMgr.count = 0 + promptAction.showToast({ message: "已退出登录" }) + this.onLogout?.() + }) + } + } +} \ No newline at end of file diff --git a/products/entry/src/main/resources/base/element/color.json b/products/entry/src/main/resources/base/element/color.json index 3c712962da3c2751c2b9ddb53559afcbd2b54a02..7d6621b71585ab0e0d464fb38f8e315b965e1c88 100644 --- a/products/entry/src/main/resources/base/element/color.json +++ b/products/entry/src/main/resources/base/element/color.json @@ -3,6 +3,10 @@ { "name": "start_window_background", "value": "#FFFFFF" + }, + { + "name": "water_flow_background_color", + "value": "#F1F3F5" } ] } \ No newline at end of file diff --git a/products/entry/src/main/resources/base/element/string.json b/products/entry/src/main/resources/base/element/string.json index f825e3f2875e68cf5d8bb5961e55bd9385b4f5ba..12e3273f84f462b79f40bd0ac6b2e3e2d6429295 100644 --- a/products/entry/src/main/resources/base/element/string.json +++ b/products/entry/src/main/resources/base/element/string.json @@ -1,5 +1,9 @@ { "string": [ + { + "name": "agcit_router_tools", + "value": "1.4.1" + }, { "name": "module_desc", "value": "module description" diff --git a/products/entry/src/main/resources/base/media/icon_back.png b/products/entry/src/main/resources/base/media/icon_back.png new file mode 100644 index 0000000000000000000000000000000000000000..71449b13f5802cf30bd91f19f91c780fe71bc900 Binary files /dev/null and b/products/entry/src/main/resources/base/media/icon_back.png differ diff --git a/products/entry/src/main/resources/base/media/img_0.jpg b/products/entry/src/main/resources/base/media/img_0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a0c66de1d8d4a51a7fb14143c3c4cf4c44e78302 Binary files /dev/null and b/products/entry/src/main/resources/base/media/img_0.jpg differ diff --git a/products/entry/src/main/resources/base/media/img_1.jpg b/products/entry/src/main/resources/base/media/img_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4d90cdd19fe776b59ec48a93d0960d39c249237c Binary files /dev/null and b/products/entry/src/main/resources/base/media/img_1.jpg differ diff --git a/products/entry/src/main/resources/base/media/img_2.jpg b/products/entry/src/main/resources/base/media/img_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fdd486a03bb8b606bb8a07e6788b207f1d467a32 Binary files /dev/null and b/products/entry/src/main/resources/base/media/img_2.jpg differ diff --git a/products/entry/src/main/resources/base/media/img_3.jpg b/products/entry/src/main/resources/base/media/img_3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..50773c61f09f5405cb630c78bcb02d0c3eac7917 Binary files /dev/null and b/products/entry/src/main/resources/base/media/img_3.jpg differ diff --git a/products/entry/src/main/resources/base/media/img_4.jpg b/products/entry/src/main/resources/base/media/img_4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5c226925fb081c947cb42eebcdac6c4f319302aa Binary files /dev/null and b/products/entry/src/main/resources/base/media/img_4.jpg differ diff --git a/products/entry/src/main/resources/base/media/img_5.jpg b/products/entry/src/main/resources/base/media/img_5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5ca2e7da214b2e00904f9ed7460916daadc1f54d Binary files /dev/null and b/products/entry/src/main/resources/base/media/img_5.jpg differ diff --git a/products/entry/src/main/resources/base/media/img_6.jpg b/products/entry/src/main/resources/base/media/img_6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1fc62657e69b353196759bc5e9a18ff1d81a7fdb Binary files /dev/null and b/products/entry/src/main/resources/base/media/img_6.jpg differ diff --git a/products/entry/src/main/resources/base/media/img_7.jpg b/products/entry/src/main/resources/base/media/img_7.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ca6a84d44c94b528634da7fd1de6f3fb12306688 Binary files /dev/null and b/products/entry/src/main/resources/base/media/img_7.jpg differ diff --git a/products/entry/src/main/resources/base/media/img_8.jpg b/products/entry/src/main/resources/base/media/img_8.jpg new file mode 100644 index 0000000000000000000000000000000000000000..48aae44de9b6e46712b3bca8345d25c69e49299f Binary files /dev/null and b/products/entry/src/main/resources/base/media/img_8.jpg differ diff --git a/products/entry/src/main/resources/base/media/img_9.jpg b/products/entry/src/main/resources/base/media/img_9.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7157275a96c502b4ca45c07a062bf863e6d70955 Binary files /dev/null and b/products/entry/src/main/resources/base/media/img_9.jpg differ diff --git a/products/hapA/hvigorfile.ts b/products/hapA/hvigorfile.ts index e490a599956ab36fcc9540f0915f4a665c808e20..6dd92d710982e562ffed3ddfd667c66eb5c2e9ae 100644 --- a/products/hapA/hvigorfile.ts +++ b/products/hapA/hvigorfile.ts @@ -1,6 +1,6 @@ import { hapTasks } from '@ohos/hvigor-ohos-plugin'; -import { routerRegisterPlugin, PluginConfig } from 'router-register-plugin' +// import { routerRegisterPlugin, PluginConfig } from 'router-register-plugin' export default { system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ - plugins:[routerRegisterPlugin({})] /* Custom plugin to extend the functionality of Hvigor. */ + plugins:[/*routerRegisterPlugin({})*/] /* Custom plugin to extend the functionality of Hvigor. */ } diff --git "a/\346\216\245\345\217\243\345\210\227\350\241\250.md" "b/\346\216\245\345\217\243\345\210\227\350\241\250.md" new file mode 100644 index 0000000000000000000000000000000000000000..c681e8f996816eb9dfe2446d498748a2aa848643 --- /dev/null +++ "b/\346\216\245\345\217\243\345\210\227\350\241\250.md" @@ -0,0 +1,61 @@ +## 接口列表 + +- ZRouter: 是路由管理器,包括了初始化、路由操作、服务路由、生命周期、拦截器、模板化、动画等功能的管理 +- NavDestBuilder: 路由的进出栈管理,主要包括跳转、返回、替换、移除等操作。 + +### ZRouter 类接口 + +| 方法名 | 描述 | 参数 | 返回值 | 废弃状态 | +|------------------------------|-----------------------------------|-------------------------------------------------------------------------------------|--------------------------------|------| +| `initialize` | 初始化路由,可传入一个配置函数 | `invoke?: ((config: ConfigInitializer) => void)` | `void` | 否 | +| `setModuleLoadedListener` | 设置模块加载完成的监听器,在使用了服务路由会生效 | `listener: () => void` | `void` | 否 | +| `isDynamicLoadedComplete` | 判断模块是否动态加载完成 | 无 | `boolean` | 否 | +| `getInstance` | 获取路由操作的实例,用于进行跳转、返回等操作 | `stackName: string = DEFAULT_STACK_NAME` | `NavDestBuilder` | 否 | +| `addService` | 添加服务路由,外部不需要手动调用,会在编译阶段自动注册 | `name: string`, `service: IProvider` | `void` | 否 | +| `getService` | 获取服务路由管理类 | `name: string` | `T extends IProvider null` | 否 | +| `animateMgr` | 获取动画管理类 | 无 | `NavAnimationMgr` | 否 | +| `registerNavStack` | 注册路由栈 | `stackName: string = DEFAULT_STACK_NAME`, `pathStack: NavPathStack` | `void` | 否 | +| `unregisterNavStack` | 解除注册路由栈 | `stackName: string` | `void` | 否 | +| `getNavStack` | 获取默认栈名的路由栈 | `willShow?: InterceptionShowCallback` | `NavPathStack` | 否 | +| `getNavStackByName` | 根据栈名获取路由栈,通常用不上 | `stackName: string` | `NavPathStack` | 否 | +| `getCurrentNavStack` | 获取当前路由栈实例 | 无 | `NavPathStack` | 否 | +| `getLastNavDestinationId` | 获取当前路由栈的栈顶页面 `DestinationId` | 无 | `string or undefined` | 否 | +| `setGlobalInterceptor` | 设置全局拦截器 | `interceptor: IGlobalNavigateInterceptor`, `stackName: string = DEFAULT_STACK_NAME` | `void` | 否 | +| `setInterceptor` | 添加拦截器 | `interceptor: T extends IInterceptor`, `stackName: string = DEFAULT_STACK_NAME` | `void` | 否 | +| `removeInterceptor` | 移除拦截器 | `interceptor: T extends IInterceptor`, `stackName: string = DEFAULT_STACK_NAME` | `boolean` | 否 | +| `addGlobalLifecycleObserver` | 添加全局的 `NavDestination` 页面的生命周期观察者 | `observer: IL extends ILifecycleObserver` | 未明确 | 否 | +| `addLifecycleObserver` | 添加单个 `NavDestination` 页面的生命周期观察者 | `observer: LifecycleObserver` | 未明确 | 否 | +| `templateMgr` | 获取 `NavDestination` 页面模板管理器,通常用不上 | 无 | `TemplateMgr` | 否 | +| `getCurrentStackName` | 获取当前路由栈名称 | 无 | `string` | 否 | +| `getParamByKey` | 获取路由参数,建议带上默认值和泛型 | `key: string`, `defVal: ObjectOrNull = null` | `P = ObjectOrNull` | 否 | + +### NavDestBuilder 类接口 + +| 方法名 | 描述 | 参数 | 返回值 | 废弃状态 | +|----------------------------------------------|----------------------------------------------------------------------|-------------------------------------------------------------|---------------------|------| +| `setAnimate` | 设置是否启动动画 | `animated: boolean` | `NavDestBuilder` | 否 | +| `setLunchMode` | 设置启动模式,api12 起才会生效 | `mode: LaunchMode` | `NavDestBuilder` | 否 | +| `setParam` | 设置页面跳转携带的参数,通过`getParam`获取参数 | `param: ObjectOrNull` | `NavDestBuilder` | 否 | +| `withParam` | 设置页面跳转携带的参数,key-value形式,建议使用,通过`ZRouter.getParamByKey(key)`获取参数 | `key: string`, `value: ObjectOrNull` | `NavDestBuilder` | 否 | +| `setPopListener` | 监听页面返回携带的结果,所有页面返回结果都会回调着 | `callback: OnPopResultCallback` | `NavDestBuilder` | 否 | +| ~~`enableCrossPageParamReturn`~~ | 跳转时开启跨多级页面回调监听,可在`onPopListener`回调函数内监听 - 弃用 | 无 | `NavDestBuilder` | 是 | +| `listenPopResultOnRootView` | 如果在Navigation根视图中push需要监听返回数据 \n 则需要设置listenPopResultOnRootView为true | 无 | `NavDestBuilder` | 否 | +| `enableConvertJumpParamZeroAndFalseToString` | 开启将 0 和 false 转成字符串 | 无 | `NavDestBuilder` | 是 | +| `navigation` | 页面跳转 | `name: string` | `void` | 否 | +| `push` | 页面跳转 | `name: string` | `void` | 否 | +| `replace` | 替换当前页面 | `name: string` | `void` | 否 | +| `replaceWithDefaultAnim` | 替换当前页面并使用默认动画 | `name: string` | `void` | 否 | +| `redirect` | 重定位页面,通常用于判断是否登录,然后重新跳转,返回是需要使用`finishWithResult` | `name: string`, `type: RedirectType = RedirectType.REPLACE` | `void` | 否 | | `name: string`, `type: RedirectType = RedirectType.REPLACE` | `void` | 否 | +| `pop` | 返回上一页 | `animated: boolean = true` | `void` | 否 | +| `popToName` | 返回指定路由名称的页面 | `name: string`, `animated: boolean = true` | `void` | 否 | +| `popWithResult` | 携带结果返回上一页 | `result: Object`, `animated: boolean = true` | `void` | 否 | +| ~~`popNavWithResult`~~ | 携带结果返回到指定的页面 - 弃用可用popToNameWithResult替代 | `name: string`, `result: T`, `animated: boolean = true` | `void` | 是 | +| `popToNameWithResult` | 携带结果返回到指定的页面 | `name: string`, `result: T`, `animated: boolean = true` | `void` | 否 | +| `popToRootWithResult` | 携带结果返回到根视图 | `result: T`, `animated: boolean = true` | `void` | 否 | +| `finishWithResult` | 携带结果返回到重定向页面 | `result: T`, `animated: boolean = true` | `void` | 否 | +| `clear` | 清除路由栈,返回Navigation根视图,通常是首页 | 无 | `void` | 否 | +| `getAllPathName` | 获取路由栈的所有路由 | 无 | `string[]` | 否 | +| `getTopPathName` | 获取路由栈顶的路由 | 无 | `string null` | 否 | +| `getParam` | 获取当前页面的参数 | 无 | `ObjectOrNull` | 否 | +| `getParamByKey` | 通过 key 获取当前页面的参数 | `key: string`, `defVal: ObjectOrNull = null` | `P = ObjectOrNull` | 否 | +