在元服务的开发与上架过程中,必须提供符合规范的图标。DevEco Studio支持Image Asset功能,帮助开发者生成统一的元服务图标样式,具体步骤请参考生成元服务图标。虽然该功能可以便捷地生成元服务图标,但当需要处理的图标数量较多时,在IDE中逐一操作效率过低。因此,需要寻求一种能够批量、快速生成整套元服务图标的解决方案。
睫毛图生成工具:为了满足服务商使用场景,ASCF提供了SDK版本:@atomicservice/as-icon-generator,帮助服务商集成睫毛图生成能力。
鉴于SDK对浏览器环境的强依赖,以及文件系统写入权限的限制,本文采用“本地轻量化Web容器+JSZip内存打包”的方案。
方案架构如下:
构建流程如下:
import { AsIconGenerator } from '@atomicservice/as-icon-generator';
import JSZip from 'jszip';
// 动态构建界面
document.querySelector('#app').innerHTML = `
<div style="padding: 20px; font-family: sans-serif; max-width: 800px; margin: 0 auto;">
<h2>批量睫毛图生成器 (All Colors版)</h2>
<div style="background: #eef; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h3>使用说明:</h3>
<ol>
<li>点击下方按钮,可以<strong>框选多张图片</strong>。</li>
<li>程序会自动分析每张图,并为<strong>每一个识别出的颜色</strong>生成一张图。</li>
<li>全部完成后,会自动下载一个 <strong>images.zip</strong> 压缩包。</li>
<li>解压该压缩包到你的项目文件夹即可。</li>
</ol>
</div>
<div style="margin-bottom: 20px;">
<input type="file" id="fileInput" accept="image/*" multiple
style="padding: 10px; border: 2px dashed #ccc; width: 100%; box-sizing: border-box;">
</div>
<div id="logArea" style="
background: #222; color: #0f0;
padding: 15px; height: 300px; overflow-y: auto;
font-family: monospace; font-size: 12px; border-radius: 4px;
display: none;
"></div>
</div>
`;
const fileInput = document.getElementById('fileInput');
const logArea = document.getElementById('logArea');
// 简单的日志工具
function log(msg, type = 'info') {
logArea.style.display = 'block';
const time = new Date().toLocaleTimeString();
const color = type === 'error' ? '#ff4444' : type === 'success' ? '#00ff00' : '#ddd';
logArea.innerHTML += `<div style="color:${color}; margin-bottom: 4px;">[${time}] ${msg}</div>`;
logArea.scrollTop = logArea.scrollHeight; // 自动滚动到底部
}
fileInput.addEventListener('change', async (e) => {
const files = Array.from(e.target.files);
if (files.length === 0) return;
log(` 开始处理,共选择了 ${files.length} 张图片...`);
// 创建 ZIP 对象
const zip = new JSZip();
// 创建一个文件夹存放结果
const folder = zip.folder("generated_icons");
let successCount = 0;
// --- 第一层循环:遍历所有图片 ---
for (let i = 0; i < files.length; i++) {
const file = files[i];
const fileName = file.name.substring(0, file.name.lastIndexOf('.')); // 去掉后缀
log(`\n----------------------------------------`);
log(` [${i + 1}/${files.length}] 正在处理图片: ${file.name}`);
try {
// 1. 加载图片
const objectUrl = URL.createObjectURL(file);
// 2. 初始化 SDK
const generator = new AsIconGenerator({ srcImg: objectUrl });
// 3. 提取颜色 (设置超时防止卡死)
const colors = await Promise.race([
generator.getColors(),
new Promise((_, r) => setTimeout(() => r(new Error('提取颜色超时')), 5000))
]);
if (!colors || colors.length === 0) {
log(`⚠️ 图片 ${file.name} 未提取到有效颜色,跳过。`, 'error');
continue;
}
log(` 识别到 ${colors.length} 种颜色: ${JSON.stringify(colors)}`);
// --- 第二层循环:遍历该图片的所有颜色 ---
for (const color of colors) {
try {
// 设置颜色
generator.setColor(color);
// 生成图片
const result = await generator.generate({ agc: true });
if (result && result.outputImage) {
// 处理 Base64 数据
const base64Data = result.outputImage.split(',')[1];
// 构造文件名:原名_颜色.png (例如: icon_#ff0000.png)
// 注意:#在文件名中可能有些系统不喜欢,替换为 _
const safeColorName = color.replace('#', '');
const outputName = `${fileName}_${safeColorName}.png`;
// 添加到压缩包
folder.file(outputName, base64Data, { base64: true });
log(` ✅ 生成成功: ${outputName}`);
successCount++;
}
} catch (innerErr) {
log(` ❌ 颜色 ${color} 生成失败: ${innerErr.message}`, 'error');
}
}
} catch (err) {
log(`❌ 图片 ${file.name} 处理出错: ${err.message}`, 'error');
}
}
// --- 结束:生成并下载 ZIP ---
if (successCount > 0) {
log(`\n 全部处理完成!共生成 ${successCount} 张图片。`);
log(` 正在打包压缩,请稍候...`);
try {
const content = await zip.generateAsync({ type: "blob" });
// 触发下载
const link = document.createElement('a');
link.href = URL.createObjectURL(content);
link.download = "batch_eyelash_icons.zip";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
log(`⬇️ 已触发下载,请查看浏览器下载栏。`, 'success');
} catch (zipErr) {
log(`❌ 压缩包生成失败: ${zipErr.message}`, 'error');
}
} else {
log(`⚠️ 没有生成任何有效图片。`, 'error');
}
});