g***@qq.com
g***@qq.com
  • 发布:2025-11-22 11:59
  • 更新:2025-11-27 13:11
  • 阅读:276

【报Bug】【鸿蒙上架】鸿蒙上架审核不通过,需要支持连接键盘时tab键或方向键能切换页面的焦点元素

分类:鸿蒙Next

产品分类: uniapp/App

PC开发环境操作系统: Mac

PC开发环境操作系统版本号: macOS Ventura 13.0

HBuilderX类型: Alpha

HBuilderX版本号: 4.86

手机系统: HarmonyOS NEXT

手机系统版本号: HarmonyOS 5.1.0

手机厂商: 华为

手机机型: nova 13

页面类型: vue

vue版本: vue3

打包方式: 云端

项目创建方式: HBuilderX

操作步骤:

连接键盘,用tab键和回车键无反应

预期结果:

连接键盘,用tab键和回车键可以进入页面

实际结果:

连接键盘,用tab键和回车键无反应

bug描述:

鸿蒙上架审核时,会审核不通过:应用中的走焦事件未能响应tab键或方向键切换的兼容性问题,影响用户体验。
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/device-compatible

1、也就是说连接键盘后需要支持tab键或上下键能够切换页面元素,然后点回车可以进入。
2、尝试过在view上用tabindex是可以支持切换的,但是按回车键不能进入。

目前是否可以解决这个问题?最近看到华为鸿蒙开发社区也在提这个问题,如附件。

感觉这个问题比较严重,有没有上架过的开发者来说说呢?

2025-11-22 11:59 负责人:无 分享
已邀请:
DCloud_heavensoft

DCloud_heavensoft

响应回车需要监听onkeydown,然后发现是13,就触发相应的逻辑。这可能需要在渲染层监听。
我们已经关注到此事 后续会在内置组件里解决

  • g***@qq.com (作者)

    鸿蒙不支持plus吧,之前可以plus.key.addEventListener,现在鸿蒙这边怎么去监听onkeydown呢?

    2025-11-22 14:58

  • DCloud_heavensoft

    回复 g***@qq.com: 用renderjs在渲染层监听浏览器的事件

    2025-11-22 15:25

DCloud_UNI_OttoJi

DCloud_UNI_OttoJi - 日常回复 uni-app/x 问题,如果艾特我没看到,请主动私信

你的应用里的现在的表现是什么?我测试了一下 平板模式+键盘,在多个 input 里可以正常切换 tab 和响应回车事件。

<template>  
    <view>  
        <view>page index</view>  
        <view>  
            input 测试, 键盘输入 123 按下回车应当会弹窗</view>  
        <input @confirm="onConrm" style="border: 1px solid;" />  
        <view> 第二个 input</view>  
        <input style="border: 1px solid blue;" />  
    </view>  
</template>  

<script>  
    export default {  
        data() {  
            return {  
            }  
        },  
        methods: {  
            onConrm(e) {  
                console.log('onConfirm', e.detail.value);  
                uni.showToast({  
                    title: e.detail.value  
                })  
            }  
        }  
    }  
</script>
  • DCloud_UNI_OttoJi

    键盘焦点应该是优化建议,并不是必须的,如果你因为这一条理由被驳回,可以发起申诉,和审核员沟通。

    2025-11-24 16:32

  • g***@qq.com (作者)

    回复 DCloud_UNI_OttoJi: 现在已经不仅仅是input框这些,页面的功能块,比如用<view @tap="xxx()">功能1</view>的功能块也需要支持tap键了。这个应该已经成了审核规范了,因为华为开发者论坛也出现了一些这个问题。

    2025-11-24 20:34

  • DCloud_UNI_OttoJi

    回复 g***@qq.com: 看到你的反馈了。这个问题是新出的,我认为你是可以进一步反馈的,和审核员沟通,这个之前并没有测试过。你也自查你是否错误勾选了 2in1 devicetype

    2025-11-24 21:03

  • DCloud_UNI_OttoJi

    https://developer.huawei.com/consumer/cn/forum/topic/0202199120278303128?fid=0109140870620153026

    2025-11-24 21:03

g***@qq.com

g***@qq.com (作者)

类似这样的功能,他们也会要支持tab键

qingtong

qingtong

我的应用也是这个原因被卡了,已经一周了,还没找到适合的解决方法。
华为的《设备兼容性》规范11月18号已经更新了,页面没有 input 也要响应tab键或方向键切换!
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/device-compatible

  • DCloud_UNI_OttoJi

    我看了这个是针对的 平板、电脑、折叠电脑的强制要求,你可在代码中搜索 devicetype 只保留 phone ,在 uniapp 后台或者 agc 后台也确保不勾选上述平台,只勾选 phone 平台

    2025-11-26 14:29

  • g***@qq.com (作者)

    目前我们试下来,可以在提交审核的时候不要勾选平板,只勾选手机,这样是可以过。只勾选手机默认是支持平板的,下面是他们的说明,可以先这样提交审核。


    1、支持设备类型包含手机的应用会默认兼容分发到平板或PC设备

    2、支持设备类型包含平板的应用会默认兼容分发到PC设备,详请参考官网

    2025-11-28 10:11

c***@163.com

c***@163.com

下面的例子解决了鸿蒙上的走焦问题。可以通过审核

<template>  
  <view tabindex="-1" class="test-content">  
    <view class="test" tabindex="0" :class="{ 'focused': focusedElement === `test-click1` }" id="test-click1"  
      @focus="setFocusedElement(`test-click1`)" @click="handleClick"></view>  
    <view class="test" tabindex="0" :class="{ 'focused': focusedElement === `test-click2` }" id="test-click2"  
      @focus="setFocusedElement(`test-click2`)" @click="handleClick"></view>  
  </view>  
</template>  

<script>  
export default {  
  data() {  
    return {  
      focusedElement: ''  
    }  
  },  
  methods: {  
    setFocusedElement(elementId) {  
      this.focusedElement = elementId;  
    },  
    handleClick() {  
      this.setFocusedElement('text-click1')  
    },  
  },  

}  
</script>  

<script module="test" lang="renderjs">  
// RenderJS 模块 - 专门处理键盘事件  
export default {  
  mounted() {  
    this.initKeyboardEvents();  
  },  
  methods: {  
    // 初始化键盘事件监听  
    initKeyboardEvents() {  
      console.log('Initializing keyboard events in RenderJS');  

      // 为整个页面添加键盘事件监听  
      if (typeof document !== 'undefined' && document.addEventListener) {  
        document.addEventListener('keydown', this.handleKeyDown.bind(this));  
        console.log('Keyboard event listener added in RenderJS');  
      } else {  
        console.warn('Document not available for keyboard events');  
      }  
    },  

    // 处理键盘事件  
    handleKeyDown(event) {  
      console.log('Key event in RenderJS:', event.key, event.keyCode);  

      // 获取按键信息  
      const key = event.key;  
      const keyCode = event.keyCode;  

      // 阻止默认行为  
      event.preventDefault();  
      event.stopPropagation();  

      // 调用逻辑层方法处理键盘导航  
      this.handleKeyboardNavigation(key, keyCode);  
    },  

    // 处理键盘导航  
    handleKeyboardNavigation(key, keyCode) {  
      // 获取所有可聚焦元素  
      const focusableElements = this.getFocusableElements();  
      if (focusableElements.length === 0) return;  

      // 获取当前聚焦元素  
      const currentElement = document.activeElement;  
      let currentIndex = focusableElements.findIndex(el => el === currentElement);  

      // 如果没有当前聚焦元素,默认聚焦第一个  
      if (currentIndex === -1) {  
        currentIndex = 0;  
        focusableElements[currentIndex].focus();  
        this.updateFocusState(focusableElements[currentIndex].id);  
        return;  
      }  

      // 根据按键处理导航  
      switch(key) {  
        case 'ArrowRight':  
        case 'Right':  
        case 'ArrowDown':  
        case 'Down':  
          // 下一个元素  
          this.focusNextElement(focusableElements, currentIndex);  
          break;  

        case 'ArrowLeft':  
        case 'Left':  
        case 'ArrowUp':  
        case 'Up':  
          // 上一个元素  
          this.focusPreviousElement(focusableElements, currentIndex);  
          break;  

        case 'Enter':  
        case ' ':  
          // 触发点击事件  
          this.triggerClick(currentElement);  
          break;  

        case 'Escape':  
        case 'Esc':  
          // 关闭子菜单  
          this.callOwnerMethod('closeSubMenu');  
          // 聚焦回主菜单  
          if (focusableElements.length > 0) {  
            focusableElements[0].focus();  
            this.updateFocusState(focusableElements[0].id);  
          }  
          break;  

        case 'Tab':  
          // Tab键:下一个元素,Shift+Tab:上一个元素  
          if (event.shiftKey) {  
            this.focusPreviousElement(focusableElements, currentIndex);  
          } else {  
            this.focusNextElement(focusableElements, currentIndex);  
          }  
          break;  

        default:  
          console.log('Unhandled key:', key);  
          break;  
      }  
    },  

    // 获取所有可聚焦元素  
    getFocusableElements() {  
      try {  
        const elements = document.querySelectorAll('[tabindex="0"]');  
        return Array.from(elements).filter(el => {  
          // 过滤掉隐藏或不可见的元素  
          const style = window.getComputedStyle(el);  
          return style.display !== 'none' && style.visibility !== 'hidden';  
        });  
      } catch (error) {  
        console.error('Error getting focusable elements:', error);  
        return [];  
      }  
    },  

    // 聚焦下一个元素  
    focusNextElement(elements, currentIndex) {  
      const nextIndex = (currentIndex + 1) % elements.length;  
      elements[nextIndex].focus();  
      this.updateFocusState(elements[nextIndex].id);  

      // 确保元素在可视区域内  
      this.scrollToElement(elements[nextIndex]);  
    },  

    // 聚焦上一个元素  
    focusPreviousElement(elements, currentIndex) {  
      const prevIndex = currentIndex > 0 ? currentIndex - 1 : elements.length - 1;  
      elements[prevIndex].focus();  
      this.updateFocusState(elements[prevIndex].id);  

      // 确保元素在可视区域内  
      this.scrollToElement(elements[prevIndex]);  
    },  

    // 触发点击事件  
    triggerClick(element) {  
      if (element) {  
        console.log('Triggering click on element:', element.id);  

        // 创建并派发鼠标点击事件  
        const clickEvent = new MouseEvent('click', {  
          view: window,  
          bubbles: true,  
          cancelable: true  
        });  
        element.dispatchEvent(clickEvent);  
      }  
    },  

    // 更新焦点状态到逻辑层  
    updateFocusState(elementId) {  
      this.callOwnerMethod('setFocusedElement', elementId);  
    },  

    // 滚动到元素确保可见  
    scrollToElement(element) {  
      if (element && element.scrollIntoView) {  
        element.scrollIntoView({  
          behavior: 'smooth',  
          block: 'center',  
          inline: 'nearest'  
        });  
      }  
    },  

    // 调用逻辑层方法  
    callOwnerMethod(methodName, ...args) {  
      if (this.$ownerInstance) {  
        this.$ownerInstance.callMethod(methodName, ...args);  
      }  
    }  
  }  
}  
</script>  

<style lang="scss" scoped>  
.test-content {  
  height: 100vh;  

  .test {  
    width: 100rpx;  
    height: 100rpx;  
    background-color: red;  
  }  

  .focused {  
    // 样式  
  }  
}  
</style>
  • g***@qq.com (作者)

    感谢老哥提供代码,我们试下。

    2025-11-28 10:05

  • qingtong

    参考renderjs方案成功完成鸿蒙适配,已顺利上架。非常感谢大牛提供代码示例。?

    2025-12-02 17:19

要回复问题请先登录注册