Files
yinx-cli/lib/plugins/image/index.js
2026-01-15 11:03:06 +08:00

143 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 {
// 强制清理并重新初始化键盘
stopKeypress();
await new Promise(resolve => setTimeout(resolve, 100));
const steps = getSteps();
const ui = createStepUI({
title,
getSteps: () => steps,
validate: (results) => {
// 验证是否有文件选择且至少有一个操作未跳过
const files = results[0] || [];
const hasOperation = OPERATIONS.some(op => results[op.stepIndex] !== "skip");
return files.length > 0 && hasOperation;
}
});
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();
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,
};