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