首页素材案例弹窗修改

This commit is contained in:
sjy
2026-07-10 14:20:49 +08:00
parent d99062ca98
commit f2f8625425
9 changed files with 383 additions and 110 deletions
@@ -0,0 +1,199 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { PlayCircleOutlined, PauseCircleOutlined, SoundOutlined, CloseCircleOutlined } from '@ant-design/icons';
interface CustomAudioPlayerProps {
src: string;
size?: 'small' | 'normal';
}
const CustomAudioPlayer: React.FC<CustomAudioPlayerProps> = ({ src, size = 'normal' }) => {
const audioRef = useRef<HTMLAudioElement>(null);
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const [volume, setVolume] = useState(1);
const [isMuted, setIsMuted] = useState(false);
const formatTime = (time: number) => {
const mins = Math.floor(time / 60);
const secs = Math.floor(time % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
};
const togglePlay = useCallback(() => {
if (!audioRef.current) return;
if (isPlaying) {
audioRef.current.pause();
} else {
audioRef.current.play();
}
setIsPlaying(!isPlaying);
}, [isPlaying]);
const handleTimeUpdate = useCallback(() => {
if (!audioRef.current) return;
setCurrentTime(audioRef.current.currentTime);
}, []);
const handleLoadedMetadata = useCallback(() => {
if (!audioRef.current) return;
setDuration(audioRef.current.duration);
}, []);
const handleEnded = useCallback(() => {
setIsPlaying(false);
setCurrentTime(0);
}, []);
const handleProgressClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
if (!audioRef.current || duration === 0) return;
const rect = e.currentTarget.getBoundingClientRect();
const percent = (e.clientX - rect.left) / rect.width;
const newTime = percent * duration;
audioRef.current.currentTime = newTime;
setCurrentTime(newTime);
}, [duration]);
const handleVolumeChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
if (!audioRef.current) return;
const newVolume = parseFloat(e.target.value);
audioRef.current.volume = newVolume;
setVolume(newVolume);
setIsMuted(newVolume === 0);
}, []);
const toggleMute = useCallback(() => {
if (!audioRef.current) return;
if (isMuted) {
audioRef.current.volume = volume || 0.5;
setIsMuted(false);
} else {
audioRef.current.volume = 0;
setIsMuted(true);
}
}, [isMuted, volume]);
useEffect(() => {
const audio = audioRef.current;
if (!audio) return;
audio.addEventListener('timeupdate', handleTimeUpdate);
audio.addEventListener('loadedmetadata', handleLoadedMetadata);
audio.addEventListener('ended', handleEnded);
return () => {
audio.removeEventListener('timeupdate', handleTimeUpdate);
audio.removeEventListener('loadedmetadata', handleLoadedMetadata);
audio.removeEventListener('ended', handleEnded);
};
}, [handleTimeUpdate, handleLoadedMetadata, handleEnded]);
const progress = duration > 0 ? (currentTime / duration) * 100 : 0;
return (
<div style={{ width: '100%', height: size === 'small' ? 130 : 112, background: '#f1f5f9', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: size === 'small' ? '16px 16px' : '12px 16px' }}>
<audio ref={audioRef} src={src} />
<div style={{ display: 'flex', alignItems: 'center', gap: size === 'small' ? 16 : 16, width: '100%' }}>
<button
onClick={togglePlay}
style={{
width: size === 'small' ? 48 : 48,
height: size === 'small' ? 48 : 48,
borderRadius: '50%',
background: 'linear-gradient(135deg, #8b5cf6, #a78bfa)',
border: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
flexShrink: 0,
}}
>
{isPlaying ? (
<PauseCircleOutlined style={{ fontSize: size === 'small' ? 24 : 24, color: '#fff' }} />
) : (
<PlayCircleOutlined style={{ fontSize: size === 'small' ? 24 : 24, color: '#fff', marginLeft: size === 'small' ? 2 : 2 }} />
)}
</button>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 8 }}>
<div
onClick={handleProgressClick}
style={{
height: size === 'small' ? 5 : 5,
background: '#e2e8f0',
borderRadius: 10,
cursor: 'pointer',
overflow: 'visible',
position: 'relative',
}}
>
<div
style={{
height: '100%',
width: `${progress}%`,
background: 'linear-gradient(90deg, #8b5cf6, #a78bfa)',
borderRadius: 10,
transition: 'width 0.1s linear',
}}
/>
<div
style={{
position: 'absolute',
left: `${progress}%`,
top: '50%',
transform: 'translate(-50%, -50%)',
width: size === 'small' ? 12 : 14,
height: size === 'small' ? 12 : 14,
background: '#fff',
borderRadius: '50%',
boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
opacity: progress > 0 ? 1 : 0,
transition: 'opacity 0.2s',
}}
/>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontSize: size === 'small' ? 11 : 12, color: '#64748b' }}>
{formatTime(currentTime)}
</span>
<span style={{ fontSize: size === 'small' ? 11 : 12, color: '#64748b' }}>
{formatTime(duration)}
</span>
</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
<button
onClick={toggleMute}
style={{ border: 'none', background: 'transparent', cursor: 'pointer', padding: 0 }}
>
{isMuted ? (
<CloseCircleOutlined style={{ fontSize: size === 'small' ? 14 : 16, color: '#94a3b8' }} />
) : (
<SoundOutlined style={{ fontSize: size === 'small' ? 14 : 16, color: '#94a3b8' }} />
)}
</button>
<input
type="range"
min="0"
max="1"
step="0.1"
value={isMuted ? 0 : volume}
onChange={handleVolumeChange}
style={{
width: size === 'small' ? 40 : 50,
height: 4,
borderRadius: 2,
appearance: 'none',
background: '#e2e8f0',
cursor: 'pointer',
}}
/>
</div>
</div>
</div>
);
};
export default CustomAudioPlayer;
@@ -3,6 +3,7 @@ import { AudioOutlined, DeleteOutlined, DownloadOutlined, EyeOutlined, PictureOu
import { Button, Checkbox, Empty, Input, message, Modal, Pagination, Select, Space, Spin, Tag, Typography } from 'antd';
import { deleteUpload, deleteUploadResourceHistoryBatch, getUploadResourceHistory, getUploadResourceHistoryItems } from '../../api';
import type { UploadResourceHistoryDayGroup, UploadResourceHistoryItem } from '../../types';
import CustomAudioPlayer from './CustomAudioPlayer';
const { Text } = Typography;
@@ -167,7 +168,7 @@ const UploadResourceHistoryPanel: React.FC = () => {
: { width: '100%', maxHeight: 620, objectFit: 'contain' as const };
if (item.resourceType === 'image') return <img src={url} alt={item.fileName || item.id} style={style} />;
if (item.resourceType === 'video') return <video src={url} controls={size === 'preview'} muted={size === 'card'} style={style} />;
return <audio src={url} controls style={{ width: '100%' }} />;
return <CustomAudioPlayer src={url} size={size === 'card' ? 'small' : 'normal'} />;
};
return (
@@ -1,8 +1,9 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { AudioOutlined, CheckOutlined, PictureOutlined, ReloadOutlined, SearchOutlined, VideoCameraOutlined } from '@ant-design/icons';
import { Button, Empty, Input, message, Modal, Pagination, Select, Space, Spin, Tag, Typography } from 'antd';
import { getUploadResourceHistoryItems ,getUploadResourceHistory} from '../../api';
import { getUploadResourceHistoryItems, getUploadResourceHistory } from '../../api';
import type { UploadResourceHistoryItem } from '../../types';
import CustomAudioPlayer from './CustomAudioPlayer';
const { Text } = Typography;
@@ -326,15 +327,13 @@ const UploadResourceHistoryPicker: React.FC<UploadResourceHistoryPickerProps> =
boxShadow: checked ? '0 10px 24px rgba(139, 92, 246, 0.18)' : '0 6px 18px rgba(15,23,42,0.06)',
}}
>
<div style={{ height: 112, background: '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{ background: '#f1f5f9' }}>
{item.resourceType === 'image' ? (
<img src={preview} alt={item.fileName || item.id} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
<img src={preview} alt={item.fileName || item.id} style={{ width: '100%', height: 130, objectFit: 'cover' }} />
) : item.resourceType === 'video' ? (
<video src={preview} muted style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
<video src={preview} muted style={{ width: '100%', height: 130, objectFit: 'cover' }} />
) : (
<div style={{ width: 58, height: 58, borderRadius: 18, background: 'linear-gradient(135deg, #8b5cf6, #a78bfa)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: 24 }}>
<AudioOutlined />
</div>
<CustomAudioPlayer src={preview} size="small" />
)}
</div>
<div style={{ padding: 10 }}>