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