Soybean Admin 使用tv-focusable兼容电视TV端支持遥控器移动焦点
环境
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();
引入完成之后,使用插件,实现聚焦登录按钮
<NButtonv-focusableclass="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"><NInputv-focusablev-model:value="model.userName":placeholder="$t('page.login.common.userNamePlaceholder')"@click="handleUserNameClick"@on-blur="handleUserNameBlur"/></NFormItem><NFormItem path="password"><NInputv-focusablev-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 插件,实现按钮聚焦、输入框聚焦、下拉菜单聚焦,解决了当页面全屏标签页内容时,摁方向键全部元素都无法聚焦的问题。有疑问或者遇到什么问题可以评论区补充,感谢观看
相关文章:
Soybean Admin 使用tv-focusable兼容电视TV端支持遥控器移动焦点
环境 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这里必须使用飞视浏览器,…...
经济金融最优化:从理论到MATLAB实践——最大利润问题全解析
内容摘要 本文聚焦经济金融领域的最大利润问题,深入探讨不考虑销售影响和考虑销售影响两种情形下的利润最大化模型柯布 - 道格拉斯生产函数等理论构建与求解。 关键词:经济金融;最大利润问题;柯布-道格拉斯生产函数 1. 引言 在…...
大模型学习七:小米8闲置,直接安装ubuntu,并安装VNC远程连接手机,使劲造
一、说明 对于咱们技术人来说,就没有闲的蛋疼的时候,那不是现在机会来了 二、刷机器准备 1、申请解锁手机 申请解锁小米手机https://www.miui.com/unlock/download.html 下载工具,安装下面的步骤来,官网不欺人吧 打开开发者工…...
高可用之战:Redis Sentinal(哨兵模式)
参考:Redis系列24:Redis使用规范 - Hello-Brand - 博客园 1 背景 在我们的《Redis高可用之战:主从架构》篇章中,介绍了Redis的主从架构模式,可以有效的提升Redis服务的可用性,减少甚至避免Redis服务发生完…...
简单括号匹配_栈
课程笔记 10:数据结构(清华) 栈_opnd push-CSDN博客 括号匹配。对于一个表达式,要想确认其中所使用的括号是否匹配,可以采用减而治之的思路,将每对邻近括号消去,则剩下的达式括号匹配当且仅当…...
CSS Grid布局:从入门到放弃再到真香
Flexbox 与 Grid 布局:基础概念与特点 Flexbox Flexbox(Flexible Box Layout),即弹性盒布局模型,主要用于创建一维布局,能够轻松实现元素在一行或一列中的排列、对齐与分布。通过display: flex属性启用 Fl…...
Springboot把外部jar包打包进最终的jar包,并实现上传服务器
1、创建lib目录,把jar包放进这个目录下,然后标记lib目录为“资源根路径”(鼠标右键lib目录->将目录标记为->资源根路径。之后lib文件夹会有如下的图标变化) 文件结构如下: 2、pom文件添加依赖 <dependency…...
仿照管理系统布局配置
1.vue仿照snowy 配置,如下图: 2.代码实现 <template><div class"theme-settings"><!-- 导航栏 --><div class"nav-bar"><el-breadcrumb separator"/"><el-breadcrumb-item>导航设置…...
A2L文件解析
目录 1 摘要2 A2L文件介绍2.1 A2L文件作用2.2 A2L文件格式详解2.2.1 A2L文件基本结构2.2.2 关键元素与声明2.2.3 完整A2L文件示例 3 总结 1 摘要 A2L文件(也称为ASAP2文件)是ECU开发的核心接口文件,用于标定、测量和诊断的关键配置文件&…...
GPT - 因果掩码(Causal Mask)
本节代码定义了一个函数 causal_mask,用于生成因果掩码(Causal Mask)。因果掩码通常用于自注意力机制中,以确保模型在解码时只能看到当前及之前的位置,而不能看到未来的信息。这种掩码在自然语言处理任务(如…...
SpringBoot 数据库MySql的读写分离 多数据源 Shardingsphere高并发优化
介绍 传统的 MySQL 架构中,所有的数据库操作(包括读操作和写操作)都在同一个数据库实例上进行。随着应用程序的规模增长,单一数据库实例可能会成为瓶颈,无法满足高并发的需求。为了优化性能,可以将数据库的…...
适合工程建筑行业的OA系统有什么推荐?
工程行业具有项目周期长、协作链条复杂等特性,传统管理模式下的 “人治”“纸质化” 弊端日益凸显。OA 系统作为数字化管理的核心载体,通过流程标准化、数据可视化,精准解决工程行业项目管理核心痛点。 泛微 e-office 深度聚焦工程场景&#…...
如何使用 DeepSeek 帮助自己的工作?
1. 信息检索 信息检索是获取特定信息的过程,尤其是在大量数据或文本中查找相关内容。这个过程应用广泛,从网页搜索引擎到数据库查询,再到企业内部信息系统。在使用 DeepSeek 或其它类似工具进行信息检索时,可以考虑以下几个重要方…...
python对mysql数据库的操作
现在遇到一个问题如何将数据批量的插入mysql数据库中 基础操作 import asyncio from config import config from mysql_pool import MysqlPoolclass MysqlLoop(object):def __init__(self):self.logger config.loggerself.pool MysqlPool()def loop_query(self, queries):lo…...
MFC案例:利用CFileDialog类选择多个文件的实验
在MFC项目中使用CFileDialog打开文件时,一般的使用场景是选择一个文件,今天我们做一个选择多个文件的实验,运行环境是VS2022。 实验目标:在基于对话框的MFC项目中,通过调用CFileDialog类对象,将选择…...
深入解析栈回溯技术:如何通过异常处理精准定位程序崩溃点
一、栈回溯 1.1 栈回溯的原理 调试程序时,经常发生这类错误: 1.读写某个地址,导致程序崩溃 2.调用某个空函数,导致程序崩溃在异常处理函数中,可以打印出”发生错误瞬间”的所有寄存器。 我们调试时,可以…...
封装uniapp request promise化
uniapp request 封装 一、 封装方法1. 使用 promis 封装 request2. 封装 api 在 api.js3.在要请求的页面 调用 api 一、 封装方法 1. 使用 promis 封装 request const BASE_URL 你的url接口 //比如 http://198.12.3.3/pzexport function request(config {}){let {url,dat…...
重构居家养老安全网:从 “被动响应” 到 “主动守护”
随着全球老龄化加剧,居家养老安全成为社会关注的核心议题。 传统养老模式依赖人工巡检或单一传感器,存在响应滞后、隐私泄露、场景覆盖不足等问题。 由此智绅科技应运而生,七彩喜智慧养老系统构筑居家养老安全网。 而物联网(Io…...
深入理解 GLOG_minloglevel 与 GLOG_v:原理与使用示例
文章目录 深入理解 GLOG_minloglevel 与 GLOG_v:原理与使用示例1. GLOG_minloglevel:最低日志等级控制2. GLOG_v:控制 VLOG() 的详细输出等级3. GLOG_minloglevel 与 GLOG_v 的优先级关系4. 使用示例4.1 基础示例:不同日志等级4.2…...
Unity6下架中国区,团结引擎接棒:这是分裂,还是本地化的开始?
就在近日,一则消息在国内游戏开发圈内迅速传播开来:Unity 6 及其后续版本已在中国大陆及港澳地区下架。这意味着,未来中国用户将无法直接使用 Unity 最新的主线版本。而取而代之的,是由 Unity 中国主导推出的本地化产品 —— 团结…...
ESP8266水位监测以及温湿度数据采集
上面就是ESP8266的引脚图,水温检测使用的是水位监测传感器,温湿度测量使用的是DHT11,DHT11的反应时间是2秒,这里要注意。开发采用Arduino程序 1. 传感器初始化 功能:初始化DHT11温湿度传感器和串口通信。 代码实现&…...
国产信创数据库:PolarDB 分布式版 V2.0,支持集中分布式一体化
阿里云PolarDB数据库管理软件(分布式版)V2.0 ,安全可靠的集中分布式一体化数据库管理软件。点此查看详情https://www.aliyun.com/activity/database/polardbx-v2?spma2c6h.13046898.publish-article.8.44146ffaE0lEWT 立即咨询专家…...
iOS按键精灵辅助工具在游戏开发中的创新应用
一、iOS自动化测试辅助工具 在移动游戏开发领域,iOS按键精灵类辅助工具不同于传统的安卓自动化方案,iOS环境下的自动化测试面临更严峻的技术挑战,但通过创新方法仍可实现精准控制。 # 基于图像识别的智能定位算法示例 def find_button(butt…...
淘宝 API 与 AI 图像识别整合:开启商品主图智能解析新时代
在电商蓬勃发展的当下,淘宝作为行业巨头,承载着海量的商品信息。如何让买家更高效地筛选心仪好物,让卖家精准把握商品展示要点?淘宝 API 与 AI 图像识别技术的整合为这一难题提供了创新性解法,实现对商品主图实时解析&…...
Axure PR 9 中继器 09 删除行
大家好,我是大明同学。 接着上期的内容,这期内容,我们来了解一下Axure中继器数据表删除行交互设计。 预览地址:https://vvlmqu.axshare.com 删除行 1.打开上期RP 文件,设计一个删除弹窗元件, 创建为动态面…...
HDCP(五)
HDCP 2.2 测试用例设计详解 基于HDCP 2.2 CTS v1.1规范及协议核心机制,以下从正常流程与异常场景两大方向拆解测试用例设计要点,覆盖认证、密钥管理、拓扑验证等关键环节: 1. 正常流程测试 1.1 单设备认证 • 测试目标:验证源设…...
商城APP打包教程
下载 HBuilderX 工具 HBuilderX支持插件拓展功能。App开发版已集成相关插件、开箱即用 根据自身电脑系统选择对应软件下载,建议选择APP开发版 2. 下载好软件安装后打开 建议直接在uniapp插件页面一键导入,正常情况下uniapp插件都是最新的,大家…...
Spring 框架的核心基础:IoC 和 AOP
一、IoC(Inversion of Control,控制反转) 定义: IoC(Inversion of Control,控制反转),就是把对象创建和依赖关系的管理交给 Spring 容器,而不是由程序员手动去创建对象…...
SpringBoot 基础知识,HTTP 概述
1. 概述 1.1 Spring Spring 提供若干个子项目,每个项目用于完成特定功能 Spring 的若干个子项目都基于一个基础的框架:Spring Framework 框架类似于 房屋的地基 但 Spring Framework 配置繁琐,入门难度大 1.2 Spring Boot 于是…...
《网络管理》实践环节04:SNMP监控数据采集流程及SNMP协议详细分析
兰生幽谷,不为莫服而不芳; 君子行义,不为莫知而止休。 1 实验目标 1. 理解SNMP网络管理原理 2. 掌握SNMP服务器采集SNMP Agent数据的方法 3. 掌握SNMP报文发送和应答流程 4. 掌握典型GetResponsePDU数据结构分析的方法 4. 具备SNMP通信…...
