feat: 新增Cookie同意条控件,Footer链接隐私政策
This commit is contained in:
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
|||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import Header from "@/components/layout/Header";
|
import Header from "@/components/layout/Header";
|
||||||
import Footer from "@/components/layout/Footer";
|
import Footer from "@/components/layout/Footer";
|
||||||
|
import CookieConsent from "@/components/layout/CookieConsent";
|
||||||
import ReactQueryProvider from "@/providers/react-query-provider";
|
import ReactQueryProvider from "@/providers/react-query-provider";
|
||||||
|
|
||||||
// 面向中国大陆用户,避免依赖 next/font/google(构建期需访问 Google 服务器)。
|
// 面向中国大陆用户,避免依赖 next/font/google(构建期需访问 Google 服务器)。
|
||||||
@@ -23,6 +24,7 @@ export default function RootLayout({ children }: LayoutProps<"/">) {
|
|||||||
<Header />
|
<Header />
|
||||||
<main className="flex-1">{children}</main>
|
<main className="flex-1">{children}</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
<CookieConsent />
|
||||||
</ReactQueryProvider>
|
</ReactQueryProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
const CONSENT_STORAGE_KEY = "cookie-consent";
|
||||||
|
|
||||||
|
export default function CookieConsent() {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 仅在浏览器已明确同意/拒绝前展示,避免每次访问重复弹出。
|
||||||
|
const consent = window.localStorage.getItem(CONSENT_STORAGE_KEY);
|
||||||
|
if (!consent) {
|
||||||
|
setVisible(true);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function handleChoice(choice: "accepted" | "rejected") {
|
||||||
|
window.localStorage.setItem(CONSENT_STORAGE_KEY, choice);
|
||||||
|
setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!visible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="dialog"
|
||||||
|
aria-live="polite"
|
||||||
|
aria-label="Cookie 使用提示"
|
||||||
|
className="fixed inset-x-0 bottom-0 z-[100] border-t border-slate-800 bg-slate-950 px-6 py-4 text-sm text-slate-300"
|
||||||
|
>
|
||||||
|
<div className="mx-auto flex max-w-7xl flex-col items-center justify-between gap-3 sm:flex-row">
|
||||||
|
<p>
|
||||||
|
本网站使用 Cookie 以提升您的浏览体验。继续访问即表示您同意我们按照{" "}
|
||||||
|
<Link
|
||||||
|
href="/privacy-policy"
|
||||||
|
className="underline underline-offset-2 hover:text-white"
|
||||||
|
>
|
||||||
|
隐私政策
|
||||||
|
</Link>{" "}
|
||||||
|
使用 Cookie。您也可以选择拒绝非必要 Cookie。
|
||||||
|
</p>
|
||||||
|
<div className="flex shrink-0 gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleChoice("rejected")}
|
||||||
|
className="rounded-md border border-slate-600 px-4 py-2 text-slate-300 hover:bg-slate-800"
|
||||||
|
>
|
||||||
|
拒绝
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleChoice("accepted")}
|
||||||
|
className="rounded-md bg-primary px-4 py-2 font-medium text-white hover:opacity-90"
|
||||||
|
>
|
||||||
|
接受
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user