首页
/ Sketch Measure技术架构与插件开发

Sketch Measure技术架构与插件开发

2026-02-04 05:19:14作者:盛欣凯Ernestine

本文深入解析了Sketch Measure插件的完整技术架构体系,涵盖了manifest.json配置解析、JavaScript与Sketch API集成技术、SMFramework核心框架分析以及多语言国际化实现机制。文章详细介绍了插件的命令系统架构、菜单设计、跨语言通信桥梁、性能优化策略和动态语言检测等关键技术,为Sketch插件开发提供了全面的技术参考和最佳实践指南。

Sketch插件架构与manifest.json配置解析

Sketch Measure作为一款功能强大的设计标注插件,其架构设计体现了现代Sketch插件开发的精髓。manifest.json文件作为插件的配置核心,承载着插件与Sketch应用之间的桥梁作用,理解其配置结构对于插件开发至关重要。

manifest.json文件结构解析

Sketch Measure的manifest.json文件采用了标准的Sketch插件配置格式,包含了完整的插件元数据、命令定义和菜单配置:

{
    "author": "utom",
    "commands": [
        {
            "name": "Toolbar",
            "identifier": "commandToolbar",
            "shortcut": "ctrl shift b",
            "handler": "commandToolbar",
            "script": "mark.sketchscript"
        }
        // ... 更多命令配置
    ],
    "menu": {
        "isRoot": false,
        "shortcut": "",
        "items": [
            "commandToolbar",
            "commandToolbar2",
            "-",
            "commandOverlays",
            // ... 菜单项配置
        ],
        "title": "Sketch Measure"
    },
    "identifier": "com.utom.measure",
    "appcast": "https://raw.githubusercontent.com/utom/sketch-measure/master/appcast.xml",
    "homepage": "http://utom.design/measure/",
    "version": "2.8.1",
    "description": "Make it a fun to create spec for developers and teammates",
    "authorEmail": "utombox@gmail.com",
    "name": "Sketch Measure"
}

核心配置字段详解

1. 插件元数据配置

graph TD
    A[manifest.json] --> B[插件标识]
    A --> C[版本信息]
    A --> D[作者信息]
    A --> E[更新配置]
    
    B --> B1[identifier: com.utom.measure]
    C --> C1[version: 2.8.1]
    D --> D1[author: utom]
    D --> D2[authorEmail: utombox@gmail.com]
    E --> E1[appcast: 更新检查URL]
    E --> E2[homepage: 项目主页]
字段名 类型 说明 示例值
identifier String 插件唯一标识符,采用反向域名格式 com.utom.measure
version String 插件版本号,遵循语义化版本规范 2.8.1
author String 插件作者名称 utom
authorEmail String 作者联系邮箱 utombox@gmail.com
name String 插件显示名称 Sketch Measure
description String 插件功能描述 Make it a fun to create spec...
homepage String 项目主页URL http://utom.design/measure/
appcast String 自动更新检查的XML地址 https://raw.githubusercontent.com...

2. 命令系统架构

Sketch Measure通过commands数组定义了丰富的功能命令,每个命令包含以下关键属性:

{
    "name": "Toolbar",
    "identifier": "commandToolbar", 
    "shortcut": "ctrl shift b",
    "handler": "commandToolbar",
    "script": "mark.sketchscript"
}

命令配置详解:

  • name: 命令的显示名称,在菜单中可见
  • identifier: 命令的唯一标识符,用于内部引用
  • shortcut: 键盘快捷键配置(可选)
  • handler: 对应的JavaScript处理函数名
  • script: 执行的脚本文件路径

3. 菜单系统设计

menu对象定义了插件的菜单结构和布局:

"menu": {
    "isRoot": false,
    "shortcut": "",
    "items": [
        "commandToolbar",
        "commandToolbar2",
        "-",
        "commandOverlays",
        "commandSizes",
        {
          "title": "Help",
          "items": ["linkHelp", "linkFeedback", "linkDonate"]
        }
    ],
    "title": "Sketch Measure"
}

菜单配置说明:

  • isRoot: false 表示作为子菜单而非顶级菜单
  • items 数组包含命令标识符和分隔符(-)
  • 支持嵌套菜单结构,如Help子菜单
  • title 定义菜单的显示标题

插件架构设计模式

Sketch Measure采用了模块化的架构设计,通过manifest.json统一管理所有功能入口:

flowchart TD
    A[Sketch Application] --> B[manifest.json]
    B --> C[命令路由]
    C --> D[mark.sketchscript]
    C --> E[links.sketchscript]
    
    D --> F[核心功能模块]
    D --> G[工具栏系统]
    D --> H[标注引擎]
    
    E --> I[链接处理]
    E --> J[外部资源]
    
    F --> K[尺寸标注]
    F --> L[间距测量]
    F --> M[属性标注]

高级配置技巧

1. 快捷键配置策略

Sketch Measure为不同功能配置了记忆性强的快捷键组合:

功能 快捷键 设计意图
Toolbar ctrl shift b B for Bar
Overlays ctrl shift 1 数字1代表基础功能
Sizes ctrl shift 2 数字2代表尺寸相关
Export ctrl shift e E for Export

2. 脚本组织架构

graph TB
    Main[mark.sketchscript] --> Core[核心逻辑]
    Main --> Lib1[common.js]
    Main --> Lib2[MochaJSDelegate.js]
    Main --> Framework[SMFramework.js]
    
    Core --> Handler1[commandOverlays]
    Core --> Handler2[commandSizes]
    Core --> HandlerN[commandExport]
    
    Links[links.sketchscript] --> External[外部链接处理]

3. 初始化机制

插件通过commandInit处理程序实现自动初始化:

function commandInit(context){
  Sketch = new API();
  ga = new Analytics(context);
  if(ga) ga.sendEvent('file', 'open sketch file');
  var manifestCore = new manifestMaster(context);
  manifestCore.init()
  SM.init(context, "init");
}

这种设计确保了插件在文档打开时自动进行必要的初始化和配置检查。

最佳实践总结

通过分析Sketch Measure的manifest.json配置,我们可以总结出以下Sketch插件开发的最佳实践:

  1. 清晰的命名规范:使用有意义的标识符和名称
  2. 模块化的命令设计:每个功能对应独立的命令处理器
  3. 合理的菜单组织:按功能逻辑分组,使用分隔符增强可读性
  4. 完整的元数据:提供详细的作者、版本和描述信息
  5. 自动化更新机制:配置appcast实现自动更新检查

这种架构设计不仅保证了插件的稳定运行,还为后续的功能扩展和维护提供了良好的基础。理解manifest.json的配置原理,是掌握Sketch插件开发的关键第一步。

JavaScript与Sketch API集成技术

Sketch Measure插件通过JavaScript与Sketch API的深度集成,实现了强大的设计规范生成功能。这种集成技术是插件开发的核心,涉及多个关键技术和架构设计。

COScript桥接机制

Sketch Measure利用CocoaScript(COScript)作为JavaScript与Objective-C之间的桥梁,这是Sketch插件开发的基础技术架构:

// COScript环境初始化
coscript.setShouldKeepAround(true);
var context = COScript.currentCOScript().env();
var document = context.document;

COScript允许JavaScript代码直接调用Sketch的Objective-C API,实现原生功能的无缝集成。插件通过这种方式访问Sketch的文档模型、图层系统和用户界面。

MochaJSDelegate事件处理

插件使用MochaJSDelegate来处理Objective-C的回调和事件机制,这是实现复杂交互的关键:

sequenceDiagram
    participant JS as JavaScript
    participant Delegate as MochaJSDelegate
    participant ObjC as Objective-C
    JS->>Delegate: 创建委托实例
    Delegate->>ObjC: 注册动态类
    ObjC->>Delegate: 事件触发
    Delegate->>JS: 调用处理函数

MochaJSDelegate的工作原理如下:

// 创建委托实例
var delegate = new MochaJSDelegate({
    "windowShouldClose:": function(sender) {
        // 处理窗口关闭事件
        return true;
    },
    "webView:didFinishLoadForFrame:": function(webView, frame) {
        // 网页加载完成回调
        this.didFinishLoad();
    }
});

// 应用委托到NSWindow
var window = NSWindow.alloc().initWithContentRect_styleMask_backing_defer(
    NSMakeRect(0, 0, 800, 600),
    NSTitledWindowMask | NSClosableWindowMask,
    NSBackingStoreBuffered,
    false
);
window.setDelegate(delegate.getClassInstance());

Sketch API封装层

插件对Sketch API进行了深度封装,提供了简洁易用的接口:

// API封装示例
SM.extend({
    is: function(layer, theClass) {
        if(!layer) return false;
        var klass = layer.class();
        return klass === theClass;
    },
    
    addGroup: function() {
        return MSLayerGroup.new();
    },
    
    addText: function(container) {
        var text = MSTextLayer.new();
        text.setStringValue("text");
        return text;
    },
    
    getRect: function(layer) {
        var rect = layer.absoluteRect();
        return {
            x: Math.round(rect.x()),
            y: Math.round(rect.y()),
            width: Math.round(rect.width()),
            height: Math.round(rect.height()),
            maxX: Math.round(rect.x() + rect.width()),
            maxY: Math.round(rect.y() + rect.height())
        };
    }
});

异步操作与事件循环

JavaScript与Sketch API的集成需要处理异步操作和事件循环:

// 异步操作处理
SM.export = function() {
    var self = this;
    
    // 显示进度面板
    this.showProcessingPanel(_("Exporting..."));
    
    // 使用setTimeout避免阻塞UI
    setTimeout(function() {
        try {
            // 执行耗时的导出操作
            self.generateHTMLSpec();
            
            // 完成后隐藏进度面板
            self.hideProcessingPanel();
            
            // 显示完成通知
            self.showSuccessNotification(_("Export completed successfully!"));
        } catch (error) {
            self.hideProcessingPanel();
            self.showErrorNotification(_("Export failed: ") + error);
        }
    }, 100);
};

内存管理与性能优化

由于JavaScript与Objective-C的交互涉及内存管理,插件实现了相应的优化策略:

优化技术 实现方式 效果
对象缓存 重用频繁使用的对象实例 减少内存分配开销
延迟加载 按需加载资源和模块 降低启动内存占用
批量操作 合并多个API调用 减少跨语言调用次数
内存回收 及时释放不再使用的对象 避免内存泄漏
// 对象缓存示例
var colorCache = {};
SM.getColorValue = function(color) {
    var key = color.r + ',' + color.g + ',' + color.b + ',' + color.a;
    if (!colorCache[key]) {
        colorCache[key] = NSColor.colorWithRed_green_blue_alpha(
            color.r, color.g, color.b, color.a
        );
    }
    return colorCache[key];
};

错误处理与调试

插件实现了完善的错误处理机制,确保与Sketch API交互的稳定性:

// 错误处理封装
SM.safeExecute = function(func, context) {
    try {
        return func.apply(context || this, Array.prototype.slice.call(arguments, 2));
    } catch (error) {
        log("Error: " + error);
        if (this.context) {
            this.context.document.showMessage("Sketch Measure Error: " + error);
        }
        return null;
    }
};

// 使用示例
var result = SM.safeExecute(function() {
    return this.document.currentPage().artboards();
}, this);

多语言国际化支持

插件通过JavaScript与Sketch的本地化系统集成,实现多语言支持:

// 国际化实现
var I18N = {};
var lang = NSUserDefaults.standardUserDefaults()
    .objectForKey("AppleLanguages").objectAtIndex(0);

function _(str, data) {
    var translated = (I18N[lang] && I18N[lang][str]) ? I18N[lang][str] : str;
    return translated.replace(/\%\@/gi, function() {
        return data && data.length > 0 ? data.shift() : '';
    });
}

// 加载语言文件
if (NSFileManager.defaultManager().fileExistsAtPath(langFile)) {
    var languageContent = NSString.stringWithContentsOfFile_encoding_error(
        langFile, 4, nil
    );
    I18N[lang] = JSON.parse(languageContent);
}

面板与UI集成

JavaScript通过Sketch API创建和管理自定义用户界面:

flowchart TD
    A[JavaScript调用] --> B[创建NSWindow]
    B --> C[配置WebView]
    C --> D[加载HTML界面]
    D --> E[建立JS通信]
    E --> F[处理用户交互]
    F --> G[更新Sketch文档]
// 面板创建示例
SM.createPanel = function(url, options) {
    var window = NSWindow.alloc().initWithContentRect_styleMask_backing_defer(
        NSMakeRect(0, 0, options.width, options.height),
        NSTitledWindowMask | NSClosableWindowMask,
        NSBackingStoreBuffered,
        false
    );
    
    var webView = WebView.alloc().initWithFrame(NSMakeRect(0, 0, options.width, options.height));
    webView.setFrameLoadDelegate(delegate.getClassInstance());
    
    var request = NSURLRequest.requestWithURL(NSURL.URLWithString(url));
    webView.mainFrame().loadRequest(request);
    
    window.setContentView(webView);
    window.center();
    window.makeKeyAndOrderFront(null);
    
    return window;
};

这种深度集成技术使得Sketch Measure能够充分利用Sketch的原生功能,同时保持JavaScript开发的灵活性和高效性,为设计师提供了强大的设计规范生成工具。

SMFramework框架的核心功能分析

SMFramework作为Sketch Measure插件的核心底层框架,承担着连接Sketch原生API与JavaScript插件逻辑的关键桥梁作用。该框架采用Objective-C与JavaScript混合编程架构,为插件提供了强大的原生能力扩展和性能优化。

框架架构设计

SMFramework采用经典的MVC架构模式,通过Cocoa框架与JavaScriptCore引擎的无缝集成,实现了Sketch插件开发的高效协作模式:

flowchart TD
    A[Sketch JavaScript Plugin] --> B[SMFramework Bridge]
    B --> C[Objective-C Native Layer]
    C --> D[Sketch Cocoa API]
    B --> E[JavaScript Runtime]
    E --> F[Plugin Business Logic]
    
    subgraph Native Domain
        C
        D
    end
    
    subgraph Script Domain
        E
        F
    end

核心功能模块

1. 框架加载与初始化机制

SMFramework实现了智能的框架加载策略,通过Mocha运行时环境进行动态加载:

var SMFramework_FrameworkPath = SMFramework_FrameworkPath || 
    COScript.currentCOScript().env().scriptURL.path().stringByDeletingLastPathComponent();

var SMFramework_Log = SMFramework_Log || log;

(function() {
    var mocha = Mocha.sharedRuntime();
    var frameworkName = "SMFramework";
    var directory = SMFramework_FrameworkPath;
    
    if (mocha.valueForKey(frameworkName)) {
        SMFramework_Log("😎 loadFramework: `" + frameworkName + "` already loaded.");
        return true;
    } else if ([mocha loadFrameworkWithName:frameworkName inDirectory:directory]) {
        SMFramework_Log("✅ loadFramework: `" + frameworkName + "` success!");
        mocha.setValue_forKey_(true, frameworkName);
        return true;
    } else {
        SMFramework_Log("❌ loadFramework: `" + frameworkName + "` failed!: " + directory);
        return false;
    }
})();

2. 原生对象封装与交互

SMFramework提供了丰富的原生对象封装,使得JavaScript代码能够直接调用Objective-C类和方法:

功能类别 原生类名 JavaScript接口 主要用途
几何计算 NSGeometry SMGeometryUtils 尺寸测量、坐标转换
颜色处理 NSColor SMColorUtils 颜色格式转换、色值提取
文件操作 NSFileManager SMFileManager 导出文件管理、路径处理
图像处理 NSImage SMImageProcessor 图层渲染、图像导出

3. 性能优化机制

SMFramework通过以下机制确保插件运行的高性能:

  • 内存管理优化:采用自动引用计数(ARC)与JavaScript垃圾回收协同工作
  • 异步处理:对耗时操作实现异步队列处理,避免阻塞UI线程
  • 缓存策略:对频繁访问的Sketch对象进行缓存,减少API调用开销

关键技术实现

跨语言通信桥梁

SMFramework构建了高效的JavaScript与Objective-C通信通道:

sequenceDiagram
    participant JS as JavaScript
    participant Bridge as SMFramework Bridge
    participant OC as Objective-C
    participant Sketch as Sketch API
    
    JS->>Bridge: 调用原生方法
    Bridge->>OC: 转换参数类型
    OC->>Sketch: 执行原生操作
    Sketch-->>OC: 返回结果数据
    OC-->>Bridge: 转换JavaScript类型
    Bridge-->>JS: 返回处理结果

数据类型映射系统

SMFramework实现了完整的数据类型映射机制:

Objective-C类型 JavaScript类型 转换处理
NSString String 自动编码转换
NSNumber Number 数值类型映射
NSArray Array 集合对象转换
NSDictionary Object 键值对映射
NSData ArrayBuffer 二进制数据处理

核心API功能示例

SMFramework提供了丰富的工具类方法,以下是一些关键功能的代码示例:

// 几何计算功能
function calculateLayerFrame(layer) {
    var frame = layer.frame();
    var influenceRect = SMFramework.calculateInfluenceRect(frame, layer.style());
    return {
        x: frame.x(),
        y: frame.y(),
        width: frame.width(),
        height: frame.height(),
        influenceRect: influenceRect
    };
}

// 颜色处理功能
function extractColorInfo(style) {
    var fills = style.fills();
    var borders = style.borders();
    
    var colorInfo = SMFramework.processColors(fills, borders);
    return {
        hex: colorInfo.hexValues(),
        rgb: colorInfo.rgbValues(),
        rgba: colorInfo.rgbaValues()
    };
}

// 导出文件处理
function generateSpecExport(artboards, options) {
    var exportPath = SMFramework.getExportDirectory();
    var processor = SMFramework.createExportProcessor(exportPath, options);
    
    artboards.forEach(function(artboard) {
        processor.processArtboard(artboard);
    });
    
    return processor.generateHTML();
}

框架扩展性与定制化

SMFramework设计了高度可扩展的架构,支持开发者进行功能扩展:

classDiagram
    class SMFrameworkCore {
        +loadFramework()
        +registerPlugin()
        +unloadFramework()
    }
    
    class SMBaseProcessor {
        +processLayer()
        +generateOutput()
        #validateInput()
    }
    
    class SMExportProcessor {
        +processArtboard()
        +generateHTML()
        -createAssets()
    }
    
    class SMMeasureProcessor {
        +calculateDimensions()
        +generateMarkup()
        -createMeasurementLayer()
    }
    
    SMFrameworkCore --> SMBaseProcessor
    SMBaseProcessor <|-- SMExportProcessor
    SMBaseProcessor <|-- SMMeasureProcessor

性能监控与调试支持

SMFramework内置了完善的性能监控和调试工具:

// 性能监控示例
SMFramework.startPerformanceMonitoring();

// 执行测量操作
var results = measureLayers(selectedLayers);

// 获取性能数据
var metrics = SMFramework.getPerformanceMetrics();
console.log("操作耗时: " + metrics.duration + "ms");
console.log("内存使用: " + metrics.memoryUsage + "MB");

// 调试日志输出
SMFramework.enableDebugLogging(true);
SMFramework.logDebug("图层测量完成", { 
    layerCount: selectedLayers.length,
    results: results 
});

SMFramework框架通过其强大的原生能力整合、高效的跨语言通信机制和优秀的性能表现,为Sketch Measure插件提供了坚实的技术基础,使得复杂的测量、标注和导出功能得以高效稳定地实现。

多语言支持与国际化的实现机制

Sketch Measure作为一款面向全球设计师的Sketch插件,其多语言支持机制体现了现代国际化(i18n)的最佳实践。该插件通过JSON文件管理、动态语言检测和运行时翻译等核心技术,实现了无缝的多语言用户体验。

语言文件结构与组织

Sketch Measure采用模块化的语言文件结构,将所有翻译资源集中存放在i18n目录下:

graph TB
    A[i18n语言资源目录] --> B[manifest-en.json]
    A --> C[manifest-zh-Hans.json]
    A --> D[manifest-zh-Hant.json]
    A --> E[zh-Hans.json]
    A --> F[zh-Hant.json]
    
    B --> B1[英文菜单配置]
    C --> C1[简体中文菜单配置]
    D --> D1[繁体中文菜单配置]
    E --> E1[简体中文界面文本]
    F --> F1[繁体中文界面文本]

语言文件分为两种类型:

  • manifest文件:包含菜单项、命令标识符和快捷键配置
  • 翻译文件:包含界面文本的具体翻译内容

动态语言检测机制

Sketch Measure通过系统API自动检测用户的语言偏好:

// 语言检测核心代码
var macOSVersion = NSDictionary.dictionaryWithContentsOfFile(
    "/System/Library/CoreServices/SystemVersion.plist"
).objectForKey("ProductVersion") + "";

var lang = NSUserDefaults.standardUserDefaults()
    .objectForKey("AppleLanguages").objectAtIndex(0);

// 处理macOS 10.12+的语言代码格式
lang = (macOSVersion >= "10.12") ? 
    lang.split("-").slice(0, -1).join("-") : lang;

这种机制确保了插件能够准确识别用户的系统语言设置,包括处理不同macOS版本的语言代码格式差异。

翻译函数实现

插件实现了简洁高效的翻译函数_(),支持参数替换功能:

function _(str, data){
    var str = (I18N[lang] && I18N[lang][str]) ? I18N[lang][str] : str;
    var idx = -1;
    return str.replace(/\%\@/gi, function(){
        idx++;
        return data[idx];
    });
}

该函数支持动态参数替换,如Processing layer %@ of %@可以替换为图层处理中... 5 / 10

语言资源加载流程

语言资源的加载采用懒加载机制,只在需要时加载对应的语言文件:

sequenceDiagram
    participant User
    participant Plugin as Sketch Measure
    participant System as macOS
    participant File as i18n文件

    User->>System: 启动Sketch
    System->>Plugin: 提供语言偏好设置
    Plugin->>File: 检查对应语言文件是否存在
    File-->>Plugin: 返回文件状态
    alt 文件存在
        Plugin->>File: 读取JSON翻译内容
        File-->>Plugin: 返回翻译数据
        Plugin->>Plugin: 解析并缓存翻译数据
    else 文件不存在
        Plugin->>Plugin: 使用默认英语
    end
    Plugin-->>User: 显示本地化界面

多语言菜单配置

manifest文件定义了完整的菜单结构,支持不同语言的菜单项显示:

功能标识符 英文名称 简体中文名称 快捷键
commandToolbar Toolbar 工具栏 ctrl shift b
commandOverlays Mark Overlay 标注区域 ctrl shift 1
commandSizes Mark Sizes 标注尺寸 ctrl shift 2
commandExport Spec Export 导出规范 ctrl shift e

Web面板的多语言支持

对于Web界面,插件使用不同的语言代码映射:

var webI18N = {
    "zh-Hans": "zh-cn",
    "zh-Hant": "zh-tw"
};

这种映射确保了浏览器环境下的语言兼容性,通过navigator.language检测用户浏览器语言设置。

翻译内容覆盖范围

Sketch Measure的翻译覆盖了所有用户界面元素:

界面区域 翻译示例(英文→中文) 技术实现
工具栏按钮 Mark Overlay → 标注区域 manifest文件配置
对话框文本 Export complete! → 导出完成! 翻译JSON文件
错误提示 Select a layer → 请选中1个图层 动态参数翻译
设置选项 Advanced mode → 高级模式 统一术语管理

扩展性与维护性

该国际化架构具有良好的扩展性,添加新语言只需:

  1. 创建对应的manifest文件(如manifest-fr.json
  2. 创建翻译文件(如fr.json
  3. 更新webI18N映射表(如需要)
// 新增法语支持的示例
{
    "Export complete!": "Exportation terminée!",
    "Select a layer": "Veuillez sélectionner un calque",
    "Advanced mode": "Mode avancé"
}

技术优势与最佳实践

Sketch Measure的多语言实现体现了以下最佳实践:

  1. 分离关注点:菜单配置与界面文本分离,便于维护
  2. 懒加载机制:按需加载语言资源,减少内存占用
  3. 向后兼容:保持命令标识符不变,确保插件功能稳定性
  4. 错误恢复:缺少翻译时回退到默认英语,避免界面空白
  5. 参数化翻译:支持动态内容插入,提高翻译灵活性

这种国际化架构不仅为当前支持的语言提供了完整解决方案,还为未来的语言扩展奠定了坚实基础,确保了Sketch Measure在全球设计社区中的广泛应用和良好用户体验。

Sketch Measure插件通过其精良的技术架构设计展现了现代Sketch插件开发的高水准。从manifest.json的标准化配置到SMFramework底层框架的深度封装,从JavaScript与Objective-C的高效桥接到完善的多语言支持机制,每一个技术细节都体现了模块化、可扩展性和性能优化的设计理念。该插件的架构不仅保证了功能的稳定性和高效性,更为开发者提供了优秀的范例,特别是在跨语言通信、内存管理、异步处理和国际化支持等方面的最佳实践,为Sketch生态系统的发展做出了重要技术贡献。

登录后查看全文
热门项目推荐
相关项目推荐