diff --git a/packages/vantui/src/button/demo/demo1.tsx b/packages/vantui/src/button/demo/demo1.tsx
index 8349d3c11..7f5118228 100644
--- a/packages/vantui/src/button/demo/demo1.tsx
+++ b/packages/vantui/src/button/demo/demo1.tsx
@@ -3,6 +3,11 @@ import { View } from '@tarojs/components'
import { Button } from '@antmjs/vantui'
export default function Demo() {
+ // 点击事件返回Promise,即可让按钮自带loading状态
+ const handle = async () => {
+ await timeout()
+ }
+
return (
@@ -10,6 +15,21 @@ export default function Demo() {
+
+
+
)
}
+
+function timeout() {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(1)
+ }, 3000)
+ })
+}
diff --git a/packages/vantui/src/button/index.tsx b/packages/vantui/src/button/index.tsx
index 2a51095d4..9b9d52dcd 100644
--- a/packages/vantui/src/button/index.tsx
+++ b/packages/vantui/src/button/index.tsx
@@ -1,11 +1,15 @@
import type { ButtonProps } from '../../types/button.d'
+import { useState, useEffect, useCallback } from 'react'
import { pxTransform } from '@tarojs/taro'
import { Button as TaroButton, View } from '@tarojs/components'
import * as utils from '../wxs/utils'
import { Icon } from '../icon/index'
import { Loading } from '../loading/index'
+import { Toast } from '../toast/index'
import * as computed from './wxs'
+let index = 0
+
export function Button(props: ButtonProps) {
const {
type = 'default',
@@ -20,16 +24,56 @@ export function Button(props: ButtonProps) {
color,
loadingSize = pxTransform(40),
loadingType = 'circular',
- loadingText,
+ loadingText = '加载中...',
icon,
classPrefix = 'van-icon',
onClick,
children,
style,
className,
+ loadingMode = 'normal',
...others
} = props
+ const [innerLoading, setInnerLoading] = useState(false)
+ const [compIndex] = useState(++index)
+
+ const toastId = `van-button-toast_${compIndex}`
+
+ useEffect(() => {
+ setInnerLoading(loading)
+ }, [loading])
+
+ useEffect(() => {
+ if (innerLoading && loadingMode === 'toast') {
+ Toast.loading({
+ selector: `#${toastId}`,
+ duration: 60 * 60,
+ message: loadingText,
+ loadingType: loadingType,
+ })
+ } else {
+ Toast.clear()
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [innerLoading])
+
+ const _click = useCallback(
+ (e) => {
+ if (onClick && !loading) {
+ const res = onClick(e)
+ // @ts-ignore
+ if (res?.then && res?.catch) {
+ setInnerLoading(true)
+ res.finally(() => {
+ setInnerLoading(false)
+ })
+ }
+ }
+ },
+ [loading, onClick],
+ )
+
return (
+
- {loading ? (
+ {innerLoading && loadingMode === 'normal' ? (
= {}
+const timers: Record> = {}
+
+export function createDynamicNode>({
+ element,
+ delay = 60,
+ idPrefix = 'common', // 'dialog'、'toast'、'notify'
+}: {
+ element: (props: T) => JSX.Element | ReactNode
+ delay?: number
+ idPrefix: string
+}) {
+ if (!idPrefix) return console.error('createDynamicNode: idPrefix is unset')
+ if (!renderNodes[idPrefix]) {
+ renderNodes[idPrefix] = []
+ }
+
+ if (!timers[idPrefix]) {
+ timers[idPrefix] = {}
+ }
+
+ const Com = element
+ const view = document.createElement('View')
+ id += 1
+ view.id = `${idPrefix || ''}_element_${id}`
+ const currentPages = Taro.getCurrentPages()
+ const currentPage = currentPages[currentPages.length - 1] // 获取当前页面对象
+ const path = currentPage.$taroPath
+
+ const clearTimer = () => {
+ const t = timers[idPrefix][view.id]
+ if (t) {
+ clearTimeout(t)
+ delete timers[idPrefix][view.id]
+ }
+ }
+ const destroy = () => {
+ for (let i = 0; i < renderNodes[idPrefix]?.length; i++) {
+ if (renderNodes[idPrefix] === view) {
+ renderNodes[idPrefix]?.splice(i, 1)
+ break
+ }
+ }
+ clearTimer()
+ try {
+ unmountComponentAtNode(view)
+ const pageElement = document.getElementById(path)
+ pageElement?.removeChild(view)
+ } catch (error) {}
+ }
+
+ const clear = () => {
+ for (const key in timers) {
+ if (key === idPrefix) {
+ const m = timers[key]
+
+ for (const k in m) {
+ const t = m[k]
+ clearTimeout(t)
+ }
+ }
+ }
+
+ renderNodes[idPrefix]?.map((view) => {
+ try {
+ unmountComponentAtNode(view)
+ const pageElement = document.getElementById(path)
+ pageElement?.removeChild(view)
+ } catch (error) {}
+ })
+
+ delete renderNodes[idPrefix]
+ }
+
+ return {
+ show: (props: T) => {
+ if (delay) {
+ clearTimer()
+ timers[idPrefix][view.id] = setTimeout(() => {
+ destroy()
+ }, delay)
+ }
+ const isRenderFlag = renderNodes[idPrefix]?.filter((item) => {
+ return item === view
+ })?.length
+ if (!!isRenderFlag) return
+ render(, view)
+ const pageElement = document.getElementById(path)
+ pageElement?.appendChild(view)
+ renderNodes[idPrefix].push(view)
+ },
+ hide: destroy,
+ clear: clear,
+ }
+}
diff --git a/packages/vantui/types/button.d.ts b/packages/vantui/types/button.d.ts
index 573b48fb5..7e6904194 100644
--- a/packages/vantui/types/button.d.ts
+++ b/packages/vantui/types/button.d.ts
@@ -1,5 +1,5 @@
import { FunctionComponent, ReactNode } from 'react'
-import { ButtonProps as TaroButtonProps } from '@tarojs/components'
+import { ButtonProps as TaroButtonProps, ITouchEvent } from '@tarojs/components'
export interface ButtonProps extends Omit {
/**
@@ -15,11 +15,21 @@ export interface ButtonProps extends Omit {
loading?: boolean
hairline?: boolean
disabled?: boolean
+ /**
+ * @description loading的文案
+ * @default 加载中
+ */
loadingText?: ReactNode
loadingSize?: string
loadingType?: 'spinner' | 'circular'
+ /**
+ * @description loading的模式,按钮内loading 或 Toast.loading
+ * @default normal
+ */
+ loadingMode?: 'normal' | 'toast'
color?: string
children?: ReactNode
+ onClick?: (event: ITouchEvent) => void | Promise
}
declare const Button: FunctionComponent