# (Vue)JsvFocusBlock
# 1. 基本介绍
JsvFocusBlock(焦点控制)组件用于 JsView-Vue3 架构。
它是一个穿插于UI组件之间,用于焦点变更以及按键流响应的组件。
JsvFocusBlock可以通过设置namespace属性进行命名空间隔离。你可以通过其中一个JsvFocusBlock的引用获取到相同namesapce下的其他JsvFocusBlock节点。但是获取本namespace外层嵌套的JsvFocusBlock节点是被禁止的(替代方法请参考 Demo)。
# 2. 控件说明
对应代码位置:
node_modules/@shijiu/jsview-vue/utils/JsViewEngineWidget/JsvFocus/JsvFocusBlock.vue
前提条件:
在使用JsvFocusBlock前,首先需要将JsvFocusManager组件添加到app中。添加后,插件会自动注册一个默认的顶级namespace到app。后续添加的所有组件,均会以此为根。
例如 main.ts 文件中添加
import { jsvCreateFocusManager } from 'jsview/utils/JsViewEngineWidget' ... app.use(jsvCreateFocusManager(), '#app')
1
2
3
4
5输入参数
jsvCreateFocusManager(): 创建一个JsView焦点插件。 '#app': app mount的根元素
属性设置(props):
name / namespace: [String]
- 二选一。手动给JsvFocusBlock设置一个名字/命名空间,该属性不能包含"."关键字,"."用于级联namespace。
- name: 名字。可以使用getBlock(name)函数通过名字获取到该组件的引用。
- namespace:命名空间。隔离JsvFocusBlock组件,用于解决 name 冲突。namespace下name唯一,也就是说:<JsvFocusBlock namespace="xxx">节点包含的所有的<JsvFocusBlock name="yyy">, name必须各不相同。
- 当节点不需要被setFocus作为目标时, name和namespace都可以不设置, 默认会给与一个匿名的name(通过 getCurrentNodeStask 可以看到)
- 二选一。手动给JsvFocusBlock设置一个名字/命名空间,该属性不能包含"."关键字,"."用于级联namespace。
autoFocus: [Boolean]
- 在 mounted 后自动尝试申请焦点。
onAction: [Object]
- 包含事件回调函数的对象。可选则 onDispatchKeyUp, onDispatchKeyDown, onKeyUp, onKeyDown, onFocus, onBlur 其中的几个或全部。当onAction成员的回调和下面其他直接设置的回调事件同时存在时,onAction成员的回调被忽略,直接设置的回调有效。
onDispatchKeyUp, onDispatchKeyDown: [(event: JsvFocusEvent) => Boolean]
- Key Up/Down 分发事件回调,当Root获取到按键时,从Root节点向当前Focus节点方向逐级分发。 当某个节点注册此回调函数并返回true时,截断该事件继续传递。
onKeyUp, onKeyDown: [(event: JsvFocusEvent) => Boolean]
- Key Up/Down 事件回调,如果Dispatch事件没有被截断,则尝试从当前Focus节点向Root节点方向逐级触发。 当某个节点注册此回调函数并返回true时,截断该事件继续传递。
onFocus, onBlur: [(ownerNode: JsvFocusBlock) => Void]
- onFocus: 当某个节点注册onFocus回调函数并获取到焦点时,触发该回调。
- onBlur: 当前Focus节点注册ononBlur回调函数并失去到焦点时,触发该回调。
JsvFocusEvent 成员说明:
- ownerNode: [JsvFocusBlock] 回调函数所在的节点实例
- type: [String] 键类型, "keyup"/"keydown"
- keyCode: [Number] 键值, 例如:
- 13(Enter) / 37(ArrowLeft) / 38(ArrowUp) / 39(ArrowRight) / 40(ArrowDown)
- key: [String] 键值字符串,例如:
- "Enter" / "ArrowLeft" / "ArrowUp" / "ArrowRight" / "ArrowDown"
方法(methods):
async getFullName() => String
说明:
- 获取JsvFocusBlock的包含有全局namespace的名字
返回值:
- 格式为 ".{namespace}. * * * .{namespace}" 或 ".{namespace}. * * * .{namespace}.{name}" 形式的字符串。
常见的返回值(ns = namespace):
UI结构 名字 错误理解 name1 .name1 name1 -> name2 .name2" ns1 -> name2 .ns1.name2" ns1 -> ns2 -> name3 .ns1.ns2.name3 ns1 -> name2 -> name3 .ns1.name4 .ns1.name2.name4ns1 -> name2 -> ns3 .ns1.ns3 .ns1.name2.ns3ns1 -> name2 -> ns3 -> name4 .ns1.ns3.name4 .ns1.name2.ns3.name4首字符"."代表全局namesapce
提示
namespace下的所有name都是扁平化的。 这和编程语言中的命名空间没什么不同。
async requestFocus() => void
- 说明:
- 让此节点获得焦点(不常用, 基本可以被 useFocusHub + setFocus 调用替代)
- 说明:
注意
所以的方法均为异步处理, 在其挂载到链接着根节点的dom树后产生效果, 可使用await对齐调用
焦点切换
useFocusHub(bool withNameSpace) => Hub
- 说明:
- 全局方法, 可获得根节点focusHub对象, 用于控制焦点切换
- 输入参数:
- withNameSpace: 若为false, 拿到root的FocusHub, 对在NameSpace节点中的子节点进行setFocus操作时,需要传完整名称(命名空间.名称);若为true则获得含有当前深度nameSpace的hub, 在相同的命名空间中,对子节点进行setFocus时,直接传名称即可。
- 说明:
FocusHub对象
- 接口和说明
/**
* setFocus
*
* 设置焦点,在焦点对应的 JsvFocusBlock 完成mounted之前设置的话,
* 会在JsvFocusBlock完成mounted后立即设上焦点
*
* @param {string} focusName
* 当前命名空间的名字/全局命名空间的名字。
* 当前命名空间的名字: 是指和当前节点相同命名空间的名字(首字符不为".")。
* 对于二级命名空间,可以使用 "{namespace}.{name}"的方式。
* 全局命名空间的名字: 是指通过getName()函数获取后再附加下级节点的名字。
* *请注意: 请避免手写的全局命名空间名字,防止在二次集成时可能会出现名字找不到的问题。
* @param {boolean} passToChild 保持/恢复子焦点的聚焦状态(若此焦点的子焦点为聚焦状态,
* 当本焦点从失焦状态恢复为聚焦状态时,子焦点也自动回复为聚焦状态),默认为不保持/恢复。
*/
public setFocus(focusName: string, passToChild: boolean = false)
/**
* returnFocusToParent
*
* 将焦点从当前节点还给其父JsvFocusBlock节点
*/
public returnFocusToParent()
/**
* getCurrentFocus
*
* 获取当前焦点的的 focusName 信息,用于后续的进行的 setFocus 操作
* (注意, 不带NameSpace)
*
* @return {string} 当前焦点的focusName
*/
public getCurrentFocus(): string
/**
* getLastFocus
*
* 获得切到到当前焦点前的上一个焦点的name(注意, 不带NameSpace),
* 注意:如果上一次的焦点已经不在dom树时, 返回null
*
* @return {string} 焦点的focusName
*/
public getLastFocus(): string
/**
* getDeactivedPageFocus
*
* 在对一个keep-alive的界面进行deactive后,为了未来将其active时恢复离开时的焦点时使用。
* 在 onDeactive 回调中调用
* 注意: 只有当一个有焦点的keep-alive倍deactive时才能获得
*
* @return {string} 焦点的focusName
*/
public getDeactivedPageFocus(): string
/**
* getNameSpace
*
* 获取当前FocusHub的nameSpace
*
* @return {string} 当前FocusHub的nameSpace
*/
public getNameSpace(): string
/**
* enableFocusTrace
*
* 调试API: 启用焦点变化时的回调日志,通过console.log打出,其中含产生此行为的调用堆栈
*
* @param {boolean} needCallStack 是否打印焦点变化时产生变化的api调用的堆栈,例如setFocus, unmount之类
*/
public enableFocusTrace(needCallStack: boolean = true)
/**
* printAllFocusable
*
* 调试API: console.log当前 Hub 中所有的节点引用,只打印当前NameSpace的内容
*/
public printAllFocusable()
/**
* getCurrentFocusStack
*
* 调试API: console.log当前焦点的焦点链条(从root到此节点的链条)
*
* @return {Array} 当前焦点的链条队列
*/
public getCurrentFocusStack(): Array<Object>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# 3. 手把手搭建JsvFocusBlock
- 在src下创建空白的 vue3 文件 demo.vue
<template>
<div></div>
</template>
<script setup>
<script/>
<style scoped>
</style>
2
3
4
5
6
7
8
9
- 在template部分,将<div>更换为<jsv-focus-block>,给组件设置onAction配置,此配置为一个map{}
<template>
<jsv-focus-block :onAction="{
onKeyDown: onKeyDown_Level0,
onDispatchKeyDown: onDispatchKeyDown_Level0,
onFocus: onFocus_Level0,
onBlur: onBlur_Level0,
}">
</jsv-focus-block>
</template>
2
3
4
5
6
7
8
9
- 在script部分,声明上述onAction中的函数
<script setup>
let onKeyDown_Level0 = (event)=>{
console.log(`level 0 收到回流的按键 code=${event.keyCode}`);
return false; // 标识为未使用, 允许继续回流
}
let onDispatchKeyDown_Level0 = (event)=>{
console.log(`level 0 收到下发的按键 code=${event.keyCode}`);
return false; // 标识为未使用, 允许继续下发
}
let onFocus_Level0 = ()=>{
console.log(`level 0 收到focus事件`);
}
let onBlur_Level0 = ()=>{
console.log(`level 0 收到blur事件`);
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 在template部分,为<jsv-focus-block>追加一个子节点,并通过name属性进行命名(为了聚焦操作),同样添加onAction和函数定义
<template>
<jsv-focus-block :onAction="{
onKeyDown: onKeyDown_Level0,
onDispatchKeyDown: onDispatchKeyDown_Level0,
onFocus: onFocus_Level0,
onBlur: onBlur_Level0,
}">
<jsv-focus-block name="level1" :onAction="{
onKeyDown: onKeyDown_Level1,
onDispatchKeyDown: onDispatchKeyDown_Level1,
onFocus: onFocus_Level1,
onBlur: onBlur_Level1,
}"/>
</jsv-focus-block>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
...
let onKeyDown_Level1 = (event)=>{
console.log(`level 1 收到回流的按键 code=${event.keyCode}`);
return false; // 标识为未使用, 允许继续回流
}
let onDispatchKeyDown_Level1 = (event)=>{
console.log(`level 1 收到下发的按键 code=${event.keyCode}`);
return false; // 标识为未使用, 允许继续下发
}
let onFocus_Level1 = ()=>{
console.log(`level 1 收到focus事件`);
}
let onBlur_Level1 = ()=>{
console.log(`level 1 收到blur事件`);
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 在script部分,从jsview模块引入useFocusHub,添加初始化后的聚焦动作,让命名为level1的节点获得焦点
<script setup>
import { useFocusHub } from "jsview";
...
let hub = useFocusHub();
hub.setFocus("level1");
</script>
2
3
4
5
6
7
8
- 为此独立程序添加通知小程序运行器关闭启动图的处理(需要引入onMounted和jsvRuntimeBridge)
<script setup>
import { onMounted } from "vue"
import { jJsvRuntimeBridge, useFocusHub } from "jsview"
...
onMounted(()=>{
jJsvRuntimeBridge.notifyPageLoaded(); // 通知小程序运行器关闭启动图和超时计时器
})
</script>
2
3
4
5
6
7
8
9
10
- 在script标签纸中纠正eslint和vue3 setup语法在no-unused-vars上的兼容问题
<script setup>
/* eslint-disable no-unused-vars */
...
<script>
2
3
4
- 代码整体一览
<template>
<jsv-focus-block :onAction="{
onKeyDown: onKeyDown_Level0,
onDispatchKeyDown: onDispatchKeyDown_Level0,
onFocus: onFocus_Level0,
onBlur: onBlur_Level0,
}">
<jsv-focus-block name="level1" :onAction="{
onKeyDown: onKeyDown_Level1,
onDispatchKeyDown: onDispatchKeyDown_Level1,
onFocus: onFocus_Level1,
onBlur: onBlur_Level1,
}"/>
</jsv-focus-block>
</template>
<script setup>
/* eslint-disable no-unused-vars */
import { onMounted } from "vue"
import { jJsvRuntimeBridge, useFocusHub } from "jsview"
let onKeyDown_Level0 = (event)=>{
console.log(`level 0 收到回流的按键 code=${event.keyCode}`);
return false; // 标识为未使用, 允许继续回流
}
let onDispatchKeyDown_Level0 = (event)=>{
console.log(`level 0 收到下发的按键 code=${event.keyCode}`);
return false; // 标识为未使用, 允许继续下发
}
let onFocus_Level0 = ()=>{
console.log(`level 0 收到focus事件`);
}
let onBlur_Level0 = ()=>{
console.log(`level 0 收到blur事件`);
}
let onKeyDown_Level1 = (event)=>{
console.log(`level 1 收到回流的按键 code=${event.keyCode}`);
return false; // 标识为未使用, 允许继续回流
}
let onDispatchKeyDown_Level1 = (event)=>{
console.log(`level 1 收到下发的按键 code=${event.keyCode}`);
return false; // 标识为未使用, 允许继续下发
}
let onFocus_Level1 = ()=>{
console.log(`level 1 收到focus事件`);
}
let onBlur_Level1 = ()=>{
console.log(`level 1 收到blur事件`);
}
let hub = useFocusHub();
hub.setFocus("level1");
onMounted(()=>{
jJsvRuntimeBridge.notifyPageLoaded(); // 通知小程序运行器关闭启动图和超时计时器
})
</script>
<style scoped>
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
- 将main.ts指向此vue文件
...
// import App from '@/App.vue'
import App from './demo.vue'
const app = createApp(App)
...
2
3
4
5
6
- 运行后的打印(在devtools中看见,或者android中的
adb logcat -s JSIConsole
可以看见)
level 0 收到focus事件
level 1 收到focus事件
2
- 按下OK按键后的打印信息
level 0 收到下发的按键 code=13
level 1 收到下发的按键 code=13
level 1 收到回流的按键 code=13
level 0 收到回流的按键 code=13
2
3
4
# 4. 渐进式焦点样例
- 参考开发环境的
node_modules/@shijiu/jsview-vue-samples/FocusBlockDemos/ProgressiveFocusControl
课题通过改动 src/main.ts 的内容启动这个页面:
import { createApp } from 'vue'
import { jsvCreateFocusManager } from 'jsview/utils/JsViewEngineWidget'
// 改动点!!!!! 为如下两行
// import App from '@/App.vue'
import App from 'jsview/samples/FocusBlockDemos/ProgressiveFocusControl/App.vue'
const app = createApp(App)
...
2
3
4
5
6
7
8
9
此Demo的示意目的是(节选自本目录App.vue):
<!--
* 【界面概述】
* 渐进式焦点展示,整个界面分为上下两个部分,每个部分有3个方格,当方格获焦的时候会变更颜色。
* 所展示的效果是,上下两个部分只了解到兄弟节点focusBlock的名字,而并不知道其子节点的名字。
* 当从上部分的子焦点切换到下部分的子焦点过程,是首先指定父节点来进行setFocus,
* 然后通过onFocus事件处理再进一步setFocus到子焦点上
*
*
* 【技巧说明】
* Q: 如何进行按键响应?
* A: 重载函数onKeyDown/onKeyUp/onDispatchKeyDown/onDispatchKeyUp中任何一个关心
* 的按键事件响应函数,处理ev.keyCode判断按键值,通过返回值控制消息传递链是否中止
*
* Q: 如何进行焦点切换?
* A: 首先为子焦点设置name属性,当需要进行焦点切换的时候,通过 useFocusHub() 提供的hub
* 的 setFocus 函数处理
*
* Q: setFocus的第二个参数keepChildFocus的作用是什么?
* A: 使用场景举例:
* 针对有子节点的FocusBlock,当子节点已经获焦后,通过对自身进行setFocus并且设置
* keepChildFocus=false,可让自己的子焦点失焦
*
-->
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
效果如下所示: 一个六个可获焦方块的界面中,分为上层3个方块(分别为A,B,C)为一组,下层3个方块(1,2,3)为另一组,方格处于焦点状态则表现为红色,非焦点状态则表现为绿色
其焦点树关系为:
App -+- UpPlane -+- A
| +- B
| +- C
+- DownPlane -+- 1
+- 2
+- 3
2
3
4
5
6
按照低耦合的程序设计思想,App只调度 UpPlane 和 DownPlane 之间的焦点切换,而当UpPlane获焦后其内部的焦点调度由UpPlane自己完成, 所以落实到代码中就是 App 收到按键处理后,根据按键是否为上键或者下键,进行调度UpPlane和DownPlane(节选自本目录App.vue):
<script setup>
...
const onKeyDownFunc = (ev)=>{
console.log(`App 根节点 收到回流按键 code=${ev.keyCode}`);
// 将按键转化为left/right指令
let keyConsumed = true;
switch(ev.keyCode) {
case DefaultKeyCodeMap.Up:
focusHub.setFocus("UpPlane");
break;
case DefaultKeyCodeMap.Down:
focusHub.setFocus("DownPlane");
break;
default:
keyConsumed = false;
}
return keyConsumed; // 若为true则会阻止按键下发到子节点
}
...
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
UpPlane内部当获焦后再进行如下所示(节选自本目录UpPlane.vue)的子节点的调度。
<script setup>
...
const onFocusFunc = () => {
console.log(`父节点 ${planeName} 获得焦点`);
// 让子焦点获焦
lastIndex = globalColumn.index; // 同步全局节点信息
focusHub.setFocus(ChildBlockNames[lastIndex]);
};
...
</script>
2
3
4
5
6
7
8
9
10
11
这种方法叫JsvFocusBlock的渐进式获取焦点的方式。
另外,可以在运行此demo时,打开devtools观察console信息,可观察到上下左右移动焦点时各个模块获焦失焦的打印。
# 5. 自动获焦属性autoFocus
- 参考开发环境的
node_modules/@shijiu/jsview-vue-samples/FocusBlockDemos/AutoFocus
课题通过改动 src/main.ts 的内容启动这个页面:
import { createApp } from 'vue'
import { jsvCreateFocusManager } from 'jsview/utils/JsViewEngineWidget'
// 改动点!!!!! 为如下两行
// import App from '@/App.vue'
import App from 'jsview/samples/FocusBlockDemos/AutoFocus/App.vue'
const app = createApp(App)
...
2
3
4
5
6
7
8
9
此Demo的示意目的是(节选自本目录App.vue):
<!--
* 【界面概述】
* JsvFocusBlock的autoFocus效果展示
* 主要用于Dialog弹出的场景,当Dialog内部的JsvFocusBlock设置autoFocus后,
* Dialog只要展示就会抢走焦点,同时,此样例也展示了,当Dialog退出后,只要App讲焦点
* 还给Dialog弹出前的焦点分支的根部,按照渐进式焦点处理写法,各级的onFocus触发后
* 后会再次将焦点传递给目标的子节点
*
-->
2
3
4
5
6
7
8
9
效果图如下所示: 一个三个可获焦方块的界面中,方格处于焦点状态则表现为红色,非焦点状态则表现为绿色,按下OK键时,弹出对话框,对话框通过autoFocus属性获得焦点。在对话框点击OK键后,会返回主界面中启动对话框前的方格上
Dialog中加入autoFocus的处理(节选自本目录下的DialogBlock.vue)如下所示:
可以在运行此demo时,打开devtools观察console信息,可观察到上下左右移动焦点时各个模块获焦失焦的打印。
# 6. 其他技巧提示
提问1: 为什么不把焦点控制集成在普通的HTML元素中?
为了加速焦点传递。
一个复杂的应用里,可能包含几千个HTML元素,而其中需要只有一小部分需要用到焦点功能。大量的元素去传递按键流,非常影响按键响应时间。
所以将焦点控制部分单独提取出来做成独立组件,由开发者在需要的地方手动植入,是一个既不影响开发体验,又能获得良好响应时间的处理方法。
提问2: namespace节点如果让带nameSpace的节点尽心共聚焦?
- 例如namespace1->namespace2->name3,想通过namespace2的引用获取name3时,namespace2所在的命名空间为namespace1,所以路径中需要添加上自己的命名空间。
<templete> <JsvFocusBlock namespace="ns1"> <JsvFocusBlock namespace="ns2" ref="jfbNs2"> ... <JsvFocusBlock name="n3"> ... </JsvFocusBlock> </JsvFocusBlock> </JsvFocusBlock> </templete> <script setup> import { useFocusHub } from "jsview"; ... const focusHub = useFocusHub(); focusHub.setFocus("ns2.n3"); // NameSpace以.进行切分 ... </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
提问3: 我可以通过名字直接跳转到任意焦点?
- focusHub.setFocus(name)就可以实现跳转。
import { useFocusHub } from "jsview";
const focusHub = useFocusHub();
//需要跳转焦点时
focusHub.setFocus(name);
2
3
4
提问4: 当不知道按键事件发给哪个焦点时?调试过程中想找出当前的焦点时?
- 在App.vue文件中将 focusHub 挂载到window上,方便devtools的命令输入行调用
<script setup>
import { useFocusHub } from "jsview"
window.DebugJsvFocusHub = useFocusHub();
...
<script>
2
3
4
5
6
- 开始确认焦点前,先按一下按键触发一个按键事件来激活debug的log信息
- 上述的
window.DebugJsvFocusHub
对象,通过printGlobalLastFocus
接口获得从焦点信息 - 上述的
window.DebugJsvFocusHub
对象,通过printFocusList
接口获得从根节点到焦点的堆栈信息
提问5: 在代码时如何获取当前焦点的name?
<script setup>
import { useFocusHub } from "jsview"
let hub = useFocusHub();
let name = hub.getCurrentFocus(); // 输出为焦点的名称的String
<script>
2
3
4
5
6
提问6: 当焦点丢失时,如何定位焦点丢失到了哪
方案1 通过 FocusHub 的 getCurrentFocus 接口,定位当前焦点到底落在哪了
<script setup>
import { useFocusHub } from "jsview"
let hub = useFocusHub();
let name = hub.getCurrentFocus(); // 输出为焦点的名称的String
<script>
2
3
4
5
6
方案2 通过 FocusHub 的 getCurrentFocusStack 接口, 定位从root节点到当前焦点的 stack, 以此确认焦点在哪
<script setup>
import { useFocusHub } from "jsview"
let hub = useFocusHub();
let name = hub.getCurrentFocusStack(); // 输出为焦点的名称的String
<script>
2
3
4
5
6
:::
提问7: 如何将焦点退后一级,仅仅让最后一级焦点失焦?
- 方案1,直接对焦点链上的父焦点进行setFocus
<script setup>
import { useFocusHub } from "jsview"
let hub = useFocusHub();
hub.setFocus(parentName);
<script>
2
3
4
5
6
- 方案2,视通 focusHub 的 returnFocusToParent 接口
<script setup>
import { useFocusHub } from "jsview"
let hub = useFocusHub();
hub.returnFocusToParent();
<script>
2
3
4
5
6
提问8: 对焦点执行setFocus时,不想影响其子焦点的focus状态(或者进行焦点回归时,想让回归的这个焦点的子焦点链条恢复成最后一次失焦前的状态)?
- 方案: setFocus调用的第二个参数设置为true,第二个参数的功能是恢复/保持子链条焦点状态(restoreChildFocus)
<script setup>
import { useFocusHub } from "jsview"
let hub = useFocusHub();
hub.setFocus(parentName, true); // restoreChildFocus = true
<script>
2
3
4
5
6
← 触控功能 (Vue)MetroWidget →