1
0
mirror of https://github.com/sendevia/website.git synced 2026-03-05 23:32:45 +08:00

feat: refactor screen width handling to use Pinia store

This commit is contained in:
2025-11-30 20:03:10 +08:00
parent 6d60ed1c94
commit 8cdca34922
5 changed files with 101 additions and 59 deletions

View File

@@ -4,20 +4,19 @@ import { useGlobalData } from "../composables/useGlobalData";
import { useGlobalScroll } from "../composables/useGlobalScroll";
import { useAllPosts, type Post } from "../composables/useAllPosts";
import { useSearchState } from "../composables/useSearchState";
import { useScreenWidth } from "../composables/useScreenWidth";
import { useScreenWidthStore } from "../stores/screenWidth";
import { handleTabNavigation } from "../utils/tabNavigation";
const { frontmatter } = useGlobalData();
const { isScrolled } = useGlobalScroll({ threshold: 100 });
const { isSearchActive, isSearchTyping, deactivateSearch, setSearchFocus, setSearchTyping } = useSearchState();
const { isAboveBreakpoint } = useScreenWidth(840);
const screenWidthStore = useScreenWidthStore();
const isHome = computed(() => frontmatter.value.home === true);
const articlesRef = useAllPosts(true);
const query = ref("");
const appbar = ref<HTMLElement | null>(null);
const searchInput = ref<HTMLInputElement | null>(null);
const isTabFocusable = computed(() => !isAboveBreakpoint.value);
const isTabFocusable = computed(() => !screenWidthStore.isAboveBreakpoint);
// 计算过滤后的文章
const filteredPosts = computed<Post[]>(() => {
@@ -120,20 +119,14 @@ const handleKeydown = (event: KeyboardEvent) => {
}
};
const handlePopState = () => {
if (isSearchActive.value) clearSearchState();
};
onMounted(() => {
document.addEventListener("click", handleDocumentClick);
document.addEventListener("keydown", handleKeydown);
window.addEventListener("popstate", handlePopState);
});
onUnmounted(() => {
document.removeEventListener("click", handleDocumentClick);
document.removeEventListener("keydown", handleKeydown);
window.removeEventListener("popstate", handlePopState);
});
</script>

View File

@@ -1,11 +1,11 @@
<script setup lang="ts">
import { computed } from "vue";
import { useGlobalData } from "../composables/useGlobalData";
import { useScreenWidth } from "../composables/useScreenWidth";
import { useScreenWidthStore } from "../stores/screenWidth";
import { useSearchState } from "../composables/useSearchState";
const { page, theme } = useGlobalData();
const { isAboveBreakpoint } = useScreenWidth(840);
const screenWidthStore = useScreenWidthStore();
const { isSearchActive, activateSearch, deactivateSearch } = useSearchState();
// 计算导航段落
@@ -40,7 +40,7 @@ function toggleSearch(event: MouseEvent) {
</script>
<template>
<nav :class="isAboveBreakpoint ? 'rail' : 'bar'">
<nav :class="screenWidthStore.isAboveBreakpoint ? 'rail' : 'bar'">
<button class="fab" @mousedown.prevent @click.stop="toggleSearch">
<span>{{ isSearchActive ? "close" : "search" }}</span>
</button>

View File

@@ -1,10 +1,10 @@
<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch } from "vue";
import { useGlobalData } from "../composables/useGlobalData";
import { useScreenWidth } from "../composables/useScreenWidth";
import { useScreenWidthStore } from "../stores/screenWidth";
const { page, frontmatter } = useGlobalData();
const { isAboveBreakpoint: isMonitoring } = useScreenWidth(840);
const screenWidthStore = useScreenWidthStore();
const navRef = ref<HTMLElement | null>(null);
const indicator = ref({ top: "0px", left: "0px", width: "100%", height: "0px", opacity: 0 });
const headings = ref<Array<{ id: string; text: string; level: number }>>([]);
@@ -165,7 +165,7 @@ function toggleMonitoring(shouldMonitor: boolean) {
}
const resizeHandler = () => {
if (isMonitoring.value) {
if (screenWidthStore.isAboveBreakpoint) {
collectHeadings();
createObserver();
}
@@ -173,18 +173,18 @@ const resizeHandler = () => {
if (typeof window !== "undefined") {
onMounted(() => {
toggleMonitoring(isMonitoring.value);
toggleMonitoring(screenWidthStore.isAboveBreakpoint);
window.addEventListener("resize", resizeHandler);
window.addEventListener("resize", updateIndicator, { passive: true });
window.addEventListener("hashchange", () => {
if (isMonitoring.value) {
if (screenWidthStore.isAboveBreakpoint) {
collectHeadings();
createObserver();
}
});
window.addEventListener("popstate", () => {
if (isMonitoring.value) {
if (screenWidthStore.isAboveBreakpoint) {
collectHeadings();
createObserver();
}
@@ -224,14 +224,17 @@ if (typeof window !== "undefined") {
}
});
watch(isMonitoring, (newValue) => {
toggleMonitoring(newValue);
});
watch(
() => screenWidthStore.isAboveBreakpoint,
(newValue) => {
toggleMonitoring(newValue);
}
);
watch(
() => headingsActiveId.value,
() => {
if (isMonitoring.value) {
if (screenWidthStore.isAboveBreakpoint) {
nextTick(() => updateIndicator());
}
}
@@ -240,7 +243,7 @@ if (typeof window !== "undefined") {
watch(
() => grouped.value,
() => {
if (isMonitoring.value) {
if (screenWidthStore.isAboveBreakpoint) {
nextTick(() => updateIndicator());
}
}

View File

@@ -1,35 +0,0 @@
import { ref, onMounted, onBeforeUnmount } from "vue";
/**
* 响应式屏幕宽度检测组合式函数
* @param breakpoint 断点值默认为840px
* @returns 包含屏幕宽度和是否超过断点的响应式对象
*/
export function useScreenWidth(breakpoint = 840) {
const screenWidth = ref<number>(0);
const isAboveBreakpoint = ref<boolean>(true);
function updateScreenWidth() {
if (typeof window !== "undefined") {
screenWidth.value = window.innerWidth;
isAboveBreakpoint.value = screenWidth.value > breakpoint;
}
}
if (typeof window !== "undefined") {
onMounted(() => {
updateScreenWidth();
window.addEventListener("resize", updateScreenWidth);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", updateScreenWidth);
});
}
return {
screenWidth,
isAboveBreakpoint,
updateScreenWidth,
};
}

View File

@@ -0,0 +1,81 @@
import { defineStore } from "pinia";
import { ref, watch } from "vue";
/**
* 屏幕宽度响应式状态管理
*/
export const useScreenWidthStore = defineStore("screenWidth", () => {
// 响应式状态
const screenWidth = ref<number>(0);
const isAboveBreakpoint = ref<boolean>(true);
const breakpoint = ref<number>(840);
// 内部状态
let resizeHandler: (() => void) | null = null;
let isInitialized = false;
/**
* 更新屏幕宽度状态
*/
function update() {
if (typeof window !== "undefined") {
screenWidth.value = window.innerWidth;
isAboveBreakpoint.value = screenWidth.value > breakpoint.value;
}
}
/**
* 初始化监听器
*/
function init() {
if (typeof window !== "undefined" && !isInitialized) {
update();
resizeHandler = () => update();
window.addEventListener("resize", resizeHandler);
isInitialized = true;
}
}
/**
* 清理监听器
*/
function cleanup() {
if (typeof window !== "undefined" && resizeHandler) {
window.removeEventListener("resize", resizeHandler);
resizeHandler = null;
isInitialized = false;
}
}
/**
* 设置断点阈值
* @param value - 新的断点阈值
*/
function setBreakpoint(value: number) {
breakpoint.value = value;
update();
}
// 监听断点变化,自动更新状态
watch(breakpoint, () => {
update();
});
// 自动初始化
if (typeof window !== "undefined") {
init();
}
return {
// 状态
screenWidth,
isAboveBreakpoint,
breakpoint,
// 方法
update,
init,
cleanup,
setBreakpoint,
};
});