--- title: "Cookie 同意" description: "通过类型化的逐脚本 `consent` 对象,在用户同意后再加载脚本,并驱动厂商原生的同意 API。" canonical_url: "https://nuxt-scripts.zhcndoc.com/docs/guides/consent" last_updated: "2026-05-22T14:11:55.928Z" --- 尝试在 StackBlitz 上的实时示例:[Cookie 同意示例](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/cookie-consent) 或 [细粒度同意示例](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/granular-consent)。 ## 两个互补的原语 Nuxt Scripts 提供了两个协同工作的同意原语: 1. **useScriptTriggerConsent()**:一个二元加载门控。脚本只会在获得同意后开始加载。 2. **每个脚本的 consent 对象**,由每个支持同意的 `useScriptX()` 返回。它是一个厂商原生、类型化的 API,用于在运行时授予、撤销或更新同意类别。并与每个脚本的 `defaultConsent` 选项配合使用,以定义在厂商首次初始化调用之前应用的初始策略。 每个厂商都有自己的同意语义(GA/GTM 使用 Google Consent Mode v2,Meta 使用二元授予/撤销,TikTok 使用三态,Matomo 使用 `setConsentGiven`/`forgetConsentGiven`,Mixpanel/PostHog 使用 `opt_in`/`opt_out`,Clarity 使用 cookie 开关)。你需要显式地为每一个进行配置。 ## 二元加载门控 最简单的用法与经典的 cookie 横幅流程一致:只有在用户点击接受后才加载脚本。 ```ts [utils/cookie.ts] export const scriptsConsent = useScriptTriggerConsent() ``` ```vue [app.vue] ``` ```vue [components/cookie-banner.vue] ``` ### 响应式来源 如果外部存储管理状态,则传入 `Ref`。 ```ts const agreedToCookies = ref(false) const consent = useScriptTriggerConsent({ consent: agreedToCookies }) ``` ### 撤销 撤销同意会切换响应式的 `consented` ref。一旦加载门控的 promise 已解析,脚本就已经加载完成;如果你需要在撤销时进行清理,请监听 `consented`。 ```vue ``` ### 在同意后延迟加载 ```ts const consent = useScriptTriggerConsent({ consent: agreedToCookies, postConsentTrigger: () => new Promise(resolve => setTimeout(resolve, 3000), ), }) ``` ## 逐脚本同意 API 每个具备同意感知的 `useScriptX()` 都会返回一个 `consent` 对象,其类型与厂商原生 API 对应。将它与 `defaultConsent` 结合用于初始策略(在 `clientInit` 中、厂商首次触发调用之前应用),并在你的 cookie 横幅中调用 `consent.*` 进行更新。对于 GCMv2 脚本(Google Analytics、Google Tag Manager),还提供了 `consent.default(state)`,用于基于运行时派生的默认值;这两种方法都会根据规范的 GCMv2 架构验证输入,并在遇到未知键或非 `granted`/`denied` 值时通过 `consola` 发出警告。 ```ts const { consent } = useScriptGoogleAnalytics({ id: 'G-XXXXXXXX', defaultConsent: { ad_storage: 'denied', analytics_storage: 'denied' }, }) function onAcceptAll() { consent.update({ ad_storage: 'granted', ad_user_data: 'granted', ad_personalization: 'granted', analytics_storage: 'granted', }) } ``` ### 各厂商支持范围
脚本 defaultConsent 运行时 consent.*
Google Analytics Partial < ConsentState > (GCMv2) consent . default (state) / consent . update (state)
Google Tag Manager Partial < ConsentState > (GCMv2) consent . default (state) / consent . update (state)
Bing UET { ad_storage } consent . update ( { ad_storage } )
Meta Pixel 'granted' | 'denied' consent . grant () / consent . revoke ()
TikTok Pixel 'granted' | 'denied' | 'hold' consent . grant () / consent . revoke () / consent . hold ()
Matomo 'required' | 'given' | 'not-required' consent . give () / consent . forget () (需要 defaultConsent: 'required' 'given' )
Mixpanel 'opt-in' | 'opt-out' consent . optIn () / consent . optOut ()
PostHog 'opt-in' | 'opt-out' consent . optIn () / consent . optOut ()
Clarity boolean | Record < string, string > consent . set (value)
请查看每个脚本的注册页,了解关于信息损失型映射和厂商注意事项的说明。 ### 向多个脚本分发 当一个 cookie 横幅要驱动多个厂商时,请在你的接受处理函数中显式地为它们配置。没有魔法,完全类型化,没有信息损失的重映射: ```ts const ga = useScriptGoogleAnalytics({ id: 'G-XXX', defaultConsent: { ad_storage: 'denied', analytics_storage: 'denied' } }) const meta = useScriptMetaPixel({ id: '123', defaultConsent: 'denied' }) const matomo = useScriptMatomoAnalytics({ cloudId: 'foo.matomo.cloud', defaultConsent: 'required' }) function onAcceptAll() { ga.consent.update({ ad_storage: 'granted', ad_user_data: 'granted', ad_personalization: 'granted', analytics_storage: 'granted', }) meta.consent.grant() matomo.consent.give() } function onDeclineAll() { meta.consent.revoke() matomo.consent.forget() } ``` ### 细粒度分类 如果用户可以分别切换各个分类(分析、营销、功能),同样的模式也适用;每个脚本只接收它能理解的分类: ```ts function savePreferences(choices: { analytics: boolean, marketing: boolean }) { ga.consent.update({ analytics_storage: choices.analytics ? 'granted' : 'denied', ad_storage: choices.marketing ? 'granted' : 'denied', ad_user_data: choices.marketing ? 'granted' : 'denied', ad_personalization: choices.marketing ? 'granted' : 'denied', }) if (choices.marketing) meta.consent.grant() else meta.consent.revoke() if (choices.analytics) matomo.consent.give() else matomo.consent.forget() } ``` ## 第三方 CMP 配方 当专门的同意管理平台负责 UI 时,将其事件桥接到每个脚本的 `consent` API。 ### OneTrust ```ts const ga = useScriptGoogleAnalytics({ id: 'G-XXX', defaultConsent: { ad_storage: 'denied', analytics_storage: 'denied' } }) const meta = useScriptMetaPixel({ id: '123', defaultConsent: 'denied' }) onNuxtReady(() => { function apply() { const groups = (window as any).OnetrustActiveGroups as string | undefined if (!groups) return const analytics = groups.includes('C0002') const marketing = groups.includes('C0004') ga.consent.update({ analytics_storage: analytics ? 'granted' : 'denied', ad_storage: marketing ? 'granted' : 'denied', ad_user_data: marketing ? 'granted' : 'denied', ad_personalization: marketing ? 'granted' : 'denied', }) if (marketing) meta.consent.grant() else meta.consent.revoke() } apply() window.addEventListener('OneTrustGroupsUpdated', apply) }) ``` ### Cookiebot ```ts const ga = useScriptGoogleAnalytics({ id: 'G-XXX', defaultConsent: { ad_storage: 'denied', analytics_storage: 'denied' } }) const meta = useScriptMetaPixel({ id: '123', defaultConsent: 'denied' }) onNuxtReady(() => { function apply() { const cb = (window as any).Cookiebot if (!cb?.consent) return ga.consent.update({ analytics_storage: cb.consent.statistics ? 'granted' : 'denied', ad_storage: cb.consent.marketing ? 'granted' : 'denied', ad_user_data: cb.consent.marketing ? 'granted' : 'denied', ad_personalization: cb.consent.marketing ? 'granted' : 'denied', }) if (cb.consent.marketing) meta.consent.grant() else meta.consent.revoke() } apply() window.addEventListener('CookiebotOnAccept', apply) window.addEventListener('CookiebotOnDecline', apply) }) ```