Claude Code
编写在线状态 hook、fetch 封装函数、每一个加载状态、空状态和错误状态、错误边界、备用页面,以及模拟离线和服务器错误的 Playwright 测试,然后运行这些测试。
Claude Code + 可靠性
审核人员打开飞行模式,四处点一点,再把它关掉。如果您的应用显示白屏、原始错误信息,或者一个转个不停的加载图标,就会因准则 2.1 或功能损坏而被拒。WebViewGold 用原生离线页面处理页面加载失败的情况;而在您正在运行的 Web 应用内部发生的一切,都要靠您的代码。下面介绍 Claude Code 如何构建这一部分,以及如何像审核人员那样测试它。
每个平台一次性 $109 起,或由我们的团队全程代劳,$698 起。
外壳。当 WebView 无法加载页面时,WebViewGold 会显示其原生离线页面,而不是错误页面。在 iOS 上,您可以通过 offlinetitle、offlinemsg、screen1、screen2 和 offlinebuttontext 设置其文字,offlinescreenautoretry 会每秒自动重试,无需点按。在 Android 上,您可以用 OFFLINE_SCREEN_BACKGROUND_COLOR 设置背景色;应用的文字字符串位于 strings.xml 中。想要更进一步,两个平台都可以在离线时显示一个内置的本地 HTML 页面(iOS 为 offlinelocalhtmlswitch,Android 为 FALLBACK_USE_LOCAL_HTML_FOLDER_IF_OFFLINE),并提供一个调用 reconnect:// 的“重试”按钮,重新加载最后一个在线 URL(Reconnect API)。
您的应用。一个已经在运行的单页应用,在网络断开时不会触发页面加载。它的 fetch 请求在页面内部失败,只有您的代码才能显示合理的内容:离线横幅、带“重试”的错误状态、超时提示而不是无休止的加载图标。大多数离线相关的拒审都源于此,这也是下面由 Claude Code 构建的部分。离线模式专题页比较了离线支持的三个层级。
审核人员依据准则 2.1(应用完整性:崩溃、损坏的页面、占位内容),Google 则依据其功能损坏规则。两者都不要求您的应用完全离线可用,但都期望它说明发生了什么,并且能够恢复。
编写在线状态 hook、fetch 封装函数、每一个加载状态、空状态和错误状态、错误边界、备用页面,以及模拟离线和服务器错误的 Playwright 测试,然后运行这些测试。
设置原生离线页面的文字和颜色,把备用页面复制到两个 WebViewGold 项目中,如有需要就开启备用页面,然后构建一次。
在启动时和使用过程中开启飞行模式、慢速网络、出故障的 API,全部在真实设备上进行。这是唯一与 App Review 实际做法相符的测试。
| 情况 | 由谁处理 | 用户应该看到什么 |
|---|---|---|
| 应用启动时没有网络 | WebViewGold 离线页面或本地备用页面 | 带有您品牌风格的提示和“重试” |
| 使用过程中网络断开 | 您的 Web 应用 | 一条细长的离线横幅;需要网络的操作被禁用或排队 |
| 网络非常慢 | 您的 Web 应用 | 先显示骨架屏,约 10 到 15 秒后显示超时提示和“重试” |
| API 返回 500 | 您的 Web 应用 | 一句通俗的说明和“重试”;没有堆栈跟踪,也没有 JSON |
| 会话已过期 | 您的 Web 应用 | 返回登录页并附上简短说明,登录后再回到原来的页面 |
| 未知路由 | 您的 Web 应用 | 一个像应用一样的“未找到”页面,带“首页”按钮 |
| 计划内维护 | 您的 Web 应用 | 一个无需部署就能开启的维护页面 |

把 Web 应用转换成原生应用的绝佳方式!周转速度快,无需开发完整的原生应用就能把应用上架到应用商店!
第 7 项中的 Playwright 测试让这一切可以重复:Claude Code 编写测试,通过它的 Bash 工具运行,并修复失败的部分。
审核人员会在飞行模式和糟糕的网络下测试我们的应用。应用运行在用 WebViewGold 构建的原生 iOS 和 Android 应用中。页面加载失败时,封装层会显示原生离线页面;但一旦我们的单页应用运行起来,失败的 fetch 和路由切换就只能由我们的代码处理。让每一种故障都可见、可恢复。 1. src/lib/use-online.ts:一个基于窗口 online 和 offline 事件的 hook。离线时,在安全区域下方显示一条细长横幅:您已离线。网络恢复后,我们会重新加载您的数据。恢复在线后隐藏横幅,并重新获取可见的查询。 2. src/lib/fetch-json.ts:一个 fetch 封装函数,超时时间 12 秒(AbortController),针对离线、超时和请求失败抛出 NetworkError,遇到 401 时重定向到 /login?reason=expired,登录后返回原来的路径。用它替换所有直接的 fetch 调用。 3. 每个加载数据的页面都要有加载骨架屏、带一个有用操作的空状态,以及带一句通俗说明和“重试”按钮的错误状态。界面中不得出现原始错误信息、状态码或堆栈跟踪。 4. 需要网络的操作在离线时禁用,并显示“等待网络连接”,或者排队,在恢复在线后发送。绝不能丢失用户输入的内容。 5. 一个带友好备用界面和“重新加载”按钮的根错误边界,一个像应用一样、带“首页”按钮的“未找到”页面,以及一个可以通过环境变量开启的 /maintenance 页面。 6. 任何加载图标都不能永远转下去:每个加载状态最终都要变成内容、空状态或错误状态。 7. 使用 context.setOffline(true) 的 Playwright 测试,检查横幅、带“重试”的错误状态以及恢复在线后的恢复情况,再加一个模拟 API 返回 500 的测试。运行这些测试并修复失败项。 8. public/offline-fallback/index.html:一个完全自包含的页面(内联 CSS,不使用外部字体、脚本或图片),采用我们的品牌色,包含一段简短的提示和一个导航到 reconnect:// 的“重试”按钮。我会把它复制到 WebViewGold 项目中。 列出改动过的每个页面,以及所有无法测试的内容。
小到一次就能审阅完。请注意,navigator.onLine 为 true 并不能证明互联网可用(想想酒店的 Wi-Fi),所以失败的请求也会被当作网络错误处理。
// src/lib/use-online.ts
import { useEffect, useState } from "react";
export function useOnline() {
const [online, setOnline] = useState(typeof navigator === "undefined" ? true : navigator.onLine);
useEffect(() => {
const up = () => setOnline(true);
const down = () => setOnline(false);
window.addEventListener("online", up);
window.addEventListener("offline", down);
return () => {
window.removeEventListener("online", up);
window.removeEventListener("offline", down);
};
}, []);
return online;
}
// src/lib/fetch-json.ts
export class NetworkError extends Error {}
export async function fetchJson<T>(url: string, init: RequestInit = {}, timeoutMs = 12000): Promise<T> {
if (typeof navigator !== "undefined" && !navigator.onLine) throw new NetworkError("offline");
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
try {
const res = await fetch(url, { ...init, signal: ctrl.signal });
if (res.status === 401) {
window.location.href = "/login?reason=expired&next=" + encodeURIComponent(location.pathname);
throw new Error("unauthorized");
}
if (!res.ok) throw new Error("HTTP " + res.status);
return (await res.json()) as T;
} catch (e) {
if (e instanceof DOMException && e.name === "AbortError") throw new NetworkError("timeout");
if (e instanceof TypeError) throw new NetworkError("network"); // 请求根本没有到达服务器
throw e;
} finally {
clearTimeout(timer);
}
}所有内容都内联,因为这个页面恰恰是在什么都下载不了的时候显示的。按照iOS 设置指南和Android 设置指南(选项 C)中的说明,把它复制到每个 WebViewGold 项目的本地 HTML 文件夹中。请把页面文字改成您应用所用的语言。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Offline</title>
<style>
body { margin: 0; min-height: 100vh; display: grid; place-items: center; padding: 24px;
box-sizing: border-box; text-align: center; background: #0f172a; color: #e2e8f0;
font-family: -apple-system, system-ui, sans-serif; }
h1 { font-size: 22px; margin: 0 0 8px; }
p { margin: 0; line-height: 1.5; color: #94a3b8; }
button { margin-top: 24px; min-height: 48px; padding: 0 28px; border: 0; border-radius: 12px;
background: #38bdf8; color: #0f172a; font-size: 17px; font-weight: 600; }
</style>
</head>
<body>
<main>
<h1>You are offline</h1>
<p>We will take you back to where you were as soon as the connection returns.</p>
<button type="button" onclick="window.location.href='reconnect://'">Try again</button>
</main>
</body>
</html>每个项目十分钟,外加一次构建。
iOS:用您应用的语气撰写 offlinetitle、offlinemsg、screen1、screen2 和 offlinebuttontext,并把 offlinescreenautoretry 设为 true。Android:设置 OFFLINE_SCREEN_BACKGROUND_COLOR,并在 strings.xml 的应用文字字符串中检查离线相关的措辞。
把上面的页面复制到本地 HTML 文件夹,并开启 offlinelocalhtmlswitch(iOS)或 FALLBACK_USE_LOCAL_HTML_FOLDER_IF_OFFLINE(Android),同时让 uselocalhtmlfolder / USE_LOCAL_HTML_FOLDER 保持为 false,这样远程 URL 仍是主要来源。
选择原生加载标志或进度条(iOS 上为 useLoadingSign、useLoadingProgressBar、loadingIndicatorColor;Android 上为 ACTIVATE_PROGRESS_BAR)。对于耗时较长的服务器操作,您的应用可以用 spinneron:// 显示原生加载指示器,再用 spinneroff:// 隐藏它。
审阅 diff,亲自运行一次 Playwright 测试,然后部署。Web 部分会立即推送到已安装的应用中。
离线文字和备用页面是原生构建的一部分。如果您希望由我们的团队来设置并构建两个应用,Full Service 可以全部包办。
autoRefreshEnabled / AUTO_REFRESH_ENABLED,并测试它不会丢掉填了一半的表单)。不必。审核人员期望的是清晰的提示和可用的恢复方式,而不是离线功能。要完全离线使用,需要一个内置的本地版本,或者一个专为离线数据设计的应用。
因为那一刻并没有在加载任何页面。一个正在运行的单页应用只会让它自己的 fetch 请求失败,所以横幅和错误状态必须由您的代码来显示。
它会重新加载应用切换到离线页面之前最后一个成功加载的 URL。如果网络仍然断开,备用页面会再次出现。
它可以用 Playwright 在浏览器中模拟离线(context.setOffline)并模拟服务器错误,这覆盖了您的 Web 代码。原生离线页面和真实的网络行为,仍然需要在手机上测试。
在状态栏和安全区域下方,覆盖在内容之上,但绝不能挡住主要按钮。顶部一条细长的横条,是用户熟悉的惯例。
结果相同,只是您需要投入的工作量不同。三种方式都附带审核通过保证和 7×24 小时人工支持。
$109 每个平台,一次性付费
WebViewGold 许可证:一个现成的 Xcode 或 Android Studio 项目。粘贴您的 URL,设置名称和图标,开启所需功能,构建并提交。永久免费更新。
在主页查看方案$349 每个许可证,一次性附加购买
无需 Mac、Xcode 或 Android Studio:在浏览器中完成配置,我们在云端构建两个应用,并将其交付到 App Store Connect 和 Play Console。
Cloud Builder 如何运作 试用免费演示$698 每个平台 $798
把您的 URL 和图标发给我们。我们的团队在 72 小时内完成配置、构建并交付您的应用,白标,附源码项目。两个平台共 $1,158。
Full Service 详情 服务提供方:冬季特惠价格有效期至 2026年12月31日。
App Store & Google Play 审核通过保证 – 否则 14 天内退款
想直接交给我们?Full Service:我们在 72 小时内完成配置、构建并交付,每个平台 $698 起。有疑问?通过 7×24 小时在线聊天联系我们。
退款依据我们 FAQ 中列出的条件:应用因非内容原因被 Apple 和/或 Google 拒审,并在购买后 14 天内提出申请。