[{"data":1,"prerenderedAt":506},["ShallowReactive",2],{"content:\u002FClarity":3,"surround:\u002FClarity":500},{"id":4,"title":5,"body":6,"categories":474,"comment":476,"date":477,"description":478,"donation":476,"draft":479,"extension":480,"image":481,"meta":482,"navigation":476,"path":486,"permalink":486,"postfooter":476,"published":487,"readingTime":488,"recommend":453,"references":487,"seo":493,"sitemap":494,"stem":495,"tags":496,"type":498,"updated":487,"__hash__":499},"content\u002Fposts\u002FClarity主题深度定制指南.md","Clarity主题深度定制指南",{"type":7,"value":8,"toc":449},"minimark",[9,24,28,31,34,38,141,144,151,157,160,233,237,244,250,253,260,266,269,272,279,285,292,298,301,309,315,318,325,331,335,341,344,347,352,358,361,369,375,380,386,391,397,401,406,412,418,424,427,433,437,443],[10,11,12],"alert",{},[13,14,15,16,23],"p",{},"部分教程来自于 ",[17,18,22],"a",{"href":19,"rel":20},"https:\u002F\u002Fblog.linux-qitong.top\u002Fposts\u002F90ddf487\u002F",[21],"nofollow","栖童の小站","，我在此基础上做了些调整，感谢大佬的详细教程。",[25,26,27],"h2",{"id":27},"前言",[13,29,30],{},"持续更新中",[25,32,33],{"id":33},"页面相关",[35,36,37],"h3",{"id":37},"添加说说页面",[39,40,44,102],"tab",{":center":41,":tabs":42,":active":43},"true","[\"本地说说\",\"远程说说（适配柳神二改的Ech0）\"]","2",[45,46,48,59,68,76,82,90,96],"template",{"v-slot:tab1":47},"",[13,49,50,51,55,56],{},"在",[52,53,54],"code",{"code":54},"\u002Fapp\u002Fpages\u002F","新建",[52,57,58],{"code":58},"essay.vue",[60,61,66],"pre",{"className":62,"code":64,"language":65},[63],"language-text","\u003Cscript setup lang=\"ts\">\nimport talks from '~\u002Ftalks'\n\nconst title = '说说'\nconst description = '记录生活点滴，一些想法。'\nconst image = 'https:\u002F\u002F图片链接'\nuseSeoMeta({ title, description, ogImage: image })\n\nconst { author } = useAppConfig()\n\nconst recentTalks = [...talks]\n  .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())\n  .slice(0, 30)\n\nfunction replyTalk(content: string): void {\n  const input = document.querySelector('#twikoo .tk-input textarea')\n  if (!(input instanceof HTMLTextAreaElement)) return\n\n  if (content.trim()) {\n    const quotes = content.split('\\n').map(str => `> ${str}`)\n    input.value = `${quotes}\\n\\n`\n  } else {\n    input.value = ''\n  }\n  input.dispatchEvent(new InputEvent('input'))\n\n  const length = input.value.length\n  input.setSelectionRange(length, length)\n  input.focus()\n}\n\nfunction getEssayDate(date?: string | Date) {\n  if (!date) {\n    return ''\n  }\n  \n  const dateStr = typeof date === 'string' ? date : date.toISOString()\n  return toZdtLocaleString(dateStr, {\n    year: 'numeric',\n    month: '2-digit',\n    day: '2-digit',\n    hour: '2-digit',\n    minute: '2-digit',\n  }).replace(\u002F\\\u002F\u002Fg, '-')\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n\u003Ctemplate #aside>\n\t\u003CWidgetBlogStats \u002F>\n\t\u003CWidgetBlogTech \u002F>\n\t\u003CWidgetBlogLog \u002F>\n\u003C\u002Ftemplate>\n\n\u003CZPageBanner :title :description :image \u002F>\n\n\u003Cdiv class=\"talk-list\">\n  \u003Cdiv class=\"talk-item\" v-for=\"talk in recentTalks\" :key=\"talk.date\">\n    \u003Cdiv class=\"talk-meta\">\n      \u003CNuxtImg class=\"avatar\" :src=\"author.avatar\" :alt=\"author.name\" \u002F>\n      \u003Cdiv class=\"info\">\n        \u003Cdiv class=\"nick\">\n          {{ author.name }}\n          \u003CIcon class=\"verified\" name=\"i-material-symbols:verified\" \u002F>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"date\">{{ getEssayDate(talk.date) }}\u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"talk-content\">\n      \u003Cdiv class=\"text\" v-if=\"talk.text\" v-html=\"talk.text\">\u003C\u002Fdiv>\n      \u003Cdiv class=\"images\" v-if=\"talk.images\">\n        \u003CPic class=\"image\" v-for=\"image in talk.images\" :src=\"image\" \u002F>\n      \u003C\u002Fdiv>\n      \u003CVideoEmbed class=\"video\" v-if=\"talk.video\" v-bind=\"talk.video\" height=\"\" \u002F>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"talk-bottom\">\n      \u003Cdiv class=\"tags\">\n        \u003Cspan class=\"tag\" v-for=\"tag in talk.tags\">\n          \u003CIcon name=\"tabler:tag\" \u002F>\n          \u003Cspan>{{ tag }}\u003C\u002Fspan>\n        \u003C\u002Fspan>\n        \u003CUtilLink\n          class=\"location\"\n          v-if=\"talk.location\"\n          v-tip=\"`搜索: ${talk.location}`\"\n          :to=\"`https:\u002F\u002Fbing.com\u002Fmaps?q=${encodeURIComponent(talk.location)}`\"\n        >\n          \u003CIcon name=\"tabler:map-pin\" \u002F>\n          \u003Cspan>{{ talk.location }}\u003C\u002Fspan>\n        \u003C\u002FUtilLink>\n      \u003C\u002Fdiv>\n      \u003Cbutton class=\"comment-btn\" v-tip=\"'评论'\" @click=\"replyTalk(talk.text)\">\n        \u003CIcon name=\"tabler:brand-hipchat\" \u002F>\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\n  \u003Cdiv class=\"talk-footer\">\n    \u003Cp>仅显示最近 30 条记录\u003C\u002Fp>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\n\u003CPostComment \u002F>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.talk-list {\n  animation: float-in .2s backwards;\n  margin: 1rem;\n\n  .talk-item {\n    animation: float-in .3s backwards;\n    animation-delay: var(--delay);\n    border-radius: 8px;\n    box-shadow: 0 0 0 1px var(--c-bg-soft);\n    display: flex;\n    flex-direction: column;\n    gap: .5rem;\n    margin-bottom: 1rem;\n    padding: 1rem;\n\n    .talk-meta {\n      align-items: center;\n      display: flex;\n      gap: 10px;\n\n      .avatar {\n        border-radius: 50%;\n        box-shadow: 2px 4px 1rem var(--ld-shadow);\n        width: 3em;\n\n        @supports (corner-shape: squircle) {\n          corner-shape: superellipse(1.2);\n         }\n      }\n\n      .nick {\n        align-items: center;\n        display: flex;\n        gap: 5px;\n      }\n\n      .date {\n        color: var(--c-text-3);\n        font-family: var(--font-monospace);\n        font-size: .8rem;\n      }\n\n      .verified {\n        color: var(--c-primary);\n        font-size: 16px;\n      }\n    }\n\n    .talk-content {\n      color: var(--c-text-2);\n      display: flex;\n      flex-direction: column;\n      gap: .5rem;\n      line-height: 1.6;\n\n      :deep(a[href]) {\n        margin: -.1em -.2em;\n        padding: .1em .2em;\n        background: linear-gradient(var(--c-primary-soft), var(--c-primary-soft)) no-repeat center bottom \u002F 100% .1em;\n        color: var(--c-primary);\n        transition: all .2s;\n\n        &:hover {\n          border-radius: .3em;\n          background-size: 100% 100%;\n        }\n      }\n\n      .images {\n        display: grid;\n        gap: 8px;\n        grid-template-columns: repeat(3, 1fr);\n      }\n\n      .image {\n        border-radius: 8px;\n        overflow: hidden;\n        padding-bottom: 100%;\n        position: relative;\n\n        :deep(img) {\n          height: 100%;\n          object-fit: cover;\n          position: absolute;\n          transition: transform .3s;\n          width: 100%;\n\n          &:hover {\n            transform: scale(1.05);\n          }\n        }\n      }\n\n      .video {\n        border-radius: 8px;\n        margin: 0;\n      }\n    }\n\n    .talk-bottom {\n      align-items: center;\n      color: var(--c-text-3);\n      display: flex;\n      justify-content: space-between;\n\n      .tags {\n        display: flex;\n        font-size: .7rem;\n        gap: 4px;\n      }\n\n      .tag, .location {\n        display: flex;\n        padding: 2px 4px;\n        border-radius: 4px;\n        background-color: var(--c-bg-2);\n        align-items: center;\n        cursor: pointer;\n        transition: all .2s;\n\n        &:hover {\n          opacity: .8;\n        }\n      }\n\n      .tag .tabler-tag + * {\n        margin-left: .15em;\n      }\n\n      .location {\n        color: var(--c-primary);\n      }\n    }\n  }\n\n  .talk-footer {\n    color: var(--c-text-3);\n    font-size: 1rem;\n    margin: 2rem 0;\n    text-align: center;\n  }\n}\n\u003C\u002Fstyle>\n","text",[52,67,64],{"__ignoreMap":47},[13,69,50,70,55,73],{},[52,71,72],{"code":72},"\u002Fapp\u002Ftypes\u002F",[52,74,75],{"code":75},"talk.ts",[60,77,80],{"className":78,"code":79,"language":65},[63],"export type TalkItem = {  \n  text?: string\n  date: string\n  images?: string[]\n  video?: {\n    type?: 'raw' | 'bilibili' | 'bilibili-nano' | 'youtube' | 'douyin' | 'douyin-wide' | 'tiktok'\n    id: string\n    ratio?: string | number\n    poster?: string\n  }\n  tags?: string[]\n  location?: string\n}\n",[52,81,79],{"__ignoreMap":47},[13,83,50,84,55,87],{},[52,85,86],{"code":86},"\u002Fapp\u002F",[52,88,89],{"code":89},"talks.ts",[91,92,93],"blockquote",{},[13,94,95],{},"这个文件是更新说说内容的地方",[60,97,100],{"className":98,"code":99,"language":65},[63],"import type { TalkItem } from '~\u002Ftypes\u002Ftalk'\n\nexport default [\n  {\n    text: '这是一个包含\u003Cb>原始视频\u003C\u002Fb>的动态内容示例。\u003Cbr>现在支持使用&lt;br&gt;进行换行和使用&lt;b&gt;标签实现加粗。',\n    date: '2025-09-24 00:00',\n    video: {\n      id: 'https:\u002F\u002Fmedia.w3.org\u002F2010\u002F05\u002Fsintel\u002Ftrailer.mp4',\n      poster: 'https:\u002F\u002Flf-package-cn.feishucdn.com\u002Fobj\u002Fatsx-throne\u002Fhire-fe-prod\u002Fportal\u002Fi18n\u002Fstatic\u002Fimage\u002Fvideo-poster.d9fdf4be.jpeg'\n    },\n    tags: ['游戏'],\n    location: '天津'\n  },\n  {\n    text: '这是一个包含B站视频的示例。',\n    date: '2025-09-23 23:00',\n    video: {\n      type: 'bilibili',\n      id: 'BV1Yr421p7rW'\n    },\n    tags: ['网站'],\n    location: '天津'\n  },\n  {\n    text: '这是一个同时包含\u003Cb>视频\u003C\u002Fb>和\u003Cb>图片\u003C\u002Fb>的示例。\u003Cbr>支持多种媒体格式的展示。',\n    date: '1885-07-22 20:00',\n    images: [\n      'https:\u002F\u002F图片链接',\n      'https:\u002F\u002F图片链接',\n      'https:\u002F\u002F图片链接'\n    ],\n    video: {\n      type: 'bilibili',\n      id: 'BV1xx411c7mD'\n    },\n    tags: ['旅行'],\n    location: '成都'\n  }\n] satisfies TalkItem[]\n",[52,101,99],{"__ignoreMap":47},[45,103,104,117,123,129,135],{"v-slot:tab2":47},[105,106,109],"div",{"className":107},[108],"text-center",[13,110,111,112],{},"点击查看",[17,113,116],{"href":114,"rel":115},"https:\u002F\u002Fgithub.com\u002FLiuShen-Fork\u002FEch0",[21],"Ech0",[13,118,50,119,55,121],{},[52,120,54],{"code":54},[52,122,58],{"code":58},[60,124,127],{"className":125,"code":126,"language":65},[63],"\u003Cscript setup lang=\"ts\">\nimport type { EchoItem, EchoPage, TalkItem } from '~\u002Ftypes\u002Ftalk'\n\nconst title = '说说'\nconst description = '记录生活点滴，一些想法。'\nconst image = 'https:\u002F\u002F图片链接'\nuseSeoMeta({ title, description, ogImage: image })\n\nconst { author } = useAppConfig()\n\nconst PAGE_SIZE = 8\n\u002F** 说说源站，单条页面拼接为 `${ECHO_SITE}\u002Fecho\u002F${id}` *\u002F\nconst ECHO_SITE = 'https:\u002F\u002F说说链接'\n\u002F** 源站接口已支持跨域，浏览器直连，避免静态打包后 server route 不可用 \u002F 数据被固化 *\u002F\nconst ECHO_API = `${ECHO_SITE}\u002Fapi\u002Fecho\u002Fpage`\n\ninterface EchoResponse {\n  code: number\n  msg: string\n  data: EchoPage\n}\n\nconst talks = ref\u003CTalkItem[]>([])\nconst total = ref(0)\nconst page = ref(0)\nconst loading = ref(false)\nconst finished = ref(false)\n\nfunction mapEchoToTalk(item: EchoItem): TalkItem {\n  const talk: TalkItem = {\n    text: item.content,\n    date: new Date(item.created_at * 1000).toISOString(),\n    tags: item.tags?.map(tag => tag.name),\n    link: `${ECHO_SITE}\u002Fecho\u002F${item.id}`,\n  }\n\n  const ext = item.extension\n  if (ext?.type === 'VIDEO' && ext.payload?.videoId) {\n    const id = ext.payload.videoId\n    talk.video = \u002F^https?:\\\u002F\\\u002F\u002F.test(id)\n      ? { type: 'raw', id }\n      : { type: 'bilibili', id }\n  }\n  if (ext?.payload?.images?.length) {\n    talk.images = ext.payload.images\n  }\n\n  return talk\n}\n\nasync function fetchEcho(targetPage: number): Promise\u003CEchoPage> {\n  const res = await $fetch\u003CEchoResponse>(ECHO_API, {\n    query: { page: targetPage, size: PAGE_SIZE },\n  })\n  return res.data\n}\n\nasync function loadMore(): Promise\u003Cvoid> {\n  if (loading.value || finished.value)\n    return\n  loading.value = true\n  try {\n    const next = page.value + 1\n    const data = await fetchEcho(next)\n    const items = (data.items || []).map(mapEchoToTalk)\n    talks.value.push(...items)\n    total.value = data.total ?? talks.value.length\n    page.value = next\n    if (!items.length || talks.value.length >= total.value)\n      finished.value = true\n  }\n  finally {\n    loading.value = false\n  }\n}\n\n\u002F\u002F 静态打包（pnpm generate）后无服务端执行环境，首屏数据也在浏览器挂载后实时拉取\nconst sentinel = useTemplateRef('sentinel')\nlet observer: IntersectionObserver | undefined\nonMounted(() => {\n  loadMore()\n  if (!sentinel.value)\n    return\n  observer = new IntersectionObserver((entries) => {\n    if (entries[0]?.isIntersecting)\n      loadMore()\n  }, { rootMargin: '200px' })\n  observer.observe(sentinel.value)\n})\nonBeforeUnmount(() => observer?.disconnect())\n\nfunction replyTalk(content: string): void {\n  const input = document.querySelector('#twikoo .tk-input textarea')\n  if (!(input instanceof HTMLTextAreaElement)) return\n\n  if (content.trim()) {\n    const quotes = content.split('\\n').map(str => `> ${str}`)\n    input.value = `${quotes}\\n\\n`\n  } else {\n    input.value = ''\n  }\n  input.dispatchEvent(new InputEvent('input'))\n\n  const length = input.value.length\n  input.setSelectionRange(length, length)\n  input.focus()\n}\n\nfunction getEssayDate(date?: string | Date) {\n  if (!date) {\n    return ''\n  }\n  \n  const dateStr = typeof date === 'string' ? date : date.toISOString()\n  return toZdtLocaleString(dateStr, {\n    year: 'numeric',\n    month: '2-digit',\n    day: '2-digit',\n    hour: '2-digit',\n    minute: '2-digit',\n  }).replace(\u002F\\\u002F\u002Fg, '-')\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n\u003Ctemplate #aside>\n\t\u003CWidgetWelcomeVisitor \u002F>\n\t\u003CWidgetBlogStats \u002F>\n\t\u003CWidgetBlogTech \u002F>\n\t\u003CWidgetLatestComments \u002F>\n\t\u003CWidgetBlogLog \u002F>\n\t\u003CWidgetCommGroup \u002F>\n\u003C\u002Ftemplate>\n\n\u003CZPageBanner :title :description :image>\n  \u003Ctemplate #corner>\n    \u003Cspan class=\"powered-by\">Powered by ech0\u003C\u002Fspan>\n    \u003CUtilLink class=\"view-more\" :to=\"ECHO_SITE\">\n      \u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" viewBox=\"0 0 32 32\">\u003C!-- Icon from KDesign Icons by Kingdee \u002F KDesign - Apache-2.0 https:\u002F\u002Fwww.apache.org\u002Flicenses\u002FLICENSE-2.0 -->\u003Cpath fill=\"currentColor\" d=\"M12 17a3 3 0 012.995 2.824L15 20v7a3 3 0 01-2.824 2.995L12 30H5a3 3 0 01-2.995-2.824L2 27v-7a3 3 0 012.824-2.995L5 17zm15 0a3 3 0 012.995 2.824L30 20v7a3 3 0 01-2.824 2.995L27 30h-7a3 3 0 01-2.995-2.824L17 27v-7a3 3 0 012.824-2.995L20 17zm-15 2H5a1 1 0 00-.993.883L4 20v7a1 1 0 00.883.993L5 28h7a1 1 0 00.993-.883L13 27v-7a1 1 0 00-.883-.993zm15 0h-7a1 1 0 00-.993.883L19 20v7a1 1 0 00.883.993L20 28h7a1 1 0 00.993-.883L28 27v-7a1 1 0 00-.883-.993zM12 2a3 3 0 012.995 2.824L15 5v7a3 3 0 01-2.824 2.995L12 15H5a3 3 0 01-2.995-2.824L2 12V5a3 3 0 012.824-2.995L5 2zm15 0a3 3 0 012.995 2.824L30 5v7a3 3 0 01-2.824 2.995L27 15h-7a3 3 0 01-2.995-2.824L17 12V5a3 3 0 012.824-2.995L20 2zM12 4H5a1 1 0 00-.993.883L4 5v7a1 1 0 00.883.993L5 13h7a1 1 0 00.993-.883L13 12V5a1 1 0 00-.883-.993zm15 0h-7a1 1 0 00-.993.883L19 5v7a1 1 0 00.883.993L20 13h7a1 1 0 00.993-.883L28 12V5a1 1 0 00-.883-.993z\"\u002F>\u003C\u002Fsvg>\n      \u003Cspan>查看更多\u003C\u002Fspan>\n    \u003C\u002FUtilLink>\n  \u003C\u002Ftemplate>\n\u003C\u002FZPageBanner>\n\n\u003Cdiv class=\"talk-list\">\n  \u003Cdiv class=\"talk-item\" v-for=\"(talk, index) in talks\" :key=\"`${talk.date}-${index}`\">\n    \u003Cdiv class=\"talk-meta\">\n      \u003CNuxtImg class=\"avatar\" :src=\"author.avatar\" :alt=\"author.name\" \u002F>\n      \u003Cdiv class=\"info\">\n        \u003Cdiv class=\"nick\">\n          {{ author.name }}\n          \u003CIcon class=\"verified\" name=\"i-material-symbols:verified\" \u002F>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"date\">{{ getEssayDate(talk.date) }}\u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"talk-content\">\n      \u003Cdiv class=\"text\" v-if=\"talk.text\" v-html=\"talk.text\">\u003C\u002Fdiv>\n      \u003Cdiv class=\"images\" v-if=\"talk.images\">\n        \u003CPic class=\"image\" v-for=\"image in talk.images\" :src=\"image\" \u002F>\n      \u003C\u002Fdiv>\n      \u003CVideoEmbed class=\"video\" v-if=\"talk.video\" v-bind=\"talk.video\" height=\"\" \u002F>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"talk-bottom\">\n      \u003Cdiv class=\"tags\">\n        \u003Cspan class=\"tag\" v-for=\"tag in talk.tags\">\n          \u003CIcon name=\"tabler:tag\" \u002F>\n          \u003Cspan>{{ tag }}\u003C\u002Fspan>\n        \u003C\u002Fspan>\n        \u003CUtilLink\n          class=\"location\"\n          v-if=\"talk.location\"\n          v-tip=\"`搜索: ${talk.location}`\"\n          :to=\"`https:\u002F\u002Fbing.com\u002Fmaps?q=${encodeURIComponent(talk.location)}`\"\n        >\n          \u003CIcon name=\"tabler:map-pin\" \u002F>\n          \u003Cspan>{{ talk.location }}\u003C\u002Fspan>\n        \u003C\u002FUtilLink>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"actions\">\n        \u003CUtilLink\n          class=\"origin\"\n          v-if=\"talk.link\"\n          v-tip=\"'查看原文'\"\n          :to=\"talk.link\"\n        >\n          \u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" viewBox=\"0 0 24 24\">\u003C!-- Icon from Majesticons by Gerrit Halfmann - MIT https:\u002F\u002Fgithub.com\u002Fhalfmage\u002Fmajesticons\u002Fblob\u002Fmain\u002FLICENSE -->\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-4m-8-2l8-8m0 0v5m0-5h-5\"\u002F>\u003C\u002Fsvg>\n        \u003C\u002FUtilLink>\n        \u003Cbutton class=\"comment-btn\" v-tip=\"'评论'\" @click=\"replyTalk(talk.text)\">\n          \u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" viewBox=\"0 0 24 24\">\u003C!-- Icon from Mística Icons by Telefónica \u002F Mística - MIT https:\u002F\u002Fopensource.org\u002Flicenses\u002FMIT -->\u003Cpath fill=\"currentColor\" d=\"M6 9.25a.75.75 0 010 1.5H4a.25.25 0 00-.25.25v8.19l1.72-1.72.114-.094A.75.75 0 016 17.25h7a.25.25 0 00.25-.25v-2a.75.75 0 011.5 0v2A1.75 1.75 0 0113 18.75H6.31l-2.78 2.78A.75.75 0 012.25 21V11A1.75 1.75 0 014 9.25zm14-7A1.75 1.75 0 0121.75 4v10a.75.75 0 01-1.28.53l-2.78-2.78H11A1.75 1.75 0 019.25 10V4A1.75 1.75 0 0111 2.25zm-9 1.5a.25.25 0 00-.25.25v6a.25.25 0 00.25.25h7c.199 0 .39.08.53.22l1.72 1.72V4a.25.25 0 00-.25-.25z\"\u002F>\u003C\u002Fsvg>\n        \u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\n  \u003Cdiv ref=\"sentinel\" class=\"talk-footer\">\n    \u003Cp v-if=\"loading\">加载中…\u003C\u002Fp>\n    \u003Cp v-else-if=\"!talks.length\">暂无说说\u003C\u002Fp>\n    \u003Cp v-else-if=\"finished\">没有更多了\u003C\u002Fp>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\n\u003CPostComment \u002F>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n\u002F* 横幅右下角角标 *\u002F\n.powered-by {\n  opacity: .75;\n}\n\n.view-more {\n  display: inline-flex;\n  align-items: center;\n  gap: .25em;\n  color: inherit;\n  opacity: .75;\n  transition: opacity .2s;\n\n  &:hover {\n    opacity: 1;\n  }\n\n  svg {\n    display: block;\n    width: 1.1em;\n    height: 1.1em;\n  }\n}\n\n.talk-list {\n  animation: float-in .2s backwards;\n  margin: 1rem;\n\n  .talk-item {\n    animation: float-in .3s backwards;\n    animation-delay: var(--delay);\n    border-radius: 8px;\n    box-shadow: 0 0 0 1px var(--c-bg-soft);\n    display: flex;\n    flex-direction: column;\n    gap: .5rem;\n    margin-bottom: 1rem;\n    padding: 1rem;\n\n    .talk-meta {\n      align-items: center;\n      display: flex;\n      gap: 10px;\n\n      .avatar {\n        border-radius: 50%;\n        box-shadow: 2px 4px 1rem var(--ld-shadow);\n        width: 3em;\n\n        @supports (corner-shape: squircle) {\n          corner-shape: superellipse(1.2);\n         }\n      }\n\n      .nick {\n        align-items: center;\n        display: flex;\n        gap: 5px;\n      }\n\n      .date {\n        color: var(--c-text-3);\n        font-family: var(--font-monospace);\n        font-size: .8rem;\n      }\n\n      .verified {\n        color: var(--c-primary);\n        font-size: 16px;\n      }\n    }\n\n    .talk-content {\n      color: var(--c-text-2);\n      display: flex;\n      flex-direction: column;\n      gap: .5rem;\n      line-height: 1.6;\n\n      :deep(a[href]) {\n        margin: -.1em -.2em;\n        padding: .1em .2em;\n        background: linear-gradient(var(--c-primary-soft), var(--c-primary-soft)) no-repeat center bottom \u002F 100% .1em;\n        color: var(--c-primary);\n        transition: all .2s;\n\n        &:hover {\n          border-radius: .3em;\n          background-size: 100% 100%;\n        }\n      }\n\n      .images {\n        display: grid;\n        gap: 8px;\n        grid-template-columns: repeat(3, 1fr);\n      }\n\n      .image {\n        border-radius: 8px;\n        overflow: hidden;\n        padding-bottom: 100%;\n        position: relative;\n\n        :deep(img) {\n          height: 100%;\n          object-fit: cover;\n          position: absolute;\n          transition: transform .3s;\n          width: 100%;\n\n          &:hover {\n            transform: scale(1.05);\n          }\n        }\n      }\n\n      .video {\n        border-radius: 8px;\n        margin: 0;\n      }\n    }\n\n    .talk-bottom {\n      align-items: center;\n      color: var(--c-text-3);\n      display: flex;\n      justify-content: space-between;\n\n      .tags {\n        display: flex;\n        font-size: .7rem;\n        gap: 4px;\n      }\n\n      .tag, .location {\n        display: flex;\n        padding: 2px 4px;\n        border-radius: 4px;\n        background-color: var(--c-bg-2);\n        align-items: center;\n        cursor: pointer;\n        transition: all .2s;\n\n        &:hover {\n          opacity: .8;\n        }\n      }\n\n      .tag .tabler-tag + * {\n        margin-left: .15em;\n      }\n\n      .location {\n        color: var(--c-primary);\n      }\n\n      .actions {\n        align-items: center;\n        display: flex;\n        gap: 4px;\n      }\n\n      .origin {\n        align-items: center;\n        background-color: var(--c-bg-2);\n        border: 1px solid var(--c-bg-soft);\n        border-radius: 4px;\n        color: var(--c-text-3);\n        display: flex;\n        padding: 2px 6px;\n        transition: all .2s;\n\n        &:hover {\n          opacity: .8;\n        }\n\n        svg {\n          display: block;\n          height: 1.15em;\n          width: 1.15em;\n        }\n      }\n\n      .comment-btn {\n        align-items: center;\n        background-color: var(--c-bg-2);\n        border: 1px solid var(--c-bg-soft);\n        border-radius: 4px;\n        color: var(--c-text-3);\n        cursor: pointer;\n        display: flex;\n        padding: 2px 6px;\n        transition: all .2s;\n\n        &:hover {\n          opacity: .8;\n        }\n\n        svg {\n          display: block;\n          height: 1.15em;\n          width: 1.15em;\n        }\n      }\n    }\n  }\n\n  .talk-footer {\n    color: var(--c-text-3);\n    font-size: 1rem;\n    margin: 2rem 0;\n    text-align: center;\n  }\n}\n\u003C\u002Fstyle>\n",[52,128,126],{"__ignoreMap":47},[13,130,50,131,55,133],{},[52,132,72],{"code":72},[52,134,75],{"code":75},[60,136,139],{"className":137,"code":138,"language":65},[63],"export type TalkItem = {\n   text?: string\n   date: string\n   images?: string[]\n   video?: {\n      type?: 'raw' | 'bilibili' | 'bilibili-nano' | 'youtube' | 'douyin' | 'douyin-wide' | 'tiktok'\n      id: string\n      ratio?: string | number\n      poster?: string\n   }\n   tags?: string[]\n   location?: string\n   \u002F** 查看原文的链接（源站单条说说页面） *\u002F\n   link?: string\n}\n\n\u002F** 说说接口返回的标签 *\u002F\nexport interface EchoTag {\n   id: string\n   name: string\n   usage_count?: number\n   created_at?: number\n}\n\n\u002F** 说说接口返回的媒体扩展 *\u002F\nexport interface EchoExtension {\n   id?: string\n   echo_id?: string\n   type?: string\n   payload?: {\n      \u002F** B 站 bvid 或原始视频直链 *\u002F\n      videoId?: string\n      images?: string[]\n      [key: string]: unknown\n   }\n   created_at?: number\n   updated_at?: number\n}\n\n\u002F** 说说接口返回的单条数据 *\u002F\nexport interface EchoItem {\n   id: string\n   content?: string\n   username?: string\n   user_id?: string\n   layout?: string\n   private?: boolean\n   tags?: EchoTag[]\n   extension?: EchoExtension\n   fav_count?: number\n   \u002F** Unix 秒级时间戳 *\u002F\n   created_at: number\n}\n\n\u002F** 说说分页接口数据 *\u002F\nexport interface EchoPage {\n   total: number\n   items: EchoItem[]\n}\n",[52,140,138],{"__ignoreMap":47},[35,142,143],{"id":143},"添加标签页面",[13,145,50,146,55,148],{},[52,147,54],{"code":54},[52,149,150],{"code":150},"tags.vue",[60,152,155],{"className":153,"code":154,"language":65},[63],"\u003Cscript setup lang=\"ts\">\nimport type { ArticleProps } from '~\u002Ftypes\u002Farticle'\n\nconst appConfig = useAppConfig()\nconst title = '标签'\nconst description = `${appConfig.title}的所有文章标签。`\nuseSeoMeta({ title, description })\n\nconst { data: listRaw } = await useAsyncData('posts:index', () => queryArticleIndex(), { default: () => [] })\nconst { listSorted, isAscending, sortOrder } = useArticleSort(listRaw)\n\n\u002F\u002F 选中的标签\nconst selectedTag = ref\u003Cstring>('')\n\n\u002F\u002F 计算每个标签对应的文章\nconst articlesByTag = computed(() => {\n\tconst result: Record\u003Cstring, ArticleProps[]> = {}\n\tfor (const article of listSorted.value) {\n\t\tif (article.tags) {\n\t\t\tfor (const tag of article.tags) {\n\t\t\t\t(result[tag] ||= []).push(article)\n\t\t\t}\n\t\t}\n\t}\n\treturn result\n})\n\n\u002F\u002F 排序后的标签列表（按文章数量降序）\nconst sortedTags = computed(() => {\n\treturn Object.keys(articlesByTag.value).sort((a, b) => {\n\t\treturn (articlesByTag.value[b]?.length || 0) - (articlesByTag.value[a]?.length || 0)\n\t})\n})\n\n\u002F\u002F 根据文章数量计算标签大小的函数\nfunction getTagSize(count: number): string {\n\tconst counts = Object.values(articlesByTag.value).map(articles => articles.length)\n\tconst maxCount = Math.max(...counts)\n\tconst minCount = Math.min(...counts)\n\tconst range = maxCount - minCount\n\tif (range === 0) return 'medium'\n\t\n\tconst ratio = (count - minCount) \u002F range\n\tif (ratio \u003C 0.33) return 'small'\n\tif (ratio \u003C 0.66) return 'medium'\n\treturn 'large'\n}\n\n\u002F\u002F 点击标签显示对应文章\nfunction handleTagClick(tag: string) {\n\tselectedTag.value = tag\n\t\u002F\u002F 滚动到页面顶部\n\twindow.scrollTo({ top: 0, behavior: 'smooth' })\n}\n\n\u002F\u002F 取消选中标签，返回标签云视图\nfunction clearSelectedTag() {\n\tselectedTag.value = ''\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n\u003Ctemplate #aside>\n\t\u003CWidgetBlogStats \u002F>\n\t\u003CWidgetBlogLog \u002F>\n\u003C\u002Ftemplate>\n\u003Cdiv class=\"tags proper-height\">\n\t\u003C!-- 选中标签时显示 -->\n\t\u003Cdiv v-if=\"selectedTag\" class=\"tag-selected\">\n\t\t\u003Cdiv class=\"tag-selected-header\">\n\t\t\t\u003Ch1 class=\"tag-selected-title\">\n\t\t\t\t\u003Cspan class=\"tag-hashtag\">#\u003C\u002Fspan> {{ selectedTag }}\n\t\t\t\u003C\u002Fh1>\n\t\t\t\u003Cbutton class=\"tag-clear-btn\" @click=\"clearSelectedTag\" aria-label=\"返回标签云\">\n\t\t\t\t\u003CIcon name=\"tabler:x\" \u002F>\n\t\t\t\u003C\u002Fbutton>\n\t\t\u003C\u002Fdiv>\n\t\t\u003Cdiv class=\"tag-selected-info\">\n\t\t\t共 {{ articlesByTag[selectedTag]?.length }} 篇文章\n\t\t\u003C\u002Fdiv>\n\t\t\n\t\t\u003Cmenu class=\"archive-list\">\n\t\t\t\u003CUtilListTransition v-slot=\"{ items }\" :items=\"articlesByTag[selectedTag]\" :state=\"selectedTag\">\n\t\t\t\t\u003CPostArchive\n\t\t\t\t\tv-for=\"article, index in items\"\n\t\t\t\t\t:key=\"article.path\"\n\t\t\t\t\t:data-list-key=\"article.path\"\n\t\t\t\t\tv-bind=\"article\"\n\t\t\t\t\t:to=\"article.path\"\n\t\t\t\t\t:use-updated=\"sortOrder === 'updated'\"\n\t\t\t\t\t:style=\"getFixedDelay(index * 0.03)\"\n\t\t\t\t\u002F>\n\t\t\t\u003C\u002FUtilListTransition>\n\t\t\u003C\u002Fmenu>\n\t\u003C\u002Fdiv>\n\n\t\u003C!-- 标签云视图 -->\n\t\u003Cdiv v-else class=\"tag-cloud\">\n\t\t\u003Ch1 class=\"tag-cloud-title\">{{ title }}\u003C\u002Fh1>\n\t\t\u003Cdiv class=\"tag-cloud-content\">\n\t\t\t\u003Cbutton\n\t\t\t\tv-for=\"tag in sortedTags\"\n\t\t\t\t:key=\"tag\"\n\t\t\t\tclass=\"tag-cloud-item gradient-card\"\n\t\t\t\t:class=\"getTagSize(articlesByTag[tag]?.length)\"\n\t\t\t\t@click=\"handleTagClick(tag)\"\n\t\t\t>\n\t\t\t\t# {{ tag }}\n                \u003Cspan class=\"tag-count\">{{ articlesByTag[tag]?.length }}\u003C\u002Fspan>\n\t\t\t\u003C\u002Fbutton>\n\t\t\u003C\u002Fdiv>\n\t\t\n\t\t\u003Cdiv class=\"tag-cloud-stats\">\n\t\t\t共 {{ sortedTags.length }} 个标签\n\t\t\u003C\u002Fdiv>\n\t\u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.tags {\n\tmargin: 1rem;\n\tpadding: 2rem 0;\n}\n\n\u002F* 标签云样式 *\u002F\n.tag-cloud {\n\tmax-width: 800px;\n\tmargin: 0 auto;\n}\n\n.tag-cloud-title {\n\ttext-align: center;\n\tfont-size: 2.5rem;\n\tmargin-bottom: 2rem;\n\tcolor: var(--c-text);\n}\n\n.tag-cloud-content {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tjustify-content: center;\n\tgap: 1rem;\n\tmargin-bottom: 2rem;\n}\n\n.tag-cloud-item {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 0.5rem;\n\tpadding: 0.5rem 1rem;\n\tborder-radius: 2rem;\n\tbackground-color: var(--c-bg-2);\n\tcolor: var(--c-text);\n\tcursor: pointer;\n\tline-height: 1.4;\n\n\t&.small {\n\t\tfont-size: 0.9rem;\n\t}\n\n\t&.medium {\n\t\tfont-size: 1.1rem;\n\t}\n\n\t&.large {\n\t\tfont-size: 1.3rem;\n\t\tfont-weight: 600;\n\t}\n}\n\n.tag-count {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tmin-width: 20px;\n\theight: 20px;\n\tpadding: 0 6px;\n\tborder-radius: 10px;\n\tbackground-color: var(--c-bg-3);\n\tcolor: var(--c-text-2);\n\tfont-size: 0.8rem;\n\tfont-weight: 500;\n}\n\n.tag-cloud-stats {\n\ttext-align: center;\n\tcolor: var(--c-text-2);\n\tfont-size: 0.9rem;\n\tmargin-top: 2rem;\n}\n\n\u002F* 选中标签时的样式 *\u002F\n.tag-selected {\n\tmax-width: 800px;\n\tmargin: 0 auto;\n}\n\n.tag-selected-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tmargin-bottom: 1rem;\n}\n\n.tag-selected-title {\n\tfont-size: 2.5rem;\n\tfont-family: var(--font-creative);\n\tfont-weight: 550;\n\tcolor: var(--c-text);\n\tmargin: 0;\n}\n\n.tag-hashtag {\n\tmargin-inline-end: 0.1em;\n\tpadding: 0 2px;\n\tborder-radius: 0.2rem;\n\tbackground-color: var(--c-primary);\n\tcolor: white;\n}\n\n.tag-clear-btn {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 40px;\n\theight: 40px;\n\tborder-radius: 50%;\n\tbackground-color: var(--c-bg-2);\n\tcolor: var(--c-text-2);\n\tborder: none;\n\tcursor: pointer;\n\ttransition: all 0.2s ease;\n\n\t&:hover {\n\t\tbackground-color: var(--c-bg-3);\n\t\tcolor: var(--c-text);\n\t\ttransform: rotate(90deg);\n\t}\n}\n\n.tag-selected-info {\n\tcolor: var(--c-text-2);\n\tfont-size: 1rem;\n\tmargin-bottom: 2rem;\n\tpadding-bottom: 1rem;\n\tborder-bottom: 1px solid var(--c-border);\n}\n\n.archive-list {\n\tmargin-top: 1.5rem;\n}\n\n\u002F* 响应式设计 *\u002F\n@media (max-width: 768px) {\n\t.tags {\n\t\tmargin: 0.5rem;\n\t\tpadding: 1rem 0;\n\t}\n\n\t.tag-cloud-title,\n\t.tag-selected-title {\n\t\tfont-size: 2rem;\n\t}\n\n\t.tag-cloud-item {\n\t\tpadding: 0.4rem 0.8rem;\n\t\tgap: 0.4rem;\n\t}\n\n\t.tag-cloud-item.large {\n\t\tfont-size: 1.2rem;\n\t}\n}\n\u003C\u002Fstyle>\n",[52,156,154],{"__ignoreMap":47},[35,158,159],{"id":159},"友链朋友圈",[39,161,163,218],{":center":41,":tabs":162},"[\"方案1\",\"方案2\"]",[45,164,165,173,181,187,190,196,212],{"v-slot:tab1":47},[13,166,167,168],{},"适配六神的",[17,169,172],{"href":170,"rel":171},"https:\u002F\u002Fgithub.com\u002Fwillow-god\u002FFriend-Circle-Lite",[21],"轻量友链朋友圈",[13,174,50,175,55,178],{},[52,176,177],{"code":177},"\u002Fscripts\u002F",[52,179,180],{"code":180},"generate-friend.ts",[60,182,185],{"className":183,"code":184,"language":65},[63],"#!\u002Fusr\u002Fbin\u002Fenv node\n\nimport fs from 'node:fs'\nimport { resolve } from 'node:path'\nimport { intro, log, outro } from '@clack\u002Fprompts'\nimport { entries, displayName } from '.\u002Fframework\u002Futils'\n\nconst blacklist = ['xxx', 'xxx']\n\nintro('👥 生成友链数据')\n\ntry {\n\tconst friends = entries\n\t\t.filter(e => !e.error)\n\t\t.map(e => {\n\t\t\tconst name = displayName(e)\n\t\t\treturn blacklist.includes(name) ? null : [name, e.link, e.avatar]\n\t\t})\n\t\t.filter(Boolean)\n\n\tfs.mkdirSync(resolve('public'), { recursive: true })\n\tconst outputPath = resolve('public', 'friend.json')\n\n\tconst friendData = { friends }\n\tfs.writeFileSync(outputPath, JSON.stringify(friendData, null, 2), 'utf-8')\n\n\tlog.success(`成功生成 ${friends.length} 个友链`) \n\tlog.info(`输出路径: ${outputPath}`)\n\n\toutro('✅ 完成')\n} catch (error) {\n\tlog.error('生成失败:', error instanceof Error ? error.message : String(error))\n\tprocess.exit(1)\n}\n",[52,186,184],{"__ignoreMap":47},[13,188,189],{},"执行",[60,191,194],{"className":192,"code":193,"language":65},[63],"pnpm tsx scripts\u002Fgenerate-friend.ts\n",[52,195,193],{"__ignoreMap":47},[13,197,198,199,202,203,206,207,55,209],{},"会在",[52,200,201],{"code":201},"public","目录生成",[52,204,205],{"code":205},"friend.json","\n在",[52,208,54],{"code":54},[52,210,211],{"code":211},"fcircle.vue",[60,213,216],{"className":214,"code":215,"language":65},[63],"\u003Cscript setup lang=\"ts\">\nimport { ref, reactive, onMounted, onUnmounted, computed } from 'vue'\n\nconst title = '朋友圈'\nconst description = '发现更多有趣的博主。'\nconst image = 'https:\u002F\u002F图片链接'\nuseSeoMeta({ title, description, ogImage: image })\n\n\u002F\u002F 配置选项\nconst UserConfig = reactive({\n  api_url: 'https:\u002F\u002F轻量朋友圈\u002F',\n  page_size: 20\n})\n\n\u002F\u002F 状态管理\nconst allArticles = ref([])\nconst displayCount = ref(20)\nconst isLoading = ref(true)\nconst randomArticle = ref(null)\nconst showAvatarPopup = ref(false)\nconst selectedAuthor = ref('')\nconst selectedAuthorAvatar = ref('')\nconst selectedArticleLink = ref('')\nconst articlesByAuthor = ref({})\nconst lastUpdatedDate = ref('')\n\n\u002F\u002F 计算属性\nconst displayedArticles = computed(() => allArticles.value.slice(0, displayCount.value))\nconst hasMoreArticles = computed(() => allArticles.value.length > displayCount.value)\n\n\u002F\u002F 格式化日期\nconst formatDate = (dateString: string) => {\n  if (!dateString) return ''\n  \n  return toZdtLocaleString(dateString, 'date').replace(\u002F\\\u002F\u002Fg, '-')\n}\n\n\u002F\u002F 刷新随机文章\nconst refreshRandomArticle = () => {\n  if (allArticles.value.length > 0) {\n    const randomIndex = Math.floor(Math.random() * allArticles.value.length)\n    randomArticle.value = allArticles.value[randomIndex]\n  }\n}\n\n\u002F\u002F 加载更多\nconst loadMore = () => {\n  displayCount.value += UserConfig.page_size\n}\n\n\u002F\u002F 模态框相关\nconst showAvatarPosts = (author, avatar, articleLink) => {\n  selectedAuthor.value = author\n  selectedAuthorAvatar.value = avatar\n  selectedArticleLink.value = articleLink\n  showAvatarPopup.value = true\n}\n\nconst closeAvatarPopup = () => {\n  showAvatarPopup.value = false\n}\n\n\u002F\u002F 监听点击外部关闭弹窗\nconst handleClickOutside = (event) => {\n  const popup = document.getElementById('avatar-popup')\n  if (popup && !popup.contains(event.target) && showAvatarPopup.value) {\n    closeAvatarPopup()\n  }\n}\n\n\u002F\u002F 获取数据\nconst fetchData = async () => {\n  try {\n    isLoading.value = true\n    const response = await fetch(`${UserConfig.api_url}all.json`)\n    const data = await response.json()\n    \n    \u002F\u002F 处理数据\n    allArticles.value = data.article_data.map(item => ({\n      id: item.link + Math.random(), \u002F\u002F 确保唯一ID\n      title: item.title,\n      link: item.link,\n      author: item.author,\n      created: item.created,\n      avatar: item.avatar\n    }))\n    \n    \u002F\u002F 按作者分组\n    articlesByAuthor.value = allArticles.value.reduce((acc, article) => {\n      if (!acc[article.author]) acc[article.author] = []\n      acc[article.author].push(article)\n      return acc\n    }, {})\n    \n    \u002F\u002F 初始化随机文章\n    refreshRandomArticle()\n    \n    \u002F\u002F 设置最新更新日期\n    if (allArticles.value.length > 0) {\n      const sortedArticles = [...allArticles.value].sort((a, b) => \n        new Date(b.created) - new Date(a.created)\n      )\n      lastUpdatedDate.value = formatDate(sortedArticles[0].created)\n    }\n  } catch (error) {\n    console.error('加载文章失败:', error)\n  } finally {\n    isLoading.value = false\n  }\n}\n\n\u002F\u002F 生命周期钩子\nonMounted(() => {\n  fetchData()\n})\n\nonUnmounted(() => {\n  document.removeEventListener('click', handleClickOutside)\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n\u003Ctemplate #aside>\n\t\u003CWidgetBlogStats \u002F>\n\t\u003CWidgetBlogTech \u002F>\n\t\u003CWidgetBlogLog \u002F>\n\u003C\u002Ftemplate>\n\n  \u003CZPageBanner :title :description :image>\n    \u003Cdiv class=\"stats-bar\">\n      \u003Cdiv class=\"stats-update-time\">Updated at {{ lastUpdatedDate || '2025-07-17' }}\u003C\u002Fdiv>\n      \u003Cdiv class=\"stats-powered-by\">Powered by FriendCircleLite\u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002FZPageBanner>\n\n  \u003Cdiv class=\"page-fcircle\">\n    \u003Cdiv class=\"fcircle\">\n      \u003C!-- 随机文章区域 -->\n      \u003Cdiv v-if=\"randomArticle\" class=\"random-article\">\n        \u003Cdiv class=\"random-title\">随机文章\u003C\u002Fdiv>\n        \u003Cdiv class=\"article-item\">\n          \u003Ca \n            :href=\"randomArticle.link\"\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            class=\"article-container gradient-card\"\n          >\n            \u003Cspan class=\"article-author\">{{ randomArticle.author }}\u003C\u002Fspan>\n            \u003Cspan class=\"article-title\">{{ randomArticle.title }}\u003C\u002Fspan>\n            \u003Cspan class=\"article-date\">{{ formatDate(randomArticle.created) }}\u003C\u002Fspan>\n          \u003C\u002Fa>\n        \u003C\u002Fdiv>\n        \u003CZButton \n          class=\"refresh-btn gradient-card\" \n          @click=\"refreshRandomArticle\"\n          icon=\"uim:process\"\n        \u002F>\n      \u003C\u002Fdiv>\n\n      \u003C!-- 文章列表区域 -->\n      \u003Cdiv class=\"articles-list\">\n        \u003Cdiv\n          v-for=\"(article, index) in displayedArticles\"\n          :key=\"article.id\"\n          class=\"article-item new-item\"\n          :style=\"{ '--delay': `${(index % UserConfig.page_size) * 0.05}s` }\"\n        >\n          \u003Cdiv class=\"article-image\" @click=\"showAvatarPosts(article.author, article.avatar, article.link)\">\n            \u003CNuxtImg \n              :src=\"article.avatar\" \n              :alt=\"article.author\"\n              loading=\"lazy\"\n            \u002F>\n          \u003C\u002Fdiv>\n          \u003Ca\n            :href=\"article.link\"\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            class=\"article-container gradient-card\"\n          >\n            \u003Cspan class=\"article-author\">{{ article.author }}\u003C\u002Fspan>\n            \u003Cspan class=\"article-title\">{{ article.title }}\u003C\u002Fspan>\n            \u003Cspan class=\"article-date\">{{ formatDate(article.created) }}\u003C\u002Fspan>\n          \u003C\u002Fa>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n\n      \u003C!-- 加载更多按钮 -->\n      \u003CZButton \n        v-show=\"hasMoreArticles\" \n        class=\"load-more gradient-card\"\n        @click=\"loadMore\"\n        text=\"加载更多\"\n      \u002F>\n\n      \u003C!-- 空状态 -->\n      \u003Cdiv v-if=\"!isLoading && allArticles.length === 0\" class=\"error-container\">\n        \u003CIcon class=\"error-icon\" name=\"tabler:file-alert\" \u002F>\n        \u003Cp>暂无文章数据\u003C\u002Fp>\n        \u003Cp class=\"empty-hint\">请稍后再试\u003C\u002Fp>\n      \u003C\u002Fdiv>\n\n      \u003C!-- 作者模态框 - 时间线样式 -->\n      \u003CTransition name=\"modal\">\n        \u003Cdiv \n          v-if=\"showAvatarPopup && selectedAuthor && articlesByAuthor[selectedAuthor]\"\n          id=\"avatar-popup\"\n          class=\"modal\"\n          @click=\"closeAvatarPopup\"\n        >\n          \u003Cdiv class=\"modal-content\" @click.stop>\n            \u003Cdiv class=\"modal-header\">\n              \u003CNuxtImg \n                :src=\"selectedAuthorAvatar\" \n                :alt=\"selectedAuthor\"\n                loading=\"lazy\"\n              \u002F>\n              \u003Ch3>{{ selectedAuthor }}\u003C\u002Fh3>\n              \u003Ca \n                :href=\"selectedArticleLink\" \n                target=\"_blank\"\n                rel=\"noopener noreferrer\"\n                class=\"author-link\"\n              >\n                \u003CIcon name=\"lucide:external-link\" \u002F>\n              \u003C\u002Fa>\n            \u003C\u002Fdiv>\n            \u003Cdiv class=\"modal-body\">\n              \u003Cdiv class=\"timeline\">\n                \u003Cdiv \n                  v-for=\"(article, index) in articlesByAuthor[selectedAuthor].slice(0, 10)\"\n                  :key=\"article.id\"\n                  class=\"timeline-item\"\n                  :style=\"{ '--delay': (0.2 + index * 0.1) + 's' }\"\n                >\n                  \u003Cspan class=\"date\">{{ formatDate(article.created) }}\u003C\u002Fspan>\n                  \u003Ca \n                    :href=\"article.link\"\n                    target=\"_blank\"\n                    rel=\"noopener noreferrer\"\n                    class=\"article-title\"\n                    @click=\"closeAvatarPopup\"\n                  >\n                    {{ article.title }}\n                  \u003C\u002Fa>\n                \u003C\u002Fdiv>\n              \u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n            \u003Cdiv class=\"modal-avatar\">\n              \u003CNuxtImg \n                :src=\"selectedAuthorAvatar\" \n                :alt=\"selectedAuthor\"\n                loading=\"lazy\"\n              \u002F>\n            \u003C\u002Fdiv>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002FTransition>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.stats-bar {\n\talign-items: flex-end;\n\tcolor: #eee;\n\tdisplay: flex;\n\tflex-direction: column;\n\tfont-family: var(--font-monospace);\n\tfont-size: .7rem;\n\tgap: .1rem;\n\topacity: .7;\n\ttext-shadow: 0 4px 5px rgba(0, 0, 0, .5);\n\n\t.stats-update-time {\n\t\topacity: 1;\n\t}\n\n\t.stats-powered-by {\n\t\topacity: .8;\n\t}\n}\n\n.page-fcircle {\n\tanimation: float-in .2s backwards;\n\tmargin: 1rem;\n\n\t.random-article {\n\t\talign-items: center;\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\tgap: 10px;\n\t\tjustify-content: space-between;\n\t\tmargin: 1rem 0;\n\n\t\t.random-title {\n\t\t\tfont-size: 1.2rem;\n\t\t\twhite-space: nowrap;\n\t\t}\n\n\t\t.article-item {\n\t\t\tflex: 1;\n\t\t\tmin-width: 0;\n\n\t\t\t.article-container {\n\t\t\t\tmin-width: 0;\n\n\t\t\t\t.article-title {\n\t\t\t\t\toverflow: hidden;\n\t\t\t\t\ttext-overflow: ellipsis;\n\t\t\t\t\twhite-space: nowrap;\n\t\t\t\t}\n\n\t\t\t\t.article-author,\n\t\t\t\t.article-date {\n\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.refresh-btn {\n\t\t\talign-items: center;\n\t\t\tbackground-color: unset;\n\t\t\tborder-radius: 8px;\n\t\t\tbox-shadow: none;\n\t\t\tcolor: var(--c-text-2);\n\t\t\tcursor: pointer;\n\t\t\tdisplay: flex;\n\t\t\tflex-shrink: 0;\n\t\t\theight: 2.5rem;\n\t\t\tjustify-content: center;\n\t\t\ttransition: all .2s ease;\n\t\t\twidth: 2.5rem;\n\n\t\t\t&:hover {\n\t\t\t\tbackground-color: unset;\n\t\t\t}\n\t\t}\n\t}\n\n\t.articles-list {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: .5rem;\n\t}\n}\n\n.article-item {\n\talign-items: center;\n\tdisplay: flex;\n\tgap: 10px;\n\twidth: 100%;\n\n\t&.new-item {\n\t\tanimation: float-in .2s var(--delay) backwards;\n\t}\n\n\t.article-image {\n\t\tborder-radius: 50%;\n\t\tbox-shadow: 0 0 0 1px var(--c-bg-soft);\n\t\tdisplay: flex;\n\t\tflex-shrink: 0;\n\t\theight: 2rem;\n\t\toverflow: hidden;\n\t\twidth: 2rem;\n\n\t\timg {\n\t\t\theight: 100%;\n\t\t\tobject-fit: cover;\n\t\t\topacity: .8;\n\t\t\ttransition: all .2s;\n\t\t\twidth: 100%;\n\t\t}\n\t}\n\n\t.article-container {\n\t\talign-items: center;\n\t\tborder-radius: 8px;\n\t\tbox-shadow: 0 0 0 1px var(--c-bg-soft);\n\t\tdisplay: flex;\n\t\tgap: 5px;\n\t\toverflow: hidden;\n\t\tpadding: 10px;\n\t\twidth: 100%;\n\n\t\t&:hover .article-title {\n\t\t\tcolor: var(--c-text);\n\t\t}\n\n\t\t.article-author {\n\t\t\tcolor: var(--c-text-3);\n\t\t\tfont-size: .85rem;\n\t\t}\n\n\t\t.article-title {\n\t\t\tcolor: var(--c-text-2);\n\t\t\tflex: 1;\n\t\t\tfont-size: .9375rem;\n\t\t\toverflow: hidden;\n\t\t\ttext-overflow: ellipsis;\n\t\t\ttransition: color .2s;\n\t\t\twhite-space: nowrap;\n\t\t}\n\n\t\t.article-date {\n\t\t\tcolor: var(--c-text-3);\n\t\t\tfont-family: var(--font-monospace);\n\t\t\tfont-size: .75rem;\n\t\t}\n\t}\n}\n\n.load-more {\n\tbackground-color: var(--ld-bg-card);\n\tborder-radius: 8px;\n\tbox-shadow: .1em .2em .5rem var(--ld-shadow);\n\tdisplay: block;\n\tfont-size: .875rem;\n\theight: 42px;\n\tmargin: 1rem auto;\n\tpadding: .75rem;\n\twidth: 200px;\n\n\t&:hover {\n\t\tcolor: var(--c-text);\n\t}\n}\n\n\u002F* 模态框样式 *\u002F\n.modal {\n\talign-items: center;\n\tbackdrop-filter: blur(20px);\n\t-webkit-backdrop-filter: blur(20px);\n\tdisplay: flex;\n\tinset: 0;\n\tjustify-content: center;\n\tposition: fixed;\n\tz-index: 900;\n\n\t.modal-content {\n\t\tbackground-color: var(--c-bg-a50);\n\t\tborder-radius: 12px;\n\t\tbox-shadow: 0 0 0 1px var(--c-bg-soft);\n\t\tmax-height: 80vh;\n\t\tmax-width: 500px;\n\t\toverflow-y: auto;\n\t\tpadding: 1.25rem;\n\t\tposition: relative;\n\t\twidth: 90%;\n\n\t\t.modal-header {\n\t\t\talign-items: center;\n\t\t\tborder-bottom: 1px solid var(--c-bg-soft);\n\t\t\tdisplay: flex;\n\t\t\tgap: 15px;\n\t\t\tmargin-bottom: 20px;\n\t\t\tpadding-bottom: 15px;\n\n\t\t\timg {\n\t\t\t\tborder-radius: 50%;\n\t\t\t\theight: 50px;\n\t\t\t\tobject-fit: cover;\n\t\t\t\twidth: 50px;\n\t\t\t}\n\n\t\t\th3 {\n\t\t\t\tflex: 1;\n\t\t\t\tfont-size: 1.2rem;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.author-link {\n\t\t\t\tborder-radius: 8px;\n\t\t\t\tcolor: var(--c-text-2);\n\t\t\t\tpadding: 8px;\n\t\t\t\ttransition: all .3s;\n\n\t\t\t\t&:hover {\n\t\t\t\t\tbackground: var(--c-bg-soft);\n\t\t\t\t\tcolor: var(--c-text);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.modal-body {\n\t\t\t.timeline {\n\t\t\t\tposition: relative;\n\n\t\t\t\t&:after {\n\t\t\t\t\tbackground-color: var(--c-bg-soft);\n\t\t\t\t\tbottom: 0;\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tleft: .25rem;\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: .5rem;\n\t\t\t\t\ttransform: translate(-50%);\n\t\t\t\t\twidth: 2px;\n\t\t\t\t}\n\n\t\t\t\t.timeline-item {\n\t\t\t\t\tanimation: slideIn .3s ease-out both;\n\t\t\t\t\tcolor: var(--c-text-2);\n\t\t\t\t\tpadding: 0 0 1rem 1.25rem;\n\t\t\t\t\tposition: relative;\n\n\t\t\t\t\t&:before {\n\t\t\t\t\t\tbackground-color: var(--c-text-2);\n\t\t\t\t\t\tborder-radius: 50%;\n\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\theight: .5rem;\n\t\t\t\t\t\tleft: .25rem;\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\ttop: .5rem;\n\t\t\t\t\t\ttransform: translateY(-50%) translate(-50%);\n\t\t\t\t\t\ttransition: transform .3s ease, box-shadow .3s ease;\n\t\t\t\t\t\twidth: .5rem;\n\t\t\t\t\t\tz-index: 1;\n\t\t\t\t\t}\n\n\t\t\t\t\t&:hover:before {\n\t\t\t\t\t\tbox-shadow: 0 0 8px var(--c-text-2);\n\t\t\t\t\t\ttransform: translateY(-50%) translate(-50%) scale(1.5);\n\t\t\t\t\t}\n\n\t\t\t\t\t.date {\n\t\t\t\t\t\tcolor: var(--c-text-3);\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t\tfont-family: var(--font-monospace);\n\t\t\t\t\t\tfont-size: .875rem;\n\t\t\t\t\t\tmargin-bottom: .3rem;\n\t\t\t\t\t}\n\n\t\t\t\t\t.article-title {\n\t\t\t\t\t\tcolor: var(--c-text-2);\n\t\t\t\t\t\tline-height: 1.4;\n\t\t\t\t\t\ttransition: color .3s;\n\n\t\t\t\t\t\t&:hover {\n\t\t\t\t\t\t\tcolor: var(--c-text);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.modal-avatar {\n\t\t\tborder-radius: 50%;\n\t\t\tbottom: 1.25rem;\n\t\t\tfilter: blur(5px);\n\t\t\theight: 128px;\n\t\t\topacity: .6;\n\t\t\toverflow: hidden;\n\t\t\tpointer-events: none;\n\t\t\tposition: absolute;\n\t\t\tright: 1.25rem;\n\t\t\twidth: 128px;\n\t\t\tz-index: 1;\n\n\t\t\timg {\n\t\t\t\theight: 100%;\n\t\t\t\tobject-fit: cover;\n\t\t\t\twidth: 100%;\n\t\t\t}\n\t\t}\n\t}\n}\n\n@keyframes slideIn {\n\t0% {\n\t\topacity: 0;\n\t\ttransform: translateY(20px);\n\t}\n\tto {\n\t\topacity: 1;\n\t\ttransform: translateY(0);\n\t}\n}\n\n\u002F* 模态框过渡 *\u002F\n.modal-enter-active,\n.modal-enter-active .modal-content,\n.modal-leave-active,\n.modal-leave-active .modal-content {\n\ttransition: all .3s ease;\n}\n\n.modal-enter-from,\n.modal-leave-to {\n\topacity: 0;\n}\n\n.modal-enter-from .modal-content,\n.modal-leave-to .modal-content {\n\ttransform: translateY(-20px);\n}\n\n.modal-enter-to,\n.modal-leave-from {\n\topacity: 1;\n}\n\n.modal-enter-to .modal-content,\n.modal-leave-from .modal-content {\n\ttransform: translateY(0);\n}\n\n.loading-container {\n\talign-items: center;\n\tcolor: var(--c-text-2);\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\theight: 500px;\n\tjustify-content: center;\n\n\t.loading-spinner {\n\t\tanimation: spin 1s linear infinite;\n\t\tborder: 3px solid var(--c-bg-3);\n\t\tborder-radius: 50%;\n\t\tborder-top-color: var(--c-primary);\n\t\theight: 40px;\n\t\twidth: 40px;\n\t}\n}\n\n\u002F* 错误容器 *\u002F\n.error-container {\n\talign-items: center;\n\tcolor: var(--c-text-2);\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\theight: 500px;\n\tjustify-content: center;\n\n\t.error-icon {\n\t\tcolor: var(--c-danger);\n\t\tfont-size: 4rem;\n\t}\n}\n\n@keyframes spin {\n\tto {\n\t\ttransform: rotate(1turn);\n\t}\n}\n\n\u002F* 移动端适配 *\u002F\n@media (max-width: 768px) {\n\t.random-article .random-title {\n\t\tdisplay: none;\n\t}\n\n\t.page-fcircle .article-item .article-container {\n\t\tflex-wrap: wrap;\n\t\theight: auto;\n\t}\n\n\t.page-fcircle .article-item .article-container .article-author {\n\t\tflex-grow: 1;\n\t}\n\n\t.page-fcircle .article-item .article-container .article-title {\n\t\tflex-basis: 100%;\n\t\torder: 3;\n\t\twhite-space: normal;\n\t}\n}\n\u003C\u002Fstyle>\n",[52,217,215],{"__ignoreMap":47},[45,219,220,227],{"v-slot:tab2":47},[13,221,222,223,55,225],{},"适配我自己写的友链订阅程序\n在",[52,224,54],{"code":54},[52,226,211],{"code":211},[60,228,231],{"className":229,"code":230,"language":65},[63],"\u003Cscript setup lang=\"ts\">\nimport { ref, reactive, onMounted, onUnmounted, computed } from 'vue'\n\nconst title = '朋友圈'\nconst description = '发现更多有趣的博主。'\nconst image = 'https:\u002F\u002F图片链接'\nuseSeoMeta({ title, description, ogImage: image })\n\n\u002F\u002F 配置选项\nconst UserConfig = reactive({\n  api_url: 'https:\u002F\u002F友链订阅地址\u002Fapi\u002Farticles.php',\n  page_size: 20\n})\n\n\u002F\u002F 状态管理\nconst allArticles = ref([])\nconst displayCount = ref(20)\nconst isLoading = ref(true)\nconst randomArticle = ref(null)\nconst showAvatarPopup = ref(false)\nconst selectedAuthor = ref('')\nconst selectedAuthorAvatar = ref('')\nconst selectedArticleLink = ref('')\nconst articlesByAuthor = ref({})\nconst lastUpdatedDate = ref('')\n\n\u002F\u002F 计算属性\nconst displayedArticles = computed(() => allArticles.value.slice(0, displayCount.value))\nconst hasMoreArticles = computed(() => allArticles.value.length > displayCount.value)\n\n\u002F\u002F 格式化日期\nconst formatDate = (dateString: string) => {\n  if (!dateString) return ''\n  \n  return toZdtLocaleString(dateString, 'date').replace(\u002F\\\u002F\u002Fg, '-')\n}\n\n\u002F\u002F 刷新随机文章\nconst refreshRandomArticle = () => {\n  if (allArticles.value.length > 0) {\n    const randomIndex = Math.floor(Math.random() * allArticles.value.length)\n    randomArticle.value = allArticles.value[randomIndex]\n  }\n}\n\n\u002F\u002F 加载更多\nconst loadMore = () => {\n  displayCount.value += UserConfig.page_size\n}\n\n\u002F\u002F 模态框相关\nconst showAvatarPosts = (author, avatar, articleLink) => {\n  selectedAuthor.value = author\n  selectedAuthorAvatar.value = avatar\n  selectedArticleLink.value = articleLink\n  showAvatarPopup.value = true\n}\n\nconst closeAvatarPopup = () => {\n  showAvatarPopup.value = false\n}\n\n\u002F\u002F 监听点击外部关闭弹窗\nconst handleClickOutside = (event) => {\n  const popup = document.getElementById('avatar-popup')\n  if (popup && !popup.contains(event.target) && showAvatarPopup.value) {\n    closeAvatarPopup()\n  }\n}\n\n\u002F\u002F 获取数据\nconst fetchData = async () => {\n  try {\n    isLoading.value = true\n    const response = await fetch(UserConfig.api_url)\n    const data = await response.json()\n\n    \u002F\u002F 处理数据\n    allArticles.value = data.data.articles.map(item => ({\n      id: item.link + Math.random(), \u002F\u002F 确保唯一ID\n      title: item.title,\n      link: item.link,\n      author: item.blogger,\n      created: item.pub_time.replace(' ', 'T'),\n      avatar: item.site_avatar\n    }))\n\n    \u002F\u002F 按作者分组\n    articlesByAuthor.value = allArticles.value.reduce((acc, article) => {\n      if (!acc[article.author]) acc[article.author] = []\n      acc[article.author].push(article)\n      return acc\n    }, {})\n\n    \u002F\u002F 初始化随机文章\n    refreshRandomArticle()\n\n    \u002F\u002F 设置最新更新日期（接口顶层直接返回更新时间）\n    if (data.data.updated) {\n      lastUpdatedDate.value = formatDate(data.data.updated.replace(' ', 'T'))\n    }\n    else if (allArticles.value.length > 0) {\n      const sortedArticles = [...allArticles.value].sort((a, b) => \n        new Date(b.created) - new Date(a.created)\n      )\n      lastUpdatedDate.value = formatDate(sortedArticles[0].created)\n    }\n  } catch (error) {\n    console.error('加载文章失败:', error)\n  } finally {\n    isLoading.value = false\n  }\n}\n\n\u002F\u002F 生命周期钩子\nonMounted(() => {\n  fetchData()\n})\n\nonUnmounted(() => {\n  document.removeEventListener('click', handleClickOutside)\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n\u003Ctemplate #aside>\n\t\u003CWidgetWelcomeVisitor \u002F>\n\t\u003CWidgetBlogStats \u002F>\n\t\u003CWidgetBlogTech \u002F>\n\t\u003CWidgetLatestComments \u002F>\n\t\u003CWidgetBlogLog \u002F>\n\t\u003CWidgetCommGroup \u002F>\n\u003C\u002Ftemplate>\n\n  \u003CZPageBanner :title :description :image>\n    \u003Cdiv class=\"stats-bar\">\n      \u003Cdiv class=\"stats-update-time\">Updated at {{ lastUpdatedDate || '2025-07-17' }}\u003C\u002Fdiv>\n      \u003Cdiv class=\"stats-powered-by\">Powered by FriendCircleLite\u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002FZPageBanner>\n\n  \u003Cdiv class=\"page-fcircle\">\n    \u003Cdiv class=\"fcircle\">\n      \u003C!-- 随机文章区域 -->\n      \u003Cdiv v-if=\"randomArticle\" class=\"random-article\">\n        \u003Cdiv class=\"random-title\">随机文章\u003C\u002Fdiv>\n        \u003Cdiv class=\"article-item\">\n          \u003Ca \n            :href=\"randomArticle.link\"\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            class=\"article-container gradient-card\"\n          >\n            \u003Cspan class=\"article-author\">{{ randomArticle.author }}\u003C\u002Fspan>\n            \u003Cspan class=\"article-title\">{{ randomArticle.title }}\u003C\u002Fspan>\n            \u003Cspan class=\"article-date\">{{ formatDate(randomArticle.created) }}\u003C\u002Fspan>\n          \u003C\u002Fa>\n        \u003C\u002Fdiv>\n        \u003CZButton \n          class=\"refresh-btn gradient-card\" \n          @click=\"refreshRandomArticle\"\n          icon=\"uim:process\"\n        \u002F>\n      \u003C\u002Fdiv>\n\n      \u003C!-- 文章列表区域 -->\n      \u003Cdiv class=\"articles-list\">\n        \u003Cdiv\n          v-for=\"(article, index) in displayedArticles\"\n          :key=\"article.id\"\n          class=\"article-item new-item\"\n          :style=\"{ '--delay': `${(index % UserConfig.page_size) * 0.05}s` }\"\n        >\n          \u003Cdiv class=\"article-image\" @click=\"showAvatarPosts(article.author, article.avatar, article.link)\">\n            \u003CNuxtImg \n              :src=\"article.avatar\" \n              :alt=\"article.author\"\n              loading=\"lazy\"\n            \u002F>\n          \u003C\u002Fdiv>\n          \u003Ca\n            :href=\"article.link\"\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            class=\"article-container gradient-card\"\n          >\n            \u003Cspan class=\"article-author\">{{ article.author }}\u003C\u002Fspan>\n            \u003Cspan class=\"article-title\">{{ article.title }}\u003C\u002Fspan>\n            \u003Cspan class=\"article-date\">{{ formatDate(article.created) }}\u003C\u002Fspan>\n          \u003C\u002Fa>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n\n      \u003C!-- 加载更多按钮 -->\n      \u003CZButton \n        v-show=\"hasMoreArticles\" \n        class=\"load-more gradient-card\"\n        @click=\"loadMore\"\n        text=\"加载更多\"\n      \u002F>\n\n      \u003C!-- 空状态 -->\n      \u003Cdiv v-if=\"!isLoading && allArticles.length === 0\" class=\"error-container\">\n        \u003CIcon class=\"error-icon\" name=\"tabler:file-alert\" \u002F>\n        \u003Cp>暂无文章数据\u003C\u002Fp>\n        \u003Cp class=\"empty-hint\">请稍后再试\u003C\u002Fp>\n      \u003C\u002Fdiv>\n\n      \u003C!-- 作者模态框 - 时间线样式 -->\n      \u003CTransition name=\"modal\">\n        \u003Cdiv \n          v-if=\"showAvatarPopup && selectedAuthor && articlesByAuthor[selectedAuthor]\"\n          id=\"avatar-popup\"\n          class=\"modal\"\n          @click=\"closeAvatarPopup\"\n        >\n          \u003Cdiv class=\"modal-content\" @click.stop>\n            \u003Cdiv class=\"modal-header\">\n              \u003CNuxtImg \n                :src=\"selectedAuthorAvatar\" \n                :alt=\"selectedAuthor\"\n                loading=\"lazy\"\n              \u002F>\n              \u003Ch3>{{ selectedAuthor }}\u003C\u002Fh3>\n              \u003Ca \n                :href=\"selectedArticleLink\" \n                target=\"_blank\"\n                rel=\"noopener noreferrer\"\n                class=\"author-link\"\n              >\n                \u003CIcon name=\"lucide:external-link\" \u002F>\n              \u003C\u002Fa>\n            \u003C\u002Fdiv>\n            \u003Cdiv class=\"modal-body\">\n              \u003Cdiv class=\"timeline\">\n                \u003Cdiv \n                  v-for=\"(article, index) in articlesByAuthor[selectedAuthor].slice(0, 10)\"\n                  :key=\"article.id\"\n                  class=\"timeline-item\"\n                  :style=\"{ '--delay': (0.2 + index * 0.1) + 's' }\"\n                >\n                  \u003Cspan class=\"date\">{{ formatDate(article.created) }}\u003C\u002Fspan>\n                  \u003Ca \n                    :href=\"article.link\"\n                    target=\"_blank\"\n                    rel=\"noopener noreferrer\"\n                    class=\"article-title\"\n                    @click=\"closeAvatarPopup\"\n                  >\n                    {{ article.title }}\n                  \u003C\u002Fa>\n                \u003C\u002Fdiv>\n              \u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n            \u003Cdiv class=\"modal-avatar\">\n              \u003CNuxtImg \n                :src=\"selectedAuthorAvatar\" \n                :alt=\"selectedAuthor\"\n                loading=\"lazy\"\n              \u002F>\n            \u003C\u002Fdiv>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002FTransition>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.stats-bar {\n\talign-items: flex-end;\n\tcolor: #eee;\n\tdisplay: flex;\n\tflex-direction: column;\n\tfont-family: var(--font-monospace);\n\tfont-size: .7rem;\n\tgap: .1rem;\n\topacity: .7;\n\ttext-shadow: 0 4px 5px rgba(0, 0, 0, .5);\n\n\t.stats-update-time {\n\t\topacity: 1;\n\t}\n\n\t.stats-powered-by {\n\t\topacity: .8;\n\t}\n}\n\n.page-fcircle {\n\tanimation: float-in .2s backwards;\n\tmargin: 1rem;\n\n\t.random-article {\n\t\talign-items: center;\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\tgap: 10px;\n\t\tjustify-content: space-between;\n\t\tmargin: 1rem 0;\n\n\t\t.random-title {\n\t\t\tfont-size: 1.2rem;\n\t\t\twhite-space: nowrap;\n\t\t}\n\n\t\t.article-item {\n\t\t\tflex: 1;\n\t\t\tmin-width: 0;\n\n\t\t\t.article-container {\n\t\t\t\tmin-width: 0;\n\n\t\t\t\t.article-title {\n\t\t\t\t\toverflow: hidden;\n\t\t\t\t\ttext-overflow: ellipsis;\n\t\t\t\t\twhite-space: nowrap;\n\t\t\t\t}\n\n\t\t\t\t.article-author,\n\t\t\t\t.article-date {\n\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.refresh-btn {\n\t\t\talign-items: center;\n\t\t\tbackground-color: unset;\n\t\t\tborder-radius: 8px;\n\t\t\tbox-shadow: none;\n\t\t\tcolor: var(--c-text-2);\n\t\t\tcursor: pointer;\n\t\t\tdisplay: flex;\n\t\t\tflex-shrink: 0;\n\t\t\theight: 2.5rem;\n\t\t\tjustify-content: center;\n\t\t\ttransition: all .2s ease;\n\t\t\twidth: 2.5rem;\n\n\t\t\t&:hover {\n\t\t\t\tbackground-color: unset;\n\t\t\t}\n\t\t}\n\t}\n\n\t.articles-list {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: .5rem;\n\t}\n}\n\n.article-item {\n\talign-items: center;\n\tdisplay: flex;\n\tgap: 10px;\n\twidth: 100%;\n\n\t&.new-item {\n\t\tanimation: float-in .2s var(--delay) backwards;\n\t}\n\n\t.article-image {\n\t\tborder-radius: 50%;\n\t\tbox-shadow: 0 0 0 1px var(--c-bg-soft);\n\t\tdisplay: flex;\n\t\tflex-shrink: 0;\n\t\theight: 2rem;\n\t\toverflow: hidden;\n\t\twidth: 2rem;\n\n\t\timg {\n\t\t\theight: 100%;\n\t\t\tobject-fit: cover;\n\t\t\topacity: .8;\n\t\t\ttransition: all .2s;\n\t\t\twidth: 100%;\n\t\t}\n\t}\n\n\t.article-container {\n\t\talign-items: center;\n\t\tborder-radius: 8px;\n\t\tbox-shadow: 0 0 0 1px var(--c-bg-soft);\n\t\tdisplay: flex;\n\t\tgap: 5px;\n\t\toverflow: hidden;\n\t\tpadding: 10px;\n\t\twidth: 100%;\n\n\t\t&:hover .article-title {\n\t\t\tcolor: var(--c-text);\n\t\t}\n\n\t\t.article-author {\n\t\t\tcolor: var(--c-text-3);\n\t\t\tfont-size: .85rem;\n\t\t}\n\n\t\t.article-title {\n\t\t\tcolor: var(--c-text-2);\n\t\t\tflex: 1;\n\t\t\tfont-size: .9375rem;\n\t\t\toverflow: hidden;\n\t\t\ttext-overflow: ellipsis;\n\t\t\ttransition: color .2s;\n\t\t\twhite-space: nowrap;\n\t\t}\n\n\t\t.article-date {\n\t\t\tcolor: var(--c-text-3);\n\t\t\tfont-family: var(--font-monospace);\n\t\t\tfont-size: .75rem;\n\t\t}\n\t}\n}\n\n.load-more {\n\tbackground-color: var(--ld-bg-card);\n\tborder-radius: 8px;\n\tbox-shadow: .1em .2em .5rem var(--ld-shadow);\n\tdisplay: block;\n\tfont-size: .875rem;\n\theight: 42px;\n\tmargin: 1rem auto;\n\tpadding: .75rem;\n\twidth: 200px;\n\n\t&:hover {\n\t\tcolor: var(--c-text);\n\t}\n}\n\n\u002F* 模态框样式 *\u002F\n.modal {\n\talign-items: center;\n\tbackdrop-filter: blur(20px);\n\t-webkit-backdrop-filter: blur(20px);\n\tdisplay: flex;\n\tinset: 0;\n\tjustify-content: center;\n\tposition: fixed;\n\tz-index: 900;\n\n\t.modal-content {\n\t\tbackground-color: var(--c-bg-a50);\n\t\tborder-radius: 12px;\n\t\tbox-shadow: 0 0 0 1px var(--c-bg-soft);\n\t\tmax-height: 80vh;\n\t\tmax-width: 500px;\n\t\toverflow-y: auto;\n\t\tpadding: 1.25rem;\n\t\tposition: relative;\n\t\twidth: 90%;\n\n\t\t.modal-header {\n\t\t\talign-items: center;\n\t\t\tborder-bottom: 1px solid var(--c-bg-soft);\n\t\t\tdisplay: flex;\n\t\t\tgap: 15px;\n\t\t\tmargin-bottom: 20px;\n\t\t\tpadding-bottom: 15px;\n\n\t\t\timg {\n\t\t\t\tborder-radius: 50%;\n\t\t\t\theight: 50px;\n\t\t\t\tobject-fit: cover;\n\t\t\t\twidth: 50px;\n\t\t\t}\n\n\t\t\th3 {\n\t\t\t\tflex: 1;\n\t\t\t\tfont-size: 1.2rem;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.author-link {\n\t\t\t\tborder-radius: 8px;\n\t\t\t\tcolor: var(--c-text-2);\n\t\t\t\tpadding: 8px;\n\t\t\t\ttransition: all .3s;\n\n\t\t\t\t&:hover {\n\t\t\t\t\tbackground: var(--c-bg-soft);\n\t\t\t\t\tcolor: var(--c-text);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.modal-body {\n\t\t\t.timeline {\n\t\t\t\tposition: relative;\n\n\t\t\t\t&:after {\n\t\t\t\t\tbackground-color: var(--c-bg-soft);\n\t\t\t\t\tbottom: 0;\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tleft: .25rem;\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: .5rem;\n\t\t\t\t\ttransform: translate(-50%);\n\t\t\t\t\twidth: 2px;\n\t\t\t\t}\n\n\t\t\t\t.timeline-item {\n\t\t\t\t\tanimation: slideIn .3s ease-out both;\n\t\t\t\t\tcolor: var(--c-text-2);\n\t\t\t\t\tpadding: 0 0 1rem 1.25rem;\n\t\t\t\t\tposition: relative;\n\n\t\t\t\t\t&:before {\n\t\t\t\t\t\tbackground-color: var(--c-text-2);\n\t\t\t\t\t\tborder-radius: 50%;\n\t\t\t\t\t\tcontent: \"\";\n\t\t\t\t\t\theight: .5rem;\n\t\t\t\t\t\tleft: .25rem;\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\ttop: .5rem;\n\t\t\t\t\t\ttransform: translateY(-50%) translate(-50%);\n\t\t\t\t\t\ttransition: transform .3s ease, box-shadow .3s ease;\n\t\t\t\t\t\twidth: .5rem;\n\t\t\t\t\t\tz-index: 1;\n\t\t\t\t\t}\n\n\t\t\t\t\t&:hover:before {\n\t\t\t\t\t\tbox-shadow: 0 0 8px var(--c-text-2);\n\t\t\t\t\t\ttransform: translateY(-50%) translate(-50%) scale(1.5);\n\t\t\t\t\t}\n\n\t\t\t\t\t.date {\n\t\t\t\t\t\tcolor: var(--c-text-3);\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t\tfont-family: var(--font-monospace);\n\t\t\t\t\t\tfont-size: .875rem;\n\t\t\t\t\t\tmargin-bottom: .3rem;\n\t\t\t\t\t}\n\n\t\t\t\t\t.article-title {\n\t\t\t\t\t\tcolor: var(--c-text-2);\n\t\t\t\t\t\tline-height: 1.4;\n\t\t\t\t\t\ttransition: color .3s;\n\n\t\t\t\t\t\t&:hover {\n\t\t\t\t\t\t\tcolor: var(--c-text);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.modal-avatar {\n\t\t\tborder-radius: 50%;\n\t\t\tbottom: 1.25rem;\n\t\t\tfilter: blur(5px);\n\t\t\theight: 128px;\n\t\t\topacity: .6;\n\t\t\toverflow: hidden;\n\t\t\tpointer-events: none;\n\t\t\tposition: absolute;\n\t\t\tright: 1.25rem;\n\t\t\twidth: 128px;\n\t\t\tz-index: 1;\n\n\t\t\timg {\n\t\t\t\theight: 100%;\n\t\t\t\tobject-fit: cover;\n\t\t\t\twidth: 100%;\n\t\t\t}\n\t\t}\n\t}\n}\n\n@keyframes slideIn {\n\t0% {\n\t\topacity: 0;\n\t\ttransform: translateY(20px);\n\t}\n\tto {\n\t\topacity: 1;\n\t\ttransform: translateY(0);\n\t}\n}\n\n\u002F* 模态框过渡 *\u002F\n.modal-enter-active,\n.modal-enter-active .modal-content,\n.modal-leave-active,\n.modal-leave-active .modal-content {\n\ttransition: all .3s ease;\n}\n\n.modal-enter-from,\n.modal-leave-to {\n\topacity: 0;\n}\n\n.modal-enter-from .modal-content,\n.modal-leave-to .modal-content {\n\ttransform: translateY(-20px);\n}\n\n.modal-enter-to,\n.modal-leave-from {\n\topacity: 1;\n}\n\n.modal-enter-to .modal-content,\n.modal-leave-from .modal-content {\n\ttransform: translateY(0);\n}\n\n.loading-container {\n\talign-items: center;\n\tcolor: var(--c-text-2);\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\theight: 500px;\n\tjustify-content: center;\n\n\t.loading-spinner {\n\t\tanimation: spin 1s linear infinite;\n\t\tborder: 3px solid var(--c-bg-3);\n\t\tborder-radius: 50%;\n\t\tborder-top-color: var(--c-primary);\n\t\theight: 40px;\n\t\twidth: 40px;\n\t}\n}\n\n\u002F* 错误容器 *\u002F\n.error-container {\n\talign-items: center;\n\tcolor: var(--c-text-2);\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\theight: 500px;\n\tjustify-content: center;\n\n\t.error-icon {\n\t\tcolor: var(--c-danger);\n\t\tfont-size: 4rem;\n\t}\n}\n\n@keyframes spin {\n\tto {\n\t\ttransform: rotate(1turn);\n\t}\n}\n\n\u002F* 移动端适配 *\u002F\n@media (max-width: 768px) {\n\t.random-article .random-title {\n\t\tdisplay: none;\n\t}\n\n\t.page-fcircle .article-item .article-container {\n\t\tflex-wrap: wrap;\n\t\theight: auto;\n\t}\n\n\t.page-fcircle .article-item .article-container .article-author {\n\t\tflex-grow: 1;\n\t}\n\n\t.page-fcircle .article-item .article-container .article-title {\n\t\tflex-basis: 100%;\n\t\torder: 3;\n\t\twhite-space: normal;\n\t}\n}\n\u003C\u002Fstyle>\n",[52,232,230],{"__ignoreMap":47},[35,234,236],{"id":235},"添加-about-页面","添加 about 页面",[13,238,50,239,55,241],{},[52,240,54],{"code":54},[52,242,243],{"code":243},"about.vue",[60,245,248],{"className":246,"code":247,"language":65},[63],"\u003Cscript setup lang=\"ts\">\nconst { author } = useAppConfig()\nconst title = '关于我'\nconst description = '博主的个人介绍页面。'\nuseSeoMeta({ title, description, ogImage: author.avatar })\n\n\u002F\u002F 初始化统计数据\nconst statsData = ref({\n  today_uv: '加载中...',\n  today_pv: '加载中...',\n  yesterday_uv: '加载中...',\n  yesterday_pv: '加载中...',\n  last_month_pv: '加载中...',\n  last_year_pv: '加载中...'\n})\n\n\u002F\u002F 获取Umami统计数据\nonMounted(async () => {\n  try {\n    const response = await $fetch\u003Cany>('https:\u002F\u002Fstatus.chario.cn\u002Fapi\u002Fstats', {\n      method: 'GET',\n      headers: {\n        \u002F\u002F 如果需要认证，请添加相应的headers\n        \u002F\u002F 'Authorization': 'Bearer your-token-here'\n      }\n    })\n\n    if (response) {\n      statsData.value = {\n        today_uv: formatNumber(response.today_uv || 0),\n        today_pv: formatNumber(response.today_pv || 0),\n        yesterday_uv: formatNumber(response.yesterday_uv || 0),\n        yesterday_pv: formatNumber(response.yesterday_pv || 0),\n        last_month_pv: formatNumber(response.last_month_pv || 0),\n        last_year_pv: formatNumber(response.last_year_pv || 0)\n      }\n    }\n  } catch (error) {\n    console.error('获取统计数据失败:', error)\n  }\n})\n\n\u002F\u002F 格式化数字\nfunction formatNumber(num: number) {\n  if (num >= 10000) {\n    return `${(num \u002F 10000).toFixed(1)}万`\n  }\n  return num.toString()\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n\u003Ctemplate #aside>\n\t\u003CWidgetWelcomeVisitor \u002F>\n\t\u003CWidgetBlogStats \u002F>\n\t\u003CWidgetBlogTech \u002F>\n\t\u003CWidgetLatestComments \u002F>\n\t\u003CWidgetBlogLog \u002F>\n\t\u003CWidgetCommGroup \u002F>\n\u003C\u002Ftemplate>\n\n\u003Cdiv class=\"about-page\">\n  \u003Cdiv class=\"about-content\">\n    \u003C!-- 页面标题 -->\n    \u003Cheader class=\"about-header\">\n      \u003Cdiv class=\"header-avatar\">\n        \u003Cdiv class=\"avatar-frame\">\n          \u003Cimg :src=\"author.avatar\" alt=\"作者头像\" class=\"avatar-image\">\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"header-text\">\n        \u003Ch1>关于我\u003C\u002Fh1>\n        \u003Cp>总有些事情比永恒更重要！\u003C\u002Fp>\n      \u003C\u002Fdiv>\n    \u003C\u002Fheader>\n\n    \u003C!-- 卡片网格布局 -->\n    \u003Cdiv class=\"cards-grid\">\n      \u003C!-- 个人介绍卡片 -->\n      \u003Cdiv class=\"card intro-card\">\n        \u003Cp>您好，很高兴认识您！👋\u003C\u002Fp>\n        \u003Ch2>我叫 {{ author.name }}\u003C\u002Fh2>\n        \u003Cp>是一名 打工人、独立开发者、知喜博客管理员、博主。\u003C\u002Fp>\n        \u003CIcon name=\"tabler:user-circle\" class=\"card-bg-icon\" \u002F>\n      \u003C\u002Fdiv>\n\n      \u003C!-- 信息卡片 - 出生和年龄 -->\n      \u003Cdiv class=\"card info-card age-card\">\n        \u003Cdiv class=\"info-item special-info-item\">\n          \u003Cspan class=\"label\">出生\u003C\u002Fspan>\n          \u003Cspan class=\"value\">2005\u003C\u002Fspan>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"info-item special-info-item\">\n          \u003Cspan class=\"label\">当前\u003C\u002Fspan>\n          \u003Cspan class=\"value\">21岁 \u003CIcon name=\"ri:graduation-cap-line\" \u002F>\u003C\u002Fspan>\n        \u003C\u002Fdiv>\n        \u003CIcon name=\"tabler:calendar-week\" class=\"card-bg-icon\" \u002F>\n      \u003C\u002Fdiv>\n\n      \u003C!-- 座右铭卡片 -->\n      \u003Cdiv class=\"card motto-card\">\n        \u003Cspan class=\"label\">座右铭\u003C\u002Fspan>\n        \u003Cp>看五年 想三年\u003C\u002Fp>\n        \u003Cp>认认真真做好一两年\u003C\u002Fp>\n        \u003CIcon name=\"tabler:compass\" class=\"card-bg-icon\" \u002F>\n      \u003C\u002Fdiv>\n\n      \u003C!-- 关注偏好卡片 -->\n      \u003Cdiv class=\"card tech-card\">\n        \u003Cspan class=\"label\">关注偏好\u003C\u002Fspan>\n        \u003Ch3>文学领域\u003C\u002Fh3>\n        \u003Cp>看书、写文章\u003C\u002Fp>\n        \u003CIcon name=\"tabler:star-filled\" class=\"card-bg-icon\" \u002F>\n      \u003C\u002Fdiv>\n\n      \u003C!-- 音乐偏好卡片 -->\n      \u003Cdiv class=\"card music-card\">\n        \u003Cspan class=\"label\">音乐偏好\u003C\u002Fspan>\n        \u003Ch3>民谣、说唱\u003C\u002Fh3>\n        \u003Cp>等我喜欢就听\u003C\u002Fp>\n        \u003CIcon name=\"tabler:music\" class=\"card-bg-icon\" \u002F>\n      \u003C\u002Fdiv>\n\n      \u003C!-- 性格卡片 -->\n      \u003Cdiv class=\"card info-card personality-card\">\n        \u003Cspan class=\"label\">性格\u003C\u002Fspan>\n        \u003Cdiv class=\"content-center\">\n          \u003Cspan class=\"value\">提倡者\u003C\u002Fspan>\n          \u003Cspan class=\"value-small\">INFJ-T\u003C\u002Fspan>\n        \u003C\u002Fdiv>\n        \u003CProseA class=\"card-link\" href=\"https:\u002F\u002Fwww.16personalities.com\">在 16 Personalities 了解更多\u003C\u002FProseA>\n        \u003CIcon name=\"tabler:brain\" class=\"card-bg-icon\" \u002F>\n      \u003C\u002Fdiv>\n\n      \u003C!-- 特长卡片 -->\n      \u003Cdiv class=\"card specialty-card\">\n        \u003Cspan class=\"label\">特长\u003C\u002Fspan>\n        \u003Cp class=\"specialty-text\">\n          Linux、绘画\n        \u003C\u002Fp>\n        \u003Cp class=\"specialty-text\">\n          学习能力 \u003Cspan class=\"highlight\">MAX\u003C\u002Fspan>\n        \u003C\u002Fp>\n        \u003CIcon name=\"tabler:wand\" class=\"card-bg-icon\" \u002F>\n      \u003C\u002Fdiv>\n\n      \u003C!-- 联系方式卡片 -->\n      \u003Cdiv class=\"card contact-card\">\n        \u003Cspan class=\"label\">联系我\u003C\u002Fspan>\n        \u003Cdiv class=\"contact-links\">\n          \u003CZButton class=\"contact-link\" v-tip=\"'邮箱'\" icon=\"tabler:mail\" :to=\"`mailto:${author.email}`\" \u002F>\n          \u003CZButton class=\"contact-link\" v-tip=\"'微信'\" icon=\"tabler:brand-wechat\" to=\"https:\u002F\u002Fweixin.qq.com\" \u002F>\n          \u003CZButton class=\"contact-link\" v-tip=\"'哔哩哔哩'\" icon=\"ri:bilibili-fill\" to=\"https:\u002F\u002Fbilibili.com\" \u002F>\n          \u003CZButton class=\"contact-link\" v-tip=\"'Telegram'\" icon=\"tabler:brand-telegram\" to=\"https:\u002F\u002Ft.me\" \u002F>\n          \u003CZButton class=\"contact-link\" v-tip=\"'Discord'\" icon=\"tabler:brand-discord\" to=\"https:\u002F\u002Fdiscord.com\" \u002F>\n          \u003CZButton class=\"contact-link\" v-tip=\"'X'\" icon=\"tabler:brand-x\" to=\"https:\u002F\u002Fx.com\" \u002F>\n        \u003C\u002Fdiv>\n        \u003CIcon name=\"tabler:address-book\" class=\"card-bg-icon\" \u002F>\n      \u003C\u002Fdiv>\n\n      \u003C!-- 网站统计卡片 -->\n      \u003Cdiv class=\"card stats-card\">\n        \u003Cspan class=\"label\">网站统计\u003C\u002Fspan>\n        \u003Cdiv class=\"stats-content\">\n          \u003Cdiv class=\"stats-range-section\">\n            \u003Cdiv class=\"stats-grid\">\n              \u003Cdiv class=\"stat-item\">\n                \u003Cspan class=\"stat-value\">{{ statsData.today_pv }}\u003C\u002Fspan>\n                \u003Cspan class=\"stat-label\">浏览量\u003C\u002Fspan>\n              \u003C\u002Fdiv>\n              \u003Cdiv class=\"stat-item\">\n                \u003Cspan class=\"stat-value\">{{ statsData.today_uv }}\u003C\u002Fspan>\n                \u003Cspan class=\"stat-label\">访客数\u003C\u002Fspan>\n              \u003C\u002Fdiv>\n              \u003Cdiv class=\"stat-item\">\n                \u003Cspan class=\"stat-value\">{{ statsData.yesterday_pv }}\u003C\u002Fspan>\n                \u003Cspan class=\"stat-label\">访问次数\u003C\u002Fspan>\n              \u003C\u002Fdiv>\n              \u003Cdiv class=\"stat-item\">\n                \u003Cspan class=\"stat-value\">{{ statsData.yesterday_uv }}\u003C\u002Fspan>\n                \u003Cspan class=\"stat-label\">分钟停留\u003C\u002Fspan>\n              \u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003CIcon name=\"tabler:chart-histogram\" class=\"card-bg-icon\" \u002F>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\n\u003CPostComment \u002F>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n\u002F* 全局样式 *\u002F\n.about-page {\n  padding: 2rem 1rem;\n  min-height: calc(100vh - var(--header-height));\n  animation: float-in .3s backwards;\n}\n\n.about-content {\n  max-width: 1000px;\n  margin: 0 auto;\n}\n\n\u002F* 页面标题 *\u002F\n.about-header {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 3rem;\n  padding: 1rem 0;\n  text-align: center;\n\n  .header-text {\n    h1 {\n      margin-bottom: .5rem;\n      font-size: 2.5rem;\n      font-weight: 800;\n      color: var(--c-text-1);\n    }\n\n    p {\n      margin: 0;\n      font-size: 1.2rem;\n      color: var(--c-text-2);\n    }\n  }\n\n  .header-avatar {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    margin-bottom: 1.5rem; \n  }\n\n  .avatar-frame {\n    display: flex;\n    flex-shrink: 0;\n    align-items: center;\n    justify-content: center;\n    overflow: hidden;\n    width: 120px;\n    height: 120px;\n    border: 3px solid var(--c-border);\n    border-radius: 50%;\n    background-color: var(--c-bg-soft);\n    transition: all .3s ease;\n\n    @supports (corner-shape: squircle) {\n      corner-shape: superellipse(1.2);\n    }\n  }\n\n  .avatar-image {\n    display: block;\n    width: 100%;\n    height: 100%;\n    object-fit: cover;\n  }\n}\n\n\u002F* 卡片网格布局 *\u002F\n.cards-grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));\n  gap: 1.5rem;\n}\n\n\u002F* 通用卡片样式 *\u002F\n.card {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  position: relative;\n  overflow: hidden;\n  min-height: 220px;\n  padding: 2rem 1.5rem;\n  border: 1px solid var(--c-border);\n  border-radius: 1.5rem;\n  background-color: var(--ld-bg-card);\n  text-align: center;\n  transition: all .3s ease;\n  box-shadow: none;\n\n  &:hover {\n    box-shadow: none;\n    transform: none;\n  }\n\n  .label {\n    position: absolute;\n    opacity: .8;\n    top: 1rem;\n    left: 1.5rem;\n    margin: 0;\n    font-size: .8rem;\n    color: var(--c-text-2);\n  }\n\n  .card-bg-icon {\n    position: absolute;\n    opacity: .1;\n    right: 1rem;\n    bottom: 1rem;\n    font-size: 5rem;\n    color: var(--c-text-1);\n    pointer-events: none;\n  }\n}\n\n\u002F* 卡片类型样式 *\u002F\n\n\u002F* 个人介绍卡片 *\u002F\n.intro-card {\n  grid-column: 1 \u002F -1;\n  color: var(--c-text-1);\n\n  h2 {\n    margin: .5rem 0;\n    font-size: 3rem;\n    font-weight: bold;\n  }\n}\n\n\u002F* 信息卡片基类 *\u002F\n.info-card {\n  align-items: stretch;\n  justify-content: center;\n  padding: 2.5rem 1.5rem;\n  color: var(--c-text-1);\n\n  .info-item {\n    display: flex;\n    flex-direction: column;\n    flex-grow: 1;\n    align-items: flex-start;\n    justify-content: center;\n    position: relative;\n    width: 100%;\n\n    .label {\n      flex-shrink: 0;\n      position: static;\n      width: 100%;\n      margin-bottom: .5rem;\n      text-align: left;\n    }\n  }\n\n  .value {\n    display: block;\n    width: 100%;\n    font-size: 2.5rem;\n    font-weight: bold;\n    text-align: center;\n  }\n\n  .value-small {\n    display: block;\n    width: 100%;\n    font-size: 2rem;\n    font-weight: bold;\n    text-align: center;\n  }\n\n  .card-link {\n    position: absolute;\n    right: 1.5rem;\n    bottom: 1rem;\n    font-size: .8rem;\n    color: var(--c-text-2);\n\n    &:hover {\n      color: var(--c-primary);\n    }\n  }\n}\n\n\u002F* 年龄卡片 *\u002F\n.age-card {\n  padding: .4rem 1.5rem .5rem;\n}\n\n\u002F* 座右铭卡片 *\u002F\n.motto-card {\n  color: var(--c-text-1);\n\n  p {\n    margin: 0;\n    font-size: 2.5rem;\n    font-weight: bold;\n    line-height: 1.2;\n  }\n}\n\n\u002F* 关注偏好卡片 *\u002F\n.tech-card {\n  color: var(--c-text-1);\n\n  h3 {\n    margin: .5rem 0;\n    font-size: 3rem;\n    font-weight: bold;\n  }\n\n  p {\n    color: var(--c-text-2);\n  }\n}\n\n\u002F* 音乐偏好卡片 *\u002F\n.music-card {\n  color: var(--c-text-1);\n\n  h3 {\n    font-size: 2.5rem;\n    font-weight: bold;\n  }\n\n  p {\n    color: var(--c-text-2);\n  }\n}\n\n\u002F* 特长卡片 *\u002F\n.specialty-card {\n  font-size: 1.8rem;\n  font-weight: bold;\n  text-align: center;\n  color: var(--c-text-1);\n\n  .specialty-text {\n    margin: .2em 0;\n  }\n\n  .highlight {\n    display: inline-block;\n    font-size: 2.5rem;\n    line-height: 1;\n    color: var(--c-primary);\n  }\n}\n\n\u002F* 联系方式卡片 *\u002F\n.contact-card {\n  grid-column: 1 \u002F -1;\n  color: var(--c-text-1);\n\n  .contact-links {\n    display: flex;\n    justify-content: center;\n    flex-wrap: wrap;\n    max-width: 600px;\n    margin: 0 auto;\n  }\n\n  .contact-link {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    width: 50px;\n    height: 50px;\n    background-color: var(--c-bg-1);\n    border: 1px solid var(--c-border);\n    border-radius: 50%;\n    color: var(--c-text-1);\n    font-size: 1.4rem;\n    transition: all .2s ease;\n    padding: 0;\n    box-shadow: none;\n\n    &:hover {\n      background-color: var(--c-bg-soft);\n      color: var(--c-text);\n    }\n  }\n}\n\n\u002F* 网站统计卡片 *\u002F\n.stats-card {\n  grid-column: 1 \u002F -1;\n  color: var(--c-text-1);\n}\n\n.stats-content {\n  width: 100%;\n}\n\n.stats-range-section {\n  margin-bottom: 0;\n}\n\n.stats-grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));\n  gap: 1rem;\n  margin-bottom: 0;\n}\n\n.stat-item {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  padding: .5rem;\n  background-color: var(--c-bg-1);\n  border-radius: .8rem;\n  transition: transform .2s ease;\n}\n\n.stat-value {\n  margin-bottom: .25rem;\n  font-size: 2rem;\n  font-weight: bold;\n  color: var(--c-text-1);\n}\n\n.stat-label {\n  opacity: .9;\n  font-size: .9rem;\n  color: var(--c-text-2);\n}\n\n\u002F* 动画效果 *\u002F\n@keyframes float-in {\n  from {\n    opacity: 0;\n    transform: translateY(10px);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n\u002F* 响应式布局 *\u002F\n@media (max-width: 768px) {\n  .about-page {\n    padding: 1rem;\n  }\n  \n  .about-header {\n    flex-direction: column;\n    text-align: center;\n    margin-bottom: 2rem;\n  }\n  \n  .about-header .header-text {\n    margin-bottom: 1.5rem;\n  }\n  \n  .avatar-frame {\n    width: 100px;\n    height: 100px;\n  }\n  \n  .card {\n    padding: 1.5rem 1rem;\n  }\n  \n  .cards-grid {\n    gap: 1rem;\n  }\n\n  .age-card {\n    padding: .4rem 1.5rem .5rem;\n  }\n\n  .contact-card .contact-link {\n    width: 45px;\n    height: 45px;\n    font-size: 1.2rem;\n  }\n\n  .stats-card {\n    padding: 3rem 1rem 1.5rem;\n  }\n\n  .stats-grid {\n    grid-template-columns: repeat(2, 1fr);\n  }\n}\n\n\u002F* 暗黑模式支持 *\u002F\n:deep(.dark-mode) {\n  .card {\n    border-color: var(--c-border);\n    background-color: var(--c-bg-2);\n  }\n\n  .label, .card-bg-icon, .tech-card p, .stat-label {\n    color: var(--c-text-2);\n  }\n\n  .info-card .card-link {\n    color: var(--c-text-2);\n\n    &:hover {\n      color: var(--c-primary);\n    }\n  }\n\n  .specialty-card .highlight {\n    color: var(--c-primary);\n  }\n\n  .contact-card .contact-link {\n    background-color: var(--c-bg-1);\n    border-color: var(--c-border);\n    color: var(--c-text-1);\n\n    &:hover {\n      background-color: var(--c-bg-soft);\n      color: var(--c-text);\n      transform: translateY(-2px);\n      border-color: var(--c-border);\n    }\n  }\n\n  .stat-item {\n    background-color: var(--c-bg-1);\n  }\n\n  .avatar-frame {\n    border-color: var(--c-border);\n    background-color: var(--c-bg-soft);\n  }\n}\n\u003C\u002Fstyle>\n\n\u003Cstyle>\n.dark .tippy-box {\n  background-color: var(--c-bg-2);\n\n  .tippy-svg-arrow {\n    fill: var(--c-bg-2);\n  }\n}\n\u003C\u002Fstyle>\n",[52,249,247],{"__ignoreMap":47},[35,251,252],{"id":252},"添加留言板页面",[13,254,50,255,55,257],{},[52,256,54],{"code":54},[52,258,259],{"code":259},"comments.vue",[60,261,264],{"className":262,"code":263,"language":65},[63],"\u003Cscript setup lang=\"ts\">\nconst title = '留言板'\nconst description = '您可以在此畅所欲言，当然，请遵守互联网相关的法律法规'\nconst image = 'https:\u002F\u002Fbu.dusays.com\u002F2025\u002F10\u002F21\u002F68f7083fcc594.webp'\nuseSeoMeta({ title, description, ogImage: image })\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n\u003Ctemplate #aside>\n\t\u003CWidgetBlogStats \u002F>\n\t\u003CWidgetBlogTech \u002F>\n\t\u003CWidgetBlogLog \u002F>\n\u003C\u002Ftemplate>\n\n\u003CZPageBanner :title :description :image \u002F>\n\n\u003Cdiv class=\"page-comments\">\n  \u003CQuote>{{ description }}\u003C\u002FQuote>\n\n  \u003CQuote icon=\"tabler:files\">\n    如果您有什么想说的、想问的，或者发现了本站的小问题🐞欢迎留言\n  \u003C\u002FQuote>\n  \n  \u003CQuote icon=\"tabler:link\">\n    需要添加友链？请移步\u003CProseA :href=\"'\u002Flink'\" class=\"link-text\">友链\u003C\u002FProseA>页面\n  \u003C\u002FQuote>\n\u003C\u002Fdiv>\n\n\u003CPostComment \u002F>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.page-comments {\n  animation: float-in .2s backwards;\n  margin: 1rem;\n  text-align: center;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: 1.5rem;\n\n  .quote {\n    max-width: 700px;\n    width: 100%;\n    color: var(--c-text-2);\n    font-size: 1.2rem;\n    font-weight: var(--font-weight-medium);\n    line-height: 1.5;\n    text-align: center;\n  }\n}\n\n.link-text {\n  color: inherit;\n  transition: background-size 0.3s ease;\n\n  &:hover {\n    color: var(--c-primary);\n    text-decoration: none;\n    background: linear-gradient(var(--c-primary-soft), var(--c-primary-soft)) no-repeat bottom\u002F100% 30%;\n  }\n}\n\u003C\u002Fstyle>\n",[52,265,263],{"__ignoreMap":47},[25,267,268],{"id":268},"侧边栏",[35,270,271],{"id":271},"如何添加自定义侧边栏",[13,273,274,275,278],{},"修改",[52,276,277],{"code":277},"\u002Fapp\u002Fcomposables\u002FuseWidgets.ts","，以恋爱墙为例。",[60,280,283],{"className":281,"code":282,"language":65},[63],"import {\n    LazyWidgetBlogLog,\n    LazyWidgetBlogStats,\n    LazyWidgetBlogTech,\n    LazyWidgetCommGroup,\n    LazyWidgetEmpty,\n    LazyWidgetGithubCard,\n    LazyWidgetToc,\n    LazyWidgetLoveWall, \u002F\u002F 恋爱墙\n} from '#components'\nimport { pascal } from 'radash'\n\n\u002F\u002F @keep-sorted\nconst rawWidgets = {\n    LazyWidgetBlogLog,\n    LazyWidgetBlogStats,\n    LazyWidgetBlogTech,\n    LazyWidgetCommGroup,\n    LazyWidgetEmpty,\n    LazyWidgetGithubCard,\n    LazyWidgetToc,\n    LazyWidgetLoveWall, \u002F\u002F 恋爱墙\n}\n",[52,284,282],{"__ignoreMap":47},[13,286,287,288,291],{},"在你需要添加的page页面修改对应的vue，",[52,289,290],{"code":290},"\u002Fapp\u002Fpages\u002Findex.vue","是文章首页.",[60,293,296],{"className":294,"code":295,"language":65},[63],"\u002F\u002F 修改下列参数，'WidgetBlogStats'在上方就是上方'WidgetLoveWall'则是最底下，可自行调整侧边栏顺序\n\u003Ctemplate #aside>\n    \u003CWidgetBlogStats \u002F>\n    \u003CWidgetBlogTech \u002F>\n    \u003CWidgetBlogLog \u002F>\n    \u003CWidgetCommGroup \u002F>\n    \u003CWidgetLoveWall \u002F> \u002F\u002F 恋爱墙\n\u003C\u002Ftemplate>\n",[52,297,295],{"__ignoreMap":47},[35,299,300],{"id":300},"添加访客欢迎显示",[13,302,50,303,55,306],{},[52,304,305],{"code":305},"\u002Fapp\u002Fcomponents\u002Fwidget\u002F",[52,307,308],{"code":308},"WelcomeVisitor.vue",[60,310,313],{"className":311,"code":312,"language":65},[63],"\u003Cscript setup lang=\"ts\">\nimport { computed, onMounted, ref, shallowRef } from 'vue'\n\n\u002F\u002F 类型定义\ninterface IPLocation {\n\tip: string\n\tbeginip: string\n\tendip: string\n\tregion: string\n\tisp: string\n\tasn: string\n\tllc: string\n\tlatitude: number\n\tlongitude: number\n}\n\n\u002F\u002F 常量配置\nconst CACHE_KEY = 'visitor_region_info_cache'\nconst CACHE_DURATION = 10 * 60  * 1000\nconst API_URL = 'https:\u002F\u002Fuapis.cn\u002Fapi\u002Fv1\u002Fnetwork\u002Fmyip'\nconst REFERENCE_LNG = 120.3074357\nconst REFERENCE_LAT = 31.4933074\nconst EARTH_RADIUS = 6371\nconst DEFAULT_WELCOME_TEXT = '欢迎客官来到本站'\n\n\u002F\u002F 欢迎语映射数据\nconst provinceWelcomeMap: Record\u003Cstring, string> = {\n\t北京市: '北——京——欢迎你~~~',\n\t天津市: '讲段相声吧',\n\t河北: '燕赵大地，英雄辈出的河北，等你探索！',\n\t山西: '展开坐具长三尺，已占山河五百余',\n\t内蒙古自治区: '草原辽阔的内蒙古，等你来策马奔腾。',\n\t辽宁: '我想吃烤鸡架！',\n\t吉林: '状元阁就是东北烧烤之王',\n\t黑龙江: '很喜欢哈尔滨大剧院',\n\t上海市: '走在外滩，感受历史与现代的交融。',\n\t江苏: '水乡泽国，江南佳丽地。',\n\t浙江: '这里是浙江，充满江南的韵味！',\n\t河南: '这里是河南，历史悠久文化灿烂。',\n\t安徽: '安徽山水，黄山、九华山欢迎你。',\n\t福建: '福建山水如画，美景无处不在。',\n\t江西: '落霞与孤鹜齐飞，秋水共长天一色',\n\t山东: '山东好客，欢迎来感受齐鲁文化！',\n\t湖北: '湖北，长江中游的明珠，风景秀丽。',\n\t湖南: '湖南，烟雨迷蒙的湘江流过这片土地。',\n\t广东: '带你感受广东的热情与美食！',\n\t广西壮族自治区: '广西山清水秀，民俗风情浓郁。',\n\t海南: '阳光、沙滩、椰风海韵，欢迎来海南度假。',\n\t四川: '来四川，品麻辣火锅，赏壮丽山河。',\n\t贵州: '来贵州，品茅台，赏黄果树瀑布。',\n\t云南: '云南风景独特，风情万种。',\n\t西藏自治区: '西藏，神秘而纯净，等待你的探索。',\n\t陕西: '陕西，历史与文化的交汇之地。',\n\t甘肃: '甘肃，丝绸之路的重要节点。',\n\t青海: '青海，湖泊与草原的美丽结合。',\n\t宁夏回族自治区: '宁夏，塞上江南，黄河流经的美丽地方。',\n\t新疆维吾尔自治区: '新疆的城市各具特色，等待你的探索。',\n\t台湾: '我在这头，大陆在那头',\n\t香港特别行政区: '永定贼有残留地鬼嚎，迎击光非岁玉',\n\t澳门特别行政区: '性感荷官，在线发牌',\n}\n\n\u002F\u002F 城市特定欢迎语映射\nconst cityWelcomeMap: Record\u003Cstring, Record\u003Cstring, string>> = {\n\t北京市: {\n\t\t北京市: '北——京——欢迎你~~~',\n\t},\n\t上海市: {\n\t\t上海市: '走在外滩，感受历史与现代的交融。',\n\t},\n\t广东: {\n\t\t广州市: '看小蛮腰，喝早茶了嘛~',\n\t\t深圳市: '今天你逛商场了嘛~',\n\t\t珠海市: '浪漫之城珠海，海风轻拂。',\n\t\t东莞市: '东莞，制造业之都，经济活跃。',\n\t\t佛山市: '佛山，武术之乡，陶瓷文化深厚。',\n\t},\n\t浙江: {\n\t\t杭州市: '西湖美景，三月天~',\n\t\t宁波市: '来宁波，感受大海的气息。',\n\t\t温州市: '温州人杰地灵，商贸繁荣。',\n\t\t绍兴市: '绍兴，酒乡文化，古韵悠长。',\n\t\t湖州市: '湖州，太湖之滨，风景如画。',\n\t},\n\t四川: {\n\t\t成都市: '宽窄巷子，成都慢生活。',\n\t\t绵阳市: '享受科技城的宁静与创新。',\n\t\t自贡市: '自贡的盐文化与灯会，独具魅力。',\n\t\t德阳市: '德阳，历史悠久，文化底蕴深厚。',\n\t\t乐山市: '乐山大佛，世界文化遗产。',\n\t},\n\t福建: {\n\t\t厦门市: '鼓浪屿听海，厦门美食让人流连忘返。',\n\t\t福州市: '有福之州，来此感受千年古城。',\n\t\t泉州市: '泉州，海上丝绸之路的起点。',\n\t\t漳州市: '漳州，古城文化与美食的结合。',\n\t\t南平市: '南平，武夷山的自然风光。',\n\t},\n\t山东: {\n\t\t青岛市: '来青岛喝啤酒，看大海吧！',\n\t\t济南市: '泉城济南，四面荷花三面柳。',\n\t\t烟台市: '烟台的葡萄酒与海鲜，令人陶醉。',\n\t\t潍坊市: '潍坊，风筝之都，文化底蕴深厚。',\n\t\t德州市: '德州，扒鸡闻名，文化悠久。',\n\t},\n\t江苏: {\n\t\t南京市: '六朝古都南京，历史与现代的碰撞。',\n\t\t苏州市: '来苏州，感受园林之美。',\n\t\t无锡市: '无锡太湖美景，灵山大佛令人心旷神怡。',\n\t\t常州市: '常州，文化与科技的交汇点。',\n\t\t南通市: '南通，海门潮涌，文化底蕴深厚。',\n\t},\n\t河北: {\n\t\t石家庄市: '燕赵大地，英雄辈出的河北，等你探索！',\n\t},\n\t河南: {\n\t\t郑州市: '中原大地，郑州是交通枢纽与历史重镇。',\n\t\t洛阳市: '千年古都洛阳，牡丹花开的城。',\n\t\t开封市: '开封，古都文化与美食的汇聚地。',\n\t\t新乡市: '新乡，历史悠久，文化底蕴深厚。',\n\t\t焦作市: '焦作，云台山的自然风光。',\n\t},\n\t湖南: {\n\t\t长沙市: '热辣长沙，吃小龙虾逛黄兴路步行街。',\n\t\t岳阳市: '岳阳楼，洞庭湖的美景尽收眼底。',\n\t\t株洲市: '株洲，火车制造业的发源地。',\n\t\t湘潭市: '湘潭，伟人故里，文化底蕴深厚。',\n\t},\n\t湖北: {\n\t\t武汉市: '来大武汉，过长江大桥，吃热干面！',\n\t\t宜昌市: '三峡大坝，壮丽的自然奇观。',\n\t\t荆州市: '荆州，历史文化名城，古韵悠长。',\n\t\t襄阳市: '襄阳，古城文化与美食的结合。',\n\t},\n\t安徽: {\n\t\t合肥市: '创新之城合肥，科教文化汇聚地。',\n\t\t黄山市: '黄山，天下第一奇山，风景如画。',\n\t\t芜湖市: '芜湖，长江之畔，文化底蕴深厚。',\n\t\t马鞍山市: '马鞍山，文化与自然的完美结合。',\n\t},\n\t广西壮族自治区: {\n\t\t桂林市: '桂林山水甲天下，风景如画。',\n\t\t南宁市: '绿城南宁，宜居宜游。',\n\t\t柳州市: '柳州的螺蛳粉，独具风味。',\n\t\t防城港市: '防城港，海洋资源丰富，风景迷人。',\n\t},\n\t贵州: {\n\t\t贵阳市: '贵阳，山城之美，民族风情浓郁。',\n\t\t遵义市: '遵义，红色之城，历史悠久。',\n\t\t安顺市: '安顺，黄果树瀑布的故乡，风景如画。',\n\t\t毕节市: '毕节，拥有丰富的自然资源与人文景观。',\n\t\t六盘水市: '六盘水，凉爽的夏天，避暑胜地。',\n\t\t铜仁市: '铜仁，秀美的山水与独特的民族文化。',\n\t\t凯里市: '凯里，苗族文化的发源地，风情独特。',\n\t},\n\t云南: {\n\t\t昆明市: '春城昆明，四季如春，风景秀丽。',\n\t\t大理市: '苍山洱海，大理古城，你来了就不想走。',\n\t\t丽江市: '丽江古城，纳西文化的瑰宝。',\n\t\t西双版纳傣族自治州: '西双版纳，热带雨林的奇妙之地。',\n\t},\n\t西藏自治区: {\n\t\t拉萨市: '拉萨，西藏的首府，布达拉宫的故乡。',\n\t\t日喀则市: '日喀则，历史悠久的文化名城。',\n\t\t林芝市: '林芝，素有\"西藏江南\"之称，风景如画。',\n\t\t昌都市: '昌都，历史悠久，文化底蕴深厚。',\n\t\t山南市: '山南，藏文化的发源地之一。',\n\t\t那曲市: '那曲，草原风光，牧民生活的地方。',\n\t\t阿里地区: '阿里，神秘的西部，拥有壮丽的自然景观。',\n\t},\n\t新疆维吾尔自治区: {\n\t\t乌鲁木齐市: '乌鲁木齐，天山脚下的城市，文化多元。',\n\t\t喀什市: '喀什，古丝绸之路的重要节点，历史悠久。',\n\t\t克拉玛依市: '克拉玛依，石油之城，经济发展迅速。',\n\t\t吐鲁番市: '吐鲁番，火焰山的故乡，葡萄之乡。',\n\t\t哈密市: '哈密，哈密瓜的发源地，风景如画。',\n\t\t博乐市: '博乐，草原风光，民族文化交融。',\n\t\t阿克苏市: '阿克苏，苹果之乡，风景秀丽。',\n\t\t和田市: '和田，玉石之乡，历史文化深厚。',\n\t},\n\t内蒙古自治区: {\n\t\t呼和浩特市: '呼和浩特，内蒙古的首府，历史悠久。',\n\t\t包头市: '包头，钢铁之城，经济发展迅速。',\n\t\t乌兰察布市: '乌兰察布，草原文化与现代城市的结合。',\n\t\t赤峰市: '赤峰，拥有丰富的自然资源与人文景观。',\n\t\t通辽市: '通辽，草原文化的发源地，风情独特。',\n\t\t鄂尔多斯市: '鄂尔多斯，现代化城市与草原文化的交融。',\n\t\t巴彦淖尔市: '巴彦淖尔，黄河之畔，风景如画。',\n\t\t锡林郭勒盟: '锡林郭勒，草原辽阔，马背上的民族风情。',\n\t},\n\t陕西: {\n\t\t西安市: '西安，古都文化与兵马俑的故乡。',\n\t\t咸阳市: '咸阳，历史悠久，文化底蕴深厚。',\n\t},\n\t甘肃: {\n\t\t兰州市: '兰州，黄河之滨，牛肉面闻名。',\n\t\t天水市: '天水，历史悠久，文化底蕴深厚。',\n\t},\n\t青海: {\n\t\t西宁市: '西宁，青海湖的门户，风景如画。',\n\t},\n\t吉林: {\n\t\t长春市: '长春，汽车城，文化底蕴深厚。',\n\t\t吉林市: '吉林市，松花江畔，风景如画。',\n\t},\n\t黑龙江: {\n\t\t哈尔滨市: '哈尔滨，冰雪之城，俄罗斯风情浓厚。',\n\t\t齐齐哈尔市: '齐齐哈尔，黑龙江的明珠，文化底蕴深厚。',\n\t},\n\t山西: {\n\t\t太原市: '太原，山西会，历史悠久。',\n\t},\n\t辽宁: {\n\t\t沈阳市: '沈阳，重工业基地，文化底蕴深厚。',\n\t\t大连市: '大连，北方明珠，浪漫之都。',\n\t},\n\t海南: {\n\t\t海口市: '海口，阳光沙滩，椰风海韵。',\n\t\t三亚市: '三亚，碧海蓝天，度假胜地。',\n\t},\n}\n\n\u002F\u002F 国家欢迎语映射\nconst countryWelcomeMap: Record\u003Cstring, string> = {\n\t日本: 'よろしく，一起去看樱花吗',\n\t美国: 'Let us live in peace!',\n\t英国: '想同你一起夜乘伦敦眼',\n\t俄罗斯: '干了这瓶伏特加！',\n\t法国: 'C\\'est La Vie',\n\t德国: 'Die Zeit verging im Fluge.',\n\t澳大利亚: '一起去大堡礁吧！',\n\t加拿大: '拾起一片枫叶赠予你',\n\t韩国: '一起去首尔购物吧！',\n\t新加坡: '花园城市新加坡，欢迎你！',\n\t印度: '探索神秘的咖喱之国！',\n\t巴西: '一起感受桑巴的热情！',\n\t意大利: '罗马假日，浪漫之旅！',\n\t西班牙: '弗拉门戈的舞步等你来！',\n\t荷兰: '风车与郁金香的国度！',\n\t瑞士: '阿尔卑斯山的雪景等你欣赏！',\n\t新西兰: '霍比屯的中土世界！',\n\t泰国: '萨瓦迪卡，一起去海滩吧！',\n\t马来西亚: '多元文化，美食天堂！',\n\t印度尼西亚: '千岛之国，潜水胜地！',\n\t菲律宾: '椰风海韵，度假天堂！',\n\t越南: '河内风情，岘港之美！',\n\t土耳其: '热气球之旅，卡帕多奇亚！',\n\t阿联酋: '奢华迪拜，沙漠奇迹！',\n\t沙特阿拉伯: '麦加朝圣，沙漠王国！',\n\t埃及: '金字塔，法老的呼唤！',\n\t南非: '彩虹之国，野生动物的天堂！',\n\t肯尼亚: '动物大迁徙，东非草原！',\n\t尼日利亚: '西非明珠，活力之国！',\n\t墨西哥: '玛雅文明，亡灵之城！',\n\t阿根廷: '探戈与足球的故乡！',\n\t智利: '复活节岛，葡萄酒之乡！',\n\t哥伦比亚: '咖啡之国，南美风情！',\n\t秘鲁: '马丘比丘，印加帝国！',\n\t希腊: '爱琴海畔，古典文明！',\n\t葡萄牙: '大航海时代的荣光！',\n\t比利时: '巧克力与啤酒的国度！',\n\t奥地利: '音乐之都，维也纳之声！',\n\t捷克: '布拉格广场上的钟声！',\n\t波兰: '肖邦的故乡，琥珀之路！',\n\t瑞典: '诺贝尔的故乡，北欧设计！',\n\t挪威: '峡湾与极光的胜地！',\n\t丹麦: '童话王国的魅力！',\n\t芬兰: '千湖之国，极光之地！',\n\t冰岛: '冰与火之国，温泉与瀑布！',\n\t爱尔兰: '绿岛之歌，威士忌之乡！',\n\t匈牙利: '多瑙河畔，温泉之国！',\n\t罗马尼亚: '德古拉城堡，特兰西瓦尼亚！',\n\t乌克兰: '黑海沿岸，葵花之乡！',\n\t哈萨克斯坦: '丝绸之路，中亚明珠！',\n\t巴基斯坦: '喜马拉雅的风光！',\n\t孟加拉国: '恒河三角洲，孟加拉湾！',\n\t斯里兰卡: '印度洋上的珍珠！',\n\t尼泊尔: '徒步天堂，雪山之国！',\n\t阿富汗: '兴都库什的呼唤！',\n\t伊朗: '波斯帝国的辉煌！',\n\t伊拉克: '两河流域，文明古国！',\n\t以色列: '圣城耶路撒冷！',\n\t约旦: '佩特拉古城，死海漂浮！',\n\t黎巴嫩: '中东小巴黎，美食天堂！',\n\t卡塔尔: '石油王国，沙漠明珠！',\n\t科威特: '石油之都，海湾风情！',\n\t巴林: '波斯湾的金融中心！',\n\t阿曼: '阿拉伯半岛的绿色角落！',\n\t厄瓜多尔: '赤道之国，亚马逊雨林！',\n\t乌拉圭: '南美瑞士，足球强国！',\n\t巴拉圭: '伊瓜苏瀑布的故乡！',\n\t委内瑞拉: '石油之国，加拉加斯！',\n\t玻利维亚: '天空之镜，乌尤尼盐沼！',\n\t巴拿马: '运河之都，连接两洋！',\n\t哥斯达黎加: '中美洲瑞士，生态旅游！',\n\t古巴: '加勒比海的雪茄与朗姆！',\n\t牙买加: '雷鬼音乐，蓝山咖啡！',\n\t多米尼加: '加勒比海度假胜地！',\n\t马达加斯加: '狐猴之岛，独一无二！',\n\t坦桑尼亚: '塞伦盖蒂，动物王国！',\n\t加纳: '黄金海岸，可可之国！',\n\t喀麦隆: '非洲缩影，文化熔炉！',\n\t摩洛哥: '摩洛哥风情，撒哈拉沙漠！',\n\t突尼斯: '迦太基古城，地中海明珠！',\n}\n\n\u002F\u002F 响应式数据\nconst userLocation = shallowRef\u003CIPLocation | null>(null)\nconst welcomeText = ref(DEFAULT_WELCOME_TEXT)\nconst distance = ref\u003Cnumber | null>(null)\nconst loading = ref(true)\nconst errorMessage = ref\u003Cstring | null>(null)\n\n\u002F\u002F 计算属性\nconst locationDescription = computed(() => {\n\tif (!userLocation.value?.region)\n\t\treturn '未知位置'\n\n\tconst regionParts = userLocation.value.region.split(' ')\n\tconst country = regionParts[0] || ''\n\tconst province = regionParts[1] || ''\n\tconst city = regionParts[2] || ''\n\n\tif (country === '中国') {\n\t\treturn province === city ? province : `${province} ${city}`\n\t}\n\telse {\n\t\treturn country\n\t}\n})\n\n\u002F\u002F 时间段问候语\nconst timeGreeting = computed(() => {\n\tconst hour = new Date().getHours()\n\n\tif (hour >= 5 && hour \u003C 11)\n\t\treturn '🌤️ 早上好，一日之计在于晨！'\n\tif (hour >= 11 && hour \u003C 13)\n\t\treturn '☀️ 中午好，记得午休喔~'\n\tif (hour >= 13 && hour \u003C 17)\n\t\treturn '🕞 下午好，饮茶先啦！'\n\tif (hour >= 17 && hour \u003C 19)\n\t\treturn '🚶‍♂️ 即将下班，记得按时吃饭~'\n\tif (hour >= 19 && hour \u003C 24)\n\t\treturn '🌙 晚上好，夜生活嗨起来！'\n\treturn '夜深了，早点休息，少熬夜哦~'\n})\n\nfunction getDistance(lng1: number, lat1: number, lng2: number, lat2: number): number {\n\tconst dLat = (lat2 - lat1) * Math.PI \u002F 180\n\tconst dLng = (lng2 - lng1) * Math.PI \u002F 180\n\tconst a = Math.sin(dLat \u002F 2) * Math.sin(dLat \u002F 2)\n\t\t+ Math.cos(lat1 * Math.PI \u002F 180) * Math.cos(lat2 * Math.PI \u002F 180)\n\t\t* Math.sin(dLng \u002F 2) * Math.sin(dLng \u002F 2)\n\tconst c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))\n\treturn Math.round(EARTH_RADIUS * c)\n}\n\n\u002F\u002F 缓存管理\nfunction getIpInfoFromCache(): IPLocation | null {\n\ttry {\n\t\tconst cached = localStorage.getItem(CACHE_KEY)\n\t\tif (!cached)\n\t\t\treturn null\n\n\t\tconst { data, timestamp } = JSON.parse(cached)\n\t\tif (Date.now() - timestamp > CACHE_DURATION) {\n\t\t\tlocalStorage.removeItem(CACHE_KEY)\n\t\t\treturn null\n\t\t}\n\t\treturn data\n\t}\n\tcatch (error) {\n\t\tconsole.error('读取缓存失败:', error)\n\t\treturn null\n\t}\n}\n\nfunction setIpInfoToCache(data: IPLocation) {\n\ttry {\n\t\tlocalStorage.setItem(CACHE_KEY, JSON.stringify({\n\t\t\tdata,\n\t\t\ttimestamp: Date.now(),\n\t\t}))\n\t}\n\tcatch (error) {\n\t\tconsole.error('保存缓存失败:', error)\n\t}\n}\n\n\u002F\u002F 业务逻辑\n\u002F\u002F 根据用户位置设置欢迎语\nfunction setWelcomeText(location: IPLocation) {\n\tif (!location?.region)\n\t\treturn\n\n\tconst regionParts = location.region.split(' ')\n\tconst country = regionParts[0] || ''\n\tconst province = regionParts[1] || ''\n\tconst city = regionParts[2] || ''\n\n\tif (country === '中国') {\n\t\t\u002F\u002F 优先使用城市特定欢迎语，否则使用份默认欢迎语\n\t\twelcomeText.value = cityWelcomeMap[province]?.[city]\n\t\t\t|| provinceWelcomeMap[province]\n\t\t\t|| '带我去你的城市逛逛吧！'\n\t}\n\telse {\n\t\t\u002F\u002F 其他国家的欢迎语\n\t\twelcomeText.value = countryWelcomeMap[country]\n\t\t\t|| '带我去你的国家逛逛吧'\n\t}\n}\n\n\u002F\u002F 计算用户与博主位置的距离\nfunction calculateDistance(data: IPLocation) {\n\ttry {\n\t\tconst { latitude, longitude } = data\n\n\t\t\u002F\u002F 检查经纬度是否有效\n\t\tif (latitude && longitude) {\n\t\t\tdistance.value = getDistance(REFERENCE_LNG, REFERENCE_LAT, longitude, latitude)\n\t\t}\n\t\telse {\n\t\t\tdistance.value = null\n\t\t}\n\t}\n\tcatch (error) {\n\t\tconsole.error('计算距离失败:', error)\n\t\tdistance.value = null\n\t}\n}\n\n\u002F\u002F 获取用户IP位置信息\nasync function fetchIPLocation() {\n\t\u002F\u002F 重置状态\n\tloading.value = true\n\terrorMessage.value = null\n\n\ttry {\n\t\t\u002F\u002F 1. 尝试从缓存获取数据\n\t\tconst cachedData = getIpInfoFromCache()\n\t\tif (cachedData) {\n\t\t\tuserLocation.value = cachedData\n\t\t\tsetWelcomeText(cachedData)\n\t\t\tcalculateDistance(cachedData)\n\t\t\treturn\n\t\t}\n\n\t\t\u002F\u002F 2. 从API获取新数据\n\t\tconst controller = new AbortController()\n\t\tconst timeoutId = setTimeout(() => controller.abort(), 3000)\n\n\t\tconst response = await fetch(API_URL, { signal: controller.signal })\n\n\t\tclearTimeout(timeoutId)\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`HTTP错误! 状态码: ${response.status}`)\n\t\t}\n\n\t\tconst data: IPLocation = await response.json()\n\n\t\t\u002F\u002F 处理响应数据\n\t\tif (data.ip && data.region) {\n\t\t\t\u002F\u002F 保存数据到缓存\n\t\t\tsetIpInfoToCache(data)\n\n\t\t\t\u002F\u002F 更新组件状态\n\t\t\tuserLocation.value = data\n\t\t\tsetWelcomeText(data)\n\t\t\tcalculateDistance(data)\n\t\t}\n\t}\n\tcatch (error) {\n\t\t\u002F\u002F 错误处理\n\t\tif (error instanceof Error) {\n\t\t\tif (error.name === 'AbortError') {\n\t\t\t\terrorMessage.value = '请求超时，请稍后重试'\n\t\t\t}\n\t\t\telse if (error.message.includes('Network')) {\n\t\t\t\terrorMessage.value = '网络连接失败'\n\t\t\t}\n\t\t\telse {\n\t\t\t\terrorMessage.value = error.message\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\terrorMessage.value = '获取IP位置信息失败'\n\t\t}\n\n\t\tconsole.error('获取IP位置信息失败:', error)\n\n\t\twelcomeText.value = DEFAULT_WELCOME_TEXT\n\t}\n\tfinally {\n\t\tloading.value = false\n\t}\n}\n\n\u002F\u002F 组件挂载时初始化\nonMounted(() => {\n\tfetchIPLocation()\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n\u003CBlogWidget card title=\"访客信息\">\n\t\u003Cdiv v-if=\"loading\" class=\"loading\">\n\t\t\u003Cdiv class=\"loading-spinner\" \u002F>\n\t\t\u003Cspan>获取位置信息中...\u003C\u002Fspan>\n\t\u003C\u002Fdiv>\n\n\t\u003Cdiv v-else-if=\"errorMessage\" class=\"error\">\n\t\t\u003Cdiv class=\"error-icon\">\n\t\t\t😕\n\t\t\u003C\u002Fdiv>\n\t\t\u003Cp>{{ errorMessage }}\u003C\u002Fp>\n\t\t\u003CZButton text=\"重试\" class=\"retry-btn\" @click=\"fetchIPLocation\" \u002F>\n\t\u003C\u002Fdiv>\n\n\t\u003Cdiv v-else class=\"welcome-message\">\n\t\t\u003Cdiv class=\"message-content\">\n\t\t\t\u003Cp>欢迎来自 \u003Cb>{{ locationDescription }}\u003C\u002Fb> 的小友💖\u003C\u002Fp>\n\t\t\t\u003Cp v-if=\"distance\">\n\t\t\t\t当前位置距博主约 \u003Cb>{{ distance }}\u003C\u002Fb> 公里！\n\t\t\t\u003C\u002Fp>\n\t\t\t\u003Cp>\u003Cb>{{ timeGreeting }}\u003C\u002Fb>\u003C\u002Fp>\n\t\t\t\u003Cp>Tip：\u003Cb>{{ welcomeText }}\u003C\u002Fb>\u003C\u002Fp>\n\t\t\u003C\u002Fdiv>\n\t\u003C\u002Fdiv>\n\u003C\u002FBlogWidget>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.widget-title {\n\tcolor: var(--c-primary);\n}\n\n.loading, .error {\n\tpadding: 1rem;\n\ttext-align: center;\n\tcolor: var(--c-text-2);\n}\n\n.welcome-message {\n\tpadding: 0.5rem;\n\n\t.message-content {\n\t\tline-height: 1;\n\n\t\tp {\n\t\t\tmargin-bottom: 0.3rem;\n\n\t\t\t&:last-child {\n\t\t\t\tmargin-bottom: 0;\n\t\t\t}\n\n\t\t\tb {\n\t\t\t\tcolor: var(--c-primary);\n\t\t\t}\n\t\t}\n\t}\n}\n\n.loading {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 0.8rem;\n\n\t.loading-spinner {\n\t\twidth: 40px;\n\t\theight: 40px;\n\t\tborder: 3px solid rgb(0 0 0 \u002F 10%);\n\t\tborder-top: 3px solid var(--c-primary);\n\t\tborder-radius: 50%;\n\t\tanimation: spin 1s linear infinite;\n\t}\n\n\t@keyframes spin {\n\t\t0% { transform: rotate(0deg); }\n\t\t100% { transform: rotate(360deg); }\n\t}\n}\n\n.error {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 0.5rem;\n\n\t.error-icon {\n\t\tmargin-bottom: 0.5rem;\n\t\tfont-size: 2rem;\n\t}\n}\n\n.retry-btn {\n\tmargin-top: 0.5rem;\n\n\t&:hover,\n\t&:active {\n\t\tcolor: var(--c-primary);\n\t}\n}\n\u003C\u002Fstyle>\n",[52,314,312],{"__ignoreMap":47},[35,316,317],{"id":317},"最新评论",[13,319,50,320,55,322],{},[52,321,305],{"code":305},[52,323,324],{"code":324},"LatestComments.vue",[60,326,329],{"className":327,"code":328,"language":65},[63],"\u003Cscript setup lang=\"ts\">\nimport { ref, computed, onMounted, onUnmounted } from 'vue'\n\n\u002F\u002F Twikoo配置\nconst TWIKOO_URL = 'https:\u002F\u002Ftwikoo地址'\nconst ADMIN_MAIL_MD5 = 'twikoo评论管理邮箱的SHA256值'\n\n\u002F\u002F\u002F\u002F 评论数据状态\nconst latestComments = ref({\n  comments: [],\n  loading: true,\n  error: false,\n  lastFetchTime: 0\n})\n\nconst comments = computed(() => latestComments.value.comments)\nconst loading = computed(() => latestComments.value.loading)\nconst error = computed(() => latestComments.value.error)\n\n\u002F\u002F 清理评论内容，移除HTML标签\nfunction cleanContent(content: string): string {\n  if (!content) return ''\n  \n  const replacements: [RegExp, string][] = [\n    [\u002F\u003Cpre>\u003Ccode>[\\s\\S]*?\u003C\\\u002Fcode>\u003C\\\u002Fpre>\u002Fg, '[代码块]'],\n    [\u002F\u003Ccode>([^\u003C]{4,})\u003C\\\u002Fcode>\u002Fg, '[代码]'],\n    [\u002F\u003Ccode>([^\u003C]{1,3})\u003C\\\u002Fcode>\u002Fg, '$1'],\n    [\u002F\u003Cimg(?![^>]*class=\"tk-owo-emotion\")[^>]*>\u002Fg, '[图片]'],\n    [\u002F\u003Ca[^>]*?>[\\s\\S]*?\u003C\\\u002Fa>\u002Fg, '[链接]'],\n    [\u002F\u003C(?!img[^>]*class=\"tk-owo-emotion\")[^>]+>\u002Fg, '']\n  ]\n  \n  return replacements.reduce((text, [regex, replacement]) => \n    text.replace(regex, replacement), content\n  ).trim()\n}\n\n\u002F\u002F 获取最新评论\nasync function fetchLatestComments() {\n  const now = Date.now()\n  \u002F\u002F 10分钟内不重复请求\n  if (now - latestComments.value.lastFetchTime \u003C 10 * 60 * 1000) {\n    return\n  }\n  \n  try {\n    latestComments.value.loading = true\n    latestComments.value.error = false\n    \n    const response = await fetch(TWIKOO_URL, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application\u002Fjson'\n      },\n      body: JSON.stringify({\n        event: 'GET_RECENT_COMMENTS',\n        includeReply: true,\n        pageSize: 10\n      }),\n      timeout: 5000\n    })\n    \n    const data = await response.json()\n    \n    if (!data.data) {\n      throw new Error('No data')\n    }\n    \n    \u002F\u002F 处理评论数据\n    latestComments.value.comments = data.data\n      .filter((comment: any) => comment.url !== '\u002F')\n      .slice(0, 5)\n      .map((comment: any) => ({\n        content: cleanContent(comment.comment),\n        author: comment.nick,\n        date: toZonedTemporal(new Date(comment.created).toISOString()).toString(),\n        avatar: comment.avatar,\n        isAdmin: comment.mailMd5 === ADMIN_MAIL_MD5,\n        url: comment.url,\n        id: comment.id\n      }))\n    \n    latestComments.value.lastFetchTime = now\n  } catch (err) {\n    console.error('Failed to fetch comments:', err)\n    latestComments.value.error = true\n  } finally {\n    latestComments.value.loading = false\n  }\n}\n\n\u002F\u002F 导航到评论\nfunction navigateToComment(comment: any) {\n  window.open(`${comment.url}#${comment.id}`, '_self')\n}\n\n\u002F\u002F 生命周期钩子\nlet interval: number | null = null\n\nonMounted(() => {\n  fetchLatestComments()\n  \u002F\u002F 每10分钟刷新一次\n  interval = window.setInterval(fetchLatestComments, 10 * 60 * 1000)\n})\n\nonUnmounted(() => {\n  if (interval) {\n    clearInterval(interval)\n  }\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n\u003CBlogWidget card title=\"最新评论\">\n  \u003Cdiv class=\"comments-container\">\n    \u003Ctransition name=\"fade\" mode=\"out-in\">\n      \u003Ctemplate v-if=\"loading\">\n        \u003Cdiv class=\"loading\">\n          \u003Cdiv class=\"loading-spinner\">\u003C\u002Fdiv>\n          \u003Cp>加载中...\u003C\u002Fp>\n        \u003C\u002Fdiv>\n      \u003C\u002Ftemplate>\n      \n      \u003Ctemplate v-else-if=\"error\">\n        \u003Cdiv class=\"error\">\n          \u003Cspan class=\"error-icon\">⚠️\u003C\u002Fspan>\n          \u003Cspan>评论加载失败\u003C\u002Fspan>\n        \u003C\u002Fdiv>\n      \u003C\u002Ftemplate>\n      \n      \u003Ctemplate v-else>\n        \u003Cul class=\"comments-list\">\n          \u003Cli \n            v-for=\"comment in comments\" \n            :key=\"comment.id\"\n            class=\"comment-item\"\n            @click=\"navigateToComment(comment)\"\n          >\n            \u003Cdiv class=\"comment-meta\">\n              \u003Cdiv class=\"author-info\">\n                \u003Cimg :src=\"comment.avatar\" :alt=\"comment.author\" class=\"avatar\" \u002F>\n                \u003Cspan class=\"author\">{{ comment.author }}\u003C\u002Fspan>\n                \u003CIcon v-if=\"comment.isAdmin\" class=\"admin-badge\" name=\"i-material-symbols:verified\" \u002F>\n              \u003C\u002Fdiv>\n              \u003CUtilDate class=\"date\" :date=\"comment.date\" month=\"2-digit\" day=\"numeric\" \u002F>\n            \u003C\u002Fdiv>\n            \u003Cp\n              class=\"comment-content\"\n              :innerHTML=\"comment.content\"\n              v-tip=\"{ content: '点击查看完整评论', placement: 'top-end' }\"\n            >\u003C\u002Fp>\n          \u003C\u002Fli>\n        \u003C\u002Ful>\n      \u003C\u002Ftemplate>\n    \u003C\u002Ftransition>\n  \u003C\u002Fdiv>\n\u003C\u002FBlogWidget>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.comments-container {\n  min-height: 343px;\n  position: relative;\n}\n\n.loading,\n.error {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  color: var(--c-text-2);\n  gap: 8px;\n  text-align: center;\n}\n\n.loading-spinner {\n  width: 40px;\n  height: 40px;\n  border: 3px solid var(--c-bg-3);\n  border-top-color: var(--c-primary);\n  border-radius: 50%;\n  animation: spin 1s linear infinite;\n  margin: 0 auto 0.5rem;\n}\n\n.error-icon {\n  font-size: 3rem;\n}\n\n@keyframes spin {\n  from { transform: rotate(0deg); }\n  to { transform: rotate(360deg); }\n}\n\n.comments-list {\n  list-style: none;\n  padding: 0;\n  margin: 0;\n}\n\n.comment-item {\n  border-bottom: 1px solid var(--c-border);\n  cursor: pointer;\n  padding: 0.5rem 0;\n  \n  &:last-child {\n    border-bottom: none;\n  }\n}\n\n.comment-meta {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  font-size: 0.8em;\n  color: var(--c-text-2);\n  margin-bottom: 0.3rem;\n}\n\n.author-info {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n}\n\n.avatar {\n  width: 16px;\n  height: 16px;\n  border-radius: 50%;\n  object-fit: cover;\n}\n\n.author {\n  color: var(--c-text-1);\n  font-weight: 500;\n}\n\n.admin-badge {\n  color: var(--c-primary);\n  font-size: 1.1em;\n}\n\n.date {\n  color: var(--c-text-3);\n}\n\n.comment-content {\n  background-color: var(--c-bg-1);\n  border-radius: 4px;\n  color: var(--c-text-2);\n  font-size: 0.9em;\n  overflow: hidden;\n  padding: 6px 10px !important;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  transition: color 0.2s;\n  margin: 0;\n  \n  &:hover {\n    color: var(--c-text-1);\n  }\n  \n  img.tk-owo-emotion {\n    height: 16px;\n    margin: 0 5px;\n    vertical-align: text-bottom;\n    width: 16px;\n  }\n}\n\n\u002F* 过渡动画 *\u002F\n.fade-enter-active,\n.fade-leave-active {\n  transition: opacity 0.3s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n  opacity: 0;\n}\n\u003C\u002Fstyle>\n",[52,330,328],{"__ignoreMap":47},[35,332,334],{"id":333},"修改toc","修改TOC",[60,336,339],{"className":337,"code":338,"language":65},[63],"\u003Cscript setup lang=\"ts\">\nimport type { TocLink } from '@nuxt\u002Fcontent'\n\nconst [DefineTemplate, ReuseTemplate] = createReusableTemplate({\n\tprops: {\n\t\ttocTree: { type: Array as PropType\u003CTocLink[]> },\n\t},\n})\n\nconst { toc } = useArticle()\nconst scrollableEl = useTemplateRef('toc')\nconst { tocOffsets, activeHeadingId } = useToc(toc, computed(() => scrollableEl.value?.body))\n\nfunction hasHeading(tocTree: TocLink, heading?: string): boolean {\n\treturn tocTree.id === heading || !!tocTree.children?.some(child => hasHeading(child, heading))\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n\u003CBlogWidget\n\tref=\"toc\"\n\ttitle=\"文章目录\"\n\tshrink\n\t:style=\"{ minHeight: `clamp(4rem, ${tocOffsets.length}rem, 20rem)` }\"\n>\n\t\u003Ctemplate #action>\n\t\t\u003Cdiv class=\"toc-actions\">\n\t\t\t\u003C!-- use \u003Ca> for anchor -->\n\t\t\t\u003Ca href=\"#main-content\" aria-label=\"返回开头\">\n\t\t\t\t\u003CIcon name=\"tabler:arrow-up-circle\" \u002F>\n\t\t\t\u003C\u002Fa>\n\n\t\t\t\u003Ca href=\"#twikoo\" aria-label=\"评论区\">\n\t\t\t\t\u003CIcon name=\"tabler:message-circle\" \u002F>\n\t\t\t\u003C\u002Fa>\n\t\t\u003C\u002Fdiv>\n\t\u003C\u002Ftemplate>\n\n\t\u003C!-- 放在顶层会导致 Transition 失效 -->\n\t\u003CDefineTemplate v-slot=\"{ tocTree }\">\n\t\t\u003Col>\n\t\t\t\u003Cli\n\t\t\t\tv-for=\"(entry, index) in tocTree\"\n\t\t\t\t:key=\"index\"\n\t\t\t\t:class=\"{\n\t\t\t\t\t'has-active': hasHeading(entry, activeHeadingId),\n\t\t\t\t\t'active': entry.id === activeHeadingId,\n\t\t\t\t}\"\n\t\t\t>\n\t\t\t\t\u003C!-- 使用 \u003Ca> 确保键盘焦点切换 -->\n\t\t\t\t\u003Ca :href=\"`#${entry?.id}`\" :title=\"entry.text\">{{ entry.text }}\u003C\u002Fa>\n\t\t\t\t\u003CReuseTemplate v-if=\"entry.children\" :toc-tree=\"entry.children\" \u002F>\n\t\t\t\u003C\u002Fli>\n\t\t\u003C\u002Fol>\n\t\u003C\u002FDefineTemplate>\n\n\t\u003CReuseTemplate\n\t\tv-if=\"toc?.links.length\"\n\t\tclass=\"toc\"\n\t\t:toc-tree=\"toc.links\"\n\t\u002F>\n\t\u003Cp v-else class=\"no-toc\">\n\t\t暂无目录信息\n\t\u003C\u002Fp>\n\u003C\u002FBlogWidget>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.toc {\n\tposition: relative;\n\n\t&::before {\n\t\tcontent: \"\";\n\t\tposition: absolute;\n\t\tinset: 0.3rem;\n\t\twidth: 3px;\n\t\tborder-radius: 1rem;\n\t\tbackground-color: var(--c-bg-soft);\n\t}\n}\n\nol {\n\tpadding-inline-start: 0.8rem;\n}\n\nli {\n\topacity: 0.6;\n\tfont-size: 0.94em;\n\tcolor: var(--c-text);\n\ttransition: opacity 0.2s;\n\n\t&:hover {\n\t\topacity: 0.94;\n\t}\n\n\t&.has-active, &.active {\n\t\topacity: 1;\n\t\tfont-size: 1em;\n\t}\n\n\t&.active::before {\n\t\tcontent: \"\";\n\t\tposition: absolute;\n\t\tinset-inline-start: 0.3rem;\n\t\tmargin: 0.2rem 0;\n\t\tpadding: 0.6rem 1.5px;\n\t\tborder-radius: 1rem;\n\t\tbackground-color: var(--c-primary);\n\t}\n\n\t> a {\n\t\tdisplay: block;\n\t\toverflow: hidden;\n\t\tpadding: 0.2em 0.5em;\n\t\tborder-radius: 0.5em;\n\t\twhite-space: nowrap;\n\t\ttext-overflow: ellipsis;\n\t\ttransition: all 0.2s;\n\n\t\t&:hover {\n\t\t\tbackground-color: var(--c-bg-soft);\n\t\t}\n\t}\n}\n\n.no-toc {\n\tpadding: 1em;\n\ttext-align: center;\n\tcolor: var(--c-text-3);\n}\n\n.toc-actions {\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n\tgap: 0.4rem;\n\tmargin-left: auto;\n}\n\n.toc-actions a {\n\tposition: relative;\n\tborder: 0;\n\tbackground: transparent;\n\tcolor: var(--c-text);\n\twidth: 2.2rem;\n\theight: 2.2rem;\n\tpadding: 0;\n\tborder-radius: 999px;\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tcursor: pointer;\n\toutline: none;\n\tbox-shadow: inset 0 0 0 1px var(--c-border);\n\ttransition: all 0.2s;\n}\n\n.toc-actions a:hover {\n\tcolor: var(--c-primary);\n\tbox-shadow: inset 0 0 0 1px var(--c-primary);\n}\n\n.toc-actions a svg {\n\tposition: relative;\n\tz-index: 1;\n}\n\u003C\u002Fstyle>\n",[52,340,338],{"__ignoreMap":47},[25,342,343],{"id":343},"文章footer",[35,345,346],{"id":346},"添加分享功能",[13,348,274,349],{},[52,350,351],{"code":351},"\u002Fapp\u002Fcomponents\u002Fpost\u002FPostFooter.vue",[60,353,356],{"className":354,"code":355,"language":65},[63],"\u003Cscript setup lang=\"ts\">\nimport type { ArticleProps } from '~\u002Ftypes\u002Farticle'\n\ndefineOptions({ inheritAttrs: false })\nconst props = defineProps\u003CArticleProps>()\n\nconst [DefineTemplate, ReuseTemplate] = createReusableTemplate\u003C{\n\ttitle: string\n}>({ inheritAttrs: false })\n\nconst appConfig = useAppConfig()\nconst title = `${props.title} | ${appConfig.title}`\nconst href = new URL(props.path!, appConfig.url).href\nconst { copy, copied } = useCopy(href)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n\u003Cdiv class=\"post-footer\">\n\t\u003CDefineTemplate v-slot=\"{ $slots, title }\">\n\t\t\u003Csection>\n\t\t\t\u003Cdiv class=\"title text-creative\">\n\t\t\t\t{{ title }}\n\t\t\t\u003C\u002Fdiv>\n\n\t\t\t\u003Cdiv class=\"content\">\n\t\t\t\t\u003Ccomponent :is=\"$slots.default\" \u002F>\n\t\t\t\u003C\u002Fdiv>\n\t\t\u003C\u002Fsection>\n\t\u003C\u002FDefineTemplate>\n\n\t\u003CReuseTemplate v-if=\"references\" title=\"参考链接\">\n\t\t\u003Cul>\n\t\t\t\u003Cli v-for=\"{ title, link }, i in references\" :key=\"i\">\n\t\t\t\t\u003CProseA :href=\"link || ''\">\n\t\t\t\t\t{{ title ?? link }}\n\t\t\t\t\u003C\u002FProseA>\n\t\t\t\u003C\u002Fli>\n\t\t\u003C\u002Ful>\n\t\u003C\u002FReuseTemplate>\n\n\t\u003CReuseTemplate :title=\"meta?.slots?.copyright?.props?.title as string || '许可协议'\">\n\t\t\u003CContentRenderer v-if=\"meta?.slots?.copyright\" :value=\"meta?.slots?.copyright\" \u002F>\n\t\t\u003Cp v-else>\n\t\t\t本文采用 \u003CProseA :href=\"appConfig.copyright.url\">\n\t\t\t\t{{ appConfig.copyright.name }}\n\t\t\t\u003C\u002FProseA>\n\t\t\t许可协议，转载请注明出处。\n\t\t\u003C\u002Fp>\n\t\u003C\u002FReuseTemplate>\n\n\t\u003Csection class=\"share\">\n\t\t\u003Cdiv class=\"title text-creative\">\n\t\t\t分享文章\n\t\t\u003C\u002Fdiv>\n\n\t\t\u003Cdiv class=\"content\">\n\t\t\t\u003CZButton\n\t\t\t\tclass=\"share-button\"\n\t\t\t\ticon=\"ri:qq-line\"\n\t\t\t\tv-tip=\"'QQ'\"\n\t\t\t\t:to=\"`https:\u002F\u002Fconnect.qq.com\u002Fwidget\u002Fshareqq\u002Findex.html?title=${encodeURIComponent(title)}&url=${encodeURIComponent(href)}`\"\n\t\t\t\u002F>\n\t\t\t\u003CZButton\n\t\t\t\tclass=\"share-button\"\n\t\t\t\ticon=\"ri:weibo-fill\"\n\t\t\t\tv-tip=\"'微博'\"\n\t\t\t\t:to=\"`https:\u002F\u002Fservice.weibo.com\u002Fshare\u002Fshare.php?title=${encodeURIComponent(title)}&url=${encodeURIComponent(href)}`\"\n\t\t\t\u002F>\n\t\t\t\u003CZButton\n\t\t\t\tclass=\"share-button\"\n\t\t\t\ticon=\"tabler:mail\"\n\t\t\t\tv-tip=\"'邮件'\"\n\t\t\t\t:to=\"`mailto:?subject=${encodeURIComponent(title)}&body=${encodeURIComponent(href)}`\"\n\t\t\t\u002F>\n\t\t\t\u003CZButton\n\t\t\t\tclass=\"share-button\"\n\t\t\t\ticon=\"tabler:link\"\n\t\t\t\tv-tip=\"{\n\t\t\t\t\tcontent: copied ? '已复制链接' : '复制链接',\n\t\t\t\t\thideOnClick: false\n\t\t\t\t}\"\n\t\t\t\t@click=\"copy()\"\n\t\t\t\u002F>\n\t\t\u003C\u002Fdiv>\n\t\u003C\u002Fsection>\n\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.post-footer {\n\tmargin: 2rem 0.5rem;\n\tborder: 1px solid var(--c-border);\n\tborder-radius: 1rem;\n\tbackground-color: var(--c-bg-2);\n}\n\nsection {\n\tpadding: 1rem;\n\n\t& + section {\n\t\tborder-top: 1px solid var(--c-border);\n\t}\n}\n\n.title {\n\tfont-weight: bold;\n\tcolor: var(--c-text);\n}\n\n.content {\n\tmargin-top: 0.5em;\n\tfont-size: 0.9rem;\n\n\tli {\n\t\tmargin: 0.5em 0;\n\t}\n}\n\n.share-button {\n\tdisplay: inline-flex;\n\taspect-ratio: 1;\n\tborder-radius: 50%;\n\tborder: 1px solid var(--c-border);\n\tbox-shadow: none;\n}\n\u003C\u002Fstyle>\n",[52,357,355],{"__ignoreMap":47},[35,359,360],{"id":360},"添加打赏功能",[13,362,50,363,55,366],{},[52,364,365],{"code":365},"\u002Fapp\u002Fcomponents\u002Fpost\u002F",[52,367,368],{"code":368},"Donation.vue",[60,370,373],{"className":371,"code":372,"language":65},[63],"\u003Cscript setup lang=\"ts\">\nconst { donation } = useAppConfig()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n\u003Cdiv class=\"donation\" v-if=\"donation?.enable\">\n  \u003CTooltip :delay=\"200\" interactive hide-on-click=\"toggle\" max-width=\"\">\n    \u003CZButton class=\"donate-button\" icon=\"tabler:heart-filled\" text=\"赞赏作者\" \u002F>\n    \u003Ctemplate #content>\n      \u003Cdiv class=\"donation-content\">\n        \u003Cdiv class=\"donation-list\" v-if=\"Object.keys(donation.items).length\">\n          \u003Cfigure class=\"donation-item\" v-for=\"(image, label) in donation.items\">\n            \u003CUtilImg class=\"image\" width=\"160\" height=\"160\" :src=\"image\" \u002F>\n            \u003Cfigcaption class=\"label\">{{ label }}\u003C\u002Ffigcaption>\n          \u003C\u002Ffigure>\n        \u003C\u002Fdiv>\n        \u003Cp class=\"donation-message\" v-if=\"donation.message\">{{ donation.message }}\u003C\u002Fp>\n      \u003C\u002Fdiv>\n    \u003C\u002Ftemplate>\n  \u003C\u002FTooltip>\n\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.donation {\n  display: flex;\n  justify-content: center;\n\n  .donate-button {\n    padding: .6rem .8rem;\n    border: 1px solid var(--c-border);\n    box-shadow: none;\n  }\n\n  .donation-content {\n    text-align: center;\n    padding: .5rem .6rem;\n\n    .donation-list {\n      display: flex;\n      padding: .5rem 0;\n      gap: 1.5rem;\n      justify-content: center;\n      flex-wrap: wrap;\n    }\n\n    .donation-item {\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      gap: .5rem;\n\n      .image {\n        border-radius: .5rem;\n      }\n\n      .label {\n        color: var(--c-text-2);\n      }\n    }\n\n    .donation-message {\n      color: var(--c-text-1);\n    }\n  }\n\n  :deep([data-tippy-root]) {\n    max-width: calc(100% - 1rem);\n\n    .tippy-box {\n      border: 1px solid var(--c-border);\n      background-color: var(--c-bg-2);\n    }\n\n    .tippy-svg-arrow {\n      fill: var(--c-bg-2);\n    }\n  }\n}\n\u003C\u002Fstyle>\n",[52,374,372],{"__ignoreMap":47},[13,376,274,377],{},[52,378,379],{"code":379},"\u002Fapp\u002Fpages\u002F[...slug].vue",[60,381,384],{"className":382,"code":383,"language":65},[63],"\u003CPostFooter v-bind=\"post\" \u002F>\n\u003CPostDonation \u002F> \u002F\u002F添加项\n\u003CPostSurround \u002F>\n\u003CPostComment \u002F>\n",[52,385,383],{"__ignoreMap":47},[13,387,274,388],{},[52,389,390],{"code":390},"blog.config.ts",[60,392,395],{"className":393,"code":394,"language":65},[63],"    \u002F** 禁止搜索引擎收录的路径 *\u002F\n\trobotsNotIndex: ['\u002Fpreview', '\u002Fpreviews\u002F*'],\n},\n\n\u002F** 赞赏配置 *\u002F\ndonation: {\n    enable: true,\n    message: '感谢您的支持，这将激励我创作更多优质内容！',\n    items: {\n      \"微信支付\": 'https:\u002F\u002Fexample.com\u002Fwechat-pay.png',\n      \"支付宝\": 'https:\u002F\u002Fexample.com\u002Falipay.png',\n      \"xxx\": 'https:\u002F\u002Fexample.com\u002Fxxx.png'\n    }\n},\n\n\u002F** 博客 Atom 订阅源 *\u002F\n",[52,396,394],{"__ignoreMap":47},[35,398,400],{"id":399},"指定页面关闭评论赞赏分享功能","指定页面关闭评论、赞赏、分享、功能",[13,402,403,404],{},"按照如下代码修改",[52,405,379],{"code":379},[60,407,410],{"className":408,"code":409,"language":65},[63],"   \u003CPostFooter v-if=\"post.postfooter\" v-bind=\"post\" \u002F>\n\t\u003CPostDonation v-if=\"post.donation\" \u002F>\n\t\u003CPostSurround \u002F>\n\t\u003CPostComment v-if=\"post.comment\" \u002F>\n",[52,411,409],{"__ignoreMap":47},[13,413,50,414,417],{},[52,415,416],{"code":416},"content.config.ts","中添加如下代码",[60,419,422],{"className":420,"code":421,"language":65},[63],"const articleSchema = z.object({\n\tdraft: z.boolean().default(false),\n\tpermalink: z.string().optional(),\n\tpostfooter: z.boolean().default(true),\n\tdonation: z.boolean().default(true),\n\tcomment: z.boolean().default(true),\n",[52,423,421],{"__ignoreMap":47},[13,425,426],{},"使用示例",[60,428,431],{"className":429,"code":430,"language":65},[63],"---\ntitle: '文章标题'\ndate: '2025-01-01'\npostfooter: false#关闭分享按钮（连带着版权一起隐藏）\ndonation: false#关闭赞赏\ncomment: false#关闭评论\n---\n",[52,432,430],{"__ignoreMap":47},[25,434,436],{"id":435},"twikoo评论样式美化","Twikoo评论样式美化",[13,438,274,439,442],{},[52,440,441],{"code":441},"\u002Fapp\u002Fcomponents\u002Fpost\u002FComment.vue","。",[60,444,447],{"className":445,"code":446,"language":65},[63],"\u003Cscript setup lang=\"tsx\">\nimport type { TippyComponent } from 'vue-tippy'\n\nconst appConfig = useAppConfig()\n\nconst commentEl = useTemplateRef('comment')\nconst popoverEl = useTemplateRef\u003CTippyComponent>('popover')\nconst popoverJumpTo = ref('')\nconst popoverInputEl = useTemplateRef('popover-input')\nconst showUndo = ref(false)\n\nconst popoverBind = ref\u003CTippyComponent['$props']>({})\n\n\u002F** 评论区链接守卫 *\u002F\nuseEventListener(commentEl, 'click', (e) => {\n\tif (!(e.target instanceof Element))\n\t\treturn\n\n\tif (e.target.matches('.tk-avatar-img'))\n\t\te.stopPropagation()\n\n\tconst popoverTarget = e.target.closest('a[target=\"_blank\"]')\n\tif (!(popoverTarget instanceof HTMLAnchorElement))\n\t\treturn\n\n\te.preventDefault()\n\tpopoverEl.value?.hide()\n\n\tpopoverJumpTo.value = safelyDecodeUriComponent(popoverTarget.href)\n\tpopoverBind.value = {\n\t\tgetReferenceClientRect: () => popoverTarget.getBoundingClientRect(),\n\t\ttriggerTarget: popoverTarget,\n\t}\n\n\tnextTick(checkUndoable)\n\tpopoverEl.value?.show()\n}, { capture: true })\n\nfunction checkUndoable() {\n\tshowUndo.value = popoverInputEl.value?.textContent !== popoverJumpTo.value\n}\n\nfunction undo() {\n\tif (!popoverInputEl.value)\n\t\treturn\n\tpopoverInputEl.value.textContent = popoverJumpTo.value\n\tcheckUndoable()\n}\n\nfunction confirmOpen() {\n\twindow.open(popoverInputEl.value?.textContent, '_blank')\n}\n\nonMounted(() => {\n\twindow.twikoo?.init?.({\n\t\tenvId: appConfig.twikoo?.envId,\n\t\t\u002F\u002F twikoo 会把挂载后的元素变为 #twikoo\n\t\tel: '#twikoo',\n\t})\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n\u003Csection ref=\"comment\" class=\"z-comment\">\n\t\u003Ch3 class=\"text-creative\">\n\t\t评论区\n\t\u003C\u002Fh3>\n\n\t\u003C!-- interactive 默认会把气泡移动到 triggerTarget 的父元素上 -->\n\t\u003CTooltip\n\t\tref=\"popover\"\n\t\tv-bind=\"popoverBind\"\n\t\t:append-to=\"() => commentEl!\"\n\t\tinteractive\n\t\t:aria=\"{ expanded: false }\"\n\t\ttrigger=\"focusin\"\n\t>\n\t\t\u003Ctemplate #content>\n\t\t\t\u003Cdiv class=\"popover-confirm\">\n\t\t\t\t\u003Cspan\n\t\t\t\t\tref=\"popover-input\"\n\t\t\t\t\tclass=\"input\"\n\t\t\t\t\tcontenteditable=\"plaintext-only\"\n\t\t\t\t\tspellcheck=\"false\"\n\t\t\t\t\t@input=\"checkUndoable\"\n\t\t\t\t\t@keydown.enter.prevent=\"confirmOpen\"\n\t\t\t\t\tv-text=\"popoverJumpTo\"\n\t\t\t\t\u002F>\n\n\t\t\t\t\u003Cbutton\n\t\t\t\t\tv-if=\"showUndo\"\n\t\t\t\t\taria-label=\"恢复原始内容\"\n\t\t\t\t\t@click=\"undo()\"\n\t\t\t\t>\n\t\t\t\t\t\u003CIcon name=\"tabler:arrow-back-up\" \u002F>\n\t\t\t\t\u003C\u002Fbutton>\n\n\t\t\t\t\u003CZButton\n\t\t\t\t\tprimary\n\t\t\t\t\ttext=\"访问\"\n\t\t\t\t\t@click=\"confirmOpen\"\n\t\t\t\t\u002F>\n\t\t\t\u003C\u002Fdiv>\n\t\t\u003C\u002Ftemplate>\n\t\u003C\u002FTooltip>\n\n\t\u003Cdiv id=\"twikoo\">\n\t\t\u003Cdiv class=\"comment-loading\">\n\t\t\t\u003Cdiv class=\"loading-spinner\">\u003C\u002Fdiv>\n\t\t\t\u003Cp>评论加载中...\u003C\u002Fp>\n\t\t\u003C\u002Fdiv>\n\t\u003C\u002Fdiv>\n\u003C\u002Fsection>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.z-comment {\n\tmargin: 3rem 1rem;\n\n\t> h3 {\n\t\tmargin-top: 3rem;\n\t\tfont-size: 1.25rem;\n\t}\n}\n\n:deep() > [data-tippy-root] > .tippy-box {\n\tpadding: 0;\n}\n\n.popover-confirm {\n\tdisplay: flex;\n\talign-items: center;\n\toverflow-wrap: anywhere;\n\n\t> .input {\n\t\tmin-width: 0;\n\t\tpadding: 0.3em 0.6em;\n\t\toutline: none;\n\t}\n\n\t> button {\n\t\tflex-shrink: 0;\n\t\talign-self: stretch;\n\t\tpadding: 0.3em;\n\t\tborder-radius: 0 0.5em 0.5em 0;\n\t}\n}\n\n:deep(#twikoo) {\n\tmargin: 2em 0;\n\n\t.tk-admin-container {\n\t\tposition: fixed;\n\t\tz-index: calc(var(--z-index-popover) + 1);\n\t}\n\n\t.tk-submit {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\n\t\t.tk-avatar,\n\t\t.tk-submit-action-icon.__markdown {\n\t\t\tdisplay: none;\n\t\t}\n\n\t\t.tk-preview-container {\n\t\t\tmargin: 0 0 .5rem 0;\n\t\t}\n\n\t\t.tk-row.actions {\n\t\t\tjustify-content: flex-end;\n\t\t\tmargin: 0 0 .5rem;\n\t\t\torder: 3;\n\t\t}\n\n\t\t.tk-textarea {\n\t\t\torder: 1;\n\t\t\tmargin-bottom: .5rem;\n\t\t\tfont-family: var(--font-monospace);\n\n\t\t\t.tk-input__inner {\n\t\t\t\tbackground-color: var(--c-bg-2);\n\t\t\t\tborder: 2px solid var(--c-border);\n\t\t\t\tborder-radius: 12px;\n\t\t\t\tpadding: .8rem;\n\t\t\t\ttransition: all .2s;\n\n\t\t\t\t&:focus {\n\t\t\t\t\tbackground-color: var(--c-bg);\n\t\t\t\t\tbackground-position-y: 350px;\n\t\t\t\t\tborder-color: var(--c-primary);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.tk-meta-input {\n\t\t\torder: 2;\n\t\t\tposition: relative;\n\n\t\t\t.tk-input {\n\t\t\t\tbackground: var(--c-bg-2);\n\t\t\t\tborder: 2px solid var(--c-border);\n\t\t\t\tborder-radius: 10px;\n\t\t\t\ttransition: all .2s;\n\n\t\t\t\t&:focus-within {\n\t\t\t\t\tbackground: var(--c-bg);\n\t\t\t\t\tborder-color: var(--c-primary);\n\n\t\t\t\t\t&::before, &::after {\n\t\t\t\t\t\tanimation: fadeInTip .3s ease;\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t&::before {\n\t\t\t\t\tbackground: var(--c-bg);\n\t\t\t\t\tborder: 1px solid var(--c-border);\n\t\t\t\t\tborder-radius: 8px;\n\t\t\t\t\tcolor: var(--c-text-1);\n\t\t\t\t\tdisplay: none;\n\t\t\t\t\tfont-size: .9rem;\n\t\t\t\t\tleft: 50%;\n\t\t\t\t\tpadding: .8rem 1rem;\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: -60px;\n\t\t\t\t\ttransform: translate(-50%);\n\t\t\t\t\twhite-space: nowrap;\n\t\t\t\t\tz-index: 100;\n\t\t\t\t}\n\n\t\t\t\t&::after {\n\t\t\t\t\tborder: 8px solid transparent;\n\t\t\t\t\tborder-top: 8px solid var(--c-bg);\n\t\t\t\t\tcontent: \"\";\n\t\t\t\t\tdisplay: none;\n\t\t\t\t\tleft: 50%;\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: -12px;\n\t\t\t\t\ttransform: translate(-50%);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.tk-input:first-child::before { content: \"输入QQ号会自动获取昵称和头像🐧\"; }\n\t\t\t.tk-input:nth-child(2)::before { content: \"收到回复将会发送到您的邮箱📧\"; }\n\t\t\t.tk-input:nth-child(3)::before { content: \"可以通过昵称访问您的网站🔗\"; }\n\n\t\t\t.tk-input__inner {\n\t\t\t\tborder: none !important;\n\t\t\t}\n\n\t\t\t.tk-input-group__prepend {\n\t\t\t\tbackground: var(--c-bg-1);\n\t\t\t\tborder: none;\n\t\t\t\tborder-radius: 8px 0 0 8px;\n\t\t\t\tcolor: var(--c-text-2);\n\t\t\t\ttransition: all .2s;\n\t\t\t}\n\t\t}\n\t}\n\n\t.OwO .OwO-body {\n\t\tanimation: fadeInPanel .3s ease .1s 1 normal both;\n\t\tbackground: var(--c-bg);\n\t\tborder-radius: 8px;\n\t\ttransform: translateZ(0);\n\t}\n\n\t.tk-avatar {\n\t\tborder-radius: 50%;\n\t\toverflow: hidden;\n\n\t\t@supports (corner-shape: squircle) {\n\t\t\tcorner-shape: superellipse(1.2);\n\t\t}\n\t}\n\n\t.tk-avatar.tk-clickable {\n\t\tcursor: auto;\n\t}\n\n\t.tk-time {\n\t\tcolor: var(--c-text-3);\n\t}\n\n\t\u002F* 防止 a 被 overflow hidden *\u002F\n\t.tk-content {\n\t\tmargin: -0.2em;\n\t\tpadding: 0.2em;\n\t\tfont-size: .95rem;\n\t\tline-height: 1.6;\n\n\t\t.tk-owo-emotion {\n\t\t\twidth: auto;\n\t\t\theight: 1.4em;\n\t\t\tvertical-align: text-bottom;\n\t\t}\n\n\t\tp > code, > code {\n\t\t\tbackground: var(--c-bg-2);\n\t\t\tborder: 1px solid var(--c-border);\n\t\t\tborder-radius: 6px;\n\t\t\tpadding: .2em .4em;\n\t\t}\n\n\t\t.code-toolbar, > span > pre {\n\t\t\tbackground: var(--c-bg-2);\n\t\t\tborder: 2px solid var(--c-border);\n\t\t\tborder-radius: 8px;\n\t\t\toverflow: auto;\n\t\t\tpadding: .4rem;\n\t\t\tposition: relative;\n\n\t\t\t&::before {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\n\t\t\tpre {\n\t\t\t\tmargin-top: .75rem;\n\n\t\t\t\tcode {\n\t\t\t\t\tdisplay: block;\n\t\t\t\t\tpadding-top: .75rem;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t.tk-comments-title, .tk-nick {\n\t\tfont-family: var(--font-creative);\n\t\tmargin-bottom: 0;\n\t}\n\n\t.tk-nick-link {\n\t\tcolor: var(--c-primary);\n\t}\n\n\t.tk-extras, .tk-footer {\n\t\tfont-size: 0.7em;\n\t\tcolor: var(--c-text-3);\n\t}\n\n\t\u002F* 屏蔽点赞和踩赞按钮，保留回复按钮*\u002F\n\t.tk-action .tk-action-link:not(:last-child) {\n\t\tdisplay: none !important;\n\t}\n\n\t.tk-comment .tk-main {\n\t\t.tk-meta {\n\t\t\tmargin-bottom: .3rem;\n\t\t}\n\n\t\t.tk-extras {\n\t\t\tcolor: var(--c-text-2);\n\t\t\tfont-size: .85rem;\n\t\t\tmargin-top: .5rem;\n\t\t}\n\t}\n\n\t.tk-replies:not(.tk-replies-expand) {\n\t\tmask-image: linear-gradient(to top, transparent, #FFF 4em);\n\t}\n\n\t.tk-expand {\n\t\tborder-radius: 0.5em;\n\t\tbackground-color: var(--c-bg-2);\n\t\tcolor: var(--c-text-1);\n\t\tpadding: 0.375rem 1rem;\n\t\ttransition: background-color 0.1s;\n\n\t\t&:hover {\n\t\t\tbackground-color: var(--c-bg-3);\n\t\t}\n\t}\n\n\t.tk-button:not(.tk-button--primary) {\n\t\tborder-radius: 8px;\n\t\tbackground-color: var(--c-bg-2);\n\t\tcolor: var(--c-text-1);\n\t\tborder: 1px solid var(--c-border);\n\n\t\t&:hover {\n\t\t\tbackground-color: var(--c-bg-3);\n\t\t\tborder-color: var(--c-primary);\n\t\t}\n\t}\n\n\t.tk-button--primary {\n\t\tborder-radius: 8px;\n\t\tbackground-color: var(--c-primary);\n\t\tcolor: white;\n\t\tborder: 1px solid var(--c-primary);\n\n\t\t&:hover {\n\t\t\tbackground-color: var(--c-primary-soft);\n\t\t\tborder-color: var(--c-primary-soft);\n\t\t}\n\t}\n\n\t.tippy-svg-arrow > svg {\n\t\tfill: inherit;\n\t\twidth: auto;\n\t\theight: auto;\n\t}\n}\n\n:deep(:where(.tk-preview-container,.tk-content)) {\n\tpre {\n\t\toverflow: auto;\n\t\tborder-radius: 0.5em;\n\t\tfont-size: 0.85em;\n\t}\n\n\ta {\n\t\tmargin: -0.1em -0.2em;\n\t\tpadding: 0.1em 0.2em;\n\t\tbackground: linear-gradient(var(--c-primary-soft), var(--c-primary-soft)) no-repeat center bottom \u002F 100% 0.1em;\n\t\tcolor: var(--c-primary);\n\t\ttransition: all 0.2s;\n\n\t\t&:hover {\n\t\t\tborder-radius: 0.3em;\n\t\t\tbackground-size: 100% 100%;\n\t\t}\n\t}\n\n\tp {\n\t\tmargin: 0.2em 0;\n\t}\n\n\timg {\n\t\tborder-radius: 0.5em;\n\t}\n\n\tmenu, ol, ul {\n\t\tmargin: 0.5em 0;\n\t\tpadding-inline-start: 1.5em;\n\t\tfont-size: 0.9rem;\n\t\tlist-style: revert;\n\n\t\t> li {\n\t\t\tmargin: 0.2em 0;\n\n\t\t\t&::marker {\n\t\t\t\tcolor: var(--c-primary);\n\t\t\t}\n\t\t}\n\t}\n\n\tblockquote {\n\t\tmargin: 0.5rem 0 0.8rem;\n\t\tpadding: 0.2em 0.8rem;\n\t\tborder-inline-start: 4px solid var(--c-border);\n\t\tborder-radius: 8px;\n\t\tbackground-color: var(--c-bg-2);\n\t\tfont-size: 0.9em;\n\t}\n}\n\n.comment-loading {\n\tcolor: var(--c-text-2);\n\tpadding: 2rem;\n\ttext-align: center;\n\n\t.loading-spinner {\n\t\tanimation: spin 1s linear infinite;\n\t\tborder: 3px solid var(--c-bg-3);\n\t\tborder-top-color: var(--c-primary);\n\t\tborder-radius: 50%;\n\t\theight: 40px;\n\t\tmargin: 0 auto 1rem;\n\t\twidth: 40px;\n\t}\n\n\tp {\n\t\tfont-size: .9rem;\n\t}\n}\n\n@keyframes spin {\n\t0% { transform: rotate(0); }\n\tto { transform: rotate(1turn); }\n}\n\n@keyframes fadeInTip {\n\tfrom { opacity: 0; transform: translate(-50%, 10px); }\n\tto { opacity: 1; transform: translate(-50%); }\n}\n\n@keyframes fadeInPanel {\n\tfrom { opacity: 0; transform: translateY(-20px); }\n\tto { opacity: 1; transform: translateY(0); }\n}\n\u003C\u002Fstyle>\n",[52,448,446],{"__ignoreMap":47},{"title":47,"searchDepth":450,"depth":450,"links":451},4,[452,454,462,468,473],{"id":27,"depth":453,"text":27},2,{"id":33,"depth":453,"text":33,"children":455},[456,458,459,460,461],{"id":37,"depth":457,"text":37},3,{"id":143,"depth":457,"text":143},{"id":159,"depth":457,"text":159},{"id":235,"depth":457,"text":236},{"id":252,"depth":457,"text":252},{"id":268,"depth":453,"text":268,"children":463},[464,465,466,467],{"id":271,"depth":457,"text":271},{"id":300,"depth":457,"text":300},{"id":317,"depth":457,"text":317},{"id":333,"depth":457,"text":334},{"id":343,"depth":453,"text":343,"children":469},[470,471,472],{"id":346,"depth":457,"text":346},{"id":360,"depth":457,"text":360},{"id":399,"depth":457,"text":400},{"id":435,"depth":453,"text":436},[475],"技术",true,"2026-10-21 10:14:00","由于Clarity主题的功能缺失，很多功能需要自行添加，得益于很多前辈的教程和AI的协助，制作出很多不错的页面和功能，在此发文做记录，以便自己与后来者查看。包含完整的Vue组件代码与CSS样式。",false,"md","https:\u002F\u002Fimg-upyun.chario.cn\u002Fi\u002F2026\u002F10\u002F21\u002F109276.webp",{"aside":483,"slots":485},[484],"toc",{},"\u002FClarity",null,{"text":489,"minutes":490,"time":491,"words":492},"75 min read",74.18,4450800,14836,{"title":5,"description":478},{"loc":486},"posts\u002FClarity主题深度定制指南",[497],"Clarity主题","tech","J0Sl1-NR8nsA57_YhjKbjebKaHRtMjYmboRPRyuazhM",[501,487],{"title":502,"path":503,"stem":504,"date":505,"type":498,"children":-1},"禁止用pip在系统全局Python环境安装包的解决办法","\u002Fdebian-pip","posts\u002F禁止用pip在系统全局Python环境安装包的解决办法","2026-10-19 20:15:00",1792556811246]