テンプレートのマークアップと構造
テンプレートのマークアップと構造— 待合室用のHTMLテンプレートの作成およびカスタマイズ方法。
関連項目: スタイリングとCSS変数|Vueの状態とメソッド
はじめに
テンプレートのHTMLファイルを、テキストエディタとブラウザの両方で開きます。このテンプレートには、 <script id="crowdhandler-data" type="application/ld+json"> 待合室の設定を行うJSONオブジェクトをタグに指定します。本番のAPIに接続せずにすべてのキューの状態をテストできるプレビューモードに入るには、以下を追加してください。 "dev": true このオブジェクトに対して:
<script id="crowdhandler-data" type="application/ld+json">
{
"dev": true,
"status": 1,
"title": "Test Event",
"position": 150,
"estimate": 12,
"simulatePolling": true
}
</script>
~と共に "dev": true 設定すると、テンプレートは実際のAPI呼び出しを行う代わりに、このJSONオブジェクト内の値を使用します。プロパティを変更し、ファイルを保存してから、ブラウザを更新すると、異なる状態を確認できます。削除 "dev" (または、これを false) を実行して、本番モードに戻します。
プレビューステータスの値
| ステータス | 州 |
|---|---|
1 |
アクティブキュー |
2 |
エラー |
3 |
ブロック済み |
4 |
カウントダウン |
5 |
満室 |
100 |
キャプチャの入力が必要です |
プレビュー例
// Active queue
{"dev": true, "status": 1, "position": 100, "estimate": 10}
// Countdown
{"dev": true, "status": 4, "onsale": "2026-02-15T14:00:00Z"}
// Blocked
{"dev": true, "status": 3}
プレビュー対象のプロパティの完全な一覧については、「プレビューモードのリファレンス」を参照してください。
概要
このテンプレートでは、Vue 3 のディレクティブを使用して、キューの状態に応じてセクションを条件付きでレンダリングしています。メインの状態オブジェクトは ch すべてのキューデータが含まれており、APIによるポーリングを通じて自動的に更新されます(デフォルトでは60秒ごと、TTLは可変)。
必須要素
<!-- Container that shows when loaded -->
<div id="crowdhandler-template" :class="{ 'active': ch.isLoaded }" role="main">
<!-- Screen reader announcements -->
<div class="ch-sr-only" aria-live="polite" aria-atomic="true">{{ ch.announcement }}</div>
<!-- Your template content -->
<!-- Captcha container (required if captcha is enabled) -->
<div id="ch-captcha-form"></div>
<!-- Script inclusion -->
<script type="module" src="/src/main.js"></script>
</div>
キューの状態
このテンプレートでは、 ch.captchaRequired 最上位のゲートとして設定し、その後、非キャプチャ分岐内の他の状態を v-if / v-else-if チェーン。
1. CAPTCHA が必要です
ch.captchaRequired
ユーザーは、キューに入る前にキャプチャを完了する必要があります。キャプチャウィジェットは自動的に表示されます。 #ch-captcha-form.
<div v-if="ch.captchaRequired" class="ch-captcha-section">
<p class="ch-captcha-message">Please verify you're human to continue</p>
<div id="ch-captcha-form"></div>
</div>
<div v-else>
<!-- All other states go here -->
</div>
2. ブロック済み
ch.isBlocked
不審な活動があったため、このユーザーは利用停止となりました。
<div v-if="ch.isBlocked" class="ch-state-message ch-state-danger" role="alert">
<h3 class="ch-state-title">Access Denied</h3>
<p class="ch-state-description">You've been blocked due to suspicious activity.</p>
</div>
3. 満室
ch.isRoomFull
キューは定員に達しています。必ず v-else-if 小切手の不渡り確認後。
<div v-else-if="ch.isRoomFull" class="ch-state-message ch-state-warning" role="alert">
<h3 class="ch-state-title">Queue Full</h3>
<p class="ch-state-description">We're at capacity. You'll be let in when a spot opens up.</p>
</div>
4. カウントダウン(キュー待ち)
ch.isCountdown
まだ列は始まっていません。カウントダウンタイマーを表示してください。
<div v-if="ch.isCountdown" class="ch-countdown">
<p class="ch-countdown-label">Queue opens in</p>
<div class="ch-countdown-display">
<div class="ch-countdown-unit">
<div class="ch-countdown-value">{{ ch.countdown.sHours }}</div>
<div class="ch-countdown-suffix">hrs</div>
</div>
<div class="ch-countdown-unit">
<div class="ch-countdown-value">{{ ch.countdown.sMinutes }}</div>
<div class="ch-countdown-suffix">min</div>
</div>
<div class="ch-countdown-unit">
<div class="ch-countdown-value">{{ ch.countdown.sSeconds }}</div>
<div class="ch-countdown-suffix">sec</div>
</div>
</div>
</div>
5. アクティブキュー
ch.isActive
ユーザーは、順位が割り当てられたアクティブなキューに入っています。ユーザーが先頭に達すると、自動的にリダイレクトされます。
<div v-if="ch.isActive">
<h2 v-if="ch.position">{{ ch.position }}</h2>
<div v-else>Checking in!</div>
</div>
テンプレートのセクション
ヘッダー(ロゴ+タイトル)
タイトルには、文字数に基づいて動的なサイズクラスが割り当てられます。
<header class="ch-header">
<div class="ch-logo ch-enter ch-stagger-1" v-if="ch.logo">
<img :src="ch.logo" :alt="ch.title" />
</div>
<div class="ch-header-row ch-enter ch-stagger-4">
<h1 class="ch-title" :class="ch.css.title">{{ ch.title }}</h1>
</div>
</header>
メッセージ
カードの上部に表示されます。改行は以下を通じてサポートされています。 white-space: pre-line. 変更があった際に、スクリーンリーダーに通知されます。
<div class="ch-message ch-message-box ch-enter ch-stagger-6" v-if="ch.message">
{{ ch.message }}
</div>
カウントダウン + アクティブキュー・ラッパー
キューが「カウントダウン」状態から「アクティブ」状態に移行するため、これらの状態は共通のラッパーを共有しています。
<div v-if="ch.isCountdown || ch.isActive" class="ch-queue-states ch-enter ch-stagger-4">
<!-- Countdown, position, progress bar, priority code, stock -->
</div>
位置表示
<div class="ch-position-display" v-if="ch.position && ch.isActive">
<div class="ch-position-label">Your position</div>
<h2 class="ch-position-number" :key="ch.position"><strong>{{ ch.position }}</strong></h2>
</div>
進行状況バー
ポジションが存在する場合、キューの進行状況を表示します。チェックイン時には、ポーリング方式の進行状況バーを表示します(ch.position は null).
<!-- With position -->
<div v-if="ch.position && ch.isActive" class="ch-progress-container">
<div class="ch-progress-bar" role="progressbar"
:aria-valuenow="Math.round(ch.progress || 0)"
aria-valuemin="0" aria-valuemax="100">
<div class="ch-progress-fill"
:style="{ width: (isNaN(ch.progress) ? 10 : ch.progress) + '%' }"></div>
</div>
<div class="ch-progress-info">
<span v-if="ch.estimate && ch.estimate <= 1">Almost there!</span>
<span v-else-if="ch.estimate && ch.estimate > 60">More than an hour</span>
<span v-else-if="ch.estimate">Approximately {{ ch.estimate }} minutes</span>
<span v-if="ch.eta" class="ch-eta">ETA: {{ ch.eta }}</span>
</div>
</div>
<!-- Checking in (no position yet) -->
<div v-else-if="ch.isActive" class="ch-progress-container">
<div class="ch-position-label ch-checking-in">Checking in!</div>
<div class="ch-progress-bar" role="progressbar"
:aria-valuenow="Math.round(ch.nextPoll || 0)"
aria-valuemin="0" :aria-valuemax="ch.pollTTL">
<div class="ch-progress-fill"
:style="{ width: (isNaN(ch.nextPoll) ? 0 : (ch.nextPoll / ch.pollTTL * 100)) + '%' }">
</div>
</div>
</div>
株価指標
<div v-if="ch.stock !== null" class="ch-stock-badge"
:class="{
'ch-stock-low': ch.stock > 0 && ch.stock <= 50,
'ch-stock-zero': ch.stock === 0
}">
<span v-if="ch.stock > 0">
<span class="ch-stock-count">{{ ch.stock }}</span> items remaining
</span>
<span v-else>Sold out</span>
</div>
セッション情報
<!-- During countdown -->
<div v-if="ch.isCountdown" class="ch-session-info">
Stay on this page. You'll get a random position when the queue opens.
</div>
<!-- During active queue -->
<div v-if="ch.isActive">
<span v-if="ch.sessionsExpire" class="ch-session-info">
Keep this window open. You'll move forward automatically.
</span>
<span v-else-if="ch.sessionsTimeout" class="ch-session-info">
You'll be redirected when it's your turn.
You have {{ ch.sessionsTimeout }} minutes to complete your transaction.
</span>
</div>
メール通知フォーム
3つの状態:入力フォーム、送信スピナー、確認画面。ユーザーは送信後にメールアドレスを変更できます。アクティブなキュー中にのみ表示されます。
<div v-if="ch.emailAvailable && ch.isActive" class="ch-form-section ch-email-section">
<!-- Submitting -->
<div v-if="ch.email.submitting" class="ch-email-submitting">
<div class="ch-spinner" role="status" aria-label="Submitting"></div>
</div>
<!-- Form -->
<div v-else-if="!ch.email.submitted || ch.email.changing">
<div v-if="ch.email.changing" class="ch-form-title">Change notification email</div>
<div v-else class="ch-form-title">Get notified when it's your turn</div>
<div class="ch-input-group">
<input type="email" class="ch-input" placeholder="your@email.com"
v-model="ch.email.value" @keyup="validateEmail" />
<button class="ch-btn" @click="submitEmail" :disabled="!ch.email.valid">Notify me</button>
<button v-if="ch.email.changing" class="ch-btn ch-btn-secondary"
@click="cancelChangeEmail">Cancel</button>
</div>
</div>
<!-- Confirmation -->
<div v-else role="status">
<div class="ch-form-feedback ch-success">We'll email {{ ch.email.value }}</div>
<button @click="changeEmail" class="ch-btn ch-btn-sm ch-btn-change">Change</button>
</div>
</div>
優先順位コード申請書
カウントダウン中およびアクティブキュー状態の双方で利用可能です。
<div v-if="ch.priority && ch.priority.available" class="ch-form-section ch-priority-section">
<div class="ch-form-title">Got a priority code?</div>
<div class="ch-input-group">
<input type="text" class="ch-input" id="ch-priority-input"
placeholder="Enter code" v-model="ch.priority.code"
@keyup="checkValidPriorityCode" />
<button class="ch-btn" @click="prioritySubmit"
:disabled="ch.priority.submitting || !ch.priority.code">Apply</button>
</div>
<div v-if="ch.priority.submitted && ch.priority.success"
class="ch-form-feedback ch-success" role="status">Code applied!</div>
<div v-if="ch.priority.submitted && ch.priority.error"
class="ch-form-feedback ch-error" role="status">Invalid code</div>
</div>
フッター
3列レイアウト:最終チェックイン時刻(左)、セッショントークン(中央)、提供元(右)。キャプチャ表示中は非表示。
<div class="ch-footer ch-enter ch-stagger-5" v-if="!ch.captchaRequired">
<div class="ch-footer-left">
<div class="ch-footer-meta" v-if="ch.requested">
<span>Last check in:</span>
<code class="ch-footer-meta-value">
<span :class="{ 'ch-text-shimmer': ch.nextPoll < 3 }">
{{ formatDate(ch.requested, 'toLocaleTimeString') }}
</span>
</code>
</div>
</div>
<div class="ch-footer-center">
<div class="ch-token-display" v-if="ch.token.obfuscated || ch.token.value">
<span>ID:</span>
<code class="ch-token-value" tabindex="0" role="button"
@click="(e) => ch.token.copy(e, ch)">
{{ ch.token.copied ? 'Copied!' : (ch.token.obfuscated || ch.token.value) }}
</code>
</div>
</div>
<div class="ch-powered-by">
Powered by <a href="https://www.crowdhandler.com/..." target="_blank">CrowdHandler</a>
</div>
</div>
カスタマイズの理念
その index.html テンプレートは参照実装であり、システムがサポートするすべての機能を網羅しています。作成するテンプレートは、これをそのまま複製する必要はありません。
機能はそのままに、デザインを変更する
各セクションは任意ですが、テンプレートにはすべての機能を残しておくことをお勧めします。機能のマークアップを削除してもアプリケーションが動作しなくなることはありませんが、その機能の UI は表示されなくなります。ただし、販売開始中に機能を有効にする必要がある場合、マークアップがすでに用意されている方がはるかに簡単です。 たとえば、クライアントがイベントの途中でキャプチャや在庫表示を有効にすることに決めた場合、テンプレートは追加の変更を必要とせずに即座にそれをレンダリングします。マークアップがない場合、テンプレートを更新して再デプロイする必要がありますが、テンプレートのキャッシュ機能により、その更新はすぐには反映されません。
必須要件は以下の #crowdhandler-template コンテナとスクリプトが含まれています。
状態ロジックの再構築
Vueのディレクティブは自由に配置を変更できます。以下のリンクを参照してみてください。 ch.stock === 0 まず、全画面表示の「完売」パネルを表示したり、カウントダウンにティーザーメッセージを組み合わせたり、カウントダウン内に優先コード入力フォームを埋め込んだりできます。参考テンプレートでは、各状態が特定の階層構造でグループ化されていますが、自由に再構成しても構いません。
マークアップではなく、データを軸に構築する
考えてみてください ch 所定のレイアウトではなく、データAPIとして提供されます。状態のプロパティはリアクティブな値であり、デザインに応じて適切な条件分岐ロジックを適用し、どこでも、どのような組み合わせでも使用できます。リファレンステンプレートは出発点であり、制約ではありません。