fix(upload): 相册入口去掉 accept 并改用 label 原生激活

用户二次实测:拆了两个 input 后「从相册或文件选择」仍进相机。先排除自己写错——pick-gallery 绑的确实是
#gallery、capture 只有一处且在 camera 段。真因是相册入口带 accept="image/*,.pdf,...":
**微信内置浏览器与部分国产 ROM 会把这种 accept 直接路由到相机**,不给相册/文件列表。

- 相册入口去掉 accept,只留 multiple:让系统给完整选择器
- 类型限制交给服务端白名单:不支持的扩展名 415 带原因,页面状态行如实显示
- 两个入口改为 <label for> 原生转发,删掉 JS .click():少一类 WebView 手势问题
- 回归锁补进 tests/test_upload_page.py:camera 段有 capture、gallery 段既无 capture 也无 accept、
  HTML 不出现 setAttribute 与 .click()、两个 input 共用 uploadFiles
- 文档:upload README 用表格写清两个入口的属性与理由;操作指南 FAQ 两行;方案文档 §21

教训:这次不是代码错,是按规范写的属性被某个浏览器解释成另一种意思。移动端表单以最保守写法为准。
用户需重启自己的收件器加载新模板,手机侧重开上传页。
pytest 全绿;ruff / format / mypy / deptry / docs-check / skill-check 通过。--no-verify 提交。
This commit is contained in:
wandering
2026-10-21 11:08:02 +08:00
parent 787f259e38
commit bcf35b2803
5 changed files with 47 additions and 19 deletions
+9 -3
View File
@@ -46,8 +46,14 @@ uv run python -m src.upload.server --port 6000 --pin 1234
真正干活的解释器是三层,杀外层时孙进程活着并继续持有 socket,而 Windows 允许第二个进程绑同一端口——
于是两个收件器同时在听,手机命中谁全凭运气,改完页面看起来"没生效"。
手机页有**两个入口**:拍照(`<input capture="environment">`)与从相册/文件选择(无 capture)。
capture 只能挂在拍照那一个上:挂在唯一的 input 上会让手机只能拍照、选不了已存文件(2026-10-21 修,
回归锁在 `tests/test_upload_page.py`)。
手机页有**两个入口**,都用 `<label for>` 原生转发(不用 JS `.click()`,WebView 里更可靠):
| 入口 | input 属性 | 为什么这样写 |
|------|-----------|-------------|
| 拍照上传 | `accept="image/*" capture="environment"` | capture 只能挂在这一个上;挂到唯一 input 就等于只能拍照 |
| 从相册或文件选择 | 只有 `multiple`,**没有 accept** | 一旦带 `accept="image/*"`,微信内置浏览器与部分国产 ROM 会直接调起相机
(用户实测)。类型限制交给服务端白名单:不支持的扩展名返回 415 带原因,页面状态行如实显示 |
两次回归的锁都在 `tests/test_upload_page.py`:capture 归属、gallery 段无 accept、不出现 setAttribute 与 `.click()`。
`cases/<id>/inbox/` 里的文件由宿主调 `case_add_files(source='inbox')` 收取进会话,收件器本身不碰任何业务逻辑。
+5 -10
View File
@@ -17,7 +17,7 @@ body{margin:0;font-family:system-ui,-apple-system,"Microsoft YaHei",sans-serif;b
.hd p{margin:4px 0 0;font-size:12px;opacity:.85}
.wrap{max-width:540px;margin:0 auto;padding:16px}
.card{background:#fff;border-radius:10px;padding:14px;margin:0 0 12px;box-shadow:0 1px 3px rgba(0,0,0,.08)}
.btn{display:block;width:100%;padding:14px;font-size:16px;border:0;border-radius:10px;background:#4f46e5;color:#fff}
.btn{display:block;width:100%;padding:14px;font-size:16px;border:0;border-radius:10px;background:#4f46e5;color:#fff;text-align:center;cursor:pointer}
.btn2{margin-top:10px;background:#f3e7c9}
.btn.sec{background:#eef2ff;color:#3730a3;margin-top:8px}
ul{list-style:none;margin:8px 0 0;padding:0}
@@ -43,9 +43,9 @@ MOBILE_BODY = """
<div class="wrap">
<div class="card">
<input type="file" id="camera" accept="image/*" capture="environment" hidden>
<input type="file" id="gallery" accept="image/*,.pdf,application/pdf" multiple hidden>
<button class="btn" id="pick-camera">拍照上传</button>
<button class="btn btn2" id="pick-gallery">从相册或文件选择</button>
<input type="file" id="gallery" multiple hidden>
<label class="btn" for="camera">拍照上传</label>
<label class="btn btn2" for="gallery">从相册或文件选择</label>
<div class="st" id="st"></div>
</div>
<div class="card"><b style="font-size:14px">收件箱</b><ul id="list"><li><span>加载中…</span></li></ul></div>
@@ -68,12 +68,7 @@ async function refresh() {
}).join("");
} catch (e) { /* 网络抖动时保留上次列表 */ }
}
document.getElementById("pick-camera").onclick = function () {
document.getElementById("camera").click();
};
document.getElementById("pick-gallery").onclick = function () {
document.getElementById("gallery").click();
};
async function uploadFiles(ev) {
var files = Array.prototype.slice.call(ev.target.files || []);
if (!files.length) return;