# 再开一个新问卷站点 · 完整教程

> 这份教程解决一件事：**你现在有两套问卷站，想再加第三套、第四套，每套数据各存各的**。
> 全部步骤都在本机用命令完成，不需要在 Cloudflare 后台一步步点（后台点法在第 4 节，作为备用）。

---

## 1. 先看现状

### 已经上线的站点

| # | 站点 | 线上地址 | 后台 | 数据库（D1） | 性质 |
|---|---|---|---|---|---|
| 1 | 市场调研三套卷 | https://survey-aan.pages.dev | `/admin.html` 口令 `sp-2026-x7k9m3` | `survey_db`<br>`238b054b-a23b-424f-818a-fa42aeaf2d23` | 匿名，3 套卷 61 题 |
| 2 | 见面会登记 | https://realname-survey.pages.dev | `/admin.html` 口令 `rn-2026-k3m9q7` | `realname-survey-db`<br>`bf7c6472-129d-4d40-891e-904adfd8214d` | 单页表单：2 道填空题（姓名 + 有空时间），同页一次提交 |
| 3 | 获奖资料站 | https://ss-awards.pages.dev | — | 无 | 纯静态 |

> 第 2 套原来叫「课程反馈（实名）」，已按需求改成**见面会登记**：只问姓名和有空参加的时间，两道填空题排在同一页，底部只有「提交问卷」，没有「上一题/下一题」。目录名仍叫 `realname-survey`（历史原因），线上项目名也是 `realname-survey`，所以网址不变。
> 后台口令对应的变量是 `ADMIN_TOKEN`；`REQUIRE_REALNAME` 现在是 `0`（姓名已经变成题目本身，不再走「姓名+学号」那套身份校验）。

### 每个站点背后到底是什么

```
一个站点  =  1 个 Cloudflare Pages 项目（放网页 + 接口）
          +  1 个 D1 数据库（存答卷）
          +  若干环境变量（后台口令、是否实名）
```

**为什么必须一个站一个库**：数据库是绑在项目上的。两个站共用一个库，删一个站的数据就会把另一个站的数据一起删掉；导出 CSV 时两边的卷号也会混在一起。所以「每份问卷独立存」= 每份问卷一个 Pages 项目 + 一个 D1 数据库。

---

## 2. 三条路线，选一条

| 路线 | 适合 | 耗时 | 需要手工点后台 |
|---|---|---|---|
| **A. 一键脚本**（推荐） | 你现在的情况 | 5 分钟 | 完全不用 |
| B. 后台手动点 | 脚本报错时兜底 / 想看明白原理 | 20 分钟 | 全程手点 |
| C. 新开 Cloudflare 账号 | 换人用、要独立计费 | 30 分钟 | 注册 + 验证邮箱 |

下面重点讲 A，B 作为备用，C 只列要点。

---

## 3. 路线 A：一键再开一个站

### 3.0 先准备（只做一次）

打开 **PowerShell**，先粘这一段（把 Node、Python、两个编译器的路径都设好，后面所有命令都靠它们）：

```powershell
$py   = 'C:\Users\Lenovo\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\python\python.exe'
$node = 'C:\Users\Lenovo\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\node\bin\node.exe'
$gen  = 'C:\Users\Lenovo\Documents\deepseek-harness\default-workspace\survey-gen'
$cf   = 'C:\Users\Lenovo\Documents\deepseek-harness\default-workspace\cf-tools'

$env:PYTHONIOENCODING   = 'utf-8'
$env:CLOUDFLARE_ACCOUNT_ID = '7746e8049f95d15c8b3bac5ee5a89cdc'
$env:CLOUDFLARE_API_TOKEN  = '<你的 Token>'
$env:ESBUILD_BINARY_PATH   = "$cf\node_modules\.pnpm\@esbuild+win32-x64@0.28.2\node_modules\@esbuild\win32-x64\esbuild.exe"
$env:MINIFLARE_WORKERD_PATH = "$cf\node_modules\.pnpm\@cloudflare+workerd-windows-64@1.20261006.1\node_modules\@cloudflare\workerd-windows-64\bin\workerd.exe"
```

**Token 从哪来**：打开
`C:\Users\Lenovo\Documents\deepseek-harness\default-workspace\realname-survey\.cf-token`
里面有 `CLOUDFLARE_API_TOKEN=cfut_...`，把 `cfut_` 后面那一整串复制过来替换 `<你的 Token>`。

> 这个 Token 的权限是 `Account · Cloudflare Pages · Edit` + `Account · D1 · Edit`，够用。
> **它等于你账号的钥匙**，不要发到群里、不要贴到公开网页。要是泄露了，去 Cloudflare 后台 API Tokens 页面点 `Roll` 换新的一串。

### 3.1 生成站点骨架

```powershell
cd $gen
& $py new_site.py 'D:\deepseek\市场调研\survey-3' --title '你的问卷标题' --subtitle '副标题随便写' --realname
```

- 最后那个目录名 **一定用英文**（`survey-3`、`survey-4`…）。
  用中文名也能生成，但云端项目名会退化成一个叫 `mysite` 的默认值，两个中文目录就会撞名。
- `--realname` = 要填姓名+学号、同学号不能重复交。**去掉它**就是匿名问卷站。
- 跑完会打印一行「Pages 项目名：survey-3」「D1 数据库名：survey-3-db」——**记住这两个名字**。

生成出来的目录长这样：

```
survey-3\
├─ index.html              首页（选哪套卷）
├─ 01-问卷.html            答题页  ← 自动生成，别手改
├─ admin.html              回收后台
├─ 404.html
├─ questions\papers.js     题库  ← 改题目只改这里
├─ questions\site-config.json  品牌文案 + 云端项目名
├─ assets\style.css        样式
├─ assets\quiz.js          答题逻辑（校验、提交、断点续答）
├─ functions\              后端接口（提交/列表/导出/统计/删除）
├─ migrations\             建表 SQL
└─ tools\                  生成器 + 4 个自检脚本 + 部署脚本
```

### 3.2 改题目

打开 `questions\papers.js`，结构如下（这就是现在示例卷的样子）：

```js
window.PAPERS = [
    {
        id: 'demo',                     // 卷标识，英文，全站唯一
        no: '01',
        icon: '📝',
        title: '实名问卷示例',
        subtitle: '请按实际情况填写',
        desc: '首页上的一段说明',
        meta: '5 题 · 约 2 分钟',        // 「N 题」要和实际题数一致，自检会核对
        short: '索引页的一句话简介',
        realName: true,                 // 实名卷必须有这行；匿名卷不要写
        questions: [
            { id: 'q1', type: 'single', required: true,
              title: '你的年级',
              options: ['大一','大二','大三','大四','研究生'] },

            { id: 'q2', type: 'single', required: true,
              title: '你的专业类别',
              options: ['理工类','文史类','经管类','艺术类','其他·填空'] },

            { id: 'q3', type: 'multi',  required: true,
              title: '你参加过哪些活动（可多选）',
              options: ['学术讲座','学科竞赛','志愿服务','社团活动'] },

            { id: 'q4', type: 'scale',  required: true,
              title: '总体满意度', min: 1, max: 5,
              minLabel: '很不满意', maxLabel: '非常满意' },

            { id: 'q5', type: 'text',   required: false,
              title: '还有什么想说的（选填）',
              placeholder: '欢迎写下你的建议' }
        ]
    }
];
```

改题规则（都是踩过坑总结的）：

| 想做什么 | 怎么做 |
|---|---|
| 加一道单选 | 复制 `q1` 那一块，改 `id`（不能重复）、`title`、`options` |
| 多选 | `type: 'multi'` |
| 量表（1-5 分那种） | `type: 'scale'`，写 `min` / `max` |
| 开放题 | `type: 'text'`，想让它选填就 `required: false` |
| 选项里要「其他____」 | 选项写成 `'其他·填空'`，页面上会变成「其他（请填写）」并多一个输入框 |
| 整套卷两套以上 | 复制整个 `{ id: ..., questions: [...] }` 块，**块之间要有逗号**，各卷 `id` 不能一样 |
| 题目全错了想重来 | 直接改文件，然后跑第 3.3 节的命令 |

### 3.3 生成页面（每次改完题目必须跑）

```powershell
cd 'D:\deepseek\市场调研\survey-3'
& $py tools\gen_pages.py
```

看到这样的输出就对了：

```
01-问卷.html    12.3 KB  (20 题 / 标题 8 字 / 字号 22px)
index.html       2.5 KB
functions/_lib/shared.js  已更新
```

> **最后一行最关键**。后端要认得出你这套卷的题号，靠的就是它把 `papers.js` 同步进 `functions/_lib/shared.js`。
> 不跑这一步，提交上来的答卷会被判「未知的问卷编号」。

### 3.4 本地自检（改完题目建议每次跑）

```powershell
& $node tools\check_survey.cjs
& $node tools\check_api.js
& $node tools\check_quiz.mjs
& $node tools\check_other.mjs
```

四条都出现 `合计 N 项，通过 N，失败 0` 才能往下走。
哪条报 `[FAIL]`，把那一行发我，我直接修。

### 3.5 开通云端（一个站只做一次）

**先确认当前目录是站点目录**（`cd 'D:\deepseek\市场调研\survey-3'`，它要靠这里的 `migrations\` 建表）：

```powershell
& $node "$gen\provision_site.mjs" survey-3 '给它设的后台口令' --realname
```

它会做三件事，全程自动：

1. 建数据库 `survey-3-db`
2. 执行 `migrations\0001_init.sql` 和 `0002_realname.sql`（建表、建索引、加姓名学号列）
3. 建 Pages 项目 `survey-3`，把数据库绑成变量 `DB`，写入 `ADMIN_TOKEN`（= 你刚设的口令）和 `REQUIRE_REALNAME=1`

**后台口令自己想一个**，比如 `s3-2026-ab12cd`，记在手机备忘录里——之后进 `/admin.html` 看答卷用的就是它。

### 3.5b 把凭据存进站点（强烈建议，只做一次）

```powershell
@"
CLOUDFLARE_API_TOKEN=<你的 Token>
CLOUDFLARE_ACCOUNT_ID=7746e8049f95d15c8b3bac5ee5a89cdc
ADMIN_TOKEN=<你刚设的后台口令>
"@ | Set-Content '.cf-token' -Encoding ASCII
```

好处：以后在这个目录里跑 `deploy.py` 和 `check_live.mjs`，**不用再设环境变量**。
`.cf-token` 已经写进 `.gitignore`，不会被提交、也不会被上传到网站。

### 3.6 部署上线

```powershell
& $py tools\deploy.py survey-3
```

成功的关键标志（日志里必须出现这两行）：

```
✨ Compiled Worker successfully
✨ Uploading Functions bundle
```

出现这两行 = `functions\` 被真的打包成 Worker 了，接口能跑。
最后会打印 `部署完成： https://survey-3.pages.dev`。

### 3.7 线上验收

```powershell
& $node tools\check_live.mjs
```

9 项全过就是真的能收了：

```
[PASS] 首页可访问
[PASS] /api/list 未授权被拒  → 401 + JSON（说明 Functions 真的在跑）
[PASS] 未知接口返回 404
[PASS] 实名模式下缺姓名/学号被拒
[PASS] 提交答卷成功
[PASS] 同学号重复提交被拒（409）
[PASS] 后台可读取答卷
[PASS] 导出 CSV 表头正确
[PASS] 清理验收数据
```

> 验收脚本会往库里写一条测试答卷，最后一步自己删掉。
> 如果卡在「首页可访问」并报 `fetch failed`，是这台机器 HTTPS 偶发抖动，重跑一次就行。

### 3.8 以后改了题目怎么更新

只有一条命令：

```powershell
cd 'D:\deepseek\市场调研\survey-3'
& $py tools\gen_pages.py
& $py tools\deploy.py survey-3
```

**不用**重新开通云端，数据库和项目都还在，答卷数据也不会丢。

---

## 4. 路线 B：在 Cloudflare 后台手动点（备用）

脚本万一跑不通，就照这个顺序手点。**顺序不能颠倒**，因为 Pages 项目创建时要选数据库。

### 4.1 建数据库

1. 打开 https://dash.cloudflare.com → 左侧 **Storage & Databases → D1 SQL Database**
2. **Create** → 名字填 `survey-3-db` → **Create**
3. 建好后页面上有一串 **Database ID**（`xxxxxxxx-xxxx-...`），复制下来，后面要用

### 4.2 建表

在这个数据库页面点 **Console**（或 Explore Data → Console），把两个 SQL 文件里的语句**一条一条**粘进去执行：

`migrations\0001_init.sql` 和 `migrations\0002_realname.sql`

⚠️ Cloudflare 的控制台**不接受多行 SQL**，必须一条语句一次。一共 9 条：

```
CREATE TABLE IF NOT EXISTS submissions (...)      ← 整段括号一起粘，这是一条
CREATE INDEX IF NOT EXISTS idx_submissions_paper ...
CREATE INDEX IF NOT EXISTS idx_submissions_received ...
CREATE INDEX IF NOT EXISTS idx_submissions_ip ...
CREATE VIEW IF NOT EXISTS daily_stats AS ...
ALTER TABLE submissions ADD COLUMN real_name TEXT
ALTER TABLE submissions ADD COLUMN student_id TEXT
CREATE INDEX IF NOT EXISTS idx_submissions_student ...
CREATE UNIQUE INDEX IF NOT EXISTS uniq_student_paper ...
```

执行成功会显示 `This query returned no data` —— **这就是成功**，不是没执行。

### 4.3 建 Pages 项目

1. 左侧 **Workers & Pages** → **Create** → **Pages** → **Upload assets**
2. 项目名填 `survey-3` → **Create project**
3. 这一步随便传个空文件夹都行，**马上会失败的**——没关系，目的是先把项目建出来，好去配绑定

### 4.4 绑定数据库 + 加变量

进项目 → **Settings**：

- **Bindings** → **Add** → **D1 database**
  - Variable name：**`DB`**（必须叫这个，代码里写死了）
  - D1 database：选 `survey-3-db`
- **Variables and secrets** → 加两条：
  - `ADMIN_TOKEN` → 类型选 **Secret**，值是你的后台口令
  - `REQUIRE_REALNAME` → 类型选 **Plaintext**，值是 `1`（实名）或 `0`（匿名）

### 4.5 部署

⚠️ **后台页面上的「拖文件夹 / 上传 zip」不能用**。
它只上传网页文件，**不会打包 `functions\`**，结果就是页面能打开、但一提交答卷就 404/405。

必须在本机用命令部署（走 `wrangler`，它会在本地把接口编译成 Worker 再上传）：

```powershell
cd 'D:\deepseek\市场调研\survey-3'
& $py tools\deploy.py survey-3
```

---

## 5. 命令速查

```powershell
# 0) 设好环境（每个新开的 PowerShell 窗口都要设一次）
$py   = 'C:\Users\Lenovo\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\python\python.exe'
$node = 'C:\Users\Lenovo\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\node\bin\node.exe'
$gen  = 'C:\Users\Lenovo\Documents\deepseek-harness\default-workspace\survey-gen'

# 1) 生成骨架
cd $gen
& $py new_site.py 'D:\deepseek\市场调研\survey-3' --title '标题' --realname
& $py new_site.py 'D:\deepseek\市场调研\meeting-signup' --title '见面会信息登记' --single-page   # 单页表单（两道填空题同页提交）
& $py new_site.py 'D:\deepseek\市场调研\survey-5' --title '标题'                                  # 匿名多题

# 2) 改完题目后：生成页面 → 自检 → 部署
cd 'D:\deepseek\市场调研\survey-3'
& $py tools\gen_pages.py
& $node tools\check_survey.cjs
& $node tools\check_api.js
& $py tools\deploy.py survey-3
& $node tools\check_live.mjs

# 3) 开通全新站点的云端资源（一个站只做一次；必须在站点目录里跑）
& $node "$gen\provision_site.mjs" survey-3 '后台口令' --realname

# 4) 改已有站点的环境变量（不动数据库绑定，改完立刻生效，不用重新部署）
& $node "$gen\update_project_env.mjs" realname-survey REQUIRE_REALNAME=0
& $node "$gen\update_project_env.mjs" realname-survey --secret ADMIN_TOKEN=新口令
```

> `check_live.mjs` 要后台口令：先读环境变量 `ADMIN_TOKEN`，没有就读站点根目录的 `.cf-token`。
> 所以第 3.5b 节那一步别省。

---

## 6. 一定会踩的 9 个坑

| # | 坑 | 症状 | 正确做法 |
|---|---|---|---|
| 1 | 用「上传文件夹/zip」部署 | 页面能打开，提交答卷 404 或 405 | 必须 `python tools\deploy.py` |
| 2 | 改完题目没跑 `gen_pages.py` | 提交报「未知的问卷编号」 | 每次改题都跑 |
| 3 | 把 A 站的 `functions/_lib/shared.js` 复制到 B 站 | 后端崩，报 `Cannot read properties of undefined` | 这个文件是**每站专属**的，只能由本站的 `gen_pages.py` 生成 |
| 4 | D1 ID 抄错一位 | 部署成功但一提交就 500 | 从后台「Database ID」整串复制，别手打 |
| 5 | Pages 项目名重名 | 报 `already exists` | 项目名全局唯一；中国区已有 `survey-3` 就换 `survey-4` |
| 6 | `meta` 里写的题数和实际题数不一致 | 自检报 `[FAIL]` | 改完题目顺手把 `meta: 'N 题'` 对上 |
| 7 | 访问不存在的网址 | 返回 200 而不是 404 | 已用 `404.html` 处理；判断「有没有接口」要看**返回内容**，不能只看状态码 |
| 8 | 同一学号再交一次 | 页面提示「该学号已提交过本问卷」 | 这是设计如此（防重复），不是 bug |
| 9 | 前端提交时带 `realName: ''` / `studentId: ''` | **所有答卷都被 400 拒收**，红字写「姓名格式不正确」 | 空串要当成「没填」。前端已改成非空才带这两个字段，后端也会把空值视为未填（`tools/check_api.js` 有回归用例） |

> 第 9 条是真实事故：见面会站上线后**每一份答卷都被拒**，用户看到的还是「提交成功」大标题 + 红字报错，数据一条没进库。所以提交完成页的标题现在会跟着结果变：成功是「提交成功，感谢参与！」、失败是红色的「已记录，但还没上传成功」，并且多了一个「重试提交」按钮。

---

## 7. 答卷在哪看、怎么导出

**绝大部分情况不需要下载**，后台四个页签就够看：

| 页签 | 能看到什么 |
|---|---|
| 📊 回收概况 | 一共收了多少份、按天曲线、平均用时、各问卷分别多少 |
| 📈 逐题分布 | 每题的作答人数与完成率；选择题自动画条形图；**开放题（填空题）点「👀 直接查看 N 条回答」就能在页面上读原文** |
| 📋 答卷明细 | 一人一行，答案列显示成「你的姓名：赵嘉睿」这样的题干+答案；可按问卷筛选、可分页、可单条删除 |
| ⬇️ 导出数据 | 需要发给别人、用 Excel 做统计或做备份时才用 |

| 想做的事 | 怎么做 |
|---|---|
| 看一共收了多少 | 打开 `https://<项目名>.pages.dev/admin.html`（浏览器会自动跳成 `/admin`，正常），输后台口令。见面会登记站是 `https://realname-survey.pages.dev/admin.html`，口令 `rn-2026-k3m9q7` |
| 看某个人交的 | 后台「答卷明细」页，每行一份（一人一条），翻页看；实名站还能按**学号**精确筛选 |
| 看填空题的原文 | 后台「逐题分布」页，把鼠标放到开放题上，点「👀 直接查看 N 条回答」 |
| 导出 Excel | 后台点「导出 CSV」，用 Excel 打开（中文不会乱码，带 BOM）。**一行就是一个人**，姓名和时间各占一列 |
| 删某条 / 批量删 / 清空 | 后台每行的删除键；或「危险操作区」两次确认 |
| 直接在数据库里查 | D1 控制台跑 SQL，比如 `SELECT real_name, student_id FROM submissions LIMIT 20`，或见面会站 `SELECT answers FROM submissions LIMIT 20` |

> 手机浏览器也能打开后台，但表格比较宽，建议用电脑看。

---

## 8. 路线 C：完全新开一个 Cloudflare 账号（要点）

只有当你要**换人用、或者要独立计费**时才需要：

1. https://dash.cloudflare.com/sign-up 注册 → 验证邮箱
2. 左侧 **Workers & Pages** 第一次进去会让你设一个 `workers.dev` 子域名，随便填（如 `yourname-lab`），**设了不能改**
3. 然后回到第 4 节，或者把新账号的 Account ID 和 Token 给我，用第 3 节的脚本一样能跑

新账号需要重新拿一次：
- **Account ID**：后台首页右侧栏 → Account ID
- **API Token**：右上头像 → My Profile → API Tokens → Create Token → Custom token，权限加两条：`Account · Cloudflare Pages · Edit`、`Account · D1 · Edit`，Account Resources 选你的账号

---

## 9. 现在的状态

第 2 套（`realname-survey`）已经改成你要的样子：

- 只有 **2 道填空题**：`你的姓名`、`你有空参加见面会的时间`
- **两道题在同一页**，底部只有一个「提交问卷」，没有「上一题 / 下一题」
- 每交一份就是数据库里 **一条记录**，后台一人一行，导出 CSV 也是 **一人一行**
- 空着不填点提交会被拦住，并把没填的那道题标红
- 二维码短链：`https://realname-survey.pages.dev/signup`（见 §11）
- 后台：`https://realname-survey.pages.dev/admin`，口令 `rn-2026-k3m9q7`

要改题目就编辑 `realname-survey/questions/papers.js`，然后按 3.3 → 3.4 → 3.6 重跑一遍。

---

## 10. 两种卷型：逐题翻页 / 单页表单

同一个模板支持两种问卷形态，区别只在 `questions/papers.js` 里卷对象的一个字段。

### 10.1 逐题翻页（默认）

不写 `singlePage` 就是这个模式：一次显示一道题，底部「上一题 / 下一题」，最后一题是「提交」。

### 10.2 单页表单（你现在这套）

最省事的做法是生成时直接加一个开关：

```powershell
& $py new_site.py 'D:\deepseek\市场调研\meeting-signup' --title '见面会信息登记' --single-page
```

这样出来的题库就是「姓名 + 时间」两道填空题同页提交，四个自检也全部适配这套形态（不用单选/多选题也能过）。改题目同样只改 `questions/papers.js`。

想自己手写，就是在卷对象里加一行 `singlePage: true`，所有题目一次全渲染出来，底部只剩「提交问卷」：

```js
window.PAPERS = [
    {
        id: 'meeting',
        no: '01',
        icon: '📅',
        title: '见面会信息登记',
        subtitle: '填写姓名与有空参加的时间',
        desc: '两道题在同一页，填完点底部「提交问卷」。',
        meta: '2 题 · 约 1 分钟',      // 题数必须和下面 questions 的数量一致
        short: '姓名 + 有空时间',
        singlePage: true,             // ← 关键开关
        questions: [
            {
                id: 'q1', type: 'text', required: true,
                lines: 1,             // 1 = 单行输入框（姓名这类短答案）
                maxLength: 30,
                title: '你的姓名',
                placeholder: '请填写真实姓名'
            },
            {
                id: 'q2', type: 'text', required: true,
                lines: 3,             // 3 = 三行文本框
                maxLength: 200,
                title: '你有空参加见面会的时间',
                helper: '可以写多个时间段。',
                placeholder: '例如：周三晚上 7 点后；周六上午 9-12 点'
            }
        ]
    }
];
```

| 字段 | 作用 |
|---|---|
| `singlePage: true` | 所有题排一页，一次提交 |
| `type: 'text'` + `lines: 1` | 单行填空框 |
| `lines: 3` | 多行文本框 |
| `maxLength` | 最多能输入多少字 |
| `helper` | 题目下面的灰色小字说明 |
| `required: true` | 必答，没填会被拦住并标红 |

单页表单卷**不需要** `single`/`multi` 选项题，自检脚本会自动跳过「缺少单选题」这类检查。

### 10.3 改已有站点的环境变量

`REQUIRE_REALNAME`（是否强制「姓名 + 学号」身份校验）、`ADMIN_TOKEN`（后台口令）这些存在 Cloudflare 上，用这条命令改，不会动数据库绑定：

```powershell
# 关掉实名校验（姓名变成题目本身时就要关）
node $gen\update_project_env.mjs realname-survey REQUIRE_REALNAME=0

# 改后台口令（secret 类型必须显式给新值，否则会被清空）
node $gen\update_project_env.mjs realname-survey REQUIRE_REALNAME=0 --secret ADMIN_TOKEN=新口令
```

改完变量**不需要重新部署**，下一次请求就生效。



---

## 11. 分享给别人：二维码

### 11.1 已经做好的

| 文件 | 用途 |
|---|---|
| `D:\deepseek\市场调研\二维码分享\见面会登记-扫码答题.png` | **卡片版**，发微信群 / 朋友圈 / 打印小海报 |
| `D:\deepseek\市场调研\二维码分享\见面会登记-二维码-高清.png` | **纯二维码**（1170×1170），贴纸上、PPT 里用 |
| `D:\deepseek\市场调研\二维码分享\见面会登记-群公告文案.txt` | 可直接复制的群公告文字 + 说明 |

三个文件里的网址都是：`https://realname-survey.pages.dev/signup`

### 11.2 为什么是 `/signup` 而不是答题页那一长串

答题页文件名带中文（`01-问卷.html`），二维码里塞中文路径，个别老扫描器会认错。

所以在站点根目录放了一个 `_redirects` 文件：

```
/signup    /01-问卷.html    302
```

扫码 → `/signup` → 跳到答题页，**一步到位，不用先点首页**。这个文件由 `deploy.py` 一起上传（`INCLUDE_FILES` 里已加 `_redirects`），新站点用 `new_site.py` 生成时也会自动写好。

> 验证命令：`curl.exe -s -o NUL -w "%{url_effective} %{http_code}\n" -L https://<项目名>.pages.dev/signup`，正常输出是 `.../01-问卷 200`。

### 11.3 以后换网址 / 换文案怎么重新生成

生成脚本：`C:\Users\Lenovo\Documents\deepseek-harness\default-workspace\qr-tools\make_qr_meeting.py`

改文件顶部的这几行就够了：

```python
URL = 'https://realname-survey.pages.dev/signup'   # 二维码指向的网址
OUT_DIR = r'D:\deepseek\市场调研\二维码分享'
CARD_NAME = '见面会登记-扫码答题.png'
```

标题、副标题、按钮文字在 `build_card()` 里的 `text_center(...)` 那几行；群公告文字在 `write_text()`。

跑一次：

```powershell
$py = 'C:\Users\Lenovo\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\python\python.exe'
& $py 'C:\Users\Lenovo\Documents\deepseek-harness\default-workspace\qr-tools\make_qr_meeting.py'
```

脚本会自己解码验证，再做 11 项抗干扰测试（缩放 60/40/25%、高斯模糊 1.6px、变暗 60%、提亮 150%、旋转 5°/-8°/15°、JPEG 质量 55、只截二维码区域），最后打印「全部通过」。

### 11.4 改二维码样式时唯一的硬规矩

**三个定位角（左上、右上、左下）必须是直角实心方块**，不能改圆角。改了之后 1:1:3:1:1 的明暗比例被破坏，任何半径下都扫不出来——这条是实测结论，不是理论推测。数据码点（其余位置）可以随意做圆角、改颜色，深色建议保持 `#101725` 这类近乎黑的颜色，对比度足够。
