我正在提交一个错误/功能请求(用“x”检查一个)
[x] bug report => search github for a similar issue or PR before submitting
[x] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
当前行为
看看那个,(演示:http://plnkr.co/edit/P25dYPC5ChRxpyxpL0Lj?p=preview):
@Component({
selector: 'my-app',
providers: [],
template: `
<div [formGroup]="form">
<input formControlName="first" [disabled]="isDisabled">
</div>
`,
directives: []
})
export class App {
isDisabled = true
form = new FormGroup({
'first': new FormControl('Hello')
})
}
有一条警告信息要求重构代码,但更重要的是,不幸的是,输入没有被禁用。 将代码重构为建议的内容也无济于事,即这样做将不起作用,(演示:http://plnkr.co/edit/Gf7FGR42UXkBh6e75cm2?p=preview):
@Component({
selector: 'my-app',
providers: [],
template: `
<div [formGroup]="form">
<input formControlName="first">
</div>
`,
directives: []
})
export class App {
isDisabled = false
form = new FormGroup({
'first': new FormControl({value: 'hello', disabled: this.isDisabled})
})
constructor() {
setTimeout(() {
this.isDisabled = true
}, 10)
}
}
预期/期望行为
setTimeout 的回调执行后,输入应该被禁用,但事实并非如此。
问题的重现
是的,请看这里: http :
改变行为的动机/用例是什么?
具有与 RC5 相同或相似的行为。 至少,即使是重大更改,我们也有可能动态设置disabled
属性。 就目前而言,这似乎不再可能。
请告诉我们您的环境:
@Krisa如果要以编程方式设置禁用状态,则需要调用disable()
。 更改this.isDisabled
永远不会有效果,因为它是按值传递的。
export class App {
isDisabled = false
form = new FormGroup({
'first': new FormControl({value: 'hello', disabled: false})
})
constructor() {
setTimeout(() => {
this.form.get('first').disable();
}, 10)
}
}
请参阅此处的示例: http :
关于第一个例子,我们故意没有将模板驱动的入口点连接到响应式表单。 这就是我们抛出警告的原因。 如果我们确实连接了它,用户会立即遇到“检查后更改”错误,因为响应式表单的验证是同步发生的。 出于这个原因,我们通常不建议混合反应式和模板驱动的模式。
我们考虑过抛出错误,但我们担心如果有人有一个自定义表单组件,使用disabled
作为不相关内容的输入,他们的代码会崩溃。 也许我们可以让警告信息更清楚一些。 如果您认为可以更改语言,请告诉我。 在任何情况下,关闭它,因为它按设计工作。
@kara - 我知道disable
方法,但它不一样,至少在我的情况下不是。
让我举一个比我们上面看到的更复杂的例子。 我有一个由大约 70 到 80 个字段组成的表单,这些字段分布在多个组件中。 表单的某些部分被禁用/启用、显示/隐藏基于记录的某些属性(加载时或保存时),或取决于查看此记录的用户,甚至是从哪里调用记录(即不同的路线)。 我正在使用disabled
和ngIf
属性/指令(到目前为止, required
在我的情况下始终是静态的),但所有这些都是由我定义的,我称之为规则,在属性/指令级别。 这种做法对我来说还是比较一致的,ngIf 之类的disabled 都在html 模板中。
现在,如果我必须根据您的建议重构此代码,我可能需要在我的班级中使用一个中心函数,并根据每个字段的值调用disable()
或enable()
表单和来自外部属性(用户配置文件、路由参数等)。 此函数应订阅valueChanges
,并且在加载组件或调用save
方法时也会调用。 绝对可行,但感觉非常尴尬,至少与当前的解决方案相比,你不觉得吗?
无论哪种方式,我都将不得不接受已实施的内容。 到目前为止,我已经将表单模块降级到 0.3.0,希望 PR 解决上述情况,但根据您的评论,我担心这个规范不会改变,是吗? 您能否(重新)确认其中一个?
谢谢
克里斯
如果我调用this.myForm.controls["code"].disable();
, this.myForm.valid
总是false
:
this.myForm.patchValue({code: 17});
console.log(this.myForm.valid);
this.myForm.controls["code"].disable();
console.log(this.myForm.valid);
这输出
false
为什么会这样? 我希望控件被禁用,但 from 本身仍然有效。
@capi在我看来似乎是一个错误,但它也与我报告的原始问题无关,即动态设置disabled
属性的能力。
请打开一个新问题,以免混淆
@capi我和你有同样的行为。 对我来说这是错误的......
@maku我为此提交了 #11379,我们不应该将这两个问题混在一起,
@udos86 @Krisa我们遇到了同样的行为,解决起来看起来很麻烦:一方面,我们可以通过订阅FormControl
valueChanges
来编写动态启用/禁用行为,但是每次都需要大量代码(并且我们有包含许多字段的大型表单)。
另一方面,我正在考虑一个自定义指令来替换disable
,采用FormControl
和Observable
,以便在模板中保持反应式方法,但我需要试验它。
有任何更新吗? 我的意思是,现在您有了一个视图框架,让您可以使用声明性的单向绑定代码来加载属性和整个内容项,但是对于 _disabled_ 属性,您就只能使用命令式代码了吗?
@kara @pkozlowski-opensource
不管是积极的还是消极的,如果能得到关于即将到来的内容的反馈,即使非常快,也不会让我们蒙在鼓里,可能会不必要地重构我们的代码,这将是很棒的。
谢谢
@Krisa ,我建议避免反应形式。 如果想要与模板的其他部分交互并且提供非常小的好处,响应式表单需要大量编码。 您应该能够创建可配置的自定义验证器(甚至手动验证您的数据模型)并使用它代替required
,您也可以使用一组表单而不是将所有内容组合成一个大表单。
@Krisa在我们的项目中,我们通过使用两个自定义实用程序方法快速解决了这个问题:
import { FormGroup, FormControl, AbstractControl } from "@angular/forms";
export function includeInForm(form: FormGroup, controlName: string, includeChildren?: boolean) {
const ctl = form.get(controlName);
makeOptional(ctl, false, includeChildren);
}
export function excludeFromForm(form: FormGroup, controlName: string, excludeChildren?: boolean) {
const ctl = form.get(controlName);
makeOptional(ctl, true, excludeChildren);
}
function makeOptional(ctl: AbstractControl, isOptional: boolean, children?: boolean) {
if (isOptional) {
(<any>ctl).__validator = ctl.validator || (<any>ctl).__validator;
ctl.clearValidators();
} else {
ctl.setValidators(ctl.validator || (<any>ctl).__validator);
}
if (children) {
Object.keys((<FormGroup>ctl).controls).forEach((control) => {
makeOptional(ctl.get(control), isOptional);
});
}
ctl.updateValueAndValidity();
}
它可能不是最优雅的 - 当然 - 但它可以工作并且使代码以最少的更改再次工作。
我也在寻找一种更简单、更简洁的解决方案来禁用表单变量!
为什么这是关闭的? 仍然没有明确的方法来声明性地禁用表单控件,也没有解决方法。
似乎 angular 团队不想就这个问题进行交流……他们仍在讨论替代方案
好的,我能想到的最好的解决方法是创建一个指令来完成肮脏的工作,并“声明性地”测试布尔条件并禁用表单/输入/FormControl,而无需测试有时不是可观察的表达式很容易知道什么时候发生了变化(即:依赖于变化检测系统)。
您甚至可以使用它来禁用其他 FormControl,或者使用另一个 FormControl.value 作为禁用自身的条件。
@Directive({
selector: '[disableFC][disableCond]'
})
export class DisableFCDirective {
@Input() disableFC: FormControl;
constructor() { }
get disableCond(): boolean { // getter, not needed, but here only to completude
return !!this.disableFC && this.disableFC.disabled;
}
@Input('disableCond') set disableCond(s: boolean) {
if (!this.disableFC) return;
else if (s) this.disableFC.disable();
else this.disableFC.enable();
}
}
它可以像这样使用:
<input type="text" [formControl]="textFC" [disableFC]="textFC" [disableCond]="anotherElement.value < 10" />
@andrevmatos
您好 - 一直在努力解决这个问题,并尝试应用您建议的修复程序但没有成功。 你介意看看这个 Plunk并澄清它哪里出了问题吗?
@raisindetre
之前需要在selects上设置formControlName才能使用formGroup里面的控件,或者formControl直接设置控件。 之后,您可以使用我的指令来启用或禁用控件。 我更改了您的示例以演示我的指令基于布尔变量(由按钮切换)禁用第一个选择,第二个基于第一个的值禁用它。
啊,明白了 - 谢谢! 我几乎在那里,但我将 formControlName 指令包装在方括号中。 仍在尝试掌握模型/反应驱动形式的窍门。
@andrevmatos
我将您的指令更新为我认为更简洁的方法,共享从已经是 formConrol 的 html 元素传递的 formControl 输入。
@Directive({
selector: '[formControl][disableCond]'
})
export class DisableFormControlDirective {
@Input() formControl: FormControl;
constructor() { }
get disableCond(): boolean { // getter, not needed, but here only to completude
return !!this.formControl && this.formControl.disabled;
}
@Input('disableCond') set disableCond(s: boolean) {
if (!this.formControl) return;
else if (s) this.formControl.disable();
else this.formControl.enable();
}
}
这允许我们从模板中删除重复项
<input type="text" [formControl]="textFC" [disableCond]="anotherElement.value < 10" />
@kara
如果 Angular 团队能够更新响应式表单组件以正确跟踪 formControl 禁用属性,我会非常感激。
@ PTC-JoshuaMatthews我们还发现了版本的@andrevmatos这个GH问题评论通过@unsafecode是相当有趣的。
我一直面临着同样的问题 - 没有一种干净的方法来禁用反应式/模型驱动形式的一部分。 用例很常见:考虑一组单选按钮,每个单选按钮在被选中时都显示一个子表单,并且需要禁用每个未选中的子表单以使整个表单有效。
我真的很喜欢 @PTC-JoshuaMatthews 的DisableFormControlDirective
,但后来我意识到它违背了使用反应形式的目的......换句话说,禁用成为模板驱动的(要测试它,你需要解析模板)。 据我所知,保持模型驱动/反应性的最佳方法是在控件上订阅valueChanges
,然后在相应的子表单上调用disable
(非常冗长/丑陋) )。 我真的很惊讶没有更好的方法来实现这一目标。
@pmulac虽然@PTC-JoshuaMatthews 指令绝对有用,但我只想指出,在我们的项目中,最终对于许多必须禁用的单个控件(例如输入、复选框),我们切换到[readonly]
上的绑定禁用该输入。 如果以某种方式涉及验证,例如在某些条件下必须忽略只读字段,我们进行了考虑条件的自定义验证。
另一方面,如果是禁用整个表单的问题,在我看来[disabled]
上的绑定仍然可以正常工作,没有警告。
只是我们的 2 美分
@BrainCrumbz
使用 [readonly] 或 [disabled] 执行此操作的一个问题是实际的表单控件对象不会处于禁用状态,这将影响表单的功能。 例如,您附加到表单控件的任何验证仍将运行,因此如果您有一个禁用的必填字段,该表单将被视为无效。 使用我发布的指令将正确禁用表单控件并跳过对禁用字段的验证。
编辑
我想出了如何在不使用指令的情况下做到这一点,请参阅下面的 SO 链接。
我刚刚在控制台中看到了这个警告,所以我试图按照来自@pmulac的评论来解决这个 RxJS 方式 - 订阅 ValueChanges。
就我而言,我需要启用在 init 上禁用的控件。
所以这里的问题是否有人可以阐明如何解决这个问题,谢谢。
@rmcsharry有关替代解决方案,请参阅 #13570
@noamichael感谢您的参考。
@kemsky (kemsky 于 2016 年 9 月 22 日发表评论),
这实际上似乎是最好的方法,IMO。 我第一次意识到微软参与了 Angular 2 项目,当我看到他们使用 ReactiveForms 时看到有多少Boilerplate被实现到项目中。 在某些方面,我对 Microsoft 对 Angular2 的影响感到兴奋,但这是我将拒绝 MS-Boilerplating 的一个领域——假设他们参与其中。
做事有正确的方法,也有正确的方法。 如果你在没有太多好处的情况下限制了工程师的生产力——更不用说在系统中添加更多的耦合——那么这不是“正确的方法”。 如果input
没有包含在form
我们为什么不这样做,让您的应用程序崩溃?
为什么是 ReactiveForms???
你真正得到了什么好处? 似乎验证(等)最好使用访问者模式、装饰器、中介器、服务或其他东西来完成。
也就是说——在我把丑陋的婴儿和洗澡水一起扔掉之前——你能给我们一些关于你在“无反应形式样板”潮流中遇到的任何警告的见解吗?
非常感激!
您可以使用指令解决它。 例如,一个名为 Canbedisabled 的指令和一个 poperty “blocked”。 为blocked 编写一个setter 并将其设置为nativelement.disabled 属性。
代码示例:
@Directive({
selector : ["canbedisabled"]
})
export class Canbedisabled{
constructor(private el: ElementRef) {
}
@Input()
set blocked(blocked : boolean){
this.el.nativeElement.disabled = blocked;
}
}
<input formControlName="first" canbedisabled [blocked]="isDisabled">
我一直在通过绑定到 ngDoCheck Lifecycle 钩子来处理这种情况。
ngDoCheck() { if (this.childControl.disabled != this.isDisabled) { this.isDisabled ? this.childControl.disable() : this.childControl.enable(); } }
不知道为什么,但是当我连续两次调用 .enable() 或 .disable() 时,它起作用了!
form.controls['partPerson'].enable(); form.controls['partPerson'].enable();
这种限制性方法削弱了使用 Angular Reactive 表单的优势,因为禁用和启用元素几乎是一项基本功能,尤其是在构建动态表单时。
因此,如果您需要构建一个复杂的动态表单,并且需要对该表单进行完全控制,那么现在最好使用模板驱动的方法,尽管这可能意味着更多的工作,例如验证,但这种方法在未来会变得更糟然后最终得到一个充满变通方法的复杂代码来解决诸如启用和禁用表单元素之类的基本问题。
顺便说一句,没有什么说你不能在同一个应用程序中同时使用两种方法,反应式和模板驱动,只需注意每种方法的优点和局限性。
呃......也许我在这里有点偏离,但我能够使用 [attr.disabled]="isDisabled" 来获得禁用功能
这有效:
<input [attr.disabled]="disabled?'':null"/>
private disabled: boolean = false;
// disable input box
disableInput(): void {
this.disabled = true;
}
// enable input box
enableInput(): void {
this.disabled = false;
}
@kekeh谢谢,它按预期工作
@Highspeed7那是因为您使用的是属性绑定而不是属性绑定。 我确定这在某种程度上无效是有原因的,但它可以满足我的需求,感谢您指出!
@kekeh它有效
@kekeh谢谢! 它就像一个魅力:)
对我不起作用
您可以使用form.getRawValue()
如果 FormControl 构造函数接受第一个参数的 disabled 属性的函数,则可以通过“反应式”方式解决该问题。 目前它只接受一个布尔值。
例如
form = new FormGroup({
'first': new FormControl(),
'second': new FormControl({value: '', disabled: formValue => formValue.first === 'something'})
})
Disabled,应该是允许接受功能的。 禁用状态经常会发生变化,并且仅仅为了检查条件而使用辅助方法和观察者是一团糟。
非常感谢在我们等待更改时提供的当前“hack-y”解决方案
@medeirosrich :你是好伙伴!
最佳解决方案是您的!
奇迹般有效:
ngDoCheck() {
if (this.childControl.disabled != this.isDisabled) {
this.isDisabled ? this.childControl.disable() : this.childControl.enable();
}
}
同意许多其他人...禁用需要动态控制,否则它是相当无用的
@kara为什么关闭?
@billfranklin正确的方法是绑定到 attr.disabled。 检查kekeh发布的解决方案。
@kekeh的解决方案很棒,但它不适用于自定义表单控件。 我正在尝试有条件地禁用angular2-json-form-schema 中的表单字段。 我计划按照@PTC-JoshuaMatthews 建议的方式使用指令来实现。 有没有更好的方法来实现相同的目标? 我知道我可以在自定义控件中实现 ngDoCheck ,但这不是一个有效的解决方案。
有这方面的消息吗?
即使没有给出干净的解决方案/方法,也不知道为什么这会被关闭......
@mkotsollaris您是否以最多的拇指阅读了回复?
我最终在我的应用程序中完全放弃了这种方法。 我没有在我的组件上设置 isDisabled 属性并期望表单跟踪该属性更改,而是在 ngAfterViewInit 挂钩中调用 this.form.controls[“mycontrol”].disable() 。 如果状态发生变化,我会明确调用 enable() 来响应它。
这种方法工作正常,因为我已经在响应某个事件以启用/禁用控件,因此直接通过表单 api 执行此操作没有问题,而不是尝试将禁用状态绑定到属性。 我怀疑我绑定它的愿望来自我的角度 1 天。
使用以下代码禁用表单的好/正确方法是什么???
<input [attr.disabled]="disabled?'':null"/>
我已经尝试过 angular 提供的建议,
但它仍然发出警告,并且该字段没有被禁用。
在组件中
form = new FormGroup({
firstName: new FormControl({value: 'Vishal', disabled: true}, Validators.required),
lastName: new FormControl('Drew', Validators.required)
});
然后,在 html
<input type="text" formControlName="firstName"
[value]="form.controls['firstName'].value"
[disabled]="form.controls['firstName'].disabled">
@Highspeed7 “部分”工作,因为元素的disabled
属性值绑定到预期值,但由于某些神秘的 HTML 标准原因, disabled
属性只需要存在即可禁用元素,即使disabled="false"
。
@kekeh通过绑定三元isDisabled ? '' : null
而不是仅仅isDisabled
添加到此解决方案中。 这基本上是一种让 Angular 在重新启用表单时完全清除属性的技巧。
所以在模板上,这将是解决方法:
[attr.disabled]="isDisabled ? '' : null"
设置[attr.disabled]
与[disabled]
的含义是什么?
@sgronblo afaik 没有。 我的印象是[disabled]
只是[attr.disabled]
简写,但我可能是错的。
这有效:
<input [attr.disabled]="disabled?'':null"/>
private disabled: boolean = false; // disable input box disableInput(): void { this.disabled = true; } // enable input box enableInput(): void { this.disabled = false; }
它甚至对我有用 this.disabled = false; 等等,我需要的地方。 这些功能不是必需的。
this.myForm.controls['id'].disable();
对于这种情况,是否有更合适的解决方案,而不是绑定到 disabled 属性(在重新启用表单后保留)?
@danzrou你上面的答案对你不起作用吗? 只需禁用输入绑定的表单控件,输入将被自动处理。
@danzrou是的,fwiw,afaik 这是启用/禁用表单控件的 _the_ 事实方法:
someFormGroup.controls['controlName'].disable()
和someFormGroup.controls['controlName'].enable()
请记住,反应式表单不是标准表单集。 它们_打算_以编程方式使用,而不是通过 dom,这就是为什么(我怀疑)它们不再允许绑定。
我目前的情况是我将表单控件传递给子组件,并且控件根据用户选择动态变化。 如果我传递一个禁用=真的控件,则输入元素确实被禁用。 然后我传递了一个具有 disabled=false 的新控件(并且我确保它没有被禁用),但 disabled 属性仍然保留在输入字段中。 这仍然发生在 Angular 7.2.3
@danzrou你能详细说明你是如何将表单控件传递给子组件的吗? 我的猜测是子组件没有看到新的表单控件。
@PTC-JoshuaMatthews 它使用标准输入传递它。 更多细节:
对于这种情况,是否有更合适的解决方案,而不是绑定到 disabled 属性(在重新启用表单后保留)?
this.myForm.controls['id'].enable();
@danzrou我可以
如果您确定必须交换表单控件,我建议使用输入设置器来检测表单控件何时更改并在发生这种情况时重新初始化输入。
https://stackoverflow.com/questions/36653678/angular2-input-to-a-property-with-get-set
至于如何重新初始化它,一个肮脏的解决方案是用 ngIf 隐藏它,然后直接在超时后显示它。 更简洁的解决方案可能只是调用 formControl.setDisabled(formControl.disabled)。 只要您在表单控件绑定到输入时执行此操作。 可能必须在超时时发挥创意
请在角度配置中提供一个标志以禁用此警告
@sgentile我认为警告的全部内容是您使用的是反应式表单,因此不应该使用模板驱动表单语法来控制表单状态。 当开发人员看到响应式表单,尝试以响应式表单编程,并且不知不觉地与模板驱动的表单约定作斗争时,这可能会导致很多混乱,因为它是在冲突中实现的。 解决方案是在代码中设置/取消设置禁用状态,就像您正在处理其余的反应式表单状态 ( myForm.controls['someControl'].disable()
)
这里有很多假设。
例如,我正在使用 Primeng,我需要将 disabled 传递给他们的控件
这会导致控制台警告。
我认为向开发人员显示控制台警告很好,但是他们
应该能够决定他们是否想要它们。 在里面做一个设置
angular.json 文件
imo 的扩展已经结束。
2019 年 8 月 31 日星期六晚上 8 点 06 分,Layton Miller通知@ github.com
写道:
@sgentile https://github.com/sgentile我认为整个要点
警告是您正在使用反应形式,因此不应该
使用模板驱动表单语法来控制表单状态。 这可能会导致
当开发人员看到响应式表单时,很多困惑,试图编程
反应式表单,并在不知不觉中与模板驱动的表单作斗争
公约,因为它是在冲突中实施的。—
你收到这个是因为你被提到了。
直接回复本邮件,在GitHub上查看
https://github.com/angular/angular/issues/11271?email_source=notifications&email_token=AABAKNGBGNDK3TLWNHCXVJLQHMBPTA5CNFSM4CON24SKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJK880000000000000000000000000000000000000000000000000000500000000000007同传
或静音线程
https://github.com/notifications/unsubscribe-auth/AABAKNE74ZR3UZBVBEQQXHDQHMBPTANCNFSM4CON24SA
.
此外,当我尝试使用禁用时,它不起作用
2019 年 8 月 31 日星期六晚上 8 点 06 分,Layton Miller通知@ github.com
写道:
@sgentile https://github.com/sgentile我认为整个要点
警告是您正在使用反应形式,因此不应该
使用模板驱动表单语法来控制表单状态。 这可能会导致
当开发人员看到响应式表单时,很多困惑,试图编程
反应式表单,并在不知不觉中与模板驱动的表单作斗争
公约,因为它是在冲突中实施的。—
你收到这个是因为你被提到了。
直接回复本邮件,在GitHub上查看
https://github.com/angular/angular/issues/11271?email_source=notifications&email_token=AABAKNGBGNDK3TLWNHCXVJLQHMBPTA5CNFSM4CON24SKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJK880000000000000000000000000000000000000000000000000000500000000000007同传
或静音线程
https://github.com/notifications/unsubscribe-auth/AABAKNE74ZR3UZBVBEQQXHDQHMBPTANCNFSM4CON24SA
.
@sgentile是的,抱歉 - 没有试图假设,但没有更多信息,您听起来像大多数抱怨功能没有理由的开发人员。 框架和语言开发人员必须做出最通用的选择,而且很多时候会被忽视。 我认为你的情况是一个有效的观点。 理想情况下,您会为此提交功能请求,因为这是一个试图报告错误的已关闭票证。
你激励我看看我是否可以得到一个 ViewChild 引用
控制并看看我是否可以以编程方式设置它
2019 年 8 月 31 日星期六晚上 8 点 18 分 Layton Miller通知@ github.com
写道:
@sgentile https://github.com/sgentile是的,对不起 - 没有试图
假设但没有更多信息,您听起来像大多数抱怨的开发人员
关于一个没有理由的功能。 框架和语言开发人员必须
使最通用的选择成为可能,并且很多时候
被忽视了。 我认为你的情况是一个有效的观点。—
你收到这个是因为你被提到了。
直接回复本邮件,在GitHub上查看
https://github.com/angular/angular/issues/11271?email_source=notifications&email_token=AABAKNEXR32GSL3U5ZS6TZLQHMC5LA5CNFSM4CON24SKYY3PNVWWK3TUL52HS4DFVREXG43VMJVBW63LODNX5K8ZGOZGSL3U5ZS6TZLQHMC5LA5CNFSM4CON24SKYY3PNVWWK3TUL52HS4DFVREXG43VMJVBW63LODNX5K78ZGOZGSL3U5ZS6TZLQHMC5LA5CNFSM4CON24SKYY3PNVWWK3TUL52HS4
或静音线程
https://github.com/notifications/unsubscribe-auth/AABAKND5MXVOV5BYO4R6DS3QHMC5LANCNFSM4CON24SA
.
最有用的评论
这有效: