Angular Material 18+ 高级教程 – CDK Accessibility の ListKeyManager
介绍
ListKeyManager 的作用是让我们通过 keyboard 去操作 List Items。
一个典型的例子:Menu
有 4 个步骤:
tab to menu
enter 打开 menu list
按上下键选择 item
enter 选中 item
ListKeyManager 主要是负责第三个步骤,按上下键的功能。
ListKeyManager,ActiveDescendantKeyManager,FocusKeyManager
ListKeyManager 只是一个普通的 class,它不是 Dependency injection Provider 哦,所以使用它的方式是 new ListKeyManager()。
ActiveDescendantKeyManager 是 ListKeyManager 的派生类,它也没有什么特别的,只是重载了 ListKeyManager 的一个方法而已
从方法名字可以猜得出来,在按上下键时,target item 会被 set active styles。
FocusKeyManager 也是 ListKeyManager 的派生类
从方法名字可以猜得出来,在按上下键时,target item 会被 focus。
总结:
主要处理上下键核心功能的是 ListKeyManager
ActiveDescendantKeyManager 只是多了一个 set item active styles 而已
FocusKeyManager 只是多了一个 focus item 而已
ListKeyManager
我们直接看代码学习吧,毕竟它的原理太简单了。
初始化 ListKeyManager
初始化 ListKeyManager 需要传入 item list,它可以是一个 QueryList 或者 Array。
提醒:这个接口目前 v17.3.0 还没有跟上 Angular 新潮流 -- Signal。QueryList 在 Signal-based Query 中已经不公开了,我们根本拿不到 QueryList。
相关 Github Issue – ListKeyManager: Support Signal-based Query。对 Query 不熟悉的朋友,可以看这篇 Angular 17+ 高级教程 – Component 组件 の Query Elements。
更新:v17.3.2 已经支持 Signal-based Query 了。
由于要想监听 Signal value change 只能透过 effect,而 effect 依赖 Dependancy Injection,所以我们必须传入 Injector,这比使用 QueryList 繁琐了一些。
好,回到主题。
list item 必须是一个对象,同时实现 ListKeyManagerOption 接口。
下面我会用 ActiveDescendantKeyManager 作为例子,所以我们也看看 ActiveDescendantKeyManager 的 constructor。
item 必须实现 Highlightable 接口。
好,搞清楚类型后,我们来初始化一个 ActiveDescendantKeyManager。
下面这个是 Item class,它依要求实现了 Highlightable 接口。
class Item implements Highlightable {
active = false;
setActiveStyles(): void {
this.active = true;
}
setInactiveStyles(): void {
this.active = false;
}
}
初始化 ActiveDescendantKeyManager
export class AppComponent {
constructor() {
// 1. 创建 Item List
const items = [new Item(), new Item(), new Item(), new Item()]; // 2. 初始化 ActiveDescendantKeyManager
const keyManager = new ActiveDescendantKeyManager(items);
}
}
setActiveItem
通过 ListKeyManager 的一些方法,我们可以选择要 active 哪一个 item
console.log(items[0].active); // false
keyManager.setFirstItemActive();
console.log(items[0].active); // true
调用 setFirstItemActive,它内部会执行 items[0].setActiveStyles,然后 items[0].active 就变成 true 了。
keyManager.setNextItemActive();
console.log(items[0].active); // false
console.log(items[1].active); // true
setNextItemActive 会 active 下一个 item,同时把当前的 inactive。
有 next 就有 prev,有 first 就有 last
keyManager.setPreviousItemActive(); // 前一个
keyManager.setLastItemActive(); // 最后一个
当然也有直接指定第几个 index 要 active 的
keyManager.setActiveItem(2); // active item index 2
skip disabled
Item 实现的接口 ListKeyManagerOptions 有一个 optional 的属性 -- disabled
当 KeyManager 选择 active item 时,它会避开 disabled item。
修改 class Item
class Item implements Highlightable {
// 1. 添加 init value
constructor(item?: Partial<Item>) {
Object.assign(this, item);
} active = false;
// 2. 添加一个 disabled 属性
disabled = false;
setActiveStyles(): void {
this.active = true;
}
setInactiveStyles(): void {
this.active = false;
}
}
调用
// 1. first, last, middle items 都是 disabled
const items = [
new Item({ disabled: true }),
new Item(),
new Item({ disabled: true }),
new Item(),
new Item({ disabled: true }),
]; const keyManager = new ActiveDescendantKeyManager(items);
keyManager.setFirstItemActive();
console.log(items.map(item => item.active)); // [false, true, false, false, false]
由于第一个 item 是 disabled,所以 setFirstItemActive 选了第二个 item 作为 first。
setLastItemActive 也是如此
keyManager.setLastItemActive();
console.log(items.map(item => item.active)); // [false, false, false, true, false]
setNextItemActive 也是如此
keyManager.setFirstItemActive();
keyManager.setNextItemActive();
console.log(items.map(item => item.active)); // [false, false, false, true, false]
setPreviousItemActive 也是如此
keyManager.setLastItemActive();
keyManager.setPreviousItemActive();
console.log(items.map(item => item.active)); // [false, true, false, false, false]
setActiveItem 则不同,不管 item 是否是 disabled,它都照样 set active。
keyManager.setActiveItem(0);
console.log(items.map(item => item.active)); // [true, false, false, false, false]
所以使用 setActiveItem 我们需要自己注意 disabled。
另外 -1 代表全部不选。
keyManager.setActiveItem(-1);
console.log(items.map(item => item.active)); // [false, false, false, false, false]
skipPredicate
disabled item 之所以会被 skip 掉是因为在 set first / last / next / prev active item 时,ListKeyManager 会做一个检测
如果 item 是 disabled 那 index 就会累加 / 累减去下一个或前一个。
如果想自定义 skip 机制,可以透过 ListKeyManager.skipPredicate 方法,把 skipPredicateFn 放进去。
keyManager.skipPredicate(item => {
// 判断是否要 skip 掉这个 item
return true; // return true 就 skip
});
提醒:setActiveItem 内部没有调用 _setActiveItemByIndex 方法,所以它没有 skipPredicate 机制。
withWrap 循环
如果当前 active item 已经在最后一个,而我们继续 setNextItemActive 会怎样?
const items = [new Item(), new Item(), new Item(), new Item(), new Item()]; const keyManager = new ActiveDescendantKeyManager(items);
keyManager.setLastItemActive();
keyManager.setNextItemActive();
console.log(items.map(item => item.active)); // [false, false, false, false, true]
答案是原地不动
如果我们想它 rotate 循环回到第一个,只需要调用 KeyManagerList.withWrap 方法就可以了
const keyManager = new ActiveDescendantKeyManager(items).withWrap(); // 加一个 withWrap keyManager.setLastItemActive();
keyManager.setNextItemActive();
console.log(items.map(item => item.active)); // [true, false, false, false, false]
change Observable
想监听 active item 的变化,可以透过 change 属性
keyManager.change.subscribe(activeItemIndex => {
console.log(activeItemIndex); // 0, 1
}); keyManager.setFirstItemActive();
keyManager.setNextItemActive();
它是一个 RxJS Subject。
updateActiveItem
updateActiveItem 是 setActiveItem 的底层调用
updateActiveItem 比 setActiveItem 少了一个 change 事件发布。
另外,如果是 ActiveDescendantKeyManager 的话
updateActiveItem 比 setActiveItem 还少了 setInactiveStyles 和 setActiveStyles 的调用。
activeItem & activeItemIndex
顾名思义,就是获取当前的 active item 和 index,如果没有它会返回 null 和 -1。
console.log([keyManager.activeItem, keyManager.activeItemIndex]); // [null, null] keyManager.setLastItemActive(); console.log(keyManager.activeItemIndex); // 4
console.log(keyManager.activeItem!.active); // true
onKeydown
List Key Manager,我们上面都在讲 List 的部分,这里开始讲 Key 的部分。
Key 是 keyboard 的 key。
我们学习了许多操作 active item 的方法 -- first, last, prev, next
这些方法要结合 keydown 事件才会发光发热,比如按 ArrowDown 键要调用 setNextItemActive,按 ArrowUp 键要调用 setPreviousItemActive。
看代码
const keyManager = new ActiveDescendantKeyManager(items).withWrap(); keyManager.setFirstItemActive();
console.log(items.map(item => item.active)); // [true, false, false, false, false] // 1. 模拟一个 keydown
const downEvent = new KeyboardEvent('keydown', { key: 'ArrowDown', keyCode: 40 });
keyManager.onKeydown(downEvent);
console.log(items.map(item => item.active)); // [false, true, false, false, false]
KeyListManager.onKeydown 会依据 event.key 和 keyCode 做出相应的操作。
比如 ArrowDown 它会 setNextItemActive,所以 item[1] 变成了 active。
withVerticalOrientation & withHorizontalOrientation
by default,onKeydown 指处理上下键,左右键是不处理的。
如果我们要支持左右键,那需要设置 withHorizontalOrientation。
在没有设置 withHorizontalOrientation 的情况下,按左右键 item active 不会产生任何变化。
const keyManager = new ActiveDescendantKeyManager(items).withWrap(); keyManager.setFirstItemActive(); const rightEvent = new KeyboardEvent('keydown', { key: 'ArrowRight', keyCode: 39 });
keyManager.onKeydown(rightEvent); console.log(items.map(item => item.active)); // [true, false, false, false, false]
加上 withHorizontalOrientation
const keyManager = new ActiveDescendantKeyManager(items).withWrap().withHorizontalOrientation('ltr');
ltr 是 left to right 的缩写,rtl 是 right to left 的缩写,它用来表达用户操作习惯 (比如说有些国家的字是从右边读到左边,有些则是左边读到右边,keyboard 的操作也有这样分类)
keyManager.setFirstItemActive(); const rightEvent = new KeyboardEvent('keydown', { key: 'ArrowRight', keyCode: 39 });
keyManager.onKeydown(rightEvent); console.log(items.map(item => item.active)); // [false, true, false, false, false]
ltr 情况下,ArrowRight 键代表 next。
我们也可以关掉 default 的上下键处理
const keyManager = new ActiveDescendantKeyManager(items).withWrap().withVerticalOrientation(false); // 关掉上下键处理 keyManager.setFirstItemActive(); const downEvent = new KeyboardEvent('keydown', { key: 'ArrowDown', keyCode: 40 });
keyManager.onKeydown(downEvent); console.log(items.map(item => item.active)); // [true, false, false, false, false] 原封不动
withHomeAndEnd
by default,Home 键和 End 键是不处理的。我们可以通过 withHomeAndEnd 将它开启
const keyManager = new ActiveDescendantKeyManager(items).withWrap().withHomeAndEnd();
Home 键是 setFirstItemActive,End 键是 setLastItemActive
keyManager.setFirstItemActive();
const endEvent = new KeyboardEvent('keydown', { key: 'End', keyCode: 35 });
keyManager.onKeydown(endEvent);
console.log(items.map(item => item.active)); // [false, false, false, false, true] const homeEvent = new KeyboardEvent('keydown', { key: 'Home', keyCode: 36 });
keyManager.onKeydown(homeEvent);
console.log(items.map(item => item.active)); // [true, false, false, false, false]
withPageUpDown
by default,PageUp 键和 PageDown 键是不处理的。我们可以通过 withPageUpDown 将它开启
const keyManager = new ActiveDescendantKeyManager(items).withWrap().withPageUpDown(true, 2);
第一个参数是 enabled,第二参数 delta 是指每按一下要跳多少个 item,我写 2 就表示每按一下 PageDown 会执行 2 次 next。
keyManager.setFirstItemActive(); const pageDownEvent = new KeyboardEvent('keydown', { key: 'PageDown', keyCode: 34 });
keyManager.onKeydown(pageDownEvent);
console.log(items.map(item => item.active)); // [false, false, true, false, false] const pageUpEvent = new KeyboardEvent('keydown', { key: 'PageUp', keyCode: 33 });
keyManager.onKeydown(pageUpEvent);
console.log(items.map(item => item.active)); // [true, false, false, false, false]
另外,不管有没有设置 withWrap 循环,PageDown 和 PageUp 都不会循环。
keyManager.setFirstItemActive();
const pageDownEvent = new KeyboardEvent('keydown', { key: 'PageDown', keyCode: 34 });
keyManager.onKeydown(pageDownEvent);
keyManager.onKeydown(pageDownEvent);
keyManager.onKeydown(pageDownEvent);
console.log(items.map(item => item.active)); // [false, false, false, false, true] 依然在最后一个,因为已经到底了
withAllowedModifierKeys
by default,按键如果搭配 modifier keys (Control, Alt, Shift, Meta / Windows) 是不处理的
keyManager.setFirstItemActive();
const downEvent = new KeyboardEvent('keydown', { key: 'ArrowDown', keyCode: 40, altKey: true }); // ArrowDown + Alt Key
keyManager.onKeydown(downEvent);
console.log(items.map(item => item.active)); // [true, false, false, false, false] 原封不动
我们可以通过 withAllowedModifierKeys 让它处理。
const keyManager = new ActiveDescendantKeyManager(items)
.withWrap()
.withAllowedModifierKeys(['ctrlKey', 'altKey', 'shiftKey', 'metaKey']);
不一定要四个都 allow,也可以指令部分 allow。
keyManager.setFirstItemActive();
const downEvent = new KeyboardEvent('keydown', { key: 'ArrowDown', keyCode: 40, altKey: true });
keyManager.onKeydown(downEvent);
console.log(items.map(item => item.active)); // [false, true, false, false, false] 会动了
tabOut
KeyManagerList 对 Tab 键是没有 List Item 处理的,它只是会转发事件而已。
keyManager.tabOut.subscribe(() => console.log('tab out')); // 监听 Tab 键事件
keyManager.setFirstItemActive();
const tabEvent = new KeyboardEvent('keydown', { key: 'Tab', keyCode: 9 });
keyManager.onKeydown(tabEvent); // 调用 tabOut.next()
console.log(items.map(item => item.active)); // [true, false, false, false, false] 原封不动
它只是一个小方便而已,我们也可以自己对 keyboard event 进行判断,并不是一定要依靠 KeyListManager 的 tabOut + onKeydown。
withTypeAhead
withTypeAhead 是用来做原生 DOM <select> typing select option 体验的。
我们 focus select,然后连续打几个字,select 就会去选择字母开头的 option。
首先每个 item 需要有一个 getLabel 方法来表示 item 是什么字。
这个是 ListKeyManagerOption 的接口
因为 withTypeAhead 是 optional 所以 getLabel 也是 optional,如果我们开启 withTypeAhead 要记得声明 getLabel 方法。
// 1. 每个 item 都有 label
const items = [
new Item({ label: 'toyota' }),
new Item({ label: 'tesla' }),
new Item({ label: 'triumph' }),
new Item({ label: 'tata' }),
new Item({ label: 'honda' }),
]; const keyManager = new ActiveDescendantKeyManager(items).withTypeAhead(); // 2. 开启 withTypeAhead
keyManager.setFirstItemActive();
// 3. 连续 keydown 几个字母
keyManager.onKeydown(new KeyboardEvent('keydown', { key: 't', keyCode: 84 }));
keyManager.onKeydown(new KeyboardEvent('keydown', { key: 'e', keyCode: 69 }));
keyManager.onKeydown(new KeyboardEvent('keydown', { key: 's', keyCode: 83 }));
keyManager.onKeydown(new KeyboardEvent('keydown', { key: 'l', keyCode: 76 })); window.setTimeout(() => {
console.log(items.map(item => item.active)); // 4. [false, true, false, false, false] 变成 index 1 active 了
}, 300);
withTypeAhead 默认会有一个 200 milliseonds 的 RxJS debounce time
在这 200ms 内所有 keydown 的字母会把缓存起来,一直到停止 keydown 后的 200ms,ListKeyManager 才会把字母合并成字然后去匹配 item label。
匹配方式是 startsWith & ignorecase,匹配到第一个就 active 那个 item。
相关源码在 list-key-manager.ts
isTyping
isTyping 指的是 withTypeAhead 在 debounce time 的那段期间。
console.log(keyManager.isTyping()); // false
keyManager.onKeydown(new KeyboardEvent('keydown', { key: 't', keyCode: 84 }));
console.log(keyManager.isTyping()); // true keyManager.onKeydown(new KeyboardEvent('keydown', { key: 'e', keyCode: 69 }));
keyManager.onKeydown(new KeyboardEvent('keydown', { key: 's', keyCode: 83 }));
keyManager.onKeydown(new KeyboardEvent('keydown', { key: 'l', keyCode: 76 })); window.setTimeout(() => {
console.log(items.map(item => item.active));
console.log(keyManager.isTyping()); // false
}, 300);
cancelTypeahead
cancelTypeahead 的作用是把 withTypeAhead typing 时累积的字母清掉。
destroy
KeyListManager 会监听 item list change (for QueryList 和 Signal 的话),如果已经不需要 KeyListManager 了的话,可以调用 destroy 方法,这样它会取消监听。
Items changes
假如原本 active 的是 item index 3,
然后 items 发生了变化,active item 变成了 index 5,
那么 activeItemIndex 会自动更新
但有一点要特别注意,假如 active item 没有在 new items 中,KeyListManager 并不会把 active item 设置成 null 哦。
如果这时我们对 active item 做操作的话,很可能会出现 item 指令已经 destroy 的 error。
KeyListManager with real DOM
上面例子用的是模拟 keydown event 主要是想让大家看得清楚 how it work。
这里我补上一个 real DOM 的例子,不过我就不再过多讲解了。
App Template
<ul class="item-list" appItemList>
<li class="item" appItem>Item1</li>
<li class="item" appItem>Item2</li>
<li class="item" appItem>Item3</li>
<li class="item" appItem disabled>Item4</li>
<li class="item" appItem>Item5</li>
<li class="item" appItem>Item6</li>
<li class="item" appItem>Item7</li>
<li class="item" appItem>Item8</li>
<li class="item" appItem>Item9</li>
<li class="item" appItem>Item10</li>
</ul>
appItemList 和 appItem 是指令。
App Styles
:host {
display: block;
padding: 56px;
} .item-list {
display: flex;
flex-direction: column;
gap: 1px;
width: 256px;
border: 4px solid black; /* 1. item list focused 会红框 */
&:focus {
border-color: red;
} .item {
padding: 8px 12px;
cursor: pointer;
user-select: none; /* 2. item disabled 会灰色 */
&[disabled] {
background-color: lightgray;
} /* 3. active item 会是粉色 */
&.active {
background-color: pink;
color: red;
}
}
}
App 组件
@Component({
selector: 'app-root',
standalone: true,
imports: [ItemListDirective, ItemDirective],
templateUrl: './app.component.html',
styleUrl: './app.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {}
只要 imports 指令就可以了,因为逻辑都写在指令里。
Item 指令
import type { Highlightable } from '@angular/cdk/a11y';
import { booleanAttribute, Directive, ElementRef, inject, input, signal } from '@angular/core'; @Directive({
selector: '[appItem]',
standalone: true,
host: {
// 2. 当 active 时添加 CSS class active
'[class.active]': 'isActive()',
},
})
// 1. 实现 Highlightable 接口
export class ItemDirective implements Highlightable {
readonly isActive = signal(false);
readonly disabledInput = input(false, { alias: 'disabled', transform: booleanAttribute }); setActiveStyles() {
this.isActive.set(true);
}
setInactiveStyles() {
this.isActive.set(false);
}
get disabled(): boolean {
return this.disabledInput();
} readonly hostElement: HTMLElement = inject(ElementRef).nativeElement;
getLabel(): string {
// 3. label 拿节点的 text 就好了
return this.hostElement.textContent ?? '';
}
}
Item List 指令
import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
import { afterNextRender, contentChildren, DestroyRef, Directive, inject } from '@angular/core'; import { ItemDirective } from './item.directive'; @Directive({
selector: '[appItemList]',
standalone: true,
host: {
// 1. 设置成 tabbable 不然没有地方给 user keydown
tabindex: '0',
'(keydown)': 'handleKeydown($event)',
},
})
export class ItemListDirective {
// 2. query items
readonly items = contentChildren(ItemDirective); private keyManager!: ActiveDescendantKeyManager<ItemDirective>; constructor() {
const destroyRef = inject(DestroyRef);
afterNextRender(() => {
// 3. 创建 KeyListManager
this.keyManager = new ActiveDescendantKeyManager(this.items())
.withWrap()
.withTypeAhead(500)
.withPageUpDown(true, 3)
.withHomeAndEnd(); // 5. 当指令销毁时,销毁 KeyListManager。
destroyRef.onDestroy(() => this.keyManager.destroy());
});
} handleKeydown(event: KeyboardEvent) {
// 4. 接收 keydown event 然后转交给 ListKeyManager 处理
this.keyManager.onKeydown(event);
}
}
效果
上下键,withWrap 循环,disabled item
Home 键,End 键,PageUp 键,PageDown 键
withTypeAhead,typing item5 and then item7
FocusKeyManager 小知识
focus 有 origin 的概念,这个我们在上一篇有讲解过。
这里值得注意的是,FocusKeyManager 的 focus origin 默认是 'program',而且 KeyListManager 在任何时候都不会去改变它。
比如说 KeyListManager 的 onKeydown 方法,虽然它肯定是一个 keyboard event,但是 focus origin 依然是 ’program‘。
所以,我们有责任去维护这个 origin,在调用 KeyListManager.onKeydown 方法之前,先调用 FocusKeyManager.setFocusOrigin 方法,把 origin 换成 'keyboard',完成后再修改回去。
总结
ListKeyManager 是一个很常见的功能,Angular Material 的 List,Menu,Stepper 组件都用到了 ListKeyManager。
有兴趣的朋友可以自己去逛一下源码,这几个组件都挺复杂的,它们在 ListKeyManager 基础上有扩展了许多功能。
目录
上一篇 Angular Material 18+ 高级教程 – CDK Accessibility の Focus
下一篇 Angular Material 18+ 高级教程 – CDK Overlay
想查看目录,请移步 Angular 18+ 高级教程 – 目录
喜欢请点推荐,若发现教程内容以新版脱节请评论通知我。happy coding
Angular Material 18+ 高级教程 – CDK Accessibility の ListKeyManager的更多相关文章
- Angular 学习笔记 ( CDK - Accessibility )
@angular/ckd 是 ng 对于 ui 组建的基础架构. 是由 material 团队开发与维护的, 之所以会有 cdk 看样子是因为在开发 material 的时候随便抽象一个层次出来给大家 ...
- Angular Material 教程之布局篇
Angular Material 教程之布局篇 (一) : 布局简介https://segmentfault.com/a/1190000007215707 Angular Material 教程之布局 ...
- Angular Material TreeTable Component 使用教程
一. 安装 npm i ng-material-treetable --save npm i @angular/material @angular/cdk @angular/animations -- ...
- Angular Material design设计
官网: https://material.io/design/ https://meterial.io/components 优秀的Meterial design站点: http://material ...
- Material使用11 核心模块和共享模块、 如何使用@angular/material
1 创建项目 1.1 版本说明 1.2 创建模块 1.2.1 核心模块 该模块只加载一次,主要存放一些核心的组件及服务 ng g m core 1.2.1.1 创建一些核心组件 页眉组件:header ...
- Siki_Unity_2-9_C#高级教程(未完)
Unity 2-9 C#高级教程 任务1:字符串和正则表达式任务1-1&1-2:字符串类string System.String类(string为别名) 注:string创建的字符串是不可变的 ...
- Angular Material Starter App
介绍 Material Design反映了Google基于Android 5.0 Lollipop操作系统的原生应用UI开发理念,而AngularJS还发起了一个Angular Material ...
- 关于 Angular引用Material出现node_modules/@angular/material/button-toggle/typings/button-toggle.d.ts(154,104): error TS2315: Type 'ElementRef' is not generic.问题
百度了好久 ,,,最后谷歌出来了.. 该错误可能来自于您将@ angular / material设置为6.0.0, 但所有其他Angular包都是5.x.您应该始终确保Material主要版本与An ...
- Angular Material & Hello World
前言 Angular Material(下称Material)的组件样式至少是可以满足一般的个人开发需求(我真是毫无设计天赋),也是Angular官方推荐的组件.我们通过用这个UI库来快速实现自己的i ...
- angular使用@angular/material 出现"export 'ɵɵinject' was not found in '@angular/core'
WARNING in ./node_modules/@angular/cdk/esm5/a11y.es5.js 2324:206-214 "export 'ɵɵinject' was not ...
随机推荐
- 国内版Unity 6 Preview编辑器无法切换到DX12的解决方案(6000.0.5f1c1已解决)
先放解决方案的链接:https://www.cnblogs.com/horeaper/p/18200364 6000.0.0f1c1问题依旧,仍然是没有D3D12文件夹: 不仅新文件不加,旧文件(ha ...
- oeasy教您玩转python - 012 - # 刷新时间
刷新时间 回忆上次内容 通过搜索 我们学会 import 导入 time 了 time 是一个 module import 他可以做和时间相关的事情 time.time() 得到当前时间戳 tim ...
- Excel VBA编程常用语句300句
定制模块行为 1. Option Explicit '强制对模块内所有变量进行声明 Option Private Module '标记模块为私有,仅对同一工程中其它模块有用,在宏对话框中不显示 Opt ...
- 解决IE11兼容问题的一些心得
IE11中,都不支持es6新特性. vue想要兼容IE11不要写箭头函数, UI框架不要写有箭头的回调函数(编译成es5的时候,会通不过), 使用label将es6编译成es5. 使用typescri ...
- Django 解决跨域访问API失败问题
解决跨域访问API失败问题 By:授客 QQ:103355122 实践环境 Win 10 Python 3.5.4 Django-2.0.13.tar.gz 官方下载地址: https://w ...
- 洛谷[NOIP2015 普及组] 金币
[NOIP2015 普及组] 金币 题目背景 NOIP2015 普及组 T1 题目描述 国王将金币作为工资,发放给忠诚的骑士.第一天,骑士收到一枚金币:之后两天(第二天和第三天),每天收到两枚金币:之 ...
- 在英特尔 Gaudi 2 上加速蛋白质语言模型 ProtST
引言 蛋白质语言模型 (Protein Language Models, PLM) 已成为蛋白质结构与功能预测及设计的有力工具.在 2023 年国际机器学习会议 (ICML) 上,MILA 和英特尔实 ...
- RHCA rh442 004 加载模块 ulimit cgroup
模块调优 lsmod 可以看到内核加载的模块 [root@servera ~]# lsmod | grep usb [root@servera ~]# modprobe usb_storage [ro ...
- UDP协议测试
UDP协议测试 我们一般想到测试连通性时第一考虑到的就是使用ping命令. 但是我们知道ping命令使用的是icmp协议,属于tcp/ip协议中的一个子协议,所以我们可以用ping或tcping命令来 ...
- 【C】Re11 剩下的笔记
关于字符常量问题: #include <stdio.h> #include <stdlib.h> #include <string.h> void string01 ...