feat(ui): 重构 SearchBar、集成 useScrollTitle 标题滚动显示、修复专辑搜索跳转

- 重新设计 SearchBar:左侧 Tab(播放列表/MV/排行榜)+ 滑动指示器 + 搜索框自动展开收缩
- 新增 navTitle store 和 useScrollTitle hook,支持页面滚动后在 SearchBar 显示标题
- 集成 useScrollTitle 到 MusicListPage、歌手详情、关注/粉丝列表、搜索结果页
- 修复搜索结果页专辑点击跳转失败(缺失 type 字段)
- 新增 5 种语言 searchBar tab i18n 键值
This commit is contained in:
alger
2026-03-15 14:11:59 +08:00
parent 067868f786
commit 57a441312f
15 changed files with 1103 additions and 498 deletions
+58
View File
@@ -0,0 +1,58 @@
import { isRef, onMounted, onUnmounted, Ref, watch } from 'vue';
import { useNavTitleStore } from '@/store/modules/navTitle';
/**
* 页面标题滚动监听 hook
*
* 当 titleEl 元素滚出视口时,在 SearchBar 中显示标题。
* 滚回视口后自动隐藏 SearchBar 中的标题。
*
* @param title 标题文本(字符串或 Ref<string>
* @param titleEl 需要被监听的标题 DOM 元素(通常是页面 h1/h2)
*
* @example
* // 在页面组件中:
* const titleRef = ref<HTMLElement | null>(null);
* useScrollTitle('歌单名称', titleRef);
* // 或响应式标题:
* useScrollTitle(computed(() => playlist.value?.name ?? ''), titleRef);
*/
export function useScrollTitle(title: string | Ref<string>, titleEl: Ref<HTMLElement | null>) {
const store = useNavTitleStore();
let observer: IntersectionObserver | null = null;
const setupObserver = (el: HTMLElement) => {
observer?.disconnect();
observer = new IntersectionObserver(([entry]) => store.setVisible(!entry.isIntersecting), {
threshold: 0,
rootMargin: '-56px 0px 0px 0px'
});
observer.observe(el);
};
onMounted(() => {
// 设置初始标题
store.setTitle(isRef(title) ? title.value : title);
// 等待 DOM 就绪后启动 observer
if (titleEl.value) {
setupObserver(titleEl.value);
}
});
// 响应式标题:当 Ref<string> 变化时同步更新 store
if (isRef(title)) {
watch(title, (v) => store.setTitle(v));
}
// titleEl 延迟挂载时补充 observer
watch(titleEl, (el) => {
if (el) setupObserver(el);
});
onUnmounted(() => {
observer?.disconnect();
store.clear();
});
}