- 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 助手协助完成。
134 lines
11 KiB
Markdown
134 lines
11 KiB
Markdown
# 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`。
|