281 lines
8.5 KiB
TypeScript
281 lines
8.5 KiB
TypeScript
import React, { useRef, useState } from 'react';
|
|
|
|
import { Popover, Tooltip } from 'antd';
|
|
import { FolderOpenOutlined, DatabaseOutlined, UserOutlined, TeamOutlined } from '@ant-design/icons';
|
|
import type { PrivatePortraitLibraryType, PrivatePortraitSelectableAsset, UploadResourceHistoryItem } from '../types';
|
|
|
|
import PrivatePortraitAssetPicker from './privatePortrait/picker/AssetPicker';
|
|
import UploadResourceHistoryPicker from './uploadResource/UploadResourceHistoryPicker';
|
|
|
|
interface UploadSelectorProps {
|
|
children: React.ReactNode;
|
|
accept?: string;
|
|
multiple?: boolean;
|
|
onLocalSelect?: (files: File[]) => void;
|
|
|
|
onHistorySelect?: (items: UploadResourceHistoryItem[]) => void;
|
|
/** 兼容旧页面:由 UploadSelector 内部打开素材选择器,确认后回传素材数组。 */
|
|
|
|
onPortraitSelect?: (items: PrivatePortraitSelectableAsset[]) => void;
|
|
onPortraitLibrarySelect?: (libraryType: PrivatePortraitLibraryType) => void;
|
|
uploading?: boolean;
|
|
tooltipTitle?: string;
|
|
maxImageCount?: number;
|
|
maxVideoCount?: number;
|
|
usedImageCount?: number;
|
|
usedVideoCount?: number;
|
|
usedVideoDuration?: number;
|
|
maxVideoDuration?: number;
|
|
maxAudioCount?: number;
|
|
usedAudioCount?: number;
|
|
maxAudioDuration?: number;
|
|
usedAudioDuration?: number;
|
|
hideLimitHint?: boolean;
|
|
/** 创作类型:'image' 时隐藏真人素材库/虚拟素材库选项 */
|
|
mediaType?: string;
|
|
}
|
|
|
|
const UploadSelector: React.FC<UploadSelectorProps> = ({
|
|
children,
|
|
accept = 'image/*,video/*',
|
|
multiple = true,
|
|
onLocalSelect,
|
|
onHistorySelect,
|
|
onPortraitSelect,
|
|
onPortraitLibrarySelect,
|
|
uploading,
|
|
tooltipTitle,
|
|
maxImageCount,
|
|
maxVideoCount,
|
|
usedImageCount,
|
|
usedVideoCount,
|
|
usedVideoDuration,
|
|
maxVideoDuration,
|
|
maxAudioCount,
|
|
usedAudioCount,
|
|
maxAudioDuration,
|
|
usedAudioDuration,
|
|
hideLimitHint,
|
|
mediaType,
|
|
}) => {
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
const [portraitPickerOpen, setPortraitPickerOpen] = useState(false);
|
|
const [portraitLibraryType, setPortraitLibraryType] = useState<PrivatePortraitLibraryType>('real_person');
|
|
const [historyModalVisible, setHistoryModalVisible] = useState(false);
|
|
const [popoverOpen, setPopoverOpen] = useState(false);
|
|
const [isUploading, setIsUploading] = useState(false);
|
|
|
|
const handleLocalSelect = () => {
|
|
setPopoverOpen(false);
|
|
fileInputRef.current?.click();
|
|
};
|
|
|
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const files = e.target.files;
|
|
if (files && onLocalSelect) {
|
|
const fileList = Array.from(files);
|
|
onLocalSelect(fileList);
|
|
}
|
|
if (fileInputRef.current) {
|
|
fileInputRef.current.value = '';
|
|
}
|
|
};
|
|
|
|
const openPortraitPicker = (libraryType: PrivatePortraitLibraryType) => {
|
|
setPopoverOpen(false);
|
|
if (onPortraitSelect) {
|
|
setPortraitLibraryType(libraryType);
|
|
setPortraitPickerOpen(true);
|
|
return;
|
|
}
|
|
if (onPortraitLibrarySelect) {
|
|
onPortraitLibrarySelect(libraryType);
|
|
return;
|
|
}
|
|
setPortraitLibraryType(libraryType);
|
|
setPortraitPickerOpen(true);
|
|
};
|
|
|
|
const handleHistorySelect = () => {
|
|
setPopoverOpen(false);
|
|
setHistoryModalVisible(true);
|
|
};
|
|
|
|
const content = (
|
|
<div style={{ padding: '4px 0', minWidth: 180 }}>
|
|
<div
|
|
onClick={() => {
|
|
handleLocalSelect();
|
|
}}
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 12,
|
|
padding: '8px 16px',
|
|
cursor: 'pointer',
|
|
transition: 'background 0.15s ease',
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.background = '#f1f5f9';
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.background = 'transparent';
|
|
}}
|
|
>
|
|
<FolderOpenOutlined style={{ fontSize: 14, color: '#64748b' }} />
|
|
<span style={{ fontSize: 14, color: '#334155' }}>
|
|
|
|
本地上传
|
|
{/* {multiple ? '本地上传(可多选拖拽)' : '本地上传'} */}
|
|
</span>
|
|
</div>
|
|
<div
|
|
onClick={handleHistorySelect}
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 12,
|
|
padding: '8px 16px',
|
|
cursor: 'pointer',
|
|
transition: 'background 0.15s ease',
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.background = '#f1f5f9';
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.background = 'transparent';
|
|
}}
|
|
>
|
|
<DatabaseOutlined style={{ fontSize: 14, color: '#64748b' }} />
|
|
<span style={{ fontSize: 14, color: '#334155' }}>从资产中选择</span>
|
|
</div>
|
|
{mediaType !== 'image' && (
|
|
<div
|
|
onClick={() => openPortraitPicker('real_person')}
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 12,
|
|
padding: '8px 16px',
|
|
cursor: 'pointer',
|
|
transition: 'background 0.15s ease',
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.background = '#f1f5f9';
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.background = 'transparent';
|
|
}}
|
|
>
|
|
<UserOutlined style={{ fontSize: 14, color: '#64748b' }} />
|
|
<span style={{ fontSize: 14, color: '#334155' }}>真人素材库</span>
|
|
</div>
|
|
)}
|
|
{mediaType !== 'image' && (
|
|
<div
|
|
onClick={() => openPortraitPicker('aigc_virtual')}
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 12,
|
|
padding: '8px 16px',
|
|
cursor: 'pointer',
|
|
transition: 'background 0.15s ease',
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.background = '#f1f5f9';
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.background = 'transparent';
|
|
}}
|
|
>
|
|
<TeamOutlined style={{ fontSize: 14, color: '#64748b' }} />
|
|
<span style={{ fontSize: 14, color: '#334155' }}>虚拟素材库</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<input
|
|
ref={fileInputRef}
|
|
type="file"
|
|
accept={accept}
|
|
multiple={multiple}
|
|
onChange={handleFileChange}
|
|
style={{ display: 'none' }}
|
|
/>
|
|
{tooltipTitle ? (
|
|
<Tooltip title={tooltipTitle}>
|
|
<Popover
|
|
content={content}
|
|
open={popoverOpen}
|
|
onOpenChange={setPopoverOpen}
|
|
trigger="click"
|
|
placement="bottomLeft"
|
|
>
|
|
<div style={{ cursor: uploading ? 'not-allowed' : 'pointer' }}>
|
|
{children}
|
|
</div>
|
|
</Popover>
|
|
</Tooltip>
|
|
) : (
|
|
<Popover
|
|
content={content}
|
|
open={popoverOpen}
|
|
onOpenChange={setPopoverOpen}
|
|
trigger="click"
|
|
placement="bottomLeft"
|
|
>
|
|
<div style={{ cursor: uploading ? 'not-allowed' : 'pointer' }}>
|
|
{children}
|
|
</div>
|
|
</Popover>
|
|
)}
|
|
|
|
<UploadResourceHistoryPicker
|
|
open={historyModalVisible}
|
|
onClose={() => setHistoryModalVisible(false)}
|
|
onSelect={(items) => {
|
|
onHistorySelect?.(items);
|
|
setHistoryModalVisible(false);
|
|
}}
|
|
allowedTypes={accept.includes('audio') ? ['image', 'video', 'audio'] : ['image', 'video']}
|
|
maxImageCount={maxImageCount}
|
|
maxVideoCount={maxVideoCount}
|
|
usedImageCount={usedImageCount}
|
|
usedVideoCount={usedVideoCount}
|
|
maxVideoDuration={maxVideoDuration}
|
|
usedVideoDuration={usedVideoDuration}
|
|
maxAudioCount={maxAudioCount}
|
|
usedAudioCount={usedAudioCount}
|
|
maxAudioDuration={maxAudioDuration}
|
|
usedAudioDuration={usedAudioDuration}
|
|
hideLimitHint={hideLimitHint}
|
|
defaultResourceType={mediaType === 'image' ? 'image' : ''}
|
|
/>
|
|
|
|
<PrivatePortraitAssetPicker
|
|
open={portraitPickerOpen}
|
|
libraryType={portraitLibraryType}
|
|
onClose={() => setPortraitPickerOpen(false)}
|
|
onSelect={(assets) => {
|
|
onPortraitSelect?.(assets);
|
|
setPortraitPickerOpen(false);
|
|
}}
|
|
accept={accept}
|
|
maxCount={accept === 'image/*' && !multiple ? 1 : undefined}
|
|
maxImageCount={maxImageCount}
|
|
maxVideoCount={maxVideoCount}
|
|
usedImageCount={usedImageCount}
|
|
usedVideoCount={usedVideoCount}
|
|
usedVideoDuration={usedVideoDuration}
|
|
maxVideoDuration={maxVideoDuration}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default UploadSelector;
|