Soybean Admin 使用tv-focusable兼容电视TV端支持遥控器移动焦点

news2025/4/13 18:55:05

环境

window10
pnpm 8.15.4
node 8.15.4
vite 5.1.4
soybean admin: 1.0.0
native-ui: 2.38.0
vue-tv-focusable: 2.0.1
小米电视 MIUI TV版本:MiTV OS 2.7.1886(稳定版)
飞视浏览器:https://www.fenxm.com/1220.html

这里必须使用飞视浏览器,其它浏览器都不行,在“测试”步骤会告诉你原因。在小米电视安装飞视浏览器可以去小红书查安装教程:苹果手机小米电视安装第三方 app 教程

描述

其实最好的做法还是安卓开发,前端网页开发远没有安卓开发那么方便的去管理焦点,不过简单支持还是能做到的!

大部分的电视浏览器都支持模拟鼠标,就是在浏览器打开一个网页,会出现一个鼠标,可以通过遥控器去移动这个鼠标,点遥控器的OK键就是鼠标单击键,如下图的蓝色鼠标

在这里插入图片描述

但是客户反馈说,无法点击登录按钮,点了没有反应。那这时候要借助 vue 插件去实现获取登录按钮的焦点了,不能用浏览器自带的模拟鼠标

实现

找了一圈,找到了一个 vue-tv-focusable 插件。如果有更好用的,欢迎评论区补充

vue-tv-focusable csdn
vue-tv-focusable 官方文档
vue-tv-focusable 案例
vue-tv-focusable 案例源码:

安装插件

pnpm i vue-tv-focusable --save

引入插件

新建一个插件文件,位置:src\plugins\tvfocusable.ts

import type { App } from 'vue';
import focusable from 'vue-tv-focusable'

export function setupTvFocusable(app: App) {
  
  // 1.注册
  app.use(focusable);

  // 2.初始化配置
  app.config.globalProperties.$tv.init({
    focusClassName: 'tv-focus',
  })
  
}

引入插件,位置:src\main.ts

import 'core-js/stable';
import 'regenerator-runtime/runtime'; // 如果你的代码使用了生成器(Generator),你也需要这个 Polyfill
import { createApp } from 'vue';
import './plugins/assets';
import { setupDayjs, setupIconifyOffline, setupLoading, setupNProgress, setupTvFocusable } from './plugins';
import { setupStore } from './store';
import { setupRouter } from './router';
import { setupI18n } from './locales';
import App from './App.vue';

async function setupApp() {
  setupLoading();

  setupNProgress();

  setupIconifyOffline();

  setupDayjs();

  const app = createApp(App);

  setupStore(app);

  setupTvFocusable(app); // 这里

  await setupRouter(app);

  setupI18n(app);

  app.mount('#app');
}

setupApp();

引入完成之后,使用插件,实现聚焦登录按钮

<NButton
  v-focusable
   class="flex-1"
   block
   @click="toggleLoginModule('pwd-login')"
 >
   {{ $t(loginModuleRecord["pwd-login"]) }}
</NButton>

仅仅使用 v-focusable 指令还不够,还要写聚焦样式,不然登录按钮被选中都不知道。有两种方式,

一个是全局聚焦样式,位置:src\styles\css\global.css

.tv-focus {
  transform: scale(1.01);
  border: 1px solid #FF9933;
  box-shadow: 0 0 20px #FF9933;
}

一个是局部聚焦样式

<style scoped>
.tv-focus {
  /* 电视用遥控器选择或者电脑键盘选择的时候,能给聚焦对象一个放大+灰色阴影的聚焦效果 */
  transform: scale(1.02);
  box-shadow: 0 0 15px rgb(207, 207, 207);
}
</style>

看需求去选择,我这里直接全局设置元素聚焦样式(橙色边框,橙色阴影,放大效果)。

那么如何去测试呢,开发环境不可能一直打开电视去测试,这里我们可以用键盘事件模拟遥控器事件:

键盘遥控器
enter键OK键
方向键方向键

看看测试效果

在这里插入图片描述
下面是实现输入框聚焦

<script setup lang="ts">
function handleUserNameClick() {
  const el: HTMLInputElement | null = document.querySelector(".tv-focus input");
  if (el) el.focus();
}

function handleUserNameBlur() {
  const el: HTMLInputElement | null = document.querySelector(".tv-focus input");
  if (el) el.blur();
}

function handlePasswordClick() {
  const el: HTMLInputElement | null = document.querySelector(".tv-focus input");
  if (el) el.focus();
}

function handlePasswordBlur() {
  const el: HTMLInputElement | null = document.querySelector(".tv-focus input");
  if (el) el.blur();
}
</script>
<template>
	...
	<NFormItem path="userName">
      <NInput
        v-focusable
        v-model:value="model.userName"
        :placeholder="$t('page.login.common.userNamePlaceholder')"
        @click="handleUserNameClick"
        @on-blur="handleUserNameBlur"
      />
    </NFormItem>
    <NFormItem path="password">
      <NInput
        v-focusable
        v-model:value="model.password"
        type="password"
        show-password-on="click"
        :placeholder="$t('page.login.common.passwordPlaceholder')"
        @click="handlePasswordClick"
        @on-blur="handlePasswordBlur"
      />
    </NFormItem>
    ...
</template>

除了加入 v-focusable指令,还监听了输入框的 click事件、on-blur事件

来看看测试效果

在这里插入图片描述
输入账号密码登录进去之后,到了首页,还有按钮聚焦(全屏按钮,切换语言按钮,切换主题按钮),这个就不重复代码了,都是按钮聚焦。

在这里插入图片描述

下面实现下拉菜单的聚焦,按理说电视app根本不会做这种交互,但是这里还是可以分享一下做法

src\components\common\lang-switch.vue

<script setup lang="ts">
import type { VNode } from 'vue';
import type { DropdownOption, DropdownGroupOption } from "naive-ui";
import { computed, h, getCurrentInstance } from 'vue';
import { $t } from '@/locales';
import FocusableWrapper from "@/components/custom/focusable-wrapper.vue";

defineOptions({
  name: 'LangSwitch'
});

interface Props {
  /** Current language */
  lang: App.I18n.LangType;
  /** Language options */
  langOptions: App.I18n.LangOption[];
  /** Show tooltip */
  showTooltip?: boolean;
}

const { proxy, ctx } = getCurrentInstance();

const props = withDefaults(defineProps<Props>(), {
  showTooltip: true
});

type Emits = {
  (e: 'changeLang', lang: App.I18n.LangType): void;
};

const emit = defineEmits<Emits>();

const tooltipContent = computed(() => {
  if (!props.showTooltip) return '';

  return $t('icon.lang');
});

const renderOption = (props: { node: VNode, option: DropdownOption | DropdownGroupOption }) => {
  return h(FocusableWrapper,
    {
      class: "tv-focus-lang-dropdown-option"
    },
    [
      h(props.node)
    ]
  )
}

function changeLang(lang: App.I18n.LangType) {
  emit('changeLang', lang);
}

function handleUpdateShow(value: boolean) {
  if (value === true) {
    // 下拉菜单开启状态
    const oDropdown = document.querySelector(".tv-focus-lang-dropdown");
    proxy.$tv.limitingEl = oDropdown; // 点击浮层,限制只能在下拉浮层里移动焦点
  } else {
    proxy.$tv.resetLimitingEl(); // 收起浮层,解除限制
  }
}
</script>

<template>
  <NDropdown class="tv-focus-lang-dropdown" :value="lang" :options="langOptions" trigger="click"
    :render-option="renderOption" :on-update:show="handleUpdateShow" @select="changeLang">
    <div>
      <ButtonIcon focusable :tooltip-content="tooltipContent" tooltip-placement="left">
        <SvgIcon icon="heroicons:language" />
      </ButtonIcon>
    </div>
  </NDropdown>
</template>

<style scoped></style>

src\components\custom\focusable-wrapper.vue

<script setup lang="ts">
defineOptions({
  name: "FocusableWrapper",
});

</script>

<template>
  <div v-focusable v-bind="$attrs">
    <slot></slot>
  </div>
</template>

主要通过renderOption 这属性给下拉菜单的菜单项去设置元素焦点, 然后 handleUpdateShow 方法去限制焦点的范围。

在聚焦全屏按钮,并使用全屏按钮的时候遇到了问题:当页面全屏标签页内容时,摁方向键全部元素都无法聚焦

具体什么原因还分析不出来,全屏后,不论怎么摁键盘的方向键,所有元素都失去了聚焦效果,解决方案如下:

src\layouts\modules\global-tab\index.vue

<script setup lang="ts">
import { nextTick, reactive, ref, watch, getCurrentInstance } from 'vue';

const { proxy } = getCurrentInstance();

watch(
  () => appStore.fullContent,
  async (val) => {
    if (val === true) {
      // 全屏状态
      // fix: 解决全屏标签页内容时按方向键全部元素都无法聚焦的问题
      // 找不到原因,莫名其妙无法聚焦,手动用next聚焦
      await nextTick();
      const oBtn = document.querySelector("#tv-tab-fullscreen-btn");
      proxy.$tv.next(oBtn);
    }
  },
  { immediate: true }
);
</script>

<template>
	...
	<FullScreen id="tv-tab-fullscreen-btn" :full="appStore.fullContent" @click="appStore.toggleFullContent" />
	...
</template>

简单兼容电视TV端遥控器交互,常用的基本就是以上的场景。


### 测试

前面提供了飞视浏览器的下载地址,需要关闭鼠标模拟才能聚焦成功。经过测试发现只有飞视浏览器才有这个关闭模拟鼠标的设置,其它浏览器没有这样的设置

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
设置完了,在地址栏输入访问地址,使用遥控器查看你的元素聚焦效果

最后

做了一大通操作,才能支持兼容电视TV端,而且只能在飞视浏览器访问项目,很局限,有条件还是安卓原生开发

博客分享了vue-tv-focusable 插件,实现按钮聚焦、输入框聚焦、下拉菜单聚焦,解决了当页面全屏标签页内容时,摁方向键全部元素都无法聚焦的问题。有疑问或者遇到什么问题可以评论区补充,感谢观看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2332201.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大模型学习七:‌小米8闲置,直接安装ubuntu,并安装VNC远程连接手机,使劲造

一、说明 对于咱们技术人来说&#xff0c;就没有闲的蛋疼的时候&#xff0c;那不是现在机会来了 二、刷机器准备 1、申请解锁手机 申请解锁小米手机https://www.miui.com/unlock/download.html 下载工具&#xff0c;安装下面的步骤来&#xff0c;官网不欺人吧 打开开发者工…

高可用之战:Redis Sentinal(哨兵模式)

参考&#xff1a;Redis系列24&#xff1a;Redis使用规范 - Hello-Brand - 博客园 1 背景 在我们的《Redis高可用之战&#xff1a;主从架构》篇章中&#xff0c;介绍了Redis的主从架构模式&#xff0c;可以有效的提升Redis服务的可用性&#xff0c;减少甚至避免Redis服务发生完…

CSS Grid布局:从入门到放弃再到真香

Flexbox 与 Grid 布局&#xff1a;基础概念与特点 Flexbox Flexbox&#xff08;Flexible Box Layout&#xff09;&#xff0c;即弹性盒布局模型&#xff0c;主要用于创建一维布局&#xff0c;能够轻松实现元素在一行或一列中的排列、对齐与分布。通过display: flex属性启用 Fl…

Springboot把外部jar包打包进最终的jar包,并实现上传服务器

1、创建lib目录&#xff0c;把jar包放进这个目录下&#xff0c;然后标记lib目录为“资源根路径”&#xff08;鼠标右键lib目录->将目录标记为->资源根路径。之后lib文件夹会有如下的图标变化&#xff09; 文件结构如下&#xff1a; 2、pom文件添加依赖 <dependency…

仿照管理系统布局配置

1.vue仿照snowy 配置&#xff0c;如下图&#xff1a; 2.代码实现 <template><div class"theme-settings"><!-- 导航栏 --><div class"nav-bar"><el-breadcrumb separator"/"><el-breadcrumb-item>导航设置…

GPT - 因果掩码(Causal Mask)

本节代码定义了一个函数 causal_mask&#xff0c;用于生成因果掩码&#xff08;Causal Mask&#xff09;。因果掩码通常用于自注意力机制中&#xff0c;以确保模型在解码时只能看到当前及之前的位置&#xff0c;而不能看到未来的信息。这种掩码在自然语言处理任务&#xff08;如…

适合工程建筑行业的OA系统有什么推荐?

工程行业具有项目周期长、协作链条复杂等特性&#xff0c;传统管理模式下的 “人治”“纸质化” 弊端日益凸显。OA 系统作为数字化管理的核心载体&#xff0c;通过流程标准化、数据可视化&#xff0c;精准解决工程行业项目管理核心痛点。 泛微 e-office 深度聚焦工程场景&#…

深入解析栈回溯技术:如何通过异常处理精准定位程序崩溃点

一、栈回溯 1.1 栈回溯的原理 调试程序时&#xff0c;经常发生这类错误&#xff1a; 1.读写某个地址&#xff0c;导致程序崩溃 2.调用某个空函数&#xff0c;导致程序崩溃在异常处理函数中&#xff0c;可以打印出”发生错误瞬间”的所有寄存器。 我们调试时&#xff0c;可以…

重构居家养老安全网:从 “被动响应” 到 “主动守护”

随着全球老龄化加剧&#xff0c;居家养老安全成为社会关注的核心议题。 传统养老模式依赖人工巡检或单一传感器&#xff0c;存在响应滞后、隐私泄露、场景覆盖不足等问题。 由此智绅科技应运而生&#xff0c;七彩喜智慧养老系统构筑居家养老安全网。 而物联网&#xff08;Io…

Unity6下架中国区,团结引擎接棒:这是分裂,还是本地化的开始?

就在近日&#xff0c;一则消息在国内游戏开发圈内迅速传播开来&#xff1a;Unity 6 及其后续版本已在中国大陆及港澳地区下架。这意味着&#xff0c;未来中国用户将无法直接使用 Unity 最新的主线版本。而取而代之的&#xff0c;是由 Unity 中国主导推出的本地化产品 —— 团结…

ESP8266水位监测以及温湿度数据采集

上面就是ESP8266的引脚图&#xff0c;水温检测使用的是水位监测传感器&#xff0c;温湿度测量使用的是DHT11&#xff0c;DHT11的反应时间是2秒&#xff0c;这里要注意。开发采用Arduino程序 1. 传感器初始化 功能&#xff1a;初始化DHT11温湿度传感器和串口通信。 代码实现&…

国产信创数据库:PolarDB 分布式版 V2.0,支持集中分布式一体化

阿里云PolarDB数据库管理软件&#xff08;分布式版&#xff09;V2.0 &#xff0c;安全可靠的集中分布式一体化数据库管理软件。点此查看详情https://www.aliyun.com/activity/database/polardbx-v2?spma2c6h.13046898.publish-article.8.44146ffaE0lEWT 立即咨询专家&#xf…

Axure PR 9 中继器 09 删除行

大家好&#xff0c;我是大明同学。 接着上期的内容&#xff0c;这期内容&#xff0c;我们来了解一下Axure中继器数据表删除行交互设计。 预览地址&#xff1a;https://vvlmqu.axshare.com 删除行 1.打开上期RP 文件&#xff0c;设计一个删除弹窗元件&#xff0c; 创建为动态面…

HDCP(五)

HDCP 2.2 测试用例设计详解 基于HDCP 2.2 CTS v1.1规范及协议核心机制&#xff0c;以下从正常流程与异常场景两大方向拆解测试用例设计要点&#xff0c;覆盖认证、密钥管理、拓扑验证等关键环节&#xff1a; 1. 正常流程测试 1.1 单设备认证 • 测试目标&#xff1a;验证源设…

商城APP打包教程

下载 HBuilderX 工具 HBuilderX支持插件拓展功能。App开发版已集成相关插件、开箱即用 根据自身电脑系统选择对应软件下载&#xff0c;建议选择APP开发版 2. 下载好软件安装后打开 建议直接在uniapp插件页面一键导入&#xff0c;正常情况下uniapp插件都是最新的&#xff0c;大家…

Spring 框架的核心基础:IoC 和 AOP

一、IoC&#xff08;Inversion of Control&#xff0c;控制反转&#xff09; 定义&#xff1a; IoC&#xff08;Inversion of Control&#xff0c;控制反转&#xff09;&#xff0c;就是把对象创建和依赖关系的管理交给 Spring 容器&#xff0c;而不是由程序员手动去创建对象…

SpringBoot 基础知识,HTTP 概述

1. 概述 1.1 Spring Spring 提供若干个子项目&#xff0c;每个项目用于完成特定功能 Spring 的若干个子项目都基于一个基础的框架&#xff1a;Spring Framework 框架类似于 房屋的地基 但 Spring Framework 配置繁琐&#xff0c;入门难度大 1.2 Spring Boot 于是&#xf…

《网络管理》实践环节04:SNMP监控数据采集流程及SNMP协议详细分析

兰生幽谷&#xff0c;不为莫服而不芳&#xff1b; 君子行义&#xff0c;不为莫知而止休。 1 实验目标 1. 理解SNMP网络管理原理 2. 掌握SNMP服务器采集SNMP Agent数据的方法 3. 掌握SNMP报文发送和应答流程 4. 掌握典型GetResponsePDU数据结构分析的方法 4. 具备SNMP通信…

《Uniapp-Vue 3-TS 实战开发》构建HTTP请求拦截器

引言 在 UniApp 结合 TypeScript 和 Vue3 的项目开发中&#xff0c;请求拦截器起着至关重要的作用。它能够在请求发送前和响应接收后对数据进行统一处理&#xff0c;极大地提高了代码的可维护性和功能性。本文将详细解析上述代码中请求拦截器的实现及其在 UniApp-Ts-Vue3 项目中…

从PDF中提取表格:以GB/T2260—2007为例

文章目录 先说结论前因后果思路1、PDF2CSV2、PDF2MD → MD2CSV3、针对不同表格的两种思路1&#xff09; 竖形三线表2&#xff09;五元素为一组 还没结束批量处理1、分割markdown文档2、跳过另一种格式的文档 总结一下 先说结论 结论就是&#xff0c;博主用了一天的时间去研究如…