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

::callout{icon="i-heroicons-play" to="https://stackblitz.com/github/nuxt/scripts/tree/main/examples/cookie-consent" target="_blank"}
尝试在 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 为每种情况提供了一个 API：

1. **[`useScriptTriggerConsent()`{lang="ts"}](/docs/api/use-script-trigger-consent)**：用于正常主线程路径的二进制加载门控。只有在获得同意后，脚本才会开始加载。实验性的 Partytown 路径是一个例外，因为它会在 SSR 期间写入标签，并忽略触发时机。
2. 每个具备同意功能的 `useScriptX()`{lang="ts"} 返回的单独 `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>
```
::

### 响应式来源

如果外部存储管理状态，则传入 `Ref<boolean>`{lang="html"}。

```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()`{lang="ts"} 都会返回一个类型与厂商 API 对应的 `consent` 对象。`defaultConsent` 会在厂商首次调用之前，在 `clientInit` 中设置初始策略。随后，你的 Cookie 横幅可以调用 `consent.*` 来更新该策略。

Google Analytics 和 Google Tag Manager 还会公开原始运行时命令 `consent.default(state)`{lang="ts"}。使用 `defaultConsent` 选项设置初始状态：`consent.default()`{lang="ts"} 会在组合式函数的 `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',
  })
}
```

### 各厂商支持范围

| 脚本                 | `defaultConsent`                                      | 运行时 `consent.*`                                                                                             |
| ------------------ | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| Google Analytics   | `ConsentState \| ConsentState[]`{lang="html"} (GCMv2) | `consent.default(state)`{lang="ts"} / `consent.update(state)`{lang="ts"}                                    |
| Google Tag Manager | `ConsentState \| ConsentState[]`{lang="html"} (GCMv2) | `consent.default(state)`{lang="ts"} / `consent.update(state)`{lang="ts"}                                    |
| Bing UET           | `{ ad_storage }`                                      | `consent.update({ ad_storage })`{lang="ts"}                                                                 |
| Meta Pixel         | `'granted' \| 'denied'`                               | `consent.grant()`{lang="ts"} / `consent.revoke()`{lang="ts"}                                                |
| TikTok Pixel       | `'granted' \| 'denied' \| 'hold'`                     | `consent.grant()`{lang="ts"} / `consent.revoke()`{lang="ts"} / `consent.hold()`{lang="ts"}                  |
| Matomo             | `'required' \| 'given' \| 'not-required'`             | `consent.give()`{lang="ts"} / `consent.forget()`{lang="ts"} *(需要 `defaultConsent: 'required'` 或 `'given'`)* |
| Mixpanel           | `'opt-in' \| 'opt-out'`                               | `consent.optIn()`{lang="ts"} / `consent.optOut()`{lang="ts"}                                                |
| PostHog            | `'opt-in' \| 'opt-out'`                               | `consent.optIn()`{lang="ts"} / `consent.optOut()`{lang="ts"}                                                |
| Clarity            | `boolean` *(当前架构也接受一个未记录文档的记录值)*                      | `consent.set(value)`{lang="ts"}                                                                             |

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

### 向多个脚本分发

各厂商不会共享统一的同意模型。当一个横幅控制多个脚本时，请明确调用每个脚本的同意 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)
})
```

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
