From 4a4d07ddae066ac43dfd45e8bb8704c494dbf6b2 Mon Sep 17 00:00:00 2001 From: seaHi Date: Fri, 7 Aug 2026 14:21:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9C=A8=E7=AE=A1=E7=90=86=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E8=A1=A8=E5=8D=95=E4=B8=AD=E9=9B=86=E6=88=90=20Slug?= =?UTF-8?q?=20=E8=87=AA=E5=8A=A8=E7=94=9F=E6=88=90=E4=B8=8E=E9=98=B2?= =?UTF-8?q?=E6=8A=96=E5=A4=84=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/utils/slugify.ts | 91 ++++++++++++++++++++++++ src/routes/admin/categories/+page.svelte | 38 ++++++++-- src/routes/admin/pages/new/+page.svelte | 36 +++++++--- src/routes/admin/posts/new/+page.svelte | 37 +++++++--- src/routes/admin/series/+page.svelte | 41 +++++++++-- 5 files changed, 216 insertions(+), 27 deletions(-) create mode 100644 src/lib/utils/slugify.ts diff --git a/src/lib/utils/slugify.ts b/src/lib/utils/slugify.ts new file mode 100644 index 0000000..8a101c7 --- /dev/null +++ b/src/lib/utils/slugify.ts @@ -0,0 +1,91 @@ +/** + * 发送请求至后台 /api/slugify 接口生成英文 Slug + */ +export async function fetchSlug(text: string): Promise { + if (!text || !text.trim()) return ''; + + try { + const res = await fetch('/api/slugify', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ text }) + }); + + if (res.ok) { + const data = (await res.json()) as { slug?: string }; + return data.slug || ''; + } + } catch (err) { + console.error('获取 Slug 错误:', err); + } + return ''; +} + +/** + * 带有防抖功能的前端 Slug 自动同步处理器 + */ +export function createSlugAutoHandler(opts: { + getSlug: () => string; + setSlug: (val: string) => void; + getIsUserModified: () => boolean; + setIsUserModified: (val: boolean) => void; + delayMs?: number; +}) { + const delay = opts.delayMs ?? 400; + let timer: ReturnType | null = null; + let isGenerating = false; + + return { + /** + * 当名称/标题 Input 事件触发时调用 + */ + onNameInput(nameValue: string) { + // 若用户已经手动更改过 slug,则不自动强行覆盖 + if (opts.getIsUserModified()) return; + + if (timer) clearTimeout(timer); + + if (!nameValue.trim()) { + opts.setSlug(''); + return; + } + + timer = setTimeout(async () => { + isGenerating = true; + const generated = await fetchSlug(nameValue); + // 在异步返回后,再次确认用户期间没有手动修改 slug + if (!opts.getIsUserModified() && generated) { + opts.setSlug(generated); + } + isGenerating = false; + }, delay); + }, + + /** + * 当用户手动编辑 Slug 输入框时调用 + */ + onSlugInput(slugValue: string) { + if (slugValue.trim()) { + opts.setIsUserModified(true); + } else { + // 如果把 slug 清空了,则恢复自动生成能力 + opts.setIsUserModified(false); + } + }, + + /** + * 用户点击 🔄 重新生成按钮时强制生成 + */ + async forceGenerate(nameValue: string) { + if (!nameValue.trim()) return; + const generated = await fetchSlug(nameValue); + if (generated) { + opts.setSlug(generated); + // 标记为重置/由系统填充 + opts.setIsUserModified(false); + } + } + }; +} diff --git a/src/routes/admin/categories/+page.svelte b/src/routes/admin/categories/+page.svelte index 55a2572..b650b6b 100644 --- a/src/routes/admin/categories/+page.svelte +++ b/src/routes/admin/categories/+page.svelte @@ -1,16 +1,27 @@ @@ -43,7 +54,26 @@
- +
+ + +
diff --git a/src/routes/admin/pages/new/+page.svelte b/src/routes/admin/pages/new/+page.svelte index 98f99b2..d965a72 100644 --- a/src/routes/admin/pages/new/+page.svelte +++ b/src/routes/admin/pages/new/+page.svelte @@ -4,21 +4,29 @@ import type { ActionData } from './$types'; import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte'; + import { createSlugAutoHandler } from '$lib/utils/slugify'; + let { form }: { form: ActionData } = $props(); let title = $state(''); let slug = $state(''); let content = $state(''); let mode = $state<'edit' | 'preview'>('edit'); + let isSlugUserModified = $state(false); + + const slugHandler = createSlugAutoHandler({ + getSlug: () => slug, + setSlug: (val) => (slug = val), + getIsUserModified: () => isSlugUserModified, + setIsUserModified: (val) => (isSlugUserModified = val) + }); - // 自动从标题生成 Slug 简易逻辑 function handleTitleInput() { - if (!slug) { - slug = title - .toLowerCase() - .replace(/[^\w\u4e00-\u9fa5\s-]/g, '') - .replace(/\s+/g, '-'); - } + slugHandler.onNameInput(title); + } + + function handleSlugInput() { + slugHandler.onSlugInput(slug); } @@ -92,13 +100,25 @@