feat: init

This commit is contained in:
2025-09-05 16:44:12 +08:00
parent 85244a451e
commit 242a15c589
27 changed files with 191 additions and 168 deletions

View File

@@ -0,0 +1,148 @@
import { useEffect, useRef, useState } from "react";
import { Divider, Image, SpinLoading, Toast } from "antd-mobile";
import { VoiceIcon } from "@workspace/shared";
import dogSvg from "@/assets/translate/dog.svg";
import catSvg from "@/assets/translate/cat.svg";
import pigSvg from "@/assets/translate/pig.svg";
import { Message } from "../../../types";
import "./index.less";
interface DefinedProps {
data: Message[];
isRecording: boolean;
}
function Index(props: DefinedProps) {
const { data, isRecording } = props;
const audioRefs = useRef<{ [key: string]: HTMLAudioElement }>({});
const [isPlaying, setIsPlating] = useState(false);
const [currentPlayingId, setCurrentPlayingId] = useState<number>();
useEffect(() => {
if (isRecording) {
stopAllAudio();
}
}, [isRecording]);
const onVoiceChange = () => {
setIsPlating(!isPlaying);
};
const playAudio = (messageId: number, audioUrl: string) => {
if (isRecording) {
Toast.show("录音中,无法播放音频");
return;
}
if (currentPlayingId === messageId) {
if (audioRefs.current[messageId]) {
audioRefs.current[messageId].pause();
audioRefs.current[messageId].currentTime = 0;
}
setCurrentPlayingId(undefined);
setIsPlating(false);
return;
}
stopAllAudio();
if (!audioRefs.current[messageId]) {
audioRefs.current[messageId] = new Audio(audioUrl);
}
const audio = audioRefs.current[messageId];
audio.currentTime = 0;
audio.onended = () => {
setCurrentPlayingId(undefined);
setIsPlating(false);
};
audio.onerror = (error) => {
console.error("音频播放错误:", error);
Toast.show("音频播放失败");
setIsPlating(false);
};
audio
.play()
.then(() => {
setCurrentPlayingId(messageId);
setIsPlating(true);
})
.catch((error) => {
console.error("音频播放失败:", error);
Toast.show("音频播放失败");
});
};
const stopAllAudio = () => {
if (currentPlayingId && audioRefs.current[currentPlayingId]) {
audioRefs.current[currentPlayingId].pause();
audioRefs.current[currentPlayingId].currentTime = 0;
setIsPlating(false);
setCurrentPlayingId(undefined);
}
Object.values(audioRefs.current).forEach((audio) => {
if (!audio.paused) {
audio.pause();
audio.currentTime = 0;
}
});
};
const renderAvatar = (type?: "pig" | "cat" | "dog") => {
if (type === "pig") {
<Image src={pigSvg} width={40} height={40} fit="cover" style={{ borderRadius: 32 }} />;
}
if (type === "cat") {
return <Image src={catSvg} width={40} height={40} fit="cover" style={{ borderRadius: 32 }} />;
}
return <Image src={dogSvg} width={40} height={40} fit="cover" style={{ borderRadius: 32 }} />;
};
return (
<div className="message">
{data.map((item, index) => (
<div className="item" key={index} onClick={() => playAudio(item.id, item.audioUrl)}>
{renderAvatar(item.type)}
<div className="rig">
<div>
<span className="name">{item.name}</span>
<Divider direction="vertical" style={{ margin: "0px 8px" }} />
<span className="">{item.timestamp}</span>
</div>
<div className="voice-container">
<VoiceIcon
onChange={onVoiceChange}
isPlaying={isPlaying && currentPlayingId === item.id}
/>
<div className="time">{item.duration}''</div>
</div>
{item.isTranslating ? (
<div className="translate">
<SpinLoading color="default" style={{ "--size": "12px" }} />
<span>...</span>
</div>
) : (
<div className="translate">{item.translatedText}</div>
)}
</div>
</div>
))}
<div className="item">
<div className="avatar"></div>
<div className="rig">
<div>
<span className="name"></span>
<Divider direction="vertical" style={{ margin: "0px 8px" }} />
<span className="">15:00</span>
</div>
<div className="voice-container">
<VoiceIcon isPlaying={false} />
<div className="tips">{isRecording ? "录制中..." : "轻点麦克风录制"}</div>
</div>
</div>
</div>
</div>
);
}
export default Index;