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

JavaScript秒表跨页面控制:管理员页操控,客户端页显示时间

Hey there! I’ve built similar real-time stopwatch systems for local sports tournaments, so let’s walk through exactly how to implement your setup. Here’s a step-by-step breakdown that’s tested and ready to adapt:

Core Architecture Overview

We’ll use WebSocket-based real-time communication (via Socket.IO) to sync the stopwatch state instantly between the admin panel and client displays—critical for low-latency sports event use. We’ll also integrate MySQL for fetching event details, with a Node.js/Express backend handling both the real-time sync and database queries.

1. Backend Setup (Node.js + Socket.IO + MySQL)

First, initialize your project and install dependencies:

npm init -y && npm install express socket.io mysql2

Then create your server file (server.js) with the following code—this handles Socket connections, stopwatch state management, and MySQL data retrieval:

const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const mysql = require('mysql2/promise');

const app = express();
const server = http.createServer(app);
const io = new Server(server, {
  cors: { origin: ["https://your-admin-domain.com", "https://your-client-display-domain.com"] } // Lock this down in production!
});

// Global stopwatch state (persist to Redis/MySQL if you need server restart resilience)
let stopwatchState = {
  isRunning: false,
  startTime: null,
  elapsed: 0
};

// MySQL connection pool (efficient for repeated queries)
const pool = mysql.createPool({
  host: 'your-mysql-host',
  user: 'your-db-user',
  password: 'your-db-password',
  database: 'sports_events_db'
});

// Handle Socket connections
io.on('connection', (socket) => {
  // Send current stopwatch state to new connections
  socket.emit('update-stopwatch', stopwatchState);

  // Admin triggers start
  socket.on('start-stopwatch', () => {
    if (!stopwatchState.isRunning) {
      stopwatchState.isRunning = true;
      stopwatchState.startTime = Date.now() - stopwatchState.elapsed;
      io.emit('update-stopwatch', stopwatchState); // Broadcast to all clients
    }
  });

  // Admin triggers stop
  socket.on('stop-stopwatch', () => {
    if (stopwatchState.isRunning) {
      stopwatchState.isRunning = false;
      stopwatchState.elapsed = Date.now() - stopwatchState.startTime;
      io.emit('update-stopwatch', stopwatchState);
    }
  });

  // Admin triggers reset
  socket.on('reset-stopwatch', () => {
    stopwatchState = { isRunning: false, startTime: null, elapsed: 0 };
    io.emit('update-stopwatch', stopwatchState);
  });
});

// API endpoint to fetch MySQL event data
app.get('/api/events', async (req, res) => {
  try {
    const [rows] = await pool.query('SELECT * FROM event_details ORDER BY event_time ASC');
    res.json(rows);
  } catch (err) {
    console.error('MySQL query error:', err);
    res.status(500).json({ error: 'Failed to load event data' });
  }
});

server.listen(3000, () => console.log('Backend running on port 3000'));

2. Admin Control Panel (Simple HTML/JS)

This page is for staff only—no stopwatch display, just the three control buttons:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Stopwatch Admin Control</title>
  <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
  <style>
    .control-btn { padding: 15px 30px; margin: 0 10px; font-size: 24px; border: none; border-radius: 8px; cursor: pointer; }
    .start { background: #2ecc71; color: white; }
    .stop { background: #e74c3c; color: white; }
    .reset { background: #95a5a6; color: white; }
    body { display: flex; flex-direction: column; align-items: center; margin-top: 100px; font-family: Arial, sans-serif; }
  </style>
</head>
<body>
  <h1>Event Stopwatch Controls</h1>
  <div class="btn-group">
    <button class="control-btn start" onclick="triggerStart()">Start</button>
    <button class="control-btn stop" onclick="triggerStop()">Stop</button>
    <button class="control-btn reset" onclick="triggerReset()">Reset</button>
  </div>

  <script>
    const socket = io('https://your-backend-domain.com');

    function triggerStart() {
      socket.emit('start-stopwatch');
    }
    function triggerStop() {
      socket.emit('stop-stopwatch');
    }
    function triggerReset() {
      socket.emit('reset-stopwatch');
    }
  </script>
</body>
</html>

3. Client Display (Big Screen Ready)

This page is for the TV大屏—only shows the stopwatch and MySQL event data, no controls:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Sports Event Stopwatch</title>
  <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
  <style>
    body { margin: 0; padding: 20px; font-family: Arial, sans-serif; background: #000; color: white; }
    .stopwatch { font-size: 12vw; font-weight: bold; text-align: center; margin-top: 5vh; }
    .events-section { max-width: 90vw; margin: 10vh auto 0; }
    .events-section h2 { font-size: 4vw; text-align: center; }
    .event-item { padding: 2vw; border-bottom: 1px solid #333; font-size: 2.5vw; }
  </style>
</head>
<body>
  <div class="stopwatch" id="stopwatchDisplay">00:00:00</div>
  <div class="events-section">
    <h2>Upcoming Events</h2>
    <div id="eventsList"></div>
  </div>

  <script>
    const socket = io('https://your-backend-domain.com');
    const stopwatchDisplay = document.getElementById('stopwatchDisplay');
    const eventsList = document.getElementById('eventsList');
    let updateInterval;

    // Format milliseconds to HH:MM:SS
    function formatElapsedTime(elapsedMs) {
      const totalSeconds = Math.floor(elapsedMs / 1000);
      const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0');
      const minutes = String(Math.floor((totalSeconds % 3600) / 60)).padStart(2, '0');
      const seconds = String(totalSeconds % 60).padStart(2, '0');
      return `${hours}:${minutes}:${seconds}`;
    }

    // Update stopwatch display based on state
    socket.on('update-stopwatch', (state) => {
      // Clear existing interval to avoid duplicates
      if (updateInterval) clearInterval(updateInterval);

      if (state.isRunning) {
        // Update every second while running
        updateInterval = setInterval(() => {
          const currentElapsed = Date.now() - state.startTime;
          stopwatchDisplay.textContent = formatElapsedTime(currentElapsed);
        }, 1000);
      } else {
        // Show static elapsed time when stopped
        stopwatchDisplay.textContent = formatElapsedTime(state.elapsed);
      }
    });

    // Fetch and display MySQL event data
    async function loadEventData() {
      try {
        const response = await fetch('https://your-backend-domain.com/api/events');
        const events = await response.json();
        eventsList.innerHTML = events.map(event => `
          <div class="event-item">
            <h3>${event.event_name}</h3>
            <p>Time: ${new Date(event.event_time).toLocaleString()}</p>
            <p>Venue: ${event.venue}</p>
          </div>
        `).join('');
      } catch (err) {
        eventsList.innerHTML = '<p>Failed to load event data</p>';
      }
    }

    // Load events on page load
    window.addEventListener('load', loadEventData);
  </script>
</body>
</html>

Key Production Tips

  • CORS Security: Never use origin: "*" in production—specify only your admin and client domains to prevent unauthorized access.
  • State Persistence: If your backend restarts, the stopwatch state will reset. Add Redis or a MySQL table to save the state so it can be restored on server startup.
  • 大屏适配: Use vw (viewport width) units for text to ensure it scales perfectly on any TV size.
  • MySQL Optimization: Add indexes to your event_details table (e.g., on event_time) to speed up queries when dealing with large datasets.
  • Authentication: Add password protection to the admin panel so only authorized staff can control the stopwatch—you can use simple HTTP basic auth or a full OAuth flow.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:44:16