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

如何让Service Worker在特定页面仅返回缓存响应并实现工单离线功能?

Got it, let’s break down how to implement this offline-capable ticket portal step by step—this is a classic progressive web app (PWA) use case, so we’ll combine Service Workers, client-side storage, and intentional state management to make it work smoothly.

1. Core Setup & Architecture

First, we’ll need three key pieces to pull this off:

  • Service Worker: Handles caching for specific pages and intercepts network requests.
  • Client-Side Storage: Use IndexedDB (or a friendly wrapper like localForage) to store checked-out tickets, offline edits, and new draft tickets.
  • State Management: Track online/offline status, ticket sync state, and handle reconnection logic.
2. Implementing Ticket Checkout & Offline Editing

Step 1: Handle "Check Out" Action (Online Only)

When a user clicks to check out a ticket while online:

  1. Fetch the full ticket data from your backend API.
  2. Store it in IndexedDB with a checked-out status and a lastSynced timestamp to track when it was pulled from the server.
  3. Update the UI to show the ticket is available offline.

Example code using localForage (simplified):

async function checkOutTicket(ticketId) {
  if (!navigator.onLine) {
    alert("You need to be online to check out a ticket!");
    return;
  }

  // Fetch ticket from API
  const response = await fetch(`/api/tickets/${ticketId}`);
  const ticketData = await response.json();

  // Store in local storage with checkout metadata
  await localForage.setItem(`ticket-${ticketId}`, {
    ...ticketData,
    status: "checked-out",
    lastSynced: new Date().toISOString(),
    needsSync: false
  });
}

Step 2: Offline View & Edit

When the app detects it’s offline (via navigator.onLine or navigator.connection):

  1. Load checked-out tickets directly from IndexedDB instead of hitting the API.
  2. For edits, save changes locally and mark the ticket with needsSync: true to flag it for later synchronization.

Example edit logic:

async function saveOfflineTicketEdit(ticketId, updatedFields) {
  const existingTicket = await localForage.getItem(`ticket-${ticketId}`);
  const updatedTicket = {
    ...existingTicket,
    ...updatedFields,
    needsSync: true,
    lastEdited: new Date().toISOString()
  };

  await localForage.setItem(`ticket-${ticketId}`, updatedTicket);
  // Update UI to confirm edit saved offline
}
3. Offline Ticket Creation & Sync

Step 1: Create Tickets Offline

When offline, generate a temporary local ID (using crypto.randomUUID() for uniqueness) and store the new ticket in IndexedDB with status: "new-ticket" and needsSync: true.

Example:

async function createOfflineTicket(ticketDetails) {
  const localId = crypto.randomUUID();
  const newTicket = {
    id: localId,
    ...ticketDetails,
    status: "new-ticket",
    needsSync: true,
    createdAt: new Date().toISOString()
  };

  await localForage.setItem(`ticket-${localId}`, newTicket);
  return localId; // Return local ID for UI tracking
}

Step 2: Sync on Reconnection

Listen for the online event to trigger synchronization when the user gets back online:

  1. Iterate through all tickets in IndexedDB marked with needsSync: true.
  2. For existing checked-out tickets: Send a PUT request to your API with the updated data. On success, clear the needsSync flag and update lastSynced.
  3. For new tickets: Send a POST request to your API. On success, replace the local ID with the server-generated ID, update the ticket status, and clear needsSync.

Example sync logic:

async function syncOfflineTickets() {
  if (!navigator.onLine) return;

  const allTickets = await localForage.keys();
  for (const key of allTickets) {
    const ticket = await localForage.getItem(key);
    if (!ticket.needsSync) continue;

    try {
      if (ticket.status === "new-ticket") {
        // Create new ticket on server
        const response = await fetch("/api/tickets", {
          method: "POST",
          body: JSON.stringify(ticket),
          headers: { "Content-Type": "application/json" }
        });
        const serverTicket = await response.json();

        // Replace local ticket with server's version
        await localForage.removeItem(key);
        await localForage.setItem(`ticket-${serverTicket.id}`, {
          ...serverTicket,
          needsSync: false,
          lastSynced: new Date().toISOString()
        });
      } else if (ticket.status === "checked-out") {
        // Update existing ticket on server
        await fetch(`/api/tickets/${ticket.id}`, {
          method: "PUT",
          body: JSON.stringify(ticket),
          headers: { "Content-Type": "application/json" }
        });

        // Mark as synced
        await localForage.setItem(key, {
          ...ticket,
          needsSync: false,
          lastSynced: new Date().toISOString()
        });
      }
    } catch (error) {
      // Handle sync failure (e.g., retry later, notify user)
      console.error(`Failed to sync ticket ${ticket.id}:`, error);
    }
  }
}

// Listen for reconnection
window.addEventListener("online", syncOfflineTickets);
4. Service Worker: Cache-Only Specific Pages

To make specific pages always return cached responses (no network requests), configure your Service Worker to intercept those requests and serve from cache exclusively.

Step 1: Pre-Cache Pages During Installation

First, cache the target pages and their dependencies (CSS, JS) when the Service Worker installs:

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open("offline-ticket-pages-v1").then((cache) => {
      return cache.addAll([
        "/ticket/offline-dashboard",
        "/ticket/checked-out",
        "/css/offline-ticket.css",
        "/js/offline-ticket.js"
        // Add any other assets these pages need
      ]);
    })
  );
});

Step 2: Intercept Requests for Cache-Only Pages

In the fetch event, check if the request is for one of your target pages. If so, serve the cached response directly—skip the network entirely. If the page isn’t cached (e.g., first load while offline), return a friendly error or fallback page.

self.addEventListener("fetch", (event) => {
  const requestUrl = new URL(event.request.url);
  // Define your cache-only page paths
  const cacheOnlyPaths = ["/ticket/offline-dashboard", "/ticket/checked-out"];

  if (cacheOnlyPaths.some(path => requestUrl.pathname.startsWith(path))) {
    event.respondWith(
      caches.match(event.request).then((cachedResponse) => {
        // Return cached response if available; else, return a fallback
        return cachedResponse || new Response(
          "<h1>Offline Page Unavailable</h1><p>Please check your connection or try again later.</p>",
          { status: 503, headers: { "Content-Type": "text/html" } }
        );
      })
    );
  }
});
5. Critical Edge Cases to Handle
  • Conflict Resolution: If a ticket was modified on the server while the user edited it offline, you’ll need logic to resolve conflicts (e.g., show both versions to the user, auto-merge non-overlapping changes).
  • Storage Limits: IndexedDB has browser-specific storage limits. Add logic to clean up old, synced tickets or notify users when storage is low.
  • Service Worker Updates: When you update your Service Worker, make sure to handle cache invalidation so users get the latest versions of your cache-only pages.
  • UI Feedback: Clearly indicate when the app is in offline mode, when edits are saved locally, and when sync is in progress.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:41:26