HarmonyOS鸿蒙Next中Navigation跳转到下一页是空白的

HarmonyOS鸿蒙Next中Navigation跳转到下一页是空白的

import { image } from '[@kit](/user/kit).ImageKit';
import { fileIo, } from '[@kit](/user/kit).CoreFileKit';
import { photoAccessHelper } from '[@kit](/user/kit).MediaLibraryKit';
import { BusinessError } from '[@kit](/user/kit).BasicServicesKit';
import { textRecognition } from '[@kit](/user/kit).CoreVisionKit';
import { common } from '[@kit](/user/kit).AbilityKit';
import { PromptAction } from '[@kit](/user/kit).ArkUI';
import router from '[@ohos](/user/ohos).router';

// import { GlobalContext } from '../common/GlobalContext';

const TAG: string = 'ImageTextRecognizer';

@Entry
@Component
struct ImageTextRecognizerPage {
  @State chooseImage: PixelMap | undefined = undefined
  @State dataValues: string = ''
  @State isLoading: boolean = false
  @State hasResult: boolean = false
  @State isTimeout: boolean = false;
  private context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;
  private promptAction: PromptAction = this.getUIContext().getPromptAction() as PromptAction;
  private recognizeTimer: number | null = null;
  private pageInfos: NavPathStack = new NavPathStack();

  openPicture(): Promise<string> {
    return new Promise<string>((resolve) => {
      let photoPicker: photoAccessHelper.PhotoViewPicker = new photoAccessHelper.PhotoViewPicker();
      photoPicker.select({
        MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
        maxSelectNumber: 1
      }).then((res: photoAccessHelper.PhotoSelectResult) => {
        resolve(res.photoUris[0]);
      }).catch((err: BusinessError) => {
        resolve('');
      })
    })
  }

  async selectImage() {
    this.hasResult = false;
    let uri = await this.openPicture();
    console.warn(TAG, `图片是:${JSON.stringify(uri)}`)
    if (!uri) {
      return;
    }

    setTimeout(
      async () => {
        let imageSource: image.ImageSource | undefined = undefined;
        try {
          let fileSource = await fileIo.open(uri, fileIo.OpenMode.READ_ONLY);
          imageSource = image.createImageSource(fileSource.fd);
          this.chooseImage = await imageSource.createPixelMap();
        } catch (error) {
          console.error(TAG, 'Failed to load image: ' + JSON.stringify(error));
        }
      }, 100)
  }

  async textRecogn() {
    if (!this.chooseImage) {
      this.dataValues = '请先选择图片';
      return;
    }

    // 重置状态
    this.isLoading = true;
    this.hasResult = false;
    this.isTimeout = false;
    this.dataValues = '';
    // 清除可能存在的旧计时器
    if (this.recognizeTimer) {
      clearTimeout(this.recognizeTimer);
    }

    // 启动5秒超时计时器
    this.recognizeTimer = setTimeout(() => {
      if (this.isLoading) {
        this.isTimeout = true;
        this.isLoading = false;
        this.hasResult = true;
        this.dataValues = '识别超时(超过5秒),请重试';
        this.promptAction.showToast({ message: '识别超时' });
      }
    }, 5000);

    let visionInfo: textRecognition.VisionInfo = {
      pixelMap: this.chooseImage
    };
    let textConfiguration: textRecognition.TextRecognitionConfiguration = {
      isDirectionDetectionSupported: false
    };

    try {
      // 执行文字识别
      const data = await textRecognition.recognizeText(visionInfo, textConfiguration);
      // 识别成功:清除计时器,更新状态
      if (this.recognizeTimer) {
        clearTimeout(this.recognizeTimer);
      }
      this.dataValues = data.value;
      this.hasResult = true;
      this.isLoading = false;
    } catch (error) {
      // 识别失败:清除计时器,更新状态
      if (this.recognizeTimer) {
        clearTimeout(this.recognizeTimer);
      }
      // 排除超时导致的失败(已在计时器中处理)
      if (!this.isTimeout) {
        this.dataValues = `识别失败: ${error.message}`;
        this.hasResult = true;
        this.isLoading = false;
      }
    }
  }

  // 跳转到预览页面
  navigateToPreview() {
    if (!this.chooseImage) {
      return;
    }

    // // 保存图片到全局上下文
    // GlobalContext.getInstance().pixelMap = this.chooseImage;
    // 1. 定义跳转参数(直接封装pixelMap,无需GlobalContext)


    this.pageInfos.pushPath({
      name: 'PreviewPage', param: this.chooseImage, onPop: (popInfo: PopInfo) => {
        console.warn(TAG, `page is: ${popInfo.info.name},result: ${JSON.stringify(popInfo.result)}`)

      }
    }); // 将name
  }

  aboutToDisappear() {
    // 页面销毁时清除计时器
    if (this.recognizeTimer) {
      clearTimeout(this.recognizeTimer);
      this.recognizeTimer = null;
    }
  }

  build() {
    // 标题区域
    Navigation(this.pageInfos) {
      Column() {
        Text('图片文字识别')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .margin({ top: 20, bottom: 10 })
          .fontColor('#1272FF')

        // 图片显示区域
        if (this.chooseImage) {
          Stack() {
            Image(this.chooseImage)
              .objectFit(ImageFit.Contain)
              .width('90%')
              .height(240)
              .borderRadius(12)
              .backgroundColor('#F5F5F5')
              .shadow({
                radius: 8,
                color: '#1A000000',
                offsetX: 2,
                offsetY: 4
              })
              .sharedTransition('imageTransition', { duration: 400, curve: Curve.EaseOut })
              .onClick(() => {
                this.navigateToPreview();
              })

            if (this.isLoading) {
              Column() {
                LoadingProgress()
                  .width(40)
                  .height(40)
                  .color('#1272FF')
                Text('识别中...')
                  .fontSize(14)
                  .margin({ top: 8 })
                  .fontColor('#666666')
              }
              .width('100%')
              .height('100%')
              .justifyContent(FlexAlign.Center)
              .backgroundColor('#CCFFFFFF')
              .borderRadius(12)
            }
          }
          .width('90%')
          .height(240)
          .margin({ bottom: 20 })
        } else {
          // 占位图
          Column() {
            Image($r('app.media.bg_fish'))
              .width(60)
              .height(60)
              .fillColor('#CCCCCC')
            Text('请选择图片')
              .fontSize(16)
              .margin({ top: 12 })
              .fontColor('#999999')
          }
          .width('90%')
          .height(240)
          .justifyContent(FlexAlign.Center)
          .border({ width: 2, color: '#E5E5E5', style: BorderStyle.Dashed })
          .borderRadius(12)
          .backgroundColor('#FAFAFA')
          .margin({ bottom: 20 })
        }
      }


      // 识别结果区域
      if (this.hasResult || this.dataValues) {
        Column() {
          Row() {
            Text('识别结果')
              .fontSize(18)
              .fontWeight(FontWeight.Medium)
              .fontColor('#333333')

            if (this.dataValues && !this.dataValues.startsWith('识别失败') &&
              !this.dataValues.startsWith('请先选择')) {
              Text(`字数: ${this.dataValues.length}`)
                .fontSize(12)
                .fontColor('#666666')
                .margin({ left: 12 })
            }
          }
          .width('100%')
          .justifyContent(FlexAlign.Start)
          .margin({ bottom: 8 })

          Scroll() {
            Text(this.dataValues)
              .fontSize(16)
              .fontColor('#444444')
              .textAlign(TextAlign.Start)
              .lineHeight(24)
              .width('100%')
              .copyOption(CopyOptions.LocalDevice)
          }
          .width('100%')
          .height(180)
          .padding(12)
          .borderRadius(8)
          .backgroundColor('#F8F9FA')
          .border({ width: 1, color: '#E8E8E8' })
        }
        .width('90%')
        .margin({ bottom: 20 })
      }

      // 按钮区域
      Column() {
        Button('选择图片')
          .type(ButtonType.Normal)
          .fontColor(Color.White)
          .fontSize(16)
          .backgroundColor('#1272FF')
          .width('100%')
          .height(48)
          .margin({ bottom: 12 })
          .borderRadius(24)
          .onClick(() => {
            this.selectImage()
          })

        Button('文字识别')
          .type(ButtonType.Normal)
          .fontColor(Color.White)
          .fontSize(16)
          .backgroundColor(this.chooseImage ? '#1272FF' : '#CCCCCC')
          .width('100%')
          .height(48)
          .borderRadius(24)
          .enabled(!!this.chooseImage)
          .onClick(async () => {
            this.textRecogn()
          })
      }
      .width('90%')
      .margin({ bottom: 30 })
      .justifyContent(FlexAlign.Start)
      .alignItems(HorizontalAlign.Center)
      .backgroundColor('#FFFFFF')
    }
    .width('100%')
    .height('100%')

    // .title('图片文字识别')

  }
}
// pages/PreviewPage.ets
import { image } from '[@kit](/user/kit).ImageKit';
import { fileUri } from '[@kit](/user/kit).CoreFileKit';
import { photoAccessHelper } from '[@kit](/user/kit).MediaLibraryKit';
import { systemShare } from '[@kit](/user/kit).ShareKit';
import { uniformTypeDescriptor as utd } from '[@kit](/user/kit).ArkData';
import { common } from '[@kit](/user/kit).AbilityKit';
import { display, PromptAction } from '[@kit](/user/kit).ArkUI';
import fs from '[@ohos](/user/ohos).file.fs';
// import { GlobalContext } from '../common/GlobalContext';
import { deviceInfo } from '[@kit](/user/kit).BasicServicesKit';

const TAG: string = 'ImageTextRecognizer';

interface ImageSizeType {
  width: number
  height: number
  scale: number
  offsetX: number
  offsetY: number
  offsetStartX: number
  offsetStartY: number
  dragOffsetX: number
  dragOffsetY: number
}

//用于挖孔区避让
interface StatusBarMargin {
  left: number;
  right: number;
  top: number;
  bottom: number;
}

[@Builder](/user/Builder)
export function PreviewPageBuilder() {
  PreviewPage()
}

[@ComponentV2](/user/ComponentV2)
export struct PreviewPage {
  [@Local](/user/Local) statusBarMargin: StatusBarMargin = {
    left: 0,
    right: 0,
    top: 0,
    bottom: 0
  };
  [@Local](/user/Local) pixelMap: PixelMap | undefined = undefined;
  [@Local](/user/Local) containerHeight: number = 0
  [@Local](/user/Local) containerWidth: number = 0
  [@Local](/user/Local) disabledSwipe: boolean = false
  [@Local](/user/Local) isScaled: boolean = false // 标记图片是否处于放大状态
  [@Local](/user/Local) activeImage: ImageSizeType = {
    width: 0,
    height: 0,
    scale: 1,
    offsetX: 0,
    offsetY: 0,
    offsetStartX: 0,
    offsetStartY: 0,
    dragOffsetX: 0,
    dragOffsetY: 0,
  }
  [@Local](/user/Local) rotationAngle: number = 0;
  private defaultScale: number = 1
  private context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;
  private promptAction: PromptAction = this.getUIContext().getPromptAction() as PromptAction;
  private uiContext = this.getUIContext() as UIContext;
  private deviceType: string = '';
  private pathStack: NavPathStack = new NavPathStack();

  aboutToAppear() {
    // // 从全局上下文获取图片
    // this.pixelMap = GlobalContext.getInstance().pixelMap;
    // 设置沉浸式
    this.setImmersive();

    //this.registerOrientationListener();
    this.getDeviceType();
    //计算挖孔区边距

    this.context.windowStage?.getMainWindow().then(async (win) => {
      const properties = win.getWindowProperties();
      const windowWidth = this.uiContext.px2vp(properties.windowRect.width);
      const windowHeight = this.uiContext.px2vp(properties.windowRect.height);
      await this.calculateStatusBarMargin(windowWidth, windowHeight);
    });
  }

  // 处理物理返回键
  onBackPress() {
    this.goBack();
    return true; // 阻止默认返回行为
  }

  // 重置图片状态
  resetImageState() {
    this.getUIContext().animateTo({
      duration: 300,
      curve: Curve.EaseOut,
      onFinish: () => {
        this.disabledSwipe = false;
        this.isScaled = false;
      }
    }, () => {
      this.activeImage = {
        width: this.activeImage.width,
        height: this.activeImage.height,
        scale: 1,
        offsetX: 0,
        offsetY: 0,
        offsetStartX: 0,
        offsetStartY: 0,
        dragOffsetX: 0,
        dragOffsetY: 0,
      };
    });
  }

  // 保存图片到相册
  async saveImage(): Promise<void> {
    if (!this.pixelMap) {
      return; // 确保有图片
    }
    try {
      // 1. 将 PixelMap 转为 ArrayBuffer
      const packer = image.createImagePacker();
      const buffer = await packer.packToData(this.pixelMap, { format: 'image/jpeg', quality: 90 });
      // 2. 保存到相册
      const helper = photoAccessHelper.getPhotoAccessHelper(this.context);
      const uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg');
      const file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
      await fs.write(file.fd, buffer);
      await fs.close(file.fd);
      this.promptAction.showToast({ message: "图片保存成功" });
      console.warn(`${TAG} 图片保存成功`);
    } catch (error) {
      this.promptAction.showToast({ message: "图片保存失败" });
      console.error(`${TAG} 保存失败: ${JSON.stringify(error)}`);
    }
  }

  // 分享图片
  async shareImage() {
    if (!this.pixelMap) {
      this.promptAction.showToast({ message: "无图片可分享" });
      return;
    }
    try {
      // 1. 定义临时文件路径
      const tempPath = `${this.context.filesDir}/temp_share.jpg`;
      // 2. 将 PixelMap 转为 buffer 并写入临时文件
      const packer = image.createImagePacker();
      const buffer = await packer.packToData(this.pixelMap, { format: 'image/jpeg', quality: 90 });
      // 3. 同步打开文件
      const file = fs.openSync(tempPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
      await fs.write(file.fd, buffer);
      await fs.close(file.fd);
      // 4. 检查文件是否存在
      if (!fs.accessSync(tempPath)) {
        console.error('临时图片文件不存在');
        this.promptAction.showToast({ message: "分享失败:文件不存在" });
        return;
      }
      // 5. 调用系统分享
      const imageUtd = utd.getUniformDataTypeByFilenameExtension('.jpg', utd.UniformDataType.IMAGE);
      const shareData = new systemShare.SharedData({
        utd: imageUtd,
        uri: fileUri.getUriFromPath(tempPath),
        title: '分享图片',
        description: '识别后的图片'
      });
      const controller = new systemShare.ShareController(shareData);
      await controller.show(this.context, {
        selectionMode: systemShare.SelectionMode.SINGLE,
        previewMode: systemShare.SharePreviewMode.DETAIL
      });
      this.promptAction.showToast({ message: "分享成功" });
    } catch (error) {
      console.error(`${TAG} 分享失败: ${JSON.stringify(error)}`);
      this.promptAction.showToast({ message: "分享失败" });
    }
  }

  build() {
    NavDestination() {
      Stack() {
        // 图片区域 - 支持手势操作
        if (this.pixelMap) {
          // 顶部返回键(固定在左上角,放大时隐藏)
          Row() {
            Image($rawfile('back.png'))
              .width(40)
              .height(40)
              .onClick(() => {
                this.goBack();
              })
          }
          .position({
            x: this.statusBarMargin.left,
            y: this.statusBarMargin.top
          })
          .visibility(this.isScaled ? Visibility.Hidden : Visibility.Visible) // 放大时隐藏
          .zIndex(1)

          // 图片预览区域
          Image(this.pixelMap)
            .objectFit(ImageFit.Contain)
            .width('100%')
            .height('100%')
            .sharedTransition('imageTransition', { duration: 400, curve: Curve.EaseOut })
            .scale({ x: this.activeImage.scale, y: this.activeImage.scale })
            .offset({ x: this.activeImage.offsetX, y: this.activeImage.offsetY })
            .rotate({
              angle: this.rotationAngle,
              centerX: '50%',
              centerY: '50%'
            })
            .draggable(false)
            .onAreaChange((_, newValue) => {
              this.containerWidth = Number(newValue.width);
              this.containerHeight = Number(newValue.height);
            })

更多关于HarmonyOS鸿蒙Next中Navigation跳转到下一页是空白的的实战教程也可以访问 https://www.itying.com/category-93-b0.html

8 回复

更多关于HarmonyOS鸿蒙Next中Navigation跳转到下一页是空白的的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


空白应该没有在route_map.json中添加路由信息,导航不到真正要去的页面。

你是不是。。。在你第二个页面里没取你传过来的参数,然后你的组件就没构建,因为if的关系。

.onReady((ctx: NavDestinationContext) => {
      this.pathStack = ctx.pathStack
      this.pixelMap = ctx.pathInfo.param as image.PixelMap
    });

我没有加导航页,光用子页了,,

能用 this.pathStack.getParamByName 或者 this.pathStack.getParamByIndex 获取参数吗,该咋写呢,

let fileName: string[] = this.pathStack.getParamByName('ReadPage') as string[]

// 跳转时传递参数(参考检索信息)
this.pathStack.pushPathByName('pageOne', { id: 123, title: "示例" });
// 目标页面获取参数
let params: any[] = this.pathStack.getParamByName('pageOne');
console.log(params.id); // 输出:123,

在HarmonyOS Next中,Navigation跳转后页面空白通常由以下原因导致:

  1. 页面组件未正确构建或加载。
  2. Navigation组件配置错误,如目标页面路径不正确。
  3. 页面生命周期函数(如aboutToAppear)中未正确初始化数据或UI。
  4. 使用了不兼容的API或组件。

检查目标页面的构建逻辑和Navigation的路径配置。

在HarmonyOS Next中,Navigation跳转后页面空白通常由以下几个原因导致:

主要问题分析

1. 页面注册问题

从你的routerMap配置看,PreviewPage使用了@Builder构建器,但注册方式可能存在问题。在HarmonyOS Next中,页面注册需要确保:

// 正确的页面注册方式
@Entry
@Component
struct PreviewPage {
  // ...
}

// 或者在routerMap中直接使用组件名

2. 参数传递问题

你的跳转代码中传递了PixelMap参数:

this.pageInfos.pushPath({
  name: 'PreviewPage', 
  param: this.chooseImage,  // 直接传递PixelMap对象
  onPop: ...
});

PixelMap是复杂对象,直接通过Navigation参数传递可能导致序列化问题。建议使用以下方式:

3. 页面生命周期问题

PreviewPage中缺少必要的生命周期方法初始化数据:

aboutToAppear() {
  // 需要从Navigation参数中获取传递的数据
  const params = this.getNavDestinationContext()?.getParams();
  if (params) {
    this.pixelMap = params as PixelMap;
  }
}

解决方案

方案一:修复页面注册

确保PreviewPage使用正确的装饰器:

// PreviewPage.ets
@Entry
@Component
export struct PreviewPage {
  // 移除 @ComponentV2 和 @Builder
  // ...
}

方案二:修改参数传递方式

避免直接传递PixelMap:

// 在主页面
navigateToPreview() {
  // 使用全局状态管理或临时存储
  const imageId = this.saveImageToTemp(this.chooseImage);
  this.pageInfos.pushPath({
    name: 'PreviewPage',
    param: { imageId: imageId },
    onPop: ...
  });
}

// 在PreviewPage中
aboutToAppear() {
  const params = this.getNavDestinationContext()?.getParams();
  if (params?.imageId) {
    this.pixelMap = this.loadImageFromTemp(params.imageId);
  }
}

方案三:检查页面结构

确保PreviewPage的build方法正确包裹:

build() {
  // 必须有根容器
  Column() {
    // 页面内容
  }
  .width('100%')
  .height('100%')
}

方案四:调试建议

  1. aboutToAppear中添加日志,确认页面是否被加载
  2. 检查控制台是否有错误信息
  3. 尝试先传递简单参数(如字符串)测试跳转是否正常
  4. 确保routerMap配置路径正确

关键检查点

  1. 页面装饰器:使用@Entry@Component
  2. 参数类型:避免传递复杂对象,使用ID或路径引用
  3. 生命周期:在aboutToAppear中接收参数
  4. 路由配置:确保routerMap中的name与pushPath的name一致

可以先从传递简单参数开始测试,确认Navigation跳转基本功能正常,再逐步解决PixelMap传递问题。

回到顶部