新增 GA4 / Meta Pixel 追蹤與 Cookie 同意機制
- 追蹤 ID 存 site_settings(空字串即關閉),於 /admin/settings 管理 - Base.astro 僅在用戶同意後載入分析工具,未同意前零請求 - 新增 /privacy 私隱政策頁與「重設 Cookie 偏好」按鈕 - 前台 SSR 頁面(首頁、blog、about、privacy)傳入 gaId / metaPixelId - seed 加入預設空值,Footer 加入私隱政策連結 - 同步更新 AGENTS.md 與 README 說明
This commit is contained in:
@@ -9,6 +9,8 @@ interface Props {
|
||||
publishedTime?: Date | null;
|
||||
image?: string;
|
||||
jsonLd?: object | null | (object | null)[];
|
||||
gaId?: string;
|
||||
metaPixelId?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
@@ -19,8 +21,12 @@ const {
|
||||
publishedTime = null,
|
||||
image,
|
||||
jsonLd = null,
|
||||
gaId = "",
|
||||
metaPixelId = "",
|
||||
} = Astro.props;
|
||||
|
||||
const hasAnalytics = Boolean(gaId || metaPixelId);
|
||||
|
||||
const siteName = SITE_NAME;
|
||||
const origin = (Astro.site ?? Astro.url).toString().replace(/\/$/, "");
|
||||
const canonical = new URL(Astro.url.pathname, origin + "/").toString();
|
||||
@@ -61,6 +67,65 @@ const jsonLdHtml = jsonLdItems.length
|
||||
|
||||
{jsonLdHtml && <script type="application/ld+json" is:inline set:html={jsonLdHtml} />}
|
||||
|
||||
{
|
||||
hasAnalytics && (
|
||||
<script is:inline define:vars={{ gaId, metaPixelId }}>
|
||||
window.__analyticsIds = { gaId, metaPixelId };
|
||||
window.__loadAnalytics = function () {
|
||||
var ids = window.__analyticsIds || {};
|
||||
if (window.__analyticsLoaded) return;
|
||||
window.__analyticsLoaded = true;
|
||||
if (ids.gaId) {
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.gtag = function () {
|
||||
window.dataLayer.push(arguments);
|
||||
};
|
||||
window.gtag("js", new Date());
|
||||
window.gtag("config", ids.gaId);
|
||||
var gaScript = document.createElement("script");
|
||||
gaScript.async = true;
|
||||
gaScript.src =
|
||||
"https://www.googletagmanager.com/gtag/js?id=" +
|
||||
encodeURIComponent(ids.gaId);
|
||||
document.head.appendChild(gaScript);
|
||||
}
|
||||
if (ids.metaPixelId) {
|
||||
(function (f, b, e, v, n, t, s) {
|
||||
if (f.fbq) return;
|
||||
n = f.fbq = function () {
|
||||
n.callMethod
|
||||
? n.callMethod.apply(n, arguments)
|
||||
: n.queue.push(arguments);
|
||||
};
|
||||
if (!f._fbq) f._fbq = n;
|
||||
n.push = n;
|
||||
n.loaded = true;
|
||||
n.version = "2.0";
|
||||
n.queue = [];
|
||||
t = b.createElement(e);
|
||||
t.async = true;
|
||||
t.src = v;
|
||||
s = b.getElementsByTagName(e)[0];
|
||||
s.parentNode.insertBefore(t, s);
|
||||
})(
|
||||
window,
|
||||
document,
|
||||
"script",
|
||||
"https://connect.facebook.net/en_US/fbevents.js",
|
||||
);
|
||||
window.fbq("init", ids.metaPixelId);
|
||||
window.fbq("track", "PageView");
|
||||
}
|
||||
};
|
||||
try {
|
||||
if (window.localStorage.getItem("cookie_consent") === "granted") {
|
||||
window.__loadAnalytics();
|
||||
}
|
||||
} catch (e) {}
|
||||
</script>
|
||||
)
|
||||
}
|
||||
|
||||
<link rel="sitemap" href="/sitemap.xml" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
@@ -79,6 +144,58 @@ const jsonLdHtml = jsonLdItems.length
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
{
|
||||
hasAnalytics && (
|
||||
<div id="cookie-consent" class="cookie-consent" hidden>
|
||||
<p class="cookie-consent__text">
|
||||
我哋用 Cookie 分析網站使用情況(Google Analytics 同 Meta),以改善服務。你可以選擇接受或拒絕。
|
||||
</p>
|
||||
<div class="cookie-consent__actions">
|
||||
<a class="cookie-consent__link" href="/privacy">私隱政策</a>
|
||||
<button class="cookie-consent__btn" type="button" data-consent="denied">
|
||||
拒絕
|
||||
</button>
|
||||
<button
|
||||
class="cookie-consent__btn cookie-consent__btn--primary"
|
||||
type="button"
|
||||
data-consent="granted"
|
||||
>
|
||||
接受
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
hasAnalytics && (
|
||||
<script is:inline>
|
||||
(function () {
|
||||
var el = document.getElementById("cookie-consent");
|
||||
if (!el) return;
|
||||
var choice = null;
|
||||
try {
|
||||
choice = window.localStorage.getItem("cookie_consent");
|
||||
} catch (e) {}
|
||||
if (!choice) el.hidden = false;
|
||||
el.addEventListener("click", function (event) {
|
||||
var target = event.target;
|
||||
var btn = target && target.closest
|
||||
? target.closest("[data-consent]")
|
||||
: null;
|
||||
if (!btn) return;
|
||||
var value = btn.getAttribute("data-consent");
|
||||
try {
|
||||
window.localStorage.setItem("cookie_consent", value);
|
||||
} catch (e) {}
|
||||
if (value === "granted" && typeof window.__loadAnalytics === "function") {
|
||||
window.__loadAnalytics();
|
||||
}
|
||||
el.hidden = true;
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
)
|
||||
}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -90,4 +207,60 @@ const jsonLdHtml = jsonLdItems.length
|
||||
font-family: "Noto Sans HK", "PingFang HK", "Microsoft JhengHei", system-ui, -apple-system, sans-serif;
|
||||
background: #faf8f4;
|
||||
}
|
||||
|
||||
.cookie-consent[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cookie-consent {
|
||||
position: fixed;
|
||||
inset: auto 0 0 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px 24px;
|
||||
padding: 16px 24px;
|
||||
background: #faf8f4;
|
||||
border-top: 1px solid #e5dfd6;
|
||||
}
|
||||
|
||||
.cookie-consent__text {
|
||||
margin: 0;
|
||||
flex: 1 1 320px;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
color: #4a453f;
|
||||
}
|
||||
|
||||
.cookie-consent__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.cookie-consent__link {
|
||||
font-size: 13px;
|
||||
color: #6e6862;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.cookie-consent__btn {
|
||||
padding: 8px 20px;
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #1a1917;
|
||||
background: transparent;
|
||||
border: 1px solid #1a1917;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cookie-consent__btn--primary {
|
||||
color: #fff;
|
||||
background: #b45309;
|
||||
border-color: #b45309;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user