alipay-h5-program-generator 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/.claude-skill.json +10 -0
  2. package/LICENSE +15 -0
  3. package/README.md +94 -2
  4. package/SKILL.md +54 -0
  5. package/agents/openai.yaml +3 -0
  6. package/assets/frontend-template/.eslintrc.cjs +9 -0
  7. package/assets/frontend-template/.prettierrc +1 -0
  8. package/assets/frontend-template/components/AlipayGuard.tsx +27 -0
  9. package/assets/frontend-template/components/Detail.client.tsx +22 -0
  10. package/assets/frontend-template/components/example-detail.page.tsx +22 -0
  11. package/assets/frontend-template/container.ts +84 -0
  12. package/assets/frontend-template/test/AlipayGuard.test.tsx +16 -0
  13. package/assets/frontend-template/vitest.config.ts +7 -0
  14. package/package.json +54 -4
  15. package/references/architecture.md +23 -0
  16. package/references/coding-standards.md +51 -0
  17. package/references/container.md +32 -0
  18. package/references/cross-page-comm.md +17 -0
  19. package/references/env-degradation.md +32 -0
  20. package/references/pitfalls.md +34 -0
  21. package/references/release-perf.md +35 -0
  22. package/references/release.md +20 -0
  23. package/references/requirements.md +28 -0
  24. package/references/responsive-cross-end.md +44 -0
  25. package/references/ssr-seo-sharing.md +29 -0
  26. package/references/tech-selection.md +27 -0
  27. package/references/testing-acceptance.md +45 -0
  28. package/references/ux-interaction.md +42 -0
  29. package/scripts/check-build.mjs +61 -0
  30. package/scripts/check-env.mjs +34 -0
  31. package/scripts/check-project.mjs +72 -0
  32. package/scripts/check-seo.mjs +73 -0
  33. package/scripts/cli.mjs +106 -0
  34. package/scripts/install-skill.mjs +28 -0
  35. package/scripts/lib/skill-registrar.mjs +63 -0
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "alipay-h5-program-generator",
3
+ "package": "alipay-h5-program-generator",
4
+ "files": {
5
+ "SKILL.md": "SKILL.md",
6
+ "references": "references/",
7
+ "assets": "assets/",
8
+ "scripts": "scripts/"
9
+ }
10
+ }
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 唐小新 <1302947749@qq.com>
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any purpose
6
+ with or without fee is hereby granted, provided that the above copyright notice
7
+ and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
11
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,95 @@
1
- # Temporary Holding Version
1
+ # alipay-h5-program-generator
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > Codex / Claude Code Skill — 装进技能目录后,agent 用自然语言提需求即按工程化流程交付「部署在公网、可被搜索引擎收录、可分享、内嵌支付宝 WebView」的第三方纯 H5 网页应用。React + TypeScript + AntD Mobile + Next.js(SSR/SSG);能力靠支付宝开放平台服务端接口(手机网站支付 / OAuth)+ alipays scheme 唤起。
4
+
5
+ [![npm version](https://img.shields.io/npm/v/alipay-h5-program-generator.svg)](https://www.npmjs.com/package/alipay-h5-program-generator)
6
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
7
+
8
+ ## 适用 / 不适用
9
+ - **用**:在 [open.alipay.com](https://open.alipay.com) 注册「网页&移动应用」、把 H5 部署到公网 CDN 的第三/外部开发者;需要 SEO 收录 + 分享 + 支付/授权;React + TS + AntD Mobile + Next。
10
+ - **不用**:依赖小程序容器 JSAPI / 离线包 / 私有 RPC / 企业监控等私有基建的项目;**支付宝小程序**(需 `my.*` 原生能力、原生多页/分享/离线包/原生标题栏);无「在支付宝内 + 收录/分享」诉求的普通移动网页。
11
+
12
+ 把「公网可收录 H5 + 支付宝 WebView 内嵌」的工程范式做成可外网版:只迁移架构与工程范式,不内嵌任何私有代码 / 内部文档 / 私有 API,引用仅限公开源(opendocs.alipay.com / MDN / web.dev / antd-mobile 官方)。
13
+
14
+ ## 核心范式
15
+ 1. 每屏一条公网 URL + SSR/SSG(可收录/可分享内容进 HTML;登录态 hydrate 后拉)
16
+ 2. 多页导航退化为标准多页(无多 webview → `location` + 历史后退)
17
+ 3. 能力靠「开放平台服务端接口 + scheme」:支付 = 服务端 `alipay.trade.wap.pay` 跳转、授权 = `publicAppAuthorize` 跳转、浏览器→App = alipays scheme 唤起;默认无需前端 JSSDK
18
+ 4. 分享双路径:外部 = OG meta(进 SSR HTML);App 内 = URL 分享 + scheme 唤起
19
+ 5. 环境降级:UA 判定是否在支付宝,noindex 功能页在浏览器给「请在支付宝中打开」
20
+ 6. 跨页/跨次状态:服务端持久化为主,前端 localStorage + storage 事件 + visibilitychange 补偿
21
+ 7. 发布/性能:SSR/SSG + CDN + HTTP 缓存 + 图片优化;无离线包
22
+
23
+ 另含**响应式与跨端适配、运行时性能、交互体验**三套移动 H5 底线规则。
24
+
25
+ ## 能力边界(公网核实,零编造)
26
+ - ✅ **可用**:手机网站支付 `alipay.trade.wap.pay`、OAuth 网页授权(`publicAppAuthorize`,可选 `ap.getAuthCode`)、alipays scheme 唤起进入支付宝。
27
+ - ❌ **不可用**(第三方纯 H5):`pushWindow`/`popWindow`(多 webview)、`setTitle`(原生栏)、`setShareContent`(自定义分享)、`my.*`、私有 RPC 网关、离线包、客户端开关——opendocs 搜均 0 命中。
28
+ - ⚠️ **谨慎**:`AlipayJSBridge` 对象可能存在但仅用于**环境判定**,绝不 `.call(...)` 其容器 JSAPI(存在 ≠ 可用);`ap.tradePay` 公网无第三方 H5 版,支付一律服务端跳转。
29
+
30
+ ## Installation
31
+ ```bash
32
+ npm install alipay-h5-program-generator
33
+ ```
34
+ 注册到 `~/.agents/skills/alipay-h5-program-generator`;换根目录设 `ALIPAY_SKILL_ROOT`;`--ignore-scripts` / 不联网 CD 环境补跑 `npx alipay-h5-program-generator install`。
35
+
36
+ ## Update
37
+ ```bash
38
+ npm install alipay-h5-program-generator@latest
39
+ ```
40
+ 覆盖更新、无残留。
41
+
42
+ ## Uninstall
43
+ ```bash
44
+ npx alipay-h5-program-generator uninstall
45
+ ```
46
+ 一行清技能目录 + 自动卸载 npm 包。`npm uninstall` 单条不清技能目录(npm 10 卸载依赖不触发依赖自身的 preuninstall),请用上面这条。
47
+
48
+ ## Usage
49
+ 无需手动调用——在 Codex / Claude Code 里用自然语言提需求即可:
50
+ ```text
51
+ - 帮我搭一个能被 Google 收录、可分享、内嵌支付宝的第三方纯 H5 详情页(React + AntD Mobile + Next SSR)
52
+ - 给这个 H5 接入支付宝手机网站支付(服务端建 wap.pay 跳转)和网页授权
53
+ - 浏览器里把用户用 alipays scheme 唤起进支付宝
54
+ - 排查支付宝内 H5 能不能用 pushWindow / setShareContent(结论:不能用)
55
+ ```
56
+
57
+ ## Scripts
58
+ | Script | Purpose | Exit Code |
59
+ |---|---|---|
60
+ | `scripts/check-env.mjs` | 打印能力矩阵 + 浏览器探针(viewport / UA / AlipayJSBridge / ServiceWorker / localStorage) | `0` done |
61
+ | `scripts/check-project.mjs` | 项目护栏确定性扫描:禁止容器/小程序/私有 API 调用遗址 | `0` pass / `1` 禁止项 |
62
+ | `scripts/check-seo.mjs` | 构建前 SEO 收录一致性:canonical↔og↔URL 一致、sitemap/robots 在场、无 cloaking 嫌疑 | `0` pass / `1` 须修 / `2` 非 App Router |
63
+ | `scripts/check-build.mjs` | 发布前门:NEXT_PUBLIC_ 不暴露 secret、(构建后)产物无私钥串、app/api 不误用 public 变量 | `0` pass / `1` 须修 |
64
+
65
+ ## Directory
66
+ ```
67
+ SKILL.md # Skill 指令正文
68
+ .claude-skill.json # 包 → 技能 manifest
69
+ references/
70
+ requirements.md · architecture.md · tech-selection.md · coding-standards.md · container.md
71
+ ssr-seo-sharing.md · env-degradation.md · cross-page-comm.md · responsive-cross-end.md · ux-interaction.md
72
+ testing-acceptance.md · release.md · release-perf.md · pitfalls.md
73
+ assets/frontend-template/ # container.ts / AlipayGuard.tsx / Detail.client.tsx / example-detail.page.tsx + vitest.config/.eslintrc/.prettierrc + test/AlipayGuard.test.tsx
74
+ scripts/ # check-env / check-project / check-seo / check-build / install-skill / cli / lib/skill-registrar.mjs
75
+ ```
76
+
77
+ ## Requirements
78
+ - [Node.js](https://nodejs.org) ≥ 18
79
+ - 技术栈:React + TypeScript + AntD Mobile v5 + Next.js(App Router,SSR/SSG)
80
+ - 一台公网 CDN;支付宝开放平台「网页&移动应用」应用;支付/授权能力在 [开放平台](https://open.alipay.com) 配置后获取
81
+
82
+ ## Workflow
83
+ G1 需求与细化(requirements) → G2 设计与选型(architecture/tech-selection/container/ssr-seo) → G3 编码(coding-standards + 模板) → G4 测试验收(testing-acceptance + check-env/check-project/check-seo/check-build + 真机核对) → G5 发布(release + 发布前门)。每门满足出口准则才进下一门、发现上层错回退。详见 `SKILL.md`。
84
+
85
+ ## Contributing
86
+ 修订能力边界时同步:`scripts/check-env.mjs` 能力矩阵 + `references/container.md` + `references/pitfalls.md` + `scripts/check-project.mjs` 禁止项规则;改完跑 `node scripts/check-env.mjs` 与 `node scripts/check-project.mjs .`。
87
+
88
+ ## Trademark
89
+ `alipay` / 支付宝 等相关商标归其各自所有权人所有;本技能与支付宝及其关联方无任何隶属或授权关系。
90
+
91
+ ## License
92
+ [ISC](https://opensource.org/licenses/ISC)
93
+
94
+ ## Author
95
+ [**hey_tangxiaoxin**](https://github.com/hey_tangxiaoxin)
package/SKILL.md ADDED
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: alipay-h5-program-generator
3
+ description: 在支付宝开放平台「网页&移动应用」下开发第三方纯 H5 网页应用——部署到公网、可被搜索引擎收录、可分享、内嵌在支付宝 WebView。技术栈 React+TS+AntD Mobile+Next(SSR/SSG);能力以服务端接口(手机网站支付、OAuth)+alipays scheme 唤起为主;以 G1–G5 全链路工程门禁(需求→细化→设计→选型→编码→测试→发布)覆盖,自带 check-env/check-project/check-seo/check-build 确定性脚本。不要用于依赖原生容器/离线包/私有 RPC 等私有基建的项目,也不要用于支付宝小程序。
4
+ ---
5
+
6
+ # 支付宝第三方纯 H5 网页应用
7
+
8
+ 面向在 open.alipay.com 注册「网页&移动应用」、把 H5 部署到公网 CDN 的第三方开发者。页面运行在支付宝 WebView 内,但**不是小程序、不在小程序运行时内**——因此没有 `my.*`、没有原生容器 JSAPI、没有离线包。本 skill 把「公网可收录 H5 + 支付宝 WebView 内嵌」的工程范式做成可外网版:只迁移架构范式,不内嵌任何私有代码、内部文档或私有 API,引用仅限公开源(opendocs.alipay.com)。
9
+
10
+ ## 何时用 / 何时不用
11
+
12
+ - 用:第三方/外网部署的纯 H5;需要 SEO 收录 + 分享 + 支付/授权;React+TS+AntD Mobile。
13
+ - 不用:
14
+ - 依赖小程序容器 JSAPI / 离线包 / 私有 RPC / 企业监控等私有基建的项目——私有栈,非本范畴。
15
+ - **支付宝小程序**(需 `my.*` 原生能力、原生多页/分享/离线包/原生标题栏)——改用小程序框架。
16
+ - 无"在支付宝内 + 收录/分享"诉求的普通移动网页——标准 H5 即可。
17
+
18
+ ## 核心范式
19
+
20
+ 1. **每屏一条公网 URL + SSR/SSG**:可收录/可分享内容必有独立 URL 并在 HTML 含可抓取内容;登录态内容客户端再拉。见 references/architecture.md、references/ssr-seo-sharing.md。
21
+ 2. **多页导航退化为标准多页**:第三方 H5 无多 WebView 能力;跨屏跳转用 `location`+历史后退,不在页面间用 SPA 客户端路由做"全站多屏"。只有多步流程压在单页内用客户端状态。
22
+ 3. **能力靠"开放平台服务端接口 + scheme"**:支付走手机网站支付(服务端生成 wap.pay URL 跳转)、授权走 OAuth(publicAppAuthorize 跳转)、浏览器用户用 alipays scheme 唤起进支付宝。默认**无需前端 JSSDK**。授权可选公开 `ap.getAuthCode`(半屏,/mini/02xtl8、/open/01zsdu);支付一律服务端跳转,**勿臆造 `ap.tradePay`**(公网无 H5 版)。见 references/container.md。
23
+ 4. **分享双路径**:外部=OG meta(进 SSR HTML);App 内=URL 分享 + scheme 唤起(无自定义分享 SDK)。见 references/ssr-seo-sharing.md。
24
+ 5. **环境降级**:UA 判定是否在支付宝,浏览器内对 noindex 功能页给"请在支付宝中打开"。见 references/env-degradation.md。
25
+ 6. **跨页/跨次状态**:服务端持久化为主,前端补偿用 localStorage+storage 事件+visibilitychange(无多 webview 实时通信)。见 references/cross-page-comm.md。
26
+ 7. **发布/性能**:SSR/SSG+CDN+HTTP 缓存+图片优化;无离线包——秒开靠 SSR 与 CDN,灰度靠 CDN 权重+feature flag,监控 Sentry+RUM。见 references/release-perf.md。
27
+
28
+ ## 关键 guardrail
29
+
30
+ 下列能力**纯 H5 拿不到,不要在本 skill 路径硬做**——它们属于支付宝小程序:原生多 WebView / 原生标题栏 / App 内自定义分享 / 离线包秒开 / 客户端开关灰度。若同时要"被收录 + App 内富功能":公开内容用本 H5(SSR 收录/分享/支付),站内富功能入口另做小程序(或在 H5 内给"用小程序打开"引导)。
31
+
32
+ ## 易踩坑(AI 常见误判,勿重复;详见 references/pitfalls.md)
33
+ 1. **不要把 `AlipayJSBridge.pushWindow/popWindow/setTitle/setShareContent` 当"第三方可选/待验证"留着**——第三方纯 H5 拿不到(属小程序/私有栈),直接不写。(opendocs 搜均 0 命中)
34
+ 2. **`my.*` 是小程序 API,不是 H5**——第三方纯 H5 没有;用户给的 `/mini/*` 链接别被带偏。H5 走服务端接口 + 可选 `ap.*`。
35
+ 3. **`AlipayJSBridge` 存在 ≠ 其 JSAPI 可调**——只用于环境判定(`isAlipayClient`),绝不 `.call(...)` 其容器 JSAPI。
36
+ 4. **服务端按 UA 给爬虫/用户出不同内容 = cloaking**——公开 HTML 必须一致。
37
+ 5. **别猜 SDK/包名**——`@alipay/jssdk` 实测 npm 404;前端默认无需 SDK(服务端跳转即可),`ap.*` 来源以 open/08xgr7 为准,服务端用公开 `alipay-sdk`。
38
+
39
+ ## 响应式 / 性能 / 交互体验(移动 H5 底线)
40
+ - **响应式与跨端适配**:viewport + 安全区 / rem 适配(antd-mobile 官方方案)/ 高 DPI / 触摸热区 / 输入与键盘 / 横竖屏 / 桌面降级。见 references/responsive-cross-end.md。
41
+ - **性能**:发布侧(SSR/SSG + CDN + 图片优化 + Service Worker)见 references/release-perf.md;编码侧运行时性能(React 渲染 / 虚拟列表 / CLS / INP / bundle 预算)亦在其中。
42
+ - **交互体验**:骨架 / 空错态 / 重试、下拉刷新 + 上拉加载、点击反馈与防抖、表单与键盘、返回与转场、弱网降级。见 references/ux-interaction.md。
43
+
44
+ ## 工作流(G1–G5 工程门禁;每门满足出口准则才进下一门,发现上层决策错回退)
45
+
46
+ **G1 需求确认与细化** — 入口:拿到 H5 需求。出口:填好需求细化表(A/B/C 页拆分 + 公开/登录态字段 + 收录词 + 分享/支付/授权取舍 + 接口清单)。回退:若实际要原生小程序能力(多 webview/离线包/自定义分享)→ 改做小程序,不进。细则见 references/requirements.md。
47
+
48
+ **G2 设计与选型** — 入口:G1 细化表。出口:URL 结构(A/B/C) + 每页渲染决策(SSG/SSG+ISR/SSR/CSR) + 容器层公开能力清单 + 接口契约/mock。回退:能力结论与公网不符(勾到不可用 API)→ 回 G1 重核 opendocs,勿臆造。细则见 references/architecture.md、references/tech-selection.md、references/container.md、references/ssr-seo-sharing.md。
49
+
50
+ **G3 编码** — 入口:G2 设计。出口:按 coding-standards.md 落目录/类型/server·client 边界/env 边界/a11y;模板见 assets/frontend-template。回退:违反 env 边界(私钥入前端)或引入禁用 API 时回 G2 调整。细则见 references/coding-standards.md、references/responsive-cross-end.md、references/ux-interaction.md。
51
+
52
+ **G4 测试验收** — 入口:可运行。出口:护栏脚本 rc=0(check-env/check-project/check-seo/check-build)且 testing-acceptance.md §1–9 全过(SEO 收录/分享/支付 e2e/降级/移动/性能预算/a11y)。回退:挂哪回哪门(能力→G2、编法→G3)。细则见 references/testing-acceptance.md。
53
+
54
+ **G5 发布** — 入口:G4 全绿。出口:发布前门(release.md)过(产物/secret/灰度计划/监控/回滚),按 CDN 灰度放量,发布后核对收录/分享/支付真机。回退:超阈值即回滚切版本。细则见 references/release.md、references/release-perf.md。
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "Alipay H5 程序生成器"
3
+ short_description: "支付宝第三方纯H5网页(开放平台/纯H5,非小程序/不依赖私有基建),SEO+分享+支付,G1–G5全链路"
@@ -0,0 +1,9 @@
1
+ // 依赖:eslint + eslint-config-next + @typescript-eslint + eslint-plugin-jsx-a11y (devDependencies)
2
+ module.exports = {
3
+ root: true,
4
+ extends: ['next/core-web-vitals', 'plugin:@typescript-eslint/recommended', 'plugin:jsx-a11y/recommended'],
5
+ rules: {
6
+ '@typescript-eslint/no-explicit-any': 'warn',
7
+ 'react/jsx-key': 'error',
8
+ },
9
+ };
@@ -0,0 +1 @@
1
+ { "printWidth": 120, "semi": true, "singleQuote": false, "trailingComma": "all" }
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+ import { useEffect, useState } from 'react';
3
+ import { Button, Result } from 'antd-mobile';
4
+ import { isAlipayClient, openPageInAlipay } from '../container';
5
+
6
+ /** 是否在支付宝内(SSR 期返回 null,挂载后判定,避免首屏闪烁与 hydration mismatch)。 */
7
+ export function useInAlipay(): boolean | null {
8
+ const [v, setV] = useState<boolean | null>(null);
9
+ useEffect(() => setV(isAlipayClient()), []);
10
+ return v;
11
+ }
12
+
13
+ /**
14
+ * noindex 功能页(B 类 /publish、/order、/result/:tradeNo)的浏览器降级屏:
15
+ * 支付宝内渲染子内容;浏览器内显示「请在支付宝中打开」+ scheme/landing 唤起。
16
+ */
17
+ export function AlipayGuard({ children }: { children: React.ReactNode }) {
18
+ const inApp = useInAlipay();
19
+ if (inApp === null) return null;
20
+ if (inApp) return <>{children}</>;
21
+ return (
22
+ <div style={{ padding: '24px 0', textAlign: 'center' }}>
23
+ <Result status="info" title="请在支付宝中打开" description="本功能需在支付宝客户端内使用" />
24
+ <Button color="primary" onClick={() => openPageInAlipay(location.href)}>在支付宝中打开</Button>
25
+ </div>
26
+ );
27
+ }
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+ // hydrate 后追加登录态/支付入口;用标准 Web + 服务端支付/授权(见 container.ts)
3
+ import { useEffect } from 'react';
4
+ import { isAlipayClient, openPageInAlipay } from '../container';
5
+
6
+ export default function DetailClient({ data, id }: { data: { title: string; summary: string; cover: string }; id: string }) {
7
+ useEffect(() => {
8
+ document.title = `${data.title} - 站点名`; // 没有 setTitle,统一 HTML <title>
9
+ // 支付/授权按钮:调后端拿 wap.pay / 授权 URL 后 location.assign(container.startPay / redirectToAuth)
10
+ }, [id, data]);
11
+
12
+ return (
13
+ <div>
14
+ <h1>{data.title}</h1>
15
+ <p>{data.summary}</p>
16
+ {/* 登录态互动(无登录自动降级);支付走服务端 wap.pay 跳转 */}
17
+ {!isAlipayClient() && (
18
+ <button onClick={() => openPageInAlipay(location.href)}>在支付宝中打开体验更佳</button>
19
+ )}
20
+ </div>
21
+ );
22
+ }
@@ -0,0 +1,22 @@
1
+ // Next App Router 服务端组件 — A 类公开页,SSR head + SSR 公开内容(无登录)
2
+ import type { Metadata } from 'next';
3
+ import { fetchDetail } from '@/lib/api'; // 你的接口层(公开内容)
4
+ import DetailClient from './Detail.client';
5
+
6
+ export async function generateMetadata({ params }: { params: { id: string } }): Promise<Metadata> {
7
+ const d = await fetchDetail(params.id);
8
+ const url = `https://m.example.com/detail/${params.id}`;
9
+ return {
10
+ title: `${d.title} - 站点名`,
11
+ description: d.summary,
12
+ alternates: { canonical: url },
13
+ openGraph: { type: 'article', url, title: d.title, description: d.summary, images: [d.cover] },
14
+ twitter: { card: 'summary_large_image' },
15
+ robots: { index: true },
16
+ };
17
+ }
18
+
19
+ export default async function Page({ params }: { params: { id: string } }) {
20
+ const d = await fetchDetail(params.id); // SSR 输出可抓内容到 HTML
21
+ return <DetailClient data={d} id={params.id} />;
22
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * 支付宝第三方纯 H5 — 容器抽象层(模板)
3
+ * 约定见 references/container.md:默认 Level 1(标准 Web) + Level 2(开放平台服务端接口/scheme)。
4
+ * 不要在此依赖 AlipayJSBridge.pushWindow/setTitle/setShareContent(第三方不可用)。
5
+ */
6
+
7
+ // ---------- env ----------
8
+ export function isAlipayClient(): boolean {
9
+ if (typeof window !== 'undefined' && typeof (window as any).AlipayJSBridge !== 'undefined') return true;
10
+ return /AlipayClient|AliApp|Alipay/i.test(typeof navigator !== 'undefined' ? navigator.userAgent : '');
11
+ }
12
+
13
+ /**
14
+ * SSR 安全判定(传 request 的 UA):只在需要"是否在支付宝内"做极轻分流时用。
15
+ * ⚠ 反 cloaking:绝不要据此给爬虫与用户出不同内容/head;爬虫 UA 不在支付宝内,
16
+ * 按 UA 减内容会被判作弊。可据此"不预取登录态数据",但公开 HTML 必须与真实用户一致。
17
+ */
18
+ export function isAlipayClientSSR(ua: string | undefined): boolean {
19
+ if (!ua) return false;
20
+ return /AlipayClient|AliApp|Alipay/i.test(ua);
21
+ }
22
+
23
+
24
+ // ---------- navigation (第三方无多 webview -> 标准多页) ----------
25
+ /** 进入下一页:无论浏览器还是支付宝都是整页加载本 URL(标准多页)。 */
26
+ export function openPage(url: string, params?: Record<string, unknown>): void {
27
+ location.assign(appendQuery(url, params));
28
+ }
29
+ /** 返回上一页:标准浏览器历史后退(无原生 popWindow)。 */
30
+ export function popWindow(): void {
31
+ history.back();
32
+ }
33
+ /** 浏览器环境引导"在支付宝中打开"(landing scheme)。非 alipay.com 域名需配白名单。 */
34
+ export function openPageInAlipay(targetUrl: string): void {
35
+ const scheme = `alipays://platformapi/startapp?appId=20000067&url=${encodeURIComponent(targetUrl)}`;
36
+ const landing = `https://render.alipay.com/p/s/i/?scheme=${encodeURIComponent(scheme)}`;
37
+ location.assign(landing);
38
+ }
39
+
40
+ // ---------- pay / auth (服务端建 URL,前端只跳转) ----------
41
+ /** 手机网站支付:服务端已生成 wap.pay 支付 URL,前端跳转即可。无需前端 SDK。 */
42
+ export function startPay(payServerUrl: string): void {
43
+ location.assign(payServerUrl);
44
+ }
45
+ /** OAuth 授权:服务端已生成 publicAppAuthorize/授权 URL,前端跳转;回调页用 code 换 uid。 */
46
+ export function redirectToAuth(authServerUrl: string): void {
47
+ location.assign(authServerUrl);
48
+ }
49
+ // 可选:仅 ap.getAuthCode(H5 半屏授权,open/01zsdu / mini/02xtl8)作"不离开页面"增强;来源/包名以
50
+ // open/08xgr7 为准,默认不引入;引入后在此处扩展,仍保持上层 API 不变。
51
+ // 支付一律服务端 wap.pay 跳转(ap.tradePay 公网无 H5 版,勿臆造)。
52
+
53
+ // ---------- cross-load state: localStorage + storage 事件 + visibilitychange ----------
54
+ const BUS_KEY = '__xc_bus__';
55
+ export function emit(event: string, payload: unknown, opts?: { from?: string; to?: string }): void {
56
+ try {
57
+ localStorage.setItem(BUS_KEY, JSON.stringify({ event, payload, ts: Date.now(), from: opts?.from, to: opts?.to }));
58
+ } catch { /* 私有模式/空间不足:以服务端持久化为主 */ }
59
+ }
60
+ export function on(event: string, handler: (payload: unknown, from?: string) => void, opts?: { currentPage?: string }): () => void {
61
+ const fn = (e: StorageEvent) => {
62
+ if (e.key !== BUS_KEY || !e.newValue) return;
63
+ try {
64
+ const m = JSON.parse(e.newValue);
65
+ if (m.event !== event) return;
66
+ if (m.to && opts?.currentPage && m.to !== opts.currentPage) return;
67
+ handler(m.payload, m.from);
68
+ } catch {}
69
+ };
70
+ window.addEventListener('storage', fn);
71
+ return () => window.removeEventListener('storage', fn);
72
+ }
73
+ export function onResume(handler: () => void): () => void {
74
+ const fn = () => { if (document.visibilityState === 'visible') handler(); };
75
+ document.addEventListener('visibilitychange', fn);
76
+ return () => document.removeEventListener('visibilitychange', fn);
77
+ }
78
+
79
+ // ---------- helpers ----------
80
+ function appendQuery(url: string, p?: Record<string, unknown>): string {
81
+ if (!p || !Object.keys(p).length) return url;
82
+ const qs = new URLSearchParams(p as any).toString();
83
+ return url.includes('?') ? `${url}&${qs}` : `${url}?${qs}`;
84
+ }
@@ -0,0 +1,16 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { AlipayGuard } from '../../src/components/AlipayGuard'; // 路径按项目实际调整
4
+
5
+ // isAlipayClient 默认按 navigator.userAgent 判;测试环境 UA 非支付宝 → 浏览器分支
6
+ describe('AlipayGuard', () => {
7
+ it('浏览器内显示「请在支付宝中打开」、子内容不可见', async () => {
8
+ render(
9
+ <AlipayGuard>
10
+ <div data-testid="child">该内容对浏览器不可见</div>
11
+ </AlipayGuard>
12
+ );
13
+ expect(await screen.findByText(/请在支付宝中打开/)).toBeTruthy();
14
+ expect(screen.queryByTestId('child')).toBeNull();
15
+ });
16
+ });
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import path from 'node:path';
3
+ // 依赖:vitest + jsdom + @testing-library/react (devDependencies)
4
+ export default defineConfig({
5
+ test: { environment: 'jsdom', globals: true, setupFiles: [] },
6
+ resolve: { alias: { '@': path.resolve(__dirname, 'src') } },
7
+ });
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "alipay-h5-program-generator",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Codex / Claude Code Skill — 支付宝第三方纯 H5 网页应用工程级开发技能:部署公网可被搜索引擎收录、可分享、内嵌支付宝 WebView;React+TS+AntD Mobile+Next(SSR/SSG),能力靠开放平台服务端接口(手机网站支付/OAuth)+alipays scheme 唤起;自带 check-env / check-project 确定性脚本。",
5
+ "main": "",
6
+ "author": "唐小新 <1302947749@qq.com> (https://github.com/hey_tangxiaoxin)",
7
+ "license": "ISC",
8
+ "keywords": [
9
+ "codex",
10
+ "codex-skill",
11
+ "claude-code",
12
+ "skill",
13
+ "ai-assistant",
14
+ "alipay",
15
+ "h5",
16
+ "react",
17
+ "typescript",
18
+ "antd-mobile",
19
+ "nextjs",
20
+ "ssr",
21
+ "seo",
22
+ "支付宝H5",
23
+ "网页应用",
24
+ "open-platform"
25
+ ],
26
+ "dependencies": {},
27
+ "files": [
28
+ "SKILL.md",
29
+ "references/",
30
+ "assets/",
31
+ "scripts/",
32
+ "agents/",
33
+ ".claude-skill.json"
34
+ ],
35
+ "bin": {
36
+ "alipay-h5-program-generator": "scripts/cli.mjs"
37
+ },
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/antdigital-ai/alipay-h5-program-generator.git"
44
+ },
45
+ "publishConfig": {
46
+ "registry": "https://registry.npmjs.org/"
47
+ },
48
+ "homepage": "https://github.com/antdigital-ai/alipay-h5-program-generator",
49
+ "bugs": {
50
+ "url": "https://github.com/antdigital-ai/alipay-h5-program-generator/issues"
51
+ },
52
+ "scripts": {
53
+ "postinstall": "node scripts/install-skill.mjs",
54
+ "preuninstall": "node scripts/install-skill.mjs"
55
+ }
56
+ }
@@ -0,0 +1,23 @@
1
+ # 架构:纯 H5 的形态与 URL 结构
2
+
3
+ ## 双身份与降级
4
+ 同一份 H5 有两副面孔,差别只在客户端 hydrate 之后;**服务端 HTML 相同**(反 cloaking):
5
+ - 公网网页:被爬虫抓、被外部分享 → 需 SSR/SSG 吐真实 HTML。
6
+ - 支付宝内:被 iOS WKWebView / Android WebView 加载同一个公网 URL → hydrate 后启用支付/授权。
7
+
8
+ 第三方 H5 **没有多 webview**:跨屏=标准多页(`location`+历史后退)。失去"原生多页栈/保留前页",换 SEO 与跨端通用性。这是本范式与原生小程序容器方案最大的实际差异。
9
+
10
+ ## URL 结构(每屏一条公网 URL)
11
+ - **A 公开可索引**(SSR/SSG、进 sitemap):`/`、`/list/:cat`、`/detail/:id`、`/activity/:slug`
12
+ - **B App 内功能**(noindex、不进 sitemap):`/publish`、`/order`、`/result/:tradeNo`(浏览器打开给"请在支付宝中打开")
13
+ - **C 引导**:通过 landing scheme 把浏览器用户唤起进支付宝(见 env-degradation.md)
14
+
15
+ ## 渲染策略
16
+ - 公开页 SSR/SSG:可抓内容**无登录即可拼出并进 HTML**;登录态内容 hydrate 后拉。内容多静态→SSG;按请求动态→SSR。
17
+ - 功能页 CSR + noindex + 浏览器降级屏。
18
+
19
+ ## 反 cloaking(详见 references/pitfalls.md §4)
20
+ 服务端**不按 UA 区分爬虫**;爬虫与用户拿同一份公开 HTML;"缺失"的只是"本就需要登录、hydrate 才追加"的部分。
21
+
22
+ ## 多步流程用单页状态
23
+ 需要跨步骤共享状态的多步流程(发布/编辑):放在单 WebView(单 URL) 内用组件状态/客户端路由,整页 noindex。
@@ -0,0 +1,51 @@
1
+ # 编码规范(G3)
2
+
3
+ 纯 H5 + App Router 的可维护底线。示例见 assets/frontend-template/。
4
+
5
+ ## 目录结构(App Router,按 A/B/C 分)
6
+ ```
7
+ src/
8
+ lib/ # 接口与纯逻辑:api.ts / pay-server.ts / auth-server.ts / types.ts / constants.ts
9
+ container/ # 容器抽象层(见 container.md)
10
+ hooks/ # useInAlipay 等 SSR 安全 hook
11
+ components/ # 通用 UI + 业务组件;server/client 按需
12
+ app/
13
+ layout.tsx robots.ts sitemap.ts
14
+ <a-page>/page.tsx # A 类 server 组件:generateMetadata + SSR 公开内容 + JSON-LD
15
+ <b-page>/page.tsx # B 类 client + AlipayGuard + noindex
16
+ api/<pay|auth>/route.ts # 服务端:建 wap.pay / 授权 URL
17
+ ```
18
+ - A 类用 server 组件直出公开内容进 HTML;登录态/支付落到同名 `.client.tsx`(挂 `'use client'`)。
19
+ - B 类整页 `noindex` + AlipayGuard;不要把可索引内容放进 B 页。
20
+
21
+ ## 命名
22
+ - 文件:路由 `kebab-case`、复用组件 `PascalCase.tsx`、client 组件后缀 `.client.tsx`、server 页 `.page.tsx`。
23
+ - 变量/函数 `camelCase`;类型/接口 `PascalCase`;常量 `UPPER_SNAKE`。
24
+
25
+ ## TypeScript
26
+ - `tsconfig`:`strict: true`、`noUncheckedIndexedAccess: on`、`paths` 别名 `@/*`。
27
+ - DTO 在 `lib/types.ts`;服务端返回与前端对齐;禁用隐式 `any`。
28
+
29
+ ## server vs client 边界
30
+ - 能在 server 做的别搬 client(接口调用、内容拼装进 server 组件)。
31
+ - 只有需要 `window`/交互/登录态/支付才上 `'use client'`;模块顶层别用 `window`(防 hydration mismatch)。
32
+
33
+ ## 接口层 `lib/api`
34
+ - 统一入口;列表/详情/支付/授权各一函数;返回同一类型;mock 与真实后端同签名(SSR/CSR 同源)。
35
+ - 服务端建 wap.pay/授权 URL 放 `pay-server.ts`/`auth-server.ts`;私钥签名只在服务端(见下)。
36
+
37
+ ## 环境变量边界(安全)
38
+ - 服务端密钥(AppId 私钥等)只读服务端 env,**绝不**入前端 bundle / source map(check-build.mjs 兜底校验)。
39
+ - 只有「必须前端可见」的配置才用 `NEXT_PUBLIC_*`(白名单,如公网站点 origin/name);每加一条 `NEXT_PUBLIC_` 评审一次。
40
+
41
+ ## 错误 / 空态
42
+ - 接口失败统一:空态(antd-mobile `Empty`)+重试;server 组件取数失败给降级公开 HTML(别整页白屏),细节见 ux-interaction.md。
43
+
44
+ ## 无障碍(a11y)编码基线(发布前由 testing-acceptance.md 验收)
45
+ - 语义化标签(`button`/`a`/`nav`/`main`/`section`/有序 headings);可点元素用真 `button`/`a`(键盘可达 + 默认回车)。
46
+ - 图片 `alt`;表单 `label`/`aria-label`;键盘焦点可见(`:focus-visible`);触控目标 ≥44px(见 responsive-cross-end.md)。
47
+ - 颜色对比达标(WCAG AA);勿用纯颜色传达状态(配图标/文字)。
48
+
49
+ ## 代码风格
50
+ - ESLint(`next/core-web-vitals` + `@typescript-eslint` + `jsx-a11y`)+ Prettier;模板见 assets/frontend-template(`.eslintrc.cjs`/`.prettierrc`)。
51
+ - 提交前 `next lint`/`tsc --noEmit` 过;CI 跑 check-env/check-project/check-seo/check-build(见 testing-acceptance.md)。
@@ -0,0 +1,32 @@
1
+ # 容器与公开能力边界
2
+
3
+ 「在支付宝 WebView 内」≠「拥有支付宝小程序的容器能力」。第三方独立 H5 拿不到小程序容器的 `my.*`、`AlipayJSBridge` 容器 JSAPI、离线包等;可用的是开放平台公开能力。
4
+
5
+ ## 能力边界(公网核实,源 opendocs.alipay.com)
6
+
7
+ ### 可用(公开,默认路径)
8
+ - 网页&移动应用 /open/03lo16:服务端接口用支付/资金/营销/安全产品。
9
+ - 手机网站支付 `alipay.trade.wap.pay` /open/203/107091:**服务端生成支付 URL,前端 location 跳转**;可经 alipays 协议唤起 App。无需前端 SDK。
10
+ - OAuth 网页授权 + 获取用户信息 /mini/02xtl8:服务端 publicAppAuthorize 跳转拿 auth_code → `alipay.system.oauth.*` 换 uid;H5 内可选 `ap.getAuthCode` 半屏授权。
11
+ - PC/H5 唤起支付宝 /open/02zlo5:**仅服务端 SDK**,生成 alipays scheme 把浏览器用户唤起进支付宝(如需 PC/网页内获取用户信息,见 `/mini/02xtl7`)。
12
+ - 小程序内嵌 H5(web-view) /mini/tool/h5:若以后要宿主能力,可把 H5 包进小程序当壳(代价:形态变小程序,H5 不再独立被收录)。
13
+
14
+ ### 不可用 / 受限(纯第三方 H5)
15
+ `pushWindow`/`popWindow`(多 webview)、`setTitle`(原生栏)、`setShareContent`(自定义分享)、私有 RPC 网关、`my.*`、离线包、客户端开关、真机调试平台——**均无第三方公开版**。(opendocs 搜 `pushWindow`/`setShareContent` 均为 0 命中;`AlipayJSBridge` 命中均在 `/mini/*` 小程序范畴。)
16
+
17
+ ### 待真机验证
18
+ Service Worker 在支付宝 WKWebView(iOS)/Android WebView 内是否生效——无官方文档。
19
+
20
+ ## 容器层实现约定:两级回退
21
+ - **Level 1 标准 Web**(永远可用,主路径):`location`/`history`/`fetch`/`localStorage`/`document`/`visibilitychange`。
22
+ - **Level 2 公开 Alipay**:
23
+ - 支付 = 服务端建 wap.pay URL → 前端 `location.assign(url)`。
24
+ - 授权 = 服务端建 publicAppAuthorize URL → 前端 `location.assign(url)`,回跳后用 code 换 uid。
25
+ - 浏览器→App 唤起 = 算 `alipays://platformapi/startapp?appId=20000067&url=<encodeURIComponent(本页URL)>`,经 `https://render.alipay.com/p/s/i/?scheme=<encodeURIComponent(scheme)>` landing 跳(非 alipay.com 域名需在开放平台配白名单)。
26
+ - 可选前端 `ap.*`:仅 `ap.getAuthCode`(H5 半屏授权拿 auth_code,源 /mini/02xtl8、/open/01zsdu)作"不离开页面"的授权增强;默认用 publicAppAuthorize 服务端跳转即可。**`ap.tradePay` 未在公网确认为第三方 H5 公开**(公网只见小程序 `my.tradePay`),H5 支付一律服务端 `alipay.trade.wap.pay` 跳转,勿臆造。来源/包名以 open/08xgr7 为准,默认不引入;具体可用以你方应用开放平台授权为准。
27
+ - **不要依赖 `AlipayJSBridge` 的容器 JSAPI**(`pushWindow`/`setTitle`/`setShareContent` 等,第三方不可用)——写进代码只会误导。
28
+
29
+ ## 编码契约
30
+ - 用薄薄一层 `container/*` 封装"环境判定 + 支付/授权跳转 + scheme 唤起 + 降级",业务页只调人类可读 API(模板见 assets/frontend-template/container.ts)。
31
+ - 每个 API 写清"在支付宝内/浏览器内分别做什么";禁止直接穿透到 `AlipayJSBridge`。
32
+ - "在支付宝内能拿到的"只限:被 WebView 加载(公网 URL 正常渲染) + 上面公开能力,**不要假设更多**。
@@ -0,0 +1,17 @@
1
+ # 跨页 / 跨次状态
2
+
3
+ 纯 H5 无多 webview,"跨页通信"主要是**跨次访问/跨标签页**复用状态,不是实时多窗口消息。原则:**服务端持久化为主,前端补偿为辅**。
4
+
5
+ ## 主路径:服务端
6
+ A 页写入服务端(如状态/进度/草稿),B 页打开时拉取并写入自己页面内。最可靠,无 cookie/storage 缺失风险。
7
+
8
+ ## 前端补偿:localStorage + storage 事件(同源标准 Web)
9
+ - 写:`localStorage.setItem('__bus__', JSON.stringify({event,payload,ts}))`
10
+ - 其它同源 WebView/标签页收到 `window.addEventListener('storage', e => ...)`(本页写不触发自己,正好用于跨页)。
11
+ - "回到前台"用 `visibilitychange`(`document.visibilityState==='visible'`) 兜底刷新。
12
+ - 见 assets/frontend-template/container.ts 的 `emit/on/onResume`。
13
+ - 真机验证:同源 storage 事件在支付宝 WebView(iOS/Android) 跨 WebView/跨次的表现。
14
+
15
+ ## 不行的方式
16
+ - 跨页共享 `window` 全局/模块单例/内存里状态——拿不到。
17
+ - 依赖"上一页还活着所以能读到"——第三方 H5 多页是整页加载,上一页 JS 上下文已成历史。
@@ -0,0 +1,32 @@
1
+ # 环境判定与降级
2
+
3
+ ## 是否在支付宝内(UA 同步判定,SSR 安全)
4
+ ```ts
5
+ export function isAlipayClient(): boolean {
6
+ if (typeof window !== 'undefined' && typeof (window as any).AlipayJSBridge !== 'undefined') return true;
7
+ return /AlipayClient|AliApp|Alipay/i.test(typeof navigator !== 'undefined' ? navigator.userAgent : '');
8
+ }
9
+ ```
10
+ - 这个判定**只用于**:启用支付/授权等公开能力、给浏览器用户做降级。**不**用它去调 `AlipayJSBridge` 容器 JSAPI(第三方不可用)。
11
+ - React:用 `useInAlipay()` 钩子(SSR 期返回 null,挂载后判定)防闪烁。
12
+
13
+ ## 浏览器 → 支付宝:scheme + landing 唤起
14
+ ```ts
15
+ export function openPageInAlipay(targetUrl: string): void {
16
+ const scheme = `alipays://platformapi/startapp?appId=20000067&url=${encodeURIComponent(targetUrl)}`;
17
+ const landing = `https://render.alipay.com/p/s/i/?scheme=${encodeURIComponent(scheme)}`;
18
+ location.assign(landing); // 非 alipay.com 域名需在开放平台配白名单
19
+ }
20
+ ```
21
+
22
+ ## noindex 功能页的浏览器降级屏(AlipayGuard)
23
+ B 类功能页(`/publish`、`/order`、`/result/:tradeNo`):
24
+ - `robots: noindex`;浏览器打开时**不**渲染整页 CSR,显示"请在支付宝中打开"+`openPageInAlipay`。
25
+ - 在支付宝内则渲染子内容。
26
+ 模板见 assets/frontend-template/AlipayGuard.tsx。
27
+
28
+ ## 公开页不挡浏览器
29
+ A 类(详情/列表/活动)在浏览器与支付宝都正常展示(SSR HTML)。浏览器只是拿不到登录态/支付态,自然降级。
30
+
31
+ ## SSR / 服务端判定(避免 cloaking)
32
+ `isAlipayClientSSR(request.headers.get('user-agent'))` 在服务端轻量判定"是否在支付宝内"——**仅用于极轻分流**(如不预取登录态数据)。⚠ 严禁据此给爬虫与用户出不同可见内容/head(判为 cloaking):爬虫 UA 不在支付宝内,按 UA 减内容即作弊。模板里已有 `isAlipayClientSSR`(见 assets/frontend-template/container.ts)。详见 references/pitfalls.md。