> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.onesignal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 应用内消息HTML模板

> 可用于OneSignal应用内消息的复制粘贴HTML模板，以及所需的设置和常见问题。

使用这些复制粘贴HTML模板来更快地构建自定义OneSignal应用内消息。

我们的应用内消息HTML编辑器让您可以使用HTML、CSS和JavaScript完全控制应用内消息的布局和行为。该编辑器**不包含内置模板**，但此页面提供**可直接使用的示例**，您可以粘贴到编辑器中并进行自定义。

<Note>
  这些模板在应用内消息webview中运行。要关闭消息、打开URL、标记用户和捕获点击，请使用[应用内消息JS API](./in-app-message-api)。
</Note>

## 先决条件

在开始之前，我们建议您查看：

* [应用内消息概述](./in-app-messages-setup)页面。
* [使用HTML编辑器设计应用内消息](./design-your-in-app-message-with-html)页面。

<Warning>
  不要在模板代码中放置机密信息（API密钥、令牌）。将所有应用内消息输入视为不可信的，并在您的应用或后端中验证。
</Warning>

## 如何使用模板

1. 在OneSignal中，转到**消息 > 应用内 > 新建应用内消息**。
2. 选择**HTML编辑器**。
3. 在下面找到一个模板。
4. 从代码块中复制完整的HTML并粘贴到编辑器中。
5. 更新占位符（URL、端点、日期和文案）。
6. 在真实设备上测试，然后发布。

## 可用模板

<Card title="邮箱收集表单" img="https://mintcdn.com/onesignal/gVwen8HnVQfsFC-e/images/html-iam/email-form.png?fit=max&auto=format&n=gVwen8HnVQfsFC-e&q=85&s=d74293c6b7c0c2ed0694078d41a8c1c8" href="#email-form" width="1258" height="708" data-path="images/html-iam/email-form.png">
  询问用户的邮箱并通过点击名称发送到您的应用。
</Card>

<Card title="手机号码收集表单" img="https://mintcdn.com/onesignal/gVwen8HnVQfsFC-e/images/html-iam/sms-form.png?fit=max&auto=format&n=gVwen8HnVQfsFC-e&q=85&s=d5444750f75c638f9aacc4cadc8ce8e3" href="#sms-form" width="1251" height="704" data-path="images/html-iam/sms-form.png">
  询问并获得发送短信的同意。包含E.164格式的电话号码，并通过点击名称发送到您的应用。
</Card>

<Card title="清单调查" img="https://mintcdn.com/onesignal/gVwen8HnVQfsFC-e/images/html-iam/checklist-survey.png?fit=max&auto=format&n=gVwen8HnVQfsFC-e&q=85&s=e35945ecfefabdb013053aa814c3618d" href="#checklist-survey" width="574" height="322" data-path="images/html-iam/checklist-survey.png">
  多选调查，您可以发送到后端或转换为标签。
</Card>

<Card title="倒计时" img="https://mintcdn.com/onesignal/gVwen8HnVQfsFC-e/images/html-iam/count_down.png?fit=max&auto=format&n=gVwen8HnVQfsFC-e&q=85&s=bcc786162ca0f4837c244f0d7f03097d" href="#countdown" width="1820" height="1024" data-path="images/html-iam/count_down.png">
  用于时效性促销的倒计时器。
</Card>

<Card title="促销转盘" img="https://mintcdn.com/onesignal/gVwen8HnVQfsFC-e/images/html-iam/promo-wheel.png?fit=max&auto=format&n=gVwen8HnVQfsFC-e&q=85&s=dfd0aa50f5981774267644acb17da817" href="#promo-wheel" width="574" height="322" data-path="images/html-iam/promo-wheel.png">
  转盘赢奖促销体验（自定义促销处理）。
</Card>

<Card title="测验模态框" img="https://mintcdn.com/onesignal/gVwen8HnVQfsFC-e/images/html-iam/quiz_modal.png?fit=max&auto=format&n=gVwen8HnVQfsFC-e&q=85&s=66399edf105d0799bdfab78412a60744" href="#quiz-modal" width="1820" height="1024" data-path="images/html-iam/quiz_modal.png">
  可以根据用户得分标记用户的测验体验。
</Card>

<Card title="评分调查" img="https://mintcdn.com/onesignal/gVwen8HnVQfsFC-e/images/html-iam/ranking-survey.png?fit=max&auto=format&n=gVwen8HnVQfsFC-e&q=85&s=42cfa0b554dbfe1f59f61c47306f8460" href="#ranking-survey" width="574" height="322" data-path="images/html-iam/ranking-survey.png">
  1-5分评分调查（发送到您的端点或标记用户）。
</Card>

<Card title="音频/视频播放器" img="https://mintcdn.com/onesignal/gVwen8HnVQfsFC-e/images/html-iam/ui-ui.png?fit=max&auto=format&n=gVwen8HnVQfsFC-e&q=85&s=cf9bdf607dda82687faa1c874fddebb4" href="#audio/video-player" width="574" height="322" data-path="images/html-iam/ui-ui.png">
  直接MP3文件的简单音频预览UI。
</Card>

<Card title="垂直滑动" img="https://mintcdn.com/onesignal/gVwen8HnVQfsFC-e/images/html-iam/vertical-swiping.png?fit=max&auto=format&n=gVwen8HnVQfsFC-e&q=85&s=3cb4a092cf2e4bb14bee214a0c22c58b" href="#vertical-swiping" width="574" height="322" data-path="images/html-iam/vertical-swiping.png">
  多张幻灯片垂直滑动引导或功能介绍。
</Card>

***

### 邮箱表单

通过应用内消息收集邮箱[订阅](./subscriptions)。

此表单工作原理：

1. 用户输入邮箱地址并勾选同意框。
2. 提交时，调用OneSignal的创建用户API在您的应用中创建邮箱订阅。
3. 另外，模板调用`OneSignalIamApi.addClickName(e, email)`将邮箱地址传递给我们SDK的应用内消息点击监听器。
4. 在您的应用内，您可以添加应用内消息点击监听器来读取点击名称并将邮箱传递给我们SDK的`addEmail`方法。

您可能会注意到步骤2和步骤4都涉及创建邮箱订阅。

* 步骤2不需要直接在应用中添加代码，但如果您调用了`login`方法，也不会将邮箱订阅添加到用户。
* 步骤4需要额外的代码（应用内消息点击监听器），但如果您调用了`login`方法，也会将邮箱订阅添加到用户。

<Accordion title="邮箱表单HTML代码">
  \*\*将`YOUR_APP_ID`替换为在[设置 > 密钥和ID](./keys-and-ids)中找到的OneSignal应用ID。

  ```html theme={null}
  <!DOCTYPE html>
  <html lang="en">
  <head>
      <meta charset="UTF-8">
      <!-- 防止iOS在输入焦点时缩放 -->
      <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
      <style>
          /* ===== 重置 ===== */
          * {
              box-sizing: border-box;
              margin: 0;
              padding: 0;
          }
          
          /* ===== 基础 ===== */
          body {
              font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
              background: transparent;
              display: flex;
              justify-content: center;
              align-items: center;
              min-height: 100vh;
              padding: 20px;
          }
          
          /* ===== 卡片 ===== */
          .container {
              position: relative;
              background: #ffffff;
              border-radius: 16px;
              padding: 32px 24px;
              max-width: 340px;
              width: 100%;
              box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
              text-align: center;
              overflow: hidden;
          }
          
          /* ===== 关闭按钮 ===== */
          .close-btn {
              position: absolute;
              top: 12px;
              right: 12px;
              background: none;
              border: none;
              font-size: 24px;
              color: #999;
              cursor: pointer;
              padding: 4px 8px;
              line-height: 1;
          }
          
          .close-btn:hover {
              color: #333;
          }
          
          /* ===== 排版 ===== */
          h1 {
              font-size: 22px;
              font-weight: 600;
              color: #333;
              margin-bottom: 8px;
          }
          
          p {
              font-size: 14px;
              color: #666;
              margin-bottom: 24px;
              line-height: 1.5;
          }
          
          /* ===== 邮箱输入 ===== */
          .email-input {
              width: 100%;
              padding: 14px 12px;
              font-size: 16px; /* 防止iOS缩放 */
              border: 2px solid #e0e0e0;
              border-radius: 10px;
              margin-bottom: 16px;
              outline: none;
              transition: border-color 0.2s;
              touch-action: manipulation; /* 改善移动端体验 */
          }
          
          .email-input:focus {
              border-color: #007AFF;
          }
          
          .email-input::placeholder {
              color: #aaa;
          }
          
          /* ===== 同意复选框 ===== */
          .consent-wrapper {
              display: flex;
              align-items: flex-start;
              gap: 10px;
              text-align: left;
              margin-bottom: 16px;
          }
          
          .consent-wrapper input[type="checkbox"] {
              width: 18px;
              height: 18px;
              margin-top: 2px;
              cursor: pointer;
              flex-shrink: 0;
          }
          
          .consent-wrapper label {
              font-size: 13px;
              color: #666;
              line-height: 1.4;
              cursor: pointer;
          }
          
          /* ===== 提交按钮 ===== */
          .submit-btn {
              width: 100%;
              padding: 14px 24px;
              font-size: 16px;
              font-weight: 600;
              color: #fff;
              background: #007AFF;
              border: none;
              border-radius: 10px;
              cursor: pointer;
              transition: background 0.2s, opacity 0.2s;
          }
          
          .submit-btn:hover {
              background: #0056b3;
          }
          
          .submit-btn:disabled {
              background: #ccc;
              cursor: not-allowed;
              opacity: 0.7;
          }
          
          /* ===== 状态消息 ===== */
          .error-msg,
          .success-msg {
              font-size: 12px;
              margin-top: -12px;
              margin-bottom: 12px;
              display: none;
          }
          
          .error-msg {
              color: #dc3545;
          }
          
          .success-msg {
              color: #28a745;
          }
          
          /* ===== 响应式 ===== */
          @media (max-width: 480px) {
              body {
                  padding: 15px;
              }
              
              .container {
                  padding: 24px 20px;
              }
              
              h1 {
                  font-size: 20px;
              }
          }
      </style>
  </head>
  <body>
      <div class="container">
          <!-- 关闭按钮 -->
          <button class="close-btn" onclick="OneSignalIamApi.close(event)">&times;</button>
          
          <!-- 标题和描述 -->
          <h1>获取独家更新</h1>
          <p>输入您的邮箱以获取最新新闻和特别优惠。</p>
          
          <!-- 表单 -->
          <form id="emailForm">
              <!-- 错误消息 -->
              <div class="error-msg" id="errorMsg">请输入有效的邮箱地址</div>
              
              <!-- 成功消息 -->
              <div class="success-msg" id="successMsg">感谢您！我们会尽快联系您。</div>
              
              <!-- 邮箱输入 -->
              <input 
                  type="email" 
                  class="email-input" 
                  id="emailInput" 
                  placeholder="输入您的邮箱" 
                  required
              />
              
              <!-- 同意复选框 -->
              <div class="consent-wrapper">
                  <input type="checkbox" id="consent" required />
                  <label for="consent">
                      我同意接收邮箱更新和促销内容。您可以随时取消订阅。
                  </label>
              </div>
              
              <!-- 提交按钮 -->
              <button type="submit" class="submit-btn" id="submitBtn">
                  订阅更新
              </button>
          </form>
      </div>

      <script>
          // 等待DOM加载
          document.addEventListener('DOMContentLoaded', function() {
              const form = document.getElementById('emailForm');
              const emailInput = document.getElementById('emailInput');
              const consentCheckbox = document.getElementById('consent');
              const submitBtn = document.getElementById('submitBtn');
              const errorMsg = document.getElementById('errorMsg');
              const successMsg = document.getElementById('successMsg');

              // 邮箱验证函数
              function isValidEmail(email) {
                  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
                  return emailRegex.test(email);
              }

              // 显示错误消息
              function showError(message) {
                  errorMsg.textContent = message;
                  errorMsg.style.display = 'block';
                  successMsg.style.display = 'none';
              }

              // 显示成功消息
              function showSuccess(message) {
                  successMsg.textContent = message;
                  successMsg.style.display = 'block';
                  errorMsg.style.display = 'none';
              }

              // 隐藏消息
              function hideMessages() {
                  errorMsg.style.display = 'none';
                  successMsg.style.display = 'none';
              }

              // 输入验证和UI更新
              function validateForm() {
                  const email = emailInput.value.trim();
                  const isEmailValid = isValidEmail(email);
                  const isConsentGiven = consentCheckbox.checked;
                  
                  submitBtn.disabled = !(isEmailValid && isConsentGiven);
                  
                  if (email && !isEmailValid) {
                      showError('请输入有效的邮箱地址');
                  } else if (!email || isEmailValid) {
                      hideMessages();
                  }
              }

              // 事件监听器
              emailInput.addEventListener('input', validateForm);
              emailInput.addEventListener('blur', validateForm);
              consentCheckbox.addEventListener('change', validateForm);

              // 表单提交处理
              form.addEventListener('submit', async function(e) {
                  e.preventDefault();
                  
                  const email = emailInput.value.trim();
                  
                  if (!isValidEmail(email)) {
                      showError('请输入有效的邮箱地址');
                      return;
                  }
                  
                  if (!consentCheckbox.checked) {
                      showError('请同意接收邮箱更新');
                      return;
                  }

                  // 禁用表单
                  submitBtn.disabled = true;
                  submitBtn.textContent = '处理中...';

                  try {
                      // 步骤2：通过REST API创建邮箱订阅
                      const response = await fetch('https://api.onesignal.com/apps/YOUR_APP_ID/users', {
                          method: 'POST',
                          headers: {
                              'Content-Type': 'application/json',
                          },
                          body: JSON.stringify({
                              properties: {
                                  tags: {
                                      email_collected_via_iam: 'true'
                                  }
                              },
                              subscriptions: [
                                  {
                                      type: 'Email',
                                      token: email,
                                      enabled: true
                                  }
                              ]
                          })
                      });

                      if (response.ok) {
                          showSuccess('感谢您！我们会尽快联系您。');
                      } else {
                          throw new Error('订阅失败');
                      }

                      // 步骤3：通过点击名称传递邮箱给SDK
                      OneSignalIamApi.addClickName(e, email);

                      // 延迟关闭消息
                      setTimeout(() => {
                          OneSignalIamApi.close(e);
                      }, 2000);

                  } catch (error) {
                      console.error('邮箱订阅错误：', error);
                      showError('出现错误，请稍后重试');
                      
                      // 重新启用表单
                      submitBtn.disabled = false;
                      submitBtn.textContent = '订阅更新';
                  }
              });

              // 初始验证
              validateForm();
          });
      </script>
  </body>
  </html>
  ```
</Accordion>

**步骤4的要求：**

1. 在您的HTML提交处理器中保留[addClickName](./in-app-message-api#click-name)调用。
2. 使用我们SDK的[应用内消息点击监听器](./mobile-sdk-reference#addclicklistener-in-app)读取输入。
3. 当点击名称看起来像邮箱时，在应用内消息点击监听器中调用[addEmail](./mobile-sdk-reference#addemail-,-removeemail)方法。

**使用应用内消息点击监听器和addEmail方法的示例：**

```swift theme={null}
// 捕获HTML应用内消息中邮箱和电话的应用内消息点击处理器示例
class InAppMessageClickHandler: NSObject, OSInAppMessageClickListener {
    func onClick(event: OSInAppMessageClickEvent) {
        // 从事件中获取点击名称（操作ID）
        let clickName = event.result.actionId
        print("应用内消息被点击，actionId: \(clickName ?? "nil")")
        
        guard let value = clickName else { return }
        
        // 检查点击名称是否看起来像邮箱地址
        if value.contains("@") && value.contains(".") {
            OneSignal.User.addEmail(value)
            print("邮箱已添加到OneSignal: \(value)")
        }
        // 检查点击名称是否看起来像E.164格式的电话号码 (+1XXXXXXXXXX)
        else if value.hasPrefix("+") && value.count >= 11 {
            OneSignal.User.addSms(value)
            print("短信已添加到OneSignal: \(value)")
        }
    }
}
```

***

### 短信表单

通过应用内消息收集短信[订阅](./subscriptions)。

此表单工作原理：

1. 用户选择国家代码，输入10位数字号码，并勾选同意框。
2. 提交时，调用OneSignal的创建用户API在您的应用中创建短信订阅。
3. 另外，模板调用`OneSignalIamApi.addClickName(e, e164Phone)`将电话号码传递给我们SDK的应用内消息点击监听器。
4. 在您的应用内，您可以添加应用内消息点击监听器来读取点击名称并将电话号码传递给我们SDK的`addSms`方法。

您可能会注意到步骤2和步骤4都涉及创建短信订阅。

* 步骤2不需要直接在应用中添加代码，但如果您调用了`login`方法，也不会将短信订阅添加到用户。
* 步骤4需要额外的代码（应用内消息点击监听器），但如果您调用了`login`方法，也会将短信订阅添加到用户。

<Accordion title="短信表单HTML代码">
  \*\*将`YOUR_APP_ID`替换为在[设置 > 密钥和ID](./keys-and-ids)中找到的OneSignal应用ID。

  ```html theme={null}
  <!DOCTYPE html>
  <html lang="en">
  <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
      <style>
          /* ===== 重置 ===== */
          * {
              box-sizing: border-box;
              margin: 0;
              padding: 0;
          }
          
          /* ===== 基础 ===== */
          body {
              font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
              background: transparent;
              display: flex;
              justify-content: center;
              align-items: center;
              min-height: 100vh;
              padding: 20px;
          }
          
          /* ===== 卡片 ===== */
          .container {
              position: relative;
              background: #ffffff;
              border-radius: 16px;
              padding: 32px 24px;
              max-width: 340px;
              width: 100%;
              box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
              text-align: center;
              overflow: hidden;
          }
          
          /* ===== 关闭按钮 ===== */
          .close-btn {
              position: absolute;
              top: 12px;
              right: 12px;
              background: none;
              border: none;
              font-size: 24px;
              color: #999;
              cursor: pointer;
              padding: 4px 8px;
              line-height: 1;
          }
          
          .close-btn:hover {
              color: #333;
          }
          
          /* ===== 排版 ===== */
          h1 {
              font-size: 22px;
              font-weight: 600;
              color: #333;
              margin-bottom: 8px;
          }
          
          p {
              font-size: 14px;
              color: #666;
              margin-bottom: 24px;
              line-height: 1.5;
          }
          
          /* ===== 电话输入 ===== */
          .phone-input-wrapper {
              display: flex;
              align-items: center;
              gap: 8px;
              margin-bottom: 16px;
              width: 100%;
          }
          
          .country-select {
              display: flex;
              align-items: center;
              padding: 14px 8px;
              background: #f5f5f5;
              border: 2px solid #e0e0e0;
              border-radius: 10px;
              font-size: 14px;
              color: #333;
              flex-shrink: 0;
              cursor: pointer;
              outline: none;
              appearance: none;
              -webkit-appearance: none;
              background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
              background-repeat: no-repeat;
              background-position: right 8px center;
              padding-right: 24px;
          }
          
          .country-select:focus {
              border-color: #007AFF;
          }
          
          .phone-input {
              flex: 1;
              min-width: 0;
              padding: 14px 12px;
              font-size: 16px;
              border: 2px solid #e0e0e0;
              border-radius: 10px;
              outline: none;
              transition: border-color 0.2s;
              width: 100%;
              touch-action: manipulation;
          }
          
          .phone-input:focus {
              border-color: #007AFF;
          }
          
          .phone-input::placeholder {
              color: #aaa;
          }
          
          /* ===== 同意复选框 ===== */
          .consent-wrapper {
              display: flex;
              align-items: flex-start;
              gap: 10px;
              text-align: left;
              margin-bottom: 16px;
          }
          
          .consent-wrapper input[type="checkbox"] {
              width: 18px;
              height: 18px;
              margin-top: 2px;
              cursor: pointer;
              flex-shrink: 0;
          }
          
          .consent-wrapper label {
              font-size: 13px;
              color: #666;
              line-height: 1.4;
              cursor: pointer;
          }
          
          /* ===== 提交按钮 ===== */
          .submit-btn {
              width: 100%;
              padding: 14px 24px;
              font-size: 16px;
              font-weight: 600;
              color: #fff;
              background: #007AFF;
              border: none;
              border-radius: 10px;
              cursor: pointer;
              transition: background 0.2s, opacity 0.2s;
          }
          
          .submit-btn:hover {
              background: #0056b3;
          }
          
          .submit-btn:disabled {
              background: #ccc;
              cursor: not-allowed;
              opacity: 0.7;
          }
          
          /* ===== 状态消息 ===== */
          .error-msg,
          .success-msg {
              font-size: 12px;
              margin-top: -12px;
              margin-bottom: 12px;
              display: none;
          }
          
          .error-msg {
              color: #dc3545;
          }
          
          .success-msg {
              color: #28a745;
          }
          
          /* ===== 响应式 ===== */
          @media (max-width: 480px) {
              body {
                  padding: 15px;
              }
              
              .container {
                  padding: 24px 20px;
              }
              
              h1 {
                  font-size: 20px;
              }
              
              .phone-input-wrapper {
                  flex-direction: column;
              }
              
              .country-select {
                  width: 100%;
              }
          }
      </style>
  </head>
  <body>
      <div class="container">
          <!-- 关闭按钮 -->
          <button class="close-btn" onclick="OneSignalIamApi.close(event)">&times;</button>
          
          <!-- 标题和描述 -->
          <h1>获取短信更新</h1>
          <p>输入您的电话号码以接收重要更新和促销信息。</p>
          
          <!-- 表单 -->
          <form id="smsForm">
              <!-- 错误消息 -->
              <div class="error-msg" id="errorMsg">请输入有效的电话号码</div>
              
              <!-- 成功消息 -->
              <div class="success-msg" id="successMsg">感谢您！我们会尽快联系您。</div>
              
              <!-- 电话输入 -->
              <div class="phone-input-wrapper">
                  <select class="country-select" id="countrySelect">
                      <option value="+1">🇺🇸 +1</option>
                      <option value="+44">🇬🇧 +44</option>
                      <option value="+33">🇫🇷 +33</option>
                      <option value="+49">🇩🇪 +49</option>
                      <option value="+34">🇪🇸 +34</option>
                      <option value="+39">🇮🇹 +39</option>
                      <option value="+81">🇯🇵 +81</option>
                      <option value="+82">🇰🇷 +82</option>
                      <option value="+86">🇨🇳 +86</option>
                      <option value="+91">🇮🇳 +91</option>
                      <option value="+61">🇦🇺 +61</option>
                      <option value="+55">🇧🇷 +55</option>
                      <option value="+52">🇲🇽 +52</option>
                      <option value="+7">🇷🇺 +7</option>
                      <option value="+90">🇹🇷 +90</option>
                  </select>
                  <input 
                      type="tel" 
                      class="phone-input" 
                      id="phoneInput" 
                      placeholder="输入电话号码" 
                      required
                  />
              </div>
              
              <!-- 同意复选框 -->
              <div class="consent-wrapper">
                  <input type="checkbox" id="consent" required />
                  <label for="consent">
                      我同意接收短信更新和促销内容。标准短信费率适用。您可以随时取消订阅。
                  </label>
              </div>
              
              <!-- 提交按钮 -->
              <button type="submit" class="submit-btn" id="submitBtn">
                  订阅短信更新
              </button>
          </form>
      </div>

      <script>
          // 等待DOM加载
          document.addEventListener('DOMContentLoaded', function() {
              const form = document.getElementById('smsForm');
              const countrySelect = document.getElementById('countrySelect');
              const phoneInput = document.getElementById('phoneInput');
              const consentCheckbox = document.getElementById('consent');
              const submitBtn = document.getElementById('submitBtn');
              const errorMsg = document.getElementById('errorMsg');
              const successMsg = document.getElementById('successMsg');

              // 电话号码验证函数
              function isValidPhone(phone) {
                  // 基本验证：7-15位数字，可能包含连字符、空格或括号
                  const phoneRegex = /^[\d\s\-\(\)]{7,15}$/;
                  return phoneRegex.test(phone.replace(/\s/g, ''));
              }

              // 格式化为E.164格式
              function formatToE164(countryCode, phone) {
                  // 移除所有非数字字符
                  const cleanPhone = phone.replace(/\D/g, '');
                  return countryCode + cleanPhone;
              }

              // 显示错误消息
              function showError(message) {
                  errorMsg.textContent = message;
                  errorMsg.style.display = 'block';
                  successMsg.style.display = 'none';
              }

              // 显示成功消息
              function showSuccess(message) {
                  successMsg.textContent = message;
                  successMsg.style.display = 'block';
                  errorMsg.style.display = 'none';
              }

              // 隐藏消息
              function hideMessages() {
                  errorMsg.style.display = 'none';
                  successMsg.style.display = 'none';
              }

              // 表单验证和UI更新
              function validateForm() {
                  const phone = phoneInput.value.trim();
                  const isPhoneValid = isValidPhone(phone);
                  const isConsentGiven = consentCheckbox.checked;
                  
                  submitBtn.disabled = !(isPhoneValid && isConsentGiven);
                  
                  if (phone && !isPhoneValid) {
                      showError('请输入有效的电话号码');
                  } else if (!phone || isPhoneValid) {
                      hideMessages();
                  }
              }

              // 事件监听器
              phoneInput.addEventListener('input', validateForm);
              phoneInput.addEventListener('blur', validateForm);
              consentCheckbox.addEventListener('change', validateForm);
              countrySelect.addEventListener('change', validateForm);

              // 表单提交处理
              form.addEventListener('submit', async function(e) {
                  e.preventDefault();
                  
                  const phone = phoneInput.value.trim();
                  const countryCode = countrySelect.value;
                  
                  if (!isValidPhone(phone)) {
                      showError('请输入有效的电话号码');
                      return;
                  }
                  
                  if (!consentCheckbox.checked) {
                      showError('请同意接收短信更新');
                      return;
                  }

                  // 禁用表单
                  submitBtn.disabled = true;
                  submitBtn.textContent = '处理中...';

                  const e164Phone = formatToE164(countryCode, phone);

                  try {
                      // 步骤2：通过REST API创建短信订阅
                      const response = await fetch('https://api.onesignal.com/apps/YOUR_APP_ID/users', {
                          method: 'POST',
                          headers: {
                              'Content-Type': 'application/json',
                          },
                          body: JSON.stringify({
                              properties: {
                                  tags: {
                                      sms_collected_via_iam: 'true'
                                  }
                              },
                              subscriptions: [
                                  {
                                      type: 'SMS',
                                      token: e164Phone,
                                      enabled: true
                                  }
                              ]
                          })
                      });

                      if (response.ok) {
                          showSuccess('感谢您！我们会尽快联系您。');
                      } else {
                          throw new Error('订阅失败');
                      }

                      // 步骤3：通过点击名称传递电话号码给SDK
                      OneSignalIamApi.addClickName(e, e164Phone);

                      // 延迟关闭消息
                      setTimeout(() => {
                          OneSignalIamApi.close(e);
                      }, 2000);

                  } catch (error) {
                      console.error('短信订阅错误：', error);
                      showError('出现错误，请稍后重试');
                      
                      // 重新启用表单
                      submitBtn.disabled = false;
                      submitBtn.textContent = '订阅短信更新';
                  }
              });

              // 初始验证
              validateForm();
          });
      </script>
  </body>
  </html>
  ```
</Accordion>

### 清单调查

将调查结果发布到您的后端的多选调查。

* 在 `handleSurveyAnswer()` 中设置您的端点。
* 更新复选框 `name` 值和标签以匹配您的问题。

<Note>
  如果您保留 `var url = ""`，请求将失败。设置一个真实的端点或用标记替换 `fetch()`（下面的示例）。
</Note>

<Accordion title="HTML 代码">
  ```html theme={null}
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <title>Onesignal In-App Message</title>
      <!-- Google 字体 -->
      <link rel="preconnect" href="https://fonts.googleapis.com" />
      <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
      <link
        href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@500;700&family=Raleway:wght@500;700&display=swap"
        rel="stylesheet"
      />
      <style>
        * {
          box-sizing: border-box;
        }

        body {
          margin: 0;
          padding-top: var(--safe-area-inset-top);
          padding-right: var(--safe-area-inset-right);
          padding-bottom: calc(var(--safe-area-inset-bottom) + 20px);
          padding-left: var(--safe-area-inset-left);
          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
            Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
          display: flex;
          align-items: center;
        }

        .center-modal {
          position: relative;
          background: #fae8cd;
          margin: 18px;
          border-radius: 8px;

          display: flex;
          flex-direction: column;
          justify-content: center;
          height: 85%;
          max-height: 640px;
          width: 100%;
          box-shadow: rgb(0 0 0 / 30%) 0px 0px 12.5px,
            rgb(0 0 0 / 15%) 0px 0px 2.5px;
        }

        .center-modal .close-button {
          position: absolute;
          top: 10;
          right: 10;
          background: rgba(255, 255, 255, 0.5);
          border: none;
          z-index: 1;
          display: flex;
          justify-content: center;
          flex-direction: column;
          align-items: center;
          /* 提示：让你的关闭按钮相对较大，这样很容易点击 */
          min-width: 36px;
          min-height: 36px;
          border-radius: 50%;
        }

        .center-modal .headings {
          padding: 24px 24px 0 24px;
        }

        .center-modal h1 {
          margin: 32px 0 0 0;
          color: #222;
          text-decoration: none;
          font-family: Raleway;
          font-size: 24px;
          font-weight: 700;
          line-height: 28px;
          letter-spacing: 0px;
          text-align: left;
        }

        .center-modal h2 {
          font-family: Raleway;
          font-size: 16px;
          font-weight: 500;
          line-height: 24px;
          letter-spacing: 0px;
          text-align: left;
          color: #73777b;
        }

        form {
          overflow: auto;
          padding-bottom: 53px;
        }

        form div {
          display: flex;
          justify-content: flex-start;
          align-items: center;
          padding: 12px;
          gap: 8px;
          background: #ffffff;
          border-radius: 8px;
          border: none;
          margin: 12px 24px;
        }

        form input[type="checkbox"] {
          /* 如果不使用 autoprefixer 则添加 */
          -webkit-appearance: none;
          outline: none !important;
          appearance: none;
          /* 对于 iOS < 15 去掉渐变背景 */
          background-color: #fff;
          /* 不通过 appearance 移除 */
          margin-right: 6px;
          font: inherit;
          color: currentColor;
          width: 1.15em;
          height: 1.15em;
          border: 0.15em solid #cbd1d7;
          border-radius: 0.15em;
          transform: translateY(-0.075em);
          display: grid;
          place-content: center;
        }

        input[type="checkbox"]::before {
          content: "";
          width: 0.65em;
          height: 0.65em;
          transform: scale(0);
          transition: 120ms transform ease-in-out;
          box-shadow: inset 1em 1em #33717a;
          transform-origin: bottom left;
          clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
        }

        input[type="checkbox"]:checked::before {
          transform: scale(1);
        }

        form input[type="submit"] {
          color: #fff;
          position: absolute;
          bottom: 0;
          width: 100%;
          left: 0;
          height: 70px;
          border: none;
          background: #28333e;

          font-family: "Raleway";
          font-style: normal;
          font-weight: 500;
          font-size: 18px;
          line-height: 21px;
        }

        .flex-container {
          display: flex;
          flex-direction: column;
        }

        @media screen and (min-width: 480px) {
          .flex-container {
            flex-direction: row;
            grid-gap: 12px;
            width: 100%;
          }
        }
      </style>
    </head>

    <body>
      <div class="center-modal">
        <div class="close-button" data-onesignal-unique-label="close-button">
          <svg
            width="10"
            height="10"
            preserveAspectRatio="none"
            viewBox="0 0 8 8"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              d="M7.80309 1.14768C8.06564 0.885137 8.06564 0.459453 7.80309 0.196909C7.54055 -0.0656362 7.11486 -0.0656362 6.85232 0.196909L4 3.04923L1.14768 0.196909C0.885137 -0.0656362 0.459453 -0.0656362 0.196909 0.196909C-0.0656362 0.459453 -0.0656362 0.885137 0.196909 1.14768L3.04923 4L0.196909 6.85232C-0.0656362 7.11486 -0.0656362 7.54055 0.196909 7.80309C0.459453 8.06564 0.885137 8.06564 1.14768 7.80309L4 4.95077L6.85232 7.80309C7.11486 8.06564 7.54055 8.06564 7.80309 7.80309C8.06564 7.54055 8.06564 7.11486 7.80309 6.85232L4.95077 4L7.80309 1.14768Z"
              fill="#111111"
            />
          </svg>
        </div>
        <div class="headings">
          <h1>有什么过敏反应吗？</h1>
          <h2>获得更好的推荐和其他定制化体验</h2>
        </div>
        <form>
          <div>
            <input type="checkbox" name="dairy" />
            <label for="dairy">乳制品</label>
          </div>
          <div>
            <input type="checkbox" name="eggs" />
            <label for="eggs">鸡蛋</label>
          </div>
          <div>
            <input type="checkbox" name="treeNuts"/>
            <label for="treeNuts">坚果</label>
          </div>
          <div>
            <input type="checkbox" name="shellfish" />
            <label for="shellfish">贝类</label>
          </div>
          <div>
            <input type="checkbox" name="wheat" />
            <label for="wheat">小麦</label>
          </div>
          <div>
            <input type="checkbox" name="peanuts" />
            <label for="peanuts">花生</label>
          </div>
          <div>
            <input type="checkbox" name="soy" />
            <label for="soy">大豆</label>
          </div>
          <div>
            <input type="checkbox" name="seafood" />
            <label for="seafood">海鲜</label>
          </div>
          <div>
            <input type="checkbox" name="sesame" />
            <label for="sesame">芝麻</label>
          </div>
          <div>
            <input type="checkbox" name="gluten" />
            <label for="gluten">麸质</label>
          </div>
          <input type="submit" data-onesignal-unique-label="submit-button" />
        </form>
      </div>
      <script>
        if (iamInfo) {
          iamInfo.shouldVerticalDragDismissMessage = false;
        }
        // 您的代码在这里
        document
          .querySelector(".close-button")
          .addEventListener("click", function (e) {
            OneSignalIamApi.close(e);
          });

        document.querySelectorAll("input[type=checkbox").forEach(function (node) {
          node.addEventListener("click", function (e) {
            if (e.target.checked) {
              e.target.parentElement.style["background-color"] = "#33717A";
              e.target.parentElement.style["color"] = "#FFF";
              e.target.style["border"] = "none";
            } else {
              e.target.removeAttribute("style");
              e.target.parentElement.removeAttribute("style");
            }
          });
        });

        function handleSurveyAnswer(answers) {
          // 在此处添加您自己的调查 api 端点 url
          var url = "";
          var options = {
            method: "POST",
            headers: {
              Accept: "application/json",
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              value: answers,
            }),
          };
          fetch(url, options)
            .then((response) => response.json())
            .then((response) => console.log(response))
            .catch((err) => console.error(err));
        }

        document.querySelector("form").addEventListener("submit", function (e) {
          e.preventDefault();
          e.stopPropagation();
          var answers = {
            dairy: e.target.dairy.checked,
            eggs: e.target.eggs.checked,
            treeNuts: e.target.treeNuts.checked,
            shellfish: e.target.shellfish.checked,
            wheat: e.target.wheat.checked,
            peanuts: e.target.peanuts.checked,
            soy: e.target.soy.checked,
            seafood: e.target.seafood.checked,
            sesame: e.target.sesame.checked,
            gluten: e.target.gluten.checked,
          };
          handleSurveyAnswer(answers);
          OneSignalIamApi.close(e);
        });
      </script>
    </body>
  </html>
  ```
</Accordion>

**可选：给用户打标签而不是调用您的后端**

在提交处理程序中用以下代码替换 `handleSurveyAnswer(answers)` 调用：

```javascript theme={null}
OneSignalIamApi.tagUser(e, {
  allergies: JSON.stringify(answers)
});
```

***

### 倒计时

为限时优惠和促销活动创造紧迫感的倒计时器。自定义结束日期和重定向 URL 以匹配您的活动。

* 将 `endtime` 更新为未来的日期/时间。
* 更新 `openUrl` 目标 URL。

<Warning>
  此模板中的样本 `endtime` 是过去的日期（`2025 年 3 月 25 日`）。如果您不更新它，用户将立即触发"已结束"逻辑。
</Warning>

<Accordion title="HTML 代码">
  ```html theme={null}
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <link
        rel="stylesheet"
        href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
      />

      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <title>Onesignal In-App Message</title>
      <style>
    body {
      margin: 0;
      padding-top: var(--safe-area-inset-top);
      padding-right: var(--safe-area-inset-right);
      padding-bottom: calc(var(--safe-area-inset-bottom) + 20px);
      padding-left: var(--safe-area-inset-left);
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
      display: flex;
      align-items: center;
    }

    .container {
      color: red;
      text-align: center;
      padding-right: 10%;
    }

    li {
      display: inline-block;
      font-size: 12px;
      list-style-type: none;
      text-transform: uppercase;
    }

    li span {
      display: block;
      font-size: 20px;
    }

    .center-modal {
      position: relative;
      background: #FFF;
      margin: 18px;
      padding: 24px;
      border-radius: 8px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      height: 45%;
      width: 100%;
      box-shadow: rgb(0 0 0 / 30%) 0px 0px 12.5px, rgb(0 0 0 / 15%) 0px 0px 2.5px;
    }

    .center-modal .close-button {
      position: absolute;
      top: 0;
      right: 0;
      background: rgba(0, 0, 0, 0);
      border: none;
      z-index: 1;
      display: flex;
      justify-content: center;
      flex-direction: column;
      align-items: center;
      /* 提示：让您的关闭按钮相对较大，这样很容易点击 */
      min-width: 48px;
      min-height: 48px;
    }

    .center-modal h1 {
      margin: 0;
      margin-bottom: 12px;
      color: #222;
      font-size: 24px;
      font-style: normal;
      font-weight: normal;
      text-align: center;
      text-decoration: none;
    }

    .center-modal button {
      font-size: 16px;
      color: #fff;
      background-color: #1F8FEB;
      width: 100%;
      padding: 12px;
      border-radius: 4px;
      border: none;
      margin-bottom: 12px;
    }

    .button-container {
      display: flex;
      flex-direction: column;
    }

    @media screen and (min-width: 480px) {
      .button-container {
        flex-direction: row;
        grid-gap: 12px;
        padding-left: 20%;
        width: 40%;
      }

      .button-column {
        width: 50%;
      }

      .center-modal {
        height: 80%;
      }
    }
  </style>
    </head>

    <body>
      <div class="center-modal">
        <div class="close-button" data-onesignal-unique-label="close-button">
          <svg
            width="10"
            height="10"
            preserveAspectRatio="none"
            viewBox="0 0 8 8"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              d="M7.80309 1.14768C8.06564 0.885137 8.06564 0.459453 7.80309 0.196909C7.54055 -0.0656362 7.11486 -0.0656362 6.85232 0.196909L4 3.04923L1.14768 0.196909C0.885137 -0.0656362 0.459453 -0.0656362 0.196909 0.196909C-0.0656362 0.459453 -0.0656362 0.885137 0.196909 1.14768L3.04923 4L0.196909 6.85232C-0.0656362 7.11486 -0.0656362 7.54055 0.196909 7.80309C0.459453 8.06564 0.885137 8.06564 1.14768 7.80309L4 4.95077L6.85232 7.80309C7.11486 8.06564 7.54055 8.06564 7.80309 7.80309C8.06564 7.54055 8.06564 7.11486 7.80309 6.85232L4.95077 4L7.80309 1.14768Z"
              fill="#111111"
            />
          </svg>
        </div>

        <!-- 在下面添加通知详情 -->


        <h2>优惠即将结束！</h2>
        <h1>50% 折扣</h1>

        <div class="container">
          <div id="countdown">
            <ul>
              <li><span id="days"></span>天</li>
              <li><span id="hours"></span>小时</li>
              <li><span id="minutes"></span>分钟</li>
              <li><span id="seconds"></span>秒</li>
            </ul>
          </div>
        </div>

        <br />

        <div class="button-container">
          <div class="button-column">
            <button
              class="open-url"
              data-onesignal-unique-label="my-open-url-button"
            >
              立即注册！
            </button>
          </div>
        </div>
      </div>

      <script>
    (function() {
      const second = 1000,
        minute = second * 60,
        hour = minute * 60,
        day = hour * 24;

      // 在下面设置应用内消息应倒计时到的日期和时间
      let endtime = "Mar 25, 2025 00:00:00";
      let countDown = new Date(endtime).getTime();

      const x = setInterval(function() {
        const now = new Date().getTime();
        const distance = countDown - now;

        document.getElementById("days").innerText = Math.floor(distance / day);
        document.getElementById("hours").innerText = Math.floor((distance % day) / hour);
        document.getElementById("minutes").innerText = Math.floor((distance % hour) / minute);
        document.getElementById("seconds").innerText = Math.floor((distance % minute) / second);

        // 到达结束日期/时间时的操作
        if (distance < 0) {
          const headline = document.getElementById("headline");
          const countdown = document.getElementById("countdown");
          const content = document.getElementById("content");

          headline.innerText = "此优惠已结束..";
          countdown.style.display = "none";
          content.style.display = "block";

          clearInterval(x);
        }
      }, 1000);

      // 关闭应用内消息
      document.querySelector(".close-button").addEventListener("click", function(e) {
        OneSignalIamApi.close(e);
      });

      // 将下面的 URL 更改为您希望重定向用户的 URL
      document.querySelector(".open-url").addEventListener("click", function(e) {
        OneSignalIamApi.openUrl(e, "https://documentation.onesignal.com/docs/in-app-js-library");
      });
    })();
  </script>

    </body>
  </html>
  ```
</Accordion>

***
