Files
douba-app-web/AGENTS.md
douba 72648b4914 初始化兜爸心选 App 官网项目(构建与发布)
- src/:从线上 shop.douba.top/public/app 拉取的页面源码(v0.3.8)
- 版本号、更新日期、安装包大小改为模板变量,统一由 site.config.json 管理,
  安装包大小在构建时从 APK 实际字节数计算
- scripts/build.mjs:构建到 dist/,校验 APK、模板变量与资源引用
- scripts/deploy.mjs:与线上比对差异 → 上传 → 备份 → 原子切换 → 线上验证
- scripts/rollback.mjs、scripts/preview.mjs:回滚与本地预览
- AGENTS.md:项目说明、发版流程与服务器/nginx 约定

由 AI 助手协助完成。
2026-10-29 06:20:35 +00:00

134 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AGENTS.md · 兜爸心选 App 官网(介绍与下载页)
「兜爸心选」移动应用的官方介绍与下载页。
- **对外访问地址:<https://shop.douba.top/app>**(不带斜杠,应用商店登记的就是这个地址,对外宣传一律用它)
- `https://shop.douba.top/app/` 同样可以访问;页面里的 `canonical`、`og:url` 和 JSON-LD 的 `url` 写的是带斜杠的版本
- 开发者主体:北京臻惠网络科技有限公司 · 京ICP备17065521号
- 应用包名:`com.yizhezhe.douba`(基于 DCloud uni-app 构建)
- 代码仓库:`git@gitea.nexjs.com:jone/douba-app-web.git`(分支 `main`;APK 和 dist 不进仓库)
站点是纯静态的:手写 HTML + CSS + 原生 JS,**运行时没有任何依赖**。本项目只负责一件事:在本地编辑 `src/`,构建出 `dist/`,再发布到服务器。构建和发布脚本只用 Node 内置模块,**不需要 `npm install`**。
## 常用命令
```bash
npm run build # src/ → dist/,渲染版本变量并校验资源引用
npm run preview # 构建并在 http://localhost:8080/app 本地预览
npm run deploy:check # 构建并列出与线上的差异,不改服务器(发布前先跑这个)
npm run deploy # 正式发布到 8.130.191.152
npm run rollback # 线上目录换回最近一次发布前的备份
npm run rollback:list # 列出服务器上的备份
```
发布需要本机能免密 SSH 到 `root@8.130.191.152`(公钥登录),需要 Node 18 及以上版本。
## 项目结构
```
douba-app/
├── site.config.json 版本号、发布日期、部署目标 —— 发版时改这里
├── src/ 站点源码(HTML 中可以使用 {{变量}})
│ ├── index.html 官网首页(单页:首屏 / 三条承诺 / 品牌理念 / 应用功能 / 应用截图 / 下载 / FAQ / 页脚)
│ ├── styles.css 首页样式(含响应式断点)
│ ├── main.js 首页轻交互(无依赖 IIFE)
│ ├── terms.html 《用户服务协议》
│ ├── permissions.html 《权限说明》(Android 权限清单与第三方 SDK)
│ ├── legal.css 两个合规文本页共用的样式
│ └── assets/
│ ├── brand.webp 品牌 Logo
│ ├── favicon.png
│ ├── og.jpg 分享卡片图(og:image)
│ ├── qr.svg 指向 shop.douba.top/app 的二维码
│ ├── shot-mama-1~3.webp 孕育模块真实截图(600×1301)
│ ├── shot-mall-1~3.webp 商城模块真实截图(600×1360)
│ └── p1~p5.webp 商品图,只用于「应用截图」区的 HTML 仿真手机界面
├── releases/ APK 安装包,文件名必须是 douba-v<版本>.apk(不进 git)
├── scripts/
│ ├── build.mjs 构建 + 校验
│ ├── deploy.mjs 发布
│ ├── rollback.mjs 回滚
│ └── preview.mjs 本地预览服务器
└── dist/ 构建产物(不进 git,不要手改)
```
《隐私政策》不属于本项目:页面链接的是站点根目录的 `/protocol.html`,由 CRMEB 商城维护,改它之前先确认。
## 模板变量与 site.config.json
构建时只渲染 `.html` 里的 `{{变量}}`。CSS 和 JS 原样复制。使用未定义的变量会导致构建失败。
| 变量 | 来源 | 用在哪里 |
|---|---|---|
| `{{version}}` | `version` | JSON-LD `softwareVersion`、首屏、下载区、FAQ |
| `{{channel}}` | `channel`(如「内测」) | 「v0.3.8(内测)」「内测版」 |
| `{{releaseDate}}` | `releaseDate`(YYYY-MM-DD,北京时间) | 下载区「更新时间」 |
| `{{screenshotVersion}}` | `screenshotVersion` | 截图区「截图取自 Android 版 v…」 |
| `{{apkFile}}` | 由 `version` 推出:`douba-v<version>.apk` | 下载按钮链接 |
| `{{apkSize}}` | **自动读取** `releases/` 里 APK 的实际大小(十进制 MB) | 首屏、下载按钮、下载区 |
| `{{permissions.version}}` / `{{permissions.updatedCn}}` | `permissions.version` / `permissions.updated` | 权限说明页的适用版本、更新日期 |
截图版本和权限说明有单独的变量,因为它们只在截图或权限确实变了的时候才更新,不一定跟着每个版本走。
`terms.html` 里的协议更新日期、生效日期属于法律文本,是写死的,不做成变量。
构建会做三项校验,任何一项不通过都会中止,不会产出 dist:
- `releases/` 里存在与 `version` 对应的 APK;
- 没有未定义的模板变量;
- HTML 的 `src`/`href`/`content`(包括 `og:image` 这类写成 `https://shop.douba.top/app/...` 的完整地址)和 CSS 的 `url()`,凡是指向 `/app` 内的文件都必须存在。指向站点根目录的路径(如 `/protocol.html`)跳过不查。
## 发布新版 APK
1. 把新安装包放进 `releases/douba-v<新版本>.apk`。
2. 修改 `site.config.json`:`version`、`releaseDate`(按北京时间);渠道变了就改 `channel`;截图换了就改 `screenshotVersion`;权限或 SDK 有变化就改 `permissions` 并同步修改 `src/permissions.html` 的清单。
3. 截图有变化的话,替换 `src/assets/shot-*.webp`(保持 600px 宽)。
4. `npm run preview` 本地看一遍,然后 `npm run deploy:check` 确认差异,没问题再 `npm run deploy`。
iOS 上架后:在 `src/index.html` 里把 `.dl-actions` 中 `is-disabled` 的「iOS 版即将上架」换成 App Store 链接,同时更新首屏 `.hero-meta`、FAQ「iPhone 可以用吗?」和 JSON-LD 的 `operatingSystem`。
## 发布机制(deploy.mjs)
1. 构建,然后用 sha256 对比 dist 和线上文件,列出新增(+)、修改(~)、删除(-)的文件。没有差异就直接退出。
2. 把站点文件打包上传。APK 只有在线上没有同一文件(sha256 不同)时才上传。
3. 在服务器的 `/www/wwwroot/shop.douba.top/.deploy/`(位于 `public/` 之外,公网访问不到)组装新目录:
- 线上 `download/` 里已有的 APK 全部以**硬链接**保留,旧版下载链接不会失效,也不占额外空间;
- 校验 APK 的 sha256,统一设为 `root:root`,目录权限 755、文件权限 644。
4. `mv public/app → .deploy/backups/app-<北京时间>`,再 `mv 新目录 → public/app`。两步都在同一分区内重命名,切换是瞬间完成的。备份保留最近 `keepBackups` 份(默认 5 份)。
5. 线上验证:请求 `/app`,确认页面版本号正确;请求 APK 地址,确认大小和本地一致。验证失败会报错,这时用 `npm run rollback` 回滚。
不要直接在服务器上改 `public/app/` 下的文件:下次发布会被 `src/` 覆盖。确实需要临时在线上修改的话,事后要同步回 `src/`。
## 页面要点
- `index.html` 各区块用 `<!-- ============ 名称 ============ -->` 注释分隔,锚点:`#about` `#features` `#screens` `#download` `#faq`。
- **首屏手机**(`#heroSlides`):6 张真实截图自动轮播,由 `main.js` 控制(4.2 秒一张;悬停、切到后台时暂停;系统开启「减少动效」时不轮播)。第一张不加 `loading="lazy"`,其余都加。
- **应用截图区**(`.shots`):前 3 张是真实截图(`shot-mama-*`);后 3 张(商城首页 / 分类 / 商品详情)是**用 HTML + CSS 画出来的仿真界面**,商品名和价格是写死的示例数据。换成真实截图时,按前 3 个 `<figure>` 的写法改,同时更新 `figcaption`。
- **入场动画**:给元素加 `data-reveal`,`main.js` 用 IntersectionObserver 在元素进入视口时加 `.is-in`;`html.js` 类表示 JS 已运行(没有 JS 时内容照常显示)。
- **FAQ**:用原生 `<details class="qa">`,`main.js` 保证同时只展开一个。
## 编码约定
- **首页资源一律用绝对路径 `/app/...`。** 因为 nginx 让 `/app`(不带斜杠)直接返回 `index.html`,这时相对路径会解析到站点根目录,样式和图片会全部失效。
- 合规页(`terms.html`、`permissions.html`)用的是相对路径(`legal.css`、`assets/...`)。它们只会通过 `/app/xxx.html` 访问,所以没问题;返回首页统一链接 `/app/`。
- 视觉方向是「品牌手账」:纸感底色、印章红、宋体标题、楷体手写批注。颜色、字体只用 `:root` 里的 CSS 变量(`--paper` `--ink` `--red` `--serif` `--hand` 等),不要新写死色值。`legal.css` 里复制了一份同名变量,改主色时**两处都要改**。
- 不引入任何框架、CDN、字体文件或统计脚本;`main.js` 保持 ES5 写法、无依赖。
- 图片用 webp,`<img>` 写上 `width`/`height`,防止加载时布局跳动;首屏以外的图片加 `loading="lazy" decoding="async"`。
- 无障碍:保留 skip link、`aria-label`、装饰元素上的 `aria-hidden="true"`,以及 `prefers-reduced-motion` 下关闭动效的处理。
- 文案为简体中文,中文与英文、数字之间留空格(如「Android 5.0 及以上」「61.5 MB」)。
- 响应式断点:1080 / 900 / 760 / 560px(见 `styles.css` 末尾)。
- 公司名、ICP 备案号、包名、客服时间(工作日 9:00–18:00)在多个页面里写死,修改时全局搜索替换。
- 合规文本(用户协议、权限说明、隐私政策)的内容改动要经过运营或法务确认,不要只为了排版顺手改措辞。
## 服务器与 nginx
- 站点:`shop.douba.top` 是 CRMEB PRO(ThinkPHP)商城,Web 根目录是 `/www/wwwroot/shop.douba.top/public/`。本项目只管 `public/app/`,**不要动 `public/` 下的其他文件**。
- 站点配置(宝塔面板):`/www/server/panel/vhost/nginx/shop.douba.top.conf`
- 本页专用规则:`/www/server/panel/vhost/nginx/extension/shop.douba.top/app.conf`
```nginx
location = /app { root /www/wwwroot/shop.douba.top/public; try_files /app/index.html =404; }
```
应用商店登记的地址不带斜杠,所以 `/app` 不能 301 跳转,**不要删这条规则**。
同一个文件里还有一条 `location ~ ^/app/download/[^/]+\.apk$` 规则:让 APK 以标准类型 `application/vnd.android.package-archive` 返回,不存在的 APK 返回 404(不会被商城接走)。改这个文件前,先把原文件备份到 `/www/wwwroot/shop.douba.top/.deploy/nginx-backup/`。
- 不存在的路径会被商城的 rewrite 规则接走,返回 CRMEB 页面和 **200 状态码**,所以不能用状态码判断文件是否存在,要看返回的内容。
- 静态文件的缓存头是 `max-age=43200` + `no-cache`(每次请求都会向服务器验证),发布后一般刷新就能看到;微信内置浏览器缓存可能更久。
- 改 nginx 配置后先 `nginx -t` 检查,再 `nginx -s reload`。