智能解析

This commit is contained in:
筱野
2025-09-17 20:13:34 +08:00
parent 30935f71df
commit 137a7321c9
20 changed files with 572 additions and 62 deletions

View File

@@ -0,0 +1,184 @@
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 styles from './index.module.scss'
import images from '@/config/images'
export interface AiImportPopupProps {
visible: boolean
onClose: () => void
onManualPublish?: () => void
onPasteAndRecognize?: (text: string) => void
}
const AiImportPopup: React.FC<AiImportPopupProps> = ({
visible,
onClose,
onManualPublish,
onPasteAndRecognize
}) => {
const [text, setText] = useState('')
const [uploadFailCount, setUploadFailCount] = useState(0)
const maxFailCount = 3
// 当弹窗显示时,尝试获取剪切板内容
useEffect(() => {
if (visible) {
getClipboardData()
}
}, [visible])
const getClipboardData = async () => {
try {
const res = await Taro.getClipboardData()
if (res.data && res.data.trim()) {
setText(res.data)
Toast.show('toast', {
content: '有场读取了你的剪切板信息',
duration: 90,
wordBreak:'break-word'
})
// Taro.showToast({
// title: '已读取你的剪切板信息',
// icon: 'success',
// duration: 2000
// })
} else {
Taro.showToast({
title: '剪切板为空,请手动输入',
icon: 'none',
duration: 2000
})
}
} catch (error) {
console.error('获取剪切板失败:', error)
Taro.showToast({
title: '读取剪切板失败,请手动输入',
icon: 'error',
duration: 2000
})
}
}
const handleTextChange = (e: any) => {
setText(e.detail.value)
}
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
console.log('选择的图片:', res.tempFiles[0])
// TODO: 实现图片识别逻辑
Taro.showToast({
title: '图片识别功能开发中',
icon: 'none'
})
}
} catch (error) {
console.error('选择图片失败:', error)
setUploadFailCount(prev => prev + 1)
Taro.showToast({
title: '上传失败',
icon: 'error'
})
}
}
const handlePasteAndRecognize = () => {
if (!text.trim()) {
Taro.showToast({
title: '请输入球局信息',
icon: 'none'
})
return
}
if (onPasteAndRecognize) {
onPasteAndRecognize(text)
}
onClose()
}
const handleManualPublish = () => {
if (onManualPublish) {
onManualPublish()
}
onClose()
}
const showManualButton = uploadFailCount >= maxFailCount
return (
<Popup
visible={visible}
position="bottom"
round={true}
closeable={false}
onClose={onClose}
className={styles.aiImportPopup}
>
<View className={styles.popupContent}>
{/* 头部 */}
<View className={styles.header}>
<View className={styles.titleContainer}>
<Image src={images.ICON_STARK} className={styles.lightningIcon} />
<Text className={styles.title}></Text>
</View>
<View className={styles.closeButton} onClick={onClose}>
<Text className={styles.closeIcon}>×</Text>
</View>
</View>
{/* 文本域 */}
<View className={styles.textAreaContainer}>
<Textarea
className={styles.textArea}
value={text}
onInput={handleTextChange}
placeholder="请输入球局信息..."
maxlength={100}
showConfirmBar={false}
autoHeight
/>
<View className={styles.charCount}>
<Text className={styles.charCountText}>{text.length}/100</Text>
</View>
</View>
{/* 图片识别按钮 */}
<View className={styles.imageRecognitionContainer}>
<View className={styles.imageRecognitionButton} onClick={handleImageRecognition}>
<Image src={images.ICON_UPLOAD} className={styles.cameraIcon} />
<Text className={styles.imageRecognitionText}></Text>
<Text className={styles.imageRecognitionDesc}>/</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}>
<Image src={images.ICON_PLUS} className={styles.clipboardIcon} />
<Text className={styles.pasteButtonText}></Text>
</View>
</View>
</View>
<Toast id="toast" />
</Popup>
)
}
export default AiImportPopup

View File

@@ -0,0 +1,191 @@
@use '~@/scss/themeColor.scss' as theme;
.aiImportPopup {
.popupContent {
width: 100%;
background: #fff;
border-radius: 16px 16px 0 0;
padding: 0;
box-sizing: border-box;
max-height: 80vh;
overflow-y: auto;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #f0f1f5;
.titleContainer {
display: flex;
align-items: center;
gap: 8px;
.lightningIcon {
width: 16px;
height: 16px;
}
.title {
font-size: 16px;
font-weight: 600;
color: #1f2329;
}
}
.closeButton {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
.closeIcon {
font-size: 20px;
color: #8a8a8a;
line-height: 1;
}
}
}
.textAreaContainer {
position: relative;
padding: 16px 20px;
.textArea {
width: 100%;
min-height: 80px;
padding: 12px;
border: 1px solid #e5e6eb;
border-radius: 8px;
font-size: 14px;
color: #1f2329;
background: #fff;
box-sizing: border-box;
resize: none;
&:focus {
border-color: #165dff;
outline: none;
}
}
.charCount {
position: absolute;
bottom: 20px;
right: 32px;
.charCountText {
font-size: 12px;
color: #8a8a8a;
}
}
}
.imageRecognitionContainer {
padding: 0 20px 16px;
.imageRecognitionButton {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
background: #f8f9fa;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.2s;
&:active {
background: #e9ecef;
}
.cameraIcon {
width: 16px;
height: 16px;
}
.imageRecognitionText {
font-size: 14px;
color: #1f2329;
font-weight: 500;
}
.imageRecognitionDesc {
font-size: 12px;
color: #8a8a8a;
margin-left: auto;
}
}
}
.bottomButtons {
padding: 16px 20px 20px;
display: flex;
gap: 12px;
padding-bottom: calc(20px + env(safe-area-inset-bottom));
.manualButton {
flex: 1;
height: 44px;
background: #f5f6f7;
border: 1px solid #e5e6eb;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
&:active {
background: #e9ecef;
}
.manualButtonText {
font-size: 14px;
color: #1f2329;
font-weight: 500;
}
}
.pasteButton {
flex: 1;
height: 44px;
background: #000;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
cursor: pointer;
transition: all 0.2s;
&:active {
background: #333;
}
.clipboardIcon {
width: 16px;
height: 16px;
filter: invert(1);
}
.pasteButtonText {
font-size: 14px;
color: #fff;
font-weight: 500;
}
}
}
}
:global {
.nut-toast-inner{
max-width: 95%;
width: auto;
white-space: nowrap;
display: inline-block;
}
}

View File

@@ -0,0 +1,2 @@
export { default } from './AiImportPopup'
export type { AiImportPopupProps } from './AiImportPopup'