Files
mini-programs/src/publish_pages/publishBall/components/AiImportPopup/AiImportPopup.tsx
2026-02-07 22:15:14 +08:00

303 lines
9.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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