update
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -13,6 +13,6 @@ node_modules
|
|||||||
bun.lock
|
bun.lock
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
|
||||||
|
.edgeone/
|
||||||
.vercel
|
.vercel
|
||||||
.env*.local
|
.env*.local
|
||||||
|
|||||||
30
app/app.vue
30
app/app.vue
@@ -9,8 +9,8 @@ useHead({
|
|||||||
// meta: () => siteMetaData,
|
// meta: () => siteMetaData,
|
||||||
});
|
});
|
||||||
|
|
||||||
const desktopBg = siteConfig.theme.background || "";
|
const desktopBg = siteConfig.theme?.background || "";
|
||||||
const mobileBg = siteConfig.theme.backgroundMobile || "";
|
const mobileBg = siteConfig.theme?.backgroundMobile || "";
|
||||||
|
|
||||||
// 将 siteConfig.theme.color 转换为 CSS 变量
|
// 将 siteConfig.theme.color 转换为 CSS 变量
|
||||||
const hexToRgb = (hex: string) => {
|
const hexToRgb = (hex: string) => {
|
||||||
@@ -20,15 +20,39 @@ const hexToRgb = (hex: string) => {
|
|||||||
: "189, 131, 243";
|
: "189, 131, 243";
|
||||||
};
|
};
|
||||||
|
|
||||||
const primaryColor = siteConfig.theme.color || "#bd83f3";
|
const primaryColor = siteConfig.theme?.color || "#bd83f3";
|
||||||
const primaryRgb = hexToRgb(primaryColor);
|
const primaryRgb = hexToRgb(primaryColor);
|
||||||
|
|
||||||
|
// Parse traceConfig.script (which may contain raw <script> tags) into
|
||||||
|
// proper head script entries to avoid inserting raw HTML into a script
|
||||||
|
// element (which causes JS parse errors like "Unexpected token '<'").
|
||||||
|
const traceRaw = siteConfig.traceConfig?.script || "";
|
||||||
|
const traceScripts = [] as any[];
|
||||||
|
if (typeof traceRaw === "string" && traceRaw.trim()) {
|
||||||
|
// find all <script ... src="..."> tags and extract src
|
||||||
|
const srcRegex = /<script[^>]*src=["']([^"']+)["'][^>]*>\s*<\/script>/gi;
|
||||||
|
let m: RegExpExecArray | null;
|
||||||
|
while ((m = srcRegex.exec(traceRaw))) {
|
||||||
|
let src = m[1];
|
||||||
|
if (src?.startsWith("//")) src = `https:${src}`;
|
||||||
|
traceScripts.push({ src });
|
||||||
|
}
|
||||||
|
|
||||||
|
// find inline script contents
|
||||||
|
const inlineRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
||||||
|
while ((m = inlineRegex.exec(traceRaw))) {
|
||||||
|
const content = (m[1] || "").trim();
|
||||||
|
if (content) traceScripts.push({ children: content, type: "text/javascript" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
style: [
|
style: [
|
||||||
{
|
{
|
||||||
innerHTML: `:root { --site-primary: ${primaryColor}; --site-primary-rgb: ${primaryRgb}; }`,
|
innerHTML: `:root { --site-primary: ${primaryColor}; --site-primary-rgb: ${primaryRgb}; }`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
script: traceScripts,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,6 @@
|
|||||||
<span v-if="from && fromWho" class="ml-1.5"> · {{ fromWho }}</span>
|
<span v-if="from && fromWho" class="ml-1.5"> · {{ fromWho }}</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- 访问统计 -->
|
|
||||||
<p v-if="showStats && !statsError" class="text-text-muted text-xs m-0">
|
|
||||||
👁️ {{ visitors }} · 📊 {{ pageviews }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- 备案信息 -->
|
<!-- 备案信息 -->
|
||||||
<p v-if="contact?.beian" class="text-text-muted text-xs m-0">
|
<p v-if="contact?.beian" class="text-text-muted text-xs m-0">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
@@ -23,19 +18,19 @@
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div v-if="adStats && ads.length">
|
<div v-if="adStats && processedAds.length">
|
||||||
<template v-for="ad in ads" :key="ad.link">
|
<template v-for="ad in processedAds" :key="ad.link">
|
||||||
<a
|
<a
|
||||||
v-if="isExternal(ad.link)"
|
v-if="isExternal(ad.link)"
|
||||||
:href="ad.link"
|
:href="ad.link"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
class="text-text-muted text-sm m-0">
|
class="text-text-muted text-sm m-0">
|
||||||
<span v-html="ad.body"></span>
|
<span v-html="ad.html"></span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<NuxtLink v-else :to="ad.link" class="text-text-muted text-sm m-0">
|
<NuxtLink v-else :to="ad.link" class="text-text-muted text-sm m-0">
|
||||||
<span v-html="ad.body"></span>
|
<span v-html="ad.html"></span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,15 +81,22 @@ const contact = siteConfig.footer || {};
|
|||||||
const quote = ref("");
|
const quote = ref("");
|
||||||
const from = ref("");
|
const from = ref("");
|
||||||
const fromWho = ref("");
|
const fromWho = ref("");
|
||||||
const pageviews = ref(0);
|
|
||||||
const visitors = ref(0);
|
|
||||||
const statsError = ref(true);
|
|
||||||
const showHitokoto = siteConfig.footer?.hitokoto?.enable;
|
const showHitokoto = siteConfig.footer?.hitokoto?.enable;
|
||||||
const showStats = ref(siteConfig.umami?.enable);
|
|
||||||
|
|
||||||
const adStats = siteConfig.ad?.enable;
|
const adStats = siteConfig.ad?.enable;
|
||||||
const ads = siteConfig.ad?.ads || [];
|
const ads = siteConfig.ad?.ads || [];
|
||||||
|
|
||||||
|
// processedAds: replace root-relative src ("/...") with full site URL to
|
||||||
|
// avoid issues when deployed under different hosts/base paths or when
|
||||||
|
// root-relative assets are unavailable. Keeps existing HTML otherwise.
|
||||||
|
const processedAds = (ads || []).map((ad) => {
|
||||||
|
const html =
|
||||||
|
typeof ad.body === "string"
|
||||||
|
? ad.body.replace(/src="\/(?!\/)/g, `src="${siteConfig.siteMeta.url}/`)
|
||||||
|
: ad.body;
|
||||||
|
return { ...ad, html };
|
||||||
|
});
|
||||||
|
|
||||||
const buildHitokotoUrl = () => {
|
const buildHitokotoUrl = () => {
|
||||||
const type = siteConfig.footer?.hitokoto?.type;
|
const type = siteConfig.footer?.hitokoto?.type;
|
||||||
const url = new URL("https://v1.hitokoto.cn/");
|
const url = new URL("https://v1.hitokoto.cn/");
|
||||||
@@ -122,59 +124,11 @@ const fetchHitokoto = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchStats = async () => {
|
|
||||||
try {
|
|
||||||
if (!siteConfig.umami?.apiEndpoint || !siteConfig.umami?.websiteId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const apiEndpoint = siteConfig.umami.apiEndpoint;
|
|
||||||
const websiteId = siteConfig.umami.websiteId;
|
|
||||||
const apiKey = siteConfig.umami.apiKey;
|
|
||||||
|
|
||||||
if (!apiKey) return;
|
|
||||||
|
|
||||||
// 获取统计数据
|
|
||||||
|
|
||||||
const startAt = new Date(siteConfig.siteMeta.startTime);
|
|
||||||
const endAt = Date.now();
|
|
||||||
|
|
||||||
const resp = await fetch(
|
|
||||||
`${apiEndpoint}/v1/websites/${websiteId}/stats?startAt=${startAt}&endAt=${endAt}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${apiKey}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!resp.ok) {
|
|
||||||
console.warn(`Stats API returned ${resp.status}`);
|
|
||||||
statsError.value = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await resp.json();
|
|
||||||
if (data) {
|
|
||||||
statsError.value = false;
|
|
||||||
pageviews.value = data.pageviews;
|
|
||||||
visitors.value = data.visitors;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pageviews.value === 0 && visitors.value === 0) {
|
|
||||||
showStats.value = false;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
statsError.value = true;
|
|
||||||
console.debug("Stats fetch failed (this is normal if blocked by ad blocker):", e.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isExternal = (url) => {
|
const isExternal = (url) => {
|
||||||
return typeof url === "string" && /^https?:\/\//.test(url);
|
return typeof url === "string" && /^https?:\/\//.test(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (showHitokoto) fetchHitokoto();
|
if (showHitokoto) fetchHitokoto();
|
||||||
if (showStats.value) fetchStats();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -78,6 +78,12 @@ const siteConfig: SiteConfig = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
traceConfig: {
|
||||||
|
enable: true,
|
||||||
|
script: `<script charset="UTF-8" id="MXA_COLLECT" src="//mxana.tacool.com/sdk.js"></script>
|
||||||
|
<script>MXA.init({ id: "c2-G6ouenNf" })</script>`,
|
||||||
|
},
|
||||||
|
|
||||||
comment: {
|
comment: {
|
||||||
twikoo: {
|
twikoo: {
|
||||||
enable: true,
|
enable: true,
|
||||||
@@ -85,14 +91,6 @@ const siteConfig: SiteConfig = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
umami: {
|
|
||||||
enable: false,
|
|
||||||
scriptUrl: "https://cloud.umami.is/script.js",
|
|
||||||
apiKey: "api_MGcpRPYMcBmTKZOKdUVpr7mlBoWkck5g",
|
|
||||||
websiteId: "b33dfd14-7e62-498b-a199-de0ac38a1d44",
|
|
||||||
apiEndpoint: "https://api.umami.is",
|
|
||||||
},
|
|
||||||
|
|
||||||
ad: {
|
ad: {
|
||||||
enable: true,
|
enable: true,
|
||||||
ads: [
|
ads: [
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import { defineNuxtPlugin } from "#app";
|
|
||||||
import { VueUmamiPlugin } from "@jaseeey/vue-umami-plugin";
|
|
||||||
import type { Router } from "vue-router";
|
|
||||||
import siteConfig from "~/config";
|
|
||||||
|
|
||||||
export default defineNuxtPlugin((nuxtApp) => {
|
|
||||||
if (!import.meta.client) return;
|
|
||||||
if (!siteConfig.umami?.enable) return;
|
|
||||||
|
|
||||||
// 跳过在 localhost 环境下加载 Umami
|
|
||||||
if (
|
|
||||||
typeof window !== "undefined" &&
|
|
||||||
(window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1")
|
|
||||||
) {
|
|
||||||
console.log("Umami plugin skipped on localhost");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const router = nuxtApp.$router as Router | undefined;
|
|
||||||
if (!router) return;
|
|
||||||
|
|
||||||
nuxtApp.vueApp.use(
|
|
||||||
VueUmamiPlugin({
|
|
||||||
websiteID: siteConfig.umami.websiteId,
|
|
||||||
scriptSrc: siteConfig.umami.scriptUrl,
|
|
||||||
router,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -63,6 +63,11 @@ export interface Footer {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TraceConfig {
|
||||||
|
enable?: boolean;
|
||||||
|
script?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CommentConfig {
|
export interface CommentConfig {
|
||||||
twikoo?: {
|
twikoo?: {
|
||||||
enable?: boolean;
|
enable?: boolean;
|
||||||
@@ -70,14 +75,6 @@ export interface CommentConfig {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UmamiConfig {
|
|
||||||
enable?: boolean;
|
|
||||||
scriptUrl?: string;
|
|
||||||
apiKey?: string;
|
|
||||||
websiteId?: string;
|
|
||||||
apiEndpoint?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdConfig {
|
export interface AdConfig {
|
||||||
enable?: boolean;
|
enable?: boolean;
|
||||||
ads?: [{ body?: string; link?: string }];
|
ads?: [{ body?: string; link?: string }];
|
||||||
@@ -94,7 +91,7 @@ export interface SiteConfig {
|
|||||||
lines?: LineInfo[];
|
lines?: LineInfo[];
|
||||||
theme?: Theme;
|
theme?: Theme;
|
||||||
footer?: Footer;
|
footer?: Footer;
|
||||||
|
traceConfig?: TraceConfig;
|
||||||
comment?: CommentConfig;
|
comment?: CommentConfig;
|
||||||
umami?: UmamiConfig;
|
|
||||||
ad?: AdConfig;
|
ad?: AdConfig;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 关于我
|
# 关于我
|
||||||
|
|
||||||
大家好!👋 我是 **RhenCloud**,一名热爱技术和开源的开发者。
|
大家好!👋 我是 **@RhenCloud**、**@泠云**,一名热爱技术和开源的开发者。
|
||||||
|
|
||||||
## 个人简介
|
## 个人简介
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
- ✅ SEO 优化(Meta、OG、Sitemap、RSS)
|
- ✅ SEO 优化(Meta、OG、Sitemap、RSS)
|
||||||
- ✅ 文章搜索(Fuse.js)
|
- ✅ 文章搜索(Fuse.js)
|
||||||
- ✅ 评论系统(Twikoo)
|
- ✅ 评论系统(Twikoo)
|
||||||
- ✅ 访客统计(Umami)
|
- ✅ 访客统计
|
||||||
- ✅ 响应式设计
|
- ✅ 响应式设计
|
||||||
- ✅ 快速加载速度
|
- ✅ 快速加载速度
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ categories: ["Technology"]
|
|||||||
|
|
||||||
### Firefox
|
### Firefox
|
||||||
|
|
||||||
是我 ~~(抛弃 360 极速浏览器后)~~最早使用的浏览器,后来由于缺少插件以及兼容性不佳放弃使用
|
是我~~抛弃 360 极速浏览器后~~最早使用的浏览器,后来由于缺少插件以及兼容性不佳放弃使用
|
||||||
|
|
||||||
优点:
|
优点:
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export default defineNuxtConfig({
|
|||||||
"@nuxt/content",
|
"@nuxt/content",
|
||||||
"@nuxtjs/color-mode",
|
"@nuxtjs/color-mode",
|
||||||
"@nuxt/ui",
|
"@nuxt/ui",
|
||||||
// "nuxt-umami",
|
|
||||||
"@formkit/auto-animate",
|
"@formkit/auto-animate",
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -117,13 +116,6 @@ export default defineNuxtConfig({
|
|||||||
minify: true,
|
minify: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// umami: {
|
|
||||||
// id: siteConfig.umami.websiteId,
|
|
||||||
// host: siteConfig.umami.apiBase,
|
|
||||||
// autoTrack: true,
|
|
||||||
// enabled: siteConfig.umami.enable,
|
|
||||||
// },
|
|
||||||
|
|
||||||
colorMode: {
|
colorMode: {
|
||||||
classSuffix: "",
|
classSuffix: "",
|
||||||
preference: "system",
|
preference: "system",
|
||||||
|
|||||||
Reference in New Issue
Block a user