feat:action 支持设置提交后倒计时 (#1607)

This commit is contained in:
吴多益 2021-03-02 21:40:29 +08:00 committed by GitHub
parent 696c3e4dc4
commit f25fbbf41e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 124 additions and 5 deletions

View File

@ -319,6 +319,34 @@ icon 也可以是 url 地址,比如
| feedback | `DialogObject` | - | 如果 ajax 类型的,当 ajax 返回正常后,还能接着弹出一个 dialog 做其他交互。返回的数据可用于这个 dialog 中。格式可参考[Dialog](./Dialog.md) | | feedback | `DialogObject` | - | 如果 ajax 类型的,当 ajax 返回正常后,还能接着弹出一个 dialog 做其他交互。返回的数据可用于这个 dialog 中。格式可参考[Dialog](./Dialog.md) |
| messages | `object` | - | `success`ajax 操作成功后提示,可以不指定,不指定时以 api 返回为准。`failed`ajax 操作失败提示。 | | messages | `object` | - | `success`ajax 操作成功后提示,可以不指定,不指定时以 api 返回为准。`failed`ajax 操作失败提示。 |
### 倒计时
主要用于发验证码的场景,通过设置倒计时 `countDown`(单位是秒),让点击按钮后禁用一段时间:
```schema: scope="body"
{
"type": "form",
"controls": [
{
"name": "phone",
"type": "text",
"required": true,
"label": "手机号",
"addOn": {
"label": "发送验证码",
"type": "button",
"countDown": 60,
"countDownTpl": "${timeLeft} 秒后重发",
"actionType": "ajax",
"api": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mock2/form/saveForm?phone=${phone}"
}
}
]
}
```
同时还能通过 `countDownTpl` 来控制显示的文本,其中 `${timeLeft}` 变量是剩余时间。
## 跳转链接 ## 跳转链接
### 单页跳转 ### 单页跳转

View File

@ -1,6 +1,7 @@
import {register} from '../locale'; import {register} from '../locale';
register('en-US', { register('en-US', {
'Action.countDown': 'Wait for ${timeLeft}s',
'Alert.info': 'System Info', 'Alert.info': 'System Info',
'asc': 'Asc', 'asc': 'Asc',
'cancel': 'Cancel', 'cancel': 'Cancel',

View File

@ -1,6 +1,7 @@
import {register} from '../locale'; import {register} from '../locale';
register('zh-CN', { register('zh-CN', {
'Action.countDown': '请等待 ${timeLeft} 秒',
'Alert.info': '系统消息', 'Alert.info': '系统消息',
'asc': '正序', 'asc': '正序',
'cancel': '取消', 'cancel': '取消',

View File

@ -99,6 +99,16 @@ export interface ButtonSchema extends BaseSchema {
* *
*/ */
target?: string; target?: string;
/**
*
*/
countDown?: number;
/**
*
*/
countDownTpl?: string;
} }
export interface AjaxActionSchema extends ButtonSchema { export interface AjaxActionSchema extends ButtonSchema {
@ -324,19 +334,53 @@ export interface ActionProps
const allowedType = ['button', 'submit', 'reset']; const allowedType = ['button', 'submit', 'reset'];
export class Action extends React.Component<ActionProps> { interface ActionState {
inCountDown: boolean; // 是否在倒计时
countDownEnd: number; // 倒计时结束的精确时间
timeLeft: number; // 倒计时剩余时间
}
export class Action extends React.Component<ActionProps, ActionState> {
static defaultProps = { static defaultProps = {
type: 'button' as 'button', type: 'button' as 'button',
componentClass: 'button' as React.ReactType, componentClass: 'button' as React.ReactType,
tooltipPlacement: 'bottom' as 'bottom', tooltipPlacement: 'bottom' as 'bottom',
activeClassName: 'is-active' activeClassName: 'is-active',
countDownTpl: 'Action.countDown',
countDown: 0
}; };
state: ActionState = {
inCountDown: false,
countDownEnd: 0,
timeLeft: 0
};
localStorageKey: string;
dom: any; dom: any;
constructor(props: ActionProps) {
super(props);
this.localStorageKey = 'amis-countdownend-' + (this.props.name || '');
const countDownEnd = parseInt(
localStorage.getItem(this.localStorageKey) || '0'
);
if (countDownEnd && this.props.countDown) {
if (Date.now() < countDownEnd) {
this.state = {
inCountDown: true,
countDownEnd,
timeLeft: Math.floor((countDownEnd - Date.now()) / 1000)
};
this.handleCountDown();
}
}
}
@autobind @autobind
handleAction(e: React.MouseEvent<any>) { handleAction(e: React.MouseEvent<any>) {
const {onAction, onClick, disabled} = this.props; const {onAction, onClick, disabled, countDown} = this.props;
const result: any = onClick && onClick(e, this.props); const result: any = onClick && onClick(e, this.props);
@ -347,18 +391,51 @@ export class Action extends React.Component<ActionProps> {
e.preventDefault(); e.preventDefault();
const action = pick(this.props, ActionProps) as ActionSchema; const action = pick(this.props, ActionProps) as ActionSchema;
onAction(e, action); onAction(e, action);
if (countDown) {
const countDownEnd = Date.now() + countDown * 1000;
this.setState({
countDownEnd: countDownEnd,
inCountDown: true,
timeLeft: countDown
});
localStorage.setItem(this.localStorageKey, String(countDownEnd));
setTimeout(() => {
this.handleCountDown();
}, 1000);
}
}
@autobind
handleCountDown() {
// setTimeout 一般会晚于 1s经过几十次后就不准了所以使用真实时间进行 diff
const timeLeft = Math.floor((this.state.countDownEnd - Date.now()) / 1000);
if (timeLeft <= 0) {
this.setState({
inCountDown: false,
timeLeft: timeLeft
});
} else {
this.setState({
timeLeft: timeLeft
});
setTimeout(() => {
this.handleCountDown();
}, 1000);
}
} }
render() { render() {
const { const {
type, type,
label,
icon, icon,
iconClassName, iconClassName,
primary, primary,
size, size,
level, level,
disabled, countDownTpl,
block, block,
className, className,
componentClass, componentClass,
@ -368,6 +445,7 @@ export class Action extends React.Component<ActionProps> {
actionType, actionType,
link, link,
data, data,
translate: __,
activeClassName, activeClassName,
isCurrentUrl, isCurrentUrl,
isMenuItem, isMenuItem,
@ -377,12 +455,23 @@ export class Action extends React.Component<ActionProps> {
classnames: cx classnames: cx
} = this.props; } = this.props;
let label = this.props.label;
let disabled = this.props.disabled;
let isActive = !!active; let isActive = !!active;
if (actionType === 'link' && !isActive && link && isCurrentUrl) { if (actionType === 'link' && !isActive && link && isCurrentUrl) {
isActive = isCurrentUrl(link); isActive = isCurrentUrl(link);
} }
// 倒计时
if (this.state.inCountDown) {
label = filterContents(__(countDownTpl), {
...data,
timeLeft: this.state.timeLeft
}) as string;
disabled = true;
}
const iconElement = generateIcon(cx, icon, 'Button-icon', iconClassName); const iconElement = generateIcon(cx, icon, 'Button-icon', iconClassName);
return isMenuItem ? ( return isMenuItem ? (