You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Astro中将服务端变量传递到客户端JS?

解决Astro组件中注入Google OAuth变量的问题

核心方案:利用Astro内联脚本模板传递变量

在Astro中,直接在<script>标签使用模板语法需要配合is:inline指令,或通过全局变量注入方式传递后端参数,以下是适配后的完整Astro组件:

---
// 从后端接收凭证变量,可通过父组件传递或环境变量获取
const { CLIENT_ID, API_KEY } = Astro.props;
---

<div>
  <p>Google Calendar API Quickstart</p>

  <!-- 授权与登出按钮 -->
  <button id="authorize_button" onclick="handleAuthClick()">Authorize</button>
  <button id="signout_button" onclick="handleSignoutClick()">Sign Out</button>

  <pre id="content" style="white-space: pre-wrap;"></pre>

  <!-- 先注入全局变量,确保业务脚本可读取 -->
  <script is:inline>
    window.GOOGLE_CREDENTIALS = {
      CLIENT_ID: '{CLIENT_ID}',
      API_KEY: '{API_KEY}'
    };
  </script>

  <!-- 业务逻辑脚本,读取全局变量 -->
  <script is:inline>
    /* exported gapiLoaded */
    /* exported gisLoaded */
    /* exported handleAuthClick */
    /* exported handleSignoutClick */

    // 从全局对象获取凭证
    const CLIENT_ID = window.GOOGLE_CREDENTIALS.CLIENT_ID;
    const API_KEY = window.GOOGLE_CREDENTIALS.API_KEY;

    const DISCOVERY_DOC = 'https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest';
    const SCOPES = 'https://www.googleapis.com/auth/calendar.readonly';

    let tokenClient;
    let gapiInited = false;
    let gisInited = false;

    document.getElementById('authorize_button').style.visibility = 'hidden';
    document.getElementById('signout_button').style.visibility = 'hidden';

    function gapiLoaded() {
      gapi.load('client', intializeGapiClient);
    }

    async function intializeGapiClient() {
      await gapi.client.init({
        apiKey: API_KEY,
        discoveryDocs: [DISCOVERY_DOC],
      });
      gapiInited = true;
      maybeEnableButtons();
    }

    function gisLoaded() {
      tokenClient = google.accounts.oauth2.initTokenClient({
        client_id: CLIENT_ID,
        scope: SCOPES,
        callback: '',
      });
      gisInited = true;
      maybeEnableButtons();
    }

    function maybeEnableButtons() {
      if (gapiInited && gisInited) {
        document.getElementById('authorize_button').style.visibility = 'visible';
      }
    }

    function handleAuthClick() {
      tokenClient.callback = async (resp) => {
        if (resp.error !== undefined) {
          throw (resp);
        }
        document.getElementById('signout_button').style.visibility = 'visible';
        document.getElementById('authorize_button').innerText = 'Refresh';
        await listUpcomingEvents();
      };

      if (gapi.client.getToken() === null) {
        tokenClient.requestAccessToken({prompt: 'consent'});
      } else {
        tokenClient.requestAccessToken({prompt: ''});
      }
    }

    function handleSignoutClick() {
      const token = gapi.client.getToken();
      if (token !== null) {
        google.accounts.oauth2.revoke(token.access_token);
        gapi.client.setToken('');
        document.getElementById('content').innerText = '';
        document.getElementById('authorize_button').innerText = 'Authorize';
        document.getElementById('signout_button').style.visibility = 'hidden';
      }
    }

    async function listUpcomingEvents() {
      let response;
      try {
        const request = {
          'calendarId': 'primary',
          'timeMin': (new Date()).toISOString(),
          'showDeleted': false,
          'singleEvents': true,
          'maxResults': 10,
          'orderBy': 'startTime',
        };
        response = await gapi.client.calendar.events.list(request);
      } catch (err) {
        document.getElementById('content').innerText = err.message;
        return;
      }

      const events = response.result.items;
      if (!events || events.length == 0) {
        document.getElementById('content').innerText = 'No events found.';
        return;
      }
      const output = events.reduce(
          (str, event) => `${str}${event.summary} (${event.start.dateTime || event.start.date})\n`,
          'Events:\n');
      document.getElementById('content').innerText = output;
    }
  </script>

  <!-- Google官方API脚本 -->
  <script async defer src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
  <script async defer src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>
</div>

关键细节说明

  1. is:inline指令作用:Astro默认会打包处理脚本,添加该指令后脚本会直接渲染到HTML中,允许使用模板语法注入变量。
  2. 全局变量传递逻辑:先通过内联脚本将后端传入的凭证挂载到window,确保业务脚本加载时变量已存在。
  3. 变量接收方式:组件顶部通过Astro.props接收参数,也可直接使用环境变量(如import.meta.env.GOOGLE_CLIENT_ID)。

替代方案:通过DOM data属性传递变量

若不想使用全局变量,可将变量存储在DOM元素的data属性中,再在业务脚本中读取:

---
const { CLIENT_ID, API_KEY } = Astro.props;
---

<div id="calendar-auth-container" data-client-id="{CLIENT_ID}" data-api-key="{API_KEY}">
  <!-- ... 按钮与内容区域 ... -->
</div>

<script is:inline>
  const container = document.getElementById('calendar-auth-container');
  const CLIENT_ID = container.dataset.clientId;
  const API_KEY = container.dataset.apiKey;
  // ... 后续业务逻辑 ...
</script>

内容的提问来源于stack exchange,提问作者ThomasReggi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 06:18:29