新增图片批量处理

This commit is contained in:
yinsx
2026-01-15 10:24:02 +08:00
parent 1de2ac8491
commit 943bf4bbd4
18 changed files with 1208 additions and 123 deletions

View File

@ -1,5 +1,135 @@
async function run() {
throw new Error("尚未实现");
import color from "picocolors";
import { createStepUI } from "../../utils/stepui.js";
import { title, getSteps } from "./config.js";
import { stopKeypress, waitForKey, initKeypress } from "../../keyboard.js";
import { record } from "../../stats.js";
import { processImage } from "./service.js";
import path from "path";
const OPERATIONS = [
{ id: "compress", name: "压缩", stepIndex: 1, folder: "compressed" },
{ id: "resize", name: "生成多尺寸", stepIndex: 2, folder: "resized" },
{ id: "background", name: "统一背景", stepIndex: 3, folder: "background" },
{ id: "crop", name: "裁剪补边", stepIndex: 4, folder: "cropped" }
];
const run = async () => {
try {
while (true) {
// 强制清理并重新初始化键盘
stopKeypress();
await new Promise(resolve => setTimeout(resolve, 100));
const steps = getSteps();
const ui = createStepUI({ title, getSteps: () => steps });
const result = await ui.runInteractive();
if (!result) {
stopKeypress();
return "back";
}
const files = result.results[0] || [];
const selectedOp = OPERATIONS.find(op => result.results[op.stepIndex] !== "skip");
stopKeypress();
// 如果没有选择文件或所有操作都跳过,重新开始
if (!selectedOp || !files.length) {
continue;
}
// 有效的选择,继续处理
const params = buildProcessParams(selectedOp.id, result.results[selectedOp.stepIndex]);
ui.showSummary([
"源文件: " + files.join(", "),
"操作: " + selectedOp.name,
getParamLabel(selectedOp.id, params)
]);
const total = files.length;
let success = 0;
const failed = [];
console.log(color.cyan(`开始处理 ${total} 个文件...\\n`));
for (let i = 0; i < files.length; i++) {
const file = files[i];
const progress = `[${i + 1}/${total}]`;
console.log(color.dim(`${progress} 处理中: ${file}`));
try {
const outputs = await processImage(file, selectedOp.id, params);
success++;
record("image_operation", selectedOp.id);
const outputNames = Array.isArray(outputs)
? outputs.map(o => path.relative(process.cwd(), o)).join(", ")
: path.relative(process.cwd(), outputs);
console.log(color.green(`${progress}${file}${outputNames}`));
} catch (err) {
failed.push({ file, reason: err?.message || String(err) });
console.log(color.red(`${progress} ✖ 失败: ${file}`));
console.log(color.dim(" " + String(err?.message || err)));
}
}
console.log("\\n" + color.bgGreen(color.black(" 处理完成 ")));
console.log(color.green(`成功: ${success}`));
if (failed.length) console.log(color.yellow(`失败: ${failed.length}`));
console.log(color.cyan(`输出目录: ${selectedOp.folder}/`));
await waitForKey(color.dim("按 Esc 返回"), key => key?.name === "escape" || (key?.ctrl && key?.name === "c"));
stopKeypress();
return "back";
}
} catch (err) {
stopKeypress();
console.error(color.red("发生错误:"), err);
return "back";
}
};
function getParamLabel(operation, params) {
switch (operation) {
case "compress":
return `压缩质量: ${params.quality}%`;
case "resize":
const presets = {
responsive: "响应式 (800/400/200)",
thumbnail: "缩略图 (300/150/75)",
large: "大图 (1920/1280/640)"
};
return `尺寸预设: ${presets[params.sizes] || "响应式"}`;
case "background":
return `背景颜色: ${params.bgColor === 0xFFFFFFFF ? "白色" : "其他"}`;
case "crop":
return `画布尺寸: ${params.width}×${params.height}`;
default:
return "";
}
}
function buildProcessParams(operation, paramValue) {
switch (operation) {
case "compress":
return { quality: paramValue };
case "resize":
const sizePresets = {
responsive: [{ width: 800 }, { width: 400 }, { width: 200 }],
thumbnail: [{ width: 300 }, { width: 150 }, { width: 75 }],
large: [{ width: 1920 }, { width: 1280 }, { width: 640 }]
};
return { sizes: sizePresets[paramValue] || sizePresets.responsive };
case "background":
return { bgColor: paramValue };
case "crop":
const [width, height] = paramValue.split("x").map(Number);
return { width, height, bgColor: 0xFFFFFFFF };
default:
return null;
}
}
export default {