最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 徒手撸一个 utools 系列(二)

    正文概述 掘金(muwoo)   2021-06-29   533

    前言

    上篇手把手教你实现一个支持插件化的 uTools 工具箱我们介绍过了如何通过 electron 实现 utools 的插件功能体系,并按照 utools 的交互和设计做出了一套可以支持插件化的桌面端工具箱 Rubick

    徒手撸一个 utools 系列(二)

    Rubick 源码

    本篇将继续为大家介绍如何再基于 electron 实现 utools 的搜索能力。

    搜索能力实现

    utools 搜索核心分为系统命令、插件命令、系统app功能搜索等几大类,下面我们来一一实现这3类功能的检索能力。

    由于这 3 类搜索搜索出来的内容点击触发的交互不一样,所以我们设计了一个枚举类型来标记这三种检索内容,用于点击后触发不同的行为。

    const SEARCH_TYPE = {
      DEV: 'dev', // 测试插件
      PROD: 'prod', // 已安装的插件
      SYSTEM: 'system', // 系统插件
      APP: 'app' // 应用 app
    }
    

    开发者插件

    开发者插件分为已安装本地开发 2 种类型,分别根据 SEARCH_TYPE.PRODSEARCH_TYPE.DEV 来进行区分。

    搜索内容的基础数据结构如下:

    const item = {
        name: '搜索的title',
        icon: '插件的icon',
        desc: '插件的描述信息',
        type: 'SEARCH_TYPE 对应的插件类型',
        click: '点击事件'
    }
    

    拿一个具体的搜索插件举例:

    const item = {
        // 搜索插件功能对应的 cmd
        name: cmd,
        // 插件icon 地址
        icon: plugin.sourceFile ? 'image://' + path.join(plugin.sourceFile, `../${plugin.logo}`) : plugin.logo,
        // 功能描述
        desc: fe.explain,
        // 类型
        type: plugin.type,
        // 点击后的动作
        click: (router) => {
            actions.openPlugin({commit}, {cmd, plugin, feature: fe, router});
        }
    }
    

    整体来看已安装插件和本地插件交互展示上唯一需要区分的是本地插件需要打上一个 tag 用于标记,这样才不至于混淆线上插件和本地插件:

    <a-tag v-show="item.type === 'dev'">开发者</a-tag>
    <a-tag v-show="item.type === 'system'">系统</a-tag>
    

    我们来看一下完成后的效果:

    徒手撸一个 utools 系列(二)

    会带有开发者标记。

    接下来就是实现openPlugin点击效果,点击核心能力是需要对 plugin 进行 webview 渲染。上篇问诊已经介绍过了如何实现这个 webview 这里就不在赘述,说一下点击逻辑:

    openPlugin() {
        commit('commonUpdate', {
            // 点击后设置标签tag为搜索词
            selected: {
                key: 'plugin-container',
                name: cmd,
                icon: 'image://' + path.join(plugin.sourceFile, `../${plugin.logo}`),
            },
            // 清空搜索内容
            searchValue: '',
            // 展示 plugin webview
            showMain: true
        });
        // 计算webview内容高度
        ipcRenderer.send('changeWindowSize-rubick', {
            height: getWindowHeight(),
        });
    }
    

    再说一下点击后触发的逻辑步骤:

    1. 设置左上角标签内容为搜索关键词,并设置右上角 icon
    2. 清空搜索内容
    3. 打开 webview 加载插件,并动态计算插件高度

    最后效果如下:

    徒手撸一个 utools 系列(二)

    系统插件

    系统插件是 utools 内置的,所以我们也需要将系统插件内置到 Rubick 中,这里我拿实现一个取色器来举例,去实现一个系统插件。首先先定义好系统插件的数据结构:

    const SYSTEM_PLUFINS = [
      {
        "pluginName": "屏幕颜色拾取",
        "logo": "https://alicdn.com/img/6a1b4b8a17da45d680ea30b53a91aca8.png",
        "features": [
          {
            "code": "pick",
            "explain": "rubick 帮助文档",
            "cmds": [ "取色", "拾色", 'Pick color' ]
          },
        ],
        "tag": 'rubick-color',
      }
    ]
    

    字段说明:

    • pluginName:系统插件展示的名称
    • logo: 系统插件展示的logo
    • features: 系统插件的功能列表
    • feature.code: 系统插件执行的code码
    • tag: 系统插件唯一标记

    系统插件的交互展示和开发者插件本无太大的差异,核心较大的差异在于点击后的功能和开发者插件不太一样,我们来看看系统插件的点击交互逻辑:

    opnPlugin() {
        // 如果点击的是系统插件
        if (plugin.type === 'system') {
          // 调用系统函数
          systemMethod[plugin.tag][feature.code]();
          
          // 清空选择
          commit('commonUpdate', {
            selected: null,
            showMain: false,
            options: [],
          });
          
          // 设置高度为初始高度
          ipcRenderer.send('changeWindowSize-rubick', {
            height: getWindowHeight([]),
          });
          
          // 跳转到首页
          router.push({
            path: '/home',
          });
        }
    }
    

    所以对系统插件来说,由于系统插件本身并无 webview 所以不需要打开 webview 来承载插件,而是调用系统函数,比如 color-pick 调用的对应系统函数如下:

    export default {
      'rubick-color': {
        pick() {
          ipcRenderer.send('start-picker')
        }
      },
    }
    

    main 进程发送取色能力。如何取色将在后面章节介绍。实现后的交互如下:

    徒手撸一个 utools 系列(二)

    系统app功能搜索

    针对于 macos 用户,所安装的系统 App 都放在了 /System/Applications/Applications 下,所以要实现 app 搜索,就是需要对 /System/Applications/Applications 目录下的 app 进行检索。但有的时候除了 app 需要搜索,一些系统功能也需要搜索,比如偏好设置之类的。偏好设置一般存方的路径在 /System/Library/PreferencePanes 中。

    接下来第一步需要做的是检束所有 app 和 PreferencePanes:

    const APP_FINDER_PATH = [
      '/System/Applications',
      '/Applications',
      '/System/Library/PreferencePanes',
    ];
    
    APP_FINDER_PATH.forEach((searchPath) => {
      // 搜索对应目录
      fs.readdir(searchPath, (err, files) => {
        // 查询所有 app 和 PreferencePanes
        try {
          for (let i = 0; i < files.length; i++) {
            const appName = files[i];
            const extname = path.extname(appName);
            const appSubStr = appName.split(extname)[0];
           
            if ((extname === '.app' || extname === '.prefPane') >= 0 ) {
              // 查找 应用程序的 icon
              try {
                const path1 = path.join(searchPath, `${appName}/Contents/Resources/App.icns`);
                const path2 = path.join(searchPath, `${appName}/Contents/Resources/AppIcon.icns`);
                const path3 = path.join(searchPath, `${appName}/Contents/Resources/${appSubStr}.icns`);
                const path4 = path.join(searchPath, `${appName}/Contents/Resources/${appSubStr.replace(' ', '')}.icns`);
                let iconPath = path1;
                if (fs.existsSync(path1)) {
                  iconPath = path1;
                } else if (fs.existsSync(path2)) {
                  iconPath = path2;
                } else if (fs.existsSync(path3)) {
                  iconPath = path3;
                } else if (fs.existsSync(path4)) {
                  iconPath = path4;
                } else {
                  // 性能最低的方式
                  const resourceList = fs.readdirSync(path.join(searchPath, `${appName}/Contents/Resources`));
                  const iconName = resourceList.filter(file => path.extname(file) === '.icns')[0];
                  iconPath = path.join(searchPath, `${appName}/Contents/Resources/${iconName}`);
                }
                
                // 创建图片
                nativeImage.createThumbnailFromPath(iconPath, {width: 64, height: 64}).then(img => {
                  // 创建搜索项
                  fileLists.push({
                    name: appSubStr,
                    value: 'plugin',
                    icon: img.toDataURL(),
                    desc: path.join(searchPath, appName),
                    type: 'app',
                    action: `open ${path.join(searchPath, appName).replace(' ', '\\ ')}`
                  })
                })
              } catch (e) {
              }
    
            }
          }
        } catch (e) {
          console.log(e);
        }
      });
    });
    

    代码看的有点多,其实很简单,主要也是几步走:

    1. 根据定义好的路径查找所有 app 和 PreferencePanes
    2. 应为下拉选项需要展示插件的 icon 所以对于 app 和 PreferencePanes 需要查找 icns
    3. 根据默认规则查找 icns 如果找不到再用性能较低的方式模糊匹配
    4. 检索成功后设置好下拉选项

    最后一步就是点击呼出了:

    openPlugin() {
        if (plugin.type === 'app') {
          // 呼出 app
          execSync(plugin.action);
          commit('commonUpdate', {
            selected: null,
            showMain: false,
            options: [],
            searchValue: '',
          });
          ipcRenderer.send('changeWindowSize-rubick', {
            height: getWindowHeight([]),
          });
          return;
        }
    
    }
    

    最后来看一下系统app检索效果:

    徒手撸一个 utools 系列(二)

    结语

    本篇主要介绍如何实现一个类似于 utools 的插件搜索功能,当然这远远不是 utools 的全部,下期我们再继续介绍如何实现 utools 其他能力。欢迎大家前往体验 Rubick 有问题可以随时提 issue 我们会及时反馈。

    另外,如果觉得设计实现思路对你有用,也欢迎给个 Star:github.com/clouDr-f2e/…


    起源地下载网 » 徒手撸一个 utools 系列(二)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元