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

refactor: improve code comments for better clarity and maintainability

This commit is contained in:
2026-01-14 22:40:18 +08:00
parent 01d2ecba5b
commit d5d7b34814
17 changed files with 105 additions and 153 deletions

View File

@@ -8,35 +8,34 @@ import { useScreenWidthStore } from "../stores/screenWidth";
import { handleTabNavigation } from "../utils/tabNavigation";
import { useRoute } from "vitepress";
// 初始化 store 实例
/** 初始化 store 实例 */
const searchStateStore = useSearchStateStore();
const screenWidthStore = useScreenWidthStore();
const postsStore = usePostStore();
// 从 posts store 中解构出 posts 响应式数据
/** 解构出 posts 响应式数据 */
const { posts } = storeToRefs(postsStore);
// 初始化路由实例
/** 初始化路由实例 */
const route = useRoute();
// DOM 元素引用
/** DOM 元素引用 */
const scrollTarget = ref<HTMLElement | null>(null); // 滚动容器的 DOM 引用
const appbar = ref<HTMLElement | null>(null); // AppBar 自身的 DOM 引用
const searchInput = ref<HTMLInputElement | null>(null); // 搜索输入框的 DOM 引用
// 本地响应式状态
/** 本地响应式状态 */
const query = ref(""); // 搜索输入框的绑定值
const isHidden = ref(false); // 控制 AppBar 是否隐藏的状态
// 使用 useGlobalScroll
const { scrollResult, isScrolled: globalIsScrolled } = useGlobalScroll({ threshold: 100 });
const { y, directions } = scrollResult;
// 计算属性
const isScrolled = computed(() => globalIsScrolled.value); // 使用 useGlobalScroll 的 isScrolled
/** 计算属性 */
const isScrolled = computed(() => globalIsScrolled.value);
const isTabFocusable = computed(() => !screenWidthStore.isAboveBreakpoint); // 判断当前屏幕宽度下,元素是否应该可被 Tab 键聚焦
// 工具函数:获取滚动容器
/** 获取滚动容器 */
const getScrollContainer = () => document.querySelector<HTMLElement>(".content-flow");
/**
@@ -148,7 +147,7 @@ watch(
}
);
// 检查是否在搜索激活状态
/** 检查是否在搜索激活状态 */
const isSearchActive = computed(() => searchStateStore.isSearchActive);
/**
@@ -204,7 +203,7 @@ const handleKeydown = (event: KeyboardEvent) => {
}
};
// 监听路由变化,处理页面切换后的状态重置和滚动绑定
/** 处理页面切换后的状态重置和滚动绑定 */
watch(
() => route.path,
async () => {
@@ -225,25 +224,25 @@ watch(
}
);
// 事件处理函数引用(用于清理)
/** 事件处理函数引用 */
const eventHandlers = {
click: handleDocumentClick,
keydown: handleKeydown,
} as const;
// 添加事件监听器
/** 添加事件监听器 */
const addEventListeners = () => {
document.addEventListener("click", eventHandlers.click);
document.addEventListener("keydown", eventHandlers.keydown);
};
// 移除事件监听器
/** 移除事件监听器 */
const removeEventListeners = () => {
document.removeEventListener("click", eventHandlers.click);
document.removeEventListener("keydown", eventHandlers.keydown);
};
// 初始化函数
/** 初始化函数 */
const initializeAppBar = () => {
screenWidthStore.init();
@@ -257,7 +256,7 @@ const initializeAppBar = () => {
addEventListeners();
};
// 清理函数
/** 清理函数 */
const cleanupAppBar = () => {
// 移除事件监听器
removeEventListeners();
@@ -324,7 +323,6 @@ onUnmounted(() => {
</div>
</div>
</div>
<!-- <p class="description" v-if="post.description">{{ post.description }}</p> -->
</a>
</div>

View File

@@ -32,9 +32,7 @@ const remainingTime = ref(config.duration);
const isFastForwarding = ref(false);
const isAnimating = ref(false);
/**
* 计算当前应该显示的文章印象图列表
*/
/** 计算当前应该显示的文章印象图列表 */
const rawImgList = computed<string[]>(() => {
const imp = frontmatter.value.impression;
const list = Array.isArray(imp) ? imp : imp ? [imp] : [theme.value.defaultImpression];
@@ -45,18 +43,14 @@ const totalCount = computed(() => rawImgList.value.length);
const hasMultiple = computed(() => totalCount.value > 1);
const animDuration = computed(() => (isFastForwarding.value ? config.animFast : config.animNormal));
/**
* 计算环形进度条百分比
*/
/** 计算环形进度条百分比 */
const progress = computed(() => {
if (!hasMultiple.value) return 0;
if (isFastForwarding.value) return 100;
return ((config.duration - remainingTime.value) / config.duration) * 100;
});
/**
* 获取真实的索引(对总数取模)
*/
/** 获取真实的索引(对总数取模) */
const currentRealIndex = computed(() => {
if (totalCount.value === 0) return 0;
return ((virtualIndex.value % totalCount.value) + totalCount.value) % totalCount.value;

View File

@@ -3,9 +3,6 @@ import { ref, computed, onMounted, nextTick, watch } from "vue";
import { useWindowSize, useEventListener, useVModel } from "@vueuse/core";
import { handleTabNavigation } from "../utils/tabNavigation";
/**
* 组件属性定义
*/
interface Props {
images: string[];
currentIndex: number;
@@ -54,9 +51,7 @@ const currentImage = computed(() => props.images[activeIndex.value]);
const hasPrevious = computed(() => activeIndex.value > 0);
const hasNext = computed(() => activeIndex.value < props.images.length - 1);
/**
* 计算图片从文章位置飞出的初始变换参数
*/
/** 计算图片从文章位置飞出的初始变换参数 */
const calculateInitialTransform = () => {
const { x, y, width, height } = props.originPosition;
if (width > 0 && height > 0) {
@@ -72,17 +67,13 @@ const calculateInitialTransform = () => {
}
};
/**
* 重置缩放与平移位置
*/
/** 重置缩放与平移位置 */
const resetZoom = () => {
imageScale.value = 1;
imagePosition.value = { x: 0, y: 0 };
};
/**
* 显示查看器,并记录当前焦点
*/
/** 显示查看器,并记录当前焦点 */
const show = () => {
// 立即记录当前活跃元素
lastActiveElement.value = document.activeElement as HTMLElement;
@@ -100,9 +91,7 @@ const show = () => {
});
};
/**
* 隐藏查看器,并还原焦点
*/
/** 隐藏查看器,并还原焦点 */
const hide = () => {
calculateInitialTransform();
isAnimating.value = false;

View File

@@ -129,7 +129,7 @@ function onAnimationEnd(el: EventTarget | null) {
}
}
// 监听状态变化,手动触发宽度计算
/** 监听状态变化,触发宽度计算 */
watch(
() => [navStateStore.isNavExpanded],
() => {

View File

@@ -38,17 +38,13 @@ const articleId = computed(() => {
const shortLink = computed(() => (articleId.value ? `/p/${articleId.value}` : ""));
/**
* 复制短链到剪贴板
*/
/** 复制短链到剪贴板 */
const copyShortLink = async () => {
if (!shortLink.value) return;
await copyToClipboard(`${window.location.origin}${shortLink.value}`);
};
/**
* 收集页面中的 h1 和 h2 标题
*/
/** 收集页面中的 h1 和 h2 标题 */
const collectHeadings = () => {
if (!isClient()) return;
const nodes = Array.from(document.querySelectorAll("h1[id], h2[id]")) as HTMLElement[];
@@ -59,9 +55,7 @@ const collectHeadings = () => {
}));
};
/**
* 更新指示器(高亮块)的位置和尺寸
*/
/** 更新指示器(高亮边框)的位置和尺寸 */
const updateIndicator = () => {
const container = pageIndicator.value;
const id = headingsActiveId.value;

View File

@@ -107,6 +107,5 @@ const next = computed(() => {
<style lang="scss" scoped>
@use "sass:meta";
/* 引用现有的导航组件样式 */
@include meta.load-css("../styles/components/PrevNext");
</style>

View File

@@ -6,9 +6,7 @@
import { onMounted, onBeforeUnmount } from "vue";
import { isClient } from "../utils/env";
/**
* 元素宽度观察器配置
*/
/** 元素宽度观察器配置 */
interface ElementWidthObserverConfig {
/** CSS选择器 */
selector: string;
@@ -22,9 +20,7 @@ interface ElementWidthObserverConfig {
ignoreParentLimit?: boolean;
}
/**
* 生成有效的CSS变量名
*/
/** 生成有效的CSS变量名 */
function generateVariableName(selector: string): string {
// 移除特殊字符,用连字符连接
let name = selector
@@ -44,9 +40,7 @@ function generateVariableName(selector: string): string {
return name + "-width";
}
/**
* 格式化宽度值,保留指定精度
*/
/** 格式化宽度值,保留指定精度 */
function formatWidth(width: number, precision: number = 2): string {
// 使用toFixed确保精度但移除不必要的尾随零
const fixed = width.toFixed(precision);
@@ -54,9 +48,7 @@ function formatWidth(width: number, precision: number = 2): string {
return parseFloat(fixed).toString();
}
/**
* 设置元素宽度CSS变量到父级元素
*/
/** 设置元素宽度CSS变量到父级元素 */
export function setupWidthObserver(config: ElementWidthObserverConfig, targetElements?: HTMLElement[]): () => void {
const { selector, variableName, parentSelector, precision = 2, ignoreParentLimit = false } = config;
@@ -148,10 +140,3 @@ export function useElementWidthObserver(configs: ElementWidthObserverConfig[]) {
cleanupFunctions.forEach((cleanup) => cleanup());
});
}
/**
* 简化的单元素宽度观察器
*/
export function useSingleElementWidthObserver(config: ElementWidthObserverConfig) {
return useElementWidthObserver([config]);
}

View File

@@ -1,3 +1,7 @@
/**
* 再包装的全局数据
*/
import { useData } from "vitepress";
export function useGlobalData() {

View File

@@ -1,8 +1,12 @@
import { ref, computed, watch, onMounted, onUnmounted } from "vue";
/**
* 适用本项目的再包装 useScroll 工具函数
*/
import { ref, computed, watch, onMounted } from "vue";
import { useScroll } from "@vueuse/core";
import { isClient } from "../utils/env";
// 全局状态
/** 全局状态 */
const globalThreshold = ref(80);
const globalPrecision = ref(1);
const globalTargetScrollable = ref(".content-flow");
@@ -11,14 +15,21 @@ const globalIsScrolled = ref(false);
const globalScrollPosition = ref(0);
const globalScrollPercentage = ref(0);
// 检测可滚动容器
/**
* 容器能否滚动
* @param el 容器
*/
function isScrollable(el: HTMLElement) {
const style = window.getComputedStyle(el);
const overflowY = style.overflowY;
return overflowY === "auto" || overflowY === "scroll" || el.scrollHeight > el.clientHeight;
}
// 检测容器
/**
* 检测容器
* @param targetScrollable 目标容器
* @returns 判断通过的滚动容器
*/
function detectContainer(targetScrollable: string) {
if (!isClient()) return window;
@@ -27,7 +38,13 @@ function detectContainer(targetScrollable: string) {
return window;
}
// 计算滚动百分比
/**
* 计算滚动百分比
* @param scrollTop 顶部距离
* @param scrollContainer 滚动容器
* @param precision 浮点精度
* @returns 百分比
*/
function calculatePercentage(scrollTop: number, scrollContainer: HTMLElement | Window, precision: number): number {
try {
let scrollHeight: number, clientHeight: number;
@@ -55,13 +72,14 @@ function calculatePercentage(scrollTop: number, scrollContainer: HTMLElement | W
}
}
// 更新全局状态
/** 更新全局状态 */
function updateGlobalState(y: number, container: HTMLElement | Window, threshold: number, precision: number) {
globalScrollPosition.value = y;
globalIsScrolled.value = y > threshold;
globalScrollPercentage.value = calculatePercentage(y, container, precision);
}
/** 导出 */
export function useGlobalScroll(options?: { threshold?: number; container?: string; precision?: number }) {
const localThreshold = options?.threshold ?? globalThreshold.value;
const localPrecision = options?.precision ?? globalPrecision.value;
@@ -155,7 +173,7 @@ export function useGlobalScroll(options?: { threshold?: number; container?: stri
scrollPosition: computed(() => localScrollPosition.value),
scrollPercentage: computed(() => localScrollPercentage.value),
// 原始 useScroll 结果(用于高级用途)
// 原始 useScroll 结果
scrollResult,
// 容器引用
@@ -167,7 +185,7 @@ export function useGlobalScroll(options?: { threshold?: number; container?: stri
};
}
// 全局滚动状态
/** 全局滚动状态 */
export const globalScrollState = {
isScrolled: computed(() => globalIsScrolled.value),
threshold: computed(() => globalThreshold.value),

View File

@@ -18,9 +18,7 @@ const lastUpdatedRawTime = computed(() =>
lastUpdatedTime.value ? useDateFormat(lastUpdatedTime.value, "YYYY-MM-DD HH:mm:ss").value : ""
);
/**
* 相对时间显示配置
*/
/** 相对时间显示配置 */
const timeAgo = useTimeAgo(
computed(() => lastUpdatedTime.value || 0),
{
@@ -87,9 +85,7 @@ const openImageViewer = (index: number, event: MouseEvent) => {
showImageViewer.value = true;
};
/**
* 复制锚点链接到剪贴板
*/
/** 复制锚点链接到剪贴板 */
const handleAnchorClick = (event: MouseEvent) => {
const anchor = (event.target as HTMLElement).closest("a.title-anchor") as HTMLAnchorElement;
if (!anchor) return;
@@ -108,9 +104,7 @@ const handleAnchorClick = (event: MouseEvent) => {
}
};
/**
* 处理列表 Bullet 旋转和有序列表对齐
*/
/** 处理列表 Bullet 旋转和有序列表对齐 */
const enhanceDomStyles = () => {
if (!articleContentRef.value) return;
@@ -132,9 +126,7 @@ const enhanceDomStyles = () => {
});
};
/**
* 绑定文章图片点击监听
*/
/** 绑定文章图片点击监听 */
const bindImageEvents = () => {
articleContentRef.value?.querySelectorAll("img").forEach((img, index) => {
(img as HTMLElement).onclick = (e) => openImageViewer(index, e);

View File

@@ -8,21 +8,15 @@ import { getFormattedRandomPhrase } from "../utils/phrases";
import { useGlobalData } from "../composables/useGlobalData";
import { usePostStore } from "../stores/posts";
import { isClient } from "../utils/env";
// 导入布局组件
import ArticleLayout from "./Article.vue";
import NotFoundLayout from "./NotFound.vue";
/**
* 全局数据与路由状态
*/
/** 全局数据与路由状态 */
const { site, page, frontmatter, theme } = useGlobalData();
const route = useRoute();
const postStore = usePostStore();
const isRedirecting = ref(false);
const pageTitle = useTitle();
/** 随机问候语 */
const randomGreeting = ref(getFormattedRandomPhrase());
/** 布局映射表 */
@@ -118,9 +112,7 @@ if (isClient()) {
);
}
/**
* 路由变化监听:处理短链重定向
*/
/** 处理短链重定向 */
watch(
() => route.path,
(newPath) => {
@@ -131,9 +123,7 @@ watch(
{ immediate: true }
);
/**
* 监听首页状态变化,进入首页时更新随机问候语
*/
/** 进入首页时更新随机问候语 */
watch(
() => frontmatter.value.home,
(isHome) => {
@@ -143,9 +133,7 @@ watch(
}
);
/**
* 过渡动画钩子:进入后更新色板
*/
/** 进入后更新色板 */
function onAfterEnter() {
if (!isRedirecting.value) updatePalette();
}

View File

@@ -1,20 +1,20 @@
/**
* 侧边导航栏状态管理
*/
import { defineStore } from "pinia";
import { ref, watch } from "vue";
import { isClient } from "../utils/env";
import { getCookie, setCookie } from "../utils/cookie";
import { useScreenWidthStore } from "./screenWidth";
/**
* 侧边导航栏状态管理
*/
/** 导出 */
export const useNavStateStore = defineStore("navState", () => {
const isNavExpanded = ref<boolean>(false);
const cookieName = "nav-expanded";
const screenWidthStore = useScreenWidthStore();
/**
* 初始从 Cookie 读取状态
*/
/** 初始从 Cookie 读取状态 */
function init() {
if (!isClient()) return;
screenWidthStore.update();
@@ -51,34 +51,26 @@ export const useNavStateStore = defineStore("navState", () => {
}
}
/**
* 保存状态到 Cookie
*/
/** 保存状态到 Cookie */
function saveToCookie() {
if (!isClient()) return;
setCookie(cookieName, isNavExpanded.value.toString());
}
/**
* 展开导航栏
*/
/** 展开导航栏 */
function expand() {
if (screenWidthStore.isAboveBreakpoint) {
isNavExpanded.value = true;
}
}
/**
* 折叠导航栏
*/
/** 折叠导航栏 */
function collapse() {
isNavExpanded.value = false;
}
/**
* 切换导航栏状态
*/
/** 切换导航栏状态 */
function toggle() {
if (isNavExpanded.value) {
collapse();

View File

@@ -1,3 +1,8 @@
/**
* 文章数据获取
* 生成文章 ID格式化头部数据
*/
import { createContentLoader, type ContentData } from "vitepress";
export interface PostData {

View File

@@ -1,3 +1,7 @@
/**
* 文章数据存储处理
*/
import { defineStore } from "pinia";
import { computed, ref } from "vue";
import { data as postsData, type PostData } from "./posts.data";

View File

@@ -1,10 +1,12 @@
/**
* 屏幕宽度响应式状态管理
*/
import { defineStore } from "pinia";
import { ref, watch, onUnmounted } from "vue";
import { isClient } from "../utils/env";
/**
* 屏幕宽度响应式状态管理
*/
/** 导出 */
export const useScreenWidthStore = defineStore("screenWidth", () => {
// 响应式状态
const screenWidth = ref<number>(0);
@@ -15,9 +17,7 @@ export const useScreenWidthStore = defineStore("screenWidth", () => {
let resizeHandler: (() => void) | null = null;
let isInitialized = false;
/**
* 更新屏幕宽度状态
*/
/** 更新屏幕宽度状态 */
function update() {
if (!isClient()) return;
@@ -25,9 +25,7 @@ export const useScreenWidthStore = defineStore("screenWidth", () => {
isAboveBreakpoint.value = screenWidth.value > breakpoint.value;
}
/**
* 初始化监听器
*/
/** 初始化监听器 */
function init() {
if (!isClient() || isInitialized) return;
@@ -43,9 +41,7 @@ export const useScreenWidthStore = defineStore("screenWidth", () => {
isInitialized = true;
}
/**
* 清理监听器
*/
/** 清理监听器 */
function cleanup() {
if (resizeHandler && isClient()) {
window.removeEventListener("resize", resizeHandler);

View File

@@ -1,25 +1,23 @@
import { defineStore } from "pinia";
import { ref, watch } from "vue";
/**
* 搜索状态管理
*/
import { defineStore } from "pinia";
import { ref, watch } from "vue";
/** 导出 */
export const useSearchStateStore = defineStore("searchState", () => {
// 响应式状态
const isSearchActive = ref<boolean>(false);
const isSearchFocused = ref<boolean>(false);
const isSearchTyping = ref<boolean>(false);
/**
* 激活搜索
*/
/** 激活搜索 */
function activate() {
isSearchActive.value = true;
}
/**
* 停用搜索
*/
/** 停用搜索 */
function deactivate() {
isSearchActive.value = false;
isSearchFocused.value = false;
@@ -46,9 +44,7 @@ export const useSearchStateStore = defineStore("searchState", () => {
isSearchTyping.value = typing;
}
/**
* 切换搜索状态
*/
/** 切换搜索状态 */
function toggle() {
if (isSearchActive.value) {
deactivate();

View File

@@ -1,5 +1,5 @@
/**
* 短语库 - 用于在主页显示随机问候语
* 用于在主页显示随机问候语的短语库
*/
export interface Phrase {
@@ -23,9 +23,7 @@ export function getCurrentTimeOfDay(): "morning" | "afternoon" | "evening" {
}
}
/**
* 短语库 - 按时间段分类
*/
/** 按时间段分类 */
export const phrasesByTime: Record<"morning" | "afternoon" | "evening" | "any", Phrase[]> = {
morning: [
{ text: "早上好!新的一天开始啦", timeOfDay: "morning" },