303 lines
9.5 KiB
TypeScript
303 lines
9.5 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
||
import { View, Text, Textarea, Image } from '@tarojs/components'
|
||
import Taro from '@tarojs/taro'
|
||
import { ConfigProvider, Loading, Toast } from '@nutui/nutui-react-taro'
|
||
import styles from './index.module.scss'
|
||
import uploadFiles from '@/services/uploadFiles'
|
||
import publishService from '@/services/publishService'
|
||
import { usePublishBallActions } from '@/store/publishBallStore'
|
||
import { useKeyboardHeight } from '@/store/keyboardStore'
|
||
import images from '@/config/images'
|
||
|
||
export interface AiImportPopupProps {
|
||
visible: boolean
|
||
onClose: () => void
|
||
onManualPublish?: () => void
|
||
}
|
||
|
||
const AiImportPopup: React.FC<AiImportPopupProps> = ({
|
||
visible,
|
||
onClose,
|
||
onManualPublish,
|
||
}) => {
|
||
const [text, setText] = useState('')
|
||
const [uploadFailCount, setUploadFailCount] = useState(0)
|
||
const [loading, setLoading] = useState(false)
|
||
const [uploadLoading, setUploadLoading] = useState(false)
|
||
const maxFailCount = 3
|
||
const isCharCountExceeded = text.length > 100
|
||
|
||
// 获取 actions(在组件顶层调用 Hook)
|
||
const { setPublishData } = usePublishBallActions()
|
||
|
||
// 使用全局键盘状态
|
||
const { keyboardHeight, isKeyboardVisible, addListener, initializeKeyboardListener } = useKeyboardHeight()
|
||
|
||
const textIdentification = async (text: string) => {
|
||
setLoading(true)
|
||
const res = await publishService.extract_tennis_activity({text})
|
||
const { data } = res
|
||
if (data && data?.length > 0) {
|
||
navigateToPublishBall(data)
|
||
} else {
|
||
Taro.showToast({
|
||
title: '未识别到球局信息',
|
||
icon: 'error'
|
||
})
|
||
setUploadFailCount(prev => prev + 1)
|
||
}
|
||
setLoading(false)
|
||
}
|
||
|
||
const initAiPopup = () => {
|
||
setText('')
|
||
setUploadFailCount(0)
|
||
setLoading(false)
|
||
setUploadLoading(false)
|
||
}
|
||
const handlePasteAndRecognize = async () => {
|
||
if (text) {
|
||
if (text.length > 100) return;
|
||
textIdentification(text)
|
||
} else {
|
||
getClipboardData()
|
||
}
|
||
}
|
||
const getClipboardData = async () => {
|
||
try {
|
||
const res = await Taro.getClipboardData()
|
||
if (res.data && res.data.trim()) {
|
||
setText(res.data)
|
||
Toast.show('toast', {
|
||
content: '有场读取了你的剪切板信息',
|
||
duration: 2,
|
||
wordBreak:'break-word'
|
||
})
|
||
if (res.data.length > 100) return;
|
||
textIdentification(res.data)
|
||
// Taro.showToast({
|
||
// title: '已读取你的剪切板信息',
|
||
// icon: 'success',
|
||
// duration: 2000
|
||
// })
|
||
} else {
|
||
Taro.showToast({
|
||
title: '剪切板为空,请手动输入',
|
||
icon: 'none',
|
||
duration: 2
|
||
})
|
||
}
|
||
} catch (error) {
|
||
console.error('获取剪切板失败:', error)
|
||
Taro.showToast({
|
||
title: '读取剪切板失败,请手动输入',
|
||
icon: 'error',
|
||
duration: 2
|
||
})
|
||
}
|
||
}
|
||
|
||
const navigateToPublishBall = (data: any) => {
|
||
if (Array.isArray(data) && data.length > 0) {
|
||
setPublishData(data)
|
||
initAiPopup()
|
||
closePopupBefore()
|
||
Taro.navigateTo({
|
||
url: '/publish_pages/publishBall/index?type=ai'
|
||
})
|
||
}
|
||
}
|
||
|
||
const handleTextChange = (e: any) => {
|
||
const text = e.detail.value;
|
||
const maxAllowedLength = 120;
|
||
const truncatedVal = text.length > maxAllowedLength ? text.slice(0, maxAllowedLength) : text
|
||
setText(truncatedVal)
|
||
}
|
||
|
||
// 使用全局键盘状态监听
|
||
useEffect(() => {
|
||
// 初始化全局键盘监听器
|
||
initializeKeyboardListener()
|
||
|
||
// 添加本地监听器
|
||
const removeListener = addListener((height, visible) => {
|
||
console.log('AiImportPopup 收到键盘变化:', height, visible)
|
||
})
|
||
|
||
return () => {
|
||
removeListener()
|
||
}
|
||
}, [initializeKeyboardListener, addListener])
|
||
|
||
const handleImageRecognition = async () => {
|
||
try {
|
||
const res = await Taro.chooseMedia({
|
||
count: 1,
|
||
mediaType: ['image'],
|
||
sourceType: ['album', 'camera'],
|
||
camera: 'back'
|
||
})
|
||
|
||
if (res.tempFiles && res.tempFiles.length > 0) {
|
||
// 这里可以调用图片识别API
|
||
setUploadLoading(false)
|
||
setLoading(true)
|
||
const res_upload = await uploadFiles.upload_oss_img(res.tempFiles[0].tempFilePath)
|
||
const {ossPath} = res_upload;
|
||
if (ossPath) {
|
||
setUploadLoading(true)
|
||
const publishData = await publishService.extract_tennis_activity_from_image({image_url: ossPath})
|
||
const { data } = publishData
|
||
if (data && data?.length > 0) {
|
||
navigateToPublishBall(data)
|
||
} else {
|
||
Taro.showToast({
|
||
title: '未识别到球局信息',
|
||
icon: 'error'
|
||
})
|
||
setUploadFailCount(prev => prev + 1)
|
||
setUploadLoading(false)
|
||
}
|
||
setLoading(false)
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('选择图片失败:', error)
|
||
if (!(typeof error === 'object' && error.errMsg && error.errMsg.includes('fail cancel'))) {
|
||
setUploadFailCount(prev => prev + 1)
|
||
Taro.showToast({
|
||
title: '上传失败',
|
||
icon: 'error'
|
||
})
|
||
initLoading()
|
||
}
|
||
}
|
||
}
|
||
|
||
const initLoading = () => {
|
||
setLoading(false)
|
||
setUploadLoading(false)
|
||
}
|
||
|
||
|
||
const handleManualPublish = () => {
|
||
if (onManualPublish) {
|
||
onManualPublish()
|
||
}
|
||
closePopupBefore()
|
||
}
|
||
|
||
const closePopupBefore = () => {
|
||
initLoading()
|
||
onClose()
|
||
}
|
||
|
||
const showManualButton = uploadFailCount >= maxFailCount
|
||
if (!visible) {
|
||
return null
|
||
}
|
||
|
||
// 阻止弹窗内的触摸事件冒泡
|
||
const handleTouchMoveInPopup = (e) => {
|
||
if (!isKeyboardVisible) {
|
||
e.stopPropagation()
|
||
}
|
||
}
|
||
|
||
return (
|
||
<View
|
||
className={styles.aiImportPopupOverlay}
|
||
>
|
||
<View className={styles.aiImportPopupWrapper} onTouchMove={handleTouchMoveInPopup} catchMove></View>
|
||
<View
|
||
className={styles.aiImportPopup}
|
||
style={{ paddingBottom: isKeyboardVisible ? `${keyboardHeight}px` : undefined }}
|
||
>
|
||
<View className={styles.popupContent}>
|
||
{/* 头部 */}
|
||
<View className={styles.header}>
|
||
<View className={styles.titleContainer}>
|
||
<Image src={images.ICON_IMPORTANT_BLACK} className={styles.lightningIcon} />
|
||
<Text className={styles.title}>智能导入球局信息</Text>
|
||
</View>
|
||
<View className={styles.closeButton} onClick={closePopupBefore}>
|
||
<Image src={images.ICON_CLOSE} className={styles.lightningIcon} />
|
||
</View>
|
||
</View>
|
||
|
||
{/* 文本域 */}
|
||
<View className={styles.textAreaContainer}>
|
||
<Textarea
|
||
className={styles.textArea}
|
||
value={text}
|
||
onInput={handleTextChange}
|
||
onFocus={() => {}}
|
||
onBlur={() => {}}
|
||
placeholder="在此「粘贴识别」或输入文本,智能拆分球局时间、费用、地点和其他信息,并帮你智能生成球局标题"
|
||
maxlength={-1}
|
||
showConfirmBar={false}
|
||
placeholderClass={styles.textArea_placeholder}
|
||
autoHeight
|
||
// 关闭系统自动上推,改为手动根据键盘高度加内边距
|
||
adjustPosition={false}
|
||
/>
|
||
<View className={styles.charCount}>
|
||
<Text className={`${styles.charCountText} ${isCharCountExceeded ? styles.charCountTextExceeded : ''}`}>
|
||
{text.length}/100
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
|
||
{/* 图片识别按钮 */}
|
||
<View className={styles.imageRecognitionContainer}>
|
||
<View
|
||
className={`${styles.imageRecognitionButton} ${
|
||
uploadLoading ? styles.uploadLoadingContainer : ''
|
||
}`}
|
||
onClick={handleImageRecognition}
|
||
>
|
||
{uploadLoading ? (
|
||
<Image src={images.ICON_UPLOAD_SUCCESS} className={styles.cameraIcon} />
|
||
) : (
|
||
<Image src={images.ICON_UPLOAD_IMG} className={styles.cameraIcon} />
|
||
)}
|
||
<Text className={styles.imageRecognitionText}>图片识别</Text>
|
||
<Text className={styles.imageRecognitionDesc}>
|
||
{uploadLoading ? '已上传 1 张图片' : '支持订场截图/小红书笔记截图等图片'}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
|
||
{/* 底部按钮 */}
|
||
<View className={styles.bottomButtons}>
|
||
{showManualButton && (
|
||
<View className={styles.manualButton} onClick={handleManualPublish}>
|
||
<Text className={styles.manualButtonText}>手动发布球局</Text>
|
||
</View>
|
||
)}
|
||
<View className={styles.pasteButton} onClick={handlePasteAndRecognize}>
|
||
{loading ? (
|
||
<View className={styles.loadingContainer}>
|
||
<ConfigProvider theme={{ nutuiLoadingIconColor: '#fff', nutuiLoadingIconSize: '20px' }}>
|
||
<Loading type="circular" />
|
||
</ConfigProvider>
|
||
<Text className={styles.pasteButtonText}>识别中</Text>
|
||
</View>
|
||
) : (
|
||
<>
|
||
<Image src={images.ICON_COPY} className={styles.clipboardIcon} />
|
||
<Text className={styles.pasteButtonText}>粘贴并识别</Text>
|
||
</>
|
||
)}
|
||
</View>
|
||
</View>
|
||
</View>
|
||
<Toast id="toast" />
|
||
</View>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
export default AiImportPopup
|