# AGENTS.md · 兜爸心选 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.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 个 `
` 的写法改,同时更新 `figcaption`。 - **入场动画**:给元素加 `data-reveal`,`main.js` 用 IntersectionObserver 在元素进入视口时加 `.is-in`;`html.js` 类表示 JS 已运行(没有 JS 时内容照常显示)。 - **FAQ**:用原生 `
`,`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,`` 写上 `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`。