diff --git a/src/components/BatchRegister.tsx b/src/components/BatchRegister.tsx index a518f0e..785aa95 100644 --- a/src/components/BatchRegister.tsx +++ b/src/components/BatchRegister.tsx @@ -2,16 +2,19 @@ import React from 'react' import { Layout, Collapse, Form, Input, Button, Space, message, Spin } from 'antd' import type { FormInstance } from 'antd' import PeopleForm from './PeopleForm.tsx' +import InputDrawer from './InputDrawer.tsx' import { createPeoplesBatch, type People } from '../apis' const { Panel } = Collapse as any const { Content } = Layout -type FormItem = { id: string } +type FormItem = { id: string; initialData?: any } -const BatchRegister: React.FC = () => { +type Props = { inputOpen?: boolean; onCloseInput?: () => void; containerEl?: HTMLElement | null } + +const BatchRegister: React.FC = ({ inputOpen = false, onCloseInput, containerEl }) => { const [commonForm] = Form.useForm() - const [items, setItems] = React.useState([{ id: `${Date.now()}-${Math.random()}` }]) + const [items, setItems] = React.useState([]) const instancesRef = React.useRef>({}) const [loading, setLoading] = React.useState(false) @@ -40,6 +43,12 @@ const BatchRegister: React.FC = () => { } } + const handleInputResult = (list: any) => { + const arr = Array.isArray(list) ? list : [list] + const next: FormItem[] = arr.map((data: any) => ({ id: `${Date.now()}-${Math.random()}`, initialData: data })) + setItems((prev) => [...prev, ...next]) + } + const handleSubmit = async () => { if (loading) return try { @@ -89,7 +98,7 @@ const BatchRegister: React.FC = () => {
- +
@@ -113,6 +122,7 @@ const BatchRegister: React.FC = () => { > (instancesRef.current[item.id] = f)} /> @@ -136,6 +146,15 @@ const BatchRegister: React.FC = () => {
)} + {/* 批量页右侧输入抽屉,挂载到标题栏下方容器 */} + {})} + onResult={handleInputResult} + containerEl={containerEl} + showUpload + mode={'batch-image'} + />
) } diff --git a/src/components/HintText.tsx b/src/components/HintText.tsx index bf74f44..13534cd 100644 --- a/src/components/HintText.tsx +++ b/src/components/HintText.tsx @@ -5,9 +5,15 @@ type Props = { showUpload?: boolean }; const HintText: React.FC = ({ showUpload = true }) => { const text = showUpload - ? '提示:支持输入多行文本、上传图片或粘贴剪贴板图片。按 Enter 发送,Shift+Enter 换行。' - : '提示:支持输入多行文本。按 Enter 发送,Shift+Enter 换行。'; - return
{text}
; + ? ' · 支持多行输入文本、上传图片或粘贴剪贴板图片。' + : ' · 支持输入多行文本。'; + return ( +
+
Tips:
+
{text}
+
· 按 Enter 发送,Shift+Enter 换行。
+
+ ); }; export default HintText; \ No newline at end of file diff --git a/src/components/InputDrawer.tsx b/src/components/InputDrawer.tsx index 4863fd1..d478f2d 100644 --- a/src/components/InputDrawer.tsx +++ b/src/components/InputDrawer.tsx @@ -10,7 +10,7 @@ type Props = { onResult?: (data: any) => void; containerEl?: HTMLElement | null; // 抽屉挂载容器(用于放在标题栏下方) showUpload?: boolean; // 透传到输入面板,控制图片上传按钮 - mode?: 'input' | 'search'; // 透传到输入面板,控制工作模式 + mode?: 'input' | 'search' | 'batch-image'; // 透传到输入面板,控制工作模式 }; const InputDrawer: React.FC = ({ open, onClose, onResult, containerEl, showUpload = true, mode = 'input' }) => { diff --git a/src/components/InputPanel.css b/src/components/InputPanel.css index 5ff0b91..af49431 100644 --- a/src/components/InputPanel.css +++ b/src/components/InputPanel.css @@ -46,11 +46,8 @@ /* 左侧文件标签样式,保持短名及紧凑展示 */ .selected-image-tag { - max-width: 60%; - overflow: hidden; - text-overflow: ellipsis; + max-width: none; + overflow: visible; white-space: nowrap; - margin-right: auto; /* 保持标签在左侧,按钮在右侧 */ -} - -/* 移除右侧容器样式,按钮直接在 input-actions 中对齐 */ \ No newline at end of file + margin-right: 0; +} \ No newline at end of file diff --git a/src/components/InputPanel.tsx b/src/components/InputPanel.tsx index d63b4ae..9c151a9 100644 --- a/src/components/InputPanel.tsx +++ b/src/components/InputPanel.tsx @@ -9,27 +9,16 @@ const { TextArea } = Input; interface InputPanelProps { onResult?: (data: any) => void; showUpload?: boolean; // 是否显示图片上传按钮,默认显示 - mode?: 'input' | 'search'; // 输入面板工作模式,默认为表单填写(input) + mode?: 'input' | 'search' | 'batch-image'; // 输入面板工作模式,新增批量图片模式 } const InputPanel: React.FC = ({ onResult, showUpload = true, mode = 'input' }) => { const [value, setValue] = React.useState(''); const [fileList, setFileList] = React.useState([]); const [loading, setLoading] = React.useState(false); - const [savedText, setSavedText] = React.useState(''); + // 批量模式不保留文本内容 - // 统一显示短文件名:image.{ext} - const getImageExt = (file: any): string => { - const type = file?.type || ''; - if (typeof type === 'string' && type.startsWith('image/')) { - const sub = type.split('/')[1] || 'png'; - return sub.toLowerCase(); - } - const name = file?.name || ''; - const dot = name.lastIndexOf('.'); - const ext = dot >= 0 ? name.slice(dot + 1) : ''; - return (ext || 'png').toLowerCase(); - }; + // 不需要扩展名重命名,展示 image-序号 const send = async () => { const trimmed = value.trim(); @@ -66,6 +55,40 @@ const InputPanel: React.FC = ({ onResult, showUpload = true, mo return; } + // 批量图片模式:循环调用图片识别 API + if (mode === 'batch-image') { + if (!hasImage) { + message.info('请添加至少一张图片'); + return; + } + setLoading(true); + try { + const results: any[] = []; + for (let i = 0; i < fileList.length; i++) { + const f = fileList[i].originFileObj || fileList[i]; + if (!f) continue; + const resp = await postInputImage(f); + if (resp && resp.error_code === 0 && resp.data) { + results.push(resp.data); + } + } + if (results.length > 0) { + message.success(`已识别 ${results.length} 张图片`); + onResult?.(results); + } else { + message.error('识别失败,请检查图片后重试'); + } + setValue(''); + setFileList([]); + } catch (error) { + console.error('批量识别失败:', error); + message.error('网络错误,请稍后重试'); + } finally { + setLoading(false); + } + return; + } + setLoading(true); try { let response; @@ -96,7 +119,6 @@ const InputPanel: React.FC = ({ onResult, showUpload = true, mo // 清空输入 setValue(''); setFileList([]); - setSavedText(''); } else { message.error(response.error_info || '处理失败,请重试'); } @@ -129,39 +151,55 @@ const InputPanel: React.FC = ({ onResult, showUpload = true, mo const items = e.clipboardData?.items; if (!items || items.length === 0) return; - let pastedImage: File | null = null; - for (let i = 0; i < items.length; i++) { - const item = items[i]; - if (item.kind === 'file') { - const file = item.getAsFile(); - if (file && file.type.startsWith('image/')) { - pastedImage = file; - break; // 只取第一张 + if (mode === 'batch-image') { + const newEntries: any[] = []; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (item.kind === 'file') { + const file = item.getAsFile(); + if (file && file.type.startsWith('image/')) { + const entry = { + uid: `${Date.now()}-${Math.random()}`, + name: 'image', + status: 'done', + originFileObj: file, + } as any; + newEntries.push(entry); + } } } - } - - if (pastedImage) { - // 避免图片内容以文本方式粘贴进输入框 - e.preventDefault(); - - const ext = getImageExt(pastedImage); - const name = `image.${ext}`; - - const entry = { - uid: `${Date.now()}-${Math.random()}`, - name, - status: 'done', - originFileObj: pastedImage, - } as any; - - // 仅保留一张:新图直接替换旧图 - if (fileList.length === 0) { - setSavedText(value); + if (newEntries.length > 0) { + e.preventDefault(); + setValue(''); + setFileList([...fileList, ...newEntries]); + message.success(`已添加 ${newEntries.length} 张剪贴板图片`); + } + } else { + // 单图模式:仅添加第一张并替换已有 + let firstImage: File | null = null; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (item.kind === 'file') { + const file = item.getAsFile(); + if (file && file.type.startsWith('image/')) { + firstImage = file; + break; + } + } + } + if (firstImage) { + e.preventDefault(); + setValue(''); + setFileList([ + { + uid: `${Date.now()}-${Math.random()}`, + name: 'image', + status: 'done', + originFileObj: firstImage, + } as any, + ]); + message.success('已添加剪贴板图片'); } - setValue(''); - setFileList([entry]); - message.success('已添加剪贴板图片'); } }; @@ -178,60 +216,95 @@ const InputPanel: React.FC = ({ onResult, showUpload = true, mo })()}