Marcado y estructura de las plantillas

Marcado y estructura de la plantilla: cómo crear y personalizar la plantilla HTML de tu sala de espera.

Véase también: Estilo y variables CSS | Estado y métodos de Vue

Primeros

Abre el archivo HTML de la plantilla tanto en un editor de texto como en un navegador. La plantilla contiene un <script id="crowdhandler-data" type="application/ld+json"> etiqueta con un objeto JSON que configura la sala de espera. Para acceder al modo de vista previa —en el que puedes probar todos los estados de la cola sin conectarte a la API en producción—, añade "dev": true a este objeto:

<script id="crowdhandler-data" type="application/ld+json">
{
    "dev": true,
    "status": 1,
    "title": "Test Event",
    "position": 150,
    "estimate": 12,
    "simulatePolling": true
}
</script>

Con "dev": true Una vez configurado, la plantilla utiliza los valores de este objeto JSON en lugar de realizar llamadas reales a la API. Modifica las propiedades, guarda el archivo y actualiza el navegador para ver los distintos estados. Eliminar «dev» (o configúralo en false) para volver al modo de producción.

del estado de la vista previa

Estado Estado
1 Cola activa
2 Error
3 Bloqueado
4 Cuenta atrás
5 Habitación completa
100 Se requiere un captcha

de vista previa

// 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}

Para consultar la lista completa de propiedades de vista previa, consulta la referencia del modo de vista previa.


La plantilla utiliza directivas de Vue 3 para mostrar secciones de forma condicional en función del estado de la cola. El objeto de estado principal ch contiene todos los datos de la cola y se actualiza automáticamente mediante consultas a la API (por defecto, cada 60 segundos; TTL variable).

obligatorios#

<!-- 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>

de la cola

La plantilla utiliza ch.captchaRequired como puerta de nivel superior, y a continuación comprueba otros estados dentro de la rama que no es de captcha utilizando un v-if / v-else-if cadena.

1. un captcha#

ch.captchaRequired

El usuario debe completar el captcha antes de entrar en la cola. El widget del captcha se muestra automáticamente en #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

Se ha bloqueado al usuario debido a una actividad sospechosa.

<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. Sala

ch.isRoomFull

La cola ha alcanzado su capacidad máxima. Es necesario utilizar v-else-if tras el cheque bloqueado.

<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. Cuenta atrás (Pre-Cola)

ch.isCountdown

La cola aún no se ha abierto. Muestra un temporizador de cuenta atrás.

<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. activa.

ch.isActive

El usuario se encuentra en la cola activa y ocupa una posición. Cuando llega al principio de la cola, se le redirige automáticamente.

<div v-if="ch.isActive">
  <h2 v-if="ch.position">{{ ch.position }}</h2>
  <div v-else>Checking in!</div>
</div>

de la plantilla#

Encabezado (logotipo + título)

El título adquiere una clase de tamaño dinámica en función del número de caracteres.

<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>

Se muestra encima de la tarjeta. Admite saltos de línea mediante espacio en blanco: antes de la línea. Se avisa a los lectores de pantalla cuando cambia.

<div class="ch-message ch-message-box ch-enter ch-stagger-6" v-if="ch.message">
  {{ ch.message }}
</div>

Cuenta atrás + de cola activa

Estos estados comparten un «wrapper», ya que la cola pasa del estado de cuenta atrás al estado activo.

<div v-if="ch.isCountdown || ch.isActive" class="ch-queue-states ch-enter ch-stagger-4">
  <!-- Countdown, position, progress bar, priority code, stock -->
</div>

N. de visualización de la posición

<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>

de progreso

Muestra el progreso de la cola cuando hay una posición disponible. Muestra una barra de progreso basada en sondeos al realizar el check-in (ch.position es 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>

bursátil n.º

<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>

de la sesión n.

<!-- 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>

de notificación por correo electrónico n.º

Tres estados: formulario de introducción de datos, control deslizante de envío y confirmación. Los usuarios pueden modificar su correo electrónico tras el envío. Solo se muestra mientras la cola está activa.

<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>

N. de código de prioridad

Disponible tanto durante la cuenta atrás como mientras se está en la cola activa.

<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>

Pie de

Diseño de tres columnas: hora del último inicio de sesión (izquierda), token de sesión (centro), «desarrollado por» (derecha). Oculto durante el captcha.

<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>

de la personalización#

El index.html La plantilla es una implementación de referencia: incluye todas las funciones que admite el sistema. No es necesario que tu plantilla la reproduzca íntegramente.

Mantener las funciones, cambiar el

Cada sección es opcional, pero recomendamos mantener todas las funcionalidades en tu plantilla. La aplicación no dejará de funcionar si eliminas el código de una función; simplemente no se mostrará la interfaz de usuario correspondiente. Sin embargo, es mucho más fácil tener el código ya preparado cuando necesites activar una función durante una venta en directo. Por ejemplo, si un cliente decide activar el captcha o la visualización de existencias en mitad de un evento, la plantilla lo muestra inmediatamente sin necesidad de más cambios. Sin el código de marcado, tendrías que actualizar y volver a implementar la plantilla, y el almacenamiento en caché de la plantilla implica que esa actualización no surtirá efecto de inmediato.

Los únicos requisitos imprescindibles son los siguientes: #plantilla-gestor-de-multitudes el contenedor y el archivo de script.

Reestructurar la de estado

Puedes reorganizar las directivas de Vue como quieras. Podrías consultar ch.stock === 0 En primer lugar, puedes mostrar un panel a pantalla completa con el mensaje «Agotado», combinar la cuenta atrás con un mensaje de avance o integrar el formulario del código de prioridad dentro de la cuenta atrás. La plantilla de referencia agrupa los estados en una jerarquía concreta, pero puedes reestructurarla como prefieras.

Crea en torno a los datos, no al

Piensa en ch como una API de datos, en lugar de un diseño predefinido. Las propiedades de estado son valores reactivos que puedes utilizar en cualquier lugar, en cualquier combinación y con cualquier lógica condicional que tenga sentido para tu diseño. La plantilla de referencia es un punto de partida, no una restricción.