From 7a7ed23b8809249af1c4705523e06f9cc7f168c0 Mon Sep 17 00:00:00 2001 From: cmyymc <2215742579@qq.com> Date: Fri, 12 Dec 2025 01:23:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8D=9A=E5=AE=A2=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=A8=A1=E7=B3=8A=E6=90=9C=E7=B4=A2=20&=20?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E4=BD=93=E9=AA=8C=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端:字符级 OR 拉回候选集 + PinyinMatch 精准混写(中文/拼音/首字母) - 前端:新建通用 AppSearchBox 组件,支持防抖、ESC、回车、一键清空 - 样式:屏蔽浏览器自带 ×,DOM 数量一致,零 hydration 警告 - 交互:空输入自动加载首页,翻页保留关键词,长按 passive 事件修复 - 兼容:Firefox/Safari 无原生 ×,移动端长按功能正常 --- packages/blog/app/components/AppSearchBox.vue | 140 +++++++++++ packages/blog/app/composables/useDebounce.ts | 7 + .../blog/app/composables/useDeviceInfoCard.ts | 2 +- packages/blog/app/pages/blog/[slug].vue | 229 +++++++++--------- packages/blog/app/pages/blog/index.vue | 106 +++++--- packages/blog/package.json | 1 + packages/blog/pnpm-lock.yaml | 8 + packages/blog/server/api/blog/list.get.ts | 4 +- packages/blog/server/utils/tagService.ts | 115 +++++---- pnpm-lock.yaml | 117 +++++---- 10 files changed, 489 insertions(+), 240 deletions(-) create mode 100644 packages/blog/app/components/AppSearchBox.vue create mode 100644 packages/blog/app/composables/useDebounce.ts diff --git a/packages/blog/app/components/AppSearchBox.vue b/packages/blog/app/components/AppSearchBox.vue new file mode 100644 index 0000000..9a06bab --- /dev/null +++ b/packages/blog/app/components/AppSearchBox.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/packages/blog/app/composables/useDebounce.ts b/packages/blog/app/composables/useDebounce.ts new file mode 100644 index 0000000..e755b27 --- /dev/null +++ b/packages/blog/app/composables/useDebounce.ts @@ -0,0 +1,7 @@ +export function useDebounce void>(fn: T, delay = 300): T { + let timer: ReturnType | null = null + return ((...args: Parameters) => { + if (timer) clearTimeout(timer) + timer = setTimeout(() => fn(...args), delay) + }) as T +} diff --git a/packages/blog/app/composables/useDeviceInfoCard.ts b/packages/blog/app/composables/useDeviceInfoCard.ts index bc5582f..3fd77b0 100644 --- a/packages/blog/app/composables/useDeviceInfoCard.ts +++ b/packages/blog/app/composables/useDeviceInfoCard.ts @@ -86,7 +86,7 @@ export function useDeviceInfo() { onMounted(() => { document.addEventListener('contextmenu', onContext) - document.addEventListener('touchstart', onTouchStart, { passive: false }) + document.addEventListener('touchstart', onTouchStart, { passive: true }) document.addEventListener('touchmove', onTouchMove, { passive: true }) document.addEventListener('touchend', onTouchEnd) }) diff --git a/packages/blog/app/pages/blog/[slug].vue b/packages/blog/app/pages/blog/[slug].vue index a180071..2926059 100644 --- a/packages/blog/app/pages/blog/[slug].vue +++ b/packages/blog/app/pages/blog/[slug].vue @@ -283,129 +283,124 @@ useSeoMeta({ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); /* Markdown 内容样式 */ - :deep { - h1, - h2, - h3, - h4, - h5, - h6 { - margin-top: 2rem; - margin-bottom: 1rem; - color: var(--text); - font-weight: 600; - position: relative; - padding-bottom: 0.5rem; - - &::after { - content: ''; - position: absolute; - bottom: 0; - left: 0; - width: 60px; - height: 3px; - background: var(--primary); - border-radius: 2px; - } - } - - h1 { - font-size: 2rem; - } - h2 { - font-size: 1.75rem; - } - h3 { - font-size: 1.5rem; - } - h4 { - font-size: 1.25rem; - } - - p { - margin-bottom: 1.5rem; - font-size: 1.05rem; + :deep(h1), + :deep(h2), + :deep(h3), + :deep(h4), + :deep(h5), + :deep(h6) { + margin-top: 2rem; + margin-bottom: 1rem; + color: var(--text); + font-weight: 600; + position: relative; + padding-bottom: 0.5rem; + + &::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 60px; + height: 3px; + background: var(--primary); + border-radius: 2px; } + } - ul, - ol { - padding-left: 1.5rem; - margin-bottom: 1.5rem; - - li { - margin-bottom: 0.5rem; + /* 单独字号 */ + :deep(h1) { + font-size: 2rem; + } + :deep(h2) { + font-size: 1.75rem; + } + :deep(h3) { + font-size: 1.5rem; + } + :deep(h4) { + font-size: 1.25rem; + } - &::marker { - color: var(--primary); - } - } - } + /* 其余元素同样写法 */ + :deep(p) { + margin-bottom: 1.5rem; + font-size: 1.05rem; + } - a { - color: var(--primary); - text-decoration: none; - border-bottom: 1px solid transparent; - transition: all 0.3s ease; + :deep(ul), + :deep(ol) { + padding-left: 1.5rem; + margin-bottom: 1.5rem; - &:hover { - border-bottom-color: var(--primary); + li { + margin-bottom: 0.5rem; + &::marker { + color: var(--primary); } } + } - img { - max-width: 100%; - border-radius: 12px; - margin: 2rem 0; - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); - } - - pre { - background: rgba(var(--text), 0.05); - border-radius: 12px; - padding: 1.5rem; - margin: 1.5rem 0; - overflow-x: auto; - border: 1px solid var(--card-border); - - code { - background: transparent; - padding: 0; - border-radius: 0; - } + :deep(a) { + color: var(--primary); + text-decoration: none; + border-bottom: 1px solid transparent; + transition: all 0.3s ease; + &:hover { + border-bottom-color: var(--primary); } + } - code:not(pre code) { - background: rgba(var(--primary), 0.1); - color: var(--primary); - padding: 0.2rem 0.4rem; - border-radius: 4px; - font-size: 0.9em; - } + :deep(img) { + max-width: 100%; + border-radius: 12px; + margin: 2rem 0; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); + } - blockquote { - border-left: 4px solid var(--primary); - margin: 1.5rem 0; - padding: 0.5rem 0 0.5rem 1.5rem; - background: rgba(var(--primary), 0.05); - border-radius: 0 8px 8px 0; - font-style: italic; + :deep(pre) { + background: rgba(var(--text), 0.05); + border-radius: 12px; + padding: 1.5rem; + margin: 1.5rem 0; + overflow-x: auto; + border: 1px solid var(--card-border); + code { + background: transparent; + padding: 0; + border-radius: 0; } + } - table { - width: 100%; - border-collapse: collapse; - margin: 1.5rem 0; + :deep(code:not(pre code)) { + background: rgba(var(--primary), 0.1); + color: var(--primary); + padding: 0.2rem 0.4rem; + border-radius: 4px; + font-size: 0.9em; + } - th, - td { - padding: 0.75rem; - border: 1px solid var(--card-border); - } + :deep(blockquote) { + border-left: 4px solid var(--primary); + margin: 1.5rem 0; + padding: 0.5rem 0 0.5rem 1.5rem; + background: rgba(var(--primary), 0.05); + border-radius: 0 8px 8px 0; + font-style: italic; + } - th { - background: rgba(var(--primary), 0.1); - font-weight: 600; - } + :deep(table) { + width: 100%; + border-collapse: collapse; + margin: 1.5rem 0; + th, + td { + padding: 0.75rem; + border: 1px solid var(--card-border); + } + th { + background: rgba(var(--primary), 0.1); + font-weight: 600; } } } @@ -458,16 +453,14 @@ useSeoMeta({ .post-content { padding: 1.5rem; - :deep { - h1 { - font-size: 1.75rem; - } - h2 { - font-size: 1.5rem; - } - h3 { - font-size: 1.25rem; - } + :deep(h1) { + font-size: 1.75rem; + } + :deep(h2) { + font-size: 1.5rem; + } + :deep(h3) { + font-size: 1.25rem; } } } diff --git a/packages/blog/app/pages/blog/index.vue b/packages/blog/app/pages/blog/index.vue index 4e3fd8d..38ca796 100644 --- a/packages/blog/app/pages/blog/index.vue +++ b/packages/blog/app/pages/blog/index.vue @@ -1,21 +1,42 @@