智能导入

This commit is contained in:
筱野
2025-09-18 21:39:05 +08:00
parent ffdb0bda26
commit c096d265ab
11 changed files with 508 additions and 96 deletions

View File

@@ -1,8 +1,11 @@
import React, { useState, useEffect } from 'react'
import { View, Text, Textarea, Image } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { Popup, Toast } from '@nutui/nutui-react-taro'
import { ConfigProvider, Loading, Popup, 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 images from '@/config/images'
export interface AiImportPopupProps {
@@ -20,14 +23,21 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
}) => {
const [text, setText] = useState('')
const [uploadFailCount, setUploadFailCount] = useState(0)
const [loading, setLoading] = useState(false)
const [uploadLoading, setUploadLoading] = useState(false)
const maxFailCount = 3
// 当弹窗显示时,尝试获取剪切板内容
useEffect(() => {
if (visible) {
getClipboardData()
}
}, [visible])
// 获取 actions在组件顶层调用 Hook
const { setPublishData } = usePublishBallActions()
const textIdentification = async (text: string) => {
setLoading(true)
const res = await publishService.extract_tennis_activity({text})
console.log(res)
const {data} = res
navigateToPublishBall(data)
setLoading(false)
}
const getClipboardData = async () => {
try {
@@ -36,9 +46,10 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
setText(res.data)
Toast.show('toast', {
content: '有场读取了你的剪切板信息',
duration: 90,
duration: 2,
wordBreak:'break-word'
})
textIdentification(res.data)
// Taro.showToast({
// title: '已读取你的剪切板信息',
// icon: 'success',
@@ -48,7 +59,7 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
Taro.showToast({
title: '剪切板为空,请手动输入',
icon: 'none',
duration: 2000
duration: 2
})
}
} catch (error) {
@@ -56,11 +67,20 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
Taro.showToast({
title: '读取剪切板失败,请手动输入',
icon: 'error',
duration: 2000
duration: 2
})
}
}
const navigateToPublishBall = (data: any) => {
if (Array.isArray(data) && data.length > 0) {
setPublishData(data)
Taro.navigateTo({
url: '/publish_pages/publishBall/pages/publishBall?type=ai'
})
}
}
const handleTextChange = (e: any) => {
setText(e.detail.value)
}
@@ -76,12 +96,17 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
if (res.tempFiles && res.tempFiles.length > 0) {
// 这里可以调用图片识别API
console.log('选择的图片:', res.tempFiles[0])
// TODO: 实现图片识别逻辑
Taro.showToast({
title: '图片识别功能开发中',
icon: 'none'
})
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
navigateToPublishBall(data)
setLoading(false)
}
}
} catch (error) {
console.error('选择图片失败:', error)
@@ -93,20 +118,6 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
}
}
const handlePasteAndRecognize = () => {
if (!text.trim()) {
Taro.showToast({
title: '请输入球局信息',
icon: 'none'
})
return
}
if (onPasteAndRecognize) {
onPasteAndRecognize(text)
}
onClose()
}
const handleManualPublish = () => {
if (onManualPublish) {
@@ -144,9 +155,10 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
className={styles.textArea}
value={text}
onInput={handleTextChange}
placeholder="请输入球局信息..."
placeholder="在此「粘贴识别」或输入文本,智能拆分球局时间、费用、地点和其他信息,并帮你智能生成球局标题"
maxlength={100}
showConfirmBar={false}
placeholderClass={styles.textArea_placeholder}
autoHeight
/>
<View className={styles.charCount}>
@@ -156,10 +168,12 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
{/* 图片识别按钮 */}
<View className={styles.imageRecognitionContainer}>
<View className={styles.imageRecognitionButton} onClick={handleImageRecognition}>
<Image src={images.ICON_UPLOAD} className={styles.cameraIcon} />
<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}>/</Text>
<Text className={styles.imageRecognitionDesc}>{uploadLoading ? '已上传 1 张图片' : '支持订场截图/小红书笔记截图等图片'}</Text>
</View>
</View>
@@ -170,9 +184,22 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
<Text className={styles.manualButtonText}></Text>
</View>
)}
<View className={styles.pasteButton} onClick={handlePasteAndRecognize}>
<Image src={images.ICON_PLUS} className={styles.clipboardIcon} />
<Text className={styles.pasteButtonText}></Text>
<View className={styles.pasteButton} onClick={getClipboardData}>
{
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>

View File

@@ -57,7 +57,7 @@
.textArea {
width: 100%;
min-height: 80px;
min-height: 120px;
padding: 12px;
border: 1px solid #e5e6eb;
border-radius: 8px;
@@ -67,9 +67,10 @@
box-sizing: border-box;
resize: none;
&:focus {
border-color: #165dff;
outline: none;
.textArea_placeholder{
color: rgba(60, 60, 67, 0.30);
font-size: 14px;
line-height: 24px;
}
}
@@ -90,17 +91,15 @@
.imageRecognitionButton {
display: flex;
height: 40px;
padding: 2px 16px;
justify-content: center;
align-items: center;
gap: 8px;
padding: 12px;
background: #f8f9fa;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.2s;
&:active {
background: #e9ecef;
}
gap: 6px;
align-self: stretch;
border-radius: 999px;
border: 0.5px solid rgba(0, 0, 0, 0.16);
background: #FFF;
.cameraIcon {
width: 16px;
@@ -115,10 +114,15 @@
.imageRecognitionDesc {
font-size: 12px;
color: #8a8a8a;
margin-left: auto;
color: rgba(0, 0, 0, 0.35);
}
}
.uploadLoadingContainer{
border-radius: 999px;
border: 0.5px solid rgba(0, 0, 0, 0.16);
background: rgba(52, 199, 89, 0.10);
display: flex;
}
}
.bottomButtons {
@@ -161,21 +165,25 @@
gap: 6px;
cursor: pointer;
transition: all 0.2s;
.loadingContainer{
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
&:active {
background: #333;
}
.clipboardIcon {
width: 16px;
height: 16px;
filter: invert(1);
width: 20px;
height: 20px;
}
.pasteButtonText {
font-size: 14px;
font-size: 16px;
color: #fff;
font-weight: 500;
font-weight: 600;
}
}
}

View File

@@ -3,7 +3,7 @@ import { View, Text, Input, ScrollView, Image } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { Loading } from '@nutui/nutui-react-taro'
import StadiumDetail, { StadiumDetailRef } from './StadiumDetail'
import { CommonPopup } from '@/components'
import { CommonPopup } from '../../../../components'
import { getLocation } from '@/utils/locationUtils'
import PublishService from '@/services/publishService'
import images from '@/config/images'
@@ -135,6 +135,12 @@ const SelectStadium: React.FC<SelectStadiumProps> = ({
setSearchValue('')
}
const handleDetailCancel = () => {
setShowDetail(false)
setSelectedStadium(null)
setSearchValue('')
}
const handleItemLocation = (stadium: Stadium) => {
if (stadium.latitude && stadium.longitude) {
Taro.openLocation({
@@ -169,7 +175,7 @@ const SelectStadium: React.FC<SelectStadiumProps> = ({
cancelText="返回"
confirmText="确认"
className="select-stadium-popup"
onCancel={handleCancel}
onCancel={handleDetailCancel}
onConfirm={handleConfirm}
position="bottom"
round