Compare commits
3 Commits
c3800541d4
...
f00ec0588c
| Author | SHA1 | Date | |
|---|---|---|---|
| f00ec0588c | |||
| 1f138f5097 | |||
| 18ee8c1ac2 |
@@ -5,7 +5,7 @@ type Props = { showUpload?: boolean };
|
|||||||
|
|
||||||
const HintText: React.FC<Props> = ({ showUpload = true }) => {
|
const HintText: React.FC<Props> = ({ showUpload = true }) => {
|
||||||
const text = showUpload
|
const text = showUpload
|
||||||
? '提示:支持输入多行文本与上传图片。按 Enter 发送,Shift+Enter 换行。'
|
? '提示:支持输入多行文本、上传图片或粘贴剪贴板图片。按 Enter 发送,Shift+Enter 换行。'
|
||||||
: '提示:支持输入多行文本。按 Enter 发送,Shift+Enter 换行。';
|
: '提示:支持输入多行文本。按 Enter 发送,Shift+Enter 换行。';
|
||||||
return <div className="hint-text">{text}</div>;
|
return <div className="hint-text">{text}</div>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,20 @@
|
|||||||
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.15);
|
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 禁用态:浅灰背景与文字,明确不可编辑 */
|
||||||
|
.input-panel .ant-input[disabled],
|
||||||
|
.input-panel .ant-input-disabled,
|
||||||
|
.input-panel .ant-input-outlined.ant-input-disabled {
|
||||||
|
background: #f3f4f6; /* gray-100 */
|
||||||
|
color: #9ca3af; /* gray-400 */
|
||||||
|
border-color: #e5e7eb; /* gray-200 */
|
||||||
|
cursor: not-allowed;
|
||||||
|
-webkit-text-fill-color: #9ca3af; /* Safari 禁用态颜色 */
|
||||||
|
}
|
||||||
|
.input-panel .ant-input[disabled]::placeholder {
|
||||||
|
color: #cbd5e1; /* gray-300 */
|
||||||
|
}
|
||||||
|
|
||||||
.input-actions {
|
.input-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -29,3 +43,14 @@
|
|||||||
|
|
||||||
.input-actions .ant-btn-text { color: var(--text-secondary); }
|
.input-actions .ant-btn-text { color: var(--text-secondary); }
|
||||||
.input-actions .ant-btn-text:hover { color: var(--color-primary-600); }
|
.input-actions .ant-btn-text:hover { color: var(--color-primary-600); }
|
||||||
|
|
||||||
|
/* 左侧文件标签样式,保持短名及紧凑展示 */
|
||||||
|
.selected-image-tag {
|
||||||
|
max-width: 60%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-right: auto; /* 保持标签在左侧,按钮在右侧 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移除右侧容器样式,按钮直接在 input-actions 中对齐 */
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Input, Upload, message, Button, Spin } from 'antd';
|
import { Input, Upload, message, Button, Spin, Tag } from 'antd';
|
||||||
import { PictureOutlined, SendOutlined, LoadingOutlined, SearchOutlined } from '@ant-design/icons';
|
import { PictureOutlined, SendOutlined, LoadingOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
import { postInput, postInputImage, getPeoples } from '../apis';
|
import { postInput, postInputImage, getPeoples } from '../apis';
|
||||||
import './InputPanel.css';
|
import './InputPanel.css';
|
||||||
@@ -16,6 +16,20 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
|||||||
const [value, setValue] = React.useState('');
|
const [value, setValue] = React.useState('');
|
||||||
const [fileList, setFileList] = React.useState<any[]>([]);
|
const [fileList, setFileList] = React.useState<any[]>([]);
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
const [savedText, setSavedText] = React.useState<string>('');
|
||||||
|
|
||||||
|
// 统一显示短文件名: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();
|
||||||
|
};
|
||||||
|
|
||||||
const send = async () => {
|
const send = async () => {
|
||||||
const trimmed = value.trim();
|
const trimmed = value.trim();
|
||||||
@@ -82,6 +96,7 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
|||||||
// 清空输入
|
// 清空输入
|
||||||
setValue('');
|
setValue('');
|
||||||
setFileList([]);
|
setFileList([]);
|
||||||
|
setSavedText('');
|
||||||
} else {
|
} else {
|
||||||
message.error(response.error_info || '处理失败,请重试');
|
message.error(response.error_info || '处理失败,请重试');
|
||||||
}
|
}
|
||||||
@@ -107,6 +122,49 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理剪贴板粘贴图片:将图片加入上传列表,复用现有上传流程
|
||||||
|
const onPaste = (e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||||
|
if (!showUpload || loading) return;
|
||||||
|
|
||||||
|
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 (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);
|
||||||
|
}
|
||||||
|
setValue('');
|
||||||
|
setFileList([entry]);
|
||||||
|
message.success('已添加剪贴板图片');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="input-panel">
|
<div className="input-panel">
|
||||||
<Spin
|
<Spin
|
||||||
@@ -114,25 +172,63 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
|||||||
tip="正在处理中,请稍候..."
|
tip="正在处理中,请稍候..."
|
||||||
indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />}
|
indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />}
|
||||||
>
|
>
|
||||||
|
{/** 根据禁用状态动态占位符文案 */}
|
||||||
|
{(() => {
|
||||||
|
return null;
|
||||||
|
})()}
|
||||||
<TextArea
|
<TextArea
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={(e) => setValue(e.target.value)}
|
||||||
placeholder={showUpload ? '请输入个人信息描述,或上传图片…' : '请输入个人信息描述…'}
|
placeholder={
|
||||||
|
showUpload && fileList.length > 0
|
||||||
|
? '不可在添加图片时输入信息...'
|
||||||
|
: (showUpload ? '请输入个人信息描述,或上传图片…' : '请输入个人信息描述…')
|
||||||
|
}
|
||||||
autoSize={{ minRows: 6, maxRows: 12 }}
|
autoSize={{ minRows: 6, maxRows: 12 }}
|
||||||
style={{ fontSize: 14 }}
|
style={{ fontSize: 16 }}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
disabled={loading}
|
onPaste={onPaste}
|
||||||
|
disabled={loading || (showUpload && fileList.length > 0)}
|
||||||
/>
|
/>
|
||||||
</Spin>
|
</Spin>
|
||||||
<div className="input-actions">
|
<div className="input-actions">
|
||||||
|
{/* 左侧文件标签显示 */}
|
||||||
|
{showUpload && fileList.length > 0 && (
|
||||||
|
<Tag
|
||||||
|
className="selected-image-tag"
|
||||||
|
color="processing"
|
||||||
|
closable
|
||||||
|
onClose={() => { setFileList([]); setValue(savedText); setSavedText(''); }}
|
||||||
|
bordered={false}
|
||||||
|
>
|
||||||
|
{`image.${new Date().getSeconds()}.${getImageExt(fileList[0]?.originFileObj || fileList[0])}`}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
{showUpload && (
|
{showUpload && (
|
||||||
<Upload
|
<Upload
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
|
multiple={false}
|
||||||
beforeUpload={() => false}
|
beforeUpload={() => false}
|
||||||
fileList={fileList}
|
fileList={fileList}
|
||||||
onChange={({ fileList }) => setFileList(fileList as any)}
|
onChange={({ file, fileList: nextFileList }) => {
|
||||||
maxCount={9}
|
// 只保留最新一个,并重命名为 image.{ext}
|
||||||
showUploadList={{ showPreviewIcon: false }}
|
if (nextFileList.length === 0) {
|
||||||
|
setFileList([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const latest = nextFileList[nextFileList.length - 1] as any;
|
||||||
|
const raw = latest.originFileObj || file; // UploadFile 或原始 File
|
||||||
|
const ext = getImageExt(raw);
|
||||||
|
const renamed = { ...latest, name: `image.${ext}` };
|
||||||
|
if (fileList.length === 0) {
|
||||||
|
setSavedText(value);
|
||||||
|
}
|
||||||
|
setValue('');
|
||||||
|
setFileList([renamed]);
|
||||||
|
}}
|
||||||
|
onRemove={() => { setFileList([]); setValue(savedText); setSavedText(''); return true; }}
|
||||||
|
maxCount={1}
|
||||||
|
showUploadList={false}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
<Button type="text" icon={<PictureOutlined />} disabled={loading} />
|
<Button type="text" icon={<PictureOutlined />} disabled={loading} />
|
||||||
|
|||||||
Reference in New Issue
Block a user