Files
yinx-cli/lib/plugins/image/index.js
2026-01-15 10:24:02 +08:00

141 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 {
id: "image",
name: "图片批量处理",
desc: "裁剪/缩放/转换",
run,
};