-
-
-
-
-
+ ) : (
+
+
+
Cookie 偏好设置
+
+
+
+
+
+
+
+
+ 必要 Cookie
+ 始终启用
+
+
+ 网站正常运行所必需,无法禁用
+
+
+
+
+
+
handleTogglePreference('analytics')}
+ className="mt-1 h-4 w-4 rounded border-gray-300 text-[#C41E3A] focus:ring-[#C41E3A] cursor-pointer"
+ aria-label="分析 Cookie"
+ />
+
+
分析 Cookie
+
+ 帮助我们了解访客如何使用网站,改进用户体验
+
+
+
+
+
+
handleTogglePreference('marketing')}
+ className="mt-1 h-4 w-4 rounded border-gray-300 text-[#C41E3A] focus:ring-[#C41E3A] cursor-pointer"
+ aria-label="营销 Cookie"
+ />
+
+
营销 Cookie
+
+ 用于个性化广告(当前未使用)
+
+
+
+
+
+
+
+
+
-
+ )}
)}
);
}
+
+export function CookieSettingsButton() {
+ const [isVisible] = useState(() => {
+ if (typeof window === 'undefined') {return false;}
+ return !!getStoredPreferences();
+ });
+
+ if (!isVisible) {return null;}
+
+ return (
+
+ );
+}
diff --git a/src/components/analytics/GoogleAnalytics.tsx b/src/components/analytics/GoogleAnalytics.tsx
index 0f83e9a..4fed77b 100644
--- a/src/components/analytics/GoogleAnalytics.tsx
+++ b/src/components/analytics/GoogleAnalytics.tsx
@@ -48,7 +48,7 @@ function GoogleAnalyticsContent() {
gtag('config', '${GA_MEASUREMENT_ID}', {
send_page_view: false,
anonymize_ip: true,
- allow_google_signals: true,
+ allow_google_signals: false,
allow_ad_personalization_signals: false,
cookie_flags: 'SameSite=None;Secure'
});
diff --git a/src/components/analytics/ScrollDepthTracker.tsx b/src/components/analytics/ScrollDepthTracker.tsx
new file mode 100644
index 0000000..a7b879c
--- /dev/null
+++ b/src/components/analytics/ScrollDepthTracker.tsx
@@ -0,0 +1,42 @@
+'use client';
+
+import { useEffect, useRef, useCallback } from 'react';
+import { usePathname } from 'next/navigation';
+import { trackScrollDepth } from '@/lib/analytics';
+
+const MILESTONES = [25, 50, 75, 100] as const;
+
+export function ScrollDepthTracker() {
+ const trackedRef = useRef
>(new Set());
+ const pathname = usePathname();
+
+ const handleScroll = useCallback(() => {
+ const scrollTop = window.scrollY;
+ const docHeight = document.documentElement.scrollHeight - window.innerHeight;
+
+ if (docHeight <= 0) {
+ return;
+ }
+
+ const scrollPercent = Math.round((scrollTop / docHeight) * 100);
+
+ MILESTONES.forEach((milestone) => {
+ if (scrollPercent >= milestone && !trackedRef.current.has(milestone)) {
+ trackedRef.current.add(milestone);
+ trackScrollDepth(milestone);
+ }
+ });
+ }, []);
+
+ useEffect(() => {
+ trackedRef.current = new Set();
+
+ window.addEventListener('scroll', handleScroll, { passive: true });
+
+ return () => {
+ window.removeEventListener('scroll', handleScroll);
+ };
+ }, [pathname, handleScroll]);
+
+ return null;
+}
diff --git a/src/lib/analytics.ts b/src/lib/analytics.ts
index b36a7e7..2709eaa 100644
--- a/src/lib/analytics.ts
+++ b/src/lib/analytics.ts
@@ -149,6 +149,13 @@ export const trackProductView = (productId: string, productName: string) => {
}
};
+export interface CookiePreferences {
+ necessary: boolean;
+ analytics: boolean;
+ marketing: boolean;
+ timestamp: number;
+}
+
export const updateConsent = (granted: boolean) => {
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('consent', 'update', {
@@ -157,3 +164,44 @@ export const updateConsent = (granted: boolean) => {
});
}
};
+
+export const updateConsentDetailed = (preferences: CookiePreferences) => {
+ if (typeof window !== 'undefined' && window.gtag) {
+ window.gtag('consent', 'update', {
+ analytics_storage: preferences.analytics ? 'granted' : 'denied',
+ ad_storage: preferences.marketing ? 'granted' : 'denied',
+ functionality_storage: 'granted',
+ personalization_storage: preferences.marketing ? 'granted' : 'denied',
+ security_storage: 'granted',
+ });
+ }
+};
+
+export const getStoredPreferences = (): CookiePreferences | null => {
+ if (typeof window === 'undefined') {
+ return null;
+ }
+
+ try {
+ const stored = localStorage.getItem('cookie_preferences');
+ if (stored) {
+ return JSON.parse(stored) as CookiePreferences;
+ }
+ } catch {
+ return null;
+ }
+ return null;
+};
+
+export const storePreferences = (preferences: CookiePreferences) => {
+ if (typeof window !== 'undefined') {
+ localStorage.setItem('cookie_preferences', JSON.stringify(preferences));
+ }
+};
+
+export const getDefaultPreferences = (): CookiePreferences => ({
+ necessary: true,
+ analytics: false,
+ marketing: false,
+ timestamp: Date.now(),
+});