文章目录
-
- 一、先聊聊这次做的截图打码工具
-
- [1.1 用在什么场景](#1.1 用在什么场景)
- [1.2 这一版包含哪些功能](#1.2 这一版包含哪些功能)
- [1.3 这次重点检查什么](#1.3 这次重点检查什么)
- [1.4 当前版本的范围](#1.4 当前版本的范围)
- 二、环境与选型:桌面版码道,原生网页运行
- 三、开发(一):把需求和验收方法一起交给码道
- 四、开发(二):检查生成文件和完成报告
- 五、开发(三):先在浏览器里把页面打开
- 六、实现逻辑(一):图片坐标、框选和批量状态
-
- [6.1 多张图片和遮挡记录各自保存](#6.1 多张图片和遮挡记录各自保存)
- [6.2 显示尺寸和原图尺寸,需要换算一次](#6.2 显示尺寸和原图尺寸,需要换算一次)
- [6.3 反向框选和松手提交怎么处理](#6.3 反向框选和松手提交怎么处理)
- 七、实现逻辑(二):模板、批量导出和本地处理
-
- [7.1 存为模板,再应用到全部](#7.1 存为模板,再应用到全部)
- [7.2 导出时重新合成图片](#7.2 导出时重新合成图片)
- [7.3 文件读取与输入检查](#7.3 文件读取与输入检查)
- [八、踩坑:示例图和 README 各卡了一次](#八、踩坑:示例图和 README 各卡了一次)
-
- [8.1 sample.png 没直接生成,改在浏览器里画](#8.1 sample.png 没直接生成,改在浏览器里画)
- [8.2 上传 README 时遇到同名冲突](#8.2 上传 README 时遇到同名冲突)
- 九、效果验证:从框选到批量导出
-
- [9.1 在第一张上框选并存为模板](#9.1 在第一张上框选并存为模板)
- [9.2 应用到全部,翻看其他图片](#9.2 应用到全部,翻看其他图片)
- [9.3 批量导出](#9.3 批量导出)
- [9.4 本次验证范围](#9.4 本次验证范围)
- 十、使用判断:码道帮到了哪里,工具有什么用
-
- [10.1 从需求到可运行文件](#10.1 从需求到可运行文件)
- [10.2 工具的价值放在具体工作里看](#10.2 工具的价值放在具体工作里看)
- 十一、不足与下一步
- 资料与源码

客服把订单截图转给技术支持核对异常,是件高频又琐碎的事。商品、金额、订单状态得留着,收件人的姓名、电话、地址不能一起发出去。一张一张用微信打码,图少的时候没问题,但同一天收到几十张格式一样的订单截图时,每张都要在相同位置画几个黑框,重复操作很磨人。
我这次用华为云码道做了个叫「遮一下」的小工具,专门处理这件事。它是一个下载到电脑上就能打开的网页,不用装软件、不用登录。一次导入多张同版式截图,在第一张上框住要遮的位置,存成模板,一键应用到全部,翻看一遍确认没漏,再批量导出。图片从头到尾只在浏览器里处理,不上传任何服务器。
开发用的是桌面版码道。我把需求发过去,它生成了页面、图片处理代码和批量逻辑,也写了 README 和 MIT 开源协议。中间碰到示例图片没直接生成的小问题,用浏览器绘图代码补上了。最后我拿三张同版式的虚构订单实测:在第一张上圈好姓名、手机、地址、卡号四个位置,存为模板后应用到三张,逐张翻看遮挡位置一致,全部导出成功。
完整仓库地址(可复制):https://atomgit.com/qq_40202349/local-redact
一、先聊聊这次做的截图打码工具
1.1 用在什么场景
客服转单是最典型的场景。订单异常时,客服把截图发给技术支持,有时还要抄送给财务或归档。同一系统的订单页面版式固定,每张截图里收货人、手机号、地址、银行卡号的位置完全一样。要遮的就是这几行,但每张都要手动框选一遍。
「遮一下」针对这个场景做了批量打码:一次导入多张同版式截图,在第一张上框选要遮的位置,保存为模板,一键应用到全部图片,翻看检查后批量导出 PNG。写操作教程、公开报错截图时如果也有同版式批量处理的需求,同样适用。

1.2 这一版包含哪些功能
| 功能 | 当前实现 |
|---|---|
| 导入图片 | 选择或拖入 PNG、JPEG、WebP,支持多选,包含类型检查和读取失败提示 |
| 矩形遮挡 | 用不透明黑色矩形覆盖选区,可以连续添加多处 |
| 修改选区记录 | 撤销上一步,或清空当前图片的全部遮挡 |
| 批量打码 | 在第一张上框选 → 存为模板 → 应用到全部 → 翻看检查 |
| 导出图片 | 导出当前一张,或全部导出;按原图尺寸合成 PNG |
| 示例与交付 | 内置 3 张同版式虚构订单示例,附 README 和 MIT 协议 |
1.3 这次重点检查什么
我主要检查三件事:一是页面能否正常加载多张示例并翻看,二是在第一张上框选后存为模板、应用到全部,其他图片的遮挡位置是否一致,三是批量导出能否为每张图片生成独立的 PNG。"本地处理"则沿图片读取和下载代码核对,确认没有图片上传接口。
后面的效果验证会按这几个目标逐项看结果。
1.4 当前版本的范围
现在一次处理一组同版式截图,遮哪里由人在第一张上决定,没有自动识别。模板里的遮挡位置按原图坐标保存,应用到其他图片时会裁剪到各自边界------同版式时通常不变,尺寸不同时可能需要单独调整。导出后仍要检查是否漏遮。桌面浏览器的完整流程已有运行记录,移动端和多浏览器回归还没测完。
二、环境与选型:桌面版码道,原生网页运行
这次开发在 Windows 上进行,使用桌面版华为云码道。
| 项目 | 本次使用方式 |
|---|---|
| 开发工具 | 桌面版华为云码道,任务名为「本地截图打码工具开发」 |
| 本地项目 | C:\Users\86155\Desktop\AI征文项目\截图打码工具 |
| 实现方式 | 原生 HTML、CSS、JavaScript |
| 运行方式 | 下载源码后,双击 index.html,由浏览器打开 |
| 源码托管 | AtomGit 公开仓库 qq_40202349/local-redact |
文件选择、图片解码、Canvas 绘制和下载都可以在浏览器里完成,所以这一版没有引入前端框架,也不需要启动后端。运行主体是 index.html、style.css 和 script.js,拿到源码就能试。
我的 AtomGit 账号为 qq_40202349,对应仓库是 local-redact。在 AtomGit 工作台里,可以同时看到该账号、华为云码道 Agent 入口和关联仓库。

三、开发(一):把需求和验收方法一起交给码道
开始前,我把保存目录、功能范围和检查动作写在同一份指令里。除了单张打码的基本功能,还特别说明了批量需求:一次导入多张同版式截图,在第一张上框选后可以保存为模板,一键应用到全部,再批量导出。
下面是当时交给码道的指令,仅调整了分行:
text
请开发「遮一下------本地截图打码工具」,并实际创建项目文件。
项目根目录:
C:\Users\86155\Desktop\AI征文项目\截图打码工具
所有源码、文档和测试文件都保存在上述目录。
目录不存在则创建,保留其他项目的已有文件。
功能:
1. 导入 PNG、JPEG、WebP 图片,支持一次多选。
2. 在图片上拖动框选,用完全不透明的黑色矩形遮挡选区。
3. 支持撤销上一步、清空当前图片的遮挡。
4. 批量打码:在第一张上框选后存为模板,一键应用到全部图片。
5. 翻看上一张/下一张,逐张检查遮挡效果。
6. 导出当前一张,或全部导出;按原图分辨率合成 PNG,遮挡必须真正写入。
7. 提供多张同版式虚构信息示例图片,方便演示批量打码。
实现要求:
- 使用原生 HTML、CSS、JavaScript,无后端、无外部依赖。
- 双击 index.html 即可运行。
- 图片只在浏览器内存中处理,不上传。
- 正确处理图片缩放、反向框选和边界限制。
- 对文件类型错误、图片损坏等情况给出提示。
- 界面简洁,适配手机;使用暖灰背景、深色文字和少量青柠色强调。
同时创建:
- README.md:应用介绍、运行方法、使用步骤、隐私说明。
- LICENSE:MIT 开源协议。
完成后检查:
导入多张图片 → 在第一张上框选遮挡 → 存为模板 → 应用到全部 → 翻看检查 → 全部导出。
确认每张导出的图片都包含遮挡,尺寸与原图一致。
请实际写入文件,不要只在聊天中展示代码。
最后报告保存路径、文件清单、运行方法和验证结果。
没有实际验证的部分请明确说明。
末尾还要求码道说明没有验证的部分。这样拿到完成报告后,可以知道哪些已经检查,哪些还需要自己到浏览器里操作。
四、开发(二):检查生成文件和完成报告
码道最后交付了六个文件:
text
截图打码工具/
├── index.html 页面结构、工具栏和批量栏
├── style.css 页面样式与小屏布局
├── script.js 图片加载、框选、模板、批量导出
├── generate-sample.html 示例图片生成器(3 张同版式订单)
├── README.md 运行方法、操作步骤和隐私说明
└── LICENSE MIT 开源协议
运行主体三个文件合计约 25 KiB。generate-sample.html 用来生成 3 张同版式示例图片,README 写了运行方法和批量打码步骤,LICENSE 使用 MIT 协议。
桌面版码道的登录账号是 hw83659151。展开账号面板后,账号和「本地截图打码工具开发」任务的完成报告可以同时看到:

报告列出的验证以静态检查为主,包括文件是否写入、页面元素 ID 与脚本引用能否对应,以及加载、框选、撤销、导出的主要逻辑。浏览器点击流和像素级自动化没有执行。
还有一项需要留意:目录里没有直接生成 sample.png。项目提供了浏览器内绘制示例的办法,这部分的环境报错和实现放到第八节说明。
五、开发(三):先在浏览器里把页面打开
文件生成后,我双击项目里的 index.html。页面顶部是导入、加载示例、撤销、清空、导出当前和全部导出按钮,下面一行是批量栏:上一张、计数器、下一张、存为模板、应用到全部。中间是画布,下方显示图片尺寸和操作状态。
接着点击「加载示例(3 张)」。三张虚构订单图片依次加载,计数器显示 1 / 3,状态栏提示可以在第一张上框选后存为模板。第一节的图 1 就是加载后的界面。
到这里,页面和批量示例加载已经有了实际结果。接下来先看框选、模板和导出的代码,完整的遮挡与导出效果集中放在第九节对照。
六、实现逻辑(一):图片坐标、框选和批量状态
页面现在要同时管理多张图片,每张图片各自保存原图和遮挡记录。图片数据的流向如下:
text
导入:本地 File → 临时 Object URL → Image → images 数组
编辑:鼠标或触摸事件 → 原图坐标 → 当前图片的 masks → redraw()
模板:当前 masks → templateMasks → 应用到每张图片的 masks
导出:逐张合成 originalImage + masks → 新 Canvas → PNG Blob → 下载
6.1 多张图片和遮挡记录各自保存
script.js 中的主要状态:
| 状态 | 保存什么 | 用在哪里 |
|---|---|---|
images |
图片数组,每项为 {img, name, masks} |
翻看、模板应用、批量导出 |
currentIndex |
当前显示的图片索引 | 切换、导出当前 |
templateMasks |
保存的模板遮挡(原图坐标) | 应用到全部 |
drawing |
当前是否正在拖动 | 控制选区预览和结束操作 |
startPt、currentPt |
本次拖动的起点和当前位置 | 计算预览矩形 |
每张图片的 masks 是独立的数组,存的是 {x, y, w, h} 原图坐标。redraw() 每次先画当前图片的原图,再画出它的遮挡。正在拖动的选区也会显示,但要等到松开鼠标后才加入当前图片的 masks。
切换图片时,showImage(index) 把画布尺寸设为新图的原始尺寸,再重绘。每张图的遮挡互不影响。
6.2 显示尺寸和原图尺寸,需要换算一次
Canvas 有内部像素尺寸,也有页面上的显示尺寸。本项目把内部尺寸设为图片的 naturalWidth 和 naturalHeight,CSS 通过 max-width: 100% 控制它在页面上的显示宽度。
假设原图宽 900 像素,页面中显示为 450 像素,比例就是 2。鼠标距离图片左边缘 100 个页面像素,换算到原图中应该是 200 像素。如果直接保存 100,导出时遮挡位置就会偏移。
源码通过 getBoundingClientRect() 取得显示区域。取出鼠标或触摸的 cx、cy 后,使用下面的换算:
javascript
var rect = canvas.getBoundingClientRect();
var scaleX = canvas.width / rect.width;
var scaleY = canvas.height / rect.height;
var x = (cx - rect.left) * scaleX;
var y = (cy - rect.top) * scaleY;
x = Math.max(0, Math.min(canvas.width, x));
y = Math.max(0, Math.min(canvas.height, y));
先减去画布相对视口的位置,再乘以缩放比例,得到原图坐标。后面的两行把坐标限制在画布宽高之内。保存到 masks 中的值都来自这套原图坐标,预览与导出使用同一套位置数据。
6.3 反向框选和松手提交怎么处理
用户可以从右下往左上拖动,矩形的宽高仍然需要是正数。代码用两个端点中较小的坐标作为左上角,用差值的绝对值计算宽高:
javascript
function rectFromPoints(a, b) {
var x = Math.min(a.x, b.x);
var y = Math.min(a.y, b.y);
var w = Math.abs(b.x - a.x);
var h = Math.abs(b.y - a.y);
return { x: x, y: y, w: w, h: h };
}
源码只接收宽、高都大于 1 个原图像素的矩形。鼠标的 mousemove、mouseup 监听在 window 上,因此鼠标移出画布但仍在当前页面时,也能继续更新并结束选区。
七、实现逻辑(二):模板、批量导出和本地处理
7.1 存为模板,再应用到全部
模板的本质是把当前图片的遮挡位置复制一份,保存到 templateMasks。应用时,遍历每张图片,把模板里的每个矩形裁剪到该图的边界后写入它的 masks:
javascript
function applyTemplateToAll() {
if (!templateMasks || images.length === 0) return;
for (var i = 0; i < images.length; i++) {
var entry = images[i];
var w = entry.img.naturalWidth;
var h = entry.img.naturalHeight;
var copied = [];
for (var j = 0; j < templateMasks.length; j++) {
var r = clampRectTo(templateMasks[j], w, h);
if (r.w > 0 && r.h > 0) copied.push(r);
}
entry.masks = copied;
}
redraw();
updateButtons();
}
同版式截图尺寸一致,裁剪后位置不变。如果某张图尺寸不同,clampRectTo 会把超出边界的部分裁掉,不会画到图片外面。
7.2 导出时重新合成图片
单张导出和批量导出共用同一个合成函数。新建一张离屏 Canvas,宽高取自原图,先画原图,再叠加遮挡:
javascript
function composePNG(entry, callback) {
var out = document.createElement("canvas");
out.width = entry.img.naturalWidth;
out.height = entry.img.naturalHeight;
var octx = out.getContext("2d");
octx.drawImage(entry.img, 0, 0, out.width, out.height);
octx.fillStyle = "#000000";
for (var i = 0; i < entry.masks.length; i++) {
var m = entry.masks[i];
octx.fillRect(m.x, m.y, m.w, m.h);
}
out.toBlob(function (blob) { callback(blob, out.width, out.height); }, "image/png");
}
批量导出时依次合成每张图片,相邻下载间隔 350ms,避免浏览器拦截连续下载。文件名包含原始文件名和序号,方便对应。
7.3 文件读取与输入检查
"本地处理"也要看代码。导入时,文件来自用户选择的 File 对象,或者拖拽事件中的文件列表。程序使用 URL.createObjectURL(file) 创建临时引用,让 Image 读取,加载成功或失败后都会释放引用。导出时,图片经 Canvas 合成,再由浏览器下载。
当前源码没有图片上传接口,也没有引入远程第三方库或统计脚本。处理图片所需的逻辑都在本地脚本中。
| 检查点 | 当前源码的处理 | 作用 |
|---|---|---|
| 文件格式 | 检查 PNG、JPEG、WebP 的 MIME 或扩展名 | 先筛掉明显不支持的输入 |
| 图片读取失败 | img.onerror 提示读取失败;加载时检查自然宽高 |
处理损坏图片和解码失败 |
| 文件名与状态文案 | 通过 textContent 写入页面 |
按文本显示,不把文件名解析为 HTML |
| 重复选择同一文件 | 处理选择事件后清空文件输入值 | 可以再次选择同一组图片 |
| 空状态操作 | 未加载图片时禁用导出;没有遮挡时禁用撤销和清空 | 让按钮状态对应当前数据 |
八、踩坑:示例图和 README 各卡了一次
8.1 sample.png 没直接生成,改在浏览器里画
生成过程遇到的实际问题,是 Agent 没有直接把二进制 sample.png 写出来。完成报告中提到了运行环境限制:
text
Sandbox exe is not available
报告说明,当时无法通过命令环境生成 PNG,也没有执行浏览器点击流和像素级自动化测试。项目改用浏览器生成示例:工具里可以即时绘图,独立的 generate-sample.html 页面则可以下载示例文件。

报告列出了已生成文件、功能实现、静态检查结果,以及没有自动执行的验证项目。
点击「加载示例」后,程序在浏览器中创建 3 个 900 × 560 的 Canvas,分别画上不同客户信息的虚构订单,版式完全一致。这样即使没有独立的 PNG 文件也可以演示批量打码。
8.2 上传 README 时遇到同名冲突
最后通过网页上传六个文件时,服务端返回 HTTP 400。响应里有两行报错:
text
Path or parent path already exist
README.md already exist.
仓库已经有一个 README.md,这次批量上传无法再创建同名文件。我把 README 从上传列表移除,先提交其余五个文件,再进入仓库已有 README 的编辑页面,写入本地内容并提交。
上传后,重新下载仓库,对六个文件逐一计算 SHA256,与本地项目对照。为了比较原始字节,检出时关闭自动换行转换。下面是匿名克隆命令,运行前确认当前目录没有同名的 local-redact 文件夹:
powershell
git -c credential.helper= -c core.autocrlf=false clone --depth 1 https://atomgit.com/qq_40202349/local-redact.git local-redact
本次校验结果对应提交 067d7279fcbcb8b47a81d6eb22c1ca91ca896036:
| 文件 | 本地文件字节数 | 下载版本 SHA256 对照 |
|---|---|---|
index.html |
2,493 | 一致 |
style.css |
4,072 | 一致 |
script.js |
18,932 | 一致 |
generate-sample.html |
4,221 | 一致 |
README.md |
4,511 | 一致 |
LICENSE |
1,094 | 一致 |
六个文件的哈希全部一致,公开仓库中的源码、README 和 MIT 协议与本地版本相符。匿名克隆也验证了未登录时可以获取源码。
对试用者来说,直接在仓库页面下载 ZIP、解压、双击 index.html 即可。想先熟悉操作,可以点击「加载示例(3 张)」;也可以通过「导入图片」选择自己的 PNG、JPEG 或 WebP 文件。
九、效果验证:从框选到批量导出
这次实际操作使用内置的 3 张同版式订单示例,每张 900 × 560。下面对照开头列出的检查目标,看页面和导出文件各自的结果。
9.1 在第一张上框选并存为模板
在第一张示例图片上拖动框选,遮住收货人、手机号码、收货地址、银行卡号四行。松开鼠标后,选区成为黑色矩形,状态栏提示当前图片共有 4 处遮挡。接着点「存为模板」,模板状态显示「模板已保存:4 处」。

在第一张虚构订单上框选姓名、手机、地址、卡号四行,存为模板。批量栏显示「1 / 3」和「模板已保存:4 处」。
9.2 应用到全部,翻看其他图片
点「应用到全部」,模板中的 4 处遮挡一次性套用到 3 张图片。翻到第二张,状态栏显示「共有 4 处遮挡」,遮挡位置和第一张完全一致------姓名、手机、地址、卡号都被遮住了。

第二张虚构订单自动套用模板后,遮挡位置与第一张一致。客户信息不同(李芳华 / 139-0000-5678),但被遮住的位置相同。
第三张同样如此。如果某张图需要补遮或调整,可以单独框选或撤销,不影响其他图片。
9.3 批量导出
确认三张图片的遮挡都正确后,点「全部导出」。工具依次合成每张图片,状态栏显示导出进度。完成后提示「共 3 张,全部成功」。下载目录里出现 3 个 PNG 文件,文件名包含原始图片名和序号。

批量导出完成,3 张图片全部成功导出。
9.4 本次验证范围
本次手动运行和源码核对分别做到以下范围:
| 项目 | 检查方式 | 本次记录 |
|---|---|---|
| 页面运行 | 桌面浏览器打开本地页面 | 页面、工具栏和批量栏正常显示 |
| 批量示例 | 点击加载示例(3 张) | 生成并加载 3 张 900 × 560 同版式图片 |
| 框选与模板 | 在第一张上框选 4 处,存为模板 | 模板保存成功,显示 4 处 |
| 模板应用 | 点应用到全部,翻看第二、三张 | 遮挡位置一致,各 4 处 |
| 批量导出 | 点全部导出 | 3 张 PNG 全部生成,文件名含序号 |
| 撤销与单张导出 | 撤销一处后导出当前 | 撤销后剩余 3 处,单张导出成功 |
| 移动端、多浏览器与像素级自动化 | 未执行完整验证 | 后续补充 |
十、使用判断:码道帮到了哪里,工具有什么用
10.1 从需求到可运行文件
这次码道把页面、脚本和配套文档一起生成了。批量打码的需求------多图管理、模板保存、一键应用、批量导出------都能在代码中找到对应实现。示例文件没有直接生成时,浏览器绘图的备用方式也让项目能够继续试用。
对这类范围明确的小项目,Agent 的帮助很具体:需求交出去之后,拿到的是可以打开的项目文件,再沿着函数检查实现。页面、脚本和 README 在同一次任务里交付,功能说明也能对照着代码看。
不过,完成报告里没有运行过的测试,仍要补做。这次我还要打开页面操作、翻看每张图片的遮挡、查看下载的 PNG,再核对公开仓库与本地文件是否一致。这些工作完成后,才有文章里的运行截图和文件校验结果。
10.2 工具的价值放在具体工作里看
这个工具的用处不在"能画黑框"------很多软件都能做。它的价值在于:面对一组同版式截图,把"每张都要重复框选"变成"只框一次,其余自动套用"。客服一天处理几十张订单截图时,省下来的是大量重复操作,而不是单张打码这一步本身。
再加上图片全程在本地处理,不经过服务器,对涉及客户信息的场景来说,这条数据处理路径本身就减少了一道外发风险。团队可以把静态文件分发下去试用,不用额外维护图片上传和存储服务。
如果继续做企业定制,我会考虑把编辑入口接到客服工单的附件窗口:员工选好截图后就地遮挡,再提交到原有附件流程。批量打码、模板管理和内部部署都需要结合具体系统适配,这些工作才可能形成收费服务。
十一、不足与下一步
这次先把一个可以下载运行的批量版本做了出来,后面还有几项明确的工作:
- 补大图的输入限制。 当前没有文件大小或像素总量上限,图片过大会增加解码和 Canvas 的内存占用。需要用不同尺寸的图片测试,再决定限制和提示方式。
- 补交互回归。 撤销、清空、反向框选已经核对过源码,还要逐项操作,尤其是缩放后框选、撤销后再次导出,以及触摸结束和取消。
- 扩大实际测试范围。 目前留下的是桌面浏览器操作内置示例的记录,还需要用不同格式的本地 PNG、JPEG、WebP 文件检查导入,并补移动端、多浏览器和像素级验证。
- 考虑模板的持久化。 当前模板存在内存里,刷新页面就丢了。如果客服每天处理同版式截图,可以把模板存到 localStorage,下次打开直接复用。
下一步我会先把这些测试补齐。自动识别敏感字段、工单集成和企业试点,可以等基础操作稳定、有人实际试用之后,再按需求决定。
资料与源码
- 「遮一下」AtomGit 公开仓库:可运行源码、README、MIT 协议,以及独立的示例图片生成器。