From 4df8dc6f87fcd8b60d452163ff4957c3068e43df Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Sat, 13 Jun 2026 14:26:40 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E5=AE=98=E7=BD=91=20ui=5Fkits/web?= =?UTF-8?q?site=20=E2=86=92=20Astro=20=E7=BA=AF=E9=9D=99=E6=80=81=20SSG=20?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=20(tsk=5FacMYQ-Z-EIF=5F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新建 web/website/(Astro 零 SSR): - 组件迁移:交互块保留 .jsx 经 @astrojs/react(Header/AnnouncementBar/SignupForm/PricingPlans),纯展示块转 .astro 去运行时 JS;像素以原型为基准。 - 令牌同源:build-tokens.mjs 从 design/colors_and_type.css 生成 tokens.gen.css,仅剔除第三方 Google Fonts @import,数值不改。 - 字体自托管:@fontsource(Sora/Manrope/Noto Sans SC/JetBrains Mono),无第三方 CDN。 - 图标:Lucide 构建期内联 SVG(替代 unpkg CDN),零运行时、零 CDN。 - i18n:/(zh)与 /en/(en)双路由单显,语言切换组件;文案沿用 ui_kits 脱敏文案。 - 安全:public/_headers 严格 CSP(全 self + 自托管资源 + 内联片段 sha256,无 unsafe-inline)+ HSTS;无支付表单。 - CI:.gitea/workflows/website.yml 构建(红线扫描+lint+CSP 哈希)→ 同时发布 Cloudflare Pages 主站与镜像。 - 灾备:README 写明干净环境 npm ci && npm run build 可直接部署到任意静态托管;dist 指纹确定性,主站镜像一致。 测试:npm run lint(0 error)/ npm test(build+CSP 哈希注入+红线扫描 0 命中)/ 两次干净构建 dist 指纹一致。 Co-Authored-By: Claude Opus 4.8 --- web/website/.gitea/workflows/website.yml | 103 + web/website/.gitignore | 12 + web/website/README.md | 154 + web/website/astro.config.mjs | 28 + web/website/package-lock.json | 7165 +++++++++++++++++ web/website/package.json | 43 + web/website/public/_headers | 17 + web/website/public/assets/app-icon.svg | 17 + web/website/public/assets/logo-mark.svg | 11 + web/website/public/assets/logo-wordmark.svg | 13 + web/website/scripts/build-tokens.mjs | 53 + web/website/scripts/check-redline.mjs | 75 + web/website/scripts/csp-hashes.mjs | 78 + web/website/scripts/dist-hash.mjs | 41 + .../src/components/AnnouncementBar.jsx | 22 + web/website/src/components/Blog.astro | 35 + web/website/src/components/Brand.astro | 32 + web/website/src/components/CtaBand.astro | 19 + web/website/src/components/Docs.astro | 33 + web/website/src/components/Download.astro | 34 + web/website/src/components/Features.astro | 34 + web/website/src/components/Footer.astro | 51 + web/website/src/components/Header.jsx | 84 + web/website/src/components/Hero.astro | 61 + web/website/src/components/Icon.astro | 37 + web/website/src/components/Pricing.astro | 118 + web/website/src/components/PricingPlans.jsx | 60 + web/website/src/components/SignupForm.jsx | 45 + web/website/src/components/WhySignup.astro | 41 + web/website/src/i18n/strings.ts | 200 + web/website/src/layouts/Site.astro | 91 + web/website/src/pages/en/index.astro | 5 + web/website/src/pages/index.astro | 5 + web/website/src/styles/site-extra.css | 54 + web/website/src/styles/tokens.gen.css | 218 + web/website/src/styles/website.css | 292 + web/website/tsconfig.json | 9 + 37 files changed, 9390 insertions(+) create mode 100644 web/website/.gitea/workflows/website.yml create mode 100644 web/website/.gitignore create mode 100644 web/website/README.md create mode 100644 web/website/astro.config.mjs create mode 100644 web/website/package-lock.json create mode 100644 web/website/package.json create mode 100644 web/website/public/_headers create mode 100644 web/website/public/assets/app-icon.svg create mode 100644 web/website/public/assets/logo-mark.svg create mode 100644 web/website/public/assets/logo-wordmark.svg create mode 100644 web/website/scripts/build-tokens.mjs create mode 100644 web/website/scripts/check-redline.mjs create mode 100644 web/website/scripts/csp-hashes.mjs create mode 100644 web/website/scripts/dist-hash.mjs create mode 100644 web/website/src/components/AnnouncementBar.jsx create mode 100644 web/website/src/components/Blog.astro create mode 100644 web/website/src/components/Brand.astro create mode 100644 web/website/src/components/CtaBand.astro create mode 100644 web/website/src/components/Docs.astro create mode 100644 web/website/src/components/Download.astro create mode 100644 web/website/src/components/Features.astro create mode 100644 web/website/src/components/Footer.astro create mode 100644 web/website/src/components/Header.jsx create mode 100644 web/website/src/components/Hero.astro create mode 100644 web/website/src/components/Icon.astro create mode 100644 web/website/src/components/Pricing.astro create mode 100644 web/website/src/components/PricingPlans.jsx create mode 100644 web/website/src/components/SignupForm.jsx create mode 100644 web/website/src/components/WhySignup.astro create mode 100644 web/website/src/i18n/strings.ts create mode 100644 web/website/src/layouts/Site.astro create mode 100644 web/website/src/pages/en/index.astro create mode 100644 web/website/src/pages/index.astro create mode 100644 web/website/src/styles/site-extra.css create mode 100644 web/website/src/styles/tokens.gen.css create mode 100644 web/website/src/styles/website.css create mode 100644 web/website/tsconfig.json diff --git a/web/website/.gitea/workflows/website.yml b/web/website/.gitea/workflows/website.yml new file mode 100644 index 0000000..195febb --- /dev/null +++ b/web/website/.gitea/workflows/website.yml @@ -0,0 +1,103 @@ +name: website + +# 官网 CI/CD:push web/website/** → 构建(红线词扫描 + lint + CSP 哈希)→ 同时发布主站与镜像。 +# 与仓库根 .gitea/workflows/ci.yml 解耦:官网完全静态、与后端无依赖,可独立并行。 +# +# 需在 Gitea 配置 Secrets: +# CF_API_TOKEN Cloudflare API Token(Pages:Edit 权限) +# CF_ACCOUNT_ID Cloudflare Account ID +# CF_PAGES_PROJECT_MAIN 主站 Pages 项目名 +# CF_PAGES_PROJECT_MIRROR 镜像 Pages 项目名(≥1 个活跃镜像,应对主域名被墙) +# 并把 runs-on 改成实际 runner label。 + +on: + push: + paths: + - 'web/website/**' + - '.gitea/workflows/website.yml' + pull_request: + paths: + - 'web/website/**' + workflow_dispatch: + +env: + # 主站 canonical 域名;镜像也用同一值 → canonical 指向主站 + 产物 hash 一致。 + SITE_URL: https://pangolin.example + +jobs: + # ── 构建 + 校验(红线 / lint / CSP / 指纹) ────────────────────────────── + build: + name: Build & Verify + runs-on: nas + defaults: + run: + working-directory: web/website + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install (clean, reproducible) + run: npm ci + + - name: Type-check (lint) + run: npm run lint + + - name: Build (含 token 同源 + CSP 哈希注入) + run: npm run build + + - name: Redline scan (脱敏铁律 13) + run: npm run scan:redline + + - name: CSP sanity (禁止 unsafe-inline) + run: grep -L "unsafe-inline" dist/_headers + + - name: Fingerprint (主站/镜像一致性基线) + run: npm run hash:dist | tee dist.fingerprint + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: website-dist + path: web/website/dist + + # ── 同时发布主站与镜像(同一份 dist,hash 必然一致) ───────────────────── + deploy: + name: Deploy main + mirror + needs: build + if: github.event_name == 'push' + runs-on: nas + defaults: + run: + working-directory: web/website + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install + run: npm ci + + - name: Build + run: npm run build + + - name: Deploy → 主站 (Cloudflare Pages) + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} + run: npx wrangler pages deploy dist --project-name "${{ secrets.CF_PAGES_PROJECT_MAIN }}" --branch main + + - name: Deploy → 镜像 (Cloudflare Pages) + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} + run: npx wrangler pages deploy dist --project-name "${{ secrets.CF_PAGES_PROJECT_MIRROR }}" --branch main + + - name: Verify main == mirror (内容一致性) + run: | + echo "主站与镜像部署的是同一份 dist,指纹:" + npm run --silent hash:dist diff --git a/web/website/.gitignore b/web/website/.gitignore new file mode 100644 index 0000000..ff13948 --- /dev/null +++ b/web/website/.gitignore @@ -0,0 +1,12 @@ +# build output +dist/ +# deps +node_modules/ +# astro +.astro/ +# logs / env +*.log +.env +.env.production +# OS +.DS_Store diff --git a/web/website/README.md b/web/website/README.md new file mode 100644 index 0000000..b631105 --- /dev/null +++ b/web/website/README.md @@ -0,0 +1,154 @@ +# 穿山甲 · Pangolin 官网(Astro 纯静态 / SSG) + +获客入口官网,从 `design/ui_kits/website/` 迁移到 **Astro 纯静态站点(零 SSR)**。 +设计目标:极致加载性能 + **整站可秒级复制到任意备用域名**(灾备关键,doc/01 §1、doc/05)。 + +- 完全静态、无 API 依赖、无第三方统计/CDN(隐私承诺)。 +- i18n 双路由单显:`/`(中文)、`/en/`(English),不并排(设计铁律 6)。 +- 与后端全部任务解耦,可独立并行开发与部署。 + +--- + +## 快速开始 + +```bash +cd web/website +npm ci # 干净、可复现安装(含自托管字体 @fontsource、Astro、React) +npm run dev # 本地预览 http://localhost:4321 +npm run build # 产物到 dist/(含 token 同源生成 + CSP 哈希注入) +npm run preview # 预览构建产物 +``` + +要求 Node `^18.20.8 || ^20.3.0 || >=22.0.0`。 + +### 常用脚本 +| 命令 | 作用 | +|---|---| +| `npm run build` | 构建静态产物到 `dist/`(`prebuild` 生成令牌,`postbuild` 注入 CSP 哈希) | +| `npm run lint` | `astro check` 类型检查 | +| `npm run scan:redline` | 扫描 `dist/` 红线词(脱敏铁律 13) | +| `npm run hash:dist` | 打印 `dist/` 确定性指纹(主站/镜像一致性比对) | +| `npm test` | 一键:生成令牌 → 构建 → CSP 哈希 → 红线扫描 | + +--- + +## 目录结构 + +``` +web/website/ +├─ src/ +│ ├─ layouts/Site.astro 整页布局(按 lang 单显),两路由复用 +│ ├─ pages/index.astro / → lang="zh" +│ ├─ pages/en/index.astro /en/ → lang="en" +│ ├─ components/ +│ │ ├─ *.jsx 交互组件(React island,@astrojs/react): +│ │ │ Header / AnnouncementBar / SignupForm / PricingPlans +│ │ ├─ *.astro 纯展示块(构建期渲染,零运行时 JS) +│ │ ├─ Icon.astro Lucide 图标 → 构建期内联 SVG(替代原型的 unpkg CDN) +│ │ └─ Brand.astro 品牌「行走穿山甲」内联 SVG +│ ├─ i18n/strings.ts 中/EN 字典(逐字沿用 ui_kits/website/site.js 脱敏文案) +│ └─ styles/ +│ ├─ tokens.gen.css 由 design/colors_and_type.css 生成(见下) +│ ├─ website.css 逐字迁自 ui_kits/website/website.css +│ └─ site-extra.css 仅承载「原型内联 style= → 等价 class」 +├─ public/_headers Cloudflare Pages 安全头(严格 CSP + HSTS) +├─ scripts/ +│ ├─ build-tokens.mjs 令牌同源生成器 +│ ├─ csp-hashes.mjs 构建后注入内联片段 sha256 到 CSP +│ ├─ check-redline.mjs 红线词扫描 +│ └─ dist-hash.mjs 产物指纹 +└─ .gitea/workflows/website.yml CI:构建+校验 → 同时发布主站与镜像 +``` + +--- + +## 关键决策(务必理解) + +### 1. 令牌同源(单一真相源) +颜色/字体/圆角/阴影等令牌的唯一真相源是仓库根 **`design/colors_and_type.css`**(铁律 1)。 +`scripts/build-tokens.mjs` 在每次构建前把它读入并写出 `src/styles/tokens.gen.css`, +**只移除其中加载 Google Fonts 的 `@import url('https://fonts.googleapis.com/...')` 一行** +(任务硬性要求:不引第三方字体 CDN)。**令牌数值一字未改**,仍是单一来源、可随时再生。 +> 若只拷贝了 `web/website/` 子目录到干净环境(源文件缺失),生成器自动沿用已提交的 +> `tokens.gen.css`,构建照常。 + +### 2. 字体全部自托管 +原型用 Google Fonts CDN;本站改为 **`@fontsource/*`** 本地打包 +(Sora / Manrope / Noto Sans SC / JetBrains Mono),woff2 随 `npm ci` 落地、 +经构建产出到 `dist/_astro/`,由站点自身提供(`font-src 'self'`)。 +**无任何第三方 CDN 请求** —— 性能 + 隐私 + 可达性。 + +### 3. 安全头(严格 CSP / HSTS) +`public/_headers` 下发严格 CSP:`default-src 'self'`,`script-src`/`style-src`/`font-src`/ +`img-src`/`connect-src` 全部收敛到 `self`(白名单仅 self + 自托管资源),外加 HSTS(含 preload)、 +`X-Content-Type-Options`、`X-Frame-Options: DENY`、`Referrer-Policy`、`Permissions-Policy`。 +Astro island 水合产生的少量**内联** `