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

11 KiB
Raw Blame History

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。

常用命令

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
    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。