---
title: "Cookie 同意"
description: "通过类型化的逐脚本 `consent` 对象，在用户同意后再加载脚本，并驱动厂商原生的同意 API。"
canonical_url: "https://nuxt-scripts.zhcndoc.com/docs/guides/consent"
last_updated: "2026-08-11T09:33:01.924Z"
---

<callout icon="i-heroicons-play" target="_blank" to="https://stackblitz.com/github/nuxt/scripts/tree/main/examples/cookie-consent">

尝试在 StackBlitz 上的实时示例：[Cookie 同意示例](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/cookie-consent) 或 [细粒度同意示例](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/granular-consent)。

</callout>

## 选择同意层

同意会影响两个不同的时刻：浏览器是否加载脚本，以及已加载的供应商会接收到哪种同意状态。Nuxt Scripts 为每种情况提供了一个 API：

1. **useScriptTriggerConsent()**：用于正常主线程路径的二进制加载门控。只有在获得同意后，脚本才会开始加载。实验性的 Partytown 路径是一个例外，因为它会在 SSR 期间写入标签，并忽略触发时机。
2. 每个具备同意功能的 `useScriptX()` 返回的单独 `consent` 对象：供应商提供的类型化 API，用于在加载后授予、撤销或更新同意。其 `defaultConsent` 选项会在供应商的 init 调用*之前*设置策略。

Nuxt Scripts 会在可行的情况下，将每个集成映射到供应商所使用的术语。例如，Google Analytics 和 Google Tag Manager 使用 [Consent Mode v2](https://developers.google.com/tag-platform/security/guides/consent)，而 Matomo 使用 [`setConsentGiven`/`forgetConsentGiven`](https://developer.matomo.org/guides/tracking-consent)。下表列出了 Nuxt Scripts 的接口；它不是一个跨供应商共享的标准。

## 二元加载门控

对于二元 Cookie 横幅，请将一个同意触发器传递给每个必须等待接受操作的主线程脚本。对于其标签必须等到选择加入后才能加载的脚本，请不要使用当前的 Partytown 路径。

<code-group>

```ts [utils/cookie.ts]
export const scriptsConsent = useScriptTriggerConsent()
```

```vue [app.vue]
<script setup lang="ts">
import { scriptsConsent } from '#imports'

useScript('https://example.com/analytics.js', {
  trigger: scriptsConsent,
})
</script>
```

```vue [components/cookie-banner.vue]
<script setup lang="ts">
import { scriptsConsent } from '#imports'
</script>

<template>
  <button @click="scriptsConsent.accept()">
    接受 Cookies
  </button>
</template>
```

</code-group>

### 响应式来源

如果外部存储管理状态，则传入 `Ref<boolean>`。

```ts
const agreedToCookies = ref(false)
const consent = useScriptTriggerConsent({ consent: agreedToCookies })
```

### 撤销

撤销同意会切换响应式的 `consented` ref。一旦加载门控 Promise 解析，脚本就已经加载；如果需要在撤销同意后将其卸载，请监听 `consented`。

```vue
<template>
  <div v-if="scriptsConsent.consented.value">
    <button @click="scriptsConsent.revoke()">
      撤销同意
    </button>
  </div>
  <button v-else @click="scriptsConsent.accept()">
    接受 Cookies
  </button>
</template>
```

### 在同意后延迟加载

```ts
const consent = useScriptTriggerConsent({
  consent: agreedToCookies,
  postConsentTrigger: () => new Promise<void>(resolve =>
    setTimeout(resolve, 3000),
  ),
})
```

在这三秒的延迟期间撤销同意会将 `consented` 更改为 `false`，但不会取消待处理的计时器；计时器结束后，触发器仍会解析。

## 每个脚本的同意 API

每个支持同意管理的 `useScriptX()` 都会返回一个类型与厂商 API 对应的 `consent` 对象。`defaultConsent` 会在厂商首次调用之前，在 `clientInit` 中设置初始策略。随后，你的 Cookie 横幅可以调用 `consent.*` 来更新该策略。

Google Analytics 和 Google Tag Manager 还会公开原始运行时命令 `consent.default(state)`。使用 `defaultConsent` 选项设置初始状态：`consent.default()` 会在组合式函数的 `clientInit` 之后运行。此时，Analytics 已将其 `js` 和 `config` 命令加入队列，而 Tag Manager 已将其 `gtm.start` / `gtm.js` 事件加入队列。两种 Google 同意方法都会根据 Consent Mode v2 架构进行验证，并通过 `consola` 针对未知键或除 `granted` 和 `denied` 之外的值发出警告。

```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',
  })
}
```

### 各厂商支持范围

<table>
<thead>
  <tr>
    <th>
      脚本
    </th>
    
    <th>
      <code>
        defaultConsent
      </code>
    </th>
    
    <th>
      运行时 <code>
        consent.*
      </code>
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Google Analytics
    </td>
    
    <td>
      <code className="language-html shiki shiki-themes github-light github-light material-theme-palenight" language="html" style="">
        <span class="sqjlB">
          ConsentState | ConsentState[]
        </span>
      </code>
      
       (GCMv2)
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          default
        </span>
        
        <span class="sqjlB">
          (state)
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          update
        </span>
        
        <span class="sqjlB">
          (state)
        </span>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Google Tag Manager
    </td>
    
    <td>
      <code className="language-html shiki shiki-themes github-light github-light material-theme-palenight" language="html" style="">
        <span class="sqjlB">
          ConsentState | ConsentState[]
        </span>
      </code>
      
       (GCMv2)
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          default
        </span>
        
        <span class="sqjlB">
          (state)
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          update
        </span>
        
        <span class="sqjlB">
          (state)
        </span>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Bing UET
    </td>
    
    <td>
      <code>
        { ad_storage }
      </code>
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          update
        </span>
        
        <span class="sqjlB">
          (
        </span>
        
        <span class="sx-uw">
          {
        </span>
        
        <span class="sqjlB">
          ad_storage
        </span>
        
        <span class="sx-uw">
          }
        </span>
        
        <span class="sqjlB">
          )
        </span>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Meta Pixel
    </td>
    
    <td>
      <code>
        'granted' | 'denied'
      </code>
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          grant
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          revoke
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      TikTok Pixel
    </td>
    
    <td>
      <code>
        'granted' | 'denied' | 'hold'
      </code>
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          grant
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          revoke
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          hold
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Matomo
    </td>
    
    <td>
      <code>
        'required' | 'given' | 'not-required'
      </code>
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          give
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          forget
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
      
       <em>
        (需要 <code>
          defaultConsent: 'required'
        </code>
        
         或 <code>
          'given'
        </code>
        
        )
      </em>
    </td>
  </tr>
  
  <tr>
    <td>
      Mixpanel
    </td>
    
    <td>
      <code>
        'opt-in' | 'opt-out'
      </code>
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          optIn
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          optOut
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      PostHog
    </td>
    
    <td>
      <code>
        'opt-in' | 'opt-out'
      </code>
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          optIn
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
      
       / <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          optOut
        </span>
        
        <span class="sqjlB">
          ()
        </span>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Clarity
    </td>
    
    <td>
      <code>
        boolean
      </code>
      
       <em>
        (当前架构也接受一个未记录文档的记录值)
      </em>
    </td>
    
    <td>
      <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
        <span class="sqjlB">
          consent
        </span>
        
        <span class="sx-uw">
          .
        </span>
        
        <span class="s0YkB">
          set
        </span>
        
        <span class="sqjlB">
          (value)
        </span>
      </code>
    </td>
  </tr>
</tbody>
</table>

请参阅每个脚本的注册页面，了解厂商特定的注意事项。

### 向多个脚本分发

各厂商不会共享统一的同意模型。当一个横幅控制多个脚本时，请明确调用每个脚本的同意 API：

```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() {
  ga.consent.update({
    ad_storage: 'denied',
    ad_user_data: 'denied',
    ad_personalization: 'denied',
    analytics_storage: 'denied',
  })
  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](https://developer.onetrust.com/onetrust/docs/single-page-applications)

```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 activeGroups = new Set(groups.split(',').filter(Boolean))
    const analytics = activeGroups.has('C0002')
    const marketing = activeGroups.has('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](https://www.cookiebot.com/en/developer/)

```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('CookiebotOnConsentReady', apply)
  window.addEventListener('CookiebotOnAccept', apply)
  window.addEventListener('CookiebotOnDecline', apply)
})
```
