mirror of
https://github.com/remvze/moodist.git
synced 2025-12-18 09:24:14 +00:00
feat: 重构音乐列表UI布局与模块分离优化
- 重命名"当前选中音乐"为"当前选中的声音" - 完全分离两个模块为独立UI组件,移除互斥展开逻辑 - 优化音乐列表横向布局:音乐名称、声音名称和按钮在同一行显示 - 实现智能展开逻辑:音乐列表默认展开,超过5项时自动收起 - 增加模块间32px间距,提升视觉层次 - 修复展开按钮样式冲突,优化CSS类名结构 - 改进组件状态管理,确保模块独立性
This commit is contained in:
parent
b477733188
commit
a76585c61a
5 changed files with 219 additions and 150 deletions
|
|
@ -14,7 +14,8 @@ interface LanguageSwitcherProps {
|
|||
export function LanguageSwitcher({ className }: LanguageSwitcherProps) {
|
||||
const { currentLang, changeLanguage, t } = useTranslation();
|
||||
const { isAuthenticated, user, login, register, logout, isLoading, checkAuth, error, clearError } = useAuthStore();
|
||||
const [isDarkTheme, setIsDarkTheme] = useState<boolean>(false);
|
||||
const [isDarkTheme, setIsDarkTheme] = useState<boolean | null>(null); // 使用 null 表示未初始化
|
||||
const [isClient, setIsClient] = useState(false); // 跟踪是否在客户端
|
||||
const [showAuthForm, setShowAuthForm] = useState(false);
|
||||
const [isLogin, setIsLogin] = useState(true);
|
||||
const [showNotification, setShowNotification] = useState(false);
|
||||
|
|
@ -26,10 +27,17 @@ export function LanguageSwitcher({ className }: LanguageSwitcherProps) {
|
|||
password: '',
|
||||
});
|
||||
|
||||
// 客户端检测
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
// 认证状态检查
|
||||
useEffect(() => {
|
||||
checkAuth();
|
||||
}, []);
|
||||
if (isClient) {
|
||||
checkAuth();
|
||||
}
|
||||
}, [isClient]);
|
||||
|
||||
// 点击外部关闭用户菜单
|
||||
useEffect(() => {
|
||||
|
|
@ -63,8 +71,13 @@ export function LanguageSwitcher({ className }: LanguageSwitcherProps) {
|
|||
};
|
||||
}, []);
|
||||
|
||||
// 主题切换逻辑
|
||||
// 主题切换逻辑 - 确保只在客户端执行
|
||||
useEffect(() => {
|
||||
// 避免在 SSR 环境下执行
|
||||
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const initialDarkTheme = savedTheme ? savedTheme === 'dark' : systemPrefersDark;
|
||||
|
|
@ -114,6 +127,11 @@ export function LanguageSwitcher({ className }: LanguageSwitcherProps) {
|
|||
};
|
||||
|
||||
const toggleTheme = () => {
|
||||
// 确保主题已初始化且在客户端环境
|
||||
if (isDarkTheme === null || typeof window === 'undefined' || typeof localStorage === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const newTheme = !isDarkTheme;
|
||||
setIsDarkTheme(newTheme);
|
||||
applyTheme(newTheme);
|
||||
|
|
@ -203,23 +221,26 @@ export function LanguageSwitcher({ className }: LanguageSwitcherProps) {
|
|||
<button
|
||||
className={styles.controlButton}
|
||||
onClick={toggleTheme}
|
||||
aria-label={isDarkTheme ? 'Switch to light mode' : 'Switch to dark mode'}
|
||||
aria-label={isDarkTheme === true ? 'Switch to light mode' : isDarkTheme === false ? 'Switch to dark mode' : 'Loading theme'}
|
||||
disabled={isDarkTheme === null}
|
||||
>
|
||||
<AnimatePresence initial={false} mode="wait">
|
||||
<motion.span
|
||||
animate="show"
|
||||
aria-hidden="true"
|
||||
exit="hidden"
|
||||
initial="hidden"
|
||||
key={isDarkTheme ? 'moon' : 'sun'}
|
||||
variants={variants}
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
>
|
||||
{isDarkTheme ? <FaMoon /> : <FaSun />}
|
||||
</motion.span>
|
||||
</AnimatePresence>
|
||||
{isDarkTheme !== null && (
|
||||
<AnimatePresence initial={false} mode="wait">
|
||||
<motion.span
|
||||
animate="show"
|
||||
aria-hidden="true"
|
||||
exit="hidden"
|
||||
initial="hidden"
|
||||
key={isDarkTheme ? 'moon' : 'sun'}
|
||||
variants={variants}
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
>
|
||||
{isDarkTheme ? <FaMoon /> : <FaSun />}
|
||||
</motion.span>
|
||||
</AnimatePresence>
|
||||
)}
|
||||
<span style={{ marginLeft: '8px', fontSize: '14px' }}>
|
||||
{isDarkTheme ? '黑暗' : '明亮'}
|
||||
{isDarkTheme === null ? '...' : (isDarkTheme ? '黑暗' : '明亮')}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
|
|
@ -229,10 +250,10 @@ export function LanguageSwitcher({ className }: LanguageSwitcherProps) {
|
|||
onClick={handleAuthClick}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
aria-label={isAuthenticated ? `用户: ${user?.username}` : '登录'}
|
||||
aria-label={isClient ? (isAuthenticated ? `用户: ${user?.username}` : '登录') : '登录'}
|
||||
>
|
||||
<FaUser />
|
||||
{isAuthenticated && user && (
|
||||
{isClient && isAuthenticated && user && (
|
||||
<span className={styles.userIndicator}></span>
|
||||
)}
|
||||
<span style={{
|
||||
|
|
@ -243,7 +264,7 @@ export function LanguageSwitcher({ className }: LanguageSwitcherProps) {
|
|||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
}}>
|
||||
{isAuthenticated ? (user?.username || '用户') : '登录'}
|
||||
{isClient ? (isAuthenticated ? (user?.username || '用户') : '登录') : '登录'}
|
||||
</span>
|
||||
</motion.button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ export function SelectedSoundsDisplay() {
|
|||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [editingName, setEditingName] = useState('');
|
||||
const [expandedMusic, setExpandedMusic] = useState<Set<number>>(new Set()); // 跟踪展开的音乐项
|
||||
const [expandedCurrent, setExpandedCurrent] = useState(true); // 跟踪当前选中音乐的展开状态,默认展开
|
||||
const [expandedMyMusic, setExpandedMyMusic] = useState(false); // 跟踪我的音乐展开状态,默认收起
|
||||
const [expandedCurrent, setExpandedCurrent] = useState(true); // 跟踪当前选中声音的展开状态,默认展开
|
||||
const [expandedMyMusic, setExpandedMyMusic] = useState(true); // 跟踪音乐列表展开状态,默认展开
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [musicName, setMusicName] = useState('');
|
||||
|
||||
|
|
@ -50,24 +50,18 @@ export function SelectedSoundsDisplay() {
|
|||
Object.keys(state.sounds).filter(id => state.sounds[id].isSelected)
|
||||
);
|
||||
|
||||
// 互斥展开逻辑:展开当前音乐时收起我的音乐,反之亦然
|
||||
// 独立展开逻辑:两个区域可以独立展开/收起
|
||||
const toggleExpandedCurrent = () => {
|
||||
if (expandedCurrent) {
|
||||
setExpandedCurrent(false);
|
||||
} else {
|
||||
setExpandedCurrent(true);
|
||||
setExpandedMyMusic(false);
|
||||
setExpandedMusic(new Set()); // 收起所有展开的音乐项
|
||||
setExpandedCurrent(!expandedCurrent);
|
||||
if (!expandedCurrent) {
|
||||
setExpandedMusic(new Set()); // 展开时收起所有展开的音乐项
|
||||
}
|
||||
};
|
||||
|
||||
const toggleExpandedMyMusic = () => {
|
||||
if (expandedMyMusic) {
|
||||
setExpandedMyMusic(false);
|
||||
} else {
|
||||
setExpandedMyMusic(true);
|
||||
setExpandedCurrent(false);
|
||||
setExpandedMusic(new Set()); // 收起所有展开的音乐项
|
||||
setExpandedMyMusic(!expandedMyMusic);
|
||||
if (!expandedMyMusic) {
|
||||
setExpandedMusic(new Set()); // 展开时收起所有展开的音乐项
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -118,13 +112,21 @@ export function SelectedSoundsDisplay() {
|
|||
// 根据选中的声音ID获取声音对象
|
||||
const selectedSounds = useMemo(() => {
|
||||
return selectedSoundIds.map(id => {
|
||||
const sound = sounds[id];
|
||||
// 从 localizedCategories 中查找对应的声音数据
|
||||
const allSounds = localizedCategories
|
||||
.map(category => category.sounds)
|
||||
.flat();
|
||||
const soundData = allSounds.find(s => s.id === id);
|
||||
|
||||
if (!soundData) return null;
|
||||
|
||||
return {
|
||||
id,
|
||||
...sound
|
||||
...soundData,
|
||||
...sounds[id] // 合并状态信息(volume, speed 等)
|
||||
};
|
||||
}).filter(Boolean);
|
||||
}, [selectedSoundIds, sounds]);
|
||||
}, [selectedSoundIds, sounds, localizedCategories]);
|
||||
|
||||
// 获取音乐列表
|
||||
const fetchMusicList = async () => {
|
||||
|
|
@ -244,19 +246,28 @@ export function SelectedSoundsDisplay() {
|
|||
}
|
||||
}, [isAuthenticated, user]);
|
||||
|
||||
// 监听音乐列表数量,超过5个时默认收起
|
||||
useEffect(() => {
|
||||
if (savedMusicList.length > 5) {
|
||||
setExpandedMyMusic(false);
|
||||
} else {
|
||||
setExpandedMyMusic(true);
|
||||
}
|
||||
}, [savedMusicList.length]);
|
||||
|
||||
// 如果没有选中的声音,不渲染组件
|
||||
if (selectedSounds.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.soundsContainer}>
|
||||
{/* 当前选中音乐标题区域 */}
|
||||
{selectedSounds.length > 0 && (
|
||||
<div className={styles.musicHeader}>
|
||||
<h4 className={styles.musicTitle}>
|
||||
<div className={styles.container}>
|
||||
{/* 当前选中声音模块 */}
|
||||
<div className={styles.currentSoundsModule}>
|
||||
<div className={styles.currentSoundsHeader}>
|
||||
<h4 className={styles.currentSoundsTitle}>
|
||||
<FaMusic className={styles.musicIcon} />
|
||||
当前选中音乐
|
||||
当前选中的声音
|
||||
</h4>
|
||||
<button
|
||||
className={`${styles.expandButton} ${styles.expandButtonCurrent}`}
|
||||
|
|
@ -266,53 +277,53 @@ export function SelectedSoundsDisplay() {
|
|||
{expandedCurrent ? <FaChevronDown /> : <FaChevronRight />}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 音乐名称配置区域 */}
|
||||
{selectedSounds.length > 0 && expandedCurrent && (
|
||||
<div className={styles.musicNameConfig}>
|
||||
<input
|
||||
type="text"
|
||||
value={musicName}
|
||||
onChange={(e) => setMusicName(e.target.value)}
|
||||
placeholder="音乐名称"
|
||||
className={styles.musicNameInput}
|
||||
maxLength={50}
|
||||
/>
|
||||
<SaveMusicButton />
|
||||
<DeleteMusicButton />
|
||||
</div>
|
||||
)}
|
||||
{/* 音乐名称配置区域 */}
|
||||
{expandedCurrent && (
|
||||
<div className={styles.musicNameConfig}>
|
||||
<input
|
||||
type="text"
|
||||
value={musicName}
|
||||
onChange={(e) => setMusicName(e.target.value)}
|
||||
placeholder="音乐名称"
|
||||
className={styles.musicNameInput}
|
||||
maxLength={50}
|
||||
/>
|
||||
<SaveMusicButton />
|
||||
<DeleteMusicButton />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 选中的声音展示 */}
|
||||
{selectedSounds.length > 0 && expandedCurrent && (
|
||||
<div className={styles.sounds}>
|
||||
<AnimatePresence initial={false}>
|
||||
{selectedSounds.map((sound) => (
|
||||
<Sound
|
||||
key={sound.id}
|
||||
id={sound.id}
|
||||
icon={sound.icon}
|
||||
label={sound.label}
|
||||
src={sound.src}
|
||||
functional={false}
|
||||
displayMode={true}
|
||||
hidden={false}
|
||||
selectHidden={() => {}}
|
||||
unselectHidden={() => {}}
|
||||
/>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)}
|
||||
{/* 选中的声音展示 */}
|
||||
{expandedCurrent && (
|
||||
<div className={styles.sounds}>
|
||||
<AnimatePresence initial={false}>
|
||||
{selectedSounds.map((sound) => (
|
||||
<Sound
|
||||
key={sound.id}
|
||||
id={sound.id}
|
||||
icon={sound.icon}
|
||||
label={sound.label}
|
||||
src={sound.src}
|
||||
functional={false}
|
||||
displayMode={true}
|
||||
hidden={false}
|
||||
selectHidden={() => {}}
|
||||
unselectHidden={() => {}}
|
||||
/>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 音乐列表区域 - 只有登录用户才显示 */}
|
||||
{isAuthenticated && (
|
||||
<div className={styles.musicSection}>
|
||||
{/* 音乐列表模块 - 只有登录用户且有音乐时才显示 */}
|
||||
{isAuthenticated && savedMusicList.length > 0 && (
|
||||
<div className={`${styles.musicListModule} ${styles.musicSection}`}>
|
||||
<div className={styles.musicHeader}>
|
||||
<h4 className={styles.musicTitle}>
|
||||
<FaCog className={styles.musicIcon} />
|
||||
我的音乐
|
||||
音乐列表
|
||||
</h4>
|
||||
<button
|
||||
className={styles.expandButton}
|
||||
|
|
@ -390,43 +401,43 @@ export function SelectedSoundsDisplay() {
|
|||
</div>
|
||||
) : (
|
||||
<div className={styles.musicContent}>
|
||||
<div className={styles.musicNameRow}>
|
||||
<div className={styles.musicInfo}>
|
||||
<div className={styles.musicName}>{music.name}</div>
|
||||
<div className={styles.soundNames}>
|
||||
{music.sounds && music.sounds.length > 0 ? (
|
||||
music.sounds.map((soundId, index) => {
|
||||
// 从所有声音中查找对应的声音名称
|
||||
const allSounds = localizedCategories
|
||||
.map(category => category.sounds)
|
||||
.flat();
|
||||
const sound = allSounds.find(s => s.id === soundId);
|
||||
return sound ? (
|
||||
<span key={soundId} className={styles.soundName}>
|
||||
{sound.label}{index < music.sounds.length - 1 ? ', ' : ''}
|
||||
</span>
|
||||
) : null;
|
||||
})
|
||||
) : (
|
||||
<span className={styles.noSounds}>暂无声音</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.musicInfo}>
|
||||
<div className={styles.musicName}>{music.name}</div>
|
||||
<div className={styles.soundNames}>
|
||||
{music.sounds && music.sounds.length > 0 ? (
|
||||
music.sounds.map((soundId, index) => {
|
||||
// 从所有声音中查找对应的声音名称
|
||||
const allSounds = localizedCategories
|
||||
.map(category => category.sounds)
|
||||
.flat();
|
||||
const sound = allSounds.find(s => s.id === soundId);
|
||||
return sound ? (
|
||||
<span key={soundId} className={styles.soundName}>
|
||||
{sound.label}{index < music.sounds.length - 1 ? ', ' : ''}
|
||||
</span>
|
||||
) : null;
|
||||
})
|
||||
) : (
|
||||
<span className={styles.noSounds}>暂无声音</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => deleteMusic(music.id.toString())}
|
||||
className={styles.deleteButton}
|
||||
title="删除"
|
||||
>
|
||||
<FaTrash />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => toggleMusicExpansion(music.id)}
|
||||
className={styles.expandButton}
|
||||
title="展开/收起声音详情"
|
||||
>
|
||||
{expandedMusic.has(music.id) ? '收起 ▲' : '展开 ▼'}
|
||||
</button>
|
||||
<div className={styles.musicActions}>
|
||||
<button
|
||||
onClick={() => deleteMusic(music.id.toString())}
|
||||
className={styles.deleteButton}
|
||||
title="删除"
|
||||
>
|
||||
<FaTrash />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => toggleMusicExpansion(music.id)}
|
||||
className={styles.expandButton}
|
||||
title="展开/收起声音详情"
|
||||
>
|
||||
{expandedMusic.has(music.id) ? '收起 ▲' : '展开 ▼'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -434,7 +445,7 @@ export function SelectedSoundsDisplay() {
|
|||
{expandedMusic.has(music.id) && (
|
||||
<div className={styles.expandedMusicContent}>
|
||||
{/* 播放按钮 */}
|
||||
<div className={styles.musicActions}>
|
||||
<div className={styles.expandedMusicActions}>
|
||||
<button
|
||||
onClick={() => playMusicRecord(music)}
|
||||
className={styles.playMusicButton}
|
||||
|
|
|
|||
|
|
@ -54,9 +54,10 @@ export const Sound = forwardRef<HTMLDivElement, SoundProps>(function Sound(
|
|||
[speed, rate],
|
||||
);
|
||||
|
||||
const isLoading = useLoadingStore(state => state.loaders[src]);
|
||||
const isLoading = src ? useLoadingStore(state => state.loaders[src]) : false;
|
||||
|
||||
const sound = useSound(src, { loop: true, volume: adjustedVolume, speed: actualPlaybackRate });
|
||||
// 确保 src 存在才创建声音实例
|
||||
const sound = useSound(src || '', { loop: true, volume: adjustedVolume, speed: actualPlaybackRate });
|
||||
|
||||
useEffect(() => {
|
||||
if (locked) return;
|
||||
|
|
|
|||
|
|
@ -5,11 +5,34 @@
|
|||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* 主容器 */
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px; /* 两个模块之间的间距 */
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* 当前选中声音模块 */
|
||||
.currentSoundsModule {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* 音乐列表模块 */
|
||||
.musicListModule {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.soundsContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-top: 20px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
|
|
@ -43,12 +66,30 @@
|
|||
min-height: 40px;
|
||||
}
|
||||
|
||||
/* 音乐管理区域 */
|
||||
/* 音乐列表区域基础样式 */
|
||||
.musicSection {
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding-top: 16px;
|
||||
/* 继承模块样式,不需要额外样式 */
|
||||
}
|
||||
|
||||
/* 当前选中声音标题样式 */
|
||||
.currentSoundsHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.currentSoundsTitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
/* 音乐列表标题样式 */
|
||||
.musicHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -109,10 +150,13 @@
|
|||
background: var(--component-hover);
|
||||
}
|
||||
|
||||
/* 音乐内容容器 - 新的横向布局 */
|
||||
.musicContent {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.playButton {
|
||||
|
|
@ -131,40 +175,32 @@
|
|||
background: var(--color-foreground-subtle);
|
||||
}
|
||||
|
||||
/* 音乐名称 */
|
||||
.musicName {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground);
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
min-width: 0;
|
||||
line-height: 1.2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.musicName:hover {
|
||||
background: var(--component-hover);
|
||||
}
|
||||
|
||||
/* 音乐信息容器 */
|
||||
/* 音乐信息容器 - 左侧信息 */
|
||||
.musicInfo {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
/* 音乐名称行 */
|
||||
.musicNameRow {
|
||||
/* 音乐操作按钮容器 - 右侧按钮 */
|
||||
.musicActions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 声音名字显示 */
|
||||
|
|
@ -552,7 +588,7 @@
|
|||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.musicActions {
|
||||
.expandedMusicActions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export function useSound(
|
|||
const sound = useMemo<Howl | null>(() => {
|
||||
let sound: Howl | null = null;
|
||||
|
||||
if (isBrowser) {
|
||||
if (isBrowser && src) {
|
||||
sound = new Howl({
|
||||
html5,
|
||||
onload: () => {
|
||||
|
|
@ -46,7 +46,7 @@ export function useSound(
|
|||
setHasLoaded(true);
|
||||
},
|
||||
preload: options.preload ?? false,
|
||||
src: src,
|
||||
src: [src], // Howler.js 期望 src 是数组格式
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue