209 lines
5.3 KiB
TypeScript
209 lines
5.3 KiB
TypeScript
import React, { useRef, useState, useEffect } from 'react'
|
|
import type { CSSProperties, ReactNode } from 'react'
|
|
import { View, Text } from '@tarojs/components'
|
|
import { Button } from '@nutui/nutui-react-taro'
|
|
import { useKeyboardHeight } from '@/store/keyboardStore'
|
|
import styles from './index.module.scss'
|
|
|
|
export interface CustomPopupProps {
|
|
visible: boolean
|
|
onClose: () => void
|
|
title?: ReactNode
|
|
showHeader?: boolean
|
|
hideFooter?: boolean
|
|
cancelText?: string
|
|
confirmText?: string
|
|
onCancel?: () => void
|
|
onConfirm?: () => void
|
|
children?: ReactNode
|
|
className?: string
|
|
style?: CSSProperties
|
|
// 与 CommonPopup 保持入参一致
|
|
position?: 'center' | 'bottom' | 'top' | 'left' | 'right'
|
|
round?: boolean
|
|
zIndex?: number
|
|
enableDragToClose?: boolean
|
|
}
|
|
|
|
const CustomPopup: React.FC<CustomPopupProps> = ({
|
|
visible,
|
|
onClose,
|
|
title,
|
|
showHeader = false,
|
|
hideFooter = false,
|
|
cancelText = '返回',
|
|
confirmText = '完成',
|
|
onCancel,
|
|
onConfirm,
|
|
children,
|
|
className,
|
|
style,
|
|
position = 'bottom',
|
|
round = true,
|
|
zIndex,
|
|
enableDragToClose = true,
|
|
}) => {
|
|
const [dragOffset, setDragOffset] = useState(0)
|
|
const [isDragging, setIsDragging] = useState(false)
|
|
const touchStartY = useRef(0)
|
|
|
|
// 使用全局键盘状态
|
|
const { keyboardHeight, isKeyboardVisible, addListener, initializeKeyboardListener } = useKeyboardHeight()
|
|
|
|
// 使用全局键盘状态监听
|
|
useEffect(() => {
|
|
// 初始化全局键盘监听器
|
|
initializeKeyboardListener()
|
|
|
|
// 添加本地监听器
|
|
const removeListener = addListener((height, visible) => {
|
|
console.log('CustomPopup 收到键盘变化:', height, visible)
|
|
})
|
|
|
|
return () => {
|
|
removeListener()
|
|
}
|
|
}, [initializeKeyboardListener, addListener])
|
|
|
|
if (!visible) {
|
|
return null
|
|
}
|
|
|
|
const handleCancel = () => {
|
|
if (onCancel) {
|
|
onCancel()
|
|
} else {
|
|
onClose()
|
|
}
|
|
}
|
|
|
|
const handleTouchStart = (e: any) => {
|
|
if (!enableDragToClose) return
|
|
|
|
touchStartY.current = e.touches[0].clientY
|
|
setIsDragging(true)
|
|
}
|
|
|
|
const handleTouchMove = (e: any) => {
|
|
if (!enableDragToClose || !isDragging) return
|
|
|
|
const currentY = e.touches[0].clientY
|
|
const deltaY = currentY - touchStartY.current
|
|
|
|
if (deltaY > 0) {
|
|
setDragOffset(Math.min(deltaY, 100))
|
|
}
|
|
}
|
|
|
|
const handleTouchEnd = () => {
|
|
if (!enableDragToClose || !isDragging) return
|
|
|
|
setIsDragging(false)
|
|
|
|
if (dragOffset > 50) {
|
|
onClose()
|
|
}
|
|
|
|
setDragOffset(0)
|
|
}
|
|
|
|
const overlayAlignItems =
|
|
position === 'center'
|
|
? 'center'
|
|
: position === 'top'
|
|
? 'flex-start'
|
|
: 'flex-end'
|
|
|
|
const handleOverlayClick = () => {
|
|
onClose()
|
|
}
|
|
|
|
// 阻止弹窗内的触摸事件冒泡
|
|
const handleTouchMoveInPopup = (e: any) => {
|
|
if (!isKeyboardVisible) {
|
|
e.stopPropagation()
|
|
}
|
|
}
|
|
|
|
|
|
return (
|
|
<View
|
|
className={styles['custom-popup-overlay']}
|
|
style={{ zIndex: zIndex ?? undefined, alignItems: overlayAlignItems }}
|
|
onClick={handleOverlayClick}
|
|
>
|
|
<View className={styles['custom-popup-move']} onTouchMove={handleTouchMoveInPopup} catchMove></View>
|
|
<View
|
|
className={`${styles['custom-popup']} ${className ? className : ''}`}
|
|
style={{
|
|
...style,
|
|
paddingBottom: isKeyboardVisible ? `${keyboardHeight}px` : undefined,
|
|
}}
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
}}
|
|
>
|
|
{enableDragToClose && (
|
|
<View
|
|
className={styles['custom-popup__drag-handle-container']}
|
|
onTouchStart={handleTouchStart}
|
|
onTouchMove={handleTouchMove}
|
|
onTouchEnd={handleTouchEnd}
|
|
>
|
|
<View
|
|
className={styles['custom-popup__drag-handle']}
|
|
style={{
|
|
transform: `translateX(-50%) translateY(${dragOffset * 0.3}px)`,
|
|
opacity: isDragging ? 0.8 : 1,
|
|
transition: isDragging ? 'none' : 'all 0.3s ease-out',
|
|
}}
|
|
/>
|
|
</View>
|
|
)}
|
|
|
|
{showHeader && (
|
|
<View className={styles['custom-popup__header']}>
|
|
{typeof title === 'string' ? (
|
|
<Text className={styles['custom-popup__title']}>{title}</Text>
|
|
) : (
|
|
title
|
|
)}
|
|
<View className={styles['close_button']} onClick={onClose}>
|
|
<View className={styles['close_icon']}>
|
|
<View className={styles['close_line']} />
|
|
<View className={styles['close_line']} />
|
|
</View>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
<View className={styles['custom-popup__body']}>{children}</View>
|
|
|
|
{!hideFooter && !isKeyboardVisible && (
|
|
<View className={styles['custom-popup__footer']}>
|
|
<Button
|
|
className={`${styles['custom-popup__btn']} ${styles['custom-popup__btn-cancel']}`}
|
|
type="default"
|
|
size="small"
|
|
onClick={handleCancel}
|
|
>
|
|
{cancelText}
|
|
</Button>
|
|
<Button
|
|
className={`${styles['custom-popup__btn']} ${styles['custom-popup__btn-confirm']}`}
|
|
type="primary"
|
|
size="small"
|
|
onClick={onConfirm}
|
|
>
|
|
{confirmText}
|
|
</Button>
|
|
</View>
|
|
)}
|
|
</View>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
export default CustomPopup
|
|
|