folder 与 tag API:文件夹与标签的增删改查

文件夹和标签是素材库的两套组织系统:文件夹表达「归属」(树形、互斥),标签表达「属性」(扁平、可多选)。两套 API 的设计风格很像,但能力分布很不一样,这一节把差异讲清楚。

folder:树形结构

创建

// 创建顶层文件夹
let newFolder = await eagle.folder.create({
    name: 'New Folder',
    description: 'Folder\'s description.',
});

// 创建子文件夹(显式版)
let parentFolder = await eagle.folder.getById('folder_id');
let subFolder = await eagle.folder.createSubfolder(parentFolder.id, {
    name: 'Subfolder',
    description: 'Subfolder description.',
});

// 创建时直接指定父级(简写版)
let another = await eagle.folder.create({
    name: 'Nested',
    parent: 'parent_folder_id'
});

create() 的 parent 参数等价于 createSubfolder(parentId, options)。功能上没差别,但可读性上:明确要建子目录时用 createSubfolder,能让读代码的人一眼看出层级意图。

查询

let byIds = await eagle.folder.get({ ids: ['folder_id1', 'folder_id2'] });
let selected = await eagle.folder.get({ isSelected: true });
let recent = await eagle.folder.get({ isRecent: true });

let all = await eagle.folder.getAll();
let one = await eagle.folder.getById('folder_id');
let some = await eagle.folder.getByIds(['folder_id1', 'folder_id2']);
let current = await eagle.folder.getSelected();
let recents = await eagle.folder.getRecents();

查询条件只有 id、ids、isSelected、isRecent 四项——比 item 的 get() 简单得多。文件夹本身没有标签、评分这类元数据,可筛选的维度就那么几个。

修改与移动

let folder = await eagle.folder.getById('folder_id');

folder.name = 'New Folder Name';
folder.description = 'New description...';
folder.iconColor = eagle.folder.IconColor.Blue;
await folder.save();

改 parent 属性就是移动文件夹:

// 移到另一个父文件夹下
folder.parent = 'parent_folder_id';
await folder.save();

// 移到根目录
folder.parent = null;
await folder.save();

移动文件夹为什么不用 fs.rename?因为文件夹在 Eagle 里不只是磁盘上的目录,它的 id 被所有素材的 folders 数组引用着。直接移动磁盘目录,Eagle 的索引和实际路径就对不上了。改 parent 让主应用去处理,它才知道要连带更新哪些素材记录。

parent 和 iconColor 两项在 Eagle 4.0 build12 之前是只读的,从 build12 开始支持修改。

children 是只读的,要给文件夹加子级必须用 createSubfolder,不能 folder.children.push(...)。

图标颜色常量

eagle.folder.IconColor.Red      // 'red'
eagle.folder.IconColor.Orange   // 'orange'
eagle.folder.IconColor.Yellow   // 'yellow'
eagle.folder.IconColor.Green    // 'green'
eagle.folder.IconColor.Aqua     // 'aqua'
eagle.folder.IconColor.Blue     // 'blue'
eagle.folder.IconColor.Purple   // 'purple'
eagle.folder.IconColor.Pink     // 'pink'

这八个常量也可以直接写字符串 'red',但官方建议用常量,理由是能获得更好的代码提示和类型安全——写错 eagle.folder.IconColor.R 时编辑器会立刻提示,而不是等运行时报一个无效颜色。

tag:扁平标签

查询

const all = await eagle.tag.get();                    // 全部标签
const design = await eagle.tag.get({ name: "design" }); // 按名称模糊搜索,不区分大小写
const recents = await eagle.tag.getRecentTags();       // 最近使用
const starred = await eagle.tag.getStarredTags();      // 常用(收藏)标签

get() 的 options 是可选的,不传就返回全部。name 参数做的是模糊匹配且不区分大小写——查 "design" 会把 Design、design-works 都捞出来。做「精确查找某个名字的标签」时,要在结果里自己再过滤一次:

const tags = await eagle.tag.get({ name: "design" });
const exact = tags.filter(t => t.name === 'design');

getStarredTags() 需要 Eagle 4.0 build18 以上。

重命名与合并

// 重命名:改 name 然后 save,所有用到该标签的素材自动跟着更新
const tags = await eagle.tag.get();
const tag = tags.find(t => t.name === 'old-name');
tag.name = 'new-name';
await tag.save();

// 合并:把来源标签并入目标标签,来源标签消失
const result = await eagle.tag.merge({
    source: 'UI Design',
    target: 'UI'
});
console.log(`已合并 ${result.affectedItems} 个素材的标签`);

merge() 返回一个对象,含 affectedItems(受影响的素材数量)和 sourceRemoved(来源标签是否已移除)两个字段。

merge() 是不可逆的。 官方明确警告:合并操作会更新所有使用来源标签的素材、标签群组、收藏标签和历史标签。做整理类功能时,UI 上必须让用户二次确认。

Tag 实例属性

name 可写(改完要 save());count、groups、pinyin 都是只读,分别对应该标签的素材数量、所属分组、名称拼音(搜索排序用)。color 属性可以读取标签颜色。

save() 目前只支持修改标签名称这一件事。标签颜色在 Eagle 主界面里可以点选设置,但 API 没有开放对应的写入方法——这是个明显的能力缺口。

tagGroup:给标签分组

标签一多,检索就成了灾难。标签群组就是为此设计的:

// 创建群组
await eagle.tagGroup.create({
    name: "new group",
    color: "red",
    tags: ["tag1", "tag2"],
    description: "群组描述"    // Eagle 4.0 build18+
});

// 读取全部群组
const tagGroups = await eagle.tagGroup.get();

// 修改群组
const group = tagGroups[0];
group.name = "new name";
group.color = "red";   // red, orange, yellow, green, aqua, blue, purple, pink
group.tags = ["tag1", "tag2"];
group.description = "群组描述";
await group.save();

// 移除群组
await group.remove();

addTags() 和 removeTags() 是增量操作,比「取出完整数组、改、再整体保存」安全得多:

// 只添加,允许标签同时存在于多个群组
await group.addTags({ tags: ['UI', 'UX', 'Typography'] });

// 移动:从原群组移除后再添加
await group.addTags({
    tags: ['Branding'],
    removeFromSource: true
});

// 从群组移除指定标签
await group.removeTags({ tags: ['Outdated', 'Draft'] });

removeFromSource 这个参数的设计很实用:false 时一个标签可以同时属于多个群组(像多维分类),true 时是移动(标签只有归属一个群组,像文件夹)。

⚠️ removeTags() 只从群组移除标签,不会删除标签本身,也不会影响素材上的标签。 官方特别强调过这一点——很多人会误以为从这里移除等于删标签。

组织策略:什么时候用哪个

  • 表达归属关系(这张图属于哪个项目)→ 用文件夹。文件夹树是唯一的,一个素材在一个文件夹里。建目录层级用 folder.create() 加 parent。
  • 表达属性特征(横版、已审、需二次处理)→ 用标签。标签可以多选,一个素材可以有任意多个。
  • 标签多到难以浏览 → 用 tagGroup 分组,或者用 eagle.smartFolder 建智能文件夹(按条件自动归类,无需手动打标)。

一个实用模式是「文件夹放项目,标签放状态」:每个项目一个文件夹,文件夹内素材统一打上「已交付」「待修改」这类标签。这样加一个新项目不会污染标签体系。

开发流程

  1. 先确认素材组织需求是「归属」还是「属性」,决定用 folder 还是 tag。
  2. 建目录结构时逐层调用 folder.create() 并传 parent,或者先建父级再用 createSubfolder。
  3. 批量归档素材时,读出素材改 folders 或 tags,逐个 save()。
  4. 清理标签体系前,先用 tag.merge() 合并近义标签,UI 上必须二次确认。
  5. 标签数量超过三十个就开始考虑 tagGroup 分组。
  6. 任何删除或合并操作都用 showMessageBox 让用户确认影响范围。

常见坑

folder.create() 忘了传 name。 name 是唯一必填字段,漏了会创建失败。description 才是可选的。

用 children 数组增删子文件夹。 children 是只读的,直接 push 不生效。用 createSubfolder 创建,移动用改 parent。

延伸注意: * 指望 tag.save() 改颜色。 save() 目前只支持改名称。标签颜色在 API 层面没有写入方法,只能通过主界面操作。 * 用 children 数组增删子文件夹。 children 是只读的。创建用 createSubfolder,移动用改 parent。

整理人:WorkBuddy